[klibc] Simple cpio program (from Debian)

Debian uses klibc, and uses a small cpio program.  Include it in the
klibc distribution for convenience.

Signed-off-by: H. Peter Anvin <hpa@zytor.com>
diff --git a/usr/utils/Kbuild b/usr/utils/Kbuild
index 2007fa9..8264b59 100644
--- a/usr/utils/Kbuild
+++ b/usr/utils/Kbuild
@@ -4,7 +4,7 @@
 
 progs := chroot dd mkdir mkfifo mknod mount pivot_root umount
 progs += true false sleep ln nuke minips cat
-progs += insmod uname halt kill readlink
+progs += insmod uname halt kill readlink cpio
 
 static-y := $(addprefix static/, $(progs))
 shared-y := $(addprefix shared/, $(progs))
@@ -50,6 +50,8 @@
 shared/kill-y       := kill.o
 static/readlink-y   := readlink.o
 shared/readlink-y   := readlink.o
+static/cpio-y	    := cpio.o
+shared/cpio-y       := cpio.o
 
 # Additionally linked targets
 always := static/reboot static/poweroff shared/reboot shared/poweroff
diff --git a/usr/utils/cpio.c b/usr/utils/cpio.c
new file mode 100644
index 0000000..a41f40a
--- /dev/null
+++ b/usr/utils/cpio.c
@@ -0,0 +1,1102 @@
+/* copyin.c - extract or list a cpio archive
+   Copyright (C) 1990,1991,1992,2001,2002,2003,2004 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License along
+   with this program; if not, write to the Free Software Foundation, Inc.,
+   59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <malloc.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <unistd.h>
+#include <utime.h>
+#ifndef	FNM_PATHNAME
+#include <fnmatch.h>
+#endif
+
+#ifndef	O_BINARY
+# define O_BINARY 0
+#endif
+
+# ifndef DIRECTORY_SEPARATOR
+#  define DIRECTORY_SEPARATOR '/'
+# endif
+
+# ifndef ISSLASH
+#  define ISSLASH(C) ((C) == DIRECTORY_SEPARATOR)
+# endif
+
+# ifndef FILE_SYSTEM_PREFIX_LEN
+#  define FILE_SYSTEM_PREFIX_LEN(Filename) 0
+# endif
+
+#ifndef SYMLINK_USES_UMASK
+# define UMASKED_SYMLINK(name1,name2,mode)    symlink(name1,name2)
+#else
+# define UMASKED_SYMLINK(name1,name2,mode)    umasked_symlink(name1,name2,mode)
+#endif				/* SYMLINK_USES_UMASK */
+
+/* Return 1 if an array of N objects, each of size S, cannot exist due
+   to size arithmetic overflow.  S must be positive and N must be
+   nonnegative.  This is a macro, not an inline function, so that it
+   works correctly even when SIZE_MAX < N.
+
+   By gnulib convention, SIZE_MAX represents overflow in size
+   calculations, so the conservative dividend to use here is
+   SIZE_MAX - 1, since SIZE_MAX might represent an overflowed value.
+   However, malloc (SIZE_MAX) fails on all known hosts where
+   sizeof (ptrdiff_t) <= sizeof (size_t), so do not bother to test for
+   exactly-SIZE_MAX allocations on such hosts; this avoids a test and
+   branch when S is known to be 1.  */
+# define xalloc_oversized(n, s) \
+    ((size_t) (sizeof (ptrdiff_t) <= sizeof (size_t) ? -1 : -2) / (s) < (n))
+
+#define DISK_IO_BLOCK_SIZE	(512)
+
+char *progname = NULL;
+
+/* If true, print a . for each file processed. (-V) */
+char dot_flag = false;
+
+/* Input and output buffers.  */
+char *input_buffer, *output_buffer;
+
+/* The size of the input buffer.  */
+long input_buffer_size;
+
+/* Current locations in `input_buffer' and `output_buffer'.  */
+char *in_buff, *out_buff;
+
+/* Current number of bytes stored at `input_buff' and `output_buff'.  */
+long input_size, output_size;
+
+/* Block size value, initially 512.  -B sets to 5120.  */
+int io_block_size = 512;
+
+struct new_cpio_header {
+	unsigned short c_magic;
+	unsigned long c_ino;
+	unsigned long c_mode;
+	unsigned long c_uid;
+	unsigned long c_gid;
+	unsigned long c_nlink;
+	unsigned long c_mtime;
+	unsigned long c_filesize;
+	long c_dev_maj;
+	long c_dev_min;
+	long c_rdev_maj;
+	long c_rdev_min;
+	unsigned long c_namesize;
+	unsigned long c_chksum;
+	char *c_name;
+	char *c_tar_linkname;
+};
+
+/* Total number of bytes read and written for all files.  
+   Now that many tape drives hold more than 4Gb we need more than 32
+   bits to hold input_bytes and output_bytes.  But it's not worth
+   the trouble of adding special multi-precision arithmetic if the 
+   compiler doesn't support 64 bit ints since input_bytes and
+   output_bytes are only used to print the number of blocks copied.  */
+#ifdef __GNUC__
+long long input_bytes, output_bytes;
+#else
+long input_bytes, output_bytes;
+#endif
+
+/* Allocate N bytes of memory dynamically, with error checking.  */
+
+void *xmalloc(size_t n)
+{
+	void *p;
+	if (xalloc_oversized(n, 1) || (!(p = malloc(n)) && n != 0)) {
+		fprintf(stderr, "%s: memory exhausted\n", progname);
+		exit(1);
+	}
+	return p;
+/*   return xnmalloc_inline (n, 1); */
+}
+
+/* Change the size of an allocated block of memory P to N bytes,
+   with error checking.  */
+
+void *xrealloc(void *p, size_t n)
+{
+	if (xalloc_oversized(n, 1) || (!(p = realloc(p, n)) && n != 0)) {
+		fprintf(stderr, "%s: memory exhausted\n", progname);
+		exit(1);
+	}
+	return p;
+/*   return xnrealloc_inline (p, n, 1); */
+}
+
+/* Clone STRING.  */
+
+char *xstrdup(char const *string)
+{
+	size_t s = strlen(string) + 1;
+	return memcpy(xmalloc(s), string, s);
+/*   return xmemdup_inline (string, strlen (string) + 1); */
+}
+
+/* Copy NUM_BYTES of buffer `in_buff' into IN_BUF.
+   `in_buff' may be partly full.
+   When `in_buff' is exhausted, refill it from file descriptor IN_DES.  */
+
+static void tape_fill_input_buffer(int in_des, int num_bytes)
+{
+	in_buff = input_buffer;
+	num_bytes = (num_bytes < io_block_size) ? num_bytes : io_block_size;
+	input_size = read(in_des, input_buffer, num_bytes);
+	if (input_size < 0) {
+		fprintf(stderr, "%s: read error: %s\n", progname,
+			strerror(errno));
+		exit(1);
+	}
+	if (input_size == 0) {
+		fprintf(stderr, "%s: premature end of file\n", progname);
+		exit(1);
+	}
+	input_bytes += input_size;
+}
+
+/* Write `output_size' bytes of `output_buffer' to file
+   descriptor OUT_DES and reset `output_size' and `out_buff'.
+   If `swapping_halfwords' or `swapping_bytes' is set,
+   do the appropriate swapping first.  Our callers have
+   to make sure to only set these flags if `output_size' 
+   is appropriate (a multiple of 4 for `swapping_halfwords',
+   2 for `swapping_bytes').  The fact that DISK_IO_BLOCK_SIZE
+   must always be a multiple of 4 helps us (and our callers)
+   insure this.  */
+
+void disk_empty_output_buffer(int out_des)
+{
+	int bytes_written;
+
+	bytes_written = write(out_des, output_buffer, output_size);
+
+	if (bytes_written != output_size) {
+		fprintf(stderr, "%s: write error: %s\n",
+			progname, strerror(errno));
+		exit(1);
+	}
+	output_bytes += output_size;
+	out_buff = output_buffer;
+	output_size = 0;
+}
+
+/* Copy NUM_BYTES of buffer IN_BUF to `out_buff', which may be partly full.
+   When `out_buff' fills up, flush it to file descriptor OUT_DES.  */
+
+void disk_buffered_write(char *in_buf, int out_des, long num_bytes)
+{
+	register long bytes_left = num_bytes;	/* Bytes needing to be copied.  */
+	register long space_left;	/* Room left in output buffer.  */
+
+	while (bytes_left > 0) {
+		space_left = DISK_IO_BLOCK_SIZE - output_size;
+		if (space_left == 0)
+			disk_empty_output_buffer(out_des);
+		else {
+			if (bytes_left < space_left)
+				space_left = bytes_left;
+			memmove(out_buff, in_buf, (unsigned)space_left);
+			out_buff += space_left;
+			output_size += space_left;
+			in_buf += space_left;
+			bytes_left -= space_left;
+		}
+	}
+}
+
+/* Copy a file using the input and output buffers, which may start out
+   partly full.  After the copy, the files are not closed nor the last
+   block flushed to output, and the input buffer may still be partly
+   full.  If `crc_i_flag' is set, add each byte to `crc'.
+   IN_DES is the file descriptor for input;
+   OUT_DES is the file descriptor for output;
+   NUM_BYTES is the number of bytes to copy.  */
+
+void copy_files_tape_to_disk(int in_des, int out_des, long num_bytes)
+{
+	long size;
+
+	while (num_bytes > 0) {
+		if (input_size == 0)
+			tape_fill_input_buffer(in_des, io_block_size);
+		size = (input_size < num_bytes) ? input_size : num_bytes;
+		disk_buffered_write(in_buff, out_des, size);
+		num_bytes -= size;
+		input_size -= size;
+		in_buff += size;
+	}
+}
+
+/* if IN_BUF is NULL, Skip the next NUM_BYTES bytes of file descriptor IN_DES. */
+void tape_buffered_read(char *in_buf, int in_des, long num_bytes)
+{
+	register long bytes_left = num_bytes;	/* Bytes needing to be copied.  */
+	register long space_left;	/* Bytes to copy from input buffer.  */
+
+	while (bytes_left > 0) {
+		if (input_size == 0)
+			tape_fill_input_buffer(in_des, io_block_size);
+		if (bytes_left < input_size)
+			space_left = bytes_left;
+		else
+			space_left = input_size;
+		if (in_buf != NULL) {
+			memmove(in_buf, in_buff, (unsigned)space_left);
+			in_buf += space_left;
+		}
+		in_buff += space_left;
+		input_size -= space_left;
+		bytes_left -= space_left;
+	}
+}
+
+/* Skip the next NUM_BYTES bytes of file descriptor IN_DES.  */
+#define tape_toss_input(in_des,num_bytes) \
+(tape_buffered_read(NULL,(in_des),(num_bytes)))
+
+struct deferment {
+	struct deferment *next;
+	struct new_cpio_header header;
+};
+
+struct deferment *create_deferment(struct new_cpio_header *file_hdr)
+{
+	struct deferment *d;
+	d = (struct deferment *)xmalloc(sizeof(struct deferment));
+	d->header = *file_hdr;
+	d->header.c_name = (char *)xmalloc(strlen(file_hdr->c_name) + 1);
+	strcpy(d->header.c_name, file_hdr->c_name);
+	return d;
+}
+
+void free_deferment(struct deferment *d)
+{
+	free(d->header.c_name);
+	free(d);
+}
+
+int link_to_name(char *link_name, char *link_target)
+{
+	int res = link(link_target, link_name);
+	return res;
+}
+
+struct inode_val {
+	unsigned long inode;
+	unsigned long major_num;
+	unsigned long minor_num;
+	char *file_name;
+};
+
+/* Inode hash table.  Allocated by first call to add_inode.  */
+static struct inode_val **hash_table = NULL;
+
+/* Size of current hash table.  Initial size is 47.  (47 = 2*22 + 3) */
+static int hash_size = 22;
+
+/* Number of elements in current hash table.  */
+static int hash_num;
+
+/* Do the hash insert.  Used in normal inserts and resizing the hash
+   table.  It is guaranteed that there is room to insert the item.
+   NEW_VALUE is the pointer to the previously allocated inode, file
+   name association record.  */
+
+static void hash_insert(struct inode_val *new_value)
+{
+	int start;		/* Home position for the value.  */
+	int temp;		/* Used for rehashing.  */
+
+	/* Hash function is node number modulo the table size.  */
+	start = new_value->inode % hash_size;
+
+	/* Do the initial look into the table.  */
+	if (hash_table[start] == NULL) {
+		hash_table[start] = new_value;
+		return;
+	}
+
+	/* If we get to here, the home position is full with a different inode
+	   record.  Do a linear search for the first NULL pointer and insert
+	   the new item there.  */
+	temp = (start + 1) % hash_size;
+	while (hash_table[temp] != NULL)
+		temp = (temp + 1) % hash_size;
+
+	/* Insert at the NULL.  */
+	hash_table[temp] = new_value;
+}
+
+/* Associate FILE_NAME with the inode NODE_NUM.  (Insert into hash table.)  */
+
+void
+add_inode(unsigned long node_num, char *file_name, unsigned long major_num,
+	  unsigned long minor_num)
+{
+	struct inode_val *temp;
+
+	/* Create new inode record.  */
+	temp = (struct inode_val *)xmalloc(sizeof(struct inode_val));
+	temp->inode = node_num;
+	temp->major_num = major_num;
+	temp->minor_num = minor_num;
+	temp->file_name = xstrdup(file_name);
+
+	/* Do we have to increase the size of (or initially allocate)
+	   the hash table?  */
+	if (hash_num == hash_size || hash_table == NULL) {
+		struct inode_val **old_table;	/* Pointer to old table.  */
+		int i;		/* Index for re-insert loop.  */
+
+		/* Save old table.  */
+		old_table = hash_table;
+		if (old_table == NULL)
+			hash_num = 0;
+
+		/* Calculate new size of table and allocate it.
+		   Sequence of table sizes is 47, 97, 197, 397, 797, 1597, 3197, 6397 ...
+		   where 3197 and most of the sizes after 6397 are not prime.  The other
+		   numbers listed are prime.  */
+		hash_size = 2 * hash_size + 3;
+		hash_table = (struct inode_val **)
+		    xmalloc(hash_size * sizeof(struct inode_val *));
+		memset(hash_table, 0, hash_size * sizeof(struct inode_val *));
+
+		/* Insert the values from the old table into the new table.  */
+		for (i = 0; i < hash_num; i++)
+			hash_insert(old_table[i]);
+
+		if (old_table != NULL)
+			free(old_table);
+	}
+
+	/* Insert the new record and increment the count of elements in the
+	   hash table.  */
+	hash_insert(temp);
+	hash_num++;
+}
+
+char *find_inode_file(unsigned long node_num, unsigned long major_num,
+		      unsigned long minor_num)
+{
+	int start;		/* Initial hash location.  */
+	int temp;		/* Rehash search variable.  */
+
+	if (hash_table != NULL) {
+		/* Hash function is node number modulo the table size.  */
+		start = node_num % hash_size;
+
+		/* Initial look into the table.  */
+		if (hash_table[start] == NULL)
+			return NULL;
+		if (hash_table[start]->inode == node_num
+		    && hash_table[start]->major_num == major_num
+		    && hash_table[start]->minor_num == minor_num)
+			return hash_table[start]->file_name;
+
+		/* The home position is full with a different inode record.
+		   Do a linear search terminated by a NULL pointer.  */
+		for (temp = (start + 1) % hash_size;
+		     hash_table[temp] != NULL && temp != start;
+		     temp = (temp + 1) % hash_size) {
+			if (hash_table[temp]->inode == node_num
+			    && hash_table[start]->major_num == major_num
+			    && hash_table[start]->minor_num == minor_num)
+				return hash_table[temp]->file_name;
+		}
+	}
+	return NULL;
+}
+
+/* Try and create a hard link from FILE_NAME to another file 
+   with the given major/minor device number and inode.  If no other
+   file with the same major/minor/inode numbers is known, add this file
+   to the list of known files and associated major/minor/inode numbers
+   and return -1.  If another file with the same major/minor/inode
+   numbers is found, try and create another link to it using
+   link_to_name, and return 0 for success and -1 for failure.  */
+
+int
+link_to_maj_min_ino(char *file_name, int st_dev_maj, int st_dev_min, int st_ino)
+{
+	int link_res;
+	char *link_name;
+	link_res = -1;
+	/* Is the file a link to a previously copied file?  */
+	link_name = find_inode_file(st_ino, st_dev_maj, st_dev_min);
+	if (link_name == NULL)
+		add_inode(st_ino, file_name, st_dev_maj, st_dev_min);
+	else
+		link_res = link_to_name(file_name, link_name);
+	return link_res;
+}
+
+static void copyin_regular_file(struct new_cpio_header *file_hdr,
+				int in_file_des);
+
+void warn_junk_bytes(long bytes_skipped)
+{
+	fprintf(stderr, "%s: warning: skipped %ld byte(s) of junk\n",
+		progname, bytes_skipped);
+}
+
+/* Skip the padding on IN_FILE_DES after a header or file,
+   up to the next header.
+   The number of bytes skipped is based on OFFSET -- the current offset
+   from the last start of a header (or file) -- and the current
+   header type.  */
+
+static void tape_skip_padding(int in_file_des, int offset)
+{
+	int pad;
+	pad = (4 - (offset % 4)) % 4;
+
+	if (pad != 0)
+		tape_toss_input(in_file_des, pad);
+}
+
+static int
+try_existing_file(struct new_cpio_header *file_hdr, int in_file_des,
+		  int *existing_dir)
+{
+	struct stat file_stat;
+
+	*existing_dir = false;
+	if (lstat(file_hdr->c_name, &file_stat) == 0) {
+		if (S_ISDIR(file_stat.st_mode)
+		    && ((file_hdr->c_mode & S_IFMT) == S_IFDIR)) {
+			/* If there is already a directory there that
+			   we are trying to create, don't complain about
+			   it.  */
+			*existing_dir = true;
+			return 0;
+		} else if (S_ISDIR(file_stat.st_mode)
+			   ? rmdir(file_hdr->c_name)
+			   : unlink(file_hdr->c_name)) {
+			fprintf(stderr, "%s: cannot remove current %s: %s\n",
+				progname, file_hdr->c_name, strerror(errno));
+			tape_toss_input(in_file_des, file_hdr->c_filesize);
+			tape_skip_padding(in_file_des, file_hdr->c_filesize);
+			return -1;	/* Go to the next file.  */
+		}
+	}
+	return 0;
+}
+
+/* The newc and crc formats store multiply linked copies of the same file 
+   in the archive only once.  The actual data is attached to the last link 
+   in the archive, and the other links all have a filesize of 0.  When a 
+   file in the archive has multiple links and a filesize of 0, its data is 
+   probably "attatched" to another file in the archive, so we can't create
+   it right away.  We have to "defer" creating it until we have created
+   the file that has the data "attatched" to it.  We keep a list of the
+   "defered" links on deferments.  */
+
+struct deferment *deferments = NULL;
+
+/* Add a file header to the deferments list.  For now they all just
+   go on one list, although we could optimize this if necessary.  */
+
+static void defer_copyin(struct new_cpio_header *file_hdr)
+{
+	struct deferment *d;
+	d = create_deferment(file_hdr);
+	d->next = deferments;
+	deferments = d;
+	return;
+}
+
+/* We just created a file that (probably) has some other links to it
+   which have been defered.  Go through all of the links on the deferments
+   list and create any which are links to this file.  */
+
+static void create_defered_links(struct new_cpio_header *file_hdr)
+{
+	struct deferment *d;
+	struct deferment *d_prev;
+	int ino;
+	int maj;
+	int min;
+	int link_res;
+	ino = file_hdr->c_ino;
+	maj = file_hdr->c_dev_maj;
+	min = file_hdr->c_dev_min;
+	d = deferments;
+	d_prev = NULL;
+	while (d != NULL) {
+		if ((d->header.c_ino == ino) && (d->header.c_dev_maj == maj)
+		    && (d->header.c_dev_min == min)) {
+			struct deferment *d_free;
+			link_res =
+			    link_to_name(d->header.c_name, file_hdr->c_name);
+			if (link_res < 0) {
+				fprintf(stderr,
+					"%s: cannot link %s to %s: %s\n",
+					progname, d->header.c_name,
+					file_hdr->c_name, strerror(errno));
+			}
+			if (d_prev != NULL)
+				d_prev->next = d->next;
+			else
+				deferments = d->next;
+			d_free = d;
+			d = d->next;
+			free_deferment(d_free);
+		} else {
+			d_prev = d;
+			d = d->next;
+		}
+	}
+}
+
+/* If we had a multiply linked file that really was empty then we would
+   have defered all of its links, since we never found any with data
+   "attached", and they will still be on the deferment list even when
+   we are done reading the whole archive.  Write out all of these
+   empty links that are still on the deferments list.  */
+
+static void create_final_defers()
+{
+	struct deferment *d;
+	int link_res;
+	int out_file_des;
+	struct utimbuf times;	/* For setting file times.  */
+	/* Initialize this in case it has members we don't know to set.  */
+	memset(&times, 0, sizeof(struct utimbuf));
+
+	for (d = deferments; d != NULL; d = d->next) {
+		/* Debian hack: A line, which could cause an endless loop, was
+		   removed (97/1/2).  It was reported by Ronald F. Guilmette to
+		   the upstream maintainers. -BEM */
+		/* Debian hack:  This was reported by Horst Knobloch. This bug has
+		   been reported to "bug-gnu-utils@prep.ai.mit.edu". (99/1/6) -BEM
+		 */
+		link_res = link_to_maj_min_ino(d->header.c_name,
+					       d->header.c_dev_maj,
+					       d->header.c_dev_min,
+					       d->header.c_ino);
+		if (link_res == 0) {
+			continue;
+		}
+		out_file_des = open(d->header.c_name,
+				    O_CREAT | O_WRONLY | O_BINARY, 0600);
+		if (out_file_des < 0) {
+			fprintf(stderr, "%s: open %s: %s\n",
+				progname, d->header.c_name, strerror(errno));
+			continue;
+		}
+
+		/* File is now copied; set attributes.  */
+		if ((fchown(out_file_des, d->header.c_uid, d->header.c_gid) < 0)
+		    && errno != EPERM)
+			fprintf(stderr, "%s: fchown %s: %s\n",
+				progname, d->header.c_name, strerror(errno));
+		/* chown may have turned off some permissions we wanted. */
+		if (fchmod(out_file_des, (int)d->header.c_mode) < 0)
+			fprintf(stderr, "%s: fchmod %s: %s\n",
+				progname, d->header.c_name, strerror(errno));
+
+		if (close(out_file_des) < 0)
+			fprintf(stderr, "%s: close %s: %s\n",
+				progname, d->header.c_name, strerror(errno));
+
+	}
+}
+
+static void
+copyin_regular_file(struct new_cpio_header *file_hdr, int in_file_des)
+{
+	int out_file_des;	/* Output file descriptor.  */
+
+	/* Can the current file be linked to a previously copied file? */
+	if (file_hdr->c_nlink > 1) {
+		int link_res;
+		if (file_hdr->c_filesize == 0) {
+			/* The newc and crc formats store multiply linked copies
+			   of the same file in the archive only once.  The
+			   actual data is attached to the last link in the
+			   archive, and the other links all have a filesize
+			   of 0.  Since this file has multiple links and a
+			   filesize of 0, its data is probably attatched to
+			   another file in the archive.  Save the link, and
+			   process it later when we get the actual data.  We
+			   can't just create it with length 0 and add the
+			   data later, in case the file is readonly.  We still
+			   lose if its parent directory is readonly (and we aren't
+			   running as root), but there's nothing we can do about
+			   that.  */
+			defer_copyin(file_hdr);
+			tape_toss_input(in_file_des, file_hdr->c_filesize);
+			tape_skip_padding(in_file_des, file_hdr->c_filesize);
+			return;
+		}
+		/* If the file has data (filesize != 0), then presumably
+		   any other links have already been defer_copyin'ed(),
+		   but GNU cpio version 2.0-2.2 didn't do that, so we
+		   still have to check for links here (and also in case
+		   the archive was created and later appeneded to). */
+		/* Debian hack: (97/1/2) This was reported by Ronald
+		   F. Guilmette to the upstream maintainers. -BEM */
+		link_res = link_to_maj_min_ino(file_hdr->c_name,
+					       file_hdr->c_dev_maj,
+					       file_hdr->c_dev_min,
+					       file_hdr->c_ino);
+		if (link_res == 0) {
+			tape_toss_input(in_file_des, file_hdr->c_filesize);
+			tape_skip_padding(in_file_des, file_hdr->c_filesize);
+			return;
+		}
+	}
+
+	/* If not linked, copy the contents of the file.  */
+	out_file_des = open(file_hdr->c_name,
+			    O_CREAT | O_WRONLY | O_BINARY, 0600);
+
+	if (out_file_des < 0) {
+		fprintf(stderr, "%s: open %s: %s\n",
+			progname, file_hdr->c_name, strerror(errno));
+		tape_toss_input(in_file_des, file_hdr->c_filesize);
+		tape_skip_padding(in_file_des, file_hdr->c_filesize);
+		return;
+	}
+
+	copy_files_tape_to_disk(in_file_des, out_file_des,
+				file_hdr->c_filesize);
+	disk_empty_output_buffer(out_file_des);
+
+	if (close(out_file_des) < 0)
+		fprintf(stderr, "%s: close %s: %s\n",
+			progname, file_hdr->c_name, strerror(errno));
+
+	/* File is now copied; set attributes.  */
+	if ((chown(file_hdr->c_name, file_hdr->c_uid, file_hdr->c_gid) < 0)
+	    && errno != EPERM)
+		fprintf(stderr, "%s: chown %s: %s\n",
+			progname, file_hdr->c_name, strerror(errno));
+
+	/* chown may have turned off some permissions we wanted. */
+	if (chmod(file_hdr->c_name, (int)file_hdr->c_mode) < 0)
+		fprintf(stderr, "%s: chmod %s: %s\n",
+			progname, file_hdr->c_name, strerror(errno));
+
+	tape_skip_padding(in_file_des, file_hdr->c_filesize);
+	if (file_hdr->c_nlink > 1) {
+		/* (see comment above for how the newc and crc formats 
+		   store multiple links).  Now that we have the data 
+		   for this file, create any other links to it which
+		   we defered.  */
+		create_defered_links(file_hdr);
+	}
+}
+
+/* In general, we can't use the builtin `basename' function if available,
+   since it has different meanings in different environments.
+   In some environments the builtin `basename' modifies its argument.
+
+   Return the address of the last file name component of NAME.  If
+   NAME has no file name components because it is all slashes, return
+   NAME if it is empty, the address of its last slash otherwise.  */
+
+char *base_name(char const *name)
+{
+	char const *base = name + FILE_SYSTEM_PREFIX_LEN(name);
+	char const *p;
+
+	for (p = base; *p; p++) {
+		if (ISSLASH(*p)) {
+			/* Treat multiple adjacent slashes like a single slash.  */
+			do
+				p++;
+			while (ISSLASH(*p));
+
+			/* If the file name ends in slash, use the trailing slash as
+			   the basename if no non-slashes have been found.  */
+			if (!*p) {
+				if (ISSLASH(*base))
+					base = p - 1;
+				break;
+			}
+
+			/* *P is a non-slash preceded by a slash.  */
+			base = p;
+		}
+	}
+
+	return (char *)base;
+}
+
+/* Return the length of of the basename NAME.  Typically NAME is the
+   value returned by base_name.  Act like strlen (NAME), except omit
+   redundant trailing slashes.  */
+
+size_t base_len(char const *name)
+{
+	size_t len;
+
+	for (len = strlen(name); 1 < len && ISSLASH(name[len - 1]); len--)
+		continue;
+
+	return len;
+}
+
+/* Remove trailing slashes from PATH.
+   Return true if a trailing slash was removed.
+   This is useful when using filename completion from a shell that
+   adds a "/" after directory names (such as tcsh and bash), because
+   the Unix rename and rmdir system calls return an "Invalid argument" error
+   when given a path that ends in "/" (except for the root directory).  */
+
+bool strip_trailing_slashes(char *path)
+{
+	char *base = base_name(path);
+	char *base_lim = base + base_len(base);
+	bool had_slash = (*base_lim != '\0');
+	*base_lim = '\0';
+	return had_slash;
+}
+
+static void copyin_directory(struct new_cpio_header *file_hdr, int existing_dir)
+{
+	int res;		/* Result of various function calls.  */
+
+	/* Strip any trailing `/'s off the filename; tar puts
+	   them on.  We might as well do it here in case anybody
+	   else does too, since they cause strange things to happen.  */
+	strip_trailing_slashes(file_hdr->c_name);
+
+	/* Ignore the current directory.  It must already exist,
+	   and we don't want to change its permission, ownership
+	   or time.  */
+	if (file_hdr->c_name[0] == '.' && file_hdr->c_name[1] == '\0') {
+		return;
+	}
+
+	if (!existing_dir)
+	{
+		res = mkdir(file_hdr->c_name, file_hdr->c_mode);
+	} else
+		res = 0;
+	if (res < 0) {
+		/* In some odd cases where the file_hdr->c_name includes `.',
+		   the directory may have actually been created by
+		   create_all_directories(), so the mkdir will fail
+		   because the directory exists.  If that's the case,
+		   don't complain about it.  */
+		struct stat file_stat;
+		if ((errno != EEXIST) ||
+		    (lstat(file_hdr->c_name, &file_stat) != 0) ||
+		    !(S_ISDIR(file_stat.st_mode))) {
+			fprintf(stderr, "%s: lstat %s: %s\n",
+				progname, file_hdr->c_name, strerror(errno));
+			return;
+		}
+	}
+	if ((chown(file_hdr->c_name, file_hdr->c_uid, file_hdr->c_gid) < 0)
+	    && errno != EPERM)
+		fprintf(stderr, "%s: chown %s: %s\n",
+			progname, file_hdr->c_name, strerror(errno));
+	/* chown may have turned off some permissions we wanted. */
+	if (chmod(file_hdr->c_name, (int)file_hdr->c_mode) < 0)
+		fprintf(stderr, "%s: chmod %s: %s\n",
+			progname, file_hdr->c_name, strerror(errno));
+}
+
+static void copyin_device(struct new_cpio_header *file_hdr)
+{
+	int res;		/* Result of various function calls.  */
+
+	if (file_hdr->c_nlink > 1) {
+		int link_res;
+		/* Debian hack:  This was reported by Horst
+		   Knobloch. This bug has been reported to
+		   "bug-gnu-utils@prep.ai.mit.edu". (99/1/6) -BEM */
+		link_res = link_to_maj_min_ino(file_hdr->c_name,
+					       file_hdr->c_dev_maj,
+					       file_hdr->c_dev_min,
+					       file_hdr->c_ino);
+		if (link_res == 0) {
+			return;
+		}
+	}
+
+	res = mknod(file_hdr->c_name, file_hdr->c_mode,
+		    makedev(file_hdr->c_rdev_maj, file_hdr->c_rdev_min));
+	if (res < 0) {
+		fprintf(stderr, "%s: mknod %s: %s\n", progname,
+			file_hdr->c_name, strerror(errno));
+		return;
+	}
+	if ((chown(file_hdr->c_name, file_hdr->c_uid, file_hdr->c_gid) < 0)
+	    && errno != EPERM)
+		fprintf(stderr, "%s: chown %s: %s\n", progname,
+			file_hdr->c_name, strerror(errno));
+	/* chown may have turned off some permissions we wanted. */
+	if (chmod(file_hdr->c_name, file_hdr->c_mode) < 0)
+		fprintf(stderr, "%s: chmod %s: %s\n", progname,
+			file_hdr->c_name, strerror(errno));
+}
+
+static void copyin_link(struct new_cpio_header *file_hdr, int in_file_des)
+{
+	char *link_name = NULL;	/* Name of hard and symbolic links.  */
+	int res;		/* Result of various function calls.  */
+
+	link_name = (char *)xmalloc((unsigned int)file_hdr->c_filesize + 1);
+	link_name[file_hdr->c_filesize] = '\0';
+	tape_buffered_read(link_name, in_file_des, file_hdr->c_filesize);
+	tape_skip_padding(in_file_des, file_hdr->c_filesize);
+
+	res = UMASKED_SYMLINK(link_name, file_hdr->c_name, file_hdr->c_mode);
+	if (res < 0) {
+		fprintf(stderr, "%s: UMASKED_SYMLINK %s: %s\n",
+			progname, file_hdr->c_name, strerror(errno));
+		free(link_name);
+		return;
+	}
+	if ((lchown(file_hdr->c_name, file_hdr->c_uid, file_hdr->c_gid) < 0)
+	    && errno != EPERM) {
+		fprintf(stderr, "%s: lchown %s: %s\n",
+			progname, file_hdr->c_name, strerror(errno));
+	}
+	free(link_name);
+}
+
+static void copyin_file(struct new_cpio_header *file_hdr, int in_file_des)
+{
+	int existing_dir;
+
+	if (try_existing_file(file_hdr, in_file_des, &existing_dir) < 0)
+		return;
+
+	/* Do the real copy or link.  */
+	switch (file_hdr->c_mode & S_IFMT) {
+	case S_IFREG:
+		copyin_regular_file(file_hdr, in_file_des);
+		break;
+
+	case S_IFDIR:
+		copyin_directory(file_hdr, existing_dir);
+		break;
+
+	case S_IFCHR:
+	case S_IFBLK:
+#ifdef S_IFSOCK
+	case S_IFSOCK:
+#endif
+#ifdef S_IFIFO
+	case S_IFIFO:
+#endif
+		copyin_device(file_hdr);
+		break;
+
+#ifdef S_IFLNK
+	case S_IFLNK:
+		copyin_link(file_hdr, in_file_des);
+		break;
+#endif
+
+	default:
+		fprintf(stderr, "%s: %s: unknown file type\n",
+			progname, file_hdr->c_name);
+		tape_toss_input(in_file_des, file_hdr->c_filesize);
+		tape_skip_padding(in_file_des, file_hdr->c_filesize);
+	}
+}
+
+/* Fill in FILE_HDR by reading a new-format ASCII format cpio header from
+   file descriptor IN_DES, except for the magic number, which is
+   already filled in.  */
+
+void read_in_new_ascii(struct new_cpio_header *file_hdr, int in_des)
+{
+	char ascii_header[112];
+
+	tape_buffered_read(ascii_header, in_des, 104L);
+	ascii_header[104] = '\0';
+	sscanf(ascii_header,
+	       "%8lx%8lx%8lx%8lx%8lx%8lx%8lx%8lx%8lx%8lx%8lx%8lx%8lx",
+	       &file_hdr->c_ino, &file_hdr->c_mode, &file_hdr->c_uid,
+	       &file_hdr->c_gid, &file_hdr->c_nlink, &file_hdr->c_mtime,
+	       &file_hdr->c_filesize, &file_hdr->c_dev_maj,
+	       &file_hdr->c_dev_min, &file_hdr->c_rdev_maj,
+	       &file_hdr->c_rdev_min, &file_hdr->c_namesize,
+	       &file_hdr->c_chksum);
+	/* Read file name from input.  */
+	if (file_hdr->c_name != NULL)
+		free(file_hdr->c_name);
+	file_hdr->c_name = (char *)xmalloc(file_hdr->c_namesize);
+	tape_buffered_read(file_hdr->c_name, in_des,
+			   (long)file_hdr->c_namesize);
+
+	/* In SVR4 ASCII format, the amount of space allocated for the header
+	   is rounded up to the next long-word, so we might need to drop
+	   1-3 bytes.  */
+	tape_skip_padding(in_des, file_hdr->c_namesize + 110);
+}
+
+/* Return 16-bit integer I with the bytes swapped.  */
+#define swab_short(i) ((((i) << 8) & 0xff00) | (((i) >> 8) & 0x00ff))
+
+/* Read the header, including the name of the file, from file
+   descriptor IN_DES into FILE_HDR.  */
+
+void read_in_header(struct new_cpio_header *file_hdr, int in_des)
+{
+	long bytes_skipped = 0;	/* Bytes of junk found before magic number.  */
+
+	/* Search for a valid magic number.  */
+
+	file_hdr->c_tar_linkname = NULL;
+
+	tape_buffered_read((char *)file_hdr, in_des, 6L);
+	while (1) {
+		if (!strncmp((char *)file_hdr, "070702", 6)
+		    || !strncmp((char *)file_hdr, "070701", 6))
+		{
+			if (bytes_skipped > 0)
+				warn_junk_bytes(bytes_skipped);
+
+			read_in_new_ascii(file_hdr, in_des);
+			break;
+		}
+		bytes_skipped++;
+		memmove((char *)file_hdr, (char *)file_hdr + 1, 5);
+		tape_buffered_read((char *)file_hdr + 5, in_des, 1L);
+	}
+}
+
+/* Read the collection from standard input and create files
+   in the file system.  */
+
+void process_copy_in()
+{
+	char done = false;	/* True if trailer reached.  */
+	struct new_cpio_header file_hdr;	/* Output header information.  */
+	int in_file_des;	/* Input file descriptor.  */
+
+	/* Initialize the copy in.  */
+	file_hdr.c_name = NULL;
+
+	/* only from stdin */
+	in_file_des = 0;
+
+	/* While there is more input in the collection, process the input.  */
+	while (!done) {
+		/* Start processing the next file by reading the header.  */
+		read_in_header(&file_hdr, in_file_des);
+
+		/* Is this the header for the TRAILER file?  */
+		if (strcmp("TRAILER!!!", file_hdr.c_name) == 0) {
+			done = true;
+			break;
+		}
+
+		/* Copy the input file into the directory structure.  */
+
+		copyin_file(&file_hdr, in_file_des);
+
+		if (dot_flag)
+			fputc('.', stderr);
+	}
+
+	if (dot_flag)
+		fputc('\n', stderr);
+
+	create_final_defers();
+
+}
+
+/* Initialize the input and output buffers to their proper size and
+   initialize all variables associated with the input and output
+   buffers.  */
+
+void initialize_buffers()
+{
+	int in_buf_size, out_buf_size;
+
+	/* Make sure the input buffer can always hold 2 blocks and that it
+	   is big enough to hold 1 tar record (512 bytes) even if it
+	   is not aligned on a block boundary.  The extra buffer space
+	   is needed by process_copyin and peek_in_buf to automatically
+	   figure out what kind of archive it is reading.  */
+	if (io_block_size >= 512)
+		in_buf_size = 2 * io_block_size;
+	else
+		in_buf_size = 1024;
+	out_buf_size = DISK_IO_BLOCK_SIZE;
+
+	input_buffer = (char *)xmalloc(in_buf_size);
+	in_buff = input_buffer;
+	input_buffer_size = in_buf_size;
+	input_size = 0;
+	input_bytes = 0;
+
+	output_buffer = (char *)xmalloc(out_buf_size);
+	out_buff = output_buffer;
+	output_size = 0;
+	output_bytes = 0;
+
+}
+
+int main(int argc, char *argv[])
+{
+	int c;
+	int extract_flag = false;
+
+	progname = argv[0];
+
+	do {
+		c = getopt(argc, argv, "iV");
+		if (c == EOF)
+			break;
+		switch (c) {
+		case 'V':
+			dot_flag = true;
+			break;
+
+		case 'i':
+			extract_flag = true;
+			break;
+		case '?':
+			fprintf(stderr,
+				"%s: not implemented or invalid option -%c\n",
+				progname, optopt);
+			exit(1);
+
+		}
+	} while (1);
+
+	if (extract_flag) {
+		initialize_buffers();
+
+		process_copy_in();
+	} else {
+		fprintf(stderr, "Usage: %s [-V] -i [< archive]\n", progname);
+		exit(1);
+	}
+
+	return 0;
+}