]>
Commit | Line | Data |
---|---|---|
5958d7c0 | 1 | /* |
a40a14f8 | 2 | * Copyright (c) 2000-2009 Apple Inc. All rights reserved. |
5958d7c0 A |
3 | * |
4 | * @APPLE_LICENSE_HEADER_START@ | |
009ee3c6 | 5 | * |
009ee3c6 A |
6 | * This file contains Original Code and/or Modifications of Original Code |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. Please obtain a copy of the License at | |
10 | * http://www.opensource.apple.com/apsl/ and read it before using this | |
11 | * file. | |
12 | * | |
13 | * The Original Code and all software distributed under the License are | |
14 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
5958d7c0 A |
15 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, |
16 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
009ee3c6 A |
17 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. |
18 | * Please see the License for the specific language governing rights and | |
19 | * limitations under the License. | |
20 | * | |
5958d7c0 A |
21 | * @APPLE_LICENSE_HEADER_END@ |
22 | */ | |
23 | ||
0fae82ee A |
24 | /* |
25 | * Modification History | |
26 | * | |
edebe297 A |
27 | * June 13, 2005 Allan Nathanson <ajn@apple.com> |
28 | * - added SCPreferences support | |
29 | * | |
dbf6a266 A |
30 | * August 4, 2004 Allan Nathanson <ajn@apple.com> |
31 | * - added network configuration (prefs) support | |
32 | * | |
009ee3c6 A |
33 | * September 25, 2002 Allan Nathanson <ajn@apple.com> |
34 | * - added command line history & editing | |
35 | * | |
0fae82ee A |
36 | * July 9, 2001 Allan Nathanson <ajn@apple.com> |
37 | * - added "-r" option for checking network reachability | |
38 | * - added "-w" option to check/wait for the presence of a | |
39 | * dynamic store key. | |
40 | * | |
41 | * June 1, 2001 Allan Nathanson <ajn@apple.com> | |
42 | * - public API conversion | |
43 | * | |
44 | * November 9, 2000 Allan Nathanson <ajn@apple.com> | |
45 | * - initial revision | |
46 | */ | |
47 | ||
5958d7c0 | 48 | #include <ctype.h> |
009ee3c6 | 49 | #include <getopt.h> |
5958d7c0 A |
50 | #include <stdio.h> |
51 | #include <stdlib.h> | |
52 | #include <string.h> | |
009ee3c6 | 53 | #include <termios.h> |
5958d7c0 A |
54 | #include <unistd.h> |
55 | #include <sysexits.h> | |
56 | ||
57 | #ifdef DEBUG | |
58 | #include <mach/mach.h> | |
59 | #include <mach/mach_error.h> | |
60 | #endif /* DEBUG */ | |
61 | ||
62 | #include "scutil.h" | |
5958d7c0 A |
63 | #include "commands.h" |
64 | #include "dictionary.h" | |
dbf6a266 | 65 | #include "net.h" |
009ee3c6 | 66 | #include "prefs.h" |
dbf6a266 A |
67 | #include "session.h" |
68 | #include "tests.h" | |
0fae82ee | 69 | |
5958d7c0 A |
70 | |
71 | #define LINE_LENGTH 256 | |
72 | ||
0fae82ee | 73 | |
edebe297 | 74 | __private_extern__ AuthorizationRef authorization = NULL; |
dbf6a266 A |
75 | __private_extern__ InputRef currentInput = NULL; |
76 | __private_extern__ int nesting = 0; | |
77 | __private_extern__ CFRunLoopRef notifyRl = NULL; | |
78 | __private_extern__ CFRunLoopSourceRef notifyRls = NULL; | |
79 | __private_extern__ SCPreferencesRef prefs = NULL; | |
80 | __private_extern__ SCDynamicStoreRef store = NULL; | |
81 | __private_extern__ CFPropertyListRef value = NULL; | |
82 | __private_extern__ CFMutableArrayRef watchedKeys = NULL; | |
83 | __private_extern__ CFMutableArrayRef watchedPatterns = NULL; | |
009ee3c6 | 84 | |
dbf6a266 | 85 | static const struct option longopts[] = { |
009ee3c6 A |
86 | // { "debug", no_argument, NULL, 'd' }, |
87 | // { "verbose", no_argument, NULL, 'v' }, | |
88 | // { "SPI", no_argument, NULL, 'p' }, | |
dbf6a266 | 89 | // { "check-reachability", required_argument, NULL, 'r' }, |
009ee3c6 A |
90 | // { "timeout", required_argument, NULL, 't' }, |
91 | // { "wait-key", required_argument, NULL, 'w' }, | |
dbf6a266 | 92 | { "dns", no_argument, NULL, 0 }, |
009ee3c6 A |
93 | { "get", required_argument, NULL, 0 }, |
94 | { "help", no_argument, NULL, '?' }, | |
dbf6a266 | 95 | { "net", no_argument, NULL, 0 }, |
edebe297 | 96 | { "prefs", no_argument, NULL, 0 }, |
dbf6a266 | 97 | { "proxy", no_argument, NULL, 0 }, |
009ee3c6 | 98 | { "set", required_argument, NULL, 0 }, |
dbf6a266 | 99 | { NULL, 0, NULL, 0 } |
009ee3c6 | 100 | }; |
5958d7c0 A |
101 | |
102 | ||
103 | static char * | |
009ee3c6 | 104 | getLine(char *buf, int len, InputRef src) |
5958d7c0 | 105 | { |
009ee3c6 | 106 | int n; |
5958d7c0 | 107 | |
009ee3c6 A |
108 | if (src->el) { |
109 | int count; | |
110 | const char *line; | |
5958d7c0 | 111 | |
009ee3c6 A |
112 | line = el_gets(src->el, &count); |
113 | if (line == NULL) | |
114 | return NULL; | |
115 | ||
116 | strncpy(buf, line, len); | |
5958d7c0 | 117 | } else { |
009ee3c6 A |
118 | if (fgets(buf, len, src->fp) == NULL) |
119 | return NULL; | |
120 | } | |
121 | ||
122 | n = strlen(buf); | |
123 | if (buf[n-1] == '\n') { | |
124 | /* the entire line fit in the buffer, remove the newline */ | |
125 | buf[n-1] = '\0'; | |
126 | } else if (!src->el) { | |
5958d7c0 A |
127 | /* eat the remainder of the line */ |
128 | do { | |
009ee3c6 A |
129 | n = fgetc(src->fp); |
130 | } while ((n != '\n') && (n != EOF)); | |
131 | } | |
132 | ||
133 | if (src->h) { | |
134 | HistEvent ev; | |
135 | ||
136 | history(src->h, &ev, H_ENTER, buf); | |
5958d7c0 A |
137 | } |
138 | ||
ba83da55 | 139 | |
5958d7c0 A |
140 | return buf; |
141 | } | |
142 | ||
143 | ||
dbf6a266 | 144 | static char * |
5958d7c0 A |
145 | getString(char **line) |
146 | { | |
147 | char *s, *e, c, *string; | |
148 | int i, isQuoted = 0, escaped = 0; | |
149 | ||
150 | if (*line == NULL) return NULL; | |
151 | if (**line == '\0') return NULL; | |
152 | ||
153 | /* Skip leading white space */ | |
154 | while (isspace(**line)) *line += 1; | |
155 | ||
156 | /* Grab the next string */ | |
157 | s = *line; | |
158 | if (*s == '\0') { | |
159 | return NULL; /* no string available */ | |
160 | } else if (*s == '"') { | |
161 | isQuoted = 1; /* it's a quoted string */ | |
162 | s++; | |
163 | } | |
164 | ||
165 | for (e = s; (c = *e) != '\0'; e++) { | |
166 | if (isQuoted && (c == '"')) | |
167 | break; /* end of quoted string */ | |
168 | if (c == '\\') { | |
169 | e++; | |
170 | if (*e == '\0') | |
171 | break; /* if premature end-of-string */ | |
172 | if ((*e == '"') || isspace(*e)) | |
173 | escaped++; /* if escaped quote or white space */ | |
174 | } | |
175 | if (!isQuoted && isspace(c)) | |
176 | break; /* end of non-quoted string */ | |
177 | } | |
178 | ||
179 | string = malloc(e - s - escaped + 1); | |
180 | ||
181 | for (i = 0; s < e; s++) { | |
182 | string[i] = *s; | |
183 | if (!((s[0] == '\\') && ((s[1] == '"') || isspace(s[1])))) i++; | |
184 | } | |
185 | string[i] = '\0'; | |
186 | ||
187 | if (isQuoted) | |
188 | e++; /* move past end of quoted string */ | |
189 | ||
190 | *line = e; | |
191 | return string; | |
192 | } | |
193 | ||
194 | ||
dbf6a266 | 195 | __private_extern__ |
0fae82ee | 196 | Boolean |
009ee3c6 | 197 | process_line(InputRef src) |
5958d7c0 | 198 | { |
dbf6a266 A |
199 | char *arg; |
200 | int argc = 0; | |
201 | char **argv = NULL; | |
202 | int i; | |
203 | char line[LINE_LENGTH]; | |
204 | char *s = line; | |
205 | ||
206 | // if end-of-file, exit | |
009ee3c6 | 207 | if (getLine(line, sizeof(line), src) == NULL) |
5958d7c0 A |
208 | return FALSE; |
209 | ||
0fae82ee | 210 | if (nesting > 0) { |
a5f60add | 211 | SCPrint(TRUE, stdout, CFSTR("%d> %s\n"), nesting, line); |
5958d7c0 A |
212 | } |
213 | ||
dbf6a266 | 214 | // break up the input line |
5958d7c0 A |
215 | while ((arg = getString(&s)) != NULL) { |
216 | if (argc == 0) | |
217 | argv = (char **)malloc(2 * sizeof(char *)); | |
218 | else | |
dbf6a266 | 219 | argv = (char **)reallocf(argv, ((argc + 2) * sizeof(char *))); |
5958d7c0 A |
220 | argv[argc++] = arg; |
221 | } | |
222 | ||
dbf6a266 A |
223 | if (argc == 0) { |
224 | return TRUE; // if no arguments | |
225 | } | |
5958d7c0 | 226 | |
dbf6a266 A |
227 | /* process the command */ |
228 | if (*argv[0] != '#') { | |
229 | argv[argc] = NULL; // just in case... | |
230 | currentInput = src; | |
231 | do_command(argc, argv); | |
232 | } | |
5958d7c0 | 233 | |
dbf6a266 A |
234 | /* free the arguments */ |
235 | for (i = 0; i < argc; i++) { | |
236 | free(argv[i]); | |
5958d7c0 | 237 | } |
dbf6a266 | 238 | free(argv); |
5958d7c0 | 239 | |
dbf6a266 | 240 | return !termRequested; |
5958d7c0 A |
241 | } |
242 | ||
243 | ||
dbf6a266 | 244 | static void |
0fae82ee A |
245 | usage(const char *command) |
246 | { | |
247 | SCPrint(TRUE, stderr, CFSTR("usage: %s\n"), command); | |
009ee3c6 A |
248 | SCPrint(TRUE, stderr, CFSTR("\tinteractive access to the dynamic store.\n")); |
249 | SCPrint(TRUE, stderr, CFSTR("\n")); | |
edebe297 A |
250 | SCPrint(TRUE, stderr, CFSTR(" or: %s --prefs\n"), command); |
251 | SCPrint(TRUE, stderr, CFSTR("\tinteractive access to the [raw] stored preferences.\n")); | |
252 | SCPrint(TRUE, stderr, CFSTR("\n")); | |
a40a14f8 A |
253 | SCPrint(TRUE, stderr, CFSTR(" or: %s [-W] -r nodename\n"), command); |
254 | SCPrint(TRUE, stderr, CFSTR(" or: %s [-W] -r address\n"), command); | |
255 | SCPrint(TRUE, stderr, CFSTR(" or: %s [-W] -r local-address remote-address\n"), command); | |
256 | SCPrint(TRUE, stderr, CFSTR("\tcheck reachability of node, address, or address pair (-W to \"watch\").\n")); | |
009ee3c6 | 257 | SCPrint(TRUE, stderr, CFSTR("\n")); |
0fae82ee A |
258 | SCPrint(TRUE, stderr, CFSTR(" or: %s -w dynamic-store-key [ -t timeout ]\n"), command); |
259 | SCPrint(TRUE, stderr, CFSTR("\t-w\twait for presense of dynamic store key\n")); | |
260 | SCPrint(TRUE, stderr, CFSTR("\t-t\ttime to wait for key\n")); | |
261 | SCPrint(TRUE, stderr, CFSTR("\n")); | |
009ee3c6 A |
262 | SCPrint(TRUE, stderr, CFSTR(" or: %s --get pref\n"), command); |
263 | SCPrint(TRUE, stderr, CFSTR(" or: %s --set pref [newval]\n"), command); | |
264 | SCPrint(TRUE, stderr, CFSTR("\tpref\tdisplay (or set) the specified preference. Valid preferences\n")); | |
265 | SCPrint(TRUE, stderr, CFSTR("\t\tinclude:\n")); | |
edebe297 | 266 | SCPrint(TRUE, stderr, CFSTR("\t\t\tComputerName, LocalHostName, HostName\n")); |
009ee3c6 A |
267 | SCPrint(TRUE, stderr, CFSTR("\tnewval\tNew preference value to be set. If not specified,\n")); |
268 | SCPrint(TRUE, stderr, CFSTR("\t\tthe new value will be read from standard input.\n")); | |
dbf6a266 A |
269 | SCPrint(TRUE, stderr, CFSTR("\n")); |
270 | SCPrint(TRUE, stderr, CFSTR(" or: %s --dns\n"), command); | |
271 | SCPrint(TRUE, stderr, CFSTR("\tshow DNS configuration.\n")); | |
272 | SCPrint(TRUE, stderr, CFSTR("\n")); | |
273 | SCPrint(TRUE, stderr, CFSTR(" or: %s --proxy\n"), command); | |
274 | SCPrint(TRUE, stderr, CFSTR("\tshow \"proxy\" configuration.\n")); | |
275 | ||
276 | if (getenv("ENABLE_EXPERIMENTAL_SCUTIL_COMMANDS")) { | |
277 | SCPrint(TRUE, stderr, CFSTR("\n")); | |
278 | SCPrint(TRUE, stderr, CFSTR(" or: %s --net\n"), command); | |
279 | SCPrint(TRUE, stderr, CFSTR("\tmanage network configuration.\n")); | |
280 | } | |
281 | ||
0fae82ee A |
282 | exit (EX_USAGE); |
283 | } | |
284 | ||
285 | ||
009ee3c6 A |
286 | static char * |
287 | prompt(EditLine *el) | |
288 | { | |
289 | return "> "; | |
290 | } | |
291 | ||
292 | ||
5958d7c0 | 293 | int |
a5f60add | 294 | main(int argc, char * const argv[]) |
5958d7c0 | 295 | { |
edebe297 A |
296 | Boolean doDNS = FALSE; |
297 | Boolean doNet = FALSE; | |
298 | Boolean doPrefs = FALSE; | |
299 | Boolean doProxy = FALSE; | |
300 | Boolean doReach = FALSE; | |
009ee3c6 | 301 | char *get = NULL; |
0fae82ee A |
302 | extern int optind; |
303 | int opt; | |
009ee3c6 | 304 | int opti; |
0fae82ee | 305 | const char *prog = argv[0]; |
009ee3c6 A |
306 | char *set = NULL; |
307 | InputRef src; | |
0fae82ee A |
308 | int timeout = 15; /* default timeout (in seconds) */ |
309 | char *wait = NULL; | |
a40a14f8 | 310 | Boolean watch = FALSE; |
009ee3c6 | 311 | int xStore = 0; /* non dynamic store command line options */ |
5958d7c0 A |
312 | |
313 | /* process any arguments */ | |
314 | ||
a40a14f8 | 315 | while ((opt = getopt_long(argc, argv, "dvprt:w:W", longopts, &opti)) != -1) |
5958d7c0 A |
316 | switch(opt) { |
317 | case 'd': | |
0fae82ee A |
318 | _sc_debug = TRUE; |
319 | _sc_log = FALSE; /* enable framework logging */ | |
5958d7c0 A |
320 | break; |
321 | case 'v': | |
0fae82ee A |
322 | _sc_verbose = TRUE; |
323 | _sc_log = FALSE; /* enable framework logging */ | |
324 | break; | |
325 | case 'p': | |
326 | enablePrivateAPI = TRUE; | |
5958d7c0 A |
327 | break; |
328 | case 'r': | |
edebe297 | 329 | doReach = TRUE; |
009ee3c6 | 330 | xStore++; |
0fae82ee A |
331 | break; |
332 | case 't': | |
333 | timeout = atoi(optarg); | |
334 | break; | |
335 | case 'w': | |
336 | wait = optarg; | |
009ee3c6 A |
337 | xStore++; |
338 | break; | |
a40a14f8 A |
339 | case 'W': |
340 | watch = TRUE; | |
341 | break; | |
009ee3c6 | 342 | case 0: |
dbf6a266 | 343 | if (strcmp(longopts[opti].name, "dns") == 0) { |
edebe297 | 344 | doDNS = TRUE; |
dbf6a266 A |
345 | xStore++; |
346 | } else if (strcmp(longopts[opti].name, "get") == 0) { | |
009ee3c6 A |
347 | get = optarg; |
348 | xStore++; | |
dbf6a266 | 349 | } else if (strcmp(longopts[opti].name, "net") == 0) { |
edebe297 A |
350 | doNet = TRUE; |
351 | xStore++; | |
352 | } else if (strcmp(longopts[opti].name, "prefs") == 0) { | |
353 | doPrefs = TRUE; | |
dbf6a266 A |
354 | xStore++; |
355 | } else if (strcmp(longopts[opti].name, "proxy") == 0) { | |
edebe297 | 356 | doProxy = TRUE; |
dbf6a266 | 357 | xStore++; |
009ee3c6 A |
358 | } else if (strcmp(longopts[opti].name, "set") == 0) { |
359 | set = optarg; | |
360 | xStore++; | |
361 | } | |
5958d7c0 A |
362 | break; |
363 | case '?': | |
364 | default : | |
0fae82ee | 365 | usage(prog); |
a5f60add | 366 | } |
5958d7c0 A |
367 | argc -= optind; |
368 | argv += optind; | |
369 | ||
009ee3c6 A |
370 | if (xStore > 1) { |
371 | // if we are attempting to process more than one type of request | |
0fae82ee A |
372 | usage(prog); |
373 | } | |
374 | ||
a40a14f8 | 375 | /* are we checking (or watching) the reachability of a host/address */ |
edebe297 | 376 | if (doReach) { |
a40a14f8 | 377 | if (argc < 1) { |
009ee3c6 A |
378 | usage(prog); |
379 | } | |
a40a14f8 A |
380 | if (watch) { |
381 | do_watchReachability(argc, (char **)argv); | |
382 | } else { | |
383 | do_checkReachability(argc, (char **)argv); | |
384 | } | |
0fae82ee A |
385 | /* NOT REACHED */ |
386 | } | |
387 | ||
388 | /* are we waiting on the presense of a dynamic store key */ | |
389 | if (wait) { | |
390 | do_wait(wait, timeout); | |
391 | /* NOT REACHED */ | |
392 | } | |
393 | ||
dbf6a266 | 394 | /* are we looking up the DNS configuration */ |
edebe297 | 395 | if (doDNS) { |
dbf6a266 A |
396 | do_showDNSConfiguration(argc, (char **)argv); |
397 | /* NOT REACHED */ | |
398 | } | |
399 | ||
009ee3c6 A |
400 | /* are we looking up a preference value */ |
401 | if (get) { | |
402 | if (findPref(get) < 0) { | |
403 | usage(prog); | |
404 | } | |
405 | do_getPref(get, argc, (char **)argv); | |
406 | /* NOT REACHED */ | |
407 | } | |
408 | ||
dbf6a266 | 409 | /* are we looking up the proxy configuration */ |
edebe297 | 410 | if (doProxy) { |
dbf6a266 A |
411 | do_showProxyConfiguration(argc, (char **)argv); |
412 | /* NOT REACHED */ | |
413 | } | |
414 | ||
009ee3c6 A |
415 | /* are we changing a preference value */ |
416 | if (set) { | |
417 | if (findPref(set) < 0) { | |
418 | usage(prog); | |
419 | } | |
420 | do_setPref(set, argc, (char **)argv); | |
421 | /* NOT REACHED */ | |
422 | } | |
423 | ||
edebe297 | 424 | if (doNet) { |
dbf6a266 | 425 | /* if we are going to be managing the network configuration */ |
edebe297 A |
426 | commands = (cmdInfo *)commands_net; |
427 | nCommands = nCommands_net; | |
dbf6a266 A |
428 | |
429 | if (!getenv("ENABLE_EXPERIMENTAL_SCUTIL_COMMANDS")) { | |
430 | usage(prog); | |
431 | } | |
432 | ||
433 | do_net_init(); /* initialization */ | |
434 | do_net_open(0, NULL); /* open default prefs */ | |
edebe297 A |
435 | } else if (doPrefs) { |
436 | /* if we are going to be managing the network configuration */ | |
437 | commands = (cmdInfo *)commands_prefs; | |
438 | nCommands = nCommands_prefs; | |
439 | ||
440 | do_dictInit(0, NULL); /* start with an empty dictionary */ | |
441 | do_prefs_init(); /* initialization */ | |
442 | do_prefs_open(0, NULL); /* open default prefs */ | |
dbf6a266 A |
443 | } else { |
444 | /* if we are going to be managing the dynamic store */ | |
445 | commands = (cmdInfo *)commands_store; | |
446 | nCommands = nCommands_store; | |
447 | ||
448 | do_dictInit(0, NULL); /* start with an empty dictionary */ | |
449 | do_open(0, NULL); /* open the dynamic store */ | |
450 | } | |
5958d7c0 | 451 | |
009ee3c6 A |
452 | /* allocate command input stream */ |
453 | src = (InputRef)CFAllocatorAllocate(NULL, sizeof(Input), 0); | |
454 | src->fp = stdin; | |
455 | src->el = NULL; | |
456 | src->h = NULL; | |
457 | ||
458 | if (isatty(fileno(src->fp))) { | |
459 | int editmode = 1; | |
460 | HistEvent ev; | |
461 | struct termios t; | |
462 | ||
463 | if (tcgetattr(fileno(src->fp), &t) != -1) { | |
464 | if ((t.c_lflag & ECHO) == 0) { | |
465 | editmode = 0; | |
466 | } | |
467 | } | |
468 | src->el = el_init(prog, src->fp, stdout, stderr); | |
469 | src->h = history_init(); | |
470 | ||
471 | (void)history(src->h, &ev, H_SETSIZE, INT_MAX); | |
472 | el_set(src->el, EL_HIST, history, src->h); | |
5958d7c0 | 473 | |
009ee3c6 A |
474 | if (!editmode) { |
475 | el_set(src->el, EL_EDITMODE, 0); | |
476 | } | |
5958d7c0 | 477 | |
009ee3c6 A |
478 | el_set(src->el, EL_EDITOR, "emacs"); |
479 | el_set(src->el, EL_PROMPT, prompt); | |
5958d7c0 | 480 | |
009ee3c6 | 481 | el_source(src->el, NULL); |
5958d7c0 | 482 | |
009ee3c6 A |
483 | if ((el_get(src->el, EL_EDITMODE, &editmode) != -1) && editmode != 0) { |
484 | el_set(src->el, EL_SIGNAL, 1); | |
485 | } else { | |
486 | history_end(src->h); | |
487 | src->h = NULL; | |
488 | el_end(src->el); | |
489 | src->el = NULL; | |
490 | } | |
491 | } | |
5958d7c0 | 492 | |
a40a14f8 A |
493 | while (TRUE) { |
494 | Boolean ok; | |
495 | ||
496 | ok = process_line(src); | |
497 | if (!ok) { | |
498 | break; | |
499 | } | |
5958d7c0 A |
500 | } |
501 | ||
009ee3c6 A |
502 | /* close the socket, free resources */ |
503 | if (src->h) history_end(src->h); | |
504 | if (src->el) el_end(src->el); | |
505 | (void)fclose(src->fp); | |
506 | CFAllocatorDeallocate(NULL, src); | |
5958d7c0 A |
507 | |
508 | exit (EX_OK); // insure the process exit status is 0 | |
509 | return 0; // ...and make main fit the ANSI spec. | |
510 | } |