|
|
1.1 ! root 1: /* ! 2: * Copyright (c) 1980 Regents of the University of California. ! 3: * All rights reserved. The Berkeley software License Agreement ! 4: * specifies the terms and conditions for redistribution. ! 5: */ ! 6: ! 7: #ifndef lint ! 8: char copyright[] = ! 9: "@(#) Copyright (c) 1980 Regents of the University of California.\n\ ! 10: All rights reserved.\n"; ! 11: #endif /* not lint */ ! 12: ! 13: #ifndef lint ! 14: static char sccsid[] = "@(#)tar.c 5.15 (Berkeley) 5/15/90"; ! 15: #endif /* not lint */ ! 16: ! 17: /* ! 18: * Tape Archival Program ! 19: */ ! 20: #include <sys/param.h> ! 21: #include <sys/stat.h> ! 22: #include <sys/file.h> ! 23: #include <sys/dir.h> ! 24: #include <sys/ioctl.h> ! 25: #include <sys/mtio.h> ! 26: #include <sys/time.h> ! 27: #include <signal.h> ! 28: #include <errno.h> ! 29: #include <fcntl.h> ! 30: #include <string.h> ! 31: #include <stdio.h> ! 32: #include "pathnames.h" ! 33: ! 34: #define TBLOCK 512 ! 35: #define NBLOCK 20 ! 36: #define NAMSIZ 100 ! 37: ! 38: #define writetape(b) writetbuf(b, 1) ! 39: #define min(a,b) ((a) < (b) ? (a) : (b)) ! 40: #define max(a,b) ((a) > (b) ? (a) : (b)) ! 41: ! 42: union hblock { ! 43: char dummy[TBLOCK]; ! 44: struct header { ! 45: char name[NAMSIZ]; ! 46: char mode[8]; ! 47: char uid[8]; ! 48: char gid[8]; ! 49: char size[12]; ! 50: char mtime[12]; ! 51: char chksum[8]; ! 52: char linkflag; ! 53: char linkname[NAMSIZ]; ! 54: } dbuf; ! 55: }; ! 56: ! 57: struct linkbuf { ! 58: ino_t inum; ! 59: dev_t devnum; ! 60: int count; ! 61: char pathname[NAMSIZ]; ! 62: struct linkbuf *nextp; ! 63: }; ! 64: ! 65: union hblock dblock; ! 66: union hblock *tbuf; ! 67: struct linkbuf *ihead; ! 68: struct stat stbuf; ! 69: ! 70: int rflag; ! 71: int sflag; ! 72: int xflag; ! 73: int vflag; ! 74: int tflag; ! 75: int cflag; ! 76: int mflag; ! 77: int fflag; ! 78: int iflag; ! 79: int oflag; ! 80: int pflag; ! 81: int wflag; ! 82: int hflag; ! 83: int Bflag; ! 84: int Fflag; ! 85: ! 86: int mt; ! 87: int term; ! 88: int chksum; ! 89: int recno; ! 90: int first; ! 91: int prtlinkerr; ! 92: int freemem = 1; ! 93: int nblock = 0; ! 94: int onintr(); ! 95: int onquit(); ! 96: int onhup(); ! 97: #ifdef notdef ! 98: int onterm(); ! 99: #endif ! 100: ! 101: daddr_t low; ! 102: daddr_t high; ! 103: daddr_t bsrch(); ! 104: ! 105: FILE *vfile = stdout; ! 106: FILE *tfile; ! 107: char tname[] = _PATH_TMP; ! 108: char *usefile; ! 109: char magtape[] = _PATH_MAGTAPE; ! 110: char *malloc(); ! 111: long time(); ! 112: off_t lseek(); ! 113: char *mktemp(); ! 114: char *getcwd(); ! 115: char *getwd(); ! 116: char *getmem(); ! 117: ! 118: extern int errno; ! 119: ! 120: main(argc, argv) ! 121: int argc; ! 122: char **argv; ! 123: { ! 124: char *cp; ! 125: ! 126: if (argc < 2) ! 127: usage(); ! 128: ! 129: tfile = NULL; ! 130: usefile = magtape; ! 131: argv[argc] = 0; ! 132: argv++; ! 133: for (cp = *argv++; *cp; cp++) ! 134: switch(*cp) { ! 135: ! 136: case 'f': ! 137: if (*argv == 0) { ! 138: fprintf(stderr, ! 139: "tar: tapefile must be specified with 'f' option\n"); ! 140: usage(); ! 141: } ! 142: usefile = *argv++; ! 143: fflag++; ! 144: break; ! 145: ! 146: case 'c': ! 147: cflag++; ! 148: rflag++; ! 149: break; ! 150: ! 151: case 'o': ! 152: oflag++; ! 153: break; ! 154: ! 155: case 'p': ! 156: pflag++; ! 157: break; ! 158: ! 159: case 'u': ! 160: (void)mktemp(tname); ! 161: if ((tfile = fopen(tname, "w")) == NULL) { ! 162: fprintf(stderr, ! 163: "tar: cannot create temporary file (%s)\n", ! 164: tname); ! 165: done(1); ! 166: } ! 167: fprintf(tfile, "!!!!!/!/!/!/!/!/!/! 000\n"); ! 168: /*FALL THRU*/ ! 169: ! 170: case 'r': ! 171: rflag++; ! 172: break; ! 173: ! 174: case 's': ! 175: sflag++; ! 176: break; ! 177: ! 178: case 'v': ! 179: vflag++; ! 180: break; ! 181: ! 182: case 'w': ! 183: wflag++; ! 184: break; ! 185: ! 186: case 'x': ! 187: xflag++; ! 188: break; ! 189: ! 190: case 't': ! 191: tflag++; ! 192: break; ! 193: ! 194: case 'm': ! 195: mflag++; ! 196: break; ! 197: ! 198: case '-': ! 199: break; ! 200: ! 201: case '0': ! 202: case '1': ! 203: case '4': ! 204: case '5': ! 205: case '7': ! 206: case '8': ! 207: magtape[8] = *cp; ! 208: usefile = magtape; ! 209: break; ! 210: ! 211: case 'b': ! 212: if (*argv == 0) { ! 213: fprintf(stderr, ! 214: "tar: blocksize must be specified with 'b' option\n"); ! 215: usage(); ! 216: } ! 217: nblock = atoi(*argv); ! 218: if (nblock <= 0) { ! 219: fprintf(stderr, ! 220: "tar: invalid blocksize \"%s\"\n", *argv); ! 221: done(1); ! 222: } ! 223: argv++; ! 224: break; ! 225: ! 226: case 'l': ! 227: prtlinkerr++; ! 228: break; ! 229: ! 230: case 'h': ! 231: hflag++; ! 232: break; ! 233: ! 234: case 'i': ! 235: iflag++; ! 236: break; ! 237: ! 238: case 'B': ! 239: Bflag++; ! 240: break; ! 241: ! 242: case 'F': ! 243: Fflag++; ! 244: break; ! 245: ! 246: default: ! 247: fprintf(stderr, "tar: %c: unknown option\n", *cp); ! 248: usage(); ! 249: } ! 250: ! 251: if (!rflag && !xflag && !tflag) ! 252: usage(); ! 253: if (rflag) { ! 254: if (cflag && tfile != NULL) ! 255: usage(); ! 256: if (signal(SIGINT, SIG_IGN) != SIG_IGN) ! 257: (void) signal(SIGINT, onintr); ! 258: if (signal(SIGHUP, SIG_IGN) != SIG_IGN) ! 259: (void) signal(SIGHUP, onhup); ! 260: if (signal(SIGQUIT, SIG_IGN) != SIG_IGN) ! 261: (void) signal(SIGQUIT, onquit); ! 262: #ifdef notdef ! 263: if (signal(SIGTERM, SIG_IGN) != SIG_IGN) ! 264: (void) signal(SIGTERM, onterm); ! 265: #endif ! 266: mt = openmt(usefile, 1); ! 267: dorep(argv); ! 268: done(0); ! 269: } ! 270: mt = openmt(usefile, 0); ! 271: if (xflag) ! 272: doxtract(argv); ! 273: else ! 274: dotable(argv); ! 275: done(0); ! 276: } ! 277: ! 278: usage() ! 279: { ! 280: fprintf(stderr, ! 281: "tar: usage: tar -{txru}[cvfblmhopwBi] [tapefile] [blocksize] file1 file2...\n"); ! 282: done(1); ! 283: } ! 284: ! 285: int ! 286: openmt(tape, writing) ! 287: char *tape; ! 288: int writing; ! 289: { ! 290: if (strcmp(tape, "-") == 0) { ! 291: /* ! 292: * Read from standard input or write to standard output. ! 293: */ ! 294: if (writing) { ! 295: if (cflag == 0) { ! 296: fprintf(stderr, ! 297: "tar: can only create standard output archives\n"); ! 298: done(1); ! 299: } ! 300: vfile = stderr; ! 301: setlinebuf(vfile); ! 302: mt = dup(1); ! 303: } else { ! 304: mt = dup(0); ! 305: Bflag++; ! 306: } ! 307: } else { ! 308: /* ! 309: * Use file or tape on local machine. ! 310: */ ! 311: if (writing) { ! 312: if (cflag) ! 313: mt = open(tape, O_RDWR|O_CREAT|O_TRUNC, 0666); ! 314: else ! 315: mt = open(tape, O_RDWR); ! 316: } else ! 317: mt = open(tape, O_RDONLY); ! 318: if (mt < 0) { ! 319: fprintf(stderr, "tar: %s: %s\n", tape, strerror(errno)); ! 320: done(1); ! 321: } ! 322: } ! 323: return(mt); ! 324: } ! 325: ! 326: dorep(argv) ! 327: char *argv[]; ! 328: { ! 329: register char *cp, *cp2; ! 330: char wdir[MAXPATHLEN], tempdir[MAXPATHLEN], *parent; ! 331: ! 332: if (!cflag) { ! 333: getdir(); ! 334: do { ! 335: passtape(); ! 336: if (term) ! 337: done(0); ! 338: getdir(); ! 339: } while (!endtape()); ! 340: backtape(); ! 341: if (tfile != NULL) { ! 342: char buf[200]; ! 343: ! 344: (void)sprintf(buf, ! 345: "sort +0 -1 +1nr %s -o %s; awk '$1 != prev {print; prev=$1}' %s >%sX; mv %sX %s", ! 346: tname, tname, tname, tname, tname, tname); ! 347: fflush(tfile); ! 348: system(buf); ! 349: freopen(tname, "r", tfile); ! 350: fstat(fileno(tfile), &stbuf); ! 351: high = stbuf.st_size; ! 352: } ! 353: } ! 354: ! 355: (void) getcwd(wdir); ! 356: while (*argv && ! term) { ! 357: cp2 = *argv; ! 358: if (!strcmp(cp2, "-C") && argv[1]) { ! 359: argv++; ! 360: if (chdir(*argv) < 0) { ! 361: fprintf(stderr, ! 362: "tar: can't change directories to %s: %s\n", ! 363: *argv, strerror(errno)); ! 364: } else ! 365: (void) getcwd(wdir); ! 366: argv++; ! 367: continue; ! 368: } ! 369: parent = wdir; ! 370: for (cp = *argv; *cp; cp++) ! 371: if (*cp == '/') ! 372: cp2 = cp; ! 373: if (cp2 != *argv) { ! 374: *cp2 = '\0'; ! 375: if (chdir(*argv) < 0) { ! 376: fprintf(stderr, ! 377: "tar: can't change directories to %s: %s\n", ! 378: *argv, strerror(errno)); ! 379: continue; ! 380: } ! 381: parent = getcwd(tempdir); ! 382: *cp2 = '/'; ! 383: cp2++; ! 384: } ! 385: putfile(*argv++, cp2, parent); ! 386: if (chdir(wdir) < 0) ! 387: fprintf(stderr, "tar: cannot change back?: %s: %s\n", ! 388: wdir, strerror(errno)); ! 389: } ! 390: putempty(); ! 391: putempty(); ! 392: flushtape(); ! 393: if (prtlinkerr == 0) ! 394: return; ! 395: for (; ihead != NULL; ihead = ihead->nextp) { ! 396: if (ihead->count == 0) ! 397: continue; ! 398: fprintf(stderr, "tar: missing links to %s\n", ihead->pathname); ! 399: } ! 400: } ! 401: ! 402: endtape() ! 403: { ! 404: return (dblock.dbuf.name[0] == '\0'); ! 405: } ! 406: ! 407: getdir() ! 408: { ! 409: register struct stat *sp; ! 410: int i; ! 411: ! 412: top: ! 413: readtape((char *)&dblock); ! 414: if (dblock.dbuf.name[0] == '\0') ! 415: return; ! 416: sp = &stbuf; ! 417: sscanf(dblock.dbuf.mode, "%o", &i); ! 418: sp->st_mode = i; ! 419: sscanf(dblock.dbuf.uid, "%o", &i); ! 420: sp->st_uid = i; ! 421: sscanf(dblock.dbuf.gid, "%o", &i); ! 422: sp->st_gid = i; ! 423: sscanf(dblock.dbuf.size, "%lo", &sp->st_size); ! 424: sscanf(dblock.dbuf.mtime, "%lo", &sp->st_mtime); ! 425: sscanf(dblock.dbuf.chksum, "%o", &chksum); ! 426: if (chksum != (i = checksum())) { ! 427: fprintf(stderr, "tar: directory checksum error (%d != %d)\n", ! 428: chksum, i); ! 429: if (iflag) ! 430: goto top; ! 431: done(2); ! 432: } ! 433: /* strip off leading "/" if present */ ! 434: if (sflag && dblock.dbuf.name[0] == '/') { ! 435: register char *cp1, *cp2; ! 436: for (cp1 = cp2 = dblock.dbuf.name; *cp2 && *cp2 == '/'; ++cp2); ! 437: if (!*cp2) ! 438: goto top; ! 439: while (*cp1++ = *cp2++); ! 440: } ! 441: if (tfile != NULL) ! 442: fprintf(tfile, "%s %s\n", dblock.dbuf.name, dblock.dbuf.mtime); ! 443: } ! 444: ! 445: passtape() ! 446: { ! 447: long blocks; ! 448: char *bufp; ! 449: ! 450: if (dblock.dbuf.linkflag == '1') ! 451: return; ! 452: blocks = stbuf.st_size; ! 453: blocks += TBLOCK-1; ! 454: blocks /= TBLOCK; ! 455: ! 456: while (blocks-- > 0) ! 457: (void) readtbuf(&bufp, TBLOCK); ! 458: } ! 459: ! 460: putfile(longname, shortname, parent) ! 461: char *longname; ! 462: char *shortname; ! 463: char *parent; ! 464: { ! 465: int infile = 0; ! 466: long blocks; ! 467: char buf[TBLOCK]; ! 468: char *bigbuf; ! 469: register char *cp; ! 470: struct direct *dp; ! 471: DIR *dirp; ! 472: register int i; ! 473: long l; ! 474: char newparent[NAMSIZ+64]; ! 475: int maxread; ! 476: int hint; /* amount to write to get "in sync" */ ! 477: ! 478: if (!hflag) ! 479: i = lstat(shortname, &stbuf); ! 480: else ! 481: i = stat(shortname, &stbuf); ! 482: if (i < 0) { ! 483: fprintf(stderr, "tar: %s: %s\n", longname, strerror(errno)); ! 484: return; ! 485: } ! 486: if (tfile != NULL && checkupdate(longname) == 0) ! 487: return; ! 488: if (checkw('r', longname) == 0) ! 489: return; ! 490: if (Fflag && checkf(shortname, stbuf.st_mode, Fflag) == 0) ! 491: return; ! 492: ! 493: switch (stbuf.st_mode & S_IFMT) { ! 494: case S_IFDIR: ! 495: for (i = 0, cp = buf; *cp++ = longname[i++];) ! 496: ; ! 497: *--cp = '/'; ! 498: *++cp = 0 ; ! 499: if (!oflag) { ! 500: if ((cp - buf) >= NAMSIZ) { ! 501: fprintf(stderr, "tar: %s: file name too long\n", ! 502: longname); ! 503: return; ! 504: } ! 505: stbuf.st_size = 0; ! 506: tomodes(&stbuf); ! 507: strcpy(dblock.dbuf.name,buf); ! 508: (void)sprintf(dblock.dbuf.chksum, "%6o", checksum()); ! 509: (void) writetape((char *)&dblock); ! 510: } ! 511: (void)sprintf(newparent, "%s/%s", parent, shortname); ! 512: if (chdir(shortname) < 0) { ! 513: fprintf(stderr, "tar: chdir %s: %s\n", ! 514: shortname, strerror(errno)); ! 515: return; ! 516: } ! 517: if ((dirp = opendir(".")) == NULL) { ! 518: fprintf(stderr, "tar: %s: directory read error\n", ! 519: longname); ! 520: if (chdir(parent) < 0) { ! 521: fprintf(stderr, ! 522: "tar: cannot change back?: %s: %s\n", ! 523: parent, strerror(errno)); ! 524: } ! 525: return; ! 526: } ! 527: while ((dp = readdir(dirp)) != NULL && !term) { ! 528: if (!strcmp(".", dp->d_name) || ! 529: !strcmp("..", dp->d_name)) ! 530: continue; ! 531: strcpy(cp, dp->d_name); ! 532: l = telldir(dirp); ! 533: closedir(dirp); ! 534: putfile(buf, cp, newparent); ! 535: dirp = opendir("."); ! 536: seekdir(dirp, l); ! 537: } ! 538: closedir(dirp); ! 539: if (chdir(parent) < 0) { ! 540: fprintf(stderr, ! 541: "tar: cannot change back?: %s: %s\n", ! 542: parent, strerror(errno)); ! 543: } ! 544: break; ! 545: ! 546: case S_IFLNK: ! 547: tomodes(&stbuf); ! 548: if (strlen(longname) >= NAMSIZ) { ! 549: fprintf(stderr, "tar: %s: file name too long\n", ! 550: longname); ! 551: return; ! 552: } ! 553: strcpy(dblock.dbuf.name, longname); ! 554: if (stbuf.st_size + 1 >= NAMSIZ) { ! 555: fprintf(stderr, "tar: %s: symbolic link too long\n", ! 556: longname); ! 557: return; ! 558: } ! 559: i = readlink(shortname, dblock.dbuf.linkname, NAMSIZ - 1); ! 560: if (i < 0) { ! 561: fprintf(stderr, ! 562: "tar: can't read symbolic link %s: %s\n", ! 563: longname, strerror(errno)); ! 564: return; ! 565: } ! 566: dblock.dbuf.linkname[i] = '\0'; ! 567: dblock.dbuf.linkflag = '2'; ! 568: if (vflag) ! 569: fprintf(vfile, "a %s symbolic link to %s\n", ! 570: longname, dblock.dbuf.linkname); ! 571: (void)sprintf(dblock.dbuf.size, "%11lo", 0L); ! 572: (void)sprintf(dblock.dbuf.chksum, "%6o", checksum()); ! 573: (void) writetape((char *)&dblock); ! 574: break; ! 575: ! 576: case S_IFREG: ! 577: if ((infile = open(shortname, 0)) < 0) { ! 578: fprintf(stderr, "tar: %s: %s\n", ! 579: longname, strerror(errno)); ! 580: return; ! 581: } ! 582: tomodes(&stbuf); ! 583: if (strlen(longname) >= NAMSIZ) { ! 584: fprintf(stderr, "tar: %s: file name too long\n", ! 585: longname); ! 586: close(infile); ! 587: return; ! 588: } ! 589: strcpy(dblock.dbuf.name, longname); ! 590: if (stbuf.st_nlink > 1) { ! 591: struct linkbuf *lp; ! 592: int found = 0; ! 593: ! 594: for (lp = ihead; lp != NULL; lp = lp->nextp) ! 595: if (lp->inum == stbuf.st_ino && ! 596: lp->devnum == stbuf.st_dev) { ! 597: found++; ! 598: break; ! 599: } ! 600: if (found) { ! 601: strcpy(dblock.dbuf.linkname, lp->pathname); ! 602: dblock.dbuf.linkflag = '1'; ! 603: (void)sprintf(dblock.dbuf.chksum, "%6o", checksum()); ! 604: (void) writetape( (char *) &dblock); ! 605: if (vflag) ! 606: fprintf(vfile, "a %s link to %s\n", ! 607: longname, lp->pathname); ! 608: lp->count--; ! 609: close(infile); ! 610: return; ! 611: } ! 612: lp = (struct linkbuf *) getmem(sizeof(*lp)); ! 613: if (lp != NULL) { ! 614: lp->nextp = ihead; ! 615: ihead = lp; ! 616: lp->inum = stbuf.st_ino; ! 617: lp->devnum = stbuf.st_dev; ! 618: lp->count = stbuf.st_nlink - 1; ! 619: strcpy(lp->pathname, longname); ! 620: } ! 621: } ! 622: blocks = (stbuf.st_size + (TBLOCK-1)) / TBLOCK; ! 623: if (vflag) ! 624: fprintf(vfile, "a %s %ld blocks\n", longname, blocks); ! 625: (void)sprintf(dblock.dbuf.chksum, "%6o", checksum()); ! 626: hint = writetape((char *)&dblock); ! 627: maxread = max(stbuf.st_blksize, (nblock * TBLOCK)); ! 628: if ((bigbuf = malloc((unsigned)maxread)) == 0) { ! 629: maxread = TBLOCK; ! 630: bigbuf = buf; ! 631: } ! 632: ! 633: while ((i = read(infile, bigbuf, min((hint*TBLOCK), maxread))) > 0 ! 634: && blocks > 0) { ! 635: register int nblks; ! 636: ! 637: nblks = ((i-1)/TBLOCK)+1; ! 638: if (nblks > blocks) ! 639: nblks = blocks; ! 640: hint = writetbuf(bigbuf, nblks); ! 641: blocks -= nblks; ! 642: } ! 643: close(infile); ! 644: if (bigbuf != buf) ! 645: free(bigbuf); ! 646: if (i < 0) { ! 647: fprintf(stderr, "tar: Read error on %s: %s\n", ! 648: longname, strerror(errno)); ! 649: } else if (blocks != 0 || i != 0) ! 650: fprintf(stderr, "tar: %s: file changed size\n", ! 651: longname); ! 652: while (--blocks >= 0) ! 653: putempty(); ! 654: break; ! 655: ! 656: default: ! 657: fprintf(stderr, "tar: %s is not a file. Not dumped\n", ! 658: longname); ! 659: break; ! 660: } ! 661: } ! 662: ! 663: doxtract(argv) ! 664: char *argv[]; ! 665: { ! 666: long blocks, bytes; ! 667: int ofile, i; ! 668: ! 669: for (;;) { ! 670: if ((i = wantit(argv)) == 0) ! 671: continue; ! 672: if (i == -1) ! 673: break; /* end of tape */ ! 674: if (checkw('x', dblock.dbuf.name) == 0) { ! 675: passtape(); ! 676: continue; ! 677: } ! 678: if (Fflag) { ! 679: char *s; ! 680: ! 681: if ((s = rindex(dblock.dbuf.name, '/')) == 0) ! 682: s = dblock.dbuf.name; ! 683: else ! 684: s++; ! 685: if (checkf(s, stbuf.st_mode, Fflag) == 0) { ! 686: passtape(); ! 687: continue; ! 688: } ! 689: } ! 690: if (checkdir(dblock.dbuf.name)) { /* have a directory */ ! 691: if (mflag == 0) ! 692: dodirtimes(&dblock); ! 693: continue; ! 694: } ! 695: if (dblock.dbuf.linkflag == '2') { /* symlink */ ! 696: /* ! 697: * only unlink non directories or empty ! 698: * directories ! 699: */ ! 700: if (rmdir(dblock.dbuf.name) < 0) { ! 701: if (errno == ENOTDIR) ! 702: unlink(dblock.dbuf.name); ! 703: } ! 704: if (symlink(dblock.dbuf.linkname, dblock.dbuf.name)<0) { ! 705: fprintf(stderr, ! 706: "tar: %s: symbolic link failed: %s\n", ! 707: dblock.dbuf.name, strerror(errno)); ! 708: continue; ! 709: } ! 710: if (vflag) ! 711: fprintf(vfile, "x %s symbolic link to %s\n", ! 712: dblock.dbuf.name, dblock.dbuf.linkname); ! 713: #ifdef notdef ! 714: /* ignore alien orders */ ! 715: chown(dblock.dbuf.name, stbuf.st_uid, stbuf.st_gid); ! 716: if (mflag == 0) ! 717: setimes(dblock.dbuf.name, stbuf.st_mtime); ! 718: if (pflag) ! 719: chmod(dblock.dbuf.name, stbuf.st_mode & 07777); ! 720: #endif ! 721: continue; ! 722: } ! 723: if (dblock.dbuf.linkflag == '1') { /* regular link */ ! 724: /* ! 725: * only unlink non directories or empty ! 726: * directories ! 727: */ ! 728: if (rmdir(dblock.dbuf.name) < 0) { ! 729: if (errno == ENOTDIR) ! 730: unlink(dblock.dbuf.name); ! 731: } ! 732: if (link(dblock.dbuf.linkname, dblock.dbuf.name) < 0) { ! 733: fprintf(stderr, ! 734: "tar: can't link %s to %s: %s\n", ! 735: dblock.dbuf.name, dblock.dbuf.linkname, ! 736: strerror(errno)); ! 737: continue; ! 738: } ! 739: if (vflag) ! 740: fprintf(vfile, "%s linked to %s\n", ! 741: dblock.dbuf.name, dblock.dbuf.linkname); ! 742: continue; ! 743: } ! 744: if ((ofile = creat(dblock.dbuf.name,stbuf.st_mode&0xfff)) < 0) { ! 745: fprintf(stderr, "tar: can't create %s: %s\n", ! 746: dblock.dbuf.name, strerror(errno)); ! 747: passtape(); ! 748: continue; ! 749: } ! 750: chown(dblock.dbuf.name, stbuf.st_uid, stbuf.st_gid); ! 751: blocks = ((bytes = stbuf.st_size) + TBLOCK-1)/TBLOCK; ! 752: if (vflag) ! 753: fprintf(vfile, "x %s, %ld bytes, %ld tape blocks\n", ! 754: dblock.dbuf.name, bytes, blocks); ! 755: for (; blocks > 0;) { ! 756: register int nread; ! 757: char *bufp; ! 758: register int nwant; ! 759: ! 760: nwant = NBLOCK*TBLOCK; ! 761: if (nwant > (blocks*TBLOCK)) ! 762: nwant = (blocks*TBLOCK); ! 763: nread = readtbuf(&bufp, nwant); ! 764: if (write(ofile, bufp, (int)min(nread, bytes)) < 0) { ! 765: fprintf(stderr, ! 766: "tar: %s: HELP - extract write error: %s\n", ! 767: dblock.dbuf.name, strerror(errno)); ! 768: done(2); ! 769: } ! 770: bytes -= nread; ! 771: blocks -= (((nread-1)/TBLOCK)+1); ! 772: } ! 773: close(ofile); ! 774: if (mflag == 0) ! 775: setimes(dblock.dbuf.name, stbuf.st_mtime); ! 776: if (pflag) ! 777: chmod(dblock.dbuf.name, stbuf.st_mode & 07777); ! 778: } ! 779: if (mflag == 0) { ! 780: dblock.dbuf.name[0] = '\0'; /* process the whole stack */ ! 781: dodirtimes(&dblock); ! 782: } ! 783: } ! 784: ! 785: dotable(argv) ! 786: char *argv[]; ! 787: { ! 788: register int i; ! 789: ! 790: for (;;) { ! 791: if ((i = wantit(argv)) == 0) ! 792: continue; ! 793: if (i == -1) ! 794: break; /* end of tape */ ! 795: if (vflag) ! 796: longt(&stbuf); ! 797: printf("%s", dblock.dbuf.name); ! 798: if (dblock.dbuf.linkflag == '1') ! 799: printf(" linked to %s", dblock.dbuf.linkname); ! 800: if (dblock.dbuf.linkflag == '2') ! 801: printf(" symbolic link to %s", dblock.dbuf.linkname); ! 802: printf("\n"); ! 803: passtape(); ! 804: } ! 805: } ! 806: ! 807: putempty() ! 808: { ! 809: char buf[TBLOCK]; ! 810: ! 811: bzero(buf, sizeof (buf)); ! 812: (void) writetape(buf); ! 813: } ! 814: ! 815: longt(st) ! 816: register struct stat *st; ! 817: { ! 818: register char *cp; ! 819: char *ctime(); ! 820: ! 821: pmode(st); ! 822: printf("%3u/%1u", st->st_uid, st->st_gid); ! 823: printf("%7ld", st->st_size); ! 824: cp = ctime(&st->st_mtime); ! 825: printf(" %-12.12s %-4.4s ", cp+4, cp+20); ! 826: } ! 827: ! 828: #define SUID 04000 ! 829: #define SGID 02000 ! 830: #define ROWN 0400 ! 831: #define WOWN 0200 ! 832: #define XOWN 0100 ! 833: #define RGRP 040 ! 834: #define WGRP 020 ! 835: #define XGRP 010 ! 836: #define ROTH 04 ! 837: #define WOTH 02 ! 838: #define XOTH 01 ! 839: #define STXT 01000 ! 840: int m1[] = { 1, ROWN, 'r', '-' }; ! 841: int m2[] = { 1, WOWN, 'w', '-' }; ! 842: int m3[] = { 2, SUID, 's', XOWN, 'x', '-' }; ! 843: int m4[] = { 1, RGRP, 'r', '-' }; ! 844: int m5[] = { 1, WGRP, 'w', '-' }; ! 845: int m6[] = { 2, SGID, 's', XGRP, 'x', '-' }; ! 846: int m7[] = { 1, ROTH, 'r', '-' }; ! 847: int m8[] = { 1, WOTH, 'w', '-' }; ! 848: int m9[] = { 2, STXT, 't', XOTH, 'x', '-' }; ! 849: ! 850: int *m[] = { m1, m2, m3, m4, m5, m6, m7, m8, m9}; ! 851: ! 852: pmode(st) ! 853: register struct stat *st; ! 854: { ! 855: register int **mp; ! 856: ! 857: for (mp = &m[0]; mp < &m[9];) ! 858: selectbits(*mp++, st); ! 859: } ! 860: ! 861: selectbits(pairp, st) ! 862: int *pairp; ! 863: struct stat *st; ! 864: { ! 865: register int n, *ap; ! 866: ! 867: ap = pairp; ! 868: n = *ap++; ! 869: while (--n>=0 && (st->st_mode&*ap++)==0) ! 870: ap++; ! 871: putchar(*ap); ! 872: } ! 873: ! 874: /* ! 875: * Make all directories needed by `name'. If `name' is itself ! 876: * a directory on the tar tape (indicated by a trailing '/'), ! 877: * return 1; else 0. ! 878: */ ! 879: checkdir(name) ! 880: register char *name; ! 881: { ! 882: register char *cp; ! 883: ! 884: /* ! 885: * Quick check for existence of directory. ! 886: */ ! 887: if ((cp = rindex(name, '/')) == 0) ! 888: return (0); ! 889: *cp = '\0'; ! 890: if (access(name, F_OK) == 0) { /* already exists */ ! 891: *cp = '/'; ! 892: return (cp[1] == '\0'); /* return (lastchar == '/') */ ! 893: } ! 894: *cp = '/'; ! 895: ! 896: /* ! 897: * No luck, try to make all directories in path. ! 898: */ ! 899: for (cp = name; *cp; cp++) { ! 900: if (*cp != '/') ! 901: continue; ! 902: *cp = '\0'; ! 903: if (access(name, F_OK) < 0) { ! 904: if (mkdir(name, 0777) < 0) { ! 905: fprintf(stderr, "tar: mkdir: %s: %s\n", ! 906: name, strerror(errno)); ! 907: *cp = '/'; ! 908: return (0); ! 909: } ! 910: chown(name, stbuf.st_uid, stbuf.st_gid); ! 911: if (pflag && cp[1] == '\0') /* dir on the tape */ ! 912: chmod(name, stbuf.st_mode & 07777); ! 913: } ! 914: *cp = '/'; ! 915: } ! 916: return (cp[-1]=='/'); ! 917: } ! 918: ! 919: onintr() ! 920: { ! 921: (void) signal(SIGINT, SIG_IGN); ! 922: term++; ! 923: } ! 924: ! 925: onquit() ! 926: { ! 927: (void) signal(SIGQUIT, SIG_IGN); ! 928: term++; ! 929: } ! 930: ! 931: onhup() ! 932: { ! 933: (void) signal(SIGHUP, SIG_IGN); ! 934: term++; ! 935: } ! 936: ! 937: #ifdef notdef ! 938: onterm() ! 939: { ! 940: (void) signal(SIGTERM, SIG_IGN); ! 941: term++; ! 942: } ! 943: #endif ! 944: ! 945: tomodes(sp) ! 946: register struct stat *sp; ! 947: { ! 948: register char *cp; ! 949: ! 950: for (cp = dblock.dummy; cp < &dblock.dummy[TBLOCK]; cp++) ! 951: *cp = '\0'; ! 952: (void)sprintf(dblock.dbuf.mode, "%6o ", sp->st_mode & 07777); ! 953: (void)sprintf(dblock.dbuf.uid, "%6o ", sp->st_uid); ! 954: (void)sprintf(dblock.dbuf.gid, "%6o ", sp->st_gid); ! 955: (void)sprintf(dblock.dbuf.size, "%11lo ", sp->st_size); ! 956: (void)sprintf(dblock.dbuf.mtime, "%11lo ", sp->st_mtime); ! 957: } ! 958: ! 959: checksum() ! 960: { ! 961: register i; ! 962: register char *cp; ! 963: ! 964: for (cp = dblock.dbuf.chksum; ! 965: cp < &dblock.dbuf.chksum[sizeof(dblock.dbuf.chksum)]; cp++) ! 966: *cp = ' '; ! 967: i = 0; ! 968: for (cp = dblock.dummy; cp < &dblock.dummy[TBLOCK]; cp++) ! 969: i += *cp; ! 970: return (i); ! 971: } ! 972: ! 973: checkw(c, name) ! 974: char *name; ! 975: { ! 976: if (!wflag) ! 977: return (1); ! 978: printf("%c ", c); ! 979: if (vflag) ! 980: longt(&stbuf); ! 981: printf("%s: ", name); ! 982: return (response() == 'y'); ! 983: } ! 984: ! 985: response() ! 986: { ! 987: char c; ! 988: ! 989: c = getchar(); ! 990: if (c != '\n') ! 991: while (getchar() != '\n') ! 992: ; ! 993: else ! 994: c = 'n'; ! 995: return (c); ! 996: } ! 997: ! 998: checkf(name, mode, howmuch) ! 999: char *name; ! 1000: int mode, howmuch; ! 1001: { ! 1002: int l; ! 1003: ! 1004: if ((mode & S_IFMT) == S_IFDIR){ ! 1005: if ((strcmp(name, "SCCS")==0) || (strcmp(name, "RCS")==0)) ! 1006: return(0); ! 1007: return(1); ! 1008: } ! 1009: if ((l = strlen(name)) < 3) ! 1010: return (1); ! 1011: if (howmuch > 1 && name[l-2] == '.' && name[l-1] == 'o') ! 1012: return (0); ! 1013: if (strcmp(name, "core") == 0 || ! 1014: strcmp(name, "errs") == 0 || ! 1015: (howmuch > 1 && strcmp(name, "a.out") == 0)) ! 1016: return (0); ! 1017: /* SHOULD CHECK IF IT IS EXECUTABLE */ ! 1018: return (1); ! 1019: } ! 1020: ! 1021: /* Is the current file a new file, or the newest one of the same name? */ ! 1022: checkupdate(arg) ! 1023: char *arg; ! 1024: { ! 1025: char name[100]; ! 1026: long mtime; ! 1027: daddr_t seekp; ! 1028: daddr_t lookup(); ! 1029: ! 1030: rewind(tfile); ! 1031: for (;;) { ! 1032: if ((seekp = lookup(arg)) < 0) ! 1033: return (1); ! 1034: fseek(tfile, seekp, 0); ! 1035: fscanf(tfile, "%s %lo", name, &mtime); ! 1036: return (stbuf.st_mtime > mtime); ! 1037: } ! 1038: } ! 1039: ! 1040: done(n) ! 1041: { ! 1042: unlink(tname); ! 1043: exit(n); ! 1044: } ! 1045: ! 1046: /* ! 1047: * Do we want the next entry on the tape, i.e. is it selected? If ! 1048: * not, skip over the entire entry. Return -1 if reached end of tape. ! 1049: */ ! 1050: wantit(argv) ! 1051: char *argv[]; ! 1052: { ! 1053: register char **cp; ! 1054: ! 1055: getdir(); ! 1056: if (endtape()) ! 1057: return (-1); ! 1058: if (*argv == 0) ! 1059: return (1); ! 1060: for (cp = argv; *cp; cp++) ! 1061: if (prefix(*cp, dblock.dbuf.name)) ! 1062: return (1); ! 1063: passtape(); ! 1064: return (0); ! 1065: } ! 1066: ! 1067: /* ! 1068: * Does s2 begin with the string s1, on a directory boundary? ! 1069: */ ! 1070: prefix(s1, s2) ! 1071: register char *s1, *s2; ! 1072: { ! 1073: while (*s1) ! 1074: if (*s1++ != *s2++) ! 1075: return (0); ! 1076: if (*s2) ! 1077: return (*s2 == '/'); ! 1078: return (1); ! 1079: } ! 1080: ! 1081: #define N 200 ! 1082: int njab; ! 1083: ! 1084: daddr_t ! 1085: lookup(s) ! 1086: char *s; ! 1087: { ! 1088: register i; ! 1089: daddr_t a; ! 1090: ! 1091: for(i=0; s[i]; i++) ! 1092: if (s[i] == ' ') ! 1093: break; ! 1094: a = bsrch(s, i, low, high); ! 1095: return (a); ! 1096: } ! 1097: ! 1098: daddr_t ! 1099: bsrch(s, n, l, h) ! 1100: daddr_t l, h; ! 1101: char *s; ! 1102: { ! 1103: register i, j; ! 1104: char b[N]; ! 1105: daddr_t m, m1; ! 1106: ! 1107: njab = 0; ! 1108: ! 1109: loop: ! 1110: if (l >= h) ! 1111: return ((daddr_t) -1); ! 1112: m = l + (h-l)/2 - N/2; ! 1113: if (m < l) ! 1114: m = l; ! 1115: fseek(tfile, m, 0); ! 1116: fread(b, 1, N, tfile); ! 1117: njab++; ! 1118: for(i=0; i<N; i++) { ! 1119: if (b[i] == '\n') ! 1120: break; ! 1121: m++; ! 1122: } ! 1123: if (m >= h) ! 1124: return ((daddr_t) -1); ! 1125: m1 = m; ! 1126: j = i; ! 1127: for(i++; i<N; i++) { ! 1128: m1++; ! 1129: if (b[i] == '\n') ! 1130: break; ! 1131: } ! 1132: i = cmp(b+j, s, n); ! 1133: if (i < 0) { ! 1134: h = m; ! 1135: goto loop; ! 1136: } ! 1137: if (i > 0) { ! 1138: l = m1; ! 1139: goto loop; ! 1140: } ! 1141: return (m); ! 1142: } ! 1143: ! 1144: cmp(b, s, n) ! 1145: char *b, *s; ! 1146: { ! 1147: register i; ! 1148: ! 1149: if (b[0] != '\n') ! 1150: exit(2); ! 1151: for(i=0; i<n; i++) { ! 1152: if (b[i+1] > s[i]) ! 1153: return (-1); ! 1154: if (b[i+1] < s[i]) ! 1155: return (1); ! 1156: } ! 1157: return (b[i+1] == ' '? 0 : -1); ! 1158: } ! 1159: ! 1160: readtape(buffer) ! 1161: char *buffer; ! 1162: { ! 1163: char *bufp; ! 1164: ! 1165: if (first == 0) ! 1166: getbuf(); ! 1167: (void) readtbuf(&bufp, TBLOCK); ! 1168: bcopy(bufp, buffer, TBLOCK); ! 1169: return(TBLOCK); ! 1170: } ! 1171: ! 1172: readtbuf(bufpp, size) ! 1173: char **bufpp; ! 1174: int size; ! 1175: { ! 1176: register int i; ! 1177: ! 1178: if (recno >= nblock || first == 0) { ! 1179: if ((i = bread(mt, (char *)tbuf, TBLOCK*nblock)) < 0) ! 1180: mterr("read", i, 3); ! 1181: if (first == 0) { ! 1182: if ((i % TBLOCK) != 0) { ! 1183: fprintf(stderr, "tar: tape blocksize error\n"); ! 1184: done(3); ! 1185: } ! 1186: i /= TBLOCK; ! 1187: if (i != nblock) { ! 1188: fprintf(stderr, "tar: blocksize = %d\n", i); ! 1189: nblock = i; ! 1190: } ! 1191: first = 1; ! 1192: } ! 1193: recno = 0; ! 1194: } ! 1195: if (size > ((nblock-recno)*TBLOCK)) ! 1196: size = (nblock-recno)*TBLOCK; ! 1197: *bufpp = (char *)&tbuf[recno]; ! 1198: recno += (size/TBLOCK); ! 1199: return (size); ! 1200: } ! 1201: ! 1202: writetbuf(buffer, n) ! 1203: register char *buffer; ! 1204: register int n; ! 1205: { ! 1206: int i; ! 1207: ! 1208: if (first == 0) { ! 1209: getbuf(); ! 1210: first = 1; ! 1211: } ! 1212: if (recno >= nblock) { ! 1213: i = write(mt, (char *)tbuf, TBLOCK*nblock); ! 1214: if (i != TBLOCK*nblock) ! 1215: mterr("write", i, 2); ! 1216: recno = 0; ! 1217: } ! 1218: ! 1219: /* ! 1220: * Special case: We have an empty tape buffer, and the ! 1221: * users data size is >= the tape block size: Avoid ! 1222: * the bcopy and dma direct to tape. BIG WIN. Add the ! 1223: * residual to the tape buffer. ! 1224: */ ! 1225: while (recno == 0 && n >= nblock) { ! 1226: i = write(mt, buffer, TBLOCK*nblock); ! 1227: if (i != TBLOCK*nblock) ! 1228: mterr("write", i, 2); ! 1229: n -= nblock; ! 1230: buffer += (nblock * TBLOCK); ! 1231: } ! 1232: ! 1233: while (n-- > 0) { ! 1234: bcopy(buffer, (char *)&tbuf[recno++], TBLOCK); ! 1235: buffer += TBLOCK; ! 1236: if (recno >= nblock) { ! 1237: i = write(mt, (char *)tbuf, TBLOCK*nblock); ! 1238: if (i != TBLOCK*nblock) ! 1239: mterr("write", i, 2); ! 1240: recno = 0; ! 1241: } ! 1242: } ! 1243: ! 1244: /* Tell the user how much to write to get in sync */ ! 1245: return (nblock - recno); ! 1246: } ! 1247: ! 1248: backtape() ! 1249: { ! 1250: static int mtdev = 1; ! 1251: static struct mtop mtop = {MTBSR, 1}; ! 1252: struct mtget mtget; ! 1253: ! 1254: if (mtdev == 1) ! 1255: mtdev = ioctl(mt, MTIOCGET, (char *)&mtget); ! 1256: if (mtdev == 0) { ! 1257: if (ioctl(mt, MTIOCTOP, (char *)&mtop) < 0) { ! 1258: fprintf(stderr, "tar: tape backspace error: %s\n", ! 1259: strerror(errno)); ! 1260: done(4); ! 1261: } ! 1262: } else ! 1263: (void)lseek(mt, (daddr_t) -TBLOCK*nblock, 1); ! 1264: recno--; ! 1265: } ! 1266: ! 1267: flushtape() ! 1268: { ! 1269: int i; ! 1270: ! 1271: i = write(mt, (char *)tbuf, TBLOCK*nblock); ! 1272: if (i != TBLOCK*nblock) ! 1273: mterr("write", i, 2); ! 1274: } ! 1275: ! 1276: mterr(operation, i, exitcode) ! 1277: char *operation; ! 1278: int i; ! 1279: { ! 1280: fprintf(stderr, "tar: tape %s error: %s\n", ! 1281: operation, i < 0 ? strerror(errno) : "unexpected EOF"); ! 1282: done(exitcode); ! 1283: } ! 1284: ! 1285: bread(fd, buf, size) ! 1286: int fd; ! 1287: char *buf; ! 1288: int size; ! 1289: { ! 1290: int count; ! 1291: static int lastread = 0; ! 1292: ! 1293: if (!Bflag) ! 1294: return (read(fd, buf, size)); ! 1295: ! 1296: for (count = 0; count < size; count += lastread) { ! 1297: lastread = read(fd, buf, size - count); ! 1298: if (lastread <= 0) { ! 1299: if (count > 0) ! 1300: return (count); ! 1301: return (lastread); ! 1302: } ! 1303: buf += lastread; ! 1304: } ! 1305: return (count); ! 1306: } ! 1307: ! 1308: char * ! 1309: getcwd(buf) ! 1310: char *buf; ! 1311: { ! 1312: if (getwd(buf) == NULL) { ! 1313: fprintf(stderr, "tar: %s\n", buf); ! 1314: exit(1); ! 1315: } ! 1316: return (buf); ! 1317: } ! 1318: ! 1319: getbuf() ! 1320: { ! 1321: ! 1322: if (nblock == 0) { ! 1323: fstat(mt, &stbuf); ! 1324: if ((stbuf.st_mode & S_IFMT) == S_IFCHR) ! 1325: nblock = NBLOCK; ! 1326: else { ! 1327: nblock = stbuf.st_blksize / TBLOCK; ! 1328: if (nblock == 0) ! 1329: nblock = NBLOCK; ! 1330: } ! 1331: } ! 1332: tbuf = (union hblock *)malloc((unsigned)nblock*TBLOCK); ! 1333: if (tbuf == NULL) { ! 1334: fprintf(stderr, "tar: blocksize %d too big, can't get memory\n", ! 1335: nblock); ! 1336: done(1); ! 1337: } ! 1338: } ! 1339: ! 1340: /* ! 1341: * Save this directory and its mtime on the stack, popping and setting ! 1342: * the mtimes of any stacked dirs which aren't parents of this one. ! 1343: * A null directory causes the entire stack to be unwound and set. ! 1344: * ! 1345: * Since all the elements of the directory "stack" share a common ! 1346: * prefix, we can make do with one string. We keep only the current ! 1347: * directory path, with an associated array of mtime's, one for each ! 1348: * '/' in the path. A negative mtime means no mtime. The mtime's are ! 1349: * offset by one (first index 1, not 0) because calling this with a null ! 1350: * directory causes mtime[0] to be set. ! 1351: * ! 1352: * This stack algorithm is not guaranteed to work for tapes created ! 1353: * with the 'r' option, but the vast majority of tapes with ! 1354: * directories are not. This avoids saving every directory record on ! 1355: * the tape and setting all the times at the end. ! 1356: */ ! 1357: char dirstack[NAMSIZ]; ! 1358: #define NTIM (NAMSIZ/2+1) /* a/b/c/d/... */ ! 1359: time_t mtime[NTIM]; ! 1360: ! 1361: dodirtimes(hp) ! 1362: union hblock *hp; ! 1363: { ! 1364: register char *p = dirstack; ! 1365: register char *q = hp->dbuf.name; ! 1366: register int ndir = 0; ! 1367: char *savp; ! 1368: int savndir; ! 1369: ! 1370: /* Find common prefix */ ! 1371: while (*p == *q && *p) { ! 1372: if (*p++ == '/') ! 1373: ++ndir; ! 1374: q++; ! 1375: } ! 1376: ! 1377: savp = p; ! 1378: savndir = ndir; ! 1379: while (*p) { ! 1380: /* ! 1381: * Not a child: unwind the stack, setting the times. ! 1382: * The order we do this doesn't matter, so we go "forward." ! 1383: */ ! 1384: if (*p++ == '/') ! 1385: if (mtime[++ndir] >= 0) { ! 1386: *--p = '\0'; /* zap the slash */ ! 1387: setimes(dirstack, mtime[ndir]); ! 1388: *p++ = '/'; ! 1389: } ! 1390: } ! 1391: p = savp; ! 1392: ndir = savndir; ! 1393: ! 1394: /* Push this one on the "stack" */ ! 1395: while (*p = *q++) /* append the rest of the new dir */ ! 1396: if (*p++ == '/') ! 1397: mtime[++ndir] = -1; ! 1398: mtime[ndir] = stbuf.st_mtime; /* overwrite the last one */ ! 1399: } ! 1400: ! 1401: setimes(path, mt) ! 1402: char *path; ! 1403: time_t mt; ! 1404: { ! 1405: struct timeval tv[2]; ! 1406: ! 1407: tv[0].tv_sec = time((time_t *) 0); ! 1408: tv[1].tv_sec = mt; ! 1409: tv[0].tv_usec = tv[1].tv_usec = 0; ! 1410: if (utimes(path, tv) < 0) ! 1411: fprintf(stderr, "tar: can't set time on %s: %s\n", ! 1412: path, strerror(errno)); ! 1413: } ! 1414: ! 1415: char * ! 1416: getmem(size) ! 1417: { ! 1418: char *p = malloc((unsigned) size); ! 1419: ! 1420: if (p == NULL && freemem) { ! 1421: fprintf(stderr, ! 1422: "tar: out of memory, link and directory modtime info lost\n"); ! 1423: freemem = 0; ! 1424: } ! 1425: return (p); ! 1426: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.