Relay-Version: version B 2.10 5/3/83; site utzoo.UUCP Path: utzoo!utgpu!water!watnot!watmath!clyde!rutgers!pyrnj!mirror!rs From: rs@mirror.UUCP Newsgroups: net.sources Subject: "Everyone's" shell (un)archiving tools, Part2/2 Message-ID: <2529@mirror.TMC.COM> Date: Tue, 24-Mar-87 18:20:10 EST Article-I.D.: mirror.2529 Posted: Tue Mar 24 18:20:10 1987 Date-Received: Fri, 27-Mar-87 00:37:16 EST References: <2528@mirror.TMC.COM> Organization: Mirror Systems, Cambridge, MA Lines: 1900 #! /bin/sh # This is a shell archive. Remove anything before this line, # then unpack it by saving it in a file and typing "sh file". # If this archive is complete, you will see the message: # "End of archive 2 (of 2)." # Contents: makekit.c parser.c unshar.c # Wrapped by rs@mirror on Tue Mar 24 18:12:09 1987 PATH=/bin:/usr/bin:/usr/ucb ; export PATH echo shar: Extracting \"makekit.c\" \(9987 characters\) if test -f makekit.c ; then echo shar: Will not over-write existing file \"makekit.c\" else sed "s/^X//" >makekit.c <<'END_OF_makekit.c' X/* X** MAKEKIT X** Split up source files into reasonably-sized shar lists. X** X** Options: X** -e Leave our output file out X** -h # Number of header lines in input file X** -i name Input file name X** -k # Maximum number of archives desired X** -m Same as "-i Manifest -o Manifest -s2" X** -n name Name for resultant archives X** -o name Output file name X** -p Preserve original input order X** -s #[k] Maximum size of each archvie X** -t text Set final instructions after all are unpacked X** -x Don't actually do the shar'ing X*/ X#include "shar.h" XRCS("$Header: makekit.c,v 1.15 87/03/13 12:56:41 rs Exp $") X X X/* X** Our block of information about the files we're doing. X*/ Xtypedef struct { X char *Name; /* Filename */ X char *Text; /* What it is */ X int Where; /* Where it is */ X int Type; /* Directory or file? */ X long Size; /* Size in bytes */ X} BLOCK; X X X/* X** Our block of information about the archives we're making. X*/ Xtypedef struct { X int Count; /* Number of files */ X long Size; /* Bytes used by archive */ X} ARCHIVE; X X X/* X** We re-use parts of one buffer to hold three different strings in our X** argument vector to exec(). X*/ X#define SEG0 0 /* Ending archive number */ X#define SEG1 10 /* Current archive number */ X#define SEG2 20 /* Output name (rest of buff) */ X X/* X** Format strings; these are strict K&R so you shouldn't have to change them. X*/ X#define FORMAT1 " %-25s%2d\t%s\n" X#define FORMAT2 "%s%2.2d" X X X/* X** Global variables. X*/ Xchar *InName; /* File with list to pack */ Xchar *OutName; /* Where our output goes */ Xchar *SharName = "Part"; /* Prefix for name of each shar */ Xchar *Trailer; /* Text for shar to pack in */ Xchar TEMP[] = "/tmp/arkXXXXXX"; /* Temporary manifest file */ Xint ArchCount = 20; /* Max number of archives */ Xint ExcludeIt; /* Leave out the output file? */ Xint Header; /* Lines of prolog in input */ Xint Preserve; /* Preserve order for Manifest? */ Xint Working = TRUE; /* Call shar when done? */ Xlong Size = 55000; /* Largest legal archive size */ X X X/* X** Sorting predicate to put README first, then directories, then large X** files, then smaller files, which is how we want to assign things to X** the archives. X*/ Xstatic int XSizeP(t1, t2) X BLOCK *t1; X BLOCK *t2; X{ X long i; X X if (EQ(t1->Name, "README") || EQ(t1->Name, "readme")) X return(-1); X if (EQ(t2->Name, "README") || EQ(t1->Name, "readme")) X return(1); X if (t1->Type != t2->Type) X return(t1->Type == F_DIR ? 1 : -1); X return((i = t1->Size - t2->Size) == 0L ? 0 : (i < 0L ? -1 : 1)); X} X X X/* X** Sorting predicate to get things in alphabetical order, which is how X** we write the Manifest file. X*/ Xstatic int XNameP(t1, t2) X BLOCK *t1; X BLOCK *t2; X{ X int i; X X return((i = *t1->Name - *t2->Name) ? i : strcmp(t1->Name, t2->Name)); X} X X X/* X** Skip whitespace. X*/ Xstatic char * XSkip(p) X register char *p; X{ X while (*p && WHITE(*p)) X p++; X return(p); X} X X X/* X** Signal handler. Clean up and die. X*/ Xstatic XCatch(s) X int s; X{ X int e; X X e = errno; X (void)unlink(TEMP); X fprintf(stderr, "Got signal %d, %s.\n", s, Ermsg(e)); X exit(1); X} X X Xmain(ac, av) X register int ac; X char *av[]; X{ X register FILE *F; X register FILE *In; X register BLOCK *t; X register ARCHIVE *k; X register char *p; X register int i; X register int lines; X register int Value; X BLOCK *Table; X BLOCK *TabEnd; X ARCHIVE *Ark; X ARCHIVE *ArkEnd; X char buff[BUFSIZ]; X int LastOne; X int Start; X X /* Collect input. */ X Value = FALSE; X while ((i = getopt(ac, av, "eh:i:k:n:mop:s:t:x")) != EOF) X switch (i) { X default: X exit(1); X case 'e': X ExcludeIt = TRUE; X break; X case 'h': X Header = atoi(optarg); X break; X case 'i': X InName = optarg; X break; X case 'k': X ArchCount = atoi(optarg); X break; X case 'm': X InName = OutName = "MANIFEST"; X Header = 2; X break; X case 'n': X SharName = optarg; X break; X case 'o': X OutName = optarg; X break; X case 'p': X Preserve = TRUE; X break; X case 's': X Size = atoi(optarg); X if (IDX(optarg, 'k') || IDX(optarg, 'K')) X Size *= 1024; X break; X case 't': X Trailer = optarg; X break; X case 'x': X Working = FALSE; X break; X } X ac -= optind; X av += optind; X X /* Write the file list to a temp file. */ X F = fopen(mktemp(TEMP), "w"); X SetSigs(TRUE, Catch); X if (av[0]) X /* Got the arguments on the command line. */ X while (*av) X fprintf(F, "%s\n", *av++); X else { X /* Got the name of the file from the command line. */ X if (InName == NULL) X In = stdin; X else if ((In = fopen(InName, "r")) == NULL) { X fprintf(stderr, "Can't read %s as manifest, %s.\n", X InName, Ermsg(errno)); X exit(1); X } X /* Skip any possible prolog, then output rest of file. */ X while (--Header >= 0 && fgets(buff, sizeof buff, In)) X ; X if (feof(In)) { X fprintf(stderr, "Nothing but header lines in list!?\n"); X exit(1); X } X while (fgets(buff, sizeof buff, In)) X fputs(buff, F); X if (In != stdin) X (void)fclose(In); X } X (void)fclose(F); X X /* Count number of files, allow for NULL and our output file. */ X F = fopen(TEMP, "r"); X for (lines = 2; fgets(buff, sizeof buff, F); lines++) X ; X rewind(F); X X /* Read lines and parse lines, see if we found our OutFile. */ X Table = NEW(BLOCK, lines); X for (t = Table, Value = FALSE, lines = 0; fgets(buff, sizeof buff, F); ) { X /* Read line, skip first word, check for blank line. */ X if (p = IDX(buff, '\n')) X *p = '\0'; X else X fprintf(stderr, "Warning, line truncated:\n%s\n", buff); X p = Skip(buff); X if (*p == '\0') X continue; X X /* Copy the line, snip off the first word. */ X for (p = t->Name = COPY(p); *p && !WHITE(*p); p++) X ; X if (*p) X *p++ = '\0'; X X /* Skip ; remainder is the file description. */ X for (p = Skip(p); *p && isdigit(*p); ) X p++; X t->Text = Skip(p); X X /* Get file type. */ X if (!GetStat(t->Name)) { X fprintf(stderr, "Can't stat %s (%s), skipping.\n", X t->Name, Ermsg(errno)); X continue; X } X t->Type = Ftype(t->Name); X X /* Guesstimate it's size when archived. */ X t->Size = strlen(t->Name) * 3 + 200; X if (t->Type == F_FILE) { X i = Fsize(t->Name); X t->Size += i + i / 60; X } X if (t->Size > Size) { X fprintf(stderr, "At %ld bytes, %s is too big for any archive!\n", X t->Size, t->Name); X exit(1); X } X X /* Is our ouput file there? */ X if (!Value && OutName && EQ(OutName, t->Name)) X Value = TRUE; X X /* All done -- advance to next entry. */ X t++; X } X (void)fclose(F); X (void)unlink(TEMP); X SetSigs(S_RESET, (int (*)())NULL); X X /* Add our output file? */ X if (!ExcludeIt && !Value && OutName) { X t->Name = OutName; X t->Text = "This shipping list"; X t->Type = F_FILE; X t->Size = lines * 60; X t++; X } X X /* Sort by size, get archive space. */ X lines = t - Table; X TabEnd = &Table[lines]; X if (!Preserve) X qsort((char *)Table, lines, sizeof Table[0], SizeP); X Ark = NEW(ARCHIVE, ArchCount); X ArkEnd = &Ark[ArchCount]; X X /* Loop through the pieces, and put everyone into an archive. */ X for (t = Table; t < TabEnd; t++) { X for (k = Ark; k < ArkEnd; k++) X if (t->Size + k->Size < Size) { X k->Size += t->Size; X t->Where = k - Ark; X k->Count++; X break; X } X if (k == ArkEnd) { X fprintf(stderr, "'%s' doesn't fit -- need more then %d archives.\n", X t->Name, ArchCount); X exit(1); X } X /* Since our share doesn't build sub-directories... */ X if (t->Type == F_DIR && k != Ark) X fprintf(stderr, "Warning: directory '%s' is in archive %d\n", X t->Name, k - Ark + 1); X } X X /* Open the output file. */ X if (OutName == NULL) X F = stdout; X else { X if (GetStat(OutName)) { X /* Handle /foo/bar/VeryLongFileName.BAK for non-BSD sites. */ X (void)sprintf(buff, "%s.BAK", OutName); X p = (p = RDX(buff, '/')) ? p + 1 : buff; X if (strlen(p) > 14) X /* ... well, sort of handle it. */ X (void)strcpy(&p[10], ".BAK"); X fprintf(stderr, "Renaming %s to %s\n", OutName, buff); X (void)unlink(buff); X (void)link(OutName, buff); X (void)unlink(OutName); X } X if ((F = fopen(OutName, "w")) == NULL) { X fprintf(stderr, "Can't open '%s' for output, %s.\n", X OutName, Ermsg(errno)); X exit(1); X } X } X X /* Sort the shipping list, then write it. */ X if (!Preserve) X qsort((char *)Table, lines, sizeof Table[0], NameP); X fprintf(F, " File Name\t\tArchive #\tDescription\n"); X fprintf(F, "-----------------------------------------------------------\n"); X for (t = Table; t < TabEnd; t++) X fprintf(F, FORMAT1, t->Name, t->Where + 1, t->Text); X X /* Close output. Are we done? */ X if (F != stdout) X (void)fclose(F); X if (!Working) X exit(0); X X /* Find last archive number. */ X for (i = 0, t = Table; t < TabEnd; t++) X if (i < t->Where) X i = t->Where; X LastOne = i + 1; X X /* Find archive with most files in it. */ X for (i = 0, k = Ark; k < ArkEnd; k++) X if (i < k->Count) X i = k->Count; X X /* Build the fixed part of the argument vector. */ X av = NEW(char*, i + 10); X av[0] = "shar"; X i = 1; X if (Trailer) { X av[i++] = "-t"; X av[i++] = Trailer; X } X (void)sprintf(&buff[SEG0], "%d", LastOne); X av[i++] = "-e"; X av[i++] = &buff[SEG0]; X av[i++] = "-n"; X av[i++] = &buff[SEG1]; X av[i++] = "-o"; X av[i++] = &buff[SEG2]; X X /* Call shar to package up each archive. */ X for (Start = i, i = 0; i < LastOne; i++) { X (void)sprintf(&buff[SEG1], "%d", i + 1); X (void)sprintf(&buff[SEG2], FORMAT2, SharName, i + 1); X for (lines = Start, t = Table; t < TabEnd; t++) X if (t->Where == i) X av[lines++] = t->Name; X av[lines] = NULL; X fprintf(stderr, "Packing kit %d...\n", i + 1); X if (lines = Execute(av)) X fprintf(stderr, "Warning: shar returned status %d.\n", lines); X } X X /* That's all she wrote. */ X exit(0); X} END_OF_makekit.c if test 9987 -ne `wc -c parser.c <<'END_OF_parser.c' X/* X** An interpreter that can unpack many /bin/sh shell archives. X** This program should really be split up into a couple of smaller X** files; it started with Argify and SynTable as a cute 10-minute X** hack and it just grew. X** X** Also, note that (void) casts abound, and that every command goes X** to some trouble to return a value. That's because I decided X** not to implement $? "properly." X*/ X#include "shar.h" XRCS("$Header: parser.c,v 1.7 87/03/24 17:56:13 rs Exp $") X X X/* X** DATATYPES X*/ X X/* Command dispatch table. */ Xtypedef struct { X char Name[10]; /* Text of command name */ X int (*Func)(); /* Function that implements it */ X} COMTAB; X X/* A shell variable. We only have a few of these. */ Xtypedef struct { X char *Name; X char *Value; X} VAR; X X X/* X** Manifest constants, handy shorthands. X*/ X X/* Character classes used in the syntax table. */ X#define C_LETR 1 /* A letter within a word */ X#define C_WHIT 2 /* Whitespace to separate words */ X#define C_WORD 3 /* A single-character word */ X#define C_DUBL 4 /* Something like <<, e.g. */ X#define C_QUOT 5 /* Quotes to group a word */ X#define C_META 6 /* Heavy magic character */ X#define C_TERM 7 /* Line terminator */ X X/* Macros used to query character class. */ X#define ISletr(c) (SynTable[(c)] == C_LETR) X#define ISwhit(c) (SynTable[(c)] == C_WHIT) X#define ISword(c) (SynTable[(c)] == C_WORD) X#define ISdubl(c) (SynTable[(c)] == C_DUBL) X#define ISquot(c) (SynTable[(c)] == C_QUOT) X#define ISmeta(c) (SynTable[(c)] == C_META) X#define ISterm(c) (SynTable[(c)] == C_TERM) X X X/* X** Global variables. X*/ X XFILE *Input; /* Current input stream */ Xchar *File; /* Input filename */ Xint Interactive; /* isatty(fileno(stdin))? */ X Xextern COMTAB Dispatch[]; /* Defined below... */ Xstatic VAR VarList[MAX_VARS]; /* Our list of variables */ Xstatic char Text[BUFSIZ]; /* Current text line */ Xstatic int LineNum = 1; /* Current line number */ Xstatic int Running = TRUE; /* Working, or skipping? */ Xstatic short SynTable[256] = { /* Syntax table */ X /* \0 001 002 003 004 005 006 007 */ X C_TERM, C_WHIT, C_WHIT, C_WHIT, C_WHIT, C_WHIT, C_WHIT, C_WHIT, X /* \h \t \n 013 \f \r 016 017 */ X C_WHIT, C_WHIT, C_TERM, C_WHIT, C_TERM, C_TERM, C_WHIT, C_WHIT, X /* 020 021 022 023 024 025 026 027 */ X C_WHIT, C_WHIT, C_WHIT, C_WHIT, C_WHIT, C_WHIT, C_WHIT, C_WHIT, X /* can em sub esc fs gs rs us */ X C_WHIT, C_WHIT, C_WHIT, C_WHIT, C_WHIT, C_WHIT, C_WHIT, C_WHIT, X X /* sp ! " # $ % & ' */ X C_WHIT, C_LETR, C_QUOT, C_TERM, C_LETR, C_LETR, C_DUBL, C_QUOT, X /* ( ) * + , - . / */ X C_WORD, C_WORD, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, X /* 0 1 2 3 4 5 6 7 */ X C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, X /* 8 9 : ; < = > ? */ X C_LETR, C_LETR, C_TERM, C_DUBL, C_DUBL, C_LETR, C_DUBL, C_LETR, X X /* @ A B C D E F G */ X C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, X /* H I J K L M N O */ X C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, X /* P Q R S T U V W */ X C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, X /* X Y Z [ \ ] ^ _ */ X C_LETR, C_LETR, C_LETR, C_LETR, C_META, C_LETR, C_LETR, C_LETR, X X /* ` a b c d e f g */ X C_WORD, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, X /* h i j k l m n o */ X C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, X /* p q r s t u v w */ X C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, X /* x y z { | } ~ del */ X C_LETR, C_LETR, C_LETR, C_LETR, C_DUBL, C_LETR, C_LETR, C_WHIT, X}; X X/** X*** E R R O R R O U T I N E S X**/ X X X/* X** Print message with current line and line number. X*/ Xstatic void XNote(text, arg) X char *text; X char *arg; X{ X fprintf(stderr, "\nIn line %d of %s:\n\t", LineNum, File); X fprintf(stderr, text, arg); X fprintf(stderr, "Current line:\n\t%s\n", Text); X (void)fflush(stderr); X} X X X/* X** Print syntax message and die. X*/ Xvoid XSynErr(text) X char *text; X{ X Note("Fatal syntax error in %s statement.\n", text); X exit(1); X} X X/** X*** I N P U T R O U T I N E S X**/ X X X/* X** Miniscule regular-expression matcher; only groks the . meta-character. X*/ Xstatic int XMatches(p, text) X register char *p; X register char *text; X{ X for (; *p && *text; text++, p++) X if (*p != *text && *p != '.') X return(FALSE); X return(TRUE); X} X X X X/* X** Read input, possibly handling escaped returns. Returns a value so X** we can do things like "while (GetLine(TRUE))", which is a hack. This X** should also be split into two separate routines, and punt the Flag X** argument, but so it goes. X*/ Xint XGetLine(Flag) X register int Flag; X{ X register char *p; X register char *q; X char buf[MAX_LINE_SIZE]; X X if (Interactive) { X fprintf(stderr, "Line %d%s> ", LineNum, Running ? "" : "(SKIP)"); X (void)fflush(stderr); X } X Text[0] = '\0'; X for (q = Text; fgets(buf, sizeof buf, Input); q += strlen(strcpy(q, buf))) { X LineNum++; X p = &buf[strlen(buf) - 1]; X if (*p != '\n') { X Note("Input line too long.\n", (char *)NULL); X exit(1); X } X if (!Flag || p == buf || p[-1] != '\\') { X (void)strcpy(q, buf); X return(1); X } X p[-1] = '\0'; X if (Interactive) { X fprintf(stderr, "PS2> "); X (void)fflush(stderr); X } X } X Note("RAN OUT OF INPUT.\n", (char *)NULL); X exit(1); X /* NOTREACHED */ X} X X X/* X** Copy a sub-string of characters into dynamic space. X*/ Xstatic char * XCopyRange(Start, End) X char *Start; X char *End; X{ X char *p; X int i; X X i = End - Start + 1; X p = strncpy(NEW(char, i + 1), Start, i); X p[i] = '\0'; X return(p); X} X X X/* X** Split a line up into shell-style "words." X*/ Xint XArgify(ArgV) X char **ArgV; X{ X register char **av; X register char *p; X register char *q; X X for (av = ArgV, p = Text; *p; p++) { X /* Skip whitespace, but treat "\ " as a letter. */ X for (; ISwhit(*p); p++) X if (ISmeta(*p)) X p++; X if (ISterm(*p)) X break; X switch (SynTable[*p]) { X default: X Note("Bad case %x in Argify.\n", (char *)SynTable[*p]); X case C_META: X p++; X case C_WHIT: X case C_LETR: X for (q = p; ISletr(*++q) || ISmeta(q[-1]); ) X ; X *av++ = CopyRange(p, --q); X p = q; X break; X case C_DUBL: X if (*p == p[1]) { X *av++ = CopyRange(p, p + 1); X p++; X break; X } X case C_WORD: X *av++ = CopyRange(p, p); X break; X case C_QUOT: X for (q = p; *++q; ) X if (*q == *p && !ISmeta(q[-1])) X break; X *av++ = CopyRange(p + 1, q - 1); X p = q; X break; X } X } X *av = NULL; X if (av > &ArgV[MAX_WORDS - 1]) X SynErr("TOO MANY WORDS IN LINE"); X return(av - ArgV); X} X X/** X*** V A R I A B L E R O U T I N E S X**/ X X X/* X** Return the value of a variable, or an empty string. X*/ Xstatic char * XGetVar(Name) X register char *Name; X{ X register VAR *Vptr; X X for (Vptr = VarList; Vptr < &VarList[MAX_VARS]; Vptr++) X if (EQ(Vptr->Name, Name)) X return(Vptr->Value); X X /* Try the environment. */ X return((Name = getenv(Name)) ? Name : ""); X} X X X/* X** Insert a variable/value pair into the list of variables. X*/ Xvoid XSetVar(Name, Value) X register char *Name; X register char *Value; X{ X register VAR *Vptr; X register VAR *FreeVar; X X /* Skip leading whitespace in variable names, sorry... */ X while (ISwhit(*Name)) X Name++; X X /* Try to find the variable in the table. */ X for (Vptr = VarList, FreeVar = NULL; Vptr < &VarList[MAX_VARS]; Vptr++) X if (Vptr->Name) { X if (EQ(Vptr->Name, Name)) { X free(Vptr->Value); X Vptr->Value = COPY(Value); X return; X } X } X else if (FreeVar == NULL) X FreeVar = Vptr; X X if (FreeVar == NULL) { X fprintf(stderr, "Overflow, can't do '%s=%s'\n", Name, Value); X SynErr("ASSIGNMENT"); X } X FreeVar->Name = COPY(Name); X FreeVar->Value = COPY(Value); X} X X X/* X** Expand variable references inside a word that are of the form: X** foo${var}bar X** foo$$bar X** Returns a pointer to a static area which is overwritten every X** other time it is called, so that we can do EQ(Expand(a), Expand(b)). X*/ Xstatic char * XExpand(p) X register char *p; X{ X static char buff[2][MAX_VAR_VALUE]; X static int Flag; X register char *q; X register char *n; X register char Closer; X char name[MAX_VAR_NAME]; X X /* This is a hack, but it makes things easier in DoTEST, q.v. */ X if (p == NULL) X return(p); X X /* Pick the "other" buffer then loop over the string to be expanded. */ X for (Flag = 1 - Flag, q = buff[Flag]; *p; ) X if (*p == '$') X if (*++p == '$') { X (void)sprintf(name, "%d", Pid()); X q += strlen(strcpy(q, name)); X p++; X } X else if (*p == '?') { X /* Fake it -- all commands always succeed, here. */ X *q++ = '0'; X *q = '\0'; X p++; X } X else { X /* Read this line carefully... */ X if (Closer = *p == '{' ? '}' : '\0') X p++; X for (n = name; *p && *p != Closer; ) X *n++ = *p++; X if (*p) X p++; X *n = '\0'; X q += strlen(strcpy(q, GetVar(name))); X } X else X *q++ = *p++; X *q = '\0'; X return(buff[Flag]); X} X X X/* X** Do a variable assignment of the form: X** var=value X** var="quoted value" X** var="...${var}..." X** etc. X*/ Xstatic void XDoASSIGN(Name) X register char *Name; X{ X register char *Value; X register char *q; X register char Quote; X X /* Split out into name:value strings, and deal with quoted values. */ X Value = IDX(Name, '='); X *Value = '\0'; X if (ISquot(*++Value)) X for (Quote = *Value++, q = Value; *++q && *q != Quote; ) X ; X else X for (q = Value; ISletr(*q); q++) X ; X *q = '\0'; X X SetVar(Name, Expand(Value)); X} X X/** X*** " O U T P U T " C O M M A N D S X**/ X X X/* X** Do a cat command. Understands the following: X** cat >arg1 <>arg1 <>arg1 /dev/null X** Except that arg2 is assumed to be quoted -- i.e., no expansion of meta-chars X** inside the "here" document is done. The IO redirection can be in any order. X*/ X/* ARGSUSED */ Xstatic int XDoCAT(ac, av) X int ac; X register char *av[]; X{ X register FILE *Out; X register char *Ending; X register char *Source; X register int V; X register int l; X X /* Parse the I/O redirecions. */ X for (V = TRUE, Source = NULL, Out = NULL, Ending = NULL; *++av; ) X if (EQ(*av, ">") && av[1]) { X av++; X /* This is a hack, but maybe MS-DOS doesn't have /dev/null? */ X Out = Running ? fopen(Expand(*av), "w") : stderr; X } X else if (EQ(*av, ">>") && av[1]) { X av++; X /* And besides, things are actually faster this way. */ X Out = Running ? fopen(Expand(*av), "a") : stderr; X } X else if (EQ(*av, "<<") && av[1]) { X for (Ending = *++av; *Ending == '\\'; Ending++) X ; X l = strlen(Ending); X } X else if (!EQ(Source = *av, "/dev/null")) X SynErr("CAT (bad input filename)"); X X if (Out == NULL || (Ending == NULL && Source == NULL)) { X Note("Missing parameter in CAT command.\n", (char *)NULL); X V = FALSE; X } X X /* Read the input, spit it out. */ X if (V && Running && Out != stderr) { X if (Source == NULL) X while (GetLine(FALSE) && !EQn(Text, Ending, l)) X (void)fputs(Text, Out); X (void)fclose(Out); X } X else X while (GetLine(FALSE) && !EQn(Text, Ending, l)) X ; X X return(V); X} X X X/* X** Do a SED command. Understands the following: X** sed sX^yyyyXX >arg1 <arg1 <") && av[1]) { X av++; X Out = Running ? fopen(Expand(*av), "w") : stderr; X } X else if (EQ(*av, ">>") && av[1]) { X av++; X Out = Running ? fopen(Expand(*av), "a") : stderr; X } X else if (EQ(*av, "<<") && av[1]) { X for (Ending = *++av; *Ending == '\\'; Ending++) X ; X l = strlen(Ending); X } X else X Pattern = EQ(*av, "-e") && av[1] ? *++av : *av; X X /* All there? */ X if (Out == NULL || Ending == NULL || Pattern == NULL) { X Note("Missing parameter in SED command.\n", (char *)NULL); X V = FALSE; X } X X /* Parse the substitute command and its pattern. */ X if (*Pattern != 's') { X Note("Bad SED command -- not a substitute.\n", (char *)NULL); X V = FALSE; X } X else { X Pattern++; X p = Pattern + strlen(Pattern) - 1; X if (*p != *Pattern || *--p != *Pattern) { X Note("Bad substitute pattern in SED command.\n", (char *)NULL); X V = FALSE; X } X else { X /* Now check the pattern. */ X if (*++Pattern == '^') X Pattern++; X for (*p = '\0', i = strlen(Pattern), p = Pattern; *p; p++) X if (*p == '[' || *p == '*' || *p == '$') { X Note("Bad meta-character in SED pattern.\n", (char *)NULL); X V = FALSE; X } X } X } X X /* Spit out the input. */ X if (V && Running && Out != stderr) { X while (GetLine(FALSE) && !EQn(Text, Ending, l)) X (void)fputs(Matches(Pattern, Text) ? &Text[i] : Text, Out); X (void)fclose(Out); X } X else X while (GetLine(FALSE) && !EQn(Text, Ending, l)) X ; X X return(V); X} X X/** X*** " S I M P L E " C O M M A N D S X**/ X X X/* X** Parse a cp command of the form: X** cp /dev/null arg X** We should check if "arg" is a safe file to clobber, but... X*/ Xstatic int XDoCP(ac, av) X int ac; X char *av[]; X{ X FILE *F; X X if (Running) { X if (ac != 3 || !EQ(av[1], "/dev/null")) X SynErr("CP"); X if (F = fopen(Expand(av[2]), "w")) { X (void)fclose(F); X return(TRUE); X } X Note("Can't create %s.\n", av[2]); X } X return(FALSE); X} X X X/* X** Do a mkdir command of the form: X** mkdir arg X*/ Xstatic int XDoMKDIR(ac, av) X int ac; X char *av[]; X{ X if (Running) { X if (ac != 2) X SynErr("MKDIR"); X if (mkdir(Expand(av[1]), 0777) >= 0) X return(TRUE); X Note("Can't make directory %s.\n", av[1]); X } X return(FALSE); X} X X X/* X** Do a cd command of the form: X** cd arg X** chdir arg X*/ Xstatic int XDoCD(ac, av) X int ac; X char *av[]; X{ X if (Running) { X if (ac != 2) X SynErr("CD"); X if (chdir(Expand(av[1])) >= 0) X return(TRUE); X Note("Can't cd to %s.\n", av[1]); X } X return(FALSE); X} X X X/* X** Do the echo command. Understands the "-n" hack. X*/ X/* ARGSUSED */ Xstatic int XDoECHO(ac, av) X int ac; X char *av[]; X{ X int Flag; X X if (Running) { X if (Flag = av[1] != NULL && EQ(av[1], "-n")) X av++; X while (*++av) X fprintf(stderr, "%s ", Expand(*av)); X if (!Flag) X fprintf(stderr, "\n"); X (void)fflush(stderr); X } X return(TRUE); X} X X X/* X** Generic "handler" for commands we can't do. X*/ Xstatic int XDoIT(ac, av) X int ac; X char *av[]; X{ X if (Running) X fprintf(stderr, "You'll have to do this yourself:\n\t%s ", *av); X return(DoECHO(ac, av)); X} X X X/* X** Do an EXIT command. X*/ Xstatic XDoEXIT(ac, av) X int ac; X char *av[]; X{ X ac = *++av ? atoi(Expand(*av)) : 0; X fprintf(stderr, "Exiting, with status %d\n", ac); X exit(ac); X /* NOTREACHED */ X} X X X/* X** Do an EXPORT command. Often used to make sure the archive is being X** unpacked with the Bourne (or Korn?) shell. We look for: X** export PATH blah blah blah X*/ Xstatic int XDoEXPORT(ac, av) X int ac; X char *av[]; X{ X if (ac < 2 || !EQ(av[1], "PATH")) X SynErr("EXPORT"); X return(TRUE); X} X X/** X*** F L O W - O F - C O N T R O L C O M M A N D S X**/ X X X/* X** Parse a "test" statement. Returns TRUE or FALSE. Understands the X** following tests: X** test {!} -f arg Is arg {not} a plain file? X** test {!} -d arg Is arg {not} a directory? X** test {!} $var -eq $var Is the variable {not} equal to the variable? X** test {!} $var != $var Is the variable {not} equal to the variable? X** test {!} ddd -ne `wc -c {<} arg` X** Is size of arg {not} equal to ddd in bytes? X** test -f arg -a $var -eq val X** Used by my shar, check for file clobbering X** These last two tests are starting to really push the limits of what is X** reasonable to hard-code, but they are common cliches in shell archive X** "programming." We also understand the [ .... ] way of writing test. X** If we can't parse the test, we show the command and ask the luser. X*/ Xstatic int XDoTEST(ac, av) X register int ac; X register char *av[]; X{ X register char **p; X register char *Name; X register FILE *DEVTTY; X register int V; X register int i; X char buff[MAX_LINE_SIZE]; X X /* Quick test. */ X if (!Running) X return(FALSE); X X /* See if we're called as "[ ..... ]" */ X if (EQ(*av, "[")) { X for (i = 1; av[i] && !EQ(av[i], "]"); i++) X ; X free(av[i]); X av[i] = NULL; X ac--; X } X X /* Ignore the "test" argument. */ X av++; X ac--; X X /* Inverted test? */ X if (EQ(*av, "!")) { X V = FALSE; X av++; X ac--; X } X else X V = TRUE; X X /* Testing for file-ness? */ X if (ac == 2 && EQ(av[0], "-f") && (Name = Expand(av[1]))) X return(GetStat(Name) && Ftype(Name) == F_FILE ? V : !V); X X /* Testing for directory-ness? */ X if (ac == 2 && EQ(av[0], "-d") && (Name = Expand(av[1]))) X return(GetStat(Name) && Ftype(Name) == F_DIR ? V : !V); X X /* Testing a variable's value? */ X if (ac == 3 && (EQ(av[1], "-eq") || EQ(av[1], "="))) X return(EQ(Expand(av[0]), Expand(av[2])) ? V : !V); X if (ac == 3 && (EQ(av[1], "-ne") || EQ(av[1], "!="))) X return(!EQ(Expand(av[0]), Expand(av[2])) ? V : !V); X X /* Testing a file's size? */ X if (ac == (av[5] && EQ(av[5], "<") ? 7 : 6) && isdigit(av[0][0]) X && (EQ(av[1], "-ne") || EQ(av[1], "-eq")) X && EQ(av[2], "`") && EQ(av[3], "wc") X && EQ(av[4], "-c") && EQ(av[ac - 1], "`")) { X if (GetStat(av[ac - 2])) { X if (EQ(av[1], "-ne")) X return(Fsize(av[ac - 2]) != atol(av[0]) ? V : !V); X return(Fsize(av[ac - 2]) == atol(av[0]) ? V : !V); X } X Note("Can't get status of %s.\n", av[ac - 2]); X } X X /* Testing for existing, but can clobber? */ X if (ac == 6 && EQ(av[0], "-f") && EQ(av[2], "-a") && EQ(av[4], "-eq") X && GetStat(Name = Expand(av[1])) && Ftype(Name) == F_FILE) X return(EQ(Expand(av[3]), Expand(av[5])) ? V : !V); X X /* I give up -- print it out, and let's ask Mikey, he can do it... */ X fprintf(stderr, "Can't parse this test:\n\t"); X for (i = FALSE, p = av; *p; p++) { X fprintf(stderr, "%s ", *p); X if (p[0][0] == '$') X i = TRUE; X } X if (i) { X fprintf(stderr, "\n(Here it is with shell variables expanded...)\n\t"); X for (p = av; *p; p++) X fprintf(stderr, "%s ", Expand(*p)); X } X fprintf(stderr, "\n"); X X DEVTTY = fopen(THE_TTY, "r"); X do { X fprintf(stderr, "Is value true/false/quit [tfq] (q): "); X (void)fflush(stderr); X clearerr(DEVTTY); X if (fgets(buff, sizeof buff, DEVTTY) == NULL X || buff[0] == 'q' || buff[0] == 'Q' || buff[0] == '\n') X SynErr("TEST"); X if (buff[0] == 't' || buff[0] == 'T') { X (void)fclose(DEVTTY); X return(TRUE); X } X } while (buff[0] != 'f' && buff[0] != 'F'); X (void)fclose(DEVTTY); X return(FALSE); X} X X X/* X** Do an IF statement. X*/ Xstatic int XDoIF(ac, av) X register int ac; X register char *av[]; X{ X register char **p; X register int Flag; X char *vec[MAX_WORDS]; X char **Pushed; X X /* Skip first argument. */ X if (!EQ(*++av, "[") && !EQ(*av, "test")) X SynErr("IF"); X ac--; X X /* Look for " ; then " on this line, or "then" on next line. */ X for (Pushed = NULL, p = av; *p; p++) X if (Flag = EQ(*p, ";")) { X if (p[1] == NULL || !EQ(p[1], "then")) X SynErr("IF"); X *p = NULL; X ac -= 2; X break; X } X if (!Flag) { X (void)GetLine(TRUE); X if (Argify(vec) > 1) X Pushed = &vec[1]; X if (!EQ(vec[0], "then")) X SynErr("IF (missing THEN)"); X } X X if (DoTEST(ac, av)) { X if (Pushed) X (void)Exec(Pushed); X while (GetLine(TRUE)) { X if ((ac = Argify(vec)) == 1 && EQ(vec[0], "fi")) X break; X if (EQ(vec[0], "else")) { X DoUntil("fi", FALSE); X break; X } X (void)Exec(vec); X } X } X else X while (GetLine(TRUE)) { X if ((ac = Argify(vec)) == 1 && EQ(vec[0], "fi")) X break; X if (EQ(vec[0], "else")) { X if (ac > 1) X (void)Exec(&vec[1]); X DoUntil("fi", Running); X break; X } X } X return(TRUE); X} X X X/* X** Do a FOR statement. X*/ Xstatic int XDoFOR(ac, av) X register int ac; X register char *av[]; X{ X register char *Var; X register char **Values; X register int Found; X long Here; X char *vec[MAX_WORDS]; X X /* Check usage, get variable name and eat noise words. */ X if (ac < 4 || !EQ(av[2], "in")) X SynErr("FOR"); X Var = av[1]; X ac -= 3; X av += 3; X X /* Look for "; do" on this line, or just "do" on next line. */ X for (Values = av; *++av; ) X if (Found = EQ(*av, ";")) { X if (av[1] == NULL || !EQ(av[1], "do")) X SynErr("FOR"); X *av = NULL; X break; X } X if (!Found) { X (void)GetLine(TRUE); X if (Argify(vec) != 1 || !EQ(vec[0], "do")) X SynErr("FOR (missing DO)"); X } X X for (Here = ftell(Input); *Values; ) { X SetVar(Var, *Values); X DoUntil("done", Running); X ; X /* If we're not Running, only go through the loop once. */ X if (!Running) X break; X if (*++Values && (fseek(Input, Here, 0) < 0 || ftell(Input) != Here)) X SynErr("FOR (can't seek back)"); X } X X return(TRUE); X} X X X/* X** Do a CASE statement of the form: X** case $var in X** text1) X** ... X** ;; X** esac X** Where text1 is a simple word or an asterisk. X*/ Xstatic int XDoCASE(ac, av) X register int ac; X register char *av[]; X{ X register int FoundIt; X char *vec[MAX_WORDS]; X char Value[MAX_VAR_VALUE]; X X if (ac != 3 || !EQ(av[2], "in")) X SynErr("CASE"); X (void)strcpy(Value, Expand(av[1])); X X for (FoundIt = FALSE; GetLine(TRUE); ) { X ac = Argify(vec); X if (EQ(vec[0], "esac")) X break; X /* This is for vi: (-; sigh. */ X if (ac != 2 || !EQ(vec[1], ")")) X SynErr("CASE"); X if (!FoundIt && (EQ(vec[0], Value) || EQ(vec[0], "*"))) { X FoundIt = TRUE; X if (Running && ac > 2) X (void)Exec(&vec[2]); X DoUntil(";;", Running); X } X else X DoUntil(";;", FALSE); X } X return(TRUE); X} X X X X/* X** Dispatch table of known commands. X*/ Xstatic COMTAB Dispatch[] = { X { ":", DoIT }, X { "cat", DoCAT }, X { "case", DoCASE }, X { "cd", DoCD }, X { "chdir", DoCD }, X { "chmod", DoIT }, X { "cp", DoCP }, X { "echo", DoECHO }, X { "exit", DoEXIT }, X { "export", DoEXPORT }, X { "for", DoFOR }, X { "if", DoIF }, X { "mkdir", DoMKDIR }, X { "rm", DoIT }, X { "sed", DoSED }, X { "test", DoTEST }, X { "[", DoTEST }, X { "", NULL } X}; X X X/* X** Dispatch on a parsed line. X*/ Xint XExec(av) X register char *av[]; X{ X register int i; X register COMTAB *p; X X /* We have to re-calculate this because our callers can't always X pass the count down to us easily. */ X for (i = 0; av[i]; i++) X ; X if (i) { X /* Is this a command we know? */ X for (p = Dispatch; p->Func; p++) X if (EQ(av[0], p->Name)) { X i = (*p->Func)(i, av); X break; X } X X /* If not a command, try it as a variable assignment. */ X if (p->Func == NULL) X /* Yes, we look for "=" in the first word, but pass down X the whole line. */ X if (IDX(av[0], '=')) X DoASSIGN(Text); X else X Note("Command %s unknown.\n", av[0]); X X /* Free the line. */ X for (i = 0; av[i]; i++) X free(av[i]); X } X return(TRUE); X} X X X/* X** Do until we reach a specific terminator. X*/ Xstatic XDoUntil(Terminator, NewVal) X char *Terminator; X int NewVal; X{ X char *av[MAX_WORDS]; X int OldVal; X X for (OldVal = Running, Running = NewVal; GetLine(TRUE); ) X if (Argify(av)) { X if (EQ(av[0], Terminator)) X break; X (void)Exec(av); X } X X Running = OldVal; X} END_OF_parser.c if test 23917 -ne `wc -c unshar.c <<'END_OF_unshar.c' X/* X** UNSHAR X** Unpack shell archives that might have gone through mail, notes, news, etc. X** X** Options: X** -c dir Change to directory 'dir' before starting X** -d dir Change to directory 'dir' before starting X** -f Don't try to intuit file type X** -s Save pre-shar headers into a file X** -n Don't save pre-shar headers into a file X*/ X X#include "shar.h" XRCS("$Header: unshar.c,v 1.16 87/03/18 14:03:19 rs Exp $") X X X/* X** Print error message and die. X*/ Xstatic void XQuit(text) X char *text; X{ X int e; X X e = errno; X fprintf(stderr, "unshar: %s", text); X if (e) X fprintf(stderr, ", %s", Ermsg(e)); X fprintf(stderr, ".\n"); X exit(1); X} X X X/* X** Does this look like a mail header line? X*/ Xstatic int XIsHeader(p) X register char *p; X{ X register int i; X X if (*p == '\0' || *p == '\n') X return(FALSE); X if (WHITE(*p)) X return(TRUE); X for (i = 0; *p == '-' || *p == '_' || *p == '.' || isalnum(*p); i++) X p++; X return(i && *p == ':'); X} X X X X/* X** Is this a /bin/sh comment line? We check that because some shars X** output comments before the CUT line. X*/ Xstatic int XIsSHcomment(p) X register char *p; X{ X while (isalpha(*p) || WHITE(*p) || *p == '\n' || *p == ',' || *p == '.') X p++; X return(*p == '\0'); X} X X X/* X** Return TRUE if p has wd1 and wd2 as words (i.e., no preceeding or X** following letters). X*/ Xstatic int XHas(p, wd1, wd2) X register char *p; X register char *wd1; X register char *wd2; X{ X register char *wd; X register int first; X X wd = wd1; X first = TRUE; Xagain: X while (*p) { X if (!isalpha(*p)) { X p++; X continue; X } X while (*p++ == *wd++) { X if (*wd == '\0') { X if (!isalpha(*p)) { X if (!first) X return(TRUE); X first = FALSE; X wd = wd2; X goto again; X } X break; X } X } X while (isalpha(*p)) X p++; X wd = first ? wd1 : wd2; X } X return(FALSE); X} X X X/* X** Here's where the work gets done. Skip headers and try to intuit X** if the file is, e.g., C code, etc. X*/ Xstatic int XFound(Name, buff, Forced, Stream, Header) X register char *Name; X register char *buff; X register int Forced; X register FILE *Stream; X register FILE *Header; X{ X register char *p; X register int InHeader; X char lower[BUFSIZ]; X X if (Header) X InHeader = TRUE; X X while (TRUE) { X /* Read next line, fail if no more */ X if (fgets(buff, BUFSIZ, Stream) == NULL) { X fprintf(stderr, "unshar: No shell commands in %s.\n", Name); X return(FALSE); X } X X /* See if it looks like another language. */ X if (!Forced) { X if (PREFIX(buff, "#include") || PREFIX(buff, "# include") X || PREFIX(buff, "#define") || PREFIX(buff, "# define") X || PREFIX(buff, "#ifdef") || PREFIX(buff, "# ifdef") X || PREFIX(buff, "#ifndef") || PREFIX(buff, "# ifndef") X || (PREFIX(buff, "/*") X && !PREFIX(buff, NOTES1) && !PREFIX(buff, NOTES2))) X p = "C code"; X else if (PREFIX(buff, "(*")) /* For vi :-) */ X p = "PASCAL code"; X else if (buff[0] == '.' && isalpha(buff[1]) && isalpha(buff[2]) X && !isalpha(buff[3])) X p = "TROFF source"; X else X p = NULL; X if (p) { X fprintf(stderr, "unshar: %s is %s, not a shell archive.\n", X Name, p); X return(FALSE); X } X } X X /* Does this line start with a shell command or comment? */ X if ((buff[0] == '#' && !IsSHcomment(buff + 1)) X || buff[0] == ':' || PREFIX(buff, "echo ") X || PREFIX(buff, "sed ") || PREFIX(buff, "cat ")) { X return(TRUE); X } X X /* Does this line say "Cut here"? */ X for (p = strcpy(lower, buff); *p; p++) X if (isascii(*p) && islower(*p)) X *p = toupper(*p); X if (PREFIX(buff, "-----") || Has(lower, "cut", "here") X || Has(lower, "cut", "cut") || Has(lower, "tear", "here")) { X /* Get next non-blank line. */ X do { X if (fgets(buff, BUFSIZ, Stream) == NULL) { X fprintf(stderr, "unshar: cut line is last line of %s\n", X Name); X return(FALSE); X } X } while (*buff == '\n'); X X /* If it starts with a comment or lower-case letter we win. */ X if (*buff == '#' || *buff == ':' || islower(*buff)) X return(TRUE); X X /* The cut message lied. */ X fprintf(stderr, "unshar: %s is not a shell archive,\n", Name); X fprintf(stderr, " the 'cut' line was followed by: %s", buff); X return(FALSE); X } X X if (Header) { X (void)fputs(buff, Header); X if (InHeader && !IsHeader(buff)) X InHeader = FALSE; X } X } X} X X X/* X** Create file for the header, find true start of the archive, X** and send it off to the shell. X*/ Xstatic void XUnshar(Name, Stream, Saveit, Forced) X char *Name; X register FILE *Stream; X int Saveit; X int Forced; X{ X register FILE *Header; X#ifndef USE_MY_SHELL X register FILE *Pipe; X#endif /* USE_MY_SHELL */ X char *p; X char buff[BUFSIZ]; X X if (Saveit) { X /* Create a name for the saved header. */ X if (Name) { X p = RDX(Name, '/'); X (void)strncpy(buff, p ? p + 1 : Name, 14); X buff[10] = 0; X (void)strcat(buff, ".hdr"); X } X else X (void)strcpy(buff, "UNSHAR.HDR"); X X /* Tell user, and open the file. */ X fprintf(stderr, "unshar: Sending header to %s.\n", buff); X if ((Header = fopen(buff, "a")) == NULL) X Quit("Can't open file for header"); X } X else X Header = NULL; X X /* If name is NULL, we're being piped into... */ X p = Name ? Name : "the standard input"; X printf("unshar: Doing %s:\n", p); X X if (Found(p, buff, Forced, Stream, Header)) { X#ifdef USE_MY_SHELL X BinSh(Name, Stream, buff); X#else X if ((Pipe = popen("/bin/sh", "w")) == NULL) X Quit("Can't open pipe to /bin/sh process"); X X (void)fputs(buff, Pipe); X while (fgets(buff, sizeof buff, Stream)) X (void)fputs(buff, Pipe); X X (void)pclose(Pipe); X#endif /* USE_MY_SHELL */ X } X X /* Close the headers. */ X if (Saveit) X (void)fclose(Header); X} X X Xmain(ac, av) X register int ac; X register char *av[]; X{ X register FILE *Stream; X register int i; X char *p; X char cwd[BUFSIZ]; X char dir[BUFSIZ]; X char buff[BUFSIZ]; X int Saveit; X int Forced; X X /* Parse JCL. */ X p = getenv("UNSHARDIR"); X Saveit = DEF_SAVEIT; X for (Forced = 0; (i = getopt(ac, av, "c:d:fns")) != EOF; ) X switch (i) { X default: X Quit("Usage: unshar [-fs] [-c directory] [input files]"); X case 'c': X case 'd': X p = optarg; X break; X case 'f': X Forced++; X break; X case 'n': X Saveit = 0; X case 's': X Saveit++; X break; X } X av += optind; X X /* Going somewhere? */ X if (p) { X if (*p == '?') { X /* Ask for name; go to THE_TTY if we're being piped into. */ X Stream = isatty(fileno(stdin)) ? stdin : fopen(THE_TTY, "r"); X if (Stream == NULL) X Quit("Can't open tty to ask for directory"); X printf("unshar: what directory? "); X (void)fflush(stdout); X if (fgets(buff, sizeof buff, Stream) == NULL X || buff[0] == '\n' || (p = IDX(buff, '\n')) == NULL) X Quit("Okay, cancelled"); X *p = '\0'; X p = buff; X if (Stream != stdin) X (void)fclose(Stream); X } X X /* If name is ~/blah, he means $HOME/blah. */ X if (*p == '~') { X if (getenv("HOME") == NULL) X Quit("You have no $HOME?"); X (void)sprintf(dir, "%s/%s", getenv("HOME"), p + 1); X p = dir; X } X X /* If we're gonna move, first remember where we were. */ X if (Cwd(cwd, sizeof cwd) == NULL) { X fprintf(stderr, "unshar warning: Can't get current directory.\n"); X cwd[0] = '\0'; X } X X /* Got directory; try to go there. */ X while (chdir(p) < 0) X if (mkdir(p, 0777) < 0) X Quit("Cannot chdir nor mkdir desired directory"); X } X else X cwd[0] = '\0'; X X /* No buffering. */ X (void)setbuf(stdout, (char *)NULL); X (void)setbuf(stderr, (char *)NULL); X X /* Process args. */ X if (*av) X for (; *av; av++) { X if (cwd[0] && av[0][0] != '/') { X (void)sprintf(buff, "%s/%s", cwd, *av); X *av = buff; X } X if ((Stream = fopen(*av, "r")) == NULL) X fprintf(stderr, "unshar: File '%s' not found.\n", *av); X else { X Unshar(*av, Stream, Saveit, Forced); X (void)fclose(Stream); X } X } X else X Unshar((char *)NULL, stdin, Saveit, Forced); X X /* That's all she wrote. */ X exit(0); X} END_OF_unshar.c if test 8109 -ne `wc -c