bad144.c revision 1.9 1 /*
2 * Copyright (c) 1980, 1986, 1988, 1993
3 * The Regents of the University of California. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 * must display the following acknowledgement:
15 * This product includes software developed by the University of
16 * California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34 #ifndef lint
35 static char copyright[] =
36 "@(#) Copyright (c) 1980, 1986, 1988, 1993\n\
37 The Regents of the University of California. All rights reserved.\n";
38 #endif not lint
39
40 #ifndef lint
41 /*static char sccsid[] = "from: @(#)bad144.c 8.1 (Berkeley) 6/6/93";*/
42 static char *rcsid = "$Id: bad144.c,v 1.9 1997/02/03 20:12:34 cgd Exp $";
43 #endif not lint
44
45 /*
46 * bad144
47 *
48 * This program prints and/or initializes a bad block record for a pack,
49 * in the format used by the DEC standard 144.
50 * It can also add bad sector(s) to the record, moving the sector
51 * replacements as necessary.
52 *
53 * It is preferable to write the bad information with a standard formatter,
54 * but this program will do.
55 *
56 * RP06 sectors are marked as bad by inverting the format bit in the
57 * header; on other drives the valid-sector bit is cleared.
58 */
59 #include <sys/param.h>
60 #include <sys/dkbad.h>
61 #include <sys/ioctl.h>
62 #include <sys/file.h>
63 #include <sys/disklabel.h>
64 #include <ufs/ffs/fs.h>
65
66 #include <stdio.h>
67 #include <string.h>
68 #include <paths.h>
69
70 #define RETRIES 10 /* number of retries on reading old sectors */
71 #ifdef i386 /* XXX */
72 #define RAWPART "d" /* disk partition containing badsector tables */
73 #else
74 #define RAWPART "c" /* disk partition containing badsector tables */
75 #endif
76
77 int fflag, add, copy, verbose, nflag;
78 int compare();
79 int dups;
80 int badfile = -1; /* copy of badsector table to use, -1 if any */
81 #define MAXSECSIZE 1024
82 struct dkbad curbad, oldbad;
83 #define DKBAD_MAGIC 0x4321
84
85 char label[BBSIZE];
86 daddr_t size, getold(), badsn();
87 struct disklabel *dp;
88 char name[BUFSIZ];
89 char *malloc();
90 off_t lseek();
91
92 main(argc, argv)
93 int argc;
94 char *argv[];
95 {
96 register struct bt_bad *bt;
97 daddr_t sn, bn[126];
98 int i, f, nbad, new, bad, errs;
99
100 argc--, argv++;
101 while (argc > 0 && **argv == '-') {
102 (*argv)++;
103 while (**argv) {
104 switch (**argv) {
105 #if vax
106 case 'f':
107 fflag++;
108 break;
109 #endif
110 case 'a':
111 add++;
112 break;
113 case 'c':
114 copy++;
115 break;
116 case 'v':
117 verbose++;
118 break;
119 case 'n':
120 nflag++;
121 verbose++;
122 break;
123 default:
124 if (**argv >= '0' && **argv <= '4') {
125 badfile = **argv - '0';
126 break;
127 }
128 goto usage;
129 }
130 (*argv)++;
131 }
132 argc--, argv++;
133 }
134 if (argc < 1) {
135 usage:
136 fprintf(stderr,
137 "usage: bad144 [ -f ] disk [ snum [ bn ... ] ]\n");
138 fprintf(stderr,
139 "to read or overwrite bad-sector table, e.g.: bad144 hp0\n");
140 fprintf(stderr,
141 "or bad144 -a [ -f ] [ -c ] disk bn ...\n");
142 fprintf(stderr, "where options are:\n");
143 fprintf(stderr, "\t-a add new bad sectors to the table\n");
144 fprintf(stderr, "\t-f reformat listed sectors as bad\n");
145 fprintf(stderr, "\t-c copy original sector to replacement\n");
146 exit(1);
147 }
148 if (argv[0][0] != '/')
149 (void)sprintf(name, "%sr%s%s", _PATH_DEV, argv[0], RAWPART);
150 else
151 strcpy(name, argv[0]);
152 f = open(name, argc == 1? O_RDONLY : O_RDWR);
153 if (f < 0)
154 Perror(name);
155 #ifdef was
156 if (read(f, label, sizeof(label)) < 0)
157 Perror("read");
158 for (dp = (struct disklabel *)(label + LABELOFFSET);
159 dp < (struct disklabel *)
160 (label + sizeof(label) - sizeof(struct disklabel));
161 dp = (struct disklabel *)((char *)dp + 64))
162 if (dp->d_magic == DISKMAGIC && dp->d_magic2 == DISKMAGIC)
163 break;
164 #else
165 /* obtain label and adjust to fit */
166 dp = (struct disklabel *)&label;
167 if (ioctl(f, DIOCGDINFO, dp) < 0)
168 Perror("ioctl DIOCGDINFO");
169 #endif
170 if (dp->d_magic != DISKMAGIC || dp->d_magic2 != DISKMAGIC
171 /* dkcksum(lp) != 0 */ ) {
172 fprintf(stderr, "Bad pack magic number (pack is unlabeled)\n");
173 exit(1);
174 }
175 if (dp->d_secsize > MAXSECSIZE || dp->d_secsize <= 0) {
176 fprintf(stderr, "Disk sector size too large/small (%d)\n",
177 dp->d_secsize);
178 exit(7);
179 }
180 #ifdef i386
181 if (dp->d_type == DTYPE_SCSI) {
182 fprintf(stderr, "SCSI disks don't use bad144!\n");
183 exit(1);
184 }
185 /* are we inside a DOS partition? */
186 if (dp->d_partitions[0].p_offset) {
187 /* yes, rules change. assume bad tables at end of partition C,
188 which maps all of DOS partition we are within -wfj */
189 size = dp->d_partitions[2].p_offset + dp->d_partitions[2].p_size;
190 } else
191 #endif
192 size = dp->d_nsectors * dp->d_ntracks * dp->d_ncylinders;
193 argc--;
194 argv++;
195 if (argc == 0) {
196 sn = getold(f, &oldbad);
197 printf("bad block information at sector %d in %s:\n",
198 sn, name);
199 printf("cartridge serial number: %d(10)\n", oldbad.bt_csn);
200 switch (oldbad.bt_flag) {
201
202 case (u_short)-1:
203 printf("alignment cartridge\n");
204 break;
205
206 case DKBAD_MAGIC:
207 break;
208
209 default:
210 printf("bt_flag=%x(16)?\n", oldbad.bt_flag);
211 break;
212 }
213 bt = oldbad.bt_bad;
214 for (i = 0; i < 126; i++) {
215 bad = (bt->bt_cyl<<16) + bt->bt_trksec;
216 if (bad < 0)
217 break;
218 printf("sn=%d, cn=%d, tn=%d, sn=%d\n", badsn(bt),
219 bt->bt_cyl, bt->bt_trksec>>8, bt->bt_trksec&0xff);
220 bt++;
221 }
222 (void) checkold(&oldbad);
223 exit(0);
224 }
225 if (add) {
226 /*
227 * Read in the old badsector table.
228 * Verify that it makes sense, and the bad sectors
229 * are in order. Copy the old table to the new one.
230 */
231 (void) getold(f, &oldbad);
232 i = checkold(&oldbad);
233 if (verbose)
234 printf("Had %d bad sectors, adding %d\n", i, argc);
235 if (i + argc > 126) {
236 printf("bad144: not enough room for %d more sectors\n",
237 argc);
238 printf("limited to 126 by information format\n");
239 exit(1);
240 }
241 curbad = oldbad;
242 } else {
243 curbad.bt_csn = atoi(*argv++);
244 argc--;
245 curbad.bt_mbz = 0;
246 curbad.bt_flag = DKBAD_MAGIC;
247 if (argc > 126) {
248 printf("bad144: too many bad sectors specified\n");
249 printf("limited to 126 by information format\n");
250 exit(1);
251 }
252 i = 0;
253 }
254 errs = 0;
255 new = argc;
256 while (argc > 0) {
257 daddr_t sn = atoi(*argv++);
258 argc--;
259 if (sn < 0 || sn >= size) {
260 printf("%d: out of range [0,%d) for disk %s\n",
261 sn, size, dp->d_typename);
262 errs++;
263 continue;
264 }
265 bn[i] = sn;
266 curbad.bt_bad[i].bt_cyl = sn / (dp->d_nsectors*dp->d_ntracks);
267 sn %= (dp->d_nsectors*dp->d_ntracks);
268 curbad.bt_bad[i].bt_trksec =
269 ((sn/dp->d_nsectors) << 8) + (sn%dp->d_nsectors);
270 i++;
271 }
272 if (errs)
273 exit(1);
274 nbad = i;
275 while (i < 126) {
276 curbad.bt_bad[i].bt_trksec = -1;
277 curbad.bt_bad[i].bt_cyl = -1;
278 i++;
279 }
280 if (add) {
281 /*
282 * Sort the new bad sectors into the list.
283 * Then shuffle the replacement sectors so that
284 * the previous bad sectors get the same replacement data.
285 */
286 qsort((char *)curbad.bt_bad, nbad, sizeof (struct bt_bad),
287 compare);
288 if (dups) {
289 fprintf(stderr,
290 "bad144: bad sectors have been duplicated; can't add existing sectors\n");
291 exit(3);
292 }
293 shift(f, nbad, nbad-new);
294 }
295 if (badfile == -1)
296 i = 0;
297 else
298 i = badfile * 2;
299 for (; i < 10 && i < dp->d_nsectors; i += 2) {
300 if (lseek(f, dp->d_secsize * (size - dp->d_nsectors + i),
301 L_SET) < 0)
302 Perror("lseek");
303 if (verbose)
304 printf("write badsect file at %d\n",
305 size - dp->d_nsectors + i);
306 if (nflag == 0 && write(f, (caddr_t)&curbad, sizeof(curbad)) !=
307 sizeof(curbad)) {
308 char msg[80];
309 (void)sprintf(msg, "bad144: write bad sector file %d",
310 i/2);
311 perror(msg);
312 }
313 if (badfile != -1)
314 break;
315 }
316 #ifdef vax
317 if (nflag == 0 && fflag)
318 for (i = nbad - new; i < nbad; i++)
319 format(f, bn[i]);
320 #endif
321 #ifdef DIOCSBAD
322 if (nflag == 0 && ioctl(f, DIOCSBAD, (caddr_t)&curbad) < 0)
323 fprintf(stderr,
324 "Can't sync bad-sector file; reboot for changes to take effect\n");
325 #endif
326 if ((dp->d_flags & D_BADSECT) == 0 && nflag == 0) {
327 dp->d_flags |= D_BADSECT;
328 if (ioctl(f, DIOCWDINFO, dp) < 0) {
329 perror("label");
330 fprintf(stderr, "Can't write label to enable bad sector handling\n");
331 exit(1);
332 }
333 }
334 exit(0);
335 }
336
337 daddr_t
338 getold(f, bad)
339 struct dkbad *bad;
340 {
341 register int i;
342 daddr_t sn;
343 char msg[80];
344
345 if (badfile == -1)
346 i = 0;
347 else
348 i = badfile * 2;
349 for (; i < 10 && i < dp->d_nsectors; i += 2) {
350 sn = size - dp->d_nsectors + i;
351 if (lseek(f, sn * dp->d_secsize, L_SET) < 0)
352 Perror("lseek");
353 if (read(f, (char *) bad, dp->d_secsize) == dp->d_secsize) {
354 if (i > 0)
355 printf("Using bad-sector file %d\n", i/2);
356 return(sn);
357 }
358 (void)sprintf(msg, "bad144: read bad sector file at sn %d", sn);
359 perror(msg);
360 if (badfile != -1)
361 break;
362 }
363 fprintf(stderr, "bad144: %s: can't read bad block info\n", name);
364 exit(1);
365 /*NOTREACHED*/
366 }
367
368 checkold()
369 {
370 register int i;
371 register struct bt_bad *bt;
372 daddr_t sn, lsn;
373 int errors = 0, warned = 0;
374
375 if (oldbad.bt_flag != DKBAD_MAGIC) {
376 fprintf(stderr, "bad144: %s: bad flag in bad-sector table\n",
377 name);
378 errors++;
379 }
380 if (oldbad.bt_mbz != 0) {
381 fprintf(stderr, "bad144: %s: bad magic number\n", name);
382 errors++;
383 }
384 bt = oldbad.bt_bad;
385 for (i = 0; i < 126; i++, bt++) {
386 if (bt->bt_cyl == 0xffff && bt->bt_trksec == 0xffff)
387 break;
388 if ((bt->bt_cyl >= dp->d_ncylinders) ||
389 ((bt->bt_trksec >> 8) >= dp->d_ntracks) ||
390 ((bt->bt_trksec & 0xff) >= dp->d_nsectors)) {
391 fprintf(stderr,
392 "bad144: cyl/trk/sect out of range in existing entry: ");
393 fprintf(stderr, "sn=%d, cn=%d, tn=%d, sn=%d\n",
394 badsn(bt), bt->bt_cyl, bt->bt_trksec>>8,
395 bt->bt_trksec & 0xff);
396 errors++;
397 }
398 sn = (bt->bt_cyl * dp->d_ntracks +
399 (bt->bt_trksec >> 8)) *
400 dp->d_nsectors + (bt->bt_trksec & 0xff);
401 if (i > 0 && sn < lsn && !warned) {
402 fprintf(stderr,
403 "bad144: bad sector file is out of order\n");
404 errors++;
405 warned++;
406 }
407 if (i > 0 && sn == lsn) {
408 fprintf(stderr,
409 "bad144: bad sector file contains duplicates (sn %d)\n",
410 sn);
411 errors++;
412 }
413 lsn = sn;
414 }
415 if (errors)
416 exit(1);
417 return (i);
418 }
419
420 /*
421 * Move the bad sector replacements
422 * to make room for the new bad sectors.
423 * new is the new number of bad sectors, old is the previous count.
424 */
425 shift(f, new, old)
426 {
427 daddr_t repl;
428
429 /*
430 * First replacement is last sector of second-to-last track.
431 */
432 repl = size - dp->d_nsectors - 1;
433 new--; old--;
434 while (new >= 0 && new != old) {
435 if (old < 0 ||
436 compare(&curbad.bt_bad[new], &oldbad.bt_bad[old]) > 0) {
437 /*
438 * Insert new replacement here-- copy original
439 * sector if requested and possible,
440 * otherwise write a zero block.
441 */
442 if (!copy ||
443 !blkcopy(f, badsn(&curbad.bt_bad[new]), repl - new))
444 blkzero(f, repl - new);
445 } else {
446 if (blkcopy(f, repl - old, repl - new) == 0)
447 fprintf(stderr,
448 "Can't copy replacement sector %d to %d\n",
449 repl-old, repl-new);
450 old--;
451 }
452 new--;
453 }
454 }
455
456 char *buf;
457
458 /*
459 * Copy disk sector s1 to s2.
460 */
461 blkcopy(f, s1, s2)
462 daddr_t s1, s2;
463 {
464 register tries, n;
465
466 if (buf == (char *)NULL) {
467 buf = malloc((unsigned)dp->d_secsize);
468 if (buf == (char *)NULL) {
469 fprintf(stderr, "Out of memory\n");
470 exit(20);
471 }
472 }
473 for (tries = 0; tries < RETRIES; tries++) {
474 if (lseek(f, dp->d_secsize * s1, L_SET) < 0)
475 Perror("lseek");
476 if ((n = read(f, buf, dp->d_secsize)) == dp->d_secsize)
477 break;
478 }
479 if (n != dp->d_secsize) {
480 fprintf(stderr, "bad144: can't read sector, %d: ", s1);
481 if (n < 0)
482 perror((char *)0);
483 return(0);
484 }
485 if (lseek(f, dp->d_secsize * s2, L_SET) < 0)
486 Perror("lseek");
487 if (verbose)
488 printf("copying %d to %d\n", s1, s2);
489 if (nflag == 0 && write(f, buf, dp->d_secsize) != dp->d_secsize) {
490 fprintf(stderr,
491 "bad144: can't write replacement sector, %d: ", s2);
492 perror((char *)0);
493 return(0);
494 }
495 return(1);
496 }
497
498 char *zbuf;
499
500 blkzero(f, sn)
501 daddr_t sn;
502 {
503
504 if (zbuf == (char *)NULL) {
505 zbuf = malloc((unsigned)dp->d_secsize);
506 if (zbuf == (char *)NULL) {
507 fprintf(stderr, "Out of memory\n");
508 exit(20);
509 }
510 }
511 if (lseek(f, dp->d_secsize * sn, L_SET) < 0)
512 Perror("lseek");
513 if (verbose)
514 printf("zeroing %d\n", sn);
515 if (nflag == 0 && write(f, zbuf, dp->d_secsize) != dp->d_secsize) {
516 fprintf(stderr,
517 "bad144: can't write replacement sector, %d: ", sn);
518 perror((char *)0);
519 }
520 }
521
522 compare(b1, b2)
523 register struct bt_bad *b1, *b2;
524 {
525 if (b1->bt_cyl > b2->bt_cyl)
526 return(1);
527 if (b1->bt_cyl < b2->bt_cyl)
528 return(-1);
529 if (b1->bt_trksec == b2->bt_trksec)
530 dups++;
531 return (b1->bt_trksec - b2->bt_trksec);
532 }
533
534 daddr_t
535 badsn(bt)
536 register struct bt_bad *bt;
537 {
538 return ((bt->bt_cyl*dp->d_ntracks + (bt->bt_trksec>>8)) * dp->d_nsectors
539 + (bt->bt_trksec&0xff));
540 }
541
542 #ifdef vax
543
544 struct rp06hdr {
545 short h_cyl;
546 short h_trksec;
547 short h_key1;
548 short h_key2;
549 char h_data[512];
550 #define RP06_FMT 010000 /* 1 == 16 bit, 0 == 18 bit */
551 };
552
553 /*
554 * Most massbus and unibus drives
555 * have headers of this form
556 */
557 struct hpuphdr {
558 u_short hpup_cyl;
559 u_char hpup_sect;
560 u_char hpup_track;
561 char hpup_data[512];
562 #define HPUP_OKSECT 0xc000 /* this normally means sector is good */
563 #define HPUP_16BIT 0x1000 /* 1 == 16 bit format */
564 };
565 int rp06format(), hpupformat();
566
567 struct formats {
568 char *f_name; /* disk name */
569 int f_bufsize; /* size of sector + header */
570 int f_bic; /* value to bic in hpup_cyl */
571 int (*f_routine)(); /* routine for special handling */
572 } formats[] = {
573 { "rp06", sizeof (struct rp06hdr), RP06_FMT, rp06format },
574 { "eagle", sizeof (struct hpuphdr), HPUP_OKSECT, hpupformat },
575 { "capricorn", sizeof (struct hpuphdr), HPUP_OKSECT, hpupformat },
576 { "rm03", sizeof (struct hpuphdr), HPUP_OKSECT, hpupformat },
577 { "rm05", sizeof (struct hpuphdr), HPUP_OKSECT, hpupformat },
578 { "9300", sizeof (struct hpuphdr), HPUP_OKSECT, hpupformat },
579 { "9766", sizeof (struct hpuphdr), HPUP_OKSECT, hpupformat },
580 { 0, 0, 0, 0 }
581 };
582
583 /*ARGSUSED*/
584 hpupformat(fp, dp, blk, buf, count)
585 struct formats *fp;
586 struct disklabel *dp;
587 daddr_t blk;
588 char *buf;
589 int count;
590 {
591 struct hpuphdr *hdr = (struct hpuphdr *)buf;
592 int sect;
593
594 if (count < sizeof(struct hpuphdr)) {
595 hdr->hpup_cyl = (HPUP_OKSECT | HPUP_16BIT) |
596 (blk / (dp->d_nsectors * dp->d_ntracks));
597 sect = blk % (dp->d_nsectors * dp->d_ntracks);
598 hdr->hpup_track = (u_char)(sect / dp->d_nsectors);
599 hdr->hpup_sect = (u_char)(sect % dp->d_nsectors);
600 }
601 return (0);
602 }
603
604 /*ARGSUSED*/
605 rp06format(fp, dp, blk, buf, count)
606 struct formats *fp;
607 struct disklabel *dp;
608 daddr_t blk;
609 char *buf;
610 int count;
611 {
612
613 if (count < sizeof(struct rp06hdr)) {
614 fprintf(stderr, "Can't read header on blk %d, can't reformat\n",
615 blk);
616 return (-1);
617 }
618 return (0);
619 }
620
621 format(fd, blk)
622 int fd;
623 daddr_t blk;
624 {
625 register struct formats *fp;
626 static char *buf;
627 static char bufsize;
628 struct format_op fop;
629 int n;
630
631 for (fp = formats; fp->f_name; fp++)
632 if (strcmp(dp->d_typename, fp->f_name) == 0)
633 break;
634 if (fp->f_name == 0) {
635 fprintf(stderr, "bad144: don't know how to format %s disks\n",
636 dp->d_typename);
637 exit(2);
638 }
639 if (buf && bufsize < fp->f_bufsize) {
640 free(buf);
641 buf = NULL;
642 }
643 if (buf == NULL)
644 buf = malloc((unsigned)fp->f_bufsize);
645 if (buf == NULL) {
646 fprintf(stderr, "bad144: can't allocate sector buffer\n");
647 exit(3);
648 }
649 bufsize = fp->f_bufsize;
650 /*
651 * Here we do the actual formatting. All we really
652 * do is rewrite the sector header and flag the bad sector
653 * according to the format table description. If a special
654 * purpose format routine is specified, we allow it to
655 * process the sector as well.
656 */
657 if (verbose)
658 printf("format blk %d\n", blk);
659 bzero((char *)&fop, sizeof(fop));
660 fop.df_buf = buf;
661 fop.df_count = fp->f_bufsize;
662 fop.df_startblk = blk;
663 bzero(buf, fp->f_bufsize);
664 if (ioctl(fd, DIOCRFORMAT, &fop) < 0)
665 perror("bad144: read format");
666 if (fp->f_routine &&
667 (*fp->f_routine)(fp, dp, blk, buf, fop.df_count) != 0)
668 return;
669 if (fp->f_bic) {
670 struct hpuphdr *xp = (struct hpuphdr *)buf;
671
672 xp->hpup_cyl &= ~fp->f_bic;
673 }
674 if (nflag)
675 return;
676 bzero((char *)&fop, sizeof(fop));
677 fop.df_buf = buf;
678 fop.df_count = fp->f_bufsize;
679 fop.df_startblk = blk;
680 if (ioctl(fd, DIOCWFORMAT, &fop) < 0)
681 Perror("write format");
682 if (fop.df_count != fp->f_bufsize) {
683 char msg[80];
684 (void)sprintf(msg, "bad144: write format %d", blk);
685 perror(msg);
686 }
687 }
688 #endif
689
690 Perror(op)
691 char *op;
692 {
693
694 fprintf(stderr, "bad144: "); perror(op);
695 exit(4);
696 }
697