FreeCalypso > hg > themwi-nanp
comparison utils/themwi-update-numdb.c @ 2:1773886ef54e
themwi-update-numdb: old source as starting point
| author | Mychaela Falconia <falcon@freecalypso.org> |
|---|---|
| date | Wed, 13 Dec 2023 01:23:47 +0000 |
| parents | |
| children | 5bf2648e5413 |
comparison
equal
deleted
inserted
replaced
| 1:6534965175dd | 2:1773886ef54e |
|---|---|
| 1 /* | |
| 2 * This program reads (parses) ThemWi config file /var/gsm/number-db2, | |
| 3 * generates the compiled binary form of this database, and then makes | |
| 4 * it live via atomic rename. | |
| 5 */ | |
| 6 | |
| 7 #include <ctype.h> | |
| 8 #include <stdio.h> | |
| 9 #include <stdint.h> | |
| 10 #include <stdlib.h> | |
| 11 #include <string.h> | |
| 12 #include <strings.h> | |
| 13 #include <unistd.h> | |
| 14 #include "../include/number_db_v2.h" | |
| 15 | |
| 16 #define MAX_OWNED_NUMBERS 1000 | |
| 17 #define MAX_SHORT_NUMBERS 1000 | |
| 18 | |
| 19 static struct owned_number_rec owned_number_buf[MAX_OWNED_NUMBERS]; | |
| 20 static struct short_number_rec short_number_buf[MAX_SHORT_NUMBERS]; | |
| 21 static unsigned owned_number_count, short_number_count; | |
| 22 | |
| 23 static char *system_dir; | |
| 24 static FILE *inf; | |
| 25 static int lineno; | |
| 26 static char linebuf[256]; | |
| 27 static int prefix_set, prefix_allows_abbrev; | |
| 28 static uint16_t prefix_buf[2]; | |
| 29 | |
| 30 static void | |
| 31 enter_owned_number(rec) | |
| 32 struct owned_number_rec *rec; | |
| 33 { | |
| 34 if (owned_number_count >= MAX_OWNED_NUMBERS) { | |
| 35 fprintf(stderr, "error: MAX_OWNED_NUMBERS exceeded\n"); | |
| 36 exit(1); | |
| 37 } | |
| 38 bcopy(rec, owned_number_buf + owned_number_count, | |
| 39 sizeof(struct owned_number_rec)); | |
| 40 owned_number_count++; | |
| 41 } | |
| 42 | |
| 43 static void | |
| 44 enter_short_number(rec) | |
| 45 struct short_number_rec *rec; | |
| 46 { | |
| 47 if (short_number_count >= MAX_SHORT_NUMBERS) { | |
| 48 fprintf(stderr, "error: MAX_SHORT_NUMBERS exceeded\n"); | |
| 49 exit(1); | |
| 50 } | |
| 51 bcopy(rec, short_number_buf + short_number_count, | |
| 52 sizeof(struct short_number_rec)); | |
| 53 short_number_count++; | |
| 54 } | |
| 55 | |
| 56 static void | |
| 57 handle_prefix_line(cp) | |
| 58 char *cp; | |
| 59 { | |
| 60 char *np, prefix[7]; | |
| 61 | |
| 62 for (np = cp; *cp && !isspace(*cp); cp++) | |
| 63 ; | |
| 64 if (*cp) | |
| 65 *cp++ = '\0'; | |
| 66 if (grok_number_string(np, 1) != 6) { | |
| 67 fprintf(stderr, | |
| 68 "number-db2 line %d: prefix requires 6-digit argument\n", | |
| 69 lineno); | |
| 70 exit(1); | |
| 71 } | |
| 72 dehyphen_number_string(np, prefix); | |
| 73 if (!is_nanp_valid_prefix(prefix)) { | |
| 74 fprintf(stderr, | |
| 75 "number-db2 line %d: prefix violates NANP rules\n", | |
| 76 lineno); | |
| 77 exit(1); | |
| 78 } | |
| 79 prefix_buf[0] = digits3_to_uint16(prefix); | |
| 80 prefix_buf[1] = digits3_to_uint16(prefix + 3); | |
| 81 prefix_set = 1; | |
| 82 while (isspace(*cp)) | |
| 83 cp++; | |
| 84 if (*cp == '\0' || *cp == '#') { | |
| 85 prefix_allows_abbrev = 0; | |
| 86 return; | |
| 87 } | |
| 88 for (np = cp; *cp && !isspace(*cp); cp++) | |
| 89 ; | |
| 90 if (*cp) | |
| 91 *cp++ = '\0'; | |
| 92 if (!strcmp(np, "allow-abbrev")) { | |
| 93 prefix_allows_abbrev = 1; | |
| 94 return; | |
| 95 } | |
| 96 fprintf(stderr, | |
| 97 "number-db2 line %d: non-understood notation \"%s\" after prefix\n", | |
| 98 lineno, np); | |
| 99 exit(1); | |
| 100 } | |
| 101 | |
| 102 static int | |
| 103 parse_extra_number(rec, numstr) | |
| 104 struct owned_number_rec *rec; | |
| 105 char *numstr; | |
| 106 { | |
| 107 char buf[11]; | |
| 108 int rc; | |
| 109 | |
| 110 rc = grok_number_string(numstr, 1); | |
| 111 switch (rc) { | |
| 112 case 4: | |
| 113 if (!prefix_set) | |
| 114 return(-1); | |
| 115 dehyphen_number_string(numstr, buf); | |
| 116 rec->remap[0] = prefix_buf[0]; | |
| 117 rec->remap[1] = prefix_buf[1]; | |
| 118 rec->remap[2] = digits4_to_uint16(buf); | |
| 119 return(0); | |
| 120 case 10: | |
| 121 dehyphen_number_string(numstr, buf); | |
| 122 rec->remap[0] = digits3_to_uint16(buf); | |
| 123 rec->remap[1] = digits3_to_uint16(buf + 3); | |
| 124 rec->remap[2] = digits4_to_uint16(buf + 6); | |
| 125 return(0); | |
| 126 default: | |
| 127 return(-1); | |
| 128 } | |
| 129 } | |
| 130 | |
| 131 static void | |
| 132 handle_number_attr(rec, tail) | |
| 133 struct owned_number_rec *rec; | |
| 134 char *tail; | |
| 135 { | |
| 136 char *cp, *np; | |
| 137 int rc; | |
| 138 | |
| 139 for (cp = tail; ; ) { | |
| 140 while (isspace(*cp)) | |
| 141 cp++; | |
| 142 if (*cp == '\0' || *cp == '#') | |
| 143 return; | |
| 144 for (np = cp; *cp && !isspace(*cp); cp++) | |
| 145 ; | |
| 146 if (*cp) | |
| 147 *cp++ = '\0'; | |
| 148 if (!strcmp(np, "sms")) | |
| 149 rec->number_flags |= NUMBER_FLAG_SMSPROV; | |
| 150 else if (!strcmp(np, "e911")) | |
| 151 rec->number_flags |= NUMBER_FLAG_E911PROV; | |
| 152 else if (!strcmp(np, "gsm-sub")) | |
| 153 rec->usage = NUMBER_USAGE_TYPE_GSM_SUB; | |
| 154 else if (!strcmp(np, "map-to")) { | |
| 155 rec->usage = NUMBER_USAGE_TYPE_ALIAS; | |
| 156 while (isspace(*cp)) | |
| 157 cp++; | |
| 158 if (*cp == '\0' || *cp == '#') { | |
| 159 fprintf(stderr, | |
| 160 "number-db2 line %d: map-to requires an argument\n", | |
| 161 lineno); | |
| 162 exit(1); | |
| 163 } | |
| 164 for (np = cp; *cp && !isspace(*cp); cp++) | |
| 165 ; | |
| 166 if (*cp) | |
| 167 *cp++ = '\0'; | |
| 168 rc = parse_extra_number(rec, np); | |
| 169 if (rc < 0) { | |
| 170 fprintf(stderr, | |
| 171 "number-db2 line %d: map-to argument is invalid\n", | |
| 172 lineno); | |
| 173 exit(1); | |
| 174 } | |
| 175 } else if (!strcmp(np, "e911-via")) { | |
| 176 if (rec->usage != NUMBER_USAGE_TYPE_GSM_SUB) { | |
| 177 fprintf(stderr, | |
| 178 "number-db2 line %d: invalid usage of e911-via\n", | |
| 179 lineno); | |
| 180 exit(1); | |
| 181 } | |
| 182 rec->usage |= NUMBER_USAGE_FLAG_E911_VIA; | |
| 183 while (isspace(*cp)) | |
| 184 cp++; | |
| 185 if (*cp == '\0' || *cp == '#') { | |
| 186 fprintf(stderr, | |
| 187 "number-db2 line %d: e911-via requires an argument\n", | |
| 188 lineno); | |
| 189 exit(1); | |
| 190 } | |
| 191 for (np = cp; *cp && !isspace(*cp); cp++) | |
| 192 ; | |
| 193 if (*cp) | |
| 194 *cp++ = '\0'; | |
| 195 rc = parse_extra_number(rec, np); | |
| 196 if (rc < 0) { | |
| 197 fprintf(stderr, | |
| 198 "number-db2 line %d: e911-via argument is invalid\n", | |
| 199 lineno); | |
| 200 exit(1); | |
| 201 } | |
| 202 } else { | |
| 203 fprintf(stderr, | |
| 204 "number-db2 line %d: invalid attribute \"%s\"\n", | |
| 205 lineno, np); | |
| 206 exit(1); | |
| 207 } | |
| 208 } | |
| 209 } | |
| 210 | |
| 211 static void | |
| 212 handle_suffix_line(cp) | |
| 213 char *cp; | |
| 214 { | |
| 215 char *np; | |
| 216 uint16_t suffix; | |
| 217 struct owned_number_rec own_rec; | |
| 218 struct short_number_rec short_rec; | |
| 219 | |
| 220 if (!prefix_set) { | |
| 221 fprintf(stderr, | |
| 222 "number-db2 line %d: suffix not valid without preceding prefix\n", | |
| 223 lineno); | |
| 224 exit(1); | |
| 225 } | |
| 226 for (np = cp; *cp && !isspace(*cp); cp++) | |
| 227 ; | |
| 228 if (*cp) | |
| 229 *cp++ = '\0'; | |
| 230 if (grok_number_string(np, 0) != 4) { | |
| 231 fprintf(stderr, | |
| 232 "number-db2 line %d: suffix requires 4-digit argument\n", | |
| 233 lineno); | |
| 234 exit(1); | |
| 235 } | |
| 236 suffix = digits4_to_uint16(np); | |
| 237 bzero(&own_rec, sizeof own_rec); | |
| 238 own_rec.number[0] = prefix_buf[0]; | |
| 239 own_rec.number[1] = prefix_buf[1]; | |
| 240 own_rec.number[2] = suffix; | |
| 241 handle_number_attr(&own_rec, cp); | |
| 242 enter_owned_number(&own_rec); | |
| 243 if (!prefix_allows_abbrev) | |
| 244 return; | |
| 245 if ((own_rec.usage & NUMBER_USAGE_MASK) != NUMBER_USAGE_TYPE_GSM_SUB) | |
| 246 return; | |
| 247 bzero(&short_rec, sizeof short_rec); | |
| 248 short_rec.short_num = suffix; | |
| 249 short_rec.short_num_type = SHORT_NUM_TYPE_ABBREV; | |
| 250 short_rec.fullnum_flags = own_rec.number_flags; | |
| 251 short_rec.fullnum_prefix[0] = prefix_buf[0]; | |
| 252 short_rec.fullnum_prefix[1] = prefix_buf[1]; | |
| 253 enter_short_number(&short_rec); | |
| 254 } | |
| 255 | |
| 256 static void | |
| 257 handle_full10_line(cp) | |
| 258 char *cp; | |
| 259 { | |
| 260 char *np, full10[11]; | |
| 261 struct owned_number_rec own_rec; | |
| 262 | |
| 263 prefix_set = 0; /* cancel any previous prefix line */ | |
| 264 for (np = cp; *cp && !isspace(*cp); cp++) | |
| 265 ; | |
| 266 if (*cp) | |
| 267 *cp++ = '\0'; | |
| 268 if (grok_number_string(np, 1) != 10) { | |
| 269 fprintf(stderr, | |
| 270 "number-db2 line %d: full10 requires 10-digit argument\n", | |
| 271 lineno); | |
| 272 exit(1); | |
| 273 } | |
| 274 dehyphen_number_string(np, full10); | |
| 275 if (!is_nanp_valid_prefix(full10)) { | |
| 276 fprintf(stderr, | |
| 277 "number-db2 line %d: number violates NANP rules\n", | |
| 278 lineno); | |
| 279 exit(1); | |
| 280 } | |
| 281 bzero(&own_rec, sizeof own_rec); | |
| 282 own_rec.number[0] = digits3_to_uint16(full10); | |
| 283 own_rec.number[1] = digits3_to_uint16(full10 + 3); | |
| 284 own_rec.number[2] = digits4_to_uint16(full10 + 6); | |
| 285 handle_number_attr(&own_rec, cp); | |
| 286 enter_owned_number(&own_rec); | |
| 287 } | |
| 288 | |
| 289 static void | |
| 290 handle_itn_line(cp) | |
| 291 char *cp; | |
| 292 { | |
| 293 char *np; | |
| 294 struct short_number_rec short_rec; | |
| 295 | |
| 296 prefix_set = 0; /* cancel any previous prefix line */ | |
| 297 for (np = cp; *cp && !isspace(*cp); cp++) | |
| 298 ; | |
| 299 if (*cp) | |
| 300 *cp++ = '\0'; | |
| 301 if (grok_number_string(np, 0) != 4) { | |
| 302 fprintf(stderr, | |
| 303 "number-db2 line %d: itn requires 4-digit argument\n", | |
| 304 lineno); | |
| 305 exit(1); | |
| 306 } | |
| 307 bzero(&short_rec, sizeof short_rec); | |
| 308 short_rec.short_num = digits4_to_uint16(np); | |
| 309 short_rec.short_num_type = SHORT_NUM_TYPE_ITN; | |
| 310 enter_short_number(&short_rec); | |
| 311 } | |
| 312 | |
| 313 static void | |
| 314 handle_test_sink_line(cp) | |
| 315 char *cp; | |
| 316 { | |
| 317 char *np; | |
| 318 struct short_number_rec short_rec; | |
| 319 | |
| 320 prefix_set = 0; /* cancel any previous prefix line */ | |
| 321 for (np = cp; *cp && !isspace(*cp); cp++) | |
| 322 ; | |
| 323 if (*cp) | |
| 324 *cp++ = '\0'; | |
| 325 if (grok_number_string(np, 0) != 4) { | |
| 326 fprintf(stderr, | |
| 327 "number-db2 line %d: test-sink requires 4-digit argument\n", | |
| 328 lineno); | |
| 329 exit(1); | |
| 330 } | |
| 331 bzero(&short_rec, sizeof short_rec); | |
| 332 short_rec.short_num = digits4_to_uint16(np); | |
| 333 short_rec.short_num_type = SHORT_NUM_TYPE_TEST_SINK; | |
| 334 enter_short_number(&short_rec); | |
| 335 } | |
| 336 | |
| 337 static void | |
| 338 process_line() | |
| 339 { | |
| 340 char *cp, *np; | |
| 341 void (*handler)(); | |
| 342 | |
| 343 if (!index(linebuf, '\n')) { | |
| 344 fprintf(stderr, | |
| 345 "number-db2 line %d: too long or missing newline\n", | |
| 346 lineno); | |
| 347 exit(1); | |
| 348 } | |
| 349 for (cp = linebuf; isspace(*cp); cp++) | |
| 350 ; | |
| 351 if (*cp == '\0' || *cp == '#') | |
| 352 return; | |
| 353 for (np = cp; *cp && !isspace(*cp); cp++) | |
| 354 ; | |
| 355 if (*cp) | |
| 356 *cp++ = '\0'; | |
| 357 if (!strcmp(np, "prefix")) | |
| 358 handler = handle_prefix_line; | |
| 359 else if (!strcmp(np, "suffix")) | |
| 360 handler = handle_suffix_line; | |
| 361 else if (!strcmp(np, "full10")) | |
| 362 handler = handle_full10_line; | |
| 363 else if (!strcmp(np, "itn")) | |
| 364 handler = handle_itn_line; | |
| 365 else if (!strcmp(np, "test-sink")) | |
| 366 handler = handle_test_sink_line; | |
| 367 else { | |
| 368 fprintf(stderr, | |
| 369 "number-db2 line %d: non-understood keyword \"%s\"\n", | |
| 370 lineno, np); | |
| 371 exit(1); | |
| 372 } | |
| 373 while (isspace(*cp)) | |
| 374 cp++; | |
| 375 if (*cp == '\0' || *cp == '#') { | |
| 376 fprintf(stderr, | |
| 377 "number-db2 line %d: missing argument after \"%s\" keyword\n", | |
| 378 lineno, np); | |
| 379 exit(1); | |
| 380 } | |
| 381 handler(cp); | |
| 382 } | |
| 383 | |
| 384 static int | |
| 385 compare_owned_num(p1, p2) | |
| 386 uint16_t *p1, *p2; | |
| 387 { | |
| 388 if (p1[0] < p2[0]) | |
| 389 return(-1); | |
| 390 if (p1[0] > p2[0]) | |
| 391 return(1); | |
| 392 if (p1[1] < p2[1]) | |
| 393 return(-1); | |
| 394 if (p1[1] > p2[1]) | |
| 395 return(1); | |
| 396 if (p1[2] < p2[2]) | |
| 397 return(-1); | |
| 398 if (p1[2] > p2[2]) | |
| 399 return(1); | |
| 400 return(0); | |
| 401 } | |
| 402 | |
| 403 static int | |
| 404 compare_short_num(p1, p2) | |
| 405 uint16_t *p1, *p2; | |
| 406 { | |
| 407 if (*p1 < *p2) | |
| 408 return(-1); | |
| 409 if (*p1 > *p2) | |
| 410 return(1); | |
| 411 return(0); | |
| 412 } | |
| 413 | |
| 414 static void | |
| 415 owned_num_check_dup() | |
| 416 { | |
| 417 struct owned_number_rec *p, *endp; | |
| 418 | |
| 419 endp = owned_number_buf + owned_number_count - 1; | |
| 420 for (p = owned_number_buf; p < endp; p++) { | |
| 421 if (p[0].number[0] == p[1].number[0] && | |
| 422 p[0].number[1] == p[1].number[1] && | |
| 423 p[0].number[2] == p[1].number[2]) { | |
| 424 fprintf(stderr, | |
| 425 "error: NANP number %03u-%03u-%04u appears more than once\n", | |
| 426 p[0].number[0], p[0].number[1], p[0].number[2]); | |
| 427 exit(1); | |
| 428 } | |
| 429 } | |
| 430 } | |
| 431 | |
| 432 static void | |
| 433 short_num_check_dup() | |
| 434 { | |
| 435 struct short_number_rec *p, *endp; | |
| 436 | |
| 437 endp = short_number_buf + short_number_count - 1; | |
| 438 for (p = short_number_buf; p < endp; p++) { | |
| 439 if (p[0].short_num == p[1].short_num) { | |
| 440 fprintf(stderr, | |
| 441 "error: short number %04u appears more than once\n", | |
| 442 (unsigned) p->short_num); | |
| 443 exit(1); | |
| 444 } | |
| 445 } | |
| 446 } | |
| 447 | |
| 448 static void | |
| 449 check_secondnum_mapto(src) | |
| 450 struct owned_number_rec *src; | |
| 451 { | |
| 452 struct owned_number_rec *dest; | |
| 453 | |
| 454 dest = bsearch(src->remap, owned_number_buf, owned_number_count, | |
| 455 sizeof(struct owned_number_rec), compare_owned_num); | |
| 456 if (!dest) { | |
| 457 fprintf(stderr, | |
| 458 "error: NANP %03u-%03u-%04u map-to target is not in the database\n", | |
| 459 src->number[0], src->number[1], src->number[2]); | |
| 460 exit(1); | |
| 461 } | |
| 462 if ((dest->usage & NUMBER_USAGE_MASK) != NUMBER_USAGE_TYPE_GSM_SUB) { | |
| 463 fprintf(stderr, | |
| 464 "error: NANP %03u-%03u-%04u map-to target is not a gsm-sub number\n", | |
| 465 src->number[0], src->number[1], src->number[2]); | |
| 466 exit(1); | |
| 467 } | |
| 468 } | |
| 469 | |
| 470 static void | |
| 471 check_secondnum_e911via(src) | |
| 472 struct owned_number_rec *src; | |
| 473 { | |
| 474 struct owned_number_rec *dest; | |
| 475 | |
| 476 dest = bsearch(src->remap, owned_number_buf, owned_number_count, | |
| 477 sizeof(struct owned_number_rec), compare_owned_num); | |
| 478 if (!dest) { | |
| 479 fprintf(stderr, | |
| 480 "error: NANP %03u-%03u-%04u e911-via target is not in the database\n", | |
| 481 src->number[0], src->number[1], src->number[2]); | |
| 482 exit(1); | |
| 483 } | |
| 484 if (!(dest->number_flags & NUMBER_FLAG_E911PROV)) { | |
| 485 fprintf(stderr, | |
| 486 "error: NANP %03u-%03u-%04u e911-via target is not an E911 number\n", | |
| 487 src->number[0], src->number[1], src->number[2]); | |
| 488 exit(1); | |
| 489 } | |
| 490 } | |
| 491 | |
| 492 static void | |
| 493 check_secondary_numbers() | |
| 494 { | |
| 495 struct owned_number_rec *p, *endp; | |
| 496 | |
| 497 endp = owned_number_buf + owned_number_count; | |
| 498 for (p = owned_number_buf; p < endp; p++) { | |
| 499 if ((p->usage & NUMBER_USAGE_MASK) == NUMBER_USAGE_TYPE_ALIAS) | |
| 500 check_secondnum_mapto(p); | |
| 501 if (p->usage & NUMBER_USAGE_FLAG_E911_VIA) | |
| 502 check_secondnum_e911via(p); | |
| 503 } | |
| 504 } | |
| 505 | |
| 506 static void | |
| 507 emit_output() | |
| 508 { | |
| 509 FILE *outf; | |
| 510 struct numdb_file_hdr hdr; | |
| 511 | |
| 512 outf = fopen("number-db2.newbin", "w"); | |
| 513 if (!outf) { | |
| 514 perror("creating number-db2.newbin"); | |
| 515 exit(1); | |
| 516 } | |
| 517 hdr.owned_number_count = owned_number_count; | |
| 518 hdr.short_number_count = short_number_count; | |
| 519 if (fwrite(&hdr, sizeof hdr, 1, outf) != 1) { | |
| 520 write_err: fprintf(stderr, "error writing to new binary file\n"); | |
| 521 exit(1); | |
| 522 } | |
| 523 if (fwrite(owned_number_buf, sizeof(owned_number_buf[0]), | |
| 524 owned_number_count, outf) != owned_number_count) | |
| 525 goto write_err; | |
| 526 if (fwrite(short_number_buf, sizeof(short_number_buf[0]), | |
| 527 short_number_count, outf) != short_number_count) | |
| 528 goto write_err; | |
| 529 fclose(outf); | |
| 530 } | |
| 531 | |
| 532 main(argc, argv) | |
| 533 char **argv; | |
| 534 { | |
| 535 if (argc > 2) { | |
| 536 fprintf(stderr, "usage: %s [directory]\n", argv[0]); | |
| 537 exit(1); | |
| 538 } | |
| 539 if (argv[1]) | |
| 540 system_dir = argv[1]; | |
| 541 else | |
| 542 system_dir = "/var/gsm"; | |
| 543 if (chdir(system_dir) < 0) { | |
| 544 perror(system_dir); | |
| 545 exit(1); | |
| 546 } | |
| 547 inf = fopen("number-db2", "r"); | |
| 548 if (!inf) { | |
| 549 perror("opening number-db2"); | |
| 550 exit(1); | |
| 551 } | |
| 552 for (lineno = 1; fgets(linebuf, sizeof linebuf, inf); lineno++) | |
| 553 process_line(); | |
| 554 fclose(inf); | |
| 555 if (owned_number_count >= 2) { | |
| 556 qsort(owned_number_buf, owned_number_count, | |
| 557 sizeof(owned_number_buf[0]), compare_owned_num); | |
| 558 owned_num_check_dup(); | |
| 559 } | |
| 560 check_secondary_numbers(); | |
| 561 if (short_number_count >= 2) { | |
| 562 qsort(short_number_buf, short_number_count, | |
| 563 sizeof(short_number_buf[0]), compare_short_num); | |
| 564 short_num_check_dup(); | |
| 565 } | |
| 566 emit_output(); | |
| 567 /* make it live */ | |
| 568 if (rename("number-db2.newbin", "number-db2.bin") < 0) { | |
| 569 perror("rename"); | |
| 570 exit(1); | |
| 571 } | |
| 572 exit(0); | |
| 573 } |
