tape.c revision 1.48 1 /* $NetBSD: tape.c,v 1.48 2003/04/02 10:39:31 fvdl Exp $ */
2
3 /*
4 * Copyright (c) 1983, 1993
5 * The Regents of the University of California. All rights reserved.
6 * (c) UNIX System Laboratories, Inc.
7 * All or some portions of this file are derived from material licensed
8 * to the University of California by American Telephone and Telegraph
9 * Co. or Unix System Laboratories, Inc. and are reproduced herein with
10 * the permission of UNIX System Laboratories, Inc.
11 *
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 * 3. All advertising materials mentioning features or use of this software
21 * must display the following acknowledgement:
22 * This product includes software developed by the University of
23 * California, Berkeley and its contributors.
24 * 4. Neither the name of the University nor the names of its contributors
25 * may be used to endorse or promote products derived from this software
26 * without specific prior written permission.
27 *
28 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
29 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
30 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
31 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
32 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
33 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
34 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
35 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
36 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
37 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
38 * SUCH DAMAGE.
39 */
40
41 #include <sys/cdefs.h>
42 #ifndef lint
43 #if 0
44 static char sccsid[] = "@(#)tape.c 8.9 (Berkeley) 5/1/95";
45 #else
46 __RCSID("$NetBSD: tape.c,v 1.48 2003/04/02 10:39:31 fvdl Exp $");
47 #endif
48 #endif /* not lint */
49
50 #include <sys/param.h>
51 #include <sys/file.h>
52 #include <sys/ioctl.h>
53 #include <sys/mtio.h>
54 #include <sys/stat.h>
55
56 #include <ufs/ufs/dinode.h>
57 #include <protocols/dumprestore.h>
58
59 #include <errno.h>
60 #include <paths.h>
61 #include <setjmp.h>
62 #include <stdio.h>
63 #include <stdlib.h>
64 #include <string.h>
65 #include <time.h>
66 #include <unistd.h>
67
68 #include "restore.h"
69 #include "extern.h"
70
71 static u_int32_t fssize = MAXBSIZE;
72 static int mt = -1;
73 static int pipein = 0;
74 static char magtape[BUFSIZ];
75 static int blkcnt;
76 static int numtrec;
77 static char *tapebuf;
78 static union u_spcl endoftapemark;
79 static int blksread; /* blocks read since last header */
80 static int tpblksread = 0; /* TP_BSIZE blocks read */
81 static int tapesread;
82 static jmp_buf restart;
83 static int gettingfile = 0; /* restart has a valid frame */
84 #ifdef RRESTORE
85 static char *host = NULL;
86 #endif
87
88 static int ofile;
89 static char *map;
90 static char lnkbuf[MAXPATHLEN + 1];
91 static int pathlen;
92
93 int oldinofmt; /* old inode format conversion required */
94 int Bcvt; /* Swap Bytes (for CCI or sun) */
95
96 #define FLUSHTAPEBUF() blkcnt = ntrec + 1
97
98 union u_ospcl {
99 char dummy[TP_BSIZE];
100 struct s_ospcl {
101 int32_t c_type;
102 int32_t c_date;
103 int32_t c_ddate;
104 int32_t c_volume;
105 int32_t c_tapea;
106 u_int16_t c_inumber;
107 int32_t c_magic;
108 int32_t c_checksum;
109 struct odinode {
110 unsigned short odi_mode;
111 u_int16_t odi_nlink;
112 u_int16_t odi_uid;
113 u_int16_t odi_gid;
114 int32_t odi_size;
115 int32_t odi_rdev;
116 char odi_addr[36];
117 int32_t odi_atime;
118 int32_t odi_mtime;
119 int32_t odi_ctime;
120 } c_odinode;
121 int32_t c_count;
122 char c_addr[256];
123 } s_ospcl;
124 };
125
126 static void accthdr __P((struct s_spcl *));
127 static int checksum __P((int *));
128 static void findinode __P((struct s_spcl *));
129 static void findtapeblksize __P((void));
130 static int gethead __P((struct s_spcl *));
131 static void readtape __P((char *));
132 static void setdumpnum __P((void));
133 static void terminateinput __P((void));
134 static void xtrfile __P((char *, long));
135 static void xtrlnkfile __P((char *, long));
136 static void xtrlnkskip __P((char *, long));
137 static void xtrmap __P((char *, long));
138 static void xtrmapskip __P((char *, long));
139 static void xtrskip __P((char *, long));
140 static void swap_header __P((struct s_spcl *));
141 static void swap_old_header __P((struct s_ospcl *));
142
143 /*
144 * Set up an input source
145 */
146 void
147 setinput(source)
148 char *source;
149 {
150 FLUSHTAPEBUF();
151 if (bflag)
152 newtapebuf(ntrec);
153 else
154 newtapebuf(NTREC > HIGHDENSITYTREC ? NTREC : HIGHDENSITYTREC);
155 terminal = stdin;
156
157 #ifdef RRESTORE
158 if (strchr(source, ':')) {
159 host = source;
160 source = strchr(host, ':');
161 *source++ = '\0';
162 if (rmthost(host) == 0)
163 exit(1);
164 } else
165 #endif
166 if (strcmp(source, "-") == 0) {
167 /*
168 * Since input is coming from a pipe we must establish
169 * our own connection to the terminal.
170 */
171 terminal = fopen(_PATH_TTY, "r");
172 if (terminal == NULL) {
173 (void)fprintf(stderr, "cannot open %s: %s\n",
174 _PATH_TTY, strerror(errno));
175 terminal = fopen(_PATH_DEVNULL, "r");
176 if (terminal == NULL) {
177 (void)fprintf(stderr, "cannot open %s: %s\n",
178 _PATH_DEVNULL, strerror(errno));
179 exit(1);
180 }
181 }
182 pipein++;
183 }
184 (void) strcpy(magtape, source);
185 }
186
187 void
188 newtapebuf(size)
189 long size;
190 {
191 static int tapebufsize = -1;
192
193 ntrec = size;
194 if (size <= tapebufsize)
195 return;
196 if (tapebuf != NULL)
197 free(tapebuf);
198 tapebuf = malloc(size * TP_BSIZE);
199 if (tapebuf == NULL) {
200 fprintf(stderr, "Cannot allocate space for tape buffer\n");
201 exit(1);
202 }
203 tapebufsize = size;
204 }
205
206 /*
207 * Verify that the tape drive can be accessed and
208 * that it actually is a dump tape.
209 */
210 void
211 setup()
212 {
213 int i, j, *ip;
214 struct stat stbuf;
215
216 vprintf(stdout, "Verify tape and initialize maps\n");
217 #ifdef RRESTORE
218 if (host)
219 mt = rmtopen(magtape, 0);
220 else
221 #endif
222 if (pipein)
223 mt = 0;
224 else
225 mt = open(magtape, O_RDONLY, 0);
226 if (mt < 0) {
227 fprintf(stderr, "%s: %s\n", magtape, strerror(errno));
228 exit(1);
229 }
230 volno = 1;
231 setdumpnum();
232 FLUSHTAPEBUF();
233 if (!pipein && !bflag)
234 findtapeblksize();
235 if (gethead(&spcl) == FAIL) {
236 blkcnt--; /* push back this block */
237 blksread--;
238 tpblksread--;
239 cvtflag++;
240 if (gethead(&spcl) == FAIL) {
241 fprintf(stderr, "Tape is not a dump tape\n");
242 exit(1);
243 }
244 fprintf(stderr, "Converting to new file system format.\n");
245 }
246 if (pipein) {
247 endoftapemark.s_spcl.c_magic = cvtflag ? OFS_MAGIC :
248 FS_UFS2_MAGIC;
249 endoftapemark.s_spcl.c_type = TS_END;
250 ip = (int *)&endoftapemark;
251 j = sizeof(union u_spcl) / sizeof(int);
252 i = 0;
253 do
254 i += *ip++;
255 while (--j);
256 endoftapemark.s_spcl.c_checksum = CHECKSUM - i;
257 }
258 if (vflag || command == 't')
259 printdumpinfo();
260 dumptime = spcl.c_ddate;
261 dumpdate = spcl.c_date;
262 if (stat(".", &stbuf) < 0) {
263 fprintf(stderr, "cannot stat .: %s\n", strerror(errno));
264 exit(1);
265 }
266 if (stbuf.st_blksize >= TP_BSIZE && stbuf.st_blksize <= MAXBSIZE)
267 fssize = stbuf.st_blksize;
268 if (((fssize - 1) & fssize) != 0) {
269 fprintf(stderr, "bad block size %d\n", fssize);
270 exit(1);
271 }
272 if (spcl.c_volume != 1) {
273 fprintf(stderr, "Tape is not volume 1 of the dump\n");
274 exit(1);
275 }
276 if (gethead(&spcl) == FAIL) {
277 dprintf(stdout, "header read failed at %d blocks\n", blksread);
278 panic("no header after volume mark!\n");
279 }
280 findinode(&spcl);
281 if (spcl.c_type != TS_CLRI) {
282 fprintf(stderr, "Cannot find file removal list\n");
283 exit(1);
284 }
285 maxino = (spcl.c_count * TP_BSIZE * NBBY) + 1;
286 dprintf(stdout, "maxino = %d\n", maxino);
287 map = calloc((unsigned)1, (unsigned)howmany(maxino, NBBY));
288 if (map == NULL)
289 panic("no memory for active inode map\n");
290 usedinomap = map;
291 curfile.action = USING;
292 getfile(xtrmap, xtrmapskip);
293 if (spcl.c_type != TS_BITS) {
294 fprintf(stderr, "Cannot find file dump list\n");
295 exit(1);
296 }
297 map = calloc((unsigned)1, (unsigned)howmany(maxino, NBBY));
298 if (map == (char *)NULL)
299 panic("no memory for file dump list\n");
300 dumpmap = map;
301 curfile.action = USING;
302 getfile(xtrmap, xtrmapskip);
303 /*
304 * If there may be whiteout entries on the tape, pretend that the
305 * whiteout inode exists, so that the whiteout entries can be
306 * extracted.
307 */
308 if (oldinofmt == 0)
309 SETINO(WINO, dumpmap);
310 }
311
312 /*
313 * Prompt user to load a new dump volume.
314 * "Nextvol" is the next suggested volume to use.
315 * This suggested volume is enforced when doing full
316 * or incremental restores, but can be overrridden by
317 * the user when only extracting a subset of the files.
318 */
319 void
320 getvol(nextvol)
321 int nextvol;
322 {
323 int newvol, savecnt, wantnext, i;
324 union u_spcl tmpspcl;
325 # define tmpbuf tmpspcl.s_spcl
326 char buf[TP_BSIZE];
327
328 newvol = savecnt = wantnext = 0;
329 if (nextvol == 1) {
330 tapesread = 0;
331 gettingfile = 0;
332 }
333 if (pipein) {
334 if (nextvol != 1)
335 panic("Changing volumes on pipe input?\n");
336 if (volno == 1)
337 return;
338 goto gethdr;
339 }
340 savecnt = blksread;
341 again:
342 if (pipein)
343 exit(1); /* pipes do not get a second chance */
344 if (command == 'R' || command == 'r' || curfile.action != SKIP) {
345 newvol = nextvol;
346 wantnext = 1;
347 } else {
348 newvol = 0;
349 wantnext = 0;
350 }
351 while (newvol <= 0) {
352 if (tapesread == 0) {
353 fprintf(stderr, "%s%s%s%s%s",
354 "You have not read any tapes yet.\n",
355 "Unless you know which volume your",
356 " file(s) are on you should start\n",
357 "with the last volume and work",
358 " towards the first.\n");
359 fprintf(stderr,
360 "(Use 1 for the first volume/tape, etc.)\n");
361 } else {
362 fprintf(stderr, "You have read volumes");
363 strcpy(buf, ": ");
364 for (i = 1; i < 32; i++)
365 if (tapesread & (1 << i)) {
366 fprintf(stderr, "%s%d", buf, i);
367 strcpy(buf, ", ");
368 }
369 fprintf(stderr, "\n");
370 }
371 do {
372 fprintf(stderr, "Specify next volume #: ");
373 (void) fflush(stderr);
374 (void) fgets(buf, BUFSIZ, terminal);
375 } while (!feof(terminal) && buf[0] == '\n');
376 if (feof(terminal))
377 exit(1);
378 newvol = atoi(buf);
379 if (newvol <= 0) {
380 fprintf(stderr,
381 "Volume numbers are positive numerics\n");
382 }
383 }
384 if (newvol == volno) {
385 tapesread |= 1 << volno;
386 return;
387 }
388 closemt();
389 fprintf(stderr, "Mount tape volume %d\n", newvol);
390 fprintf(stderr, "Enter ``none'' if there are no more tapes\n");
391 fprintf(stderr, "otherwise enter tape name (default: %s) ", magtape);
392 (void) fflush(stderr);
393 (void) fgets(buf, BUFSIZ, terminal);
394 if (feof(terminal))
395 exit(1);
396 if (!strcmp(buf, "none\n")) {
397 terminateinput();
398 return;
399 }
400 if (buf[0] != '\n') {
401 (void) strcpy(magtape, buf);
402 magtape[strlen(magtape) - 1] = '\0';
403 }
404 #ifdef RRESTORE
405 if (host)
406 mt = rmtopen(magtape, 0);
407 else
408 #endif
409 mt = open(magtape, O_RDONLY, 0);
410
411 if (mt == -1) {
412 fprintf(stderr, "Cannot open %s\n", magtape);
413 volno = -1;
414 goto again;
415 }
416 gethdr:
417 volno = newvol;
418 setdumpnum();
419 FLUSHTAPEBUF();
420 if (gethead(&tmpbuf) == FAIL) {
421 dprintf(stdout, "header read failed at %d blocks\n", blksread);
422 fprintf(stderr, "tape is not dump tape\n");
423 volno = 0;
424 goto again;
425 }
426 if (tmpbuf.c_volume != volno) {
427 fprintf(stderr,
428 "Volume mismatch: expecting %d, tape header claims it is %d\n",
429 volno, tmpbuf.c_volume);
430 volno = 0;
431 goto again;
432 }
433 if (tmpbuf.c_date != dumpdate || tmpbuf.c_ddate != dumptime) {
434 time_t ttime = tmpbuf.c_date;
435 fprintf(stderr, "Wrong dump date\n\tgot: %s",
436 ctime(&ttime));
437 fprintf(stderr, "\twanted: %s", ctime(&dumpdate));
438 volno = 0;
439 goto again;
440 }
441 tapesread |= 1 << volno;
442 blksread = savecnt;
443 /*
444 * If continuing from the previous volume, skip over any
445 * blocks read already at the end of the previous volume.
446 *
447 * If coming to this volume at random, skip to the beginning
448 * of the next record.
449 */
450 dprintf(stdout, "read %ld recs, tape starts with %ld\n",
451 (long)tpblksread, (long)tmpbuf.c_firstrec);
452 if (tmpbuf.c_type == TS_TAPE && (tmpbuf.c_flags & DR_NEWHEADER)) {
453 if (!wantnext) {
454 tpblksread = tmpbuf.c_firstrec;
455 for (i = tmpbuf.c_count; i > 0; i--)
456 readtape(buf);
457 } else if (tmpbuf.c_firstrec > 0 &&
458 tmpbuf.c_firstrec < tpblksread - 1) {
459 /*
460 * -1 since we've read the volume header
461 */
462 i = tpblksread - tmpbuf.c_firstrec - 1;
463 dprintf(stderr, "Skipping %d duplicate record%s.\n",
464 i, i > 1 ? "s" : "");
465 while (--i >= 0)
466 readtape(buf);
467 }
468 }
469 if (curfile.action == USING) {
470 if (volno == 1)
471 panic("active file into volume 1\n");
472 return;
473 }
474 /*
475 * Skip up to the beginning of the next record
476 */
477 if (tmpbuf.c_type == TS_TAPE && (tmpbuf.c_flags & DR_NEWHEADER))
478 for (i = tmpbuf.c_count; i > 0; i--)
479 readtape(buf);
480 (void) gethead(&spcl);
481 findinode(&spcl);
482 if (gettingfile) {
483 gettingfile = 0;
484 longjmp(restart, 1);
485 }
486 }
487
488 /*
489 * Handle unexpected EOF.
490 */
491 static void
492 terminateinput()
493 {
494
495 if (gettingfile && curfile.action == USING) {
496 printf("Warning: %s %s\n",
497 "End-of-input encountered while extracting", curfile.name);
498 }
499 curfile.name = "<name unknown>";
500 curfile.action = UNKNOWN;
501 curfile.mode = 0;
502 curfile.ino = maxino;
503 if (gettingfile) {
504 gettingfile = 0;
505 longjmp(restart, 1);
506 }
507 }
508
509 /*
510 * handle multiple dumps per tape by skipping forward to the
511 * appropriate one.
512 */
513 static void
514 setdumpnum()
515 {
516 struct mtop tcom;
517
518 if (dumpnum == 1 || volno != 1)
519 return;
520 if (pipein) {
521 fprintf(stderr, "Cannot have multiple dumps on pipe input\n");
522 exit(1);
523 }
524 tcom.mt_op = MTFSF;
525 tcom.mt_count = dumpnum - 1;
526 #ifdef RRESTORE
527 if (host)
528 rmtioctl(MTFSF, dumpnum - 1);
529 else
530 #endif
531 if (ioctl(mt, (int)MTIOCTOP, (char *)&tcom) < 0)
532 fprintf(stderr, "ioctl MTFSF: %s\n", strerror(errno));
533 }
534
535 void
536 printdumpinfo()
537 {
538 time_t ttime;
539
540 ttime = spcl.c_date;
541 fprintf(stdout, "Dump date: %s", ctime(&ttime));
542 ttime = spcl.c_ddate;
543 fprintf(stdout, "Dumped from: %s",
544 (spcl.c_ddate == 0) ? "the epoch\n" : ctime(&ttime));
545 fprintf(stderr, "Level %d dump of %s on %s:%s\n",
546 spcl.c_level, spcl.c_filesys,
547 *spcl.c_host? spcl.c_host: "[unknown]", spcl.c_dev);
548 fprintf(stderr, "Label: %s\n", spcl.c_label);
549 }
550
551 int
552 extractfile(name)
553 char *name;
554 {
555 int flags;
556 uid_t uid;
557 gid_t gid;
558 mode_t mode;
559 struct timeval mtimep[2], ctimep[2];
560 struct entry *ep;
561 int setbirth;
562
563 curfile.name = name;
564 curfile.action = USING;
565 mtimep[0].tv_sec = curfile.atime_sec;
566 mtimep[0].tv_usec = curfile.atime_nsec / 1000;
567 mtimep[1].tv_sec = curfile.mtime_sec;
568 mtimep[1].tv_usec = curfile.mtime_nsec / 1000;
569
570 setbirth = curfile.birthtime_sec != 0;
571
572 if (setbirth) {
573 ctimep[0].tv_sec = curfile.atime_sec;
574 ctimep[0].tv_usec = curfile.atime_nsec / 1000;
575 ctimep[1].tv_sec = curfile.birthtime_sec;
576 ctimep[1].tv_usec = curfile.birthtime_nsec / 1000;
577 }
578 uid = curfile.uid;
579 gid = curfile.gid;
580 mode = curfile.mode;
581 flags = curfile.file_flags;
582 switch (mode & IFMT) {
583
584 default:
585 fprintf(stderr, "%s: unknown file mode 0%o\n", name, mode);
586 skipfile();
587 return (FAIL);
588
589 case IFSOCK:
590 vprintf(stdout, "skipped socket %s\n", name);
591 skipfile();
592 return (GOOD);
593
594 case IFDIR:
595 if (mflag) {
596 ep = lookupname(name);
597 if (ep == NULL || ep->e_flags & EXTRACT)
598 panic("unextracted directory %s\n", name);
599 skipfile();
600 return (GOOD);
601 }
602 vprintf(stdout, "extract file %s\n", name);
603 return (genliteraldir(name, curfile.ino));
604
605 case IFLNK:
606 lnkbuf[0] = '\0';
607 pathlen = 0;
608 getfile(xtrlnkfile, xtrlnkskip);
609 if (pathlen == 0) {
610 vprintf(stdout,
611 "%s: zero length symbolic link (ignored)\n", name);
612 return (GOOD);
613 }
614 if (uflag)
615 (void) unlink(name);
616 if (linkit(lnkbuf, name, SYMLINK) == GOOD) {
617 if (setbirth)
618 (void) lutimes(name, ctimep);
619 (void) lutimes(name, mtimep);
620 (void) lchown(name, uid, gid);
621 (void) lchmod(name, mode);
622 (void) lchflags(name, flags);
623 return (GOOD);
624 }
625 return (FAIL);
626
627 case IFCHR:
628 case IFBLK:
629 vprintf(stdout, "extract special file %s\n", name);
630 if (Nflag) {
631 skipfile();
632 return (GOOD);
633 }
634 if (uflag)
635 (void) unlink(name);
636 if (mknod(name, (mode & (IFCHR | IFBLK)) | 0600,
637 (int)curfile.rdev) < 0) {
638 fprintf(stderr, "%s: cannot create special file: %s\n",
639 name, strerror(errno));
640 skipfile();
641 return (FAIL);
642 }
643 skipfile();
644 if (setbirth)
645 (void) utimes(name, ctimep);
646 (void) utimes(name, mtimep);
647 (void) chown(name, uid, gid);
648 (void) chmod(name, mode);
649 (void) chflags(name, flags);
650 return (GOOD);
651
652 case IFIFO:
653 vprintf(stdout, "extract fifo %s\n", name);
654 if (Nflag) {
655 skipfile();
656 return (GOOD);
657 }
658 if (uflag)
659 (void) unlink(name);
660 if (mkfifo(name, 0600) < 0) {
661 fprintf(stderr, "%s: cannot create fifo: %s\n",
662 name, strerror(errno));
663 skipfile();
664 return (FAIL);
665 }
666 skipfile();
667 if (setbirth)
668 (void) utimes(name, ctimep);
669 (void) utimes(name, mtimep);
670 (void) chown(name, uid, gid);
671 (void) chmod(name, mode);
672 (void) chflags(name, flags);
673 return (GOOD);
674
675 case IFREG:
676 vprintf(stdout, "extract file %s\n", name);
677 if (Nflag) {
678 skipfile();
679 return (GOOD);
680 }
681 if (uflag)
682 (void) unlink(name);
683 if ((ofile = open(name, O_WRONLY | O_CREAT | O_TRUNC,
684 0600)) < 0) {
685 fprintf(stderr, "%s: cannot create file: %s\n",
686 name, strerror(errno));
687 skipfile();
688 return (FAIL);
689 }
690 getfile(xtrfile, xtrskip);
691 if (setbirth)
692 (void) futimes(ofile, ctimep);
693 (void) futimes(ofile, mtimep);
694 (void) fchown(ofile, uid, gid);
695 (void) fchmod(ofile, mode);
696 (void) fchflags(ofile, flags);
697 (void) close(ofile);
698 return (GOOD);
699 }
700 /* NOTREACHED */
701 }
702
703 /*
704 * skip over bit maps on the tape
705 */
706 void
707 skipmaps()
708 {
709
710 while (spcl.c_type == TS_BITS || spcl.c_type == TS_CLRI)
711 skipfile();
712 }
713
714 /*
715 * skip over a file on the tape
716 */
717 void
718 skipfile()
719 {
720
721 curfile.action = SKIP;
722 getfile(xtrnull, xtrnull);
723 }
724
725 /*
726 * Extract a file from the tape.
727 * When an allocated block is found it is passed to the fill function;
728 * when an unallocated block (hole) is found, a zeroed buffer is passed
729 * to the skip function.
730 */
731 void
732 getfile(fill, skip)
733 void (*fill) __P((char *, long));
734 void (*skip) __P((char *, long));
735 {
736 int i;
737 int curblk = 0;
738 quad_t size = spcl.c_size;
739 static char clearedbuf[MAXBSIZE];
740 char buf[MAXBSIZE / TP_BSIZE][TP_BSIZE];
741 char junk[TP_BSIZE];
742
743 #ifdef __GNUC__ /* XXX: to shut up gcc warnings */
744 (void)&curblk;
745 (void)&size;
746 #endif
747
748 if (spcl.c_type == TS_END)
749 panic("ran off end of tape\n");
750 if (spcl.c_magic != FS_UFS2_MAGIC)
751 panic("not at beginning of a file\n");
752 if (!gettingfile && setjmp(restart) != 0)
753 return;
754 gettingfile++;
755 loop:
756 for (i = 0; i < spcl.c_count; i++) {
757 if (spcl.c_type == TS_BITS || spcl.c_type == TS_CLRI ||
758 spcl.c_addr[i]) {
759 readtape(&buf[curblk++][0]);
760 if (curblk == fssize / TP_BSIZE) {
761 (*fill)((char *)buf, (long)(size > TP_BSIZE ?
762 fssize : (curblk - 1) * TP_BSIZE + size));
763 curblk = 0;
764 }
765 } else {
766 if (curblk > 0) {
767 (*fill)((char *)buf, (long)(size > TP_BSIZE ?
768 curblk * TP_BSIZE :
769 (curblk - 1) * TP_BSIZE + size));
770 curblk = 0;
771 }
772 (*skip)(clearedbuf, (long)(size > TP_BSIZE ?
773 TP_BSIZE : size));
774 }
775 if ((size -= TP_BSIZE) <= 0) {
776 if (spcl.c_type == TS_BITS || spcl.c_type == TS_CLRI) {
777 /*
778 * In this case, the following expression
779 * should always be false since the size was
780 * initially set to spcl.c_size and
781 * it is initialized to spcl.c_count * TP_BSIZE
782 * in gethead().
783 */
784 if (!(size == 0 && i == spcl.c_count - 1))
785 panic("inconsistent map size\n");
786 } else {
787 for (i++; i < spcl.c_count; i++)
788 if (spcl.c_addr[i])
789 readtape(junk);
790 }
791 break;
792 }
793 }
794 if (gethead(&spcl) == GOOD && size > 0) {
795 if (spcl.c_type == TS_ADDR)
796 goto loop;
797 dprintf(stdout,
798 "Missing address (header) block for %s at %d blocks\n",
799 curfile.name, blksread);
800 }
801 if (curblk > 0)
802 (*fill)((char *)buf, (long)((curblk * TP_BSIZE) + size));
803 findinode(&spcl);
804 gettingfile = 0;
805 }
806
807 /*
808 * Write out the next block of a file.
809 */
810 static void
811 xtrfile(buf, size)
812 char *buf;
813 long size;
814 {
815
816 if (Nflag)
817 return;
818 if (write(ofile, buf, (int) size) == -1) {
819 fprintf(stderr,
820 "write error extracting inode %d, name %s\nwrite: %s\n",
821 curfile.ino, curfile.name, strerror(errno));
822 exit(1);
823 }
824 }
825
826 /*
827 * Skip over a hole in a file.
828 */
829 /* ARGSUSED */
830 static void
831 xtrskip(buf, size)
832 char *buf;
833 long size;
834 {
835
836 if (lseek(ofile, size, SEEK_CUR) == -1) {
837 fprintf(stderr,
838 "seek error extracting inode %d, name %s\nlseek: %s\n",
839 curfile.ino, curfile.name, strerror(errno));
840 exit(1);
841 }
842 }
843
844 /*
845 * Collect the next block of a symbolic link.
846 */
847 static void
848 xtrlnkfile(buf, size)
849 char *buf;
850 long size;
851 {
852
853 pathlen += size;
854 if (pathlen > MAXPATHLEN) {
855 fprintf(stderr, "symbolic link name: %s->%s%s; too long %d\n",
856 curfile.name, lnkbuf, buf, pathlen);
857 exit(1);
858 }
859 (void) strcat(lnkbuf, buf);
860 }
861
862 /*
863 * Skip over a hole in a symbolic link (should never happen).
864 */
865 /* ARGSUSED */
866 static void
867 xtrlnkskip(buf, size)
868 char *buf;
869 long size;
870 {
871
872 fprintf(stderr, "unallocated block in symbolic link %s\n",
873 curfile.name);
874 exit(1);
875 }
876
877 /*
878 * Collect the next block of a bit map.
879 */
880 static void
881 xtrmap(buf, size)
882 char *buf;
883 long size;
884 {
885
886 memmove(map, buf, size);
887 map += size;
888 }
889
890 /*
891 * Skip over a hole in a bit map (should never happen).
892 */
893 /* ARGSUSED */
894 static void
895 xtrmapskip(buf, size)
896 char *buf;
897 long size;
898 {
899
900 panic("hole in map\n");
901 map += size;
902 }
903
904 /*
905 * Noop, when an extraction function is not needed.
906 */
907 /* ARGSUSED */
908 void
909 xtrnull(buf, size)
910 char *buf;
911 long size;
912 {
913
914 return;
915 }
916
917 /*
918 * Read TP_BSIZE blocks from the input.
919 * Handle read errors, and end of media.
920 */
921 static void
922 readtape(buf)
923 char *buf;
924 {
925 int rd, newvol, i;
926 int cnt, seek_failed;
927
928 if (blkcnt < numtrec) {
929 memmove(buf, &tapebuf[(blkcnt++ * TP_BSIZE)], (long)TP_BSIZE);
930 blksread++;
931 tpblksread++;
932 return;
933 }
934 for (i = 0; i < ntrec; i++)
935 ((struct s_spcl *)&tapebuf[i * TP_BSIZE])->c_magic = 0;
936 if (numtrec == 0)
937 numtrec = ntrec;
938 cnt = ntrec * TP_BSIZE;
939 rd = 0;
940 getmore:
941 #ifdef RRESTORE
942 if (host)
943 i = rmtread(&tapebuf[rd], cnt);
944 else
945 #endif
946 i = read(mt, &tapebuf[rd], cnt);
947 /*
948 * Check for mid-tape short read error.
949 * If found, skip rest of buffer and start with the next.
950 */
951 if (!pipein && numtrec < ntrec && i > 0) {
952 dprintf(stdout, "mid-media short read error.\n");
953 numtrec = ntrec;
954 }
955 /*
956 * Handle partial block read.
957 */
958 if (pipein && i == 0 && rd > 0)
959 i = rd;
960 else if (i > 0 && i != ntrec * TP_BSIZE) {
961 if (pipein) {
962 rd += i;
963 cnt -= i;
964 if (cnt > 0)
965 goto getmore;
966 i = rd;
967 } else {
968 /*
969 * Short read. Process the blocks read.
970 */
971 if (i % TP_BSIZE != 0)
972 vprintf(stdout,
973 "partial block read: %d should be %d\n",
974 i, ntrec * TP_BSIZE);
975 numtrec = i / TP_BSIZE;
976 }
977 }
978 /*
979 * Handle read error.
980 */
981 if (i < 0) {
982 fprintf(stderr, "Tape read error while ");
983 switch (curfile.action) {
984 default:
985 fprintf(stderr, "trying to set up tape\n");
986 break;
987 case UNKNOWN:
988 fprintf(stderr, "trying to resynchronize\n");
989 break;
990 case USING:
991 fprintf(stderr, "restoring %s\n", curfile.name);
992 break;
993 case SKIP:
994 fprintf(stderr, "skipping over inode %d\n",
995 curfile.ino);
996 break;
997 }
998 if (!yflag && !reply("continue"))
999 exit(1);
1000 i = ntrec * TP_BSIZE;
1001 memset(tapebuf, 0, i);
1002 #ifdef RRESTORE
1003 if (host)
1004 seek_failed = (rmtseek(i, 1) < 0);
1005 else
1006 #endif
1007 seek_failed = (lseek(mt, i, SEEK_CUR) == (off_t)-1);
1008
1009 if (seek_failed) {
1010 fprintf(stderr,
1011 "continuation failed: %s\n", strerror(errno));
1012 exit(1);
1013 }
1014 }
1015 /*
1016 * Handle end of tape.
1017 */
1018 if (i == 0) {
1019 vprintf(stdout, "End-of-tape encountered\n");
1020 if (!pipein) {
1021 newvol = volno + 1;
1022 volno = 0;
1023 numtrec = 0;
1024 getvol(newvol);
1025 readtape(buf);
1026 return;
1027 }
1028 if (rd % TP_BSIZE != 0)
1029 panic("partial block read: %d should be %d\n",
1030 rd, ntrec * TP_BSIZE);
1031 terminateinput();
1032 memmove(&tapebuf[rd], &endoftapemark, (long)TP_BSIZE);
1033 }
1034 blkcnt = 0;
1035 memmove(buf, &tapebuf[(blkcnt++ * TP_BSIZE)], (long)TP_BSIZE);
1036 blksread++;
1037 tpblksread++;
1038 }
1039
1040 static void
1041 findtapeblksize()
1042 {
1043 long i;
1044
1045 for (i = 0; i < ntrec; i++)
1046 ((struct s_spcl *)&tapebuf[i * TP_BSIZE])->c_magic = 0;
1047 blkcnt = 0;
1048 #ifdef RRESTORE
1049 if (host)
1050 i = rmtread(tapebuf, ntrec * TP_BSIZE);
1051 else
1052 #endif
1053 i = read(mt, tapebuf, ntrec * TP_BSIZE);
1054
1055 if (i <= 0) {
1056 fprintf(stderr, "tape read error: %s\n", strerror(errno));
1057 exit(1);
1058 }
1059 if (i % TP_BSIZE != 0) {
1060 fprintf(stderr, "Tape block size (%ld) %s (%ld)\n",
1061 (long)i, "is not a multiple of dump block size",
1062 (long)TP_BSIZE);
1063 exit(1);
1064 }
1065 ntrec = i / TP_BSIZE;
1066 numtrec = ntrec;
1067 vprintf(stdout, "Tape block size is %d\n", ntrec);
1068 }
1069
1070 void
1071 closemt()
1072 {
1073
1074 if (mt < 0)
1075 return;
1076 #ifdef RRESTORE
1077 if (host)
1078 rmtclose();
1079 else
1080 #endif
1081 (void) close(mt);
1082 }
1083
1084 /*
1085 * Read the next block from the tape.
1086 * Check to see if it is one of several vintage headers.
1087 * If it is an old style header, convert it to a new style header.
1088 * If it is not any valid header, return an error.
1089 */
1090 static int
1091 gethead(buf)
1092 struct s_spcl *buf;
1093 {
1094 union u_ospcl u_ospcl;
1095
1096 if (!cvtflag) {
1097 readtape((char *)buf);
1098 if (buf->c_magic != NFS_MAGIC &&
1099 buf->c_magic != FS_UFS2_MAGIC) {
1100 if (bswap32(buf->c_magic) != NFS_MAGIC &&
1101 bswap32(buf->c_magic) != FS_UFS2_MAGIC)
1102 return (FAIL);
1103 if (!Bcvt) {
1104 vprintf(stdout, "Note: Doing Byte swapping\n");
1105 Bcvt = 1;
1106 }
1107 }
1108 if (checksum((int *)buf) == FAIL)
1109 return (FAIL);
1110 if (Bcvt)
1111 swap_header(buf);
1112 goto good;
1113 }
1114
1115 readtape((char *)(&u_ospcl.s_ospcl));
1116 if (checksum((int *)(&u_ospcl.s_ospcl)) == FAIL)
1117 return (FAIL);
1118 if (u_ospcl.s_ospcl.c_magic != OFS_MAGIC) {
1119 if (bswap32(u_ospcl.s_ospcl.c_magic) != OFS_MAGIC)
1120 return (FAIL);
1121 if (!Bcvt) {
1122 vprintf(stdout, "Note: Doing Byte swapping\n");
1123 Bcvt = 1;
1124 }
1125 swap_old_header(&u_ospcl.s_ospcl);
1126 }
1127
1128 memset(buf, 0, (long)TP_BSIZE);
1129 buf->c_type = u_ospcl.s_ospcl.c_type;
1130 buf->c_date = u_ospcl.s_ospcl.c_date;
1131 buf->c_ddate = u_ospcl.s_ospcl.c_ddate;
1132 buf->c_volume = u_ospcl.s_ospcl.c_volume;
1133 buf->c_tapea = u_ospcl.s_ospcl.c_tapea;
1134 buf->c_inumber = u_ospcl.s_ospcl.c_inumber;
1135 buf->c_checksum = u_ospcl.s_ospcl.c_checksum;
1136 buf->c_mode = u_ospcl.s_ospcl.c_odinode.odi_mode;
1137 buf->c_uid = u_ospcl.s_ospcl.c_odinode.odi_uid;
1138 buf->c_gid = u_ospcl.s_ospcl.c_odinode.odi_gid;
1139 buf->c_size = u_ospcl.s_ospcl.c_odinode.odi_size;
1140 buf->c_rdev = u_ospcl.s_ospcl.c_odinode.odi_rdev;
1141 buf->c_atime = u_ospcl.s_ospcl.c_odinode.odi_atime;
1142 buf->c_mtime = u_ospcl.s_ospcl.c_odinode.odi_mtime;
1143 buf->c_count = u_ospcl.s_ospcl.c_count;
1144 memmove(buf->c_addr, u_ospcl.s_ospcl.c_addr, (long)256);
1145 buf->c_magic = FS_UFS2_MAGIC;
1146 good:
1147 switch (buf->c_type) {
1148
1149 case TS_CLRI:
1150 case TS_BITS:
1151 /*
1152 * Have to patch up missing information in bit map headers
1153 */
1154 buf->c_inumber = 0;
1155 buf->c_size = buf->c_count * TP_BSIZE;
1156 break;
1157
1158 case TS_TAPE:
1159 if ((buf->c_flags & DR_NEWINODEFMT) == 0)
1160 oldinofmt = 1;
1161 /* fall through */
1162 case TS_END:
1163 buf->c_inumber = 0;
1164 break;
1165
1166 case TS_INODE:
1167 if (buf->c_magic == NFS_MAGIC) {
1168 buf->c_tapea = buf->c_old_tapea;
1169 buf->c_firstrec = buf->c_old_firstrec;
1170 buf->c_date = buf->c_old_date;
1171 buf->c_ddate = buf->c_old_ddate;
1172 buf->c_atime = buf->c_old_atime;
1173 buf->c_mtime = buf->c_old_mtime;
1174 buf->c_birthtime = 0;
1175 buf->c_birthtimensec = 0;
1176 buf->c_atimensec = buf->c_mtimensec = 0;
1177 }
1178
1179 case TS_ADDR:
1180 break;
1181
1182 default:
1183 panic("gethead: unknown inode type %d\n", buf->c_type);
1184 break;
1185 }
1186
1187 buf->c_magic = FS_UFS2_MAGIC;
1188
1189 /*
1190 * If we are restoring a filesystem with old format inodes,
1191 * copy the uid/gid to the new location.
1192 */
1193 if (oldinofmt) {
1194 buf->c_uid = buf->c_spare1[1];
1195 buf->c_gid = buf->c_spare1[2];
1196 }
1197 if (dflag)
1198 accthdr(buf);
1199 return(GOOD);
1200 }
1201
1202 /*
1203 * Check that a header is where it belongs and predict the next header
1204 */
1205 static void
1206 accthdr(header)
1207 struct s_spcl *header;
1208 {
1209 static ino_t previno = 0x7fffffff;
1210 static int prevtype;
1211 static long predict;
1212 long blks, i;
1213
1214 if (header->c_type == TS_TAPE) {
1215 fprintf(stderr, "Volume header (%s inode format) ",
1216 oldinofmt ? "old" : "new");
1217 if (header->c_firstrec)
1218 fprintf(stderr, "begins with record %lld",
1219 (long long)header->c_firstrec);
1220 fprintf(stderr, "\n");
1221 previno = 0x7fffffff;
1222 return;
1223 }
1224 if (previno == 0x7fffffff)
1225 goto newcalc;
1226 switch (prevtype) {
1227 case TS_BITS:
1228 fprintf(stderr, "Dumped inodes map header");
1229 break;
1230 case TS_CLRI:
1231 fprintf(stderr, "Used inodes map header");
1232 break;
1233 case TS_INODE:
1234 fprintf(stderr, "File header, ino %d", previno);
1235 break;
1236 case TS_ADDR:
1237 fprintf(stderr, "File continuation header, ino %d", previno);
1238 break;
1239 case TS_END:
1240 fprintf(stderr, "End of tape header");
1241 break;
1242 }
1243 if (predict != blksread - 1)
1244 fprintf(stderr, "; predicted %ld blocks, got %ld blocks",
1245 (long)predict, (long)(blksread - 1));
1246 fprintf(stderr, "\n");
1247 newcalc:
1248 blks = 0;
1249 switch (header->c_type) {
1250 case TS_END:
1251 break;
1252 case TS_CLRI:
1253 case TS_BITS:
1254 blks = header->c_count;
1255 break;
1256 default:
1257 for (i = 0; i < header->c_count; i++)
1258 if (header->c_addr[i] != 0)
1259 blks++;
1260 break;
1261 }
1262 predict = blks;
1263 blksread = 0;
1264 prevtype = header->c_type;
1265 previno = header->c_inumber;
1266 }
1267
1268 /*
1269 * Find an inode header.
1270 * Complain if had to skip, and complain is set.
1271 */
1272 static void
1273 findinode(header)
1274 struct s_spcl *header;
1275 {
1276 static long skipcnt = 0;
1277 long i;
1278 char buf[TP_BSIZE];
1279
1280 curfile.name = "<name unknown>";
1281 curfile.action = UNKNOWN;
1282 curfile.mode = 0;
1283 curfile.ino = 0;
1284 top:
1285 do {
1286 if (header->c_magic != FS_UFS2_MAGIC) {
1287 skipcnt++;
1288 while (gethead(header) == FAIL ||
1289 header->c_date != dumpdate)
1290 skipcnt++;
1291 }
1292 switch (header->c_type) {
1293
1294 case TS_ADDR:
1295 /*
1296 * Skip up to the beginning of the next record
1297 */
1298 for (i = 0; i < header->c_count; i++)
1299 if (header->c_addr[i])
1300 readtape(buf);
1301 while (gethead(header) == FAIL ||
1302 header->c_date != dumpdate)
1303 skipcnt++;
1304 /* We've read a header; don't drop it. */
1305 goto top;
1306
1307 case TS_INODE:
1308 curfile.mode = header->c_mode;
1309 curfile.uid = header->c_uid;
1310 curfile.gid = header->c_gid;
1311 curfile.file_flags = header->c_file_flags;
1312 curfile.rdev = header->c_rdev;
1313 curfile.atime_sec = header->c_atime;
1314 curfile.atime_nsec = header->c_atimensec;
1315 curfile.mtime_sec = header->c_mtime;
1316 curfile.mtime_nsec = header->c_mtimensec;
1317 curfile.birthtime_sec = header->c_birthtime;
1318 curfile.birthtime_nsec = header->c_birthtimensec;
1319 curfile.size = header->c_size;
1320 curfile.ino = header->c_inumber;
1321 break;
1322
1323 case TS_END:
1324 curfile.ino = maxino;
1325 break;
1326
1327 case TS_CLRI:
1328 curfile.name = "<file removal list>";
1329 break;
1330
1331 case TS_BITS:
1332 curfile.name = "<file dump list>";
1333 break;
1334
1335 case TS_TAPE:
1336 panic("unexpected tape header\n");
1337 break;
1338
1339 default:
1340 panic("unknown tape header type %d\n", spcl.c_type);
1341 break;
1342
1343 }
1344 } while (header->c_type == TS_ADDR);
1345 if (skipcnt > 0)
1346 fprintf(stderr, "resync restore, skipped %ld blocks\n",
1347 (long)skipcnt);
1348 skipcnt = 0;
1349 }
1350
1351 static int
1352 checksum(buf)
1353 int *buf;
1354 {
1355 int i, j;
1356
1357 j = sizeof(union u_spcl) / sizeof(int);
1358 i = 0;
1359 if(!Bcvt) {
1360 do
1361 i += *buf++;
1362 while (--j);
1363 } else {
1364 do
1365 i += bswap32(*buf++);
1366 while (--j);
1367 }
1368
1369 if (i != CHECKSUM) {
1370 fprintf(stderr, "Checksum error %o, inode %d file %s\n", i,
1371 curfile.ino, curfile.name);
1372 return(FAIL);
1373 }
1374 return(GOOD);
1375 }
1376
1377 #ifdef RRESTORE
1378 #include <stdarg.h>
1379
1380 void
1381 msg(const char *fmt, ...)
1382 {
1383 va_list ap;
1384
1385 va_start(ap, fmt);
1386 (void)vfprintf(stderr, fmt, ap);
1387 va_end(ap);
1388 }
1389 #endif /* RRESTORE */
1390
1391 static void
1392 swap_header(struct s_spcl *s)
1393 {
1394 s->c_type = bswap32(s->c_type);
1395 s->c_old_date = bswap32(s->c_old_date);
1396 s->c_old_ddate = bswap32(s->c_old_ddate);
1397 s->c_volume = bswap32(s->c_volume);
1398 s->c_old_tapea = bswap32(s->c_old_tapea);
1399 s->c_inumber = bswap32(s->c_inumber);
1400 s->c_magic = bswap32(s->c_magic);
1401 s->c_checksum = bswap32(s->c_checksum);
1402
1403 s->c_mode = bswap16(s->c_mode);
1404 s->c_size = bswap64(s->c_size);
1405 s->c_old_atime = bswap32(s->c_old_atime);
1406 s->c_atimensec = bswap32(s->c_atimensec);
1407 s->c_old_mtime = bswap32(s->c_old_mtime);
1408 s->c_mtimensec = bswap32(s->c_mtimensec);
1409 s->c_rdev = bswap32(s->c_rdev);
1410 s->c_birthtimensec = bswap32(s->c_birthtimensec);
1411 s->c_birthtime = bswap64(s->c_birthtime);
1412 s->c_atime = bswap64(s->c_atime);
1413 s->c_mtime = bswap64(s->c_mtime);
1414 s->c_file_flags = bswap32(s->c_file_flags);
1415 s->c_uid = bswap32(s->c_uid);
1416 s->c_gid = bswap32(s->c_gid);
1417
1418 s->c_count = bswap32(s->c_count);
1419 s->c_level = bswap32(s->c_level);
1420 s->c_flags = bswap32(s->c_flags);
1421 s->c_old_firstrec = bswap32(s->c_old_firstrec);
1422
1423 s->c_date = bswap64(s->c_date);
1424 s->c_ddate = bswap64(s->c_ddate);
1425 s->c_tapea = bswap64(s->c_tapea);
1426 s->c_firstrec = bswap64(s->c_firstrec);
1427
1428 /*
1429 * These are ouid and ogid.
1430 */
1431 s->c_spare1[1] = bswap16(s->c_spare1[1]);
1432 s->c_spare1[2] = bswap16(s->c_spare1[2]);
1433 }
1434
1435 static void
1436 swap_old_header(struct s_ospcl *os)
1437 {
1438 os->c_type = bswap32(os->c_type);
1439 os->c_date = bswap32(os->c_date);
1440 os->c_ddate = bswap32(os->c_ddate);
1441 os->c_volume = bswap32(os->c_volume);
1442 os->c_tapea = bswap32(os->c_tapea);
1443 os->c_inumber = bswap16(os->c_inumber);
1444 os->c_magic = bswap32(os->c_magic);
1445 os->c_checksum = bswap32(os->c_checksum);
1446
1447 os->c_odinode.odi_mode = bswap16(os->c_odinode.odi_mode);
1448 os->c_odinode.odi_nlink = bswap16(os->c_odinode.odi_nlink);
1449 os->c_odinode.odi_uid = bswap16(os->c_odinode.odi_uid);
1450 os->c_odinode.odi_gid = bswap16(os->c_odinode.odi_gid);
1451
1452 os->c_odinode.odi_size = bswap32(os->c_odinode.odi_size);
1453 os->c_odinode.odi_rdev = bswap32(os->c_odinode.odi_rdev);
1454 os->c_odinode.odi_atime = bswap32(os->c_odinode.odi_atime);
1455 os->c_odinode.odi_mtime = bswap32(os->c_odinode.odi_mtime);
1456 os->c_odinode.odi_ctime = bswap32(os->c_odinode.odi_ctime);
1457
1458 os->c_count = bswap32(os->c_count);
1459 }
1460