/* * Original: * Copyright (c) 1999 by Aaron R. Crane. * Permission is granted to use, distribute, or modify this source, * provided that this copyright notice remains intact. * * Modified: * Copyright (c) 2014 by David I. Bell * Permission is granted to use, distribute, or modify this source, * provided that this copyright notice remains intact. * * The "ar" built-in command. * This allows extraction and listing of ar files. */ #include #include #include #include #include #include #include #include #include #include #include "sash.h" /* * Structure to hold information about the archive file. */ typedef struct { int fd; /* file reading archive from */ BOOL eof; /* end of file has been seen */ BOOL rescan; /* rescan the header just read */ char * nameTable; /* long name table */ /* * Information about the current file read from the archive. * This is extracted from the latest member header read. */ char * name; /* current file name */ time_t date; /* date of file */ uid_t uid; /* user id */ gid_t gid; /* group id */ mode_t mode; /* file protection */ off_t size; /* file size */ int pad; /* padding to next header */ } Archive; /* * Local procedures. */ static void initArchive(Archive * arch); static BOOL openArchive(const char * name, Archive * arch); static void closeArchive(Archive * arch); static BOOL readSpecialMember(Archive * arch); static BOOL readNormalMember(Archive * arch); static BOOL readMember(Archive * arch, struct ar_hdr * hdr); static BOOL skipMember(const Archive * arch); static BOOL skipPadding(int fd, int pad); static BOOL writeFile(const Archive * arch, int outfd); static int createFile(const Archive * arch); static BOOL canonicalize(Archive * arch, const struct ar_hdr * hdr); static void listMember(const Archive * arch); static int shortNameMatches44BSD(const char * name); static int shortNameMatchesSysV(const char * name); static BOOL wantMember(const Archive * arch, int n_names, const char ** names); static BOOL getNumber(const char * s, unsigned base, int max_digits, unsigned long * ul); int do_ar(int argc, const char ** argv) { const char * options; const char * archiveName; BOOL doExtract; BOOL doTable; BOOL doPrint; BOOL verbose; int r; Archive arch; r = 0; verbose = FALSE; doExtract = FALSE; doTable = FALSE; doPrint = FALSE; if (argc < 3) { fprintf(stderr, "Too few arguments for ar\n"); return 1; } /* * Get the option string and archive file name. */ options = argv[1]; archiveName = argv[2]; /* * Advance the arguments to the list of file names (if any). */ argc -= 3; argv += 3; /* * Parse the option characters. */ for (; *options; options++) { switch (*options) { case 't': doTable = TRUE; break; case 'x': doExtract = TRUE; break; case 'p': doPrint = TRUE; break; case 'v': verbose = TRUE; break; case 'd': case 'm': case 'q': case 'r': fprintf(stderr, "Writing ar files is not supported\n"); return 1; default: fprintf(stderr, "Unknown ar flag: %c\n", *options); return 1; } } if (doExtract + doTable + doPrint != 1) { fprintf(stderr, "Exactly one of 'x', 'p' or 't' must be specified\n"); return 1; } /* * Open the archive file. */ initArchive(&arch); if (!openArchive(archiveName, &arch)) return 1; /* * Read the first special member of the archive. */ if (!readSpecialMember(&arch)) return 1; /* * Read all of the normal members of the archive. */ while (readNormalMember(&arch)) { /* * If this file is not wanted then skip it. */ if (!wantMember(&arch, argc, argv)) { if (!skipMember(&arch)) break; continue; } /* * This file is wanted. */ if (doTable) { if (verbose) listMember(&arch); else puts(arch.name); if (!skipMember(&arch)) break; } else if (doPrint) { if (verbose) { /* * The verbose format makes me gag, * but 4.4BSD, GNU and even V7 all * have the same lossage. */ printf("\n<%s>\n\n", arch.name); fflush(stdout); } if (!writeFile(&arch, STDOUT)) break; } else if (doExtract) { int outfd; BOOL success; if (verbose) printf("x - %s\n", arch.name); outfd = createFile(&arch); if (outfd == -1) break; success = writeFile(&arch, outfd); if (close(outfd) == -1) { fprintf(stderr, "Can't close %s: %s\n", arch.name, strerror(errno)); break; } if (!success) { r = 1; break; } } else { fprintf(stderr, "Oops -- I don't know what to do\n"); r = 1; break; } } closeArchive(&arch); return r; } /* * Open the file for writing for the specified archive structure, * setting its mode and owner if possible. Returns the file handle * of the opened file, or -1 on an error. */ static int createFile(const Archive * arch) { int fd; fd = open(arch->name, O_WRONLY | O_CREAT | O_TRUNC, arch->mode); if (fd == -1) { fprintf(stderr, "Can't create \"%s\": %s\n", arch->name, strerror(errno)); return -1; } /* * Don't worry if these fail. We have to do the fchmod() despite * specifying the mode in the open() call, because that mode is * munged by the umask. */ checkStatus("fchmod", fchmod(fd, arch->mode)); checkStatus("fchown", fchown(fd, arch->uid, arch->gid)); return fd; } /* * Return whether the current archive member is wanted. * This means that the file name is contained in the specified list of * file names, or else that the specified list of file names is empty. */ static BOOL wantMember(const Archive * arch, int n_names, const char ** name) { int i; /* * If there are no names then all archive members are wanted. */ if (n_names == 0) return TRUE; /* * See if the member file name is contained in the list. */ for (i = 0; i < n_names; i++) { if (strcmp(arch->name, name[i]) == 0) return TRUE; } return FALSE; } /* * Parse a number from the specified string in the specified base. * The number is terminated by a null, space, or the specified number * of digits. The number is returned through the supplied pointer. * Only non-negatives numbers are parsed. Returns TRUE on success. */ static BOOL getNumber(const char * s, unsigned base, int max_digits, unsigned long * ul) { const char * p; const char * endp; unsigned long cutoff; unsigned long cutlim; if (base < 2 || base > 10 || s == 0 || *s == 0 || ul == 0) return FALSE; cutoff = ULONG_MAX / (unsigned long) base; cutlim = ULONG_MAX % (unsigned long) base; *ul = 0; endp = (max_digits >= 0) ? s + max_digits : 0; for (p = s; endp ? p < endp : 1; p++) { unsigned d; if (*p == 0 || *p == ' ') break; /* end of string */ if (!isDecimal(*p)) return FALSE; /* non-digit */ d = *p - '0'; if (d >= base) return FALSE; /* digit outside range for base */ if (*ul > cutoff || (*ul == cutoff && d > cutlim)) return FALSE; /* overflow */ *ul *= base; *ul += d; } if (p == s) return FALSE; /* nothing was converted */ if (*p && *p != ' ') return FALSE; /* trailing garbage */ return TRUE; } /* * Initialise the specified Archive structure for use. */ static void initArchive(Archive * arch) { arch->fd = -1; arch->name = 0; arch->nameTable = 0; arch->eof = FALSE; arch->rescan = FALSE; } /* * Open the specified archive file name and read the header from it. * The file handle is saved in the Archive structure for further use. * Returns TRUE on success. */ static BOOL openArchive(const char * name, Archive * arch) { unsigned char buf[SARMAG]; ssize_t cc; arch->fd = open(name, O_RDONLY); if (arch->fd == -1) { fprintf(stderr, "Can't open archive file %s: %s\n", name, strerror(errno)); return FALSE; } cc = read(arch->fd, buf, SARMAG); if (cc == -1) { fprintf(stderr, "Error reading archive header: %s\n", strerror(errno)); goto close_and_out; } if (cc != SARMAG) { fprintf(stderr, "Short read of archive header\n"); goto close_and_out; } if (memcmp(buf, ARMAG, SARMAG)) { fprintf(stderr, "Invalid archive header\n"); goto close_and_out; } return TRUE; /* * Here on an error to clean up. */ close_and_out: (void) close(arch->fd); arch->fd = -1; return FALSE; } /* * Close the archive file. */ static void closeArchive(Archive * arch) { free(arch->name); arch->name = 0; free(arch->nameTable); arch->nameTable = 0; if (arch->fd >= 0) (void) close(arch->fd); arch->fd = -1; } /* * Read an archive member header into the specified structure. * Returns TRUE on success. On failure, the eof flag is set if * the end of file had been reached. */ static BOOL readMember(Archive * arch, struct ar_hdr * hdr) { ssize_t cc; cc = read(arch->fd, hdr, sizeof(*hdr)); if (cc < 0) { fprintf(stderr, "Error reading member header: %s\n", strerror(errno)); return FALSE; } if (cc == 0) { arch->eof = TRUE; return FALSE; } if (cc != sizeof(*hdr)) { fprintf(stderr, "Short read of member header\n"); return FALSE; } if (memcmp(hdr->ar_fmag, ARFMAG, sizeof(hdr->ar_fmag))) { fprintf(stderr, "Invalid member header\n"); return FALSE; } return TRUE; } /* * Check the member file name and see if it matches the 4.4 BSD * syntax for long file names. If so, return the number of characters * of the actual long file name. Returns -1 on an error. */ static int shortNameMatches44BSD(const char * name) { const char * p; unsigned long ul; if (strncmp(name, "#1/", 3) != 0) return -1; if (!isDecimal(name[3])) return -1; for (p = name + 4; *p; p++) { if (!isDecimal(*p)) break; } while (*p) { if (*p++ != ' ') return -1; } if (!getNumber(name + 3, 10, -1, &ul)) return -1; if (ul == 0) /* broken archive */ return -1; return ul; } /* * Check the member file name and see if it matches the SYS V syntax * for long file names. If so, return the number of characters of the * actual long file name. Returns -1 on an error. */ static int shortNameMatchesSysV(const char * name) { const char * p; unsigned long ul; /* "^/(\d+) *$" */ if (name[0] != '/') return -1; if (!isDecimal(name[1])) return -1; for (p = name + 2; *p; p++) { if (!isDecimal(*p)) break; } while (*p) { if (*p++ != ' ') return -1; } if (!getNumber(name + 1, 10, -1, &ul)) return -1; return ul; } #define MEMB_NAME_ALLOC(n) \ do \ { \ arch->name = malloc(n); \ if (!arch->name) \ { \ fprintf(stderr, "Out of memory\n"); \ return FALSE; \ } \ } while (0); /* * Examine the archive structure that was read and fill in the * current member data with the extracted information. This handles * various types of archive headers. This can read data from the * archive file to obtain a long file name. Returns TRUE on success. */ static BOOL canonicalize(Archive * arch, const struct ar_hdr * hdr) { char buf[sizeof(hdr->ar_name) + 1]; int n; unsigned long ul; unsigned long bsd_len; bsd_len = 0; free(arch->name); arch->name = 0; strncpy(buf, hdr->ar_name, sizeof(hdr->ar_name)); buf[sizeof(hdr->ar_name)] = 0; /* * 1. If shortname matches "^#1/(\d+) *$", then it's a 4.4BSD * longname. Read a longname of $1 bytes from ARCH->fd, or * return FALSE if impossible. */ if ((n = shortNameMatches44BSD(buf)) != -1) { /* N is the length of the longname */ ssize_t cc; bsd_len = n; MEMB_NAME_ALLOC(n + 1); cc = read(arch->fd, arch->name, n); if (cc == -1) { fprintf(stderr, "Error reading longname: %s\n", strerror(errno)); goto free_and_out; } if (cc != n) { fprintf(stderr, "Unexpected end of file in longname\n"); goto free_and_out; } arch->name[n] = 0; } /* * 2. Otherwise, if shortname matches "^/(\d+) *$", then it's a SysV * longname. Get the longname from the nameTable, or return FALSE * if there is none. */ else if ((n = shortNameMatchesSysV(buf)) != -1) { /* * N is the index of the longname */ const char * longname; const char * p; size_t len; if (n >= strlen(arch->nameTable)) { fprintf(stderr, "Longname index too large\n"); return FALSE; } longname = arch->nameTable + n; p = strchr(longname, '/'); if (!p) { fprintf(stderr, "Bad longname index\n"); return FALSE; } if (p[1] != '\n') { fprintf(stderr, "Malformed longname table\n"); return FALSE; } len = p - longname; MEMB_NAME_ALLOC(len + 1); strncpy(arch->name, longname, len); arch->name[len] = '\0'; } /* * 3. Otherwise, it's just a shortname. If the shortname contains a * slash, then the name terminates before the slash; otherwise, * the name terminates at the first space, or at the end of the * field if there is none. */ else { const char * p; size_t len; p = strchr(buf, '/'); if (!p) p = strchr(buf, ' '); if (p) len = p - buf; else len = sizeof(hdr->ar_name); MEMB_NAME_ALLOC(len + 1); strncpy(arch->name, buf, len); arch->name[len] = 0; } /* * 4. Parse the remaining fields of the header. Return FALSE if any * are missing or ill-formed. */ #define FIELD(AFIELD, MFIELD, base) \ if (!getNumber(hdr->AFIELD, base, sizeof(hdr->AFIELD), &ul)) \ { \ fprintf(stderr, "Malformed archive member header\n"); \ goto free_and_out; \ } \ arch->MFIELD = ul; FIELD(ar_date, date, 10); FIELD(ar_uid, uid, 10); FIELD(ar_gid, gid, 10); FIELD(ar_mode, mode, 8); FIELD(ar_size, size, 10); #undef FIELD /* * 4a. Decide whether a pad byte will be present.u * * The 4.4BSD format is really broken and needs a whole pile of * cruft to deal with it. There are several cases: * * 1. Even namelen, even memberlen: no pad. * 2. Even namelen, odd memberlen: pad. Just like SysV. * 3. Odd namelen, even memberlen: pad. Cruft. * 4. Odd namelen, odd memberlen: no pad. Cruft. * * Essentially, whenever the namelen is odd, the naive determination * of whether a pad is needed is reversed. */ if (!bsd_len) arch->pad = (arch->size % 2) ? 1 : 0; else { arch->size -= bsd_len; arch->pad = (arch->size % 2) ? 1 : 0; if (bsd_len % 2) arch->pad = !arch->pad; } /* * 5. Everything was successful. */ return TRUE; /* * 5a. Error exit -- free memory. */ free_and_out: free(arch->name); arch->name = 0; return FALSE; } /* * Skip the padding character if required to position to the * beginning of the next member header. This is done if the * padding value is nonzero. Returns TRUE on success. */ static BOOL skipPadding(int fd, int pad) { if (pad) { if (lseek(fd, 1, SEEK_CUR) == -1) { fprintf(stderr, "Can't skip pad byte: %s\n", strerror(errno)); return FALSE; } } return TRUE; } /* * Read the first member of the archive file and check whether it * is a special one, and if so, handle it. If the first member is * a normal archive member, then set up to rescan it for the next * readNormalMember call. Returns TRUE on success. */ static BOOL readSpecialMember(Archive * arch) { struct ar_hdr hdr; /* * 1. Read a header H. Fail if impossible. */ if (!readMember(arch, &hdr)) return FALSE; /* * 2. If H is a symbol table, ditch it. * Fail if impossible. */ if ((strncmp(hdr.ar_name, "/ ", 2) == 0) || (strncmp(hdr.ar_name, "__.SYMTAB ", sizeof(hdr.ar_name)) == 0)) { if (!canonicalize(arch, &hdr)) return FALSE; return skipMember(arch); } /* * 3. If H is a SysV longname table, read it into ARCH. */ if (strncmp(hdr.ar_name, "//", 2) == 0) { unsigned long len; ssize_t cc; if (!getNumber(hdr.ar_size, 10, sizeof(hdr.ar_size), &len)) { fprintf(stderr, "Invalid name-table size\n"); return FALSE; } arch->nameTable = malloc(len + 1); if (!arch->nameTable) { fprintf(stderr, "Out of memory\n"); return FALSE; } cc = read(arch->fd, arch->nameTable, len); if (cc == -1) { fprintf(stderr, "Error reading name-table: %s\n", strerror(errno)); return FALSE; } if (cc != (ssize_t) len) { fprintf(stderr, "Unexpected end of file in name-table\n"); return FALSE; } arch->nameTable[len] = 0; return skipPadding(arch->fd, len % 2); } /* * 4. We read a normal header. * Canonicalize it, and mark it as needing rescanning. */ arch->rescan = TRUE; return canonicalize(arch, &hdr); } /* * Read the next normal member of the archive file if possible. * If the member is being rescanned, clear the rescan flag and * return the header that was already read. Returns TRUE on * success. On a failure, the eof flag will be set if end of * file has been reached. */ static BOOL readNormalMember(Archive * arch) { struct ar_hdr hdr; /* * If we are rereading an old header then just clear the * rescan flag and return success. */ if (arch->rescan) { arch->rescan = FALSE; return TRUE; } /* * We need to read a new member header. */ if (!readMember(arch, &hdr)) return FALSE; return canonicalize(arch, &hdr); } /* * Skip the current member of the archive so that we are positioned * to tbe beginning of the next member's header (or end of file). * Returns TRUE on success. */ static BOOL skipMember(const Archive * arch) { if (lseek(arch->fd, arch->size, SEEK_CUR) == -1) { fprintf(stderr, "Can't skip past archive member: %s\n", strerror(errno)); return FALSE; } return skipPadding(arch->fd, arch->pad); } /* * Copy all of the file data from the archive to the specified * open file. Returns TRUE on success. */ static BOOL writeFile(const Archive * arch, int outfd) { char buf[BUF_SIZE]; off_t n; n = arch->size; while (n > 0) { ssize_t cc; cc = read(arch->fd, buf, MIN(n, sizeof(buf))); if (cc == -1) { fprintf(stderr, "Error reading archive member: %s\n", strerror(errno)); return FALSE; } if (cc == 0) { fprintf(stderr, "Unexpected end of file\n"); return FALSE; } if (fullWrite(outfd, buf, cc) < 0) { fprintf(stderr, "Write error: %s\n", strerror(errno)); return FALSE; } n -= cc; } if (!skipPadding(arch->fd, arch->pad)) return FALSE; return TRUE; } /* * Print one line listing the information about the specified archive member. */ static void listMember(const Archive * arch) { printf("%s %6ld/%-6ld %8lu %s %s\n", modeString(arch->mode) + 1, (long) arch->uid, (long) arch->gid, (unsigned long) arch->size, timeString(arch->date), arch->name); } /* END CODE */