[comp.os.os9] Tar source

rh2y+@andrew.cmu.edu (Russell E. Hoffman, II) (09/26/90)

Here is the source for the un*x 'tar' utility. I'd have posted executable
as well, but then I'd have had to at least uuencode it, and judging by the fact
that the requestor does not have tar, he is not likely to have uudecode either,
so I thought I'd play it safe!


-------cut here--------


char scid[]="$Header: tar.c,v 1.8  04/13/90 SrT/stp $";

/*
 * Modified for use under OS-9/68k  (08/03/89)
 * Stephan Paschedag  stp@ethz.uucp   ..!mcvax!cernvax!chx400!ethz!stp
 *                    paschedag@strati.ethz.ch
 *
 * Compilation for OS-9/68k:  cc -qi tar.c
 *
 * 04/13/90  V1.8  problems in extraction of subdirectories fixed
 *                 change invalid filenames
 *                 LF <-> CR converion added (u modifier) (stp)
 *
 * 01/28/90  V1.7  multi-file buffer implemented (stp)
 *
 * 01/16/90  V1.6  Copy buffer implemeted, results in much faster
 *                 (re-)storing times (stp)
 *
 * 08/06/89  V1.5  Allow extraction of entire directories,
 *                 allow extraction of single files in subdirectories,
 *                 creates automatically missing directories.
 *                 continues now correctly after some errors
 *                 handles directories from unix archives correctly (stp)
 *
 * Modified for use under OS-9/6809
 * Simmule Turner   simmy@nu.cs.fsu.edu   70651,67
 *
 * Compilation for OS-9/6809:  cc -m=3k tar.c
 *     Use extra memory for recursive directory descents.
 *
 * 07/17/88  V1.4  Allow extraction of specified file(s)....
 *                 from archive named on the command line
 *                 with wildcard matching *,?. (SrT)
 *
 * 07/14/88  V1.3  Added verbose TOC ala unix.  Cleaned up
 *                 printing routines, use decimal instead of
 *                 octal. Removed conditional compilation. (SrT)
 *
 * 07/13/88  V1.2  Tries to set the correct file permissions.
 *                 Send diagnostics to stderr.  Use lseek's
 *                 for TOC generation when a rbf device. (SrT)
 *
 * 07/12/88  V1.1  Added option of reading/writing
 *                 archive to stdin/stdout (SrT)
 *
 * 07/12/88  V1.0  Initial port (SrT)
 *                 added dummy fv options
 * SrT */

/* tar - tape archiver          Author: Michiel Huisjes */
/* Usage: tar [cxt][bdfmuv] tapefile [files]
 *
 * Bugs:
 *  This tape archiver should only be used as a program to read or make
 *  simple tape archives. Its basic goal is to read (or build) UNIX V7 tape
 *  archives and extract the named files. It is not wise to use it on
 *  raw magnetic tapes. It doesn't know anything about linked files,
 *  except when the involved fields are filled in.
 *
 * Comment : It works fine on raw magnetic tapes ! (tar tv /mt0 .)
 */
#include <stdio.h>
#include <direct.h>
#include <modes.h>
#include <sgstat.h>
#include <time.h>
#ifndef OSK
#  include <utime.h>
#else
#  include <types.h>
#  include <errno.h>
#  include <strings.h>
#endif

#define STDIN   0
#define STDOUT  1

typedef char BOOL;
#define TRUE    1
#define FALSE   0

#define HEADER_SIZE     512
#define NAME_SIZE       100
#define BLOCK_BOUNDARY   20
typedef union {
  char hdr_block[HEADER_SIZE];
  struct m {
    char m_name[NAME_SIZE];
    char m_mode[8];
    char m_uid[8];
    char m_gid[8];
    char m_size[12];
    char m_time[12];
    char m_checksum[8];
    char m_linked;
    char m_link[NAME_SIZE];
  } member;
} HEADER;

/* file match structure (04/13/90 stp) */
struct files {
  char *name;
  struct files *next;
};

HEADER header;

#define INT_TYPE    (sizeof(header.member.m_uid))
#define LONG_TYPE   (sizeof(header.member.m_size))

#define NIL_HEADER  ((HEADER *) 0)
#define NIL_PTR     ((char *) 0)
#define BLOCK_SIZE  512

#define flush()     print(NIL_PTR)

BOOL show_fl, creat_fl, ext_fl, verbose_fl, modtime_fl, convert_fl, disk_fl;

char modestr[11], stbuf[132];
struct sgbuf opts;
struct fildes ofdbuf;
#ifdef OSK
char *dummy = NULL;
#endif

int tar_fd;

/* set blocksize  (01/16/90 stp) */
BOOL blks_fl;
int blocking = 1;
char *io_buffer;
char *w_buffer;
long bsize;

int total_blocks;
long convert();
char **match();

block_size() {
    return ((int) ((convert(header.member.m_size, LONG_TYPE)
            + (long) BLOCK_SIZE - 1) / (long) BLOCK_SIZE));
}

error(s1, s2)
char *s1, *s2;
{
#ifdef OSK
  fprintf(stderr,"%s %s\n", s1, s2 ? s2 : "");
  exit(1);
#else
  string_print(NIL_PTR, "%s %s\r\l", s1, s2 ? s2 : "");
  flush();
  exit(1);
#endif
}
usage() {
  fprintf(stderr,"Syntax : tar [ctxb][mfvu] [blocks] tarfile [file(s)...]\n");
  fprintf(stderr,"Function: Builds and extracts from Unix tape archive files\n");
  fprintf(stderr,"Options:                      Modifiers:\n");
  fprintf(stderr,"   c : create archive            b : set copy buffer size\n");  fprintf(stderr,"   t : list contents             d : backup floppy disks\n");
  fprintf(stderr,"   x : extract from archive      m : do not restore date\n");
  fprintf(stderr,"                                 u : convert LF <-> CR\n");
  fprintf(stderr,"                                 v : verbose mode\n");
  exit(0);
}

main(argc, argv)
int argc;
register char *argv[];
{
    register char *ptr;
#ifdef OSK
    char **p = &dummy;
#else
    char **p = "\0";
#endif
    int i;

  pflinit();

  if (argc < 3)
    usage();

  for (ptr = argv[1]; *ptr; ptr++) {
    switch (*ptr) {
        case 'c' :
            creat_fl = TRUE;
            break;
        case 't' :
            show_fl = TRUE;
            break;
        case 'x' :
            ext_fl  = TRUE;
            break;
/*
 * Modifiers
 * SrT */
/* set blocksize  (01/16/90 stp) */
        case 'b' :
            blks_fl = TRUE;
            break;
/* muliple disk backup (04/14/90 stp) */
        case 'd' :
            disk_fl = TRUE;
            break;
        case 'f':
            break;
        case 'm':
            modtime_fl = TRUE;
            break;
        case 'v':
            verbose_fl = TRUE;
            break;
/* convert flag (04/10/90 stp) */
        case 'u' :
            convert_fl = TRUE;
            break;
        default :
            usage();
    }
  }

  if (blks_fl) {
    if (sscanf(argv[2],"%d",&blocking) == -1)
      error("bad parameter for blocking",argv[3]);
    blocking = (blocking+1)>>1;
  }

  if ((io_buffer = (char*) malloc(BLOCK_SIZE*blocking)) == 0)
    error("allocation of io_buffer failed","");
  if ((w_buffer = (char *) malloc(BLOCK_SIZE*blocking)) == 0)
    error("allocation of w_buffer failed","");
  bsize = (long) BLOCK_SIZE*blocking;

  if (ext_fl) {
    if (argc > (blks_fl ? 4 : 3))
      p = &argv[blks_fl ? 4 : 3];
  }

  if (creat_fl + ext_fl + show_fl != 1)
    usage();

  if (argv[blks_fl ? 3 : 2][0] == '-')
     tar_fd = creat_fl ? STDOUT : STDIN;
  else
     tar_fd = creat_fl ? creat(argv[blks_fl ? 3 : 2],3) : open(argv[blks_fl ? 3
: 2],1);

  if (tar_fd < 0) {
#ifdef OSK
    exit(_errmsg(errno,"cannot open archive. "));
#else
    error("Cannot open ", argv[blks_fl ? 3 : 2]);
#endif
  }

  if (creat_fl) {
    do {
      for (i = (blks_fl ? 4 : 3); i < argc; i++)
        add_file(argv[i]);
      if (disk_fl) {
        do {
          printf("save another disk ?"); fflush(stdout);
          i = toupper(getchar());
        } while ((i != 'Y') && (i != 'N'));
        disk_fl = (i == 'Y');
      }
    } while(disk_fl);
    adjust_boundary();
  }
  else
    tarfile (p);
  flush();
  exit(0);
}

BOOL get_header()
{
  register int check,c;

  mread(tar_fd, &header, sizeof(header));
  if (header.member.m_name[0] == '\0')
    return FALSE;
  check = (int) convert(header.member.m_checksum, INT_TYPE);

  if (check != (c = checksum()))
    error("tar: header checksum error.", NIL_PTR);

  return TRUE;
}

