sgivol.c revision 1.17 1 /* $NetBSD: sgivol.c,v 1.17 2008/08/03 16:09:20 rumble Exp $ */
2
3 /*-
4 * Copyright (c) 2001 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Michael Hitch and Hubert Feyrer.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32 #if HAVE_NBTOOL_CONFIG_H
33 #include "nbtool_config.h"
34 #endif
35
36 #include <sys/types.h>
37 #include <sys/ioctl.h>
38 #include <sys/stat.h>
39
40 #if HAVE_NBTOOL_CONFIG_H
41 #include "../../../../../sys/sys/bootblock.h"
42 /* Ficticious geometry for cross tool usage against a file image */
43 #define SGIVOL_NBTOOL_NSECS 32
44 #define SGIVOL_NBTOOL_NTRACKS 64
45 #else
46 #include <sys/disklabel.h>
47 #endif
48
49 #include <errno.h>
50 #include <stdio.h>
51 #include <stdlib.h>
52 #include <unistd.h>
53 #include <string.h>
54 #include <fcntl.h>
55 #include <util.h>
56 #ifndef HAVE_NBTOOL_CONFIG_H
57 #include <sys/endian.h>
58 #endif
59
60 int fd;
61 int opt_i; /* Initialize volume header */
62 int opt_r; /* Read a file from volume header */
63 int opt_w; /* Write a file to volume header */
64 int opt_d; /* Delete a file from volume header */
65 int opt_m; /* Move (rename) a file in the volume header */
66 int opt_p; /* Modify a partition */
67 int opt_q; /* quiet mode */
68 int opt_f; /* Don't ask, just do what you're told */
69 int partno, partfirst, partblocks, parttype;
70 struct sgi_boot_block *volhdr;
71 int32_t checksum;
72 u_int32_t volhdr_size = SGI_BOOT_BLOCK_SIZE_VOLHDR;
73
74 const char *vfilename = "";
75 const char *ufilename = "";
76
77 #if HAVE_NBTOOL_CONFIG_H
78 struct stat st;
79 #else
80 struct disklabel lbl;
81 #endif
82
83 unsigned char buf[512];
84
85 const char *sgi_types[] = {
86 "Volume Header",
87 "Repl Trks",
88 "Repl Secs",
89 "Raw",
90 "BSD4.2",
91 "SysV",
92 "Volume",
93 "EFS",
94 "LVol",
95 "RLVol",
96 "XFS",
97 "XSFLog",
98 "XLV",
99 "XVM"
100 };
101
102 int main(int, char *[]);
103
104 void display_vol(void);
105 void init_volhdr(void);
106 void read_file(void);
107 void write_file(void);
108 void delete_file(void);
109 void move_file(void);
110 void modify_partition(void);
111 void write_volhdr(void);
112 int allocate_space(int);
113 void checksum_vol(void);
114 void usage(void);
115
116 int
117 main(int argc, char *argv[])
118 {
119 #define RESET_OPTS() opt_i = opt_m = opt_r = opt_w = opt_d = opt_p = 0
120
121 int ch;
122 while ((ch = getopt(argc, argv, "qfih:rwdmp?")) != -1) {
123 switch (ch) {
124 /* -i, -r, -w, -d, -m and -p override each other */
125 /* -q implies -f */
126 case 'q':
127 ++opt_q;
128 ++opt_f;
129 break;
130 case 'f':
131 ++opt_f;
132 break;
133 case 'i':
134 RESET_OPTS();
135 ++opt_i;
136 break;
137 case 'h':
138 volhdr_size = atoi(optarg);
139 break;
140 case 'r':
141 RESET_OPTS();
142 ++opt_r;
143 break;
144 case 'w':
145 RESET_OPTS();
146 ++opt_w;
147 break;
148 case 'd':
149 RESET_OPTS();
150 ++opt_d;
151 break;
152 case 'm':
153 RESET_OPTS();
154 ++opt_m;
155 break;
156 case 'p':
157 RESET_OPTS();
158 ++opt_p;
159 partno = atoi(argv[0]);
160 partfirst = atoi(argv[1]);
161 partblocks = atoi(argv[2]);
162 parttype = atoi(argv[3]);
163 break;
164 case '?':
165 default:
166 usage();
167 }
168 }
169 argc -= optind;
170 argv += optind;
171
172 if (opt_m || opt_r || opt_w) {
173 if (argc != 3)
174 usage();
175 vfilename = argv[0];
176 ufilename = argv[1];
177 argc -= 2;
178 argv += 2;
179 }
180 if (opt_d) {
181 if (argc != 2)
182 usage();
183 vfilename = argv[0];
184 argc--;
185 argv++;
186 }
187
188 if (opt_p) {
189 if (argc != 5)
190 usage();
191 partno = atoi(argv[0]);
192 partfirst = atoi(argv[1]);
193 partblocks = atoi(argv[2]);
194 parttype = atoi(argv[3]);
195 argc -= 4;
196 argv += 4;
197 }
198 if (argc != 1)
199 usage();
200
201 fd = open(argv[0],
202 (opt_i | opt_m | opt_w | opt_d | opt_p) ? O_RDWR : O_RDONLY);
203 if (fd < 0) {
204 #if HAVE_NBTOOL_CONFIG_H
205 perror("File open");
206 exit(1);
207 #else
208 sprintf((char *)buf, "/dev/r%s%c", argv[0], 'a' + getrawpartition());
209 fd = open((char *)buf, (opt_i | opt_w | opt_d | opt_p)
210 ? O_RDWR : O_RDONLY);
211 if (fd < 0) {
212 printf("Error opening device %s: %s\n",
213 argv[0], strerror(errno));
214 exit(1);
215 }
216 #endif
217 }
218 if (read(fd, buf, sizeof(buf)) != sizeof(buf)) {
219 perror("read volhdr");
220 exit(1);
221 }
222 #if HAVE_NBTOOL_CONFIG_H
223 if (fstat(fd, &st) < 0) {
224 perror("stat error");
225 exit(1);
226 }
227 if (!S_ISREG(st.st_mode)) {
228 printf("Must be regular file\n");
229 exit(1);
230 }
231 if (st.st_size % SGI_BOOT_BLOCK_BLOCKSIZE) {
232 printf("Size must be multiple of %d\n",
233 SGI_BOOT_BLOCK_BLOCKSIZE);
234 exit(1);
235 }
236 if (st.st_size < (SGIVOL_NBTOOL_NSECS * SGIVOL_NBTOOL_NTRACKS)) {
237 printf("Minimum size of %d required\n",
238 SGIVOL_NBTOOL_NSECS * SGIVOL_NBTOOL_NTRACKS);
239 exit(1);
240 }
241 #else
242 if (ioctl(fd, DIOCGDINFO, &lbl) < 0) {
243 perror("DIOCGDINFO");
244 exit(1);
245 }
246 #endif
247 volhdr = (struct sgi_boot_block *) buf;
248 if (opt_i) {
249 init_volhdr();
250 exit(0);
251 }
252 if (be32toh(volhdr->magic) != SGI_BOOT_BLOCK_MAGIC) {
253 printf("No Volume Header found, magic=%x. Use -i first.\n",
254 be32toh(volhdr->magic));
255 exit(1);
256 }
257 if (opt_r) {
258 read_file();
259 exit(0);
260 }
261 if (opt_w) {
262 write_file();
263 exit(0);
264 }
265 if (opt_d) {
266 delete_file();
267 exit(0);
268 }
269 if (opt_m) {
270 move_file();
271 exit(0);
272 }
273 if (opt_p) {
274 modify_partition();
275 exit(0);
276 }
277
278 if (!opt_q)
279 display_vol();
280
281 return 0;
282 }
283
284 void
285 display_vol(void)
286 {
287 int32_t *l;
288 int i;
289
290 #if HAVE_NBTOOL_CONFIG_H
291 printf("disklabel shows %d sectors\n",
292 st.st_size / SGI_BOOT_BLOCK_BLOCKSIZE);
293 #else
294 printf("disklabel shows %d sectors\n", lbl.d_secperunit);
295 #endif
296 l = (int32_t *)buf;
297 checksum = 0;
298 for (i = 0; i < 512 / 4; ++i)
299 checksum += be32toh(l[i]);
300 printf("checksum: %08x%s\n", checksum, checksum == 0 ? "" : " *ERROR*");
301 printf("root part: %d\n", be16toh(volhdr->root));
302 printf("swap part: %d\n", be16toh(volhdr->swap));
303 printf("bootfile: %s\n", volhdr->bootfile);
304 /* volhdr->devparams[0..47] */
305 printf("\nVolume header files:\n");
306 for (i = 0; i < SGI_BOOT_BLOCK_MAXVOLDIRS; ++i)
307 if (volhdr->voldir[i].name[0])
308 printf("%-8s offset %4d blocks, length %8d bytes "
309 "(%d blocks)\n",
310 volhdr->voldir[i].name,
311 be32toh(volhdr->voldir[i].block),
312 be32toh(volhdr->voldir[i].bytes),
313 (be32toh(volhdr->voldir[i].bytes) + 511) / 512);
314 printf("\nSGI partitions:\n");
315 for (i = 0; i < SGI_BOOT_BLOCK_MAXPARTITIONS; ++i) {
316 if (be32toh(volhdr->partitions[i].blocks)) {
317 printf("%2d:%c blocks %8d first %8d type %2d (%s)\n",
318 i, i + 'a', be32toh(volhdr->partitions[i].blocks),
319 be32toh(volhdr->partitions[i].first),
320 be32toh(volhdr->partitions[i].type),
321 be32toh(volhdr->partitions[i].type) > 13 ? "???" :
322 sgi_types[be32toh(volhdr->partitions[i].type)]);
323 }
324 }
325 }
326
327 void
328 init_volhdr(void)
329 {
330 memset(buf, 0, sizeof(buf));
331 volhdr->magic = htobe32(SGI_BOOT_BLOCK_MAGIC);
332 volhdr->root = htobe16(0);
333 volhdr->swap = htobe16(1);
334 strcpy(volhdr->bootfile, "/netbsd");
335 #if HAVE_NBTOOL_CONFIG_H
336 volhdr->dp.dp_skew = 0;
337 #else
338 volhdr->dp.dp_skew = lbl.d_trackskew;
339 #endif
340 volhdr->dp.dp_gap1 = 1; /* XXX */
341 volhdr->dp.dp_gap2 = 1; /* XXX */
342 #if HAVE_NBTOOL_CONFIG_H
343 volhdr->dp.dp_cyls =
344 htobe16(st.st_size / (SGIVOL_NBTOOL_NSECS * SGIVOL_NBTOOL_NTRACKS));
345 #else
346 volhdr->dp.dp_cyls = htobe16(lbl.d_ncylinders);
347 #endif
348 volhdr->dp.dp_shd0 = 0;
349 #if HAVE_NBTOOL_CONFIG_H
350 volhdr->dp.dp_trks0 = htobe16(SGIVOL_NBTOOL_NTRACKS);
351 volhdr->dp.dp_secs = htobe16(SGIVOL_NBTOOL_NSECS);
352 volhdr->dp.dp_secbytes = htobe16(SGI_BOOT_BLOCK_BLOCKSIZE);
353 volhdr->dp.dp_interleave = htobe16(1);
354 #else
355 volhdr->dp.dp_trks0 = htobe16(lbl.d_ntracks);
356 volhdr->dp.dp_secs = htobe16(lbl.d_nsectors);
357 volhdr->dp.dp_secbytes = htobe16(lbl.d_secsize);
358 volhdr->dp.dp_interleave = htobe16(lbl.d_interleave);
359 #endif
360 volhdr->dp.dp_nretries = htobe32(22);
361 #if HAVE_NBTOOL_CONFIG_H
362 volhdr->partitions[10].blocks =
363 htobe32(st.st_size / SGI_BOOT_BLOCK_BLOCKSIZE);
364 #else
365 volhdr->partitions[10].blocks = htobe32(lbl.d_secperunit);
366 #endif
367 volhdr->partitions[10].first = 0;
368 volhdr->partitions[10].type = htobe32(SGI_PTYPE_VOLUME);
369 volhdr->partitions[8].blocks = htobe32(volhdr_size);
370 volhdr->partitions[8].first = 0;
371 volhdr->partitions[8].type = htobe32(SGI_PTYPE_VOLHDR);
372 #if HAVE_NBTOOL_CONFIG_H
373 volhdr->partitions[0].blocks =
374 htobe32((st.st_size / SGI_BOOT_BLOCK_BLOCKSIZE) - volhdr_size);
375 #else
376 volhdr->partitions[0].blocks = htobe32(lbl.d_secperunit - volhdr_size);
377 #endif
378 volhdr->partitions[0].first = htobe32(volhdr_size);
379 volhdr->partitions[0].type = htobe32(SGI_PTYPE_BSD);
380 write_volhdr();
381 }
382
383 void
384 read_file(void)
385 {
386 FILE *fp;
387 int i;
388
389 if (!opt_q)
390 printf("Reading file %s\n", vfilename);
391 for (i = 0; i < SGI_BOOT_BLOCK_MAXVOLDIRS; ++i) {
392 if (strncmp(vfilename, volhdr->voldir[i].name,
393 strlen(volhdr->voldir[i].name)) == 0)
394 break;
395 }
396 if (i >= SGI_BOOT_BLOCK_MAXVOLDIRS) {
397 printf("File '%s' not found\n", vfilename);
398 exit(1);
399 }
400 /* XXX assumes volume header starts at 0? */
401 lseek(fd, be32toh(volhdr->voldir[i].block) * 512, SEEK_SET);
402 fp = fopen(ufilename, "w");
403 if (fp == NULL) {
404 perror("open write");
405 exit(1);
406 }
407 i = be32toh(volhdr->voldir[i].bytes);
408 while (i > 0) {
409 if (read(fd, buf, sizeof(buf)) != sizeof(buf)) {
410 perror("read file");
411 exit(1);
412 }
413 fwrite(buf, 1, i > sizeof(buf) ? sizeof(buf) : i, fp);
414 i -= i > sizeof(buf) ? sizeof(buf) : i;
415 }
416 fclose(fp);
417 }
418
419 void
420 write_file(void)
421 {
422 FILE *fp;
423 int slot;
424 size_t namelen;
425 int block, i;
426 struct stat st;
427 char fbuf[512];
428
429 if (!opt_q)
430 printf("Writing file %s\n", ufilename);
431 if (stat(ufilename, &st) < 0) {
432 perror("stat");
433 exit(1);
434 }
435 if (!opt_q)
436 printf("File %s has %lld bytes\n", ufilename, st.st_size);
437 slot = -1;
438 for (i = 0; i < SGI_BOOT_BLOCK_MAXVOLDIRS; ++i) {
439 if (volhdr->voldir[i].name[0] == '\0' && slot < 0)
440 slot = i;
441 if (strcmp(vfilename, volhdr->voldir[i].name) == 0) {
442 slot = i;
443 break;
444 }
445 }
446 if (slot == -1) {
447 printf("No directory space for file %s\n", vfilename);
448 exit(1);
449 }
450 /* -w can overwrite, -a won't overwrite */
451 if (be32toh(volhdr->voldir[slot].block) > 0) {
452 if (!opt_q)
453 printf("File %s exists, removing old file\n",
454 vfilename);
455 volhdr->voldir[slot].name[0] = 0;
456 volhdr->voldir[slot].block = volhdr->voldir[slot].bytes = 0;
457 }
458 if (st.st_size == 0) {
459 printf("bad file size\n");
460 exit(1);
461 }
462 /* XXX assumes volume header starts at 0? */
463 block = allocate_space((int)st.st_size);
464 if (block < 0) {
465 printf("No space for file\n");
466 exit(1);
467 }
468
469 /*
470 * Make sure the name in the volume header is max. 8 chars,
471 * NOT including NUL.
472 */
473 namelen = strlen(vfilename);
474 if (namelen > sizeof(volhdr->voldir[slot].name)) {
475 printf("Warning: '%s' is too long for volume header, ",
476 vfilename);
477 namelen = sizeof(volhdr->voldir[slot].name);
478 printf("truncating to '%.8s'\n", vfilename);
479 }
480
481 /* Populate it w/ NULs */
482 memset(volhdr->voldir[slot].name, 0,
483 sizeof(volhdr->voldir[slot].name));
484 /* Then copy the name */
485 memcpy(volhdr->voldir[slot].name, vfilename, namelen);
486
487 volhdr->voldir[slot].block = htobe32(block);
488 volhdr->voldir[slot].bytes = htobe32(st.st_size);
489
490 write_volhdr();
491
492 /* write the file itself */
493 i = lseek(fd, block * 512, SEEK_SET);
494 if (i < 0) {
495 perror("lseek write");
496 exit(1);
497 }
498 i = st.st_size;
499 fp = fopen(ufilename, "r");
500 while (i > 0) {
501 fread(fbuf, 1, i > 512 ? 512 : i, fp);
502 if (write(fd, fbuf, 512) != 512) {
503 perror("write file");
504 exit(1);
505 }
506 i -= i > 512 ? 512 : i;
507 }
508 }
509
510 void
511 delete_file(void)
512 {
513 int i;
514
515 for (i = 0; i < SGI_BOOT_BLOCK_MAXVOLDIRS; ++i) {
516 if (strcmp(vfilename, volhdr->voldir[i].name) == 0) {
517 break;
518 }
519 }
520 if (i >= SGI_BOOT_BLOCK_MAXVOLDIRS) {
521 printf("File '%s' not found\n", vfilename);
522 exit(1);
523 }
524
525 /* XXX: we don't compact the file space, so get fragmentation */
526 volhdr->voldir[i].name[0] = '\0';
527 volhdr->voldir[i].block = volhdr->voldir[i].bytes = 0;
528 write_volhdr();
529 }
530
531 void
532 move_file(void)
533 {
534 char dstfile[sizeof(volhdr->voldir[0].name) + 1];
535 size_t namelen;
536 int i, slot = -1;
537
538 /*
539 * Make sure the name in the volume header is max. 8 chars,
540 * NOT including NUL.
541 */
542 namelen = strlen(ufilename);
543 if (namelen > sizeof(volhdr->voldir[0].name)) {
544 printf("Warning: '%s' is too long for volume header, ",
545 ufilename);
546 namelen = sizeof(volhdr->voldir[0].name);
547 printf("truncating to '%.8s'\n", ufilename);
548 }
549 memset(dstfile, 0, sizeof(dstfile));
550 memcpy(dstfile, ufilename, namelen);
551
552 for (i = 0; i < SGI_BOOT_BLOCK_MAXVOLDIRS; i++) {
553 if (strcmp(vfilename, volhdr->voldir[i].name) == 0) {
554 if (slot != -1) {
555 printf("Error: Cannot move '%s' to '%s' - "
556 "duplicate source files exist!\n",
557 vfilename, dstfile);
558 exit(1);
559 }
560 slot = i;
561 }
562 if (strcmp(dstfile, volhdr->voldir[i].name) == 0) {
563 printf("Error: Cannot move '%s' to '%s' - "
564 "destination file already exists!\n",
565 vfilename, dstfile);
566 exit(1);
567 }
568 }
569 if (slot == -1) {
570 printf("File '%s' not found\n", vfilename);
571 exit(1);
572 }
573
574 /* `dstfile' is already padded with NULs */
575 memcpy(volhdr->voldir[slot].name, dstfile,
576 sizeof(volhdr->voldir[slot].name));
577
578 write_volhdr();
579 }
580
581 void
582 modify_partition(void)
583 {
584 if (!opt_q)
585 printf("Modify partition %d start %d length %d\n",
586 partno, partfirst, partblocks);
587 if (partno < 0 || partno >= SGI_BOOT_BLOCK_MAXPARTITIONS) {
588 printf("Invalid partition number: %d\n", partno);
589 exit(1);
590 }
591 volhdr->partitions[partno].blocks = htobe32(partblocks);
592 volhdr->partitions[partno].first = htobe32(partfirst);
593 volhdr->partitions[partno].type = htobe32(parttype);
594 write_volhdr();
595 }
596
597 void
598 write_volhdr(void)
599 {
600 int i;
601
602 checksum_vol();
603
604 if (!opt_q)
605 display_vol();
606 if (!opt_f) {
607 printf("\nDo you want to update volume (y/n)? ");
608 i = getchar();
609 if (i != 'Y' && i != 'y')
610 exit(1);
611 }
612 i = lseek(fd, 0, SEEK_SET);
613 if (i < 0) {
614 perror("lseek 0");
615 exit(1);
616 }
617 i = write(fd, buf, 512);
618 if (i < 0)
619 perror("write volhdr");
620 }
621
622 int
623 allocate_space(int size)
624 {
625 int n, blocks;
626 int first;
627
628 blocks = (size + 511) / 512;
629 first = 2;
630 n = 0;
631 while (n < SGI_BOOT_BLOCK_MAXVOLDIRS) {
632 if (volhdr->voldir[n].name[0]) {
633 if (first < (be32toh(volhdr->voldir[n].block) +
634 (be32toh(volhdr->voldir[n].bytes) + 511) / 512) &&
635 (first + blocks) > be32toh(volhdr->voldir[n].block)) {
636 first = be32toh(volhdr->voldir[n].block) +
637 (be32toh(volhdr->voldir[n].bytes) + 511) / 512;
638 #if 0
639 printf("allocate: n=%d first=%d blocks=%d size=%d\n", n, first, blocks, size);
640 printf("%s %d %d\n", volhdr->voldir[n].name, volhdr->voldir[n].block, volhdr->voldir[n].bytes);
641 printf("first=%d block=%d last=%d end=%d\n", first, volhdr->voldir[n].block,
642 first + blocks - 1, volhdr->voldir[n].block + (volhdr->voldir[n].bytes + 511) / 512);
643 #endif
644 n = 0;
645 continue;
646 }
647 }
648 ++n;
649 }
650 #if HAVE_NBTOOL_CONFIG_H
651 if (first + blocks > (st.st_size / SGI_BOOT_BLOCK_BLOCKSIZE))
652 #else
653 if (first + blocks > lbl.d_secperunit)
654 #endif
655 first = -1;
656 /* XXX assumes volume header is partition 8 */
657 /* XXX assumes volume header starts at 0? */
658 if (first + blocks >= be32toh(volhdr->partitions[8].blocks))
659 first = -1;
660 return (first);
661 }
662
663 void
664 checksum_vol(void)
665 {
666 int32_t *l;
667 int i;
668
669 volhdr->checksum = checksum = 0;
670 l = (int32_t *)buf;
671 for (i = 0; i < 512 / 4; ++i)
672 checksum += be32toh(l[i]);
673 volhdr->checksum = htobe32(-checksum);
674 }
675
676 void
677 usage(void)
678 {
679 printf("Usage: sgivol [-qf] -i [-h vhsize] device\n"
680 " sgivol [-qf] -r vhfilename diskfilename device\n"
681 " sgivol [-qf] -w vhfilename diskfilename device\n"
682 " sgivol [-qf] -d vhfilename device\n"
683 " sgivol [-qf] -p partno partfirst partblocks "
684 "parttype device\n"
685 );
686 exit(0);
687 }
688