1#include <errno.h>2#include <signal.h>3#include <stdarg.h>4#include <stdio.h>5#include <stdlib.h>6#include <string.h>7#include <time.h>89#include <scc/ar.h>10#include <scc/scc.h>11#include <scc/arg.h>1213enum {14 BEFORE,15 INDOT,16 AFTER,17};1819struct tmp {20 char *name;21 FILE *fp;22} tmps[3];2324char *argv0;2526static int bflag, vflag, cflag, lflag, uflag, aflag;27static char *arfile, *posname;28static char invalidchars[] = " ";2930struct member {31 FILE *src;32 struct ar_hdr hdr;33 int cur;34 char *fname;35 long size;36 long mode;37 long long date;38};3940/*41 * Best effort to try avoid calling remove from a signal42 * handler is to detect that we are in an UNIX43 * system and redirect with the preprocessor remove44 * to unlink that is defined as signal safe.45 */46#if defined(__unix) || defined(__unix__)47#include <unistd.h>48#undef remove49#define remove unlink50#endif5152static void53cleanup(void)54{55 int i;5657 for (i = 0; i < 3; i++) {58 if (tmps[i].name)59 remove(tmps[i].name);60 }61}6263#undef remove6465static char *66errstr(void)67{68 return strerror(errno);69}7071static void72error(char *fmt, ...)73{74 va_list va;7576 va_start(va, fmt);77 fprintf(stderr, "ar: %s: ", arfile);78 vfprintf(stderr, fmt, va);79 putc('\n', stderr);80 va_end(va);8182 exit(EXIT_FAILURE);83}8485/*86 * I do know that you cannot call remove from a signal handler87 * but we only can use stdio function to deal with files88 * because we are C99 compliant, and it is very likely that89 * remove is going to work in this case90 */91static void92sigfun(int signum)93{94 cleanup();95 _Exit(1);96}9798static FILE *99openar(void)100{101 FILE *fp;102 char magic[SARMAG+1];103104 if ((fp = fopen(arfile,"r+b")) == NULL) {105 if (!cflag)106 fprintf(stderr, "ar: creating %s\n", arfile);107 if ((fp = fopen(arfile, "w+b")) == NULL)108 error("opening archive: %s", errstr());109 fputs(ARMAG, fp);110 if (fflush(fp) == EOF)111 error("writing magic number: %s", errstr());112 } else {113 if (fgets(magic, sizeof(magic), fp) == NULL)114 error("error reading magic number: %s", errstr());115 if (strcmp(magic, ARMAG))116 error("invalid magic number '%s'", magic);117 }118 return fp;119}120121static void122archive(char *pname, FILE *to, char letter)123{124 int c;125 FILE *from;126 char *fname;127 struct fprop prop;128129 fname = canonical(pname);130131 if (vflag)132 printf("%c - %s\n", letter, fname);133 if ((from = fopen(pname, "rb")) == NULL)134 error("opening member '%s': %s", pname, errstr());135 if (getstat(pname, &prop) < 0)136 error("error getting '%s' attributes", pname);137138 fprintf(to,139 "%-16.16s%-12lld%-6u%-6u%-8lo%-10ld`\n",140 fname,141 fromepoch(prop.time),142 prop.uid,143 prop.gid,144 prop.mode,145 prop.size);146147 while ((c = getc(from)) != EOF)148 putc(c, to);149 if (prop.size & 1)150 putc('\n', to);151 if (ferror(from))152 error("reading input '%s': %s", pname, errstr());153 fclose(from);154}155156static void157append(FILE *fp, char *argv[])158{159 char *fname;160161 if (fseek(fp, 0, SEEK_END) == EOF)162 error("seeking archive: %s", errstr());163164 for ( ; fname = *argv; ++argv) {165 *argv = NULL;166 archive(fname, fp, 'a');167 }168169 if (fclose(fp) == EOF)170 error("error writing archive: %s", errstr());171}172173static void174copy(struct member *m, struct tmp *tmp)175{176 int c;177 size_t siz = m->size;178 struct ar_hdr *hdr = &m->hdr;179180 fwrite(hdr, sizeof(*hdr), 1, tmp->fp);181 if ((siz & 1) == 1)182 siz++;183 while (siz--) {184 if ((c = getc(m->src)) == EOF)185 break;186 fputc(c, tmp->fp);187 }188}189190static void191letters(unsigned long val, char *s)192{193 *s++ = (val & 04) ? 'r' : '-';194 *s++ = (val & 02) ? 'w' : '-';195 *s++ = (val & 01) ? 'x' : '-';196}197198static char *199perms(struct member *m)200{201 static char buf[10];202203 letters(m->mode >> 6, buf);204 letters(m->mode >> 3, buf+3);205 letters(m->mode, buf +6);206 buf[9] = '\0';207208 return buf;209}210211static char *212inlist(char *fname, int argc, char *argv[])213{214 char *p;215216 for ( ; argc-- > 0; ++argv) {217 if (*argv && !strcmp(canonical(*argv), fname)) {218 p = *argv;219 *argv = NULL;220 return p;221 }222 }223 return NULL;224}225226static int227older(struct member *m, char *pname)228{229 struct fprop prop;230231 if (getstat(pname, &prop) < 0)232 error("error getting '%s' attributes", pname);233 return prop.time > m->date;234}235236static void237move(struct member *m, int argc, char *argv[])238{239 int where;240241 if (inlist(m->fname, argc, argv)) {242 if (vflag)243 printf("m - %s\n", m->fname);244 where = INDOT;245 } else if (posname && !strcmp(posname, m->fname)) {246 where = (bflag) ? AFTER : BEFORE;247 m->cur = AFTER;248 } else {249 where = m->cur;250 }251 copy(m, &tmps[where]);252}253254static void255insert(int argc, char *argv[])256{257 for (; argc-- > 0; ++argv) {258 if (*argv) {259 archive(*argv, tmps[INDOT].fp, 'a');260 *argv = NULL;261 }262 }263}264265static void266update(struct member *m, int argc, char *argv[])267{268 int where;269 FILE *fp = tmps[BEFORE].fp;270 char *pname;271272 if (pname = inlist(m->fname, argc, argv)) {273 if (!uflag || older(m, pname)) {274 archive(pname, tmps[m->cur].fp, 'r');275 return;276 }277 }278279 if (posname && !strcmp(posname, m->fname)) {280 where = (bflag) ? AFTER : BEFORE;281 m->cur = AFTER;282 } else {283 where = m->cur;284 }285 copy(m, &tmps[where]);286}287288static void289extract(struct member *m, int argc, char *argv[])290{291 int c;292 long siz;293 FILE *fp;294 struct fprop prop;295 struct ar_hdr *hdr = &m->hdr;296297 if (argc > 0 && !inlist(m->fname, argc, argv))298 return;299 if (vflag)300 printf("x - %s\n", m->fname);301 siz = m->size;302303 if ((fp = fopen(m->fname, "wb")) == NULL)304 goto error_file;305 while (siz-- > 0 && (c = getc(m->src)) != EOF)306 putc(c, fp);307 fflush(fp);308 if (fclose(fp) == EOF)309 goto error_file;310311 prop.uid = atol(hdr->ar_uid);312 prop.gid = atol(hdr->ar_gid);313 prop.mode = m->mode;314 prop.time = totime(m->date);315 if (setstat(m->fname, &prop) < 0)316 error("%s: setting file attributes", m->fname);317 return;318319error_file:320 error("error extracting file: %s", errstr());321}322323static void324print(struct member *m, int argc, char *argv[])325{326 long siz;327 int c;328329 if (argc > 0 && !inlist(m->fname, argc, argv))330 return;331 if (vflag)332 printf("\n<%s>\n\n", m->fname);333 siz = m->size;334 while (siz-- > 0 && (c = getc(m->src)) != EOF)335 putchar(c);336}337338static void339list(struct member *m, int argc, char *argv[])340{341 time_t t;342 struct ar_hdr *hdr = &m->hdr;343 char mtime[30];344345 if (argc > 0 && !inlist(m->fname, argc, argv))346 return;347 if (!vflag) {348 printf("%s\n", m->fname);349 } else {350 t = totime(m->date);351 strftime(mtime, sizeof(mtime), "%c", localtime(&t));352 printf("%s %ld/%ld\t%s %s\n",353 perms(m),354 atol(hdr->ar_uid),355 atol(hdr->ar_gid),356 mtime,357 m->fname);358 }359}360361static void362del(struct member *m, int argc, char *argv[])363{364 if (inlist(m->fname, argc, argv)) {365 if (vflag)366 printf("d - %s\n", m->fname);367 return;368 }369 copy(m, &tmps[BEFORE]);370}371372static char *373getfname(struct ar_hdr *hdr)374{375 static char fname[SARNAM+1];376 char *p;377378 memcpy(fname, hdr->ar_name, SARNAM);379380 if (p = strchr(fname, ' '))381 *p = '\0';382 else383 fname[SARNAM] = '\0';384385 return fname;386}387388static long long389getnum(char *s, int size, int base)390{391 int c;392 long long val;393 char *p;394 static char digits[] = "0123456789";395396 for (val = 0; size > 0; val += c) {397 --size;398 if ((c = *s++) == ' ')399 break;400 if ((p = strchr(digits, c)) == NULL)401 return -1;402 if ((c = p - digits) >= base)403 return -1;404 val *= base;405 }406407 while (size > 0 && *s++ == ' ')408 --size;409 return (size == 0) ? val : -1;410}411412static int413valid(struct member *m)414{415 struct ar_hdr *hdr = &m->hdr;416417 m->fname = getfname(&m->hdr);418 m->size = getnum(hdr->ar_size, sizeof(hdr->ar_size), 10);419 m->mode = getnum(hdr->ar_mode, sizeof(hdr->ar_mode), 8);420 m->date = getnum(hdr->ar_date, sizeof(hdr->ar_date), 10);421422 if (strncmp(hdr->ar_fmag, ARFMAG, sizeof(hdr->ar_fmag)) ||423 m->size < 0 || m->mode < 0 || m->date < 0) {424 return 0;425 }426 return 1;427}428429static void430run(FILE *fp, int argc, char *argv[],431 void (*fun)(struct member *, int argc, char *files[]))432{433 struct member m;434435 m.src = fp;436 m.cur = BEFORE;437438 while (fread(&m.hdr, sizeof(m.hdr), 1, fp) == 1) {439 fpos_t pos;440441 if (!valid(&m))442 error("corrupted member '%s'", m.fname);443 fgetpos(fp, &pos);444 (*fun)(&m, argc, argv);445 fsetpos(fp, &pos);446 fseek(fp, m.size+1 & ~1, SEEK_CUR);447 }448 if (ferror(fp))449 error("reading members: %s", errstr());450 fclose(fp);451}452453static void454merge(void)455{456 FILE *fp, *fi;457 int c, i;458459 if ((fp = fopen(arfile, "wb")) == NULL)460 error("reopening archive: %s", errstr());461462 fputs(ARMAG, fp);463464 for (i = 0; i < 3; i++) {465 if ((fi = tmps[i].fp) == NULL)466 continue;467 fseek(fi, 0, SEEK_SET);468 while ((c = getc(fi)) != EOF)469 putc(c, fp);470 if (ferror(fi))471 error("error in temporary: %s", errstr());472 }473474 if (fclose(fp) == EOF)475 error("writing archive file: %s", errstr());476}477478static void479closetmp(int which)480{481 struct tmp *tmp = &tmps[which];482483 if (!tmp->fp)484 return;485 if (fclose(tmp->fp) == EOF)486 error("closing temporaries: %s", errstr());487}488489static void490opentmp(char *fname, int which)491{492 struct tmp *tmp = &tmps[which];493494 if (lflag) {495 tmp->name = fname;496 tmp->fp = fopen(fname, "w+b");497 } else {498 tmp->fp = tmpfile();499 }500501 if (tmp->fp == NULL)502 error("creating temporary: %s", errstr());503}504505static void506ar(int key, char *argv[], int argc)507{508 FILE *fp;509510 fp = openar();511 if (argc == 0 &&512 (key == 'r' || key == 'd' || key == 'm' || key == 'q')) {513 if (fclose(fp) == EOF)514 error("early close of archive file: %s", errstr());515 return;516 }517518 if (key == 'r' || key == 'm' || key == 'd')519 opentmp("ar.tmp1", BEFORE);520 if (key == 'r' || key == 'm') {521 opentmp("ar.tmp2", INDOT);522 opentmp("ar.tmp3", AFTER);523 }524525 switch (key) {526 case 'r':527 run(fp, argc, argv, update);528 insert(argc, argv);529 merge();530 break;531 case 'm':532 run(fp, argc, argv, move);533 merge();534 break;535 case 'd':536 run(fp, argc, argv, del);537 merge();538 break;539 case 't':540 run(fp, argc, argv, list);541 break;542 case 'p':543 run(fp, argc, argv, print);544 break;545 case 'x':546 run(fp, argc, argv, extract);547 break;548 case 'q':549 append(fp, argv);550 break;551 }552553 closetmp(BEFORE);554 closetmp(INDOT);555 closetmp(AFTER);556557 for ( ; argc-- > 0; ++argv) {558 if (*argv)559 error("No member named '%s'", *argv);560 }561}562563static void564checkfnames(int argc, char *argv[])565{566 size_t l;567 char *p;568569 for ( ; argc-- > 0; ++argv) {570 p = canonical(*argv);571 l = strcspn(p, invalidchars);572 if (l > 16)573 error("file: '%s': name too long", *argv);574 if (p[l] != '\0')575 error("file: '%s': name invalid", *argv);576 }577}578579static void580usage(void)581{582 fputs("scc-ar [-cluv] [-d|-m|-p|-q|-r|-t|-x] [-a|-b|-i posname] archive [file...]\n",583 stderr);584 exit(1);585}586587int588main(int argc, char *argv[])589{590 int key, nkey = 0, pos = 0;591592 atexit(cleanup);593 ARGBEGIN {594 case 'd':595 nkey++;596 key = 'd';597 break;598 case 'r':599 nkey++;600 key = 'r';601 break;602 case 'q':603 nkey++;604 key = 'q';605 break;606 case 't':607 nkey++;608 key = 't';609 break;610 case 'p':611 nkey++;612 key = 'p';613 break;614 case 'm':615 nkey++;616 key = 'm';617 break;618 case 'x':619 nkey++;620 key = 'x';621 break;622 case 'a':623 aflag = 1;624 pos++;625 posname = EARGF(usage());626 break;627 case 'i':628 case 'b':629 bflag = 1;630 pos++;631 posname = EARGF(usage());632 break;633 case 'v':634 vflag = 1;635 break;636 case 'c':637 cflag = 1;638 break;639 case 'l':640 lflag = 1;641 break;642 case 'u':643 uflag = 1;644 break;645 default:646 usage();647 } ARGEND648649 if (nkey == 0 || nkey > 1 || pos > 1 || argc == 0 ||650 (aflag || bflag) && !(key == 'm' || key == 'r') ||651 cflag && !(key == 'q' || key == 'r') ||652 uflag && key != 'r')653 usage();654655 signal(SIGINT, sigfun);656#ifdef SIGQUIT657 signal(SIGQUIT, sigfun);658#endif659 signal(SIGTERM, sigfun);660661 arfile = *argv;662 checkfnames(--argc, ++argv);663 ar(key, argv, argc);664665 fflush(stdout);666 if (ferror(stdout))667 error("error writing to stdout: %s", errstr());668669 return 0;670}