tarfile(p)
    char **p;
{
  register char *ptr;
  register char *mem_name;
  char *atime();
  int i;
  register char **q;
  register char *pp;
  struct files f0,*fptr=&f0;

/* create 'files' structure from 'p' */
  q = p;
  while (*q) {
    fptr->name = (char *) malloc(strlen(*q));
    strcpy(fptr->name,*q);
    fptr->next = (struct files*) malloc(sizeof(struct files));
    fptr = fptr->next;
    q++;
  }
  fptr->name = 0;

  _gs_opt(tar_fd,&opts);

  while (get_header()) {
    mem_name = header.member.m_name;
    if (ext_fl) {
        if (*p) {
/* extract entire directories (08/05/89 stp) */
            if (mem_name[strlen(mem_name)-1] == '/') {
              mem_name[strlen(mem_name)-1] = '\0';
              if (q=match(&f0,mem_name)) {
                fptr->name = (char *) malloc(strlen(mem_name)+3);
                strcpy(fptr->name,mem_name);
                strncat(fptr->name,"/*",3);
                fptr->next = (struct files*) malloc(sizeof(struct files));
                fptr = fptr->next;
                fptr->name = 0;
                check_name(mem_name);
                mkdir(mem_name);
              }
              else {
                q = q;
              }
            }
            else {
              if (match(&f0,mem_name)) {
                extract(mem_name);
              }
              else
                skip_entry();
            }
        }
        else {
            if (is_dir(mem_name)) {
                for (ptr = mem_name; *ptr != '/'; ptr++);
                *ptr = '\0';
                check_name(mem_name);
                mkdir(mem_name);
            }
            else {
              extract(mem_name);
            }
        }
    }
    else  {
        if (!verbose_fl)
#ifdef OSK
            printf("%s",mem_name);
#else
            string_print(NIL_PTR, "%s", mem_name);
#endif
        else {
            u29mode((int) convert(header.member.m_mode,INT_TYPE));
#ifdef OSK
            printf("%s ",modestr);
#else
            string_print(NIL_PTR, "%s ",modestr);
#endif
            sprintf(stbuf,"%3d/%3d %8ld %s %s",
                (int) convert(header.member.m_uid, INT_TYPE),
                (int) convert(header.member.m_gid, INT_TYPE),
                      convert(header.member.m_size,LONG_TYPE),
                      atime(convert(header.member.m_time,LONG_TYPE)),
                      header.member.m_name);
#ifdef OSK
            printf("%s",stbuf);
        }
        printf("\n");
#else
            print(stbuf);
        }
        print("\r\l");
#endif
        skip_entry();
    }
    flush();
  }
}
skip_entry()
{
  register int blocks = block_size();

    if (opts.sg_class == 1) {
        long pos;

        pos = ((long) blocks) * BLOCK_SIZE;
        lseek(tar_fd,pos,1);
    }
    else {
        while (blocks--)
            read(tar_fd, io_buffer, BLOCK_SIZE);
    }
}

/* convert to valid filename */
check_name(c)
register char *c;
{
  while (*c) {
    if (((*c >= '0') && (*c <= '9')) ||
        ((*c >= 'A') && (*c <= 'Z')) ||
        ((*c >= 'a') && (*c <= 'z')) ||
        (*c == '_') || (*c == '$') || (*c == '.') || (*c == '/')) {
    }
    else {
      *c = '_';
    }
    c++;
  }
}

