stp@ethz.UUCP (Stephan Paschedag) (08/16/89)
#! /bin/sh # This is a shell archive. Remove anything before this line, then unpack # it by saving it into a file and typing "sh file". To overwrite existing # files, type "sh file -c". You can also feed this as standard input via # unshar, or by typing "sh <file", e.g.. If this archive is complete, you # will see the following message at the end: # "End of shell archive." # Contents: readme makefile tar.c # Wrapped by stp@ethz on Wed Aug 16 14:17:38 1989 PATH=/bin:/usr/bin:/usr/ucb ; export PATH if test -f 'readme' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'readme'\" else echo shar: Extracting \"'readme'\" \(924 characters\) sed "s/^X//" >'readme' <<'END_OF_FILE' X Xthis version of tar has originally been Simmule Turners tar for OS-9/6809. XI've adapted it to OS-9/68k and added some new features. XI did this in a manner that it still should be able to be compiled on a X6809 system (but i haven't been able to test it). X Xnew features : X X it's now possible to extract a single file from an archive X all missing directories are now automatically created X extraction of entire directories in now possible X directories from UNIX-written archives are now correctly created X Xexamples: X X tar x archive test/abc extract file 'test/abc' from archive X directory 'test' is automatically created X X tar x archive test extracts entire directory 'test' X Xplease send more ideas or bug reports to me : X XStephan Paschedag paschedag@strati.ethz.ch or stp@ethz.UUCP XSwiss Federal Institute Of Technology Zurich ..!cernvax!ethz!stp END_OF_FILE if test 924 -ne `wc -c <'readme'`; then echo shar: \"'readme'\" unpacked with wrong size! fi # end of 'readme' fi if test -f 'makefile' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'makefile'\" else echo shar: Extracting \"'makefile'\" \(118 characters\) sed "s/^X//" >'makefile' <<'END_OF_FILE' XCFLAGS=-qit=/r0 XCC=cc X X.makedate: tar.c X $(CC) $(CFLAGS) -fd=tar tar.c X @attr tar -ea X @touch .makedate X X END_OF_FILE if test 118 -ne `wc -c <'makefile'`; then echo shar: \"'makefile'\" unpacked with wrong size! fi # end of 'makefile' fi if test -f 'tar.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'tar.c'\" else echo shar: Extracting \"'tar.c'\" \(19127 characters\) sed "s/^X//" >'tar.c' <<'END_OF_FILE' Xchar scid[]="$Header: tar.c,v 1.5 08/06/89 SrT/stp $"; X X/* X * Modified for use under OS-9/68k (08/03/89) X * Stephan Paschedag stp@ethz.uucp ..cernvax!ethz!stp X * X * Compilation for OS-9/68k: cc -qi tar.c X * X * 08/06/89 V1.5 Allow extraction of entire directories, X * allow extraction of single files in subdirectories, X * creates automatically missing directories. X * continues now correctly after some errors X * handles directories from unix archives correctly (stp) X * X * Modified for use under OS-9/6809 X * Simmule Turner simmy@nu.cs.fsu.edu 70651,67 X * X * Compilation for OS-9/6809: cc -m=3k tar.c X * Use extra memory for recursive directory descents. X * X * 07/17/88 V1.4 Allow extraction of specified file(s).... X * from archive named on the command line X * with wildcard matching *,?. (SrT) X * X * 07/14/88 V1.3 Added verbose TOC ala unix. Cleaned up X * printing routines, use decimal instead of X * octal. Removed conditional compilation. (SrT) X * X * 07/13/88 V1.2 Tries to set the correct file permissions. X * Send diagnostics to stderr. Use lseek's X * for TOC generation when a rbf device. (SrT) X * X * 07/12/88 V1.1 Added option of reading/writing X * archive to stdin/stdout (SrT) X * X * 07/12/88 V1.0 Initial port (SrT) X * added dummy fv options X * SrT */ X X/* tar - tape archiver Author: Michiel Huisjes */ X X/* Usage: tar [cxt][v] tapefile [files] X * X * Bugs: X * This tape archiver should only be used as a program to read or make X * simple tape archives. Its basic goal is to read (or build) UNIX V7 tape X * archives and extract the named files. It is not wise to use it on X * raw magnetic tapes. It doesn't know anything about linked files, X * except when the involved fields are filled in. X */ X X#include <stdio.h> X#include <direct.h> X#include <modes.h> X#include <sgstat.h> X#include <time.h> X#ifndef OSK X# include <utime.h> X#else X# include <types.h> X# include <errno.h> X# include <strings.h> X#endif X X#define STDIN 0 X#define STDOUT 1 X Xtypedef char BOOL; X#define TRUE 1 X#define FALSE 0 X X#define HEADER_SIZE 512 X#define NAME_SIZE 100 X#define BLOCK_BOUNDARY 20 X Xtypedef union { X char hdr_block[HEADER_SIZE]; X struct m { X char m_name[NAME_SIZE]; X char m_mode[8]; X char m_uid[8]; X char m_gid[8]; X char m_size[12]; X char m_time[12]; X char m_checksum[8]; X char m_linked; X char m_link[NAME_SIZE]; X } member; X} HEADER; X XHEADER header; X X#define INT_TYPE (sizeof(header.member.m_uid)) X#define LONG_TYPE (sizeof(header.member.m_size)) X X#define NIL_HEADER ((HEADER *) 0) X#define NIL_PTR ((char *) 0) X#define BLOCK_SIZE 512 X X#define flush() print(NIL_PTR) X XBOOL show_fl, creat_fl, ext_fl, verbose_fl, modtime_fl; X Xchar modestr[11], stbuf[132]; Xstruct sgbuf opts; Xstruct fildes ofdbuf; X#ifdef OSK Xchar *dummy = NULL; X#endif X Xint tar_fd; X#ifdef OSK Xchar usage[] = "Syntax : tar [ctx][mfv] tarfile [file(s)...]\nFunction: Builds and extracts from Unix tape archive files\nOptions:\n c : create archive\n t : list contents\n x : extract from archive"; X#else Xchar usage[] = "Usage: tar [ctx][mfv] tarfile [file(s)...]"; X#endif Xchar io_buffer[BLOCK_SIZE]; X Xint total_blocks; Xlong convert(); Xchar **match(); X Xblock_size() { X return ((int) ((convert(header.member.m_size, LONG_TYPE) X + (long) BLOCK_SIZE - 1) / (long) BLOCK_SIZE)); X} X Xerror(s1, s2) Xchar *s1, *s2; X{ X#ifdef OSK X fprintf(stderr,"%s %s\n", s1, s2 ? s2 : ""); X exit(1); X#else X string_print(NIL_PTR, "%s %s\r\l", s1, s2 ? s2 : ""); X flush(); X exit(1); X#endif X} X Xmain(argc, argv) Xint argc; Xregister char *argv[]; X{ X register char *ptr; X#ifdef OSK X char **p = &dummy; X#else X char **p = "\0"; X#endif X int i; X X pflinit(); X X if (argc < 3) X error(usage, NIL_PTR); X X for (ptr = argv[1]; *ptr; ptr++) { X switch (*ptr) { X case 'c' : X creat_fl = TRUE; X break; X case 't' : X show_fl = TRUE; X break; X case 'x' : X ext_fl = TRUE; X if (argc > 3) X p = &argv[3]; X break; X/* X * Modifiers X * SrT */ X case 'f': X break; X case 'm': X modtime_fl = TRUE; X break; X case 'v': X verbose_fl = TRUE; X break; X default : X error(usage, NIL_PTR); X } X } X X if (creat_fl + ext_fl + show_fl != 1) X error(usage, NIL_PTR); X X if (argv[2][0] == '-') X tar_fd = creat_fl ? STDOUT : STDIN; X else X tar_fd = creat_fl ? creat(argv[2],3) : open(argv[2],1); X X if (tar_fd < 0) { X#ifdef OSK X exit(_errmsg(errno,"cannot open archive. ")); X#else X error("Cannot open ", argv[2]); X#endif X } X X if (creat_fl) { X for (i = 3; i < argc; i++) X add_file(argv[i]); X adjust_boundary(); X } X else X tarfile (p); X X flush(); X exit(0); X} X XBOOL get_header() X{ X register int check,c; X X mread(tar_fd, &header, sizeof(header)); X if (header.member.m_name[0] == '\0') X return FALSE; X X check = (int) convert(header.member.m_checksum, INT_TYPE); X X if (check != (c = checksum())) X error("tar: header checksum error.", NIL_PTR); X X return TRUE; X} X Xtarfile(p) X char **p; X{ X register char *ptr; X register char *mem_name; X char *atime(); X int i; X register char **q; X register char *pp; X X _gs_opt(tar_fd,&opts); X X while (get_header()) { X mem_name = header.member.m_name; X if (ext_fl) { X if (*p) { X/* extract entire directories (08/05/89 stp) */ X if (mem_name[strlen(mem_name)-1] == '/') { X mem_name[strlen(mem_name)-1] = '\0'; X if (q=match(p,mem_name)) { X pp = (char *) malloc(strlen(mem_name)+3); X strcpy(pp,mem_name); X strncat(pp,"/*",3); X *q = pp; X mkdir(mem_name); X } X } X else { X if (match(p,mem_name)) { X extract(mem_name); X } X else X skip_entry(); X } X } X else { X if (is_dir(mem_name)) { X for (ptr = mem_name; *ptr != '/'; ptr++); X *ptr = '\0'; X mkdir(mem_name); X } X else { X extract(mem_name); X } X } X } X else { X if (!verbose_fl) X#ifdef OSK X printf("%s",mem_name); X#else X string_print(NIL_PTR, "%s", mem_name); X#endif X else { X u29mode((int) convert(header.member.m_mode,INT_TYPE)); X#ifdef OSK X printf("%s ",modestr); X#else X string_print(NIL_PTR, "%s ",modestr); X#endif X sprintf(stbuf,"%3d/%3d %8ld %s %s", X (int) convert(header.member.m_uid, INT_TYPE), X (int) convert(header.member.m_gid, INT_TYPE), X convert(header.member.m_size,LONG_TYPE), X atime(convert(header.member.m_time,LONG_TYPE)), X header.member.m_name); X#ifdef OSK X printf("%s",stbuf); X } X printf("\n"); X#else X print(stbuf); X } X print("\r\l"); X#endif X skip_entry(); X } X flush(); X } X} X Xskip_entry() X{ X register int blocks = block_size(); X X if (opts.sg_class == 1) { X long pos; X X pos = ((long) blocks) * BLOCK_SIZE; X lseek(tar_fd,pos,1); X } X else { X while (blocks--) X read(tar_fd, io_buffer, BLOCK_SIZE); X } X} X Xextract(file) Xregister char *file; X{ X register int fd; X X if (header.member.m_linked == '1') { X#ifdef OSK X fprintf(stderr,"Cannot link %s (symbolic links not supportet)\n",header.member.m_link); X#else X string_print(NIL_PTR,"Cannot link %s\r\l",header.member.m_link); X#endif X skip_entry(); X return; X } X X if ((fd = creat(file, 3)) < 0) { X/* create missing directories (08/06/89) stp */ Xregister char *s,*pp; X pp = file; X#ifdef OSK X while (s = index(pp,'/')) { X#else X while (s = strchr(pp,'/')) { X#endif X pp = (char *) malloc(s-file+1); X strncpy(pp,file,s-file); X pp[s-file] = '\0'; X mkdir(pp); X free(pp); X pp = s+1; X } X if ((fd = creat(file, 3)) < 0) { X#ifdef OSK X fprintf(stderr, "Cannot create %s : ", file); fflush(stderr); X prerr(0,errno); X#else X string_print(NIL_PTR, "Cannot create %s\r\l", file); X#endif X skip_entry(); X return; X } X } X X copy(file, tar_fd, fd, convert(header.member.m_size, LONG_TYPE)); X X _ss_attr(fd, u29mode((int) convert(header.member.m_mode, INT_TYPE))); X X if (!modtime_fl) X#ifdef OSK X if (_gs_gfd(fd,&ofdbuf,sizeof(ofdbuf)) != -1) { X#else X if (_gs_gfd(fd,&ofdbuf,sizeof(ofdbuf)) != ERROR) { X#endif X X struct tm *utm; X struct sgtbuf otm; X long clock; X X clock = convert(header.member.m_time,LONG_TYPE); X utm = localtime(&clock); X u2otime(&otm,utm); X _strass(&ofdbuf.fd_date[0], &otm, 5); X _ss_pfd(fd,&ofdbuf); X } X X close(fd); X flush(); X} X Xcopy(file, from, to, bytes) Xchar *file; Xint from, to; Xregister long bytes; X{ X register int rest; X int blocks = (int) ((bytes + (long) BLOCK_SIZE - 1) / (long) BLOCK_SIZE); X X if (verbose_fl) { X if (to == tar_fd) X#ifdef OSK X printf("a %s, %d tape blocks\n",file,blocks); X#else X sprintf(stbuf,"a %s, %d tape blocks\r\l",file,blocks); X#endif X else X#ifdef OSK X printf("x %s, %ld bytes, %d tape blocks\n",file,bytes,blocks); X#else X sprintf(stbuf,"x %s, %ld bytes, %d tape blocks\r\l",file,bytes,blocks); X print(stbuf); X flush(); X#endif X } X X while (blocks--) { X read(from, io_buffer, BLOCK_SIZE); X rest = (bytes > (long) BLOCK_SIZE) ? BLOCK_SIZE : (int) bytes; X mwrite(to, io_buffer, (to == tar_fd) ? BLOCK_SIZE : rest); X bytes -= (long) rest; X } X} X Xlong convert(str, type) Xchar str[]; Xint type; X{ X register long ac = 0L; X register int i; X X for (i = 0; i < type; i++) { X if (str[i] >= '0' && str[i] <= '7') { X ac <<= 3; X ac += (long) (str[i] - '0'); X } X } X X return ac; X} X Xmkdir(dir_name) Xchar *dir_name; X{ X if (mknod(dir_name,3) < 0) { X return; X } X else { X int fd; X if ((fd = open(dir_name,0x83)) > 0) { X _ss_attr(fd, S_IFDIR | u29mode((int) convert(header.member.m_mode, INT_TYPE))); X close(fd); X } X } X} X Xchecksum() X{ X register char *ptr = header.member.m_checksum; X register int ac = 0; X X while (ptr < &header.member.m_checksum[INT_TYPE]) X *ptr++ = ' '; X X ptr = header.hdr_block; X while (ptr < &header.hdr_block[BLOCK_SIZE]) X ac += *ptr++; X X return ac; X} X Xis_dir(file) Xregister char *file; X{ X while (*file++ != '\0') ; X return (*(file - 2) == '/'); X} X Xchar path[NAME_SIZE]; X Xchar pathname[NAME_SIZE]; Xchar *path_name(file) Xregister char *file; X{ X X string_print(pathname, "%s%s", path, file); X return pathname; X} X Xadd_path(name) Xregister char *name; X{ X register char *path_ptr = path; X X while (*path_ptr) X path_ptr++; X X if (name == NIL_PTR) { X while (*path_ptr-- != '/') X ; X while (*path_ptr != '/' && path_ptr != path) X path_ptr--; X if (*path_ptr == '/') X path_ptr++; X *path_ptr = '\0'; X } X else { X while (*name) { X if (path_ptr == &path[NAME_SIZE]) X error("tar: Pathname too long", NIL_PTR); X *path_ptr++ = *name++; X } X *path_ptr++ = '/'; X *path_ptr = '\0'; X } X} X Xadd_file(file) Xregister char *file; X{ X struct fildes st; X struct dirent dir; X register int fd; X#ifdef OSK X unsigned long siz; X u_char *sip = (u_char*) st.fd_fsize; X#endif X X if ((fd = open(file,0x81)) < 0) X if ((fd = open(file, 1)) < 0) { X#ifdef OSK X fprintf(stderr, "Cannot open '%s' ", file); fflush(stderr); X prerr(0,errno); X#else X string_print(NIL_PTR, "Cannot open %s\r\l", file); X#endif X return; X } X if (_gs_gfd(fd,&st,sizeof(st)) < 0) { X#ifdef OSK X fprintf(stderr, "Cannot get file descriptor for %s",file); fflush(stderr); X prerr(0,errno); X#else X string_print(NIL_PTR, "Cannot get file descriptor for %s\r\l",file); X#endif X close(fd); X return; X } X siz = (((((sip[0] << 8) + sip[1]) << 8) + sip[2]) << 8) + sip[3]; X X make_header(path_name(file), &st); X mwrite(tar_fd, &header, sizeof(header)); X if (!(st.fd_att & S_IFDIR)) X#ifdef OSK X copy(path_name(file), fd, tar_fd, siz); X#else X copy(path_name(file), fd, tar_fd, st.fd_fsize); X#endif X else if (st.fd_att & S_IFDIR) { X if (chdir(file) < 0) X string_print(NIL_PTR, "Cannot chdir to %s\n", file); X else { X add_path(file); X mread(fd, &dir, sizeof(dir)); /* "." */ X mread(fd, &dir, sizeof(dir)); /* ".." */ X while (read(fd, &dir, sizeof(dir)) == sizeof(dir)) { X#ifdef OSK X if (dir.dir_addr) { X#else X if (dir.dir_addr[0] || dir.dir_addr[1] || dir.dir_addr[2]) { X#endif X strhcpy(dir.dir_name,dir.dir_name); X if (*dir.dir_name) X add_file(dir.dir_name); X } X } X chdir(".."); X add_path(NIL_PTR); X } X } X else X#ifdef OSK X _errmsg("unknown file type. Not added. ",0); X#else X print("tar: unknown file type. Not added.\r\l"); X#endif X X close(fd); X} X Xmake_header(file, st) Xchar *file; Xregister struct fildes *st; X{ X register char *ptr = header.member.m_name; X char tbuf[6]; X#ifdef OSK X u_char *sip = (u_char*) st->fd_fsize; X unsigned long siz = (((((sip[0] << 8) + sip[1]) << 8) + X sip[2]) << 8) + sip[3]; X u_char *owp = (u_char*) st->fd_own; X unsigned short own = owp[1]; X unsigned short group = owp[0]; X#endif X X clear_header(); X X while (*ptr++ = *file++) X ; X X if (st->fd_att & S_IFDIR) { X *(ptr - 1) = '/'; X#ifdef OSK X siz = 0; X#else X st->fd_fsize = 0L; X#endif X } X X _strass(tbuf,st->fd_date,5); X tbuf[5] = 0; X string_print(header.member.m_mode, "%I ", o2umode(st->fd_att)); X#ifdef OSK X string_print(header.member.m_uid, "%I ", own); X string_print(header.member.m_gid, "%I ", group); X string_print(header.member.m_size, "%L ", siz); X#else X string_print(header.member.m_uid, "%I ", st->fd_own); X string_print(header.member.m_gid, "%I ", 101); X string_print(header.member.m_size, "%L ", st->fd_fsize); X#endif X string_print(header.member.m_time, "%L ", o2utime(tbuf)); X header.member.m_linked = ' '; X string_print(header.member.m_checksum, "%I", checksum()); X} X Xclear_header() X{ X register char *ptr = header.hdr_block; X X while (ptr < &header.hdr_block[BLOCK_SIZE]) X *ptr++ = '\0'; X} X Xadjust_boundary() X{ X clear_header(); X mwrite(tar_fd, &header, sizeof(header)); X X while (total_blocks++ < BLOCK_BOUNDARY) X mwrite(tar_fd, &header, sizeof(header)); X close(tar_fd); X} X Xmread(fd, address, bytes) Xint fd, bytes; Xchar *address; X{ Xregister int r; X X if ((r = read(fd, address, bytes)) != bytes) { X#ifdef OSK X if (r == 0) X errno = E_EOF; X exit(_errmsg(errno,"read error. ")); X#else X error("tar: read error.", NIL_PTR); X#endif X } X} X Xmwrite(fd, address, bytes) Xint fd, bytes; Xchar *address; X{ X if (write(fd, address, bytes) != bytes) { X#ifdef OSK X exit(_errmsg(errno,"write error. ")); X#else X error("tar: write error.", NIL_PTR); X#endif X } X total_blocks++; X} X Xchar output[BLOCK_SIZE]; Xprint(str) Xregister char *str; X{ X static int index = 0; X X if (str == NIL_PTR) { X write(2, output, index); X index = 0; X return; X } X X while (*str) { X output[index++] = *str++; X if (index == BLOCK_SIZE) { X write(2, output, BLOCK_SIZE); X index = 0; X } X } X} X Xchar *num_out(number) Xregister long number; X{ X static char num_buf[13]; X char temp[13]; X register int i; X X for (i = 0; i < 11; i++) { X temp[i] = (number & 07) + '0'; X number >>= 3; X } X X for (i = 0; i < 11; i++) X num_buf[i] = temp[10 - i]; X X return num_buf; X} X X/* VARARGS */ Xstring_print(buffer, fmt, args) Xchar *buffer; Xregister char *fmt; Xint args; X{ X register char *buf_ptr; X char *scan_ptr; X char buf[NAME_SIZE]; X int *argptr = &args; X BOOL pr_fl, i; X X if (pr_fl = (buffer == NIL_PTR)) X buffer = buf; X X buf_ptr = buffer; X while (*fmt) { X if (*fmt == '%') { X fmt++; X switch (*fmt++) { X case 's': X scan_ptr = (char *) *argptr; X break; X case 'I': X scan_ptr = num_out((long) *argptr) + 5; X/* for (i = 0; i < 5; i++) X scan_ptr++; */ X break; X case 'L': X scan_ptr = num_out(*((long *) argptr)); X argptr++; X break; X default: X scan_ptr = ""; X } X while (*buf_ptr++ = *scan_ptr++) X ; X buf_ptr--; X argptr++; X } X else X *buf_ptr++ = *fmt++; X } X *buf_ptr = '\0'; X X if (pr_fl) X print(buffer); X} X X o2umode(mode) X char mode; X{ X int ret_mode=0; X X if (mode & S_IFDIR) X ret_mode |= 040000; X if (mode & S_IREAD) X ret_mode |= 0400; X if (mode & S_IWRITE) X ret_mode |= 0200; X if (mode & S_IEXEC) X ret_mode |= 0100; X if (mode & S_IOREAD) X ret_mode |= 04; X if (mode & S_IOWRITE) X ret_mode |= 02; X if (mode & S_IOEXEC) X ret_mode |= 01; X X return(ret_mode); X} X X u29mode(mode) X int mode; X{ X int ret_mode=0; X X strcpy(modestr,"-----------"); X X if (mode & 040000) { X ret_mode |= S_IFDIR; X modestr[0] = 'd'; X } X X if (mode & 0400) { X ret_mode |= S_IREAD; X modestr[1] = 'r'; X } X X if (mode & 0200) { X ret_mode |= S_IWRITE; X modestr[2] = 'w'; X } X X if (mode & 0100) { X ret_mode |= S_IEXEC; X modestr[3] = 'x'; X } X X if (mode & 04) { X ret_mode |= S_IOREAD; X modestr[7] = 'r'; X } X X if (mode & 02) { X ret_mode |= S_IOWRITE; X modestr[8] = 'w'; X } X X if (mode & 01) { X ret_mode |= S_IOEXEC; X modestr[9] = 'x'; X } X X return(ret_mode); X} X Xchar *atime(clock) X long clock; X{ X static char buf[26]; X int i; X X strcpy(buf,ctime(&clock)); X X for (i=4; i< 16; i++) X buf[i-4] = buf[i]; X buf[12] = ' '; X X for (i=20; i<24; i++) X buf[i-7] = buf[i]; X buf[17] = 0; X X return(buf); X} X Xchar **match(p,name) X char **p, *name; X{ Xchar **q = p; X X while (*q) { X#ifdef OSK X if (!_cmpnam(name,*q,strlen(*q))) { X#else X if (patmatch(*q,name,1)) { X#endif X return(q); X } X q++; X } X return(NULL); X} X X#ifdef OSK Xu2otime(om,um) Xstruct sgtbuf *om; Xstruct tm *um; X{ X om->t_year = um->tm_year; X om->t_month = um->tm_mon+1; X om->t_day = um->tm_mday; X om->t_hour = um->tm_hour; X om->t_minute = um->tm_min; X om->t_second = um->tm_sec; X} X Xlong o2utime(om) Xstruct sgtbuf *om; X{ Xstruct tm um; X X um.tm_year = om->t_year; X um.tm_mon = om->t_month-1; X um.tm_mday = om->t_day; X um.tm_hour = om->t_hour; X um.tm_min = om->t_minute; X um.tm_sec = om->t_second; X return mktime(&um); X} X#endif END_OF_FILE if test 19127 -ne `wc -c <'tar.c'`; then echo shar: \"'tar.c'\" unpacked with wrong size! fi # end of 'tar.c' fi echo shar: End of shell archive. exit 0