1: /*
2: Shar puts readable text files together in a package
3: from which they are extracted with /bin/sh and friends.
4: */
5:
6: #include <stdio.h>
7: #include <sys/types.h>
8: #include <sys/stat.h>
9: #include <ctype.h>
10:
11: #ifndef lint
12: static char sccsid[] = "@(#)shar.c 2.0 (Usenet) 3/11/86";
13: #endif
14:
15: typedef int Boole;
16: #define TRUE ((Boole) 1)
17: #define FALSE ((Boole) 0)
18: typedef int Status;
19: #define SUCCESS 0
20: #define FAILURE 1
21:
22: /* GLOBALS */
23: int Lastchar; /* the last character printed */
24: int Ctrlcount; /* how many bad control characters are in file */
25:
26: /* COMMANDS */
27: #define "#! /bin/sh" /* magic exec string at shar file start */
28: #define PATH "/bin:/usr/bin:$PATH" /* search path for programs */
29: #define CAT "cat"; /* /bin/cat */
30: #define SED "sed 's/^%s//'" /* /bin/sed removes Prefix from lines */
31: #define MKDIR "mkdir" /* make a new dirctory */
32: #define CHMOD "chmod" /* change file mode */
33: #define CHDIR "cd" /* change current directory */
34: #define TEST "test" /* /bin/test files */
35: #define WC_C "wc -c <" /* counts chars in file */
36: #define ECHO "echo shar:" /* echo a message to extractor */
37: #define DECODE "uudecode" /* used to decode uuencoded files */
38:
39: /*FUNCTION main: traverse files to make archive to standard output */
40: main (argc, argv) char **argv;
41: {
42: int shar ();
43: int optind;
44:
45: if ((optind = initial (argc, argv)) < 0)
46: exit (FAILURE);
47:
48: if (header (argc, argv, optind))
49: exit (FAILURE);
50:
51: while (optind < argc)
52: traverse (argv[optind++], shar);
53:
54: footer ();
55:
56: exit (SUCCESS);
57: }
58:
59: /* OPTIONS */
60: Boole Verbose = FALSE; /* provide append/extract feedback */
61: Boole Basename = FALSE; /* extract into basenames */
62: Boole Count = FALSE; /* count characters to check transfer */
63: Boole Overcheck = TRUE; /* check overwriting */
64: Boole Uucode = FALSE; /* uuencode the file */
65: char *Delim = "SHAR_EOF"; /* put after each file */
66: char Filter[100] = CAT; /* used to extract archived files */
67: char *Prefix = NULL; /* line prefix to avoid funny chars */
68: Boole Modeset = FALSE; /* set exact modes on extraction */
69:
70: /*FUNCTION: initial: do option parsing and any setup */
71: int /* returns the index of the first operand file */
72: initial (argc, argv) char **argv;
73: {
74: int errflg = 0;
75: extern int optind;
76: extern char *optarg;
77: int C;
78: char *optstring = "abcmsuvp:d:";
79: char *usage = "[-abcmsuv] [-p prefix] [-d delim] files > archive";
80:
81: while ((C = getopt (argc, argv, optstring)) != EOF)
82: switch (C)
83: {
84: case 'u': Uucode = TRUE; break;
85: case 'b': Basename = TRUE; break;
86: case 'c': Count = TRUE; break;
87: case 'd': Delim = optarg; break;
88: case 'm': Modeset = TRUE; break;
89: case 's': /* silent running */
90: Overcheck = FALSE;
91: Verbose = FALSE;
92: Count = FALSE;
93: Prefix = NULL;
94: break;
95: case 'a': /* all the options */
96: Verbose = TRUE;
97: Count = TRUE;
98: Basename = TRUE;
99: /* fall through to set prefix */
100: optarg = " X";
101: /* FALLTHROUGH */
102: case 'p': (void) sprintf (Filter, SED, Prefix = optarg); break;
103: case 'v': Verbose = TRUE; break;
104: default: errflg++;
105: }
106:
107: if (errflg || optind == argc)
108: {
109: if (optind == argc)
110: fprintf (stderr, "shar: No input files\n");
111: fprintf (stderr, "Usage: shar %s\n", usage);
112: return (-1);
113: }
114:
115: return (optind);
116: }
117:
118: /*FUNCTION header: print header for archive */
119: (argc, argv, optind)
120: char **argv;
121: {
122: int i;
123: Boole problems = FALSE;
124: long clock;
125: char *ctime ();
126: char *getenv ();
127: char *NAME = getenv ("NAME");
128: char *ORG = getenv ("ORGANIZATION");
129:
130: for (i = optind; i < argc; i++)
131: if (access (argv[i], 4)) /* check read permission */
132: {
133: fprintf (stderr, "shar: Can't read '%s'\n", argv[i]);
134: problems++;
135: }
136:
137: if (problems)
138: return (FAILURE);
139:
140: printf ("%s\n", EXTRACT);
141: printf ("# This is a shell archive, meaning:\n");
142: printf ("# 1. Remove everything above the %s line.\n", EXTRACT);
143: printf ("# 2. Save the resulting text in a file.\n");
144: printf ("# 3. Execute the file with /bin/sh (not csh) to create:\n");
145: for (i = optind; i < argc; i++)
146: printf ("#\t%s\n", argv[i]);
147: (void) time (&clock);
148: printf ("# This archive created: %s", ctime (&clock));
149: if (NAME)
150: printf ("# By:\t%s (%s)\n", NAME, ORG ? ORG : "");
151: printf ("export PATH; PATH=%s\n", PATH);
152: return (SUCCESS);
153: }
154:
155: /*FUNCTION footer: print end of shell archive */
156: ()
157: {
158: printf ("exit 0\n");
159: printf ("#\tEnd of shell archive\n");
160: }
161:
162: /* uuencode options available to send cntrl and non-ascii chars */
163: /* really, this is getting to be too much like cpio or tar */
164:
165: /* ENC is the basic 1 character encoding function to make a char printing */
166: #define ENC(c) (((c) & 077) + ' ')
167:
168: /*FUNCTION uuarchive: simulate uuencode to send files */
169: Status
170: uuarchive (input, protection, output)
171: char *input;
172: int protection;
173: char *output;
174: {
175: FILE *in;
176: if (in = fopen (input, "r"))
177: {
178: printf ("%s << \\%s\n", DECODE, Delim);
179: printf ("begin %o %s\n", protection, output);
180: uuencode (in, stdout);
181: printf ("end\n");
182: fclose (in);
183: return (SUCCESS);
184: }
185: return (FAILURE);
186: }
187:
188: uuencode (in, out)
189: FILE *in, *out;
190: {
191: char buf[80];
192: int i, n;
193: for (;;)
194: {
195: n = fread (buf, 1, 45, in);
196: putc (ENC(n), out);
197: for (i = 0; i < n; i += 3)
198: outdec (&buf[i], out);
199: putc ('\n', out);
200: if (n <= 0)
201: break;
202: }
203: }
204:
205: /* output one group of 3 bytes, pointed at by p, on file ioptr */
206: outdec (p, ioptr)
207: char *p;
208: FILE *ioptr;
209: {
210: int c1, c2, c3, c4;
211: c1 = *p >> 2;
212: c2 = (*p << 4) & 060 | (p[1] >> 4) & 017;
213: c3 = (p[1] << 2) & 074 | (p[2] >> 6) & 03;
214: c4 = p[2] & 077;
215: putc (ENC(c1), ioptr);
216: putc (ENC(c2), ioptr);
217: putc (ENC(c3), ioptr);
218: putc (ENC(c4), ioptr);
219: }
220:
221: /*FUNCTION archive: make archive of input file to be extracted to output */
222: archive (input, output)
223: char *input, *output;
224: {
225: char buf[BUFSIZ];
226: FILE *ioptr;
227:
228: if (ioptr = fopen (input, "r"))
229: {
230: Ctrlcount = 0; /* no bad control characters so far */
231: Lastchar = '\n'; /* simulate line start */
232:
233: printf ("%s << \\%s > '%s'\n", Filter, Delim, output);
234:
235: if (Prefix)
236: {
237: while (fgets (buf, BUFSIZ, ioptr))
238: {
239: outline (Prefix);
240: outline (buf);
241: }
242: }
243: else copyout (ioptr);
244:
245: if (Lastchar != '\n') /* incomplete last line */
246: putchar ('\n'); /* Delim MUST begin new line! */
247:
248: if (Count == TRUE && Lastchar != '\n')
249: printf ("%s \"a missing newline was added to '%s'\"\n", ECHO, input);
250: if (Count == TRUE && Ctrlcount)
251: printf ("%s \"%d control character%s may be missing from '%s'\"\n",
252: ECHO, Ctrlcount, Ctrlcount > 1 ? "s" : "", input);
253:
254: (void) fclose (ioptr);
255: return (SUCCESS);
256: }
257: else
258: {
259: fprintf (stderr, "shar: Can't open '%s'\n", input);
260: return (FAILURE);
261: }
262: }
263:
264: /*FUNCTION copyout: copy ioptr to stdout */
265: /*
266: Copyout copies its ioptr almost as fast as possible
267: except that it has to keep track of the last character
268: printed. If the last character is not a newline, then
269: shar has to add one so that the end of file delimiter
270: is recognized by the shell. This checking costs about
271: a 10% difference in user time. Otherwise, it is about
272: as fast as cat. It also might count control characters.
273: */
274: #define badctrl(c) (iscntrl (c) && !isspace (c))
275: copyout (ioptr)
276: register FILE *ioptr;
277: {
278: register int C;
279: register int L;
280: register count;
281:
282: count = Count;
283:
284: while ((C = getc (ioptr)) != EOF)
285: {
286: if (count == TRUE && badctrl (C))
287: Ctrlcount++;
288: L = putchar (C);
289: }
290:
291: Lastchar = L;
292: }
293:
294: /*FUNCTION outline: output a line, recoring last character */
295: outline (s)
296: register char *s;
297: {
298: if (*s)
299: {
300: while (*s)
301: {
302: if (Count == TRUE && badctrl (*s)) Ctrlcount++;
303: putchar (*s++);
304: }
305: Lastchar = *(s-1);
306: }
307: }
308:
309: /*FUNCTION shar: main archiving routine passed to directory traverser */
310: shar (file, type, pos)
311: char *file; /* file or directory to be processed */
312: int type; /* either 'f' for file or 'd' for directory */
313: int pos; /* 0 going in to a file or dir, 1 going out */
314: {
315: struct stat statbuf;
316: char *basefile = file;
317: int protection;
318:
319: if (!strcmp (file, "."))
320: return;
321:
322: if (stat (file, &statbuf))
323: statbuf.st_size = 0;
324: else
325: protection = statbuf.st_mode & 07777;
326:
327: if (Basename == TRUE)
328: {
329: while (*basefile) basefile++; /* go to end of name */
330: while (basefile > file && *(basefile-1) != '/')
331: basefile--;
332: }
333:
334: if (pos == 0) /* before the file starts */
335: {
336: beginfile (basefile, type, statbuf.st_size, protection);
337: if (type == 'f')
338: {
339: if (Uucode)
340: {
341: if (uuarchive (file, protection, basefile) != SUCCESS)
342: exit (FAILURE);
343: }
344: else
345: if (archive (file, basefile) != SUCCESS)
346: exit (FAILURE);
347: }
348: }
349: else /* pos == 1, after the file is archived */
350: endfile (basefile, type, statbuf.st_size, protection);
351: }
352:
353: /*FUNCTION beginfile: do activities before packing up a file */
354: beginfile (basefile, type, size, protection)
355: char *basefile; /* name of the target file */
356: int type; /* either 'd' for directory, or 'f' for file */
357: long size; /* size of the file */
358: int protection; /* chmod protection bits */
359: {
360: if (type == 'd')
361: {
362: printf ("if %s ! -d '%s'\n", TEST, basefile);
363: printf ("then\n");
364: if (Verbose == TRUE)
365: printf (" %s \"creating directory '%s'\"\n", ECHO, basefile);
366: printf (" %s '%s'\n", MKDIR, basefile);
367: printf ("fi\n");
368: if (Verbose == TRUE)
369: printf ("%s \"entering directory '%s'\"\n", ECHO, basefile);
370: printf ("%s '%s'\n", CHDIR, basefile);
371: }
372: else /* type == 'f' */
373: {
374: if (Verbose == TRUE)
375: printf ("%s \"extracting '%s'\" '(%ld character%s)'\n",
376: ECHO, basefile, size, size == 1 ? "" : "s");
377:
378: if (Overcheck == TRUE)
379: {
380: printf ("if %s -f '%s'\n", TEST, basefile);
381: printf ("then\n");
382: printf (" %s \"will not over-write existing file '%s'\"\n",
383: ECHO, basefile);
384: printf ("else\n");
385: }
386:
387: }
388: }
389:
390: /*FUNCTION endfile: do activities after packing up a file */
391: endfile (basefile, type, size, protection)
392: char *basefile; /* name of the target file */
393: int type; /* either 'd' for directory, or 'f' for file */
394: long size; /* size of the file */
395: int protection; /* chmod protection bits */
396: {
397: if (type == 'd')
398: {
399: if (Modeset == TRUE)
400: printf ("%s %o .\n", CHMOD, protection);
401: if (Verbose == TRUE)
402: printf ("%s \"done with directory '%s'\"\n", ECHO, basefile);
403: printf ("%s ..\n", CHDIR);
404: }
405: else /* type == 'f' (plain file) */
406: {
407: printf ("%s\n", Delim);
408: if (Count == TRUE)
409: {
410: printf ("if %s %ld -ne \"`%s '%s'`\"\n", TEST, size, WC_C, basefile);
411: printf ("then\n");
412: printf (" %s \"error transmitting '%s'\" ", ECHO, basefile);
413: printf ("'(should have been %ld character%s)'\n",
414: size, size == 1 ? "" : "s");
415: printf ("fi\n");
416: }
417: if (Uucode == FALSE) /* might have to chmod by hand */
418: {
419: if (Modeset == TRUE) /* set all protection bits (W McKeeman) */
420: printf ("%s %o '%s'\n",
421: CHMOD, protection, basefile);
422: else if (protection & 0111) /* executable -> chmod +x */
423: printf ("%s +x '%s'\n", CHMOD, basefile);
424: }
425: if (Overcheck == TRUE)
426: printf ("fi\n");
427: }
428: }