extract(file)
register char *file;
{
  register int fd;

  if (header.member.m_linked == '1') {
#ifdef OSK
    fprintf(stderr,"Cannot link %s (symbolic links not supportet)\n",header.member.m_link);
#else
    string_print(NIL_PTR,"Cannot link %s\r\l",header.member.m_link);
#endif
    skip_entry();
    return(0);
  }

  check_name(file);
  if ((fd = creat(file, 3)) < 0) {
/* create missing directories (08/06/89) stp */
register char *s,*pp;
    pp = file;
#ifdef OSK
    while (s = index(pp,'/')) {
#else
    while (s = strchr(pp,'/')) {
#endif
      pp = (char *) malloc(s-file+1);
      strncpy(pp,file,s-file);
      pp[s-file] = '\0';
      mkdir(pp);
      free(pp);
      pp = s+1;
    }
    if ((fd = creat(file, 3)) < 0) {
#ifdef OSK
      fprintf(stderr, "Cannot create %s : ", file); fflush(stderr);
      prerr(0,errno);
#else
      string_print(NIL_PTR, "Cannot create %s\r\l", file);
#endif
      skip_entry();
      return(0);
    }
  }

  copy(file, tar_fd, fd, convert(header.member.m_size, LONG_TYPE));

  _ss_attr(fd, u29mode((int) convert(header.member.m_mode, INT_TYPE)));

  if (!modtime_fl)
#ifdef OSK
        if (_gs_gfd(fd,&ofdbuf,sizeof(ofdbuf)) != -1) {
#else
        if (_gs_gfd(fd,&ofdbuf,sizeof(ofdbuf)) != ERROR) {
#endif

            struct tm     *utm;
            struct sgtbuf otm;
            long clock;

            clock = convert(header.member.m_time,LONG_TYPE);
            utm = localtime(&clock);
            u2otime(&otm,utm);
            _strass(&ofdbuf.fd_date[0], &otm, 5);
            _ss_pfd(fd,&ofdbuf);
        }

    close(fd);
    flush();
}

/* convert one char from buffer to another char (04/10/90 stp) */
convert_buf(p,s,f,t)
register char *p; /* *buffer */
register int s;   /* size of buffer */
register f,t;     /* from, to */
{
  while (s--) {
    if (*p == f)
      *p = t;
    p++;
  }
}

copy(file, from, to, bytes)
char *file;
int from, to;
register long bytes;
{
  register int rest,cnt;
  int blocks,blks_fl = (int) ((bytes + BLOCK_SIZE - 1) / BLOCK_SIZE);

  if (verbose_fl) {
     if (to == tar_fd) {
#ifdef OSK
        printf("a %s, ",file); fflush(stdout);
#else
        sprintf(stbuf,"a %s, %d tape blocks\r\l",file,blks_fl);
#endif
     }
     else {
#ifdef OSK
        printf("x %s, %ld bytes, ",file,bytes); fflush(stdout);
     }
#else
        sprintf(stbuf,"x %s, %ld bytes, %d tape blocks\r\l",file,bytes,blks_fl);     }
     print(stbuf);
     flush();
#endif
  }

  blocks = (int) ((bytes + bsize - 1) / bsize);

  while (blocks--) {
    cnt = rest = (bytes > bsize) ? bsize : (to == tar_fd ? (int) bytes :
                    ((bytes + BLOCK_SIZE - 1) / BLOCK_SIZE)*BLOCK_SIZE);
    read(from, io_buffer, rest);
    rest = (bytes > bsize) ? bsize : (to == tar_fd ? ((bytes + BLOCK_SIZE - 1) / BLOCK_SIZE)*BLOCK_SIZE :
                                      bytes);
    if (convert_fl)
      if (to == tar_fd) {
        convert_buf(io_buffer,rest,0x0d,0x0a);
      }
      else {
        convert_buf(io_buffer,rest,0x0a,0x0d);
      }
    mwrite(to, io_buffer, rest,0);
    bytes -= (bytes > bsize ? bsize : bytes);
  }
#ifdef OSK
  if (verbose_fl)
    printf("%d tape blocks\n",blks_fl);
#endif
}

long convert(str, type)
char str[];
int type;
{
  register long ac = 0L;
  register int i;

  for (i = 0; i < type; i++) {
    if (str[i] >= '0' && str[i] <= '7') {
        ac <<= 3;
        ac += (long) (str[i] - '0');
    }
  }

  return ac;
}

mkdir(dir_name)
char *dir_name;
{
  if (mknod(dir_name,3) < 0) {
     return(0);
  }
  else {
    int fd;
    if ((fd = open(dir_name,0x83)) > 0) {
        _ss_attr(fd, S_IFDIR | u29mode((int) convert(header.member.m_mode, INT_TYPE)));
        close(fd);
    }
  }
}

checksum()
{
  register char *ptr = header.member.m_checksum;
  register int ac = 0;

  while (ptr < &header.member.m_checksum[INT_TYPE])
    *ptr++ = ' ';

  ptr = header.hdr_block;
  while (ptr < &header.hdr_block[BLOCK_SIZE])
    ac += *ptr++;

  return ac;
}

is_dir(file)
register char *file;
{
  while (*file++ != '\0') ;
  return (*(file - 2) == '/');
}

char path[NAME_SIZE];

char pathname[NAME_SIZE];
char *path_name(file)
register char *file;
{

  string_print(pathname, "%s%s", path, file);
  return pathname;
}

add_path(name)
register char *name;
{
  register char *path_ptr = path;

  while (*path_ptr)
    path_ptr++;

  if (name == NIL_PTR) {
    while (*path_ptr-- != '/')
        ;
    while (*path_ptr != '/' && path_ptr != path)
        path_ptr--;
    if (*path_ptr == '/')
        path_ptr++;
    *path_ptr = '\0';
  }
  else {
    while (*name) {
        if (path_ptr == &path[NAME_SIZE])
            error("tar: Pathname too long", NIL_PTR);
        *path_ptr++ = *name++;
    }
    *path_ptr++ = '/';
    *path_ptr = '\0';
  }
}
add_file(file)
register char *file;
{
  struct fildes st;
  struct dirent dir;
  register int fd;
#ifdef OSK
  unsigned long siz;
  u_char *sip = (u_char*) st.fd_fsize;
#endif

  if ((fd = open(file,0x81)) < 0)
     if ((fd = open(file, 1)) < 0) {
#ifdef OSK
        fprintf(stderr, "Cannot open '%s' ", file); fflush(stderr);
        prerr(0,errno);
#else
        string_print(NIL_PTR, "Cannot open %s\r\l", file);
#endif
        return(0);
     }
  if (_gs_gfd(fd,&st,sizeof(st)) < 0) {
#ifdef OSK
     fprintf(stderr, "Cannot get file descriptor for %s",file); fflush(stderr);
     prerr(0,errno);
#else
     string_print(NIL_PTR, "Cannot get file descriptor for %s\r\l",file);
#endif
     close(fd);
     return(0);
  }
  siz = (((((sip[0] << 8) + sip[1]) << 8) + sip[2]) << 8) + sip[3];

  make_header(path_name(file), &st);
  mwrite(tar_fd, &header, sizeof(header),0);
  if (!(st.fd_att & S_IFDIR))
#ifdef OSK
     copy(path_name(file), fd, tar_fd, siz);
#else
     copy(path_name(file), fd, tar_fd, st.fd_fsize);
#endif
  else if (st.fd_att & S_IFDIR) {
    if (chdir(file) < 0)
        string_print(NIL_PTR, "Cannot chdir to %s\n", file);
    else {
        add_path(file);
        mread(fd, &dir, sizeof(dir));       /* "." */
        mread(fd, &dir, sizeof(dir));       /* ".." */
        while (read(fd, &dir, sizeof(dir)) == sizeof(dir)) {
#ifdef OSK
            if (dir.dir_addr) {
#else
            if (dir.dir_addr[0] || dir.dir_addr[1] || dir.dir_addr[2]) {
#endif
                strhcpy(dir.dir_name,dir.dir_name);
                if (*dir.dir_name)
                  add_file(dir.dir_name);
            }
        }
        chdir("..");
        add_path(NIL_PTR);
    }
  }
  else
#ifdef OSK
    _errmsg("unknown file type. Not added. ",0);
#else
    print("tar: unknown file type. Not added.\r\l");
#endif

  close(fd);
}

make_header(file, st)
char *file;
register struct fildes *st;
{
  register char *ptr = header.member.m_name;
  char tbuf[6];
#ifdef OSK
  u_char *sip = (u_char*) st->fd_fsize;
  unsigned long siz = (((((sip[0] << 8) + sip[1]) << 8) +
                         sip[2]) << 8) + sip[3];
  u_char *owp = (u_char*) st->fd_own;
  unsigned short own = owp[1];
  unsigned short group = owp[0];
#endif

  clear_header();

  while (*ptr++ = *file++)
    ;

  if (st->fd_att &  S_IFDIR) {
    *(ptr - 1) = '/';
#ifdef OSK
    siz = 0;
#else
    st->fd_fsize = 0L;
#endif
  }

  _strass(tbuf,st->fd_date,5);
  tbuf[5] = 0;
  string_print(header.member.m_mode, "%I ", o2umode(st->fd_att));
#ifdef OSK
  string_print(header.member.m_uid,  "%I ", own);
  string_print(header.member.m_gid,  "%I ", group);
  string_print(header.member.m_size, "%L ", siz);
#else
  string_print(header.member.m_uid,  "%I ", st->fd_own);
  string_print(header.member.m_gid,  "%I ", 101);
  string_print(header.member.m_size, "%L ", st->fd_fsize);
#endif
  string_print(header.member.m_time, "%L ", o2utime(tbuf));
  header.member.m_linked = ' ';
  string_print(header.member.m_checksum, "%I", checksum());
}

clear_header()
{
  register char *ptr = header.hdr_block;

  while (ptr < &header.hdr_block[BLOCK_SIZE])
    *ptr++ = '\0';
}

adjust_boundary()
{
  clear_header();
  mwrite(tar_fd, &header, sizeof(header),1);
  while (total_blocks++ < BLOCK_BOUNDARY)
    mwrite(tar_fd, &header, sizeof(header),1);
  close(tar_fd);
}

mread(fd, address, bytes)
int fd, bytes;
char *address;
{
register int r;

  if ((r = read(fd, address, bytes)) != bytes) {
#ifdef OSK
    if (r == 0)
      errno = E_EOF;
    exit(_errmsg(errno,"read error. "));
#else
    error("tar: read error.", NIL_PTR);
#endif
  }
}

mwrite(fd, address, bytes, flag)
int fd, bytes;
char *address;
short flag;
{
static char *bptr;
static int fill = 0;

  if (fd == tar_fd) {
    if (fill == 0)
      bptr = w_buffer;
    while (fill + bytes > bsize) {
      _strass(bptr,address,bsize-fill);
      write(fd,w_buffer,bsize);
      address += (bsize-fill);
      bytes -= (bsize-fill);
      fill = 0;
      bptr = w_buffer;
    }
    _strass(bptr,address,bytes);
    bptr += bytes;
    fill += bytes;
    if (flag) {
      write(fd,w_buffer,fill);
      fill = 0;
    }
  }
  else {
    if (write(fd, address, bytes) != bytes) {
#ifdef OSK
      exit(_errmsg(errno,"write error. "));
#else
      error("tar: write error.", NIL_PTR);
#endif
    }
  }
  total_blocks++;
}

char output[BLOCK_SIZE];
print(str)
register char *str;
{
  static int index = 0;

  if (str == NIL_PTR) {
    write(2, output, index);
    index = 0;
    return(0);
  }

  while (*str) {
    output[index++] = *str++;
    if (index == BLOCK_SIZE) {
        write(2, output, BLOCK_SIZE);
        index = 0;
    }
  }
}

char *num_out(number)
register long number;
{
  static char num_buf[13];
  char temp[13];
  register int i;

  for (i = 0; i < 11; i++) {
    temp[i] = (number & 07) + '0';
    number >>= 3;
  }

  for (i = 0; i < 11; i++)
    num_buf[i] = temp[10 - i];

  return num_buf;
}

/* VARARGS */
string_print(buffer, fmt, args)
char *buffer;
register char *fmt;
int args;
{
  register char *buf_ptr;
  char *scan_ptr;
  char buf[NAME_SIZE];
  int *argptr = &args;
  BOOL pr_fl, i;

  if ((pr_fl = (buffer == NIL_PTR)))
    buffer = buf;

  buf_ptr = buffer;
  while (*fmt) {
    if (*fmt == '%') {
        fmt++;
        switch (*fmt++) {
            case 's':
                scan_ptr = (char *) *argptr;
                break;
            case 'I':
                scan_ptr = num_out((long) *argptr) + 5;
/*                for (i = 0; i < 5; i++)
                    scan_ptr++; */
                break;
            case 'L':
                scan_ptr = num_out(*((long *) argptr));
                argptr++;
                break;
            default:
                scan_ptr = "";
        }
        while (*buf_ptr++ = *scan_ptr++)
            ;
        buf_ptr--;
        argptr++;
    }
    else
        *buf_ptr++ = *fmt++;
  }
  *buf_ptr = '\0';

  if (pr_fl)
    print(buffer);
}

 o2umode(mode)
    char mode;
{
    int ret_mode=0;

    if (mode & S_IFDIR)
        ret_mode |= 040000;
    if (mode & S_IREAD)
        ret_mode |= 0400;
    if (mode & S_IWRITE)
        ret_mode |= 0200;
    if (mode & S_IEXEC)
        ret_mode |= 0100;
    if (mode & S_IOREAD)
        ret_mode |= 04;
    if (mode & S_IOWRITE)
        ret_mode |= 02;
    if (mode & S_IOEXEC)
        ret_mode |= 01;

    return(ret_mode);
}

 u29mode(mode)
    int mode;
{
    int ret_mode=0;

    strcpy(modestr,"-----------");

    if (mode & 040000) {
        ret_mode  |= S_IFDIR;
        modestr[0] = 'd';
    }

    if (mode & 0400) {
        ret_mode  |= S_IREAD;
        modestr[1] = 'r';
    }

    if (mode & 0200) {
        ret_mode  |= S_IWRITE;
        modestr[2] = 'w';
    }

    if (mode & 0100) {
        ret_mode  |= S_IEXEC;
        modestr[3] = 'x';
    }

    if (mode & 04) {
        ret_mode  |= S_IOREAD;
        modestr[7] = 'r';
    }
    if (mode & 02) {
        ret_mode  |= S_IOWRITE;
        modestr[8] = 'w';
    }

    if (mode & 01) {
        ret_mode  |= S_IOEXEC;
        modestr[9] = 'x';
    }

    return(ret_mode);
}

char *atime(clock)
    long clock;
{
    static char buf[26];
    int i;

    strcpy(buf,ctime(&clock));

    for (i=4; i< 16; i++)
        buf[i-4] = buf[i];
    buf[12] = ' ';

    for (i=20; i<24; i++)
        buf[i-7] = buf[i];
    buf[17] = 0;

    return(buf);
}

char **match(fpt,name)
    register struct files *fpt;
    register char *name;
{

  while (fpt->name) {
#ifdef OSK
    if (!_cmpnam(name,fpt->name,strlen(fpt->name))) {
#else
    if (patmatch(fpt->name,name,1)) {
#endif
       return(fpt->name);
    }
    fpt = fpt->next;
  }
  return(NULL);
}

#ifdef OSK
u2otime(om,um)
struct sgtbuf *om;
struct tm     *um;
{
  om->t_year = um->tm_year;
  om->t_month = um->tm_mon+1;
  om->t_day = um->tm_mday;
  om->t_hour = um->tm_hour;
  om->t_minute = um->tm_min;
  om->t_second = um->tm_sec;
}

long o2utime(om)
struct sgtbuf *om;
{
struct tm um;

  um.tm_year = om->t_year;
  um.tm_mon = om->t_month-1;
  um.tm_mday = om->t_day;
  um.tm_hour = om->t_hour;
  um.tm_min = om->t_minute;
  um.tm_sec = om->t_second;
  return mktime(&um);
}
#endif

jeffr@bcs800.UUCP (Jeff Riegel) (09/27/90)

In <kazu22S00WB389MkU0@andrew.cmu.edu> rh2y+@andrew.cmu.edu (Russell E. Hoffman, II) writes:

>Here is the source for the un*x 'tar' utility. I'd have posted executable
>as well, but then I'd have had to at least uuencode it, and judging by the fact
>that the requestor does not have tar, he is not likely to have uudecode either,
>so I thought I'd play it safe!


Correct assumption... Thanks for the source

Jeffr

krischan@strange.informatik.rwth-aachen.de (Christian Engel) (09/28/90)

STOP!!!

rh2y+@andrew.cmu.edu (Russell E. Hoffman, II) writes:
> Here is the source for the un*x 'tar' utility. [...]

I've got the same tar but I've improved it at several points.
The source is modified that much that there is no sense of
a diff for patching. Here is the complete tar V1.9 again:

---- Cut here ---- Cut here ---- Cut here ---- Cut here ---- Cut here ----
#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create:
#	tar.c
#	makefile
#	tar.doc
#	tar.hlp
# This archive created: Fri Sep 28 10:55:09 1990
export PATH; PATH=/bin:/usr/bin:$PATH
echo shar: "extracting 'tar.c'" '(28037 characters)'
cat << \SHAR_EOF > 'tar.c'
char scid[]="$Header: tar.c,v 1.9  04/13/90 SrT/stp $";

/*
 * Modified for use under OS-9/68k  (08/03/89)
 * Stephan Paschedag  stp@ethz.uucp   ..!mcvax!cernvax!chx400!ethz!stp
 *                    paschedag@strati.ethz.ch
 *
 * Compilation for OS-9/68k:  cc -qi tar.c
 *
 * 07/30/90  V1.9  Revised the source code completely and made it more readable.
 *                 Added -z option for reading file names from file or stdin.
 *                 Placed versatile write error messages (tar kept silent
 *                 though writing on a full device!).
 *                 (Christian Engel, krischan@strange.informatik.rwth-aachen.de)
 *
 * 04/13/90  V1.8  problems in extraction of subdirectories fixed
 *                 change invalid filenames
 *                 LF <-> CR conversion added (u modifier) (stp)
 *
 * 01/28/90  V1.7  multi-file buffer implemented (stp)
 *
 * 01/16/90  V1.6  Copy buffer implemeted, results in much faster
 *                 (re-)storing times (stp)
 *
 * 08/06/89  V1.5  Allow extraction of entire directories,
 *                 allow extraction of single files in subdirectories,
 *                 creates automatically missing directories.
 *                 continues now correctly after some errors
 *                 handles directories from unix archives correctly (stp)
 *
 * Modified for use under OS-9/6809
 * Simmule Turner   simmy@nu.cs.fsu.edu   70651,67
 *
 * Compilation for OS-9/6809:  cc -m=3k tar.c
 *     Use extra memory for recursive directory descents.
 *
 * 07/17/88  V1.4  Allow extraction of specified file(s)....
 *                 from archive named on the command line
 *                 with wildcard matching *,?. (SrT)
 *
 * 07/14/88  V1.3  Added verbose TOC ala unix.  Cleaned up
 *                 printing routines, use decimal instead of
 *                 octal. Removed conditional compilation. (SrT)
 *
 * 07/13/88  V1.2  Tries to set the correct file permissions.
 *                 Send diagnostics to stderr.  Use lseek's
 *                 for TOC generation when a rbf device. (SrT)
 *
 * 07/12/88  V1.1  Added option of reading/writing
 *                 archive to stdin/stdout (SrT)
 *
 * 07/12/88  V1.0  Initial port (SrT)
 *                 added dummy fv options
 * SrT */

/* tar - tape archiver          Author: Michiel Huisjes */

/* Usage: tar [cxt][bdfmuvz] tapefile [files]
 *
 * Bugs:
 *  This tape archiver should only be used as a program to read or make
 *  simple tape archives. Its basic goal is to read (or build) UNIX V7 tape
 *  archives and extract the named files. It is not wise to use it on
 *  raw magnetic tapes. It doesn't know anything about linked files,
 *  except when the involved fields are filled in.
 *
 * Comment : It works fine on raw magnetic tapes ! (tar tv /mt0 .)
 */

#include <stdio.h>
#include <direct.h>
#include <modes.h>
#include <sgstat.h>
#include <time.h>
#include <ctype.h>
#ifndef OSK
#  include <utime.h>
#else
#  include <types.h>
#  include <errno.h>
#  include <strings.h>
#endif

#define STDIN   0
#define STDOUT  1

typedef char BOOL;
#define TRUE    1
#define FALSE   0

#define HEADER_SIZE     512
#define NAME_SIZE       100
#define BLOCK_BOUNDARY   20

typedef union {
	char hdr_block[HEADER_SIZE];
	struct m {
		char m_name[NAME_SIZE];
		char m_mode[8];
		char m_uid[8];
		char m_gid[8];
		char m_size[12];
		char m_time[12];
		char m_checksum[8];
		char m_linked;
		char m_link[NAME_SIZE];
	} member;
} HEADER;

/* file match structure (04/13/90 stp) */
struct files {
	char *name;
	struct files *next;
};

HEADER header;

#define INT_TYPE    (sizeof(header.member.m_uid))
#define LONG_TYPE   (sizeof(header.member.m_size))

#define NIL_HEADER  ((HEADER *) 0)
#define NIL_PTR     ((char *) 0)
#define BLOCK_SIZE  512

#define flush()     print(NIL_PTR)

extern char *malloc (), *realloc ();
#ifdef OSK
extern char *_prgname ();
extern char *index ();
#else
#define _prgname()	prgname
char *prgname;
#define index strchr
#endif

char *getname ();

BOOL show_fl,		/* t option: list contents of archive */
     creat_fl,      /* c option: create archive */
     ext_fl,        /* x option: extract form archive */
     verbose_fl,    /* v modifier: verbose all actions */
     modtime_fl,    /* m modifier: do not restore date */
     convert_fl,    /* u modifier: convert LF <-> CR */
     disk_fl;       /* d modifier: backup to disks */

char *listfilename = NULL;	/* z modifier: read file names from file */

char modestr[11], stbuf[132];
struct sgbuf opts;
struct fildes ofdbuf;
#ifdef OSK
char *dummy = NULL;
#endif

int tar_fd;
char *tarfilename;

int Argc;
char **Argv;



/* set blocksize  (01/16/90 stp) */
BOOL blks_fl;
int blocking = 1;
char *io_buffer;
char *w_buffer;
long bsize;

int total_blocks;
long convert();
char **match();

block_size() {
    return ((int) ((convert(header.member.m_size, LONG_TYPE)
					+ (long) BLOCK_SIZE - 1) / (long) BLOCK_SIZE));
}

error(s1, s2)
char *s1, *s2;
{
#ifdef OSK
	fprintf(stderr,"%s %s\n", s1, s2 ? s2 : "");
	exit(1);
#else
	string_print(NIL_PTR, "%s %s\r\l", s1, s2 ? s2 : "");
	flush();
	exit(1);
#endif
}


/* VARARGS2 */
usage(exitcode, format, p1, p2)
	int exitcode;
	char *format, *p1, *p2;
{
	static char *text [] = {
	"Syntax : %s [ctxb][mfvu] [blocks] tarfile [file(s)...]\n",
	"Function: Builds and extracts from Unix tape archive files",
	"          if tarfile is `-' stdin/stdout will be used",
	"Options:                    Modifiers:",
	"   c : create archive          b : set copy buffer size",
	"   t : list contents           d : backup floppy disks",
	"   x : extract from archive    m : do not restore date",
	"                               u : convert LF <-> CR",
	"                               v : verbose mode",
	"                               z : read file names from named file, file",
	"                                   name - means stdin",
	0 };
	char **pp;

	if (format) {
		fprintf (stderr, format);
		putc ('\n', stderr);
	}
	fprintf (stderr, text [0], _prgname ());
	putc ('\n', stderr);
	for (pp = text; *pp; pp++)
		fprintf (stderr, "%s\n", *pp);
	exit(exitcode);
}



main(argc, argv)
	int argc;
	register char *argv[];
{
    register char *ptr;
    int i, argind;
	char *fname;

#ifndef OSK
	prgname = argv [0];
#endif
	
	pflinit();
	
	if (argc < 3)
		usage(1, "Too few arguments");
	
	argind = 2;
	ptr = argv[1];
#ifdef F_MODIFIER_ACTIVE
	tarfilename = NULL;
#else
	tarfilename = argv [argind++];
#endif

	while (*ptr) {
		switch (*ptr++) {
		case 'c' :
			creat_fl = TRUE;
			break;
		case 't' :
			show_fl = TRUE;
			break;
		case 'x' :
			ext_fl  = TRUE;
			break;
		/*
		 ** Scan Modifiers
		 */
        case 'b' :	/* set blocksize  (01/16/90 stp) */
        	if (argind >= argc)
        		usage (1, "No argument for b modifier");
            blks_fl = TRUE;
			if (sscanf(argv[argind],"%d",&blocking) == -1)
				usage (1, "%s: bad parameter for blocking", argv[argind]);
			blocking = (blocking+1)>>1;
			argind++;
            break;
        case 'd' :	/* muliple disk backup (04/14/90 stp) */
            disk_fl = TRUE;
            break;
        case 'f':
#ifdef F_MODIFIER_ACTIVE
			if (tarfilename != NULL)
				usage (1, "Just one tar file!");
        	if (argind >= argc)
        		usage (1, "No argument for f modifier");
			tarfilename = argv [argind++];
#endif
            break;
        case 'm':
            modtime_fl = TRUE;
            break;
        case 'v':
            verbose_fl = TRUE;
            break;
        case 'u' :	/* convert flag (04/10/90 stp) */
            convert_fl = TRUE;
            break;
        case 'z':	/* file list input flag (07/28/90 ce) */
        	if (argind >= argc)
        		usage (1, "No argument for z modifier");
			listfilename = argv [argind++];
			break;
		case '-' :
			if (ptr == argv [1] + 1)	/* leading '-' */
				continue;
		case '?' :
			usage (0, NULL);
		default :
			ptr--;
			usage (1, "Unknown %s '%c'",
						ptr == argv [1] ? "option" : "modifier", *ptr);
		}
	}

#ifdef F_MODIFIER_ACTIVE
	if (tarfilename == NULL)
		usage (1, "No tar file specified. Use f modifier");
#endif

	if (creat_fl + show_fl + ext_fl != 1)
		usage(1, "Use one of the c, x, and t options");

	if (strcmp(listfilename, "-")==0 && strcmp(tarfilename, "-")==0 && ext_fl)
		usage (1, "tar file and list file for z modifier are both stdin");

	if ((io_buffer = malloc(BLOCK_SIZE*blocking)) == NULL)
		error("allocation of io_buffer failed","");
	if ((w_buffer = malloc(BLOCK_SIZE*blocking)) == NULL)
		error("allocation of w_buffer failed","");
	bsize = (long) BLOCK_SIZE*blocking;

	if (strcmp (tarfilename, "-") == 0)
		tar_fd = creat_fl ? STDOUT : STDIN;
	else
		tar_fd = creat_fl ? creat(tarfilename,3) : open(tarfilename, 1);
	
	if (tar_fd < 0) {
#ifdef OSK
		exit (_errmsg (errno, "cannot open archive '%s'", tarfilename));
#else
		error ("Cannot open archive", tarfilename);
#endif
	}

	Argc = argc;
	Argv = argv;
	if (creat_fl) {
		do {
			if (getname (argind) != NULL)
				exit (99);			/* fatal error!!! */
			while ((fname = getname (0)) != NULL)
				add_file (fname);
			if (disk_fl) {
				do {
					printf ("save another disk ?"); fflush (stdout);
					i = toupper (getchar ());
				} while ((i != 'Y') && (i != 'N'));
				disk_fl = (i == 'Y');
			}
		} while (disk_fl);
		adjust_boundary ();
	}
	else
		tarfile (argind);
	
	flush ();
	exit (0);
}


/*
** read a header from the tar file
*/
BOOL get_header ()
{
	register int check,c;
	
	mread (tar_fd, &header, sizeof (header));
	if (header.member.m_name[0] == '\0')
		return FALSE;
	
	check = (int) convert (header.member.m_checksum, INT_TYPE);
	
	if (check != (c = checksum ()))
		error ("tar: header checksum error.", NIL_PTR);
	
	return TRUE;
}

tarfile (argind)
	int argind;
{
	register char *ptr;
	register char *mem_name;
	char *atime ();
	int i;
	char *fname;
	register char *pp;
	struct files f0, *fptr=&f0;
	
	/* create 'files' structure from file list */
	if (getname (argind) != NULL)
		exit (99);		/* fatal error */
	while ((fname = getname (0)) != NULL) {
		fptr->name = (char *) malloc(strlen(fname));
		strcpy(fptr->name,fname);
		fptr->next = (struct files*) malloc(sizeof(struct files));
		fptr = fptr->next;
	}
	fptr->name = NULL;
	
	_gs_opt(tar_fd, &opts);
	
	while (get_header ()) {
		mem_name = header.member.m_name;
		if (ext_fl) {
			if (f0.name) {
				/* extract entire directories (08/05/89 stp) */
				if (mem_name[strlen(mem_name)-1] == '/') {
					mem_name[strlen(mem_name)-1] = '\0';
					if (match (&f0,mem_name)) {
						fptr->name = (char *) malloc(strlen(mem_name) + 3);
						strcpy(fptr->name,mem_name);
						strncat(fptr->name,"/*",3);
						fptr->next =
							(struct files*) malloc(sizeof(struct files));
						fptr = fptr->next;
						fptr->name = 0;
						check_name(mem_name);
						mkdir(mem_name);
					}
/*					else {
						q = q;		---> Hmmm?!?!? (ce)
					}*/
				}
				else {
					if (match(&f0, mem_name)) {
						extract(mem_name);
					}
					else
						skip_entry ();
				}
			}
			else {
				if (is_dir(mem_name)) {
					for (ptr = mem_name; *ptr != '/'; ptr++);
					*ptr = '\0';
					check_name(mem_name);
					mkdir(mem_name);
				}
				else {
					extract(mem_name);
				}
			}
		}
		else  {
			if (!verbose_fl)
#ifdef OSK
				printf("%s",mem_name);
#else
        	    string_print(NIL_PTR, "%s", mem_name);
#endif
        	else {
            	u29mode((int) convert(header.member.m_mode,INT_TYPE));
#ifdef OSK
	            printf("%s ",modestr);
#else
    	        string_print(NIL_PTR, "%s ",modestr);
#endif
        	    sprintf(stbuf,"%3d/%3d %8ld %s %s",
						(int) convert(header.member.m_uid, INT_TYPE),
						(int) convert(header.member.m_gid, INT_TYPE),
						convert(header.member.m_size,LONG_TYPE),
						atime(convert(header.member.m_time,LONG_TYPE)),
						header.member.m_name);
#ifdef OSK
				printf("%s",stbuf);
			}
			printf("\n");
#else
	            print(stbuf);
        	}
	        print("\r\l");
#endif
        	skip_entry();
    	}
	    flush();
	}
}


#define NCHUNK		128

/*
** This function catches arguments as file names from the list file (if any)
** and from the command line.
** If 0 is passed by <argind> the next file name will be fetched. If
** the global variable <listfilename> isn't NULL this name will be read
** from the listfile. If EOF is reached or if there isn't any listfile
** the next name will be fetched from the command line. In this case and
** if the last command line argument has been reached already NULL
** will be returned to indicate the end of the file name list. 
** If a value not equal 0 is passed by <argind> the getname() initializes
** anything. The list file will be opened (if any) or rewound. The
** index into the command line arguments <fnameind> pointing to
** the next command line argument to be passed will be reset to <argind>.
*/
char *getname (argind)
	int argind;
{
	static char *buf = NULL;
	static int buflen = 0;
	static int fnameind;
	register int c;
	register char *p;
	static FILE *file = NULL;

	if (argind) {						/* initialize! */
		if (listfilename != NULL) {		/* z option is given */
			if (file == NULL) {			/* list file hasn't been opened */
				if (strcmp (listfilename, "-") == 0)
					file = stdin;
				else if ((file = fopen (listfilename, "r")) == NULL)
#ifdef OSK
					exit (_errmsg (errno, "can't open list file '%s'\n",
								   listfilename));
#else
					error ("Can't open list file ", listfilename);
#endif
				buflen = NCHUNK;
				if ((buf = malloc (buflen)) == NULL)
#ifdef OSK
					exit(_errmsg(errno, "Can't allocate file name buffer\n"));
#else
					error("Can't allocate file name buffer\n", NIL_PTR);
#endif
			}
			else
				rewind (file);
		}
		fnameind = argind;
		return (NULL);
	}

	if (file != NULL && !feof (file)) {
		p = buf;
		/* now we try to get the next name from the list file */
		{						/* skip white space */
			c = getc (file);
		} while (isspace (c));
		while (c != EOF && !isspace (c)) {	/* get name */
			if (p == buf + buflen - 2) {	/* buffer exhausted */
				buflen += NCHUNK;			/* enlage chunk */
				if ((buf = realloc (buf, buflen)) == NULL)
#ifdef OSK
					exit (_errmsg (errno,
								   "Can't reallocate file name buffer\n"));
#else
					error ("Can't reallocate file name buffer\n", NIL_PTR);
#endif
				p = buf + buflen - 2 - NCHUNK;
			}
			*p++ = c;
			c = getc (file);
		}
		if (c != EOF || p > buf) {
			*p = '\0';
			return (buf);
		}
	}
	/* there was no list file (no z option) or the file is exhausted
	   => continue with catching file names from the command line */
	if (fnameind >= Argc)	/*there aren't any names left in the command line*/
		return (NULL);
	return (Argv [fnameind++]);
}



/*
** skip the next entry in the tar file
*/
skip_entry()
{
	register int blocks = block_size();
	
    if (opts.sg_class == 1) {
        long pos;
		
        pos = ((long) blocks) * BLOCK_SIZE;
        lseek(tar_fd,pos,1);
    }
    else {
        while (blocks--)
            read(tar_fd, io_buffer, BLOCK_SIZE);
    }
}

/*
** convert to valid filename
*/
check_name(c)
	register char *c;
{
	while (*c) {
		if (!(isalnum(*c) || *c == '_' || *c == '$' || *c == '.' || *c == '/'))
			*c = '_';
		c++;
	}
}

extract(file)
	register char *file;
{
	register int fd;
	
	if (header.member.m_linked == '1') {
#ifdef OSK
		fprintf(stderr,"Cannot link %s (hard links not supported)\n",
				header.member.m_link);
#else
		string_print(NIL_PTR,"Cannot link %s\r\l",header.member.m_link);
#endif
		skip_entry();
		return(0);
	}
	
	check_name(file);
	if ((fd = creat(file, 3)) < 0) {
		/* create missing directories (08/06/89) stp */
		register char *s,*pp;
		pp = file;
		while (s = index(pp,'/')) {
			pp = (char *) malloc(s-file+1);
			strncpy(pp,file,s-file);
			pp[s-file] = '\0';
			mkdir(pp);
			free(pp);
			pp = s+1;
		}
		if ((fd = creat(file, 3)) < 0) {
#ifdef OSK
			fprintf(stderr, "Cannot create %s : ", file); fflush(stderr);
			prerr(0,errno);
#else
			string_print(NIL_PTR, "Cannot create %s\r\l", file);
#endif
			skip_entry();
			return(0);
		}
	}
	
	copy(file, tar_fd, fd, convert(header.member.m_size, LONG_TYPE));
	
	_ss_attr(fd, u29mode((int) convert(header.member.m_mode, INT_TYPE)));
	
	if (!modtime_fl)
#ifdef OSK
		if (_gs_gfd(fd,&ofdbuf,sizeof(ofdbuf)) != -1)
#else
		if (_gs_gfd(fd,&ofdbuf,sizeof(ofdbuf)) != ERROR)
#endif
		{			
			struct tm     *utm;
			struct sgtbuf otm;
			long clock;
			
			clock = convert(header.member.m_time,LONG_TYPE);
			utm = localtime(&clock);
			u2otime(&otm,utm);
			_strass(&ofdbuf.fd_date[0], &otm, 5);
			_ss_pfd(fd,&ofdbuf);
		}
	
	close(fd);
	flush();
}

/* convert one char from buffer to another char (04/10/90 stp) */
convert_buf(p,s,f,t)
	register char *p; /* *buffer */
	register int s;   /* size of buffer */
	register f,t;     /* from, to */
{
	while (s--) {
		if (*p == f)
			*p = t;
		p++;
	}
}


/*
** copy <bytes> bytes from the file with path descriptor <from> to the
** file with path descriptor <to>
** upon verbose use the string pointed to by <file> as the file name
*/
copy(file, from, to, bytes)
	char *file;
	int from, to;
	register long bytes;
{
	register int rest,cnt;
	int blocks,blks_fl = (int) ((bytes + BLOCK_SIZE - 1) / BLOCK_SIZE);
	
	if (verbose_fl) {
		if (to == tar_fd) {
#ifdef OSK
			printf("a %s, ",file); fflush(stdout);
#else
			sprintf(stbuf,"a %s, %d tape blocks\r\l",file,blks_fl);
#endif
		}
		else
#ifdef OSK
			printf("x %s, %ld bytes, ",file,bytes); fflush(stdout);
#else
	        sprintf(stbuf,"x %s, %ld bytes, %d tape blocks\r\l",
					file,bytes,blks_fl);
		print(stbuf);
		flush();
#endif
	}

	blocks = (int) ((bytes + bsize - 1) / bsize);

	while (blocks--) {
		cnt = rest = (bytes > bsize) ?
						bsize :
						(to == tar_fd ?
							(int) bytes :
						 	((bytes+BLOCK_SIZE-1) / BLOCK_SIZE)*BLOCK_SIZE);
		read(from, io_buffer, rest);
		rest = (bytes > bsize) ?
					bsize :
					(to == tar_fd ?
						((bytes + BLOCK_SIZE - 1) / BLOCK_SIZE)*BLOCK_SIZE :
						bytes);
		if (convert_fl)
			if (to == tar_fd) {
				convert_buf(io_buffer,rest,0x0d,0x0a);
			}
			else {
				convert_buf(io_buffer,rest,0x0a,0x0d);
			}
		mwrite(to, io_buffer, rest,0);
		bytes -= (bytes > bsize ? bsize : bytes);
	}
#ifdef OSK
	if (verbose_fl)
		printf("%d tape blocks\n",blks_fl);
#endif
}

long convert(str, type)
	char str[];
	int type;
{
	register long ac = 0L;
	register int i;
	
	for (i = 0; i < type; i++) {
		if (str[i] >= '0' && str[i] <= '7') {
			ac <<= 3;
			ac += (long) (str[i] - '0');
		}
	}
	
	return ac;
}

mkdir(dir_name)
	char *dir_name;
{
	if (mknod(dir_name,3) < 0) {
		return(0);
	}
	else {
		int fd;
		if ((fd = open(dir_name,0x83)) > 0) {
			_ss_attr(fd, S_IFDIR | u29mode((int) convert(header.member.m_mode,
														 INT_TYPE)));
			close(fd);
		}
	}
}

checksum()
{
	register char *ptr = header.member.m_checksum;
	register int ac = 0;
	
	while (ptr < &header.member.m_checksum[INT_TYPE])
		*ptr++ = ' ';
	
	ptr = header.hdr_block;
	while (ptr < &header.hdr_block[BLOCK_SIZE])
		ac += *ptr++;
	
	return ac;
}

is_dir(file)
	register char *file;
{
	while (*file++ != '\0') ;
	return (*(file - 2) == '/');
}

char path[NAME_SIZE];

char pathname[NAME_SIZE];
char *path_name(file)
	register char *file;
{

	string_print(pathname, "%s%s", path, file);
	return pathname;
}

add_path(name)
	register char *name;
{
	register char *path_ptr = path;
	
	while (*path_ptr)
		path_ptr++;
	
	if (name == NIL_PTR) {
		while (*path_ptr-- != '/')
			;
		while (*path_ptr != '/' && path_ptr != path)
			path_ptr--;
		if (*path_ptr == '/')
			path_ptr++;
		*path_ptr = '\0';
	}
	else {
		while (*name) {
			if (path_ptr == &path[NAME_SIZE])
				error("tar: Pathname too long", NIL_PTR);
			*path_ptr++ = *name++;
		}
		*path_ptr++ = '/';
		*path_ptr = '\0';
	}
}

add_file(file)
	register char *file;
{
	struct fildes st;
	struct dirent dir;
	register int fd;
#ifdef OSK
	unsigned long siz;
	u_char *sip = (u_char*) st.fd_fsize;
#endif

	if ((fd = open(file,0x81)) < 0)
		if ((fd = open(file, 1)) < 0) {
#ifdef OSK
			fprintf(stderr, "Cannot open '%s' ", file); fflush(stderr);
			prerr(0,errno);
#else
			string_print(NIL_PTR, "Cannot open %s\r\l", file);
#endif
			return(0);
		}
	if (_gs_gfd(fd,&st,sizeof(st)) < 0) {
#ifdef OSK
		fprintf(stderr, "Cannot get file descriptor for %s",file);
		fflush(stderr);
		prerr(0,errno);
#else
		string_print(NIL_PTR, "Cannot get file descriptor for %s\r\l",file);
#endif
		close(fd);
		return(0);
	}
	siz = (((((sip[0] << 8) + sip[1]) << 8) + sip[2]) << 8) + sip[3];
	
	make_header(path_name(file), &st);
	mwrite(tar_fd, &header, sizeof(header),0);
	if (!(st.fd_att & S_IFDIR))
#ifdef OSK
		copy(path_name(file), fd, tar_fd, siz);
#else
		copy(path_name(file), fd, tar_fd, st.fd_fsize);
#endif
	else if (st.fd_att & S_IFDIR) {
		if (chdir(file) < 0)
			string_print(NIL_PTR, "Cannot chdir to %s\n", file);
		else {
			add_path(file);
			mread(fd, &dir, sizeof(dir));       /* "." */
			mread(fd, &dir, sizeof(dir));       /* ".." */
			while (read(fd, &dir, sizeof(dir)) == sizeof(dir)) {
#ifdef OSK
				if (dir.dir_addr)
#else
				if (dir.dir_addr[0] || dir.dir_addr[1] || dir.dir_addr[2])
#endif
				{
					strhcpy(dir.dir_name,dir.dir_name);
					if (*dir.dir_name)
						add_file(dir.dir_name);
				}
			}
			chdir("..");
			add_path(NIL_PTR);
		}
	}
	else
#ifdef OSK
		_errmsg("unknown file type. Not added. ",0);
#else
	    print("tar: unknown file type. Not added.\r\l");
#endif

	close(fd);
}

make_header(file, st)
	char *file;
	register struct fildes *st;
{
	register char *ptr = header.member.m_name;
	char tbuf[6];
#ifdef OSK
	u_char *sip = (u_char*) st->fd_fsize;
	unsigned long siz = (((((sip[0] << 8) + sip[1]) << 8) +
						  sip[2]) << 8) + sip[3];
	u_char *owp = (u_char*) st->fd_own;
	unsigned short own = owp[1];
	unsigned short group = owp[0];
#endif

	clear_header();
	
	while (*ptr++ = *file++)
		;
	
	if (st->fd_att &  S_IFDIR) {
		*(ptr - 1) = '/';
#ifdef OSK
		siz = 0;
#else
		st->fd_fsize = 0L;
#endif
	}
	
	_strass(tbuf,st->fd_date,5);
	tbuf[5] = 0;
	string_print(header.member.m_mode, "%I ", o2umode(st->fd_att));
#ifdef OSK
	string_print(header.member.m_uid,  "%I ", own);
	string_print(header.member.m_gid,  "%I ", group);
	string_print(header.member.m_size, "%L ", siz);
#else
	string_print(header.member.m_uid,  "%I ", st->fd_own);
	string_print(header.member.m_gid,  "%I ", 101);
	string_print(header.member.m_size, "%L ", st->fd_fsize);
#endif
	string_print(header.member.m_time, "%L ", o2utime(tbuf));
	header.member.m_linked = ' ';
	string_print(header.member.m_checksum, "%I", checksum());
}

clear_header()
{
	register char *ptr = header.hdr_block;
	
	while (ptr < &header.hdr_block[BLOCK_SIZE])
		*ptr++ = '\0';
}

adjust_boundary()
{
	clear_header();
	mwrite(tar_fd, &header, sizeof(header),1);
	
	while (total_blocks++ < BLOCK_BOUNDARY)
		mwrite(tar_fd, &header, sizeof(header),1);
	close(tar_fd);
}

mread(fd, address, bytes)
	int fd, bytes;
	char *address;
{
	register int r;
	
	if ((r = read(fd, address, bytes)) != bytes) {
#ifdef OSK
		if (r == 0)
			errno = E_EOF;
		exit(_errmsg(errno,"read error. "));
#else
		error("tar: read error.", NIL_PTR);
#endif
	}
}

mwrite(fd, address, bytes, flag)
	int fd, bytes;
	char *address;
	short flag;
{
	static char *bptr;
	static int fill = 0;
	
	if (fd == tar_fd) {
		if (fill == 0)
			bptr = w_buffer;
		while (fill + bytes > bsize) {
			_strass(bptr,address,bsize-fill);
			if (write(fd,w_buffer,bsize) != bsize) {
#ifdef OSK
				exit(_errmsg(errno,"write error. "));
#else
				error("tar: write error.", NIL_PTR);
#endif
			}
			address += (bsize-fill);
			bytes -= (bsize-fill);
			fill = 0;
			bptr = w_buffer;
		}
		_strass(bptr,address,bytes);
		bptr += bytes;
		fill += bytes;
		if (flag) {
			if (write(fd,w_buffer,fill) != fill)
#ifdef OSK
				exit(_errmsg(errno,"write error. "));
#else
			error("tar: write error.", NIL_PTR);
#endif
			fill = 0;
		}
	}
	else {
		if (write(fd, address, bytes) != bytes) {
#ifdef OSK
			exit(_errmsg(errno,"write error. "));
#else
			error("tar: write error.", NIL_PTR);
#endif
		}
	}
	total_blocks++;
}

char output[BLOCK_SIZE];
print(str)
	register char *str;
{
	static int ind = 0;
	
	if (str == NIL_PTR) {
		if (write(2, output, ind) != ind)
#ifdef OSK
			exit(_errmsg(errno,"write error. "));
#else
        error("tar: write error.", NIL_PTR);
#endif
		ind = 0;
		return(0);
	}
	
	while (*str) {
		output[ind++] = *str++;
		if (ind == BLOCK_SIZE) {
			if (write(2, output, BLOCK_SIZE) != BLOCK_SIZE)
#ifdef OSK
				exit(_errmsg(errno,"write error. "));
#else
				error("tar: write error.", NIL_PTR);
#endif
			ind = 0;
		}
	}
}

char *num_out(number)
	register long number;
{
	static char num_buf[13];
	char temp[13];
	register int i;
	
	for (i = 0; i < 11; i++) {
		temp[i] = (number & 07) + '0';
		number >>= 3;
	}
	
	for (i = 0; i < 11; i++)
		num_buf[i] = temp[10 - i];
	
	return num_buf;
}

/* VARARGS */
string_print(buffer, fmt, args)
	char *buffer;
	register char *fmt;
	int args;
{
	register char *buf_ptr;
	char *scan_ptr;
	char buf[NAME_SIZE];
	int *argptr = &args;
	BOOL pr_fl, i;
	
	if ((pr_fl = (buffer == NIL_PTR)))
		buffer = buf;
	
	buf_ptr = buffer;
	while (*fmt) {
		if (*fmt == '%') {
			fmt++;
			switch (*fmt++) {
            case 's':
                scan_ptr = (char *) *argptr;
                break;
            case 'I':
                scan_ptr = num_out((long) *argptr) + 5;
				/* for (i = 0; i < 5; i++)
					scan_ptr++; */
                break;
            case 'L':
                scan_ptr = num_out(*((long *) argptr));
                argptr++;
                break;
            default:
                scan_ptr = "";
			}
			while (*buf_ptr++ = *scan_ptr++)
				;
			buf_ptr--;
			argptr++;
		}
		else
			*buf_ptr++ = *fmt++;
	}
	*buf_ptr = '\0';
	
	if (pr_fl)
		print(buffer);
}


/*
** convert OS-9 to UNIX mode flags
*/
o2umode(mode)
    char mode;
{
    int ret_mode=0;
	
    if (mode & S_IFDIR)
        ret_mode |= 040000;
    if (mode & S_IREAD)
        ret_mode |= 0400;
    if (mode & S_IWRITE)
        ret_mode |= 0200;
    if (mode & S_IEXEC)
        ret_mode |= 0100;
    if (mode & S_IOREAD)
        ret_mode |= 04;
    if (mode & S_IOWRITE)
        ret_mode |= 02;
    if (mode & S_IOEXEC)
        ret_mode |= 01;
	
    return(ret_mode);
}

/*
** convert UNIX to OS-9 mode flags
*/
u29mode(mode)
    int mode;
{
    int ret_mode=0;
	
    strcpy(modestr,"-----------");
	
    if (mode & 040000) {
        ret_mode  |= S_IFDIR;
        modestr[0] = 'd';
    }
	
    if (mode & 0400) {
        ret_mode  |= S_IREAD;
        modestr[1] = 'r';
    }
	
    if (mode & 0200) {
        ret_mode  |= S_IWRITE;
        modestr[2] = 'w';
    }
	
    if (mode & 0100) {
        ret_mode  |= S_IEXEC;
        modestr[3] = 'x';
    }
	
    if (mode & 04) {
        ret_mode  |= S_IOREAD;
        modestr[7] = 'r';
    }
	
    if (mode & 02) {
        ret_mode  |= S_IOWRITE;
        modestr[8] = 'w';
    }
	
    if (mode & 01) {
        ret_mode  |= S_IOEXEC;
        modestr[9] = 'x';
    }
	
    return(ret_mode);
}

char *atime(clock)
    long clock;
{
    static char buf[26];
    int i;
	
    strcpy(buf,ctime(&clock));
	
    for (i=4; i< 16; i++)
        buf[i-4] = buf[i];
    buf[12] = ' ';
	
    for (i=20; i<24; i++)
        buf[i-7] = buf[i];
    buf[17] = 0;
	
    return(buf);
}

char **match(fpt,name)
    register struct files *fpt;
    register char *name;
{
	
	while (fpt->name) {
#ifdef OSK
		if (!_cmpnam(name,fpt->name,strlen(fpt->name))) {
#else
	    if (patmatch(fpt->name,name,1)) {
#endif
			return(fpt->name);
		}
		fpt = fpt->next;
	}
		return(NULL);
	}

#ifdef OSK
u2otime(om,um)
	struct sgtbuf *om;
	struct tm     *um;
{
	om->t_year = um->tm_year;
	om->t_month = um->tm_mon+1;
	om->t_day = um->tm_mday;
	om->t_hour = um->tm_hour;
	om->t_minute = um->tm_min;
	om->t_second = um->tm_sec;
}

long o2utime(om)
	struct sgtbuf *om;
{
	struct tm um;
	
	um.tm_year = om->t_year;
	um.tm_mon = om->t_month-1;
	um.tm_mday = om->t_day;
	um.tm_hour = om->t_hour;
	um.tm_min = om->t_minute;
	um.tm_sec = om->t_second;
	return mktime(&um);
}
#endif
SHAR_EOF
chmod 644 'tar.c'
echo shar: "extracting 'makefile'" '(963 characters)'
cat << \SHAR_EOF > 'makefile'
#
# makefile for OS-9 tar V1.9 Mon Jul 30 09:48:59 1990
#
# ``make''		generates tar in $(ODIR)
# ``make debug''	generates debugging version _tar in $(DBG_ODIR)
# ``make tags''		generates tags file
# ``make lint''		generates lint protocol
#
# The following compile flags can be used (see DEFS macro):
#	-DF_MODIFIER_ACTIVE	makes the f modifier active like under UNIX
#
EDITION	= 19
ODIR	= /h0/CMDS/LOCAL
DBG_ODIR= /h1/CMDS
RDIR	= RELS
DEFS	= F_MODIFIER_ACTIVE
G	=
CFLAGS	= -qixt=/r0/TMP $G
LFLAGS	= -qixt=/r0/TMP $G -e=$(EDITION)
CC	= cf

TARGET	= tar

$(TARGET): tar.r
     $(CC) $(LFLAGS) -f=$(ODIR)/$(TARGET) $(RDIR)/tar.r
     @attr $(ODIR)/$(TARGET) -epe

debug: debug.r
	@echo debug made
debug.r: tar.c
	make TARGET=_tar ODIR=$(DBG_ODIR) RDIR=DBG_RELS G=-g

##########################################################

tags: tags.r
	@echo tags made
tags.r: tar.c
	ctags -t tar.c

lint: lint.r
	@echo lint made
lint.r: tar.c
	lint -bachx tar.c >-tar.lint


SHAR_EOF
chmod 644 'makefile'
echo shar: "extracting 'tar.doc'" '(4119 characters)'
cat << \SHAR_EOF > 'tar.doc'
USAGE
    tar <function> <tarfile> [<file> . . .]

DESCRIPTION
    Builds and extracts from Unix tape archive files.  The <function>
parameter consists of a one-letter function specifier with one or more
option characters.  <tarfile> is the file to be used for the archive.
If <tarfile> is specified as -, then Stdin or Stdout will be used, as
appropriate.  <file> is one or more filenames to be used by the extract
or create operations.

FUNCTION SPECIFIERS
    t   Output a table of all files in the archive.

        Recognized Options:
        v  Verbose mode.  Display file permissions, sizes, 
           and modification dates and times.

    x   Extract the named files from the archive.  Wildcard characters
        * and ? may be used in filename specifications.

        Recognized Options:
        v  Verbose mode.  Display a message for each file extracted.
        m  Do not restore modification dates.  Instead, use current
           date and time as modification date for each extracted file.
        u  convert LF -> CR (for UNIX text files)
        b  Set copy buffer size (number of tape blocks).
           The number is specified directly after the options.
        z  First read the names of the files to be extracted from a list
           file, then continue with the names given on the command line.
           The name of the list file is given after the tar file name. If
           a ``-'' is specified stdin will be used. This feature is not
           possible if the tarfile is stdin, too.

    c   Create a new archive file and include the named files.  A directory
        name means to recursively include all files in that directory.

        Recognized Options:
        v  Verbose mode.  Display a message for each file added to the archive.
        u  convert CR -> LF (for UNIX text files)
        b  Set copy buffer size (number of tape blocks).
           The number is specified directly after the options.
        d  copy contents of several floopy disks to one tar archive
        z  First read the names of the files to be tared from a list
           file, then continue with the names given on the command line.
           The name of the list file is given after the tar file name. If
           a ``-'' is specified stdin will be used.

    All of the functions will also accept an 'f' option, which is ignored
    and included only for compatibility with the Unix 'tar' program.

EXAMPLES

1)   tar c - /d0 >/t2
    Archive all of the files on drive /d0, and send the resultant archive
    to stdout, which is redirected to /t2.

2)   tar tv sample.tar
    Will produce a verbose listing of the files in the archive.

3)   tar xmb 100 sample.tar a*.c text
    Will extract 'text' and all files matching the pattern a*.c from
    the archive sample.tar, ignoring the stored modification dates.
    Use a buffer of 100 tape blocks.

