]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - windows/winutils.c
VC didn't like PATH_MAX. Use MAX_PATH instead. (This macro is mentioned in
[PuTTY.git] / windows / winutils.c
1 /*
2  * winutils.c: miscellaneous Windows utilities for GUI apps
3  */
4
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <ctype.h>
8
9 #include "winstuff.h"
10 #include "misc.h"
11
12 #ifdef TESTMODE
13 /* Definitions to allow this module to be compiled standalone for testing
14  * split_into_argv(). */
15 #define smalloc malloc
16 #define srealloc realloc
17 #define sfree free
18 #endif
19
20 /*
21  * GetOpenFileName/GetSaveFileName tend to muck around with the process'
22  * working directory on at least some versions of Windows.
23  * Here's a wrapper that gives more control over this, and hides a little
24  * bit of other grottiness.
25  */
26
27 struct filereq_tag {
28     TCHAR cwd[MAX_PATH];
29 };
30
31 /*
32  * `of' is expected to be initialised with most interesting fields, but
33  * this function does some administrivia. (assume `of' was memset to 0)
34  * save==1 -> GetSaveFileName; save==0 -> GetOpenFileName
35  * `state' is optional.
36  */
37 BOOL request_file(filereq *state, OPENFILENAME *of, int preserve, int save)
38 {
39     TCHAR cwd[MAX_PATH]; /* process CWD */
40     BOOL ret;
41
42     /* Get process CWD */
43     if (preserve) {
44         DWORD r = GetCurrentDirectory(lenof(cwd), cwd);
45         if (r == 0 || r >= lenof(cwd))
46             /* Didn't work, oh well. Stop trying to be clever. */
47             preserve = 0;
48     }
49
50     /* Open the file requester, maybe setting lpstrInitialDir */
51     {
52 #ifdef OPENFILENAME_SIZE_VERSION_400
53         of->lStructSize = OPENFILENAME_SIZE_VERSION_400;
54 #else
55         of->lStructSize = sizeof(*of);
56 #endif
57         of->lpstrInitialDir = (state && state->cwd[0]) ? state->cwd : NULL;
58         /* Actually put up the requester. */
59         ret = save ? GetSaveFileName(of) : GetOpenFileName(of);
60     }
61
62     /* Get CWD left by requester */
63     if (state) {
64         DWORD r = GetCurrentDirectory(lenof(state->cwd), state->cwd);
65         if (r == 0 || r >= lenof(state->cwd))
66             /* Didn't work, oh well. */
67             state->cwd[0] = '\0';
68     }
69     
70     /* Restore process CWD */
71     if (preserve)
72         /* If it fails, there's not much we can do. */
73         (void) SetCurrentDirectory(cwd);
74
75     return ret;
76 }
77
78 filereq *filereq_new(void)
79 {
80     filereq *ret = snew(filereq);
81     ret->cwd[0] = '\0';
82     return ret;
83 }
84
85 void filereq_free(filereq *state)
86 {
87     sfree(state);
88 }
89
90 /*
91  * Message box with optional context help.
92  */
93
94 /* Callback function to launch context help. */
95 static VOID CALLBACK message_box_help_callback(LPHELPINFO lpHelpInfo)
96 {
97     if (help_path) {
98         char *context = NULL;
99 #define CHECK_CTX(name) \
100         do { \
101             if (lpHelpInfo->dwContextId == WINHELP_CTXID_ ## name) \
102                 context = WINHELP_CTX_ ## name; \
103         } while (0)
104         CHECK_CTX(errors_hostkey_absent);
105         CHECK_CTX(errors_hostkey_changed);
106         CHECK_CTX(errors_cantloadkey);
107         CHECK_CTX(option_cleanup);
108 #undef CHECK_CTX
109         if (context) {
110             /* We avoid using malloc, in case we're in a situation where
111              * it would be awkward to do so. */
112             char cmd[WINHELP_CTX_MAXLEN+10];
113             sprintf(cmd, "JI(`',`%.*s')", WINHELP_CTX_MAXLEN, context);
114             WinHelp(hwnd, help_path, HELP_COMMAND, (DWORD)cmd);
115             requested_help = TRUE;
116         }
117     }
118 }
119
120 int message_box(LPCTSTR text, LPCTSTR caption, DWORD style, DWORD helpctxid)
121 {
122     MSGBOXPARAMS mbox;
123     
124     /*
125      * We use MessageBoxIndirect() because it allows us to specify a
126      * callback function for the Help button.
127      */
128     mbox.cbSize = sizeof(mbox);
129     /* Assumes the globals `hinst' and `hwnd' have sensible values. */
130     mbox.hInstance = hinst;
131     mbox.hwndOwner = hwnd;
132     mbox.lpfnMsgBoxCallback = &message_box_help_callback;
133     mbox.dwLanguageId = LANG_NEUTRAL;
134     mbox.lpszText = text;
135     mbox.lpszCaption = caption;
136     mbox.dwContextHelpId = helpctxid;
137     mbox.dwStyle = style;
138     if (helpctxid != 0 && help_path) mbox.dwStyle |= MB_HELP;
139     return MessageBoxIndirect(&mbox);
140 }
141
142 /*
143  * Split a complete command line into argc/argv, attempting to do
144  * it exactly the same way Windows itself would do it (so that
145  * console utilities, which receive argc and argv from Windows,
146  * will have their command lines processed in the same way as GUI
147  * utilities which get a whole command line and must break it
148  * themselves).
149  * 
150  * Does not modify the input command line.
151  * 
152  * The final parameter (argstart) is used to return a second array
153  * of char * pointers, the same length as argv, each one pointing
154  * at the start of the corresponding element of argv in the
155  * original command line. So if you get half way through processing
156  * your command line in argc/argv form and then decide you want to
157  * treat the rest as a raw string, you can. If you don't want to,
158  * `argstart' can be safely left NULL.
159  */
160 void split_into_argv(char *cmdline, int *argc, char ***argv,
161                      char ***argstart)
162 {
163     char *p;
164     char *outputline, *q;
165     char **outputargv, **outputargstart;
166     int outputargc;
167
168     /*
169      * At first glance the rules appeared to be:
170      *
171      *  - Single quotes are not special characters.
172      *
173      *  - Double quotes are removed, but within them spaces cease
174      *    to be special.
175      *
176      *  - Backslashes are _only_ special when a sequence of them
177      *    appear just before a double quote. In this situation,
178      *    they are treated like C backslashes: so \" just gives a
179      *    literal quote, \\" gives a literal backslash and then
180      *    opens or closes a double-quoted segment, \\\" gives a
181      *    literal backslash and then a literal quote, \\\\" gives
182      *    two literal backslashes and then opens/closes a
183      *    double-quoted segment, and so forth. Note that this
184      *    behaviour is identical inside and outside double quotes.
185      *
186      *  - Two successive double quotes become one literal double
187      *    quote, but only _inside_ a double-quoted segment.
188      *    Outside, they just form an empty double-quoted segment
189      *    (which may cause an empty argument word).
190      *
191      *  - That only leaves the interesting question of what happens
192      *    when one or more backslashes precedes two or more double
193      *    quotes, starting inside a double-quoted string. And the
194      *    answer to that appears somewhat bizarre. Here I tabulate
195      *    number of backslashes (across the top) against number of
196      *    quotes (down the left), and indicate how many backslashes
197      *    are output, how many quotes are output, and whether a
198      *    quoted segment is open at the end of the sequence:
199      * 
200      *                      backslashes
201      * 
202      *               0         1      2      3      4
203      * 
204      *         0   0,0,y  |  1,0,y  2,0,y  3,0,y  4,0,y
205      *            --------+-----------------------------
206      *         1   0,0,n  |  0,1,y  1,0,n  1,1,y  2,0,n
207      *    q    2   0,1,n  |  0,1,n  1,1,n  1,1,n  2,1,n
208      *    u    3   0,1,y  |  0,2,n  1,1,y  1,2,n  2,1,y
209      *    o    4   0,1,n  |  0,2,y  1,1,n  1,2,y  2,1,n
210      *    t    5   0,2,n  |  0,2,n  1,2,n  1,2,n  2,2,n
211      *    e    6   0,2,y  |  0,3,n  1,2,y  1,3,n  2,2,y
212      *    s    7   0,2,n  |  0,3,y  1,2,n  1,3,y  2,2,n
213      *         8   0,3,n  |  0,3,n  1,3,n  1,3,n  2,3,n
214      *         9   0,3,y  |  0,4,n  1,3,y  1,4,n  2,3,y
215      *        10   0,3,n  |  0,4,y  1,3,n  1,4,y  2,3,n
216      *        11   0,4,n  |  0,4,n  1,4,n  1,4,n  2,4,n
217      * 
218      * 
219      *      [Test fragment was of the form "a\\\"""b c" d.]
220      * 
221      * There is very weird mod-3 behaviour going on here in the
222      * number of quotes, and it even applies when there aren't any
223      * backslashes! How ghastly.
224      * 
225      * With a bit of thought, this extremely odd diagram suddenly
226      * coalesced itself into a coherent, if still ghastly, model of
227      * how things work:
228      * 
229      *  - As before, backslashes are only special when one or more
230      *    of them appear contiguously before at least one double
231      *    quote. In this situation the backslashes do exactly what
232      *    you'd expect: each one quotes the next thing in front of
233      *    it, so you end up with n/2 literal backslashes (if n is
234      *    even) or (n-1)/2 literal backslashes and a literal quote
235      *    (if n is odd). In the latter case the double quote
236      *    character right after the backslashes is used up.
237      * 
238      *  - After that, any remaining double quotes are processed. A
239      *    string of contiguous unescaped double quotes has a mod-3
240      *    behaviour:
241      * 
242      *     * inside a quoted segment, a quote ends the segment.
243      *     * _immediately_ after ending a quoted segment, a quote
244      *       simply produces a literal quote.
245      *     * otherwise, outside a quoted segment, a quote begins a
246      *       quoted segment.
247      * 
248      *    So, for example, if we started inside a quoted segment
249      *    then two contiguous quotes would close the segment and
250      *    produce a literal quote; three would close the segment,
251      *    produce a literal quote, and open a new segment. If we
252      *    started outside a quoted segment, then two contiguous
253      *    quotes would open and then close a segment, producing no
254      *    output (but potentially creating a zero-length argument);
255      *    but three quotes would open and close a segment and then
256      *    produce a literal quote.
257      */
258
259     /*
260      * First deal with the simplest of all special cases: if there
261      * aren't any arguments, return 0,NULL,NULL.
262      */
263     while (*cmdline && isspace(*cmdline)) cmdline++;
264     if (!*cmdline) {
265         if (argc) *argc = 0;
266         if (argv) *argv = NULL;
267         if (argstart) *argstart = NULL;
268         return;
269     }
270
271     /*
272      * This will guaranteeably be big enough; we can realloc it
273      * down later.
274      */
275     outputline = snewn(1+strlen(cmdline), char);
276     outputargv = snewn(strlen(cmdline)+1 / 2, char *);
277     outputargstart = snewn(strlen(cmdline)+1 / 2, char *);
278
279     p = cmdline; q = outputline; outputargc = 0;
280
281     while (*p) {
282         int quote;
283
284         /* Skip whitespace searching for start of argument. */
285         while (*p && isspace(*p)) p++;
286         if (!*p) break;
287
288         /* We have an argument; start it. */
289         outputargv[outputargc] = q;
290         outputargstart[outputargc] = p;
291         outputargc++;
292         quote = 0;
293
294         /* Copy data into the argument until it's finished. */
295         while (*p) {
296             if (!quote && isspace(*p))
297                 break;                 /* argument is finished */
298
299             if (*p == '"' || *p == '\\') {
300                 /*
301                  * We have a sequence of zero or more backslashes
302                  * followed by a sequence of zero or more quotes.
303                  * Count up how many of each, and then deal with
304                  * them as appropriate.
305                  */
306                 int i, slashes = 0, quotes = 0;
307                 while (*p == '\\') slashes++, p++;
308                 while (*p == '"') quotes++, p++;
309
310                 if (!quotes) {
311                     /*
312                      * Special case: if there are no quotes,
313                      * slashes are not special at all, so just copy
314                      * n slashes to the output string.
315                      */
316                     while (slashes--) *q++ = '\\';
317                 } else {
318                     /* Slashes annihilate in pairs. */
319                     while (slashes >= 2) slashes -= 2, *q++ = '\\';
320
321                     /* One remaining slash takes out the first quote. */
322                     if (slashes) quotes--, *q++ = '"';
323
324                     if (quotes > 0) {
325                         /* Outside a quote segment, a quote starts one. */
326                         if (!quote) quotes--, quote = 1;
327
328                         /* Now we produce (n+1)/3 literal quotes... */
329                         for (i = 3; i <= quotes+1; i += 3) *q++ = '"';
330
331                         /* ... and end in a quote segment iff 3 divides n. */
332                         quote = (quotes % 3 == 0);
333                     }
334                 }
335             } else {
336                 *q++ = *p++;
337             }
338         }
339
340         /* At the end of an argument, just append a trailing NUL. */
341         *q++ = '\0';
342     }
343
344     outputargv = sresize(outputargv, outputargc, char *);
345     outputargstart = sresize(outputargstart, outputargc, char *);
346
347     if (argc) *argc = outputargc;
348     if (argv) *argv = outputargv; else sfree(outputargv);
349     if (argstart) *argstart = outputargstart; else sfree(outputargstart);
350 }
351
352 #ifdef TESTMODE
353
354 const struct argv_test {
355     const char *cmdline;
356     const char *argv[10];
357 } argv_tests[] = {
358     /*
359      * We generate this set of tests by invoking ourself with
360      * `-generate'.
361      */
362     {"ab c\" d", {"ab", "c d", NULL}},
363     {"a\"b c\" d", {"ab c", "d", NULL}},
364     {"a\"\"b c\" d", {"ab", "c d", NULL}},
365     {"a\"\"\"b c\" d", {"a\"b", "c d", NULL}},
366     {"a\"\"\"\"b c\" d", {"a\"b c", "d", NULL}},
367     {"a\"\"\"\"\"b c\" d", {"a\"b", "c d", NULL}},
368     {"a\"\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}},
369     {"a\"\"\"\"\"\"\"b c\" d", {"a\"\"b c", "d", NULL}},
370     {"a\"\"\"\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}},
371     {"a\\b c\" d", {"a\\b", "c d", NULL}},
372     {"a\\\"b c\" d", {"a\"b", "c d", NULL}},
373     {"a\\\"\"b c\" d", {"a\"b c", "d", NULL}},
374     {"a\\\"\"\"b c\" d", {"a\"b", "c d", NULL}},
375     {"a\\\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}},
376     {"a\\\"\"\"\"\"b c\" d", {"a\"\"b c", "d", NULL}},
377     {"a\\\"\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}},
378     {"a\\\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}},
379     {"a\\\"\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b c", "d", NULL}},
380     {"a\\\\b c\" d", {"a\\\\b", "c d", NULL}},
381     {"a\\\\\"b c\" d", {"a\\b c", "d", NULL}},
382     {"a\\\\\"\"b c\" d", {"a\\b", "c d", NULL}},
383     {"a\\\\\"\"\"b c\" d", {"a\\\"b", "c d", NULL}},
384     {"a\\\\\"\"\"\"b c\" d", {"a\\\"b c", "d", NULL}},
385     {"a\\\\\"\"\"\"\"b c\" d", {"a\\\"b", "c d", NULL}},
386     {"a\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}},
387     {"a\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}},
388     {"a\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}},
389     {"a\\\\\\b c\" d", {"a\\\\\\b", "c d", NULL}},
390     {"a\\\\\\\"b c\" d", {"a\\\"b", "c d", NULL}},
391     {"a\\\\\\\"\"b c\" d", {"a\\\"b c", "d", NULL}},
392     {"a\\\\\\\"\"\"b c\" d", {"a\\\"b", "c d", NULL}},
393     {"a\\\\\\\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}},
394     {"a\\\\\\\"\"\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}},
395     {"a\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}},
396     {"a\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}},
397     {"a\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b c", "d", NULL}},
398     {"a\\\\\\\\b c\" d", {"a\\\\\\\\b", "c d", NULL}},
399     {"a\\\\\\\\\"b c\" d", {"a\\\\b c", "d", NULL}},
400     {"a\\\\\\\\\"\"b c\" d", {"a\\\\b", "c d", NULL}},
401     {"a\\\\\\\\\"\"\"b c\" d", {"a\\\\\"b", "c d", NULL}},
402     {"a\\\\\\\\\"\"\"\"b c\" d", {"a\\\\\"b c", "d", NULL}},
403     {"a\\\\\\\\\"\"\"\"\"b c\" d", {"a\\\\\"b", "c d", NULL}},
404     {"a\\\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b", "c d", NULL}},
405     {"a\\\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b c", "d", NULL}},
406     {"a\\\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b", "c d", NULL}},
407     {"\"ab c\" d", {"ab c", "d", NULL}},
408     {"\"a\"b c\" d", {"ab", "c d", NULL}},
409     {"\"a\"\"b c\" d", {"a\"b", "c d", NULL}},
410     {"\"a\"\"\"b c\" d", {"a\"b c", "d", NULL}},
411     {"\"a\"\"\"\"b c\" d", {"a\"b", "c d", NULL}},
412     {"\"a\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}},
413     {"\"a\"\"\"\"\"\"b c\" d", {"a\"\"b c", "d", NULL}},
414     {"\"a\"\"\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}},
415     {"\"a\"\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}},
416     {"\"a\\b c\" d", {"a\\b c", "d", NULL}},
417     {"\"a\\\"b c\" d", {"a\"b c", "d", NULL}},
418     {"\"a\\\"\"b c\" d", {"a\"b", "c d", NULL}},
419     {"\"a\\\"\"\"b c\" d", {"a\"\"b", "c d", NULL}},
420     {"\"a\\\"\"\"\"b c\" d", {"a\"\"b c", "d", NULL}},
421     {"\"a\\\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}},
422     {"\"a\\\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}},
423     {"\"a\\\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b c", "d", NULL}},
424     {"\"a\\\"\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}},
425     {"\"a\\\\b c\" d", {"a\\\\b c", "d", NULL}},
426     {"\"a\\\\\"b c\" d", {"a\\b", "c d", NULL}},
427     {"\"a\\\\\"\"b c\" d", {"a\\\"b", "c d", NULL}},
428     {"\"a\\\\\"\"\"b c\" d", {"a\\\"b c", "d", NULL}},
429     {"\"a\\\\\"\"\"\"b c\" d", {"a\\\"b", "c d", NULL}},
430     {"\"a\\\\\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}},
431     {"\"a\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}},
432     {"\"a\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}},
433     {"\"a\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}},
434     {"\"a\\\\\\b c\" d", {"a\\\\\\b c", "d", NULL}},
435     {"\"a\\\\\\\"b c\" d", {"a\\\"b c", "d", NULL}},
436     {"\"a\\\\\\\"\"b c\" d", {"a\\\"b", "c d", NULL}},
437     {"\"a\\\\\\\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}},
438     {"\"a\\\\\\\"\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}},
439     {"\"a\\\\\\\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}},
440     {"\"a\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}},
441     {"\"a\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b c", "d", NULL}},
442     {"\"a\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}},
443     {"\"a\\\\\\\\b c\" d", {"a\\\\\\\\b c", "d", NULL}},
444     {"\"a\\\\\\\\\"b c\" d", {"a\\\\b", "c d", NULL}},
445     {"\"a\\\\\\\\\"\"b c\" d", {"a\\\\\"b", "c d", NULL}},
446     {"\"a\\\\\\\\\"\"\"b c\" d", {"a\\\\\"b c", "d", NULL}},
447     {"\"a\\\\\\\\\"\"\"\"b c\" d", {"a\\\\\"b", "c d", NULL}},
448     {"\"a\\\\\\\\\"\"\"\"\"b c\" d", {"a\\\\\"\"b", "c d", NULL}},
449     {"\"a\\\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b c", "d", NULL}},
450     {"\"a\\\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b", "c d", NULL}},
451     {"\"a\\\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"\"b", "c d", NULL}},
452 };
453
454 int main(int argc, char **argv)
455 {
456     int i, j;
457
458     if (argc > 1) {
459         /*
460          * Generation of tests.
461          * 
462          * Given `-splat <args>', we print out a C-style
463          * representation of each argument (in the form "a", "b",
464          * NULL), backslash-escaping each backslash and double
465          * quote.
466          * 
467          * Given `-split <string>', we first doctor `string' by
468          * turning forward slashes into backslashes, single quotes
469          * into double quotes and underscores into spaces; and then
470          * we feed the resulting string to ourself with `-splat'.
471          * 
472          * Given `-generate', we concoct a variety of fun test
473          * cases, encode them in quote-safe form (mapping \, " and
474          * space to /, ' and _ respectively) and feed each one to
475          * `-split'.
476          */
477         if (!strcmp(argv[1], "-splat")) {
478             int i;
479             char *p;
480             for (i = 2; i < argc; i++) {
481                 putchar('"');
482                 for (p = argv[i]; *p; p++) {
483                     if (*p == '\\' || *p == '"')
484                         putchar('\\');
485                     putchar(*p);
486                 }
487                 printf("\", ");
488             }
489             printf("NULL");
490             return 0;
491         }
492
493         if (!strcmp(argv[1], "-split") && argc > 2) {
494             char *str = malloc(20 + strlen(argv[0]) + strlen(argv[2]));
495             char *p, *q;
496
497             q = str + sprintf(str, "%s -splat ", argv[0]);
498             printf("    {\"");
499             for (p = argv[2]; *p; p++, q++) {
500                 switch (*p) {
501                   case '/':  printf("\\\\"); *q = '\\'; break;
502                   case '\'': printf("\\\""); *q = '"';  break;
503                   case '_':  printf(" ");    *q = ' ';  break;
504                   default:   putchar(*p);    *q = *p;   break;
505                 }
506             }
507             *p = '\0';
508             printf("\", {");
509             fflush(stdout);
510
511             system(str);
512
513             printf("}},\n");
514
515             return 0;
516         }
517
518         if (!strcmp(argv[1], "-generate")) {
519             char *teststr, *p;
520             int i, initialquote, backslashes, quotes;
521
522             teststr = malloc(200 + strlen(argv[0]));
523
524             for (initialquote = 0; initialquote <= 1; initialquote++) {
525                 for (backslashes = 0; backslashes < 5; backslashes++) {
526                     for (quotes = 0; quotes < 9; quotes++) {
527                         p = teststr + sprintf(teststr, "%s -split ", argv[0]);
528                         if (initialquote) *p++ = '\'';
529                         *p++ = 'a';
530                         for (i = 0; i < backslashes; i++) *p++ = '/';
531                         for (i = 0; i < quotes; i++) *p++ = '\'';
532                         *p++ = 'b';
533                         *p++ = '_';
534                         *p++ = 'c';
535                         *p++ = '\'';
536                         *p++ = '_';
537                         *p++ = 'd';
538                         *p = '\0';
539
540                         system(teststr);
541                     }
542                 }
543             }
544             return 0;
545         }
546
547         fprintf(stderr, "unrecognised option: \"%s\"\n", argv[1]);
548         return 1;
549     }
550
551     /*
552      * If we get here, we were invoked with no arguments, so just
553      * run the tests.
554      */
555
556     for (i = 0; i < lenof(argv_tests); i++) {
557         int ac;
558         char **av;
559
560         split_into_argv(argv_tests[i].cmdline, &ac, &av);
561
562         for (j = 0; j < ac && argv_tests[i].argv[j]; j++) {
563             if (strcmp(av[j], argv_tests[i].argv[j])) {
564                 printf("failed test %d (|%s|) arg %d: |%s| should be |%s|\n",
565                        i, argv_tests[i].cmdline,
566                        j, av[j], argv_tests[i].argv[j]);
567             }
568 #ifdef VERBOSE
569             else {
570                 printf("test %d (|%s|) arg %d: |%s| == |%s|\n",
571                        i, argv_tests[i].cmdline,
572                        j, av[j], argv_tests[i].argv[j]);
573             }
574 #endif
575         }
576         if (j < ac)
577             printf("failed test %d (|%s|): %d args returned, should be %d\n",
578                    i, argv_tests[i].cmdline, ac, j);
579         if (argv_tests[i].argv[j])
580             printf("failed test %d (|%s|): %d args returned, should be more\n",
581                    i, argv_tests[i].cmdline, ac);
582     }
583
584     return 0;
585 }
586
587 #endif