4)   tar xvb 50 sample.tar subdir
    Will extract all files from the directory 'subdir'. If 'subdir' doesn't
    exist it will be created. Buffer size is set to 50 blocks.

5)   tar xu /mt0 test.c
    Will extract the file test.c from a tape written on a UNIX machine,
    all LF characters will be converted to CRs.

6)   tar cu /mt0 test.c
    Will write the file test.c to a tape streamer converting all CR to LF for
    usage on a UNIX system.

7)   tar cfbz sample.tar 100 -
    Will create the tarfile sample.tar with block size 100. The names
    of the files to be tared are read from stdin.

BUGS
    This program is much simpler than the standard Unix tar program, and
lacks much of its functionality.   It cannot add files to a pre-existing
archive.  It cannot extract anything but the last occurence when more than
one copy of a file appears in the archive.  There may be problems
with very long file names.

AUTHOR
     Michael Huisjes
     Ported to OS9/6809 by Simmy Turner(simmy@nu.cs.fsu.edu)
     Ported to OS9/68k & improved by Stephan Paschedag 
         (stp@ethz.uucp  ..!mcvax!cernvax!chx400!ethz!stp)
     Improved by Christian Engel
         (krischan&informatik.rwth-aachen.de  ..!mcvax!unido!strange!krischan)
SHAR_EOF
chmod 644 'tar.doc'
echo shar: "extracting 'tar.hlp'" '(4195 characters)'
cat << \SHAR_EOF > 'tar.hlp'
@H
@T0
USAGE
    tar <function> <tarfile> [<file> . . .]

DESCRIPTION
    Builds and extracts from Unix tape archive files.  The <function>
parameter consists of a one-letter function specifier with one or more
option characters.  <tarfile> is the file to be used for the archive.
If <tarfile> is specified as -, then Stdin or Stdout will be used, as
appropriate.  <file> is one or more filenames to be used by the extract
or create operations.
@D1
Function_Specifiers : 2
Examples : 3
Bugs_and_Info : 4
@T2
FUNCTION SPECIFIERS
    t   Output a table of all files in the archive.

        Recognized Options:
        v  Verbose mode.  Display file permissions, sizes, 
           and modification dates and times.

    x   Extract the named files from the archive.  Wildcard characters
        * and ? may be used in filename specifications.

        Recognized Options:
        v  Verbose mode.  Display a message for each file extracted.
        m  Do not restore modification dates.  Instead, use current
           date and time as modification date for each extracted file.
        u  convert LF -> CR (for UNIX text files)
        b  Set copy buffer size (number of tape blocks).
           The number is specified directly after the options.
        z  First read the names of the files to be extracted from a list
           file, then continue with the names given on the command line.
           The name of the list file is given after the tar file name. If
           a ``-'' is specified stdin will be used. This feature is not
           possible if the tarfile is stdin, too.

    c   Create a new archive file and include the named files.  A directory
        name means to recursively include all files in that directory.

        Recognized Options:
        v  Verbose mode.  Display a message for each file added to the archive.
        u  convert CR -> LF (for UNIX text files)
        b  Set copy buffer size (number of tape blocks).
           The number is specified directly after the options.
        d  copy contents of several floopy disks to one tar archive
        z  First read the names of the files to be tared from a list
           file, then continue with the names given on the command line.
           The name of the list file is given after the tar file name. If
           a ``-'' is specified stdin will be used.

    All of the functions will also accept an 'f' option, which is ignored
    and included only for compatibility with the Unix 'tar' program.
@T3
EXAMPLES

1)   tar c - /d0 >/t2
    Archive all of the files on drive /d0, and send the resultant archive
    to stdout, which is redirected to /t2.

2)   tar tv sample.tar
    Will produce a verbose listing of the files in the archive.

3)   tar xmb 100 sample.tar a*.c text
    Will extract 'text' and all files matching the pattern a*.c from
    the archive sample.tar, ignoring the stored modification dates.
    Use a buffer of 100 tape blocks.

4)   tar xvb 50 sample.tar subdir
    Will extract all files from the directory 'subdir'. If 'subdir' doesn't
    exist it will be created. Buffer size is set to 50 blocks.

5)   tar xu /mt0 test.c
    Will extract the file test.c from a tape written on a UNIX machine,
    all LF characters will be converted to CRs.

6)   tar cu /mt0 test.c
    Will write the file test.c to a tape streamer converting all CR to LF for
    usage on a UNIX system.

7)   tar cfbz sample.tar 100 -
    Will create the tarfile sample.tar with block size 100. The names
    of the files to be tared are read from stdin.

@T4
BUGS
    This program is much simpler than the standard Unix tar program, and
lacks much of its functionality.   It cannot add files to a pre-existing
archive.  It cannot extract anything but the last occurence when more than
one copy of a file appears in the archive.  There may be problems
with very long file names.

AUTHOR
     Michael Huisjes
     Ported to OS9/6809 by Simmy Turner(simmy@nu.cs.fsu.edu)
     Ported to OS9/68k & improved by Stephan Paschedag 
         (stp@ethz.uucp  ..!mcvax!cernvax!chx400!ethz!stp)
     Improved by Christian Engel
         (krischan&informatik.rwth-aachen.de  ..!mcvax!unido!strange!krischan)
SHAR_EOF
chmod 644 'tar.hlp'
exit 0
#	End of shell archive
---- Cut here ---- Cut here ---- Cut here ---- Cut here ---- Cut here ----

Bye, Krischan.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
krischan@informatik.rwth-aachen.de *** mcvax!unido!rwthinf!strange!krischan
Christian Engel, Lehrstuhl fuer Informatik I, RWTH Aachen
Ahornstr. 55, D-5100 Aachen, W. Germany