/* copyin.c - extract or list a cpio archive
   Copyright (C) 1990, 1991, 1992 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., 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include <stdio.h>
#include <fnmatch.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "filetypes.h"
#include "system.h"
#include "cpiohdr.h"
#include "dstring.h"
#include "extern.h"
#include "rmt.h"

#ifndef HAVE_LCHOWN
#define lchown chown
#endif

static void read_pattern_file ();
static void skip_padding ();

/* 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 (file_hdr, in_des)
     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.  */

  if (archive_format == arf_unknown)
    {
      char tmpbuf[512];
      int check_tar;
      int peeked_bytes;

      while (archive_format == arf_unknown)
	{
	  peeked_bytes = peek_in_buf (tmpbuf, in_des, 512);
	  if (peeked_bytes < 6)
	    error (1, 0, "premature end of archive");

	  if (!strncmp (tmpbuf, "070701", 6))
	    archive_format = arf_newascii;
	  else if (!strncmp (tmpbuf, "070707", 6))
	    archive_format = arf_oldascii;
	  else if (!strncmp (tmpbuf, "070702", 6))
	    {
	      archive_format = arf_crcascii;
	      crc_i_flag = TRUE;
	    }
	  else if ((*((unsigned short *) tmpbuf) == 070707) ||
		   (*((unsigned short *) tmpbuf) == swab_short ((unsigned short) 070707)))
	    archive_format = arf_binary;
	  else if (peeked_bytes >= 512
		   && (check_tar = is_tar_header (tmpbuf)))
	    {
	      if (check_tar == 2)
		archive_format = arf_ustar;
	      else
		archive_format = arf_tar;
	    }
	  else
	    {
	      copy_in_buf ((char *) tmpbuf, in_des, 1L);
	      ++bytes_skipped;
	    }
	}
    }

  if (archive_format == arf_tar || archive_format == arf_ustar)
    {
      if (append_flag)
	last_header_start = input_bytes - io_block_size +
	  (in_buff - input_buffer);
      if (bytes_skipped > 0)
	error (0, 0, "warning: skipped %ld bytes of junk", bytes_skipped);
      read_in_tar_header (file_hdr, in_des);
      return;
    }

  file_hdr->c_tar_linkname = NULL;

  copy_in_buf ((char *) file_hdr, in_des, 6L);
  while (1)
    {
      if (append_flag)
	last_header_start = input_bytes - io_block_size
	  + (in_buff - input_buffer) - 6;
      if (archive_format == arf_newascii
	  && !strncmp ((char *) file_hdr, "070701", 6))
	{
	  if (bytes_skipped > 0)
	    error (0, 0, "warning: skipped %ld bytes of junk", bytes_skipped);
	  read_in_new_ascii (file_hdr, in_des);
	  break;
	}
      if (archive_format == arf_crcascii
	  && !strncmp ((char *) file_hdr, "070702", 6))
	{
	  if (bytes_skipped > 0)
	    error (0, 0, "warning: skipped %ld bytes of junk", bytes_skipped);
	  read_in_new_ascii (file_hdr, in_des);
	  break;
	}
      if (archive_format == arf_oldascii
	  && !strncmp ((char *) file_hdr, "070707", 6))
	{
	  if (bytes_skipped > 0)
	    error (0, 0, "warning: skipped %ld bytes of junk", bytes_skipped);
	  read_in_old_ascii (file_hdr, in_des);
	  break;
	}
      if (archive_format == arf_binary
	  && (file_hdr->c_magic == 070707
	      || file_hdr->c_magic == swab_short ((unsigned short) 070707)))
	{
	  /* Having to skip 1 byte because of word alignment is normal.  */
	  if (bytes_skipped > 0)
	    error (0, 0, "warning: skipped %ld bytes of junk", bytes_skipped);
	  read_in_binary (file_hdr, in_des);
	  break;
	}
      bytes_skipped++;
      bcopy ((char *) file_hdr + 1, (char *) file_hdr, 5);
      copy_in_buf ((char *) file_hdr + 5, in_des, 1L);
    }
}

/* Fill in FILE_HDR by reading an old-format ASCII format cpio header from
   file descriptor IN_DES, except for the magic number, which is
   already filled in.  */

void
read_in_old_ascii (file_hdr, in_des)
     struct new_cpio_header *file_hdr;
     int in_des;
{
  char ascii_header[78];
  unsigned long dev;
  unsigned long rdev;

  copy_in_buf (ascii_header, in_des, 70L);
  ascii_header[70] = '\0';
  sscanf (ascii_header,
	  "%6lo%6lo%6lo%6lo%6lo%6lo%6lo%11lo%6lo%11lo",
	  &dev, &file_hdr->c_ino,
	  &file_hdr->c_mode, &file_hdr->c_uid, &file_hdr->c_gid,
	  &file_hdr->c_nlink, &rdev, &file_hdr->c_mtime,
	  &file_hdr->c_namesize, &file_hdr->c_filesize);
  file_hdr->c_dev_maj = major (dev);
  file_hdr->c_dev_min = minor (dev);
  file_hdr->c_rdev_maj = major (rdev);
  file_hdr->c_rdev_min = minor (rdev);

  /* 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 + 1);
  copy_in_buf (file_hdr->c_name, in_des, (long) file_hdr->c_namesize);
}

/* 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 (file_hdr, in_des)
     struct new_cpio_header *file_hdr;
     int in_des;
{
  char ascii_header[112];

  copy_in_buf (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);
  copy_in_buf (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.  */
  skip_padding (in_des, file_hdr->c_namesize + 110);
}

/* Fill in FILE_HDR by reading a binary format cpio header from
   file descriptor IN_DES, except for the first 6 bytes (the magic
   number, device, and inode number), which are already filled in.  */

void
read_in_binary (file_hdr, in_des)
     struct new_cpio_header *file_hdr;
     int in_des;
{
  struct old_cpio_header short_hdr;

  /* Copy the data into the short header, then later transfer
     it into the argument long header.  */
  short_hdr.c_dev = ((struct old_cpio_header *) file_hdr)->c_dev;
  short_hdr.c_ino = ((struct old_cpio_header *) file_hdr)->c_ino;
  copy_in_buf (((char *) &short_hdr) + 6, in_des, 20L);

  /* If the magic number is byte swapped, fix the header.  */
  if (file_hdr->c_magic == swab_short ((unsigned short) 070707))
    {
      static int warned = 0;

      /* Alert the user that they might have to do byte swapping on
	 the file contents.  */
      if (warned == 0)
	{
	  error (0, 0, "warning: archive header has reverse byte-order");
	  warned = 1;
	}
      swab_array ((char *) &short_hdr, 13);
    }

  file_hdr->c_dev_maj = major (short_hdr.c_dev);
  file_hdr->c_dev_min = minor (short_hdr.c_dev);
  file_hdr->c_ino = short_hdr.c_ino;
  file_hdr->c_mode = short_hdr.c_mode;
  file_hdr->c_uid = short_hdr.c_uid;
  file_hdr->c_gid = short_hdr.c_gid;
  file_hdr->c_nlink = short_hdr.c_nlink;
  file_hdr->c_rdev_maj = major (short_hdr.c_rdev);
  file_hdr->c_rdev_min = minor (short_hdr.c_rdev);
  file_hdr->c_mtime = (unsigned long) short_hdr.c_mtimes[0] << 16
    | short_hdr.c_mtimes[1];

  file_hdr->c_namesize = short_hdr.c_namesize;
  file_hdr->c_filesize = (unsigned long) short_hdr.c_filesizes[0] << 16
    | short_hdr.c_filesizes[1];

  /* 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);
  copy_in_buf (file_hdr->c_name, in_des, (long) file_hdr->c_namesize);

  /* In binary mode, the amount of space allocated in the header for
     the filename is `c_namesize' rounded up to the next short-word,
     so we might need to drop a byte.  */
  if (file_hdr->c_namesize % 2)
    toss_input (in_des, 1L);
}

/* Exchange the bytes of each element of the array of COUNT shorts
   starting at PTR.  */

void
swab_array (ptr, count)
     char *ptr;
     int count;
{
  char tmp;

  while (count-- > 0)
    {
      tmp = *ptr;
      *ptr = *(ptr + 1);
      ++ptr;
      *ptr = tmp;
      ++ptr;
    }
}

/* Current time for verbose table.  */
static time_t current_time;

/* Read the collection from standard input and create files
   in the file system.  */

void
process_copy_in ()
{
  char done = FALSE;		/* True if trailer reached.  */
  int res;			/* Result of various function calls.  */
  dynamic_string new_name;	/* New file name for rename option.  */
  FILE *tty_in;			/* Interactive file for rename option.  */
  FILE *tty_out;		/* Interactive file for rename option.  */
  char *str_res;		/* Result for string function.  */
  struct utimbuf times;		/* For setting file times.  */
  struct stat file_stat;	/* Output file stat record.  */
  struct new_cpio_header file_hdr;	/* Output header information.  */
  int out_file_des;		/* Output file descriptor.  */
  int in_file_des;		/* Input file descriptor.  */
  char skip_file;		/* Flag for use with patterns.  */
  int existing_dir;		/* True if file is a dir & already exists.  */
  int i;			/* Loop index variable.  */
  char *link_name = NULL;	/* Name of hard and symbolic links.  */

  /* Initialize the copy in.  */
  if (pattern_file_name)
    read_pattern_file ();
  file_hdr.c_name = NULL;
  ds_init (&new_name, 128);
  /* Initialize this in case it has members we don't know to set.  */
  bzero (&times, sizeof (struct utimbuf));

  /* Open interactive file pair for rename operation.  */
  if (rename_flag)
    {
      tty_in = fopen (CONSOLE, "r");
      if (tty_in == NULL)
	error (2, errno, CONSOLE);
      tty_out = fopen (CONSOLE, "w");
      if (tty_out == NULL)
	error (2, errno, CONSOLE);
    }

  /* Get date and time if needed for processing the table option.  */
  if (table_flag && verbose_flag)
    time (&current_time);

#ifdef __MSDOS__
  setmode (archive_des, O_BINARY);
#endif
  /* Check whether the input file might be a tape.  */
  in_file_des = archive_des;
  if (_isrmt (in_file_des))
    {
      input_is_special = 1;
      input_is_seekable = 0;
    }
  else
    {
      if (fstat (in_file_des, &file_stat))
	error (1, errno, "standard input is closed");
      input_is_special =
#ifdef S_ISBLK
	S_ISBLK (file_stat.st_mode) ||
#endif
	S_ISCHR (file_stat.st_mode);
      input_is_seekable = S_ISREG (file_stat.st_mode);
    }
  output_is_seekable = TRUE;

  /* While there is more input in the collection, process the input.  */
  while (!done)
    {
      link_name = NULL;
      swapping_halfwords = swapping_bytes = FALSE;

      /* 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;
	}

      /* Does the file name match one of the given patterns?  */
      if (num_patterns <= 0)
	skip_file = FALSE;
      else
	{
	  skip_file = copy_matching_files;
	  for (i = 0; i < num_patterns
	       && skip_file == copy_matching_files; i++)
	    {
	      if (fnmatch (save_patterns[i], file_hdr.c_name, 0) == 0)
		skip_file = !copy_matching_files;
	    }
	}

      if (skip_file)
	{
	  toss_input (in_file_des, file_hdr.c_filesize);
	  skip_padding (in_file_des, file_hdr.c_filesize);
	}
      else if (table_flag)
	{
	  if (verbose_flag)
	    {
#ifdef CP_IFLNK
	      if ((file_hdr.c_mode & CP_IFMT) == CP_IFLNK)
		{
		  if (archive_format != arf_tar && archive_format != arf_ustar)
		    {
		      link_name = (char *) xmalloc ((unsigned int) file_hdr.c_filesize + 1);
		      link_name[file_hdr.c_filesize] = '\0';
		      copy_in_buf (link_name, in_file_des, file_hdr.c_filesize);
		      long_format (&file_hdr, link_name);
		      free (link_name);
		      skip_padding (in_file_des, file_hdr.c_filesize);
		      continue;
		    }
		  else
		    {
		      long_format (&file_hdr, file_hdr.c_tar_linkname);
		      continue;
		    }
		}
	      else
#endif
		long_format (&file_hdr, (char *) 0);
	    }
	  else
	    printf ("%s\n", file_hdr.c_name);

	  toss_input (in_file_des, file_hdr.c_filesize);
	  skip_padding (in_file_des, file_hdr.c_filesize);
	}
      else if (append_flag)
	{
	  toss_input (in_file_des, file_hdr.c_filesize);
	  skip_padding (in_file_des, file_hdr.c_filesize);
	}
      else
	{
	  /* Copy the input file into the directory structure.  */

	  /* Do we need to rename the file? */
	  if (rename_flag)
	    {
	      fprintf (tty_out, "rename %s -> ", file_hdr.c_name);
	      fflush (tty_out);
	      str_res = ds_fgets (tty_in, &new_name);
	      if (str_res == NULL || str_res[0] == 0)
		{
		  toss_input (in_file_des, file_hdr.c_filesize);
		  skip_padding (in_file_des, file_hdr.c_filesize);
		  continue;
		}
	      else
		file_hdr.c_name = xstrdup (new_name.ds_string);
	    }

	  /* See if the file already exists.  */
	  existing_dir = FALSE;
	  if (lstat (file_hdr.c_name, &file_stat) == 0)
	    {
	      if (S_ISDIR (file_stat.st_mode)
		  && ((file_hdr.c_mode & CP_IFMT) == CP_IFDIR))
		{
		  /* If there is already a directory there that
		     we are trying to create, don't complain about
		     it.  */
		  existing_dir = TRUE;
		}
	      else if (!unconditional_flag
		       && file_hdr.c_mtime <= file_stat.st_mtime)
		{
		  error (0, 0, "%s not created: newer or same age version exists",
			 file_hdr.c_name);
		  toss_input (in_file_des, file_hdr.c_filesize);
		  skip_padding (in_file_des, file_hdr.c_filesize);
		  continue;	/* Go to the next file.  */
		}
	      else if (unlink (file_hdr.c_name))
		{
		  error (0, errno, "cannot remove current %s",
			 file_hdr.c_name);
		  toss_input (in_file_des, file_hdr.c_filesize);
		  skip_padding (in_file_des, file_hdr.c_filesize);
		  continue;	/* Go to the next file.  */
		}
	    }

	  /* Do the real copy or link.  */
	  switch (file_hdr.c_mode & CP_IFMT)
	    {
	    case CP_IFREG:
#ifndef __MSDOS__
	      /* Can the current file be linked to a previously copied file? */
	      if (file_hdr.c_nlink > 1 && archive_format != arf_tar
		  && archive_format != arf_ustar)
		{
		  link_name = find_inode_file (file_hdr.c_ino,
				    file_hdr.c_dev_maj, file_hdr.c_dev_min);
		  if (link_name == NULL)
		    add_inode (file_hdr.c_ino, file_hdr.c_name,
			       file_hdr.c_dev_maj, file_hdr.c_dev_min);
		  else
		    {
		      res = link (link_name, file_hdr.c_name);
		      if (res < 0 && create_dir_flag)
			{
			  create_all_directories (file_hdr.c_name);
			  res = link (link_name, file_hdr.c_name);
			}
		      if (res == 0)
			{
			  if (verbose_flag)
			    error (0, 0, "%s linked to %s",
				   link_name, file_hdr.c_name);
			  toss_input (in_file_des, file_hdr.c_filesize);
			  skip_padding (in_file_des, file_hdr.c_filesize);
			}
		      else
			link_name = NULL;
		    }
		}
	      else if ((archive_format == arf_tar || archive_format == arf_ustar)
		       && file_hdr.c_tar_linkname)
		{
		  link_name = file_hdr.c_tar_linkname;
		  res = link (link_name, file_hdr.c_name);
		  if (res < 0 && create_dir_flag)
		    {
		      create_all_directories (file_hdr.c_name);
		      res = link (link_name, file_hdr.c_name);
		    }
		  if (res == 0)
		    {
		      if (verbose_flag)
			error (0, 0, "%s linked to %s",
			       link_name, file_hdr.c_name);
		    }
		  else
		    {
		      error (0, errno, "cannot link %s to %s",
			     file_hdr.c_tar_linkname, file_hdr.c_name);
		    }
		}
#endif

	      /* If not linked, copy the contents of the file.  */
	      if (link_name == NULL)
		{
		  out_file_des = open (file_hdr.c_name,
				       O_CREAT | O_WRONLY | O_BINARY, 0600);
		  if (out_file_des < 0 && create_dir_flag)
		    {
		      create_all_directories (file_hdr.c_name);
		      out_file_des = open (file_hdr.c_name,
					   O_CREAT | O_WRONLY | O_BINARY,
					   0600);
		    }
		  if (out_file_des < 0)
		    {
		      error (0, errno, "%s", file_hdr.c_name);
		      toss_input (in_file_des, file_hdr.c_filesize);
		      skip_padding (in_file_des, file_hdr.c_filesize);
		      continue;
		    }

		  crc = 0;
		  if (swap_halfwords_flag)
		    {
		      if ((file_hdr.c_filesize % 4) == 0)
			swapping_halfwords = TRUE;
		      else
			error (0, 0, "cannot swap halfwords of %s: odd number of halfwords",
			       file_hdr.c_name);
		    }
		  if (swap_bytes_flag)
		    {
		      if ((file_hdr.c_filesize % 2) == 0)
			swapping_bytes = TRUE;
		      else
			error (0, 0, "cannot swap bytes of %s: odd number of bytes",
			       file_hdr.c_name);
		    }
		  copy_files (in_file_des, out_file_des, file_hdr.c_filesize);
		  empty_output_buffer (out_file_des);
		  if (close (out_file_des) < 0)
		    error (0, errno, "%s", file_hdr.c_name);

		  if (archive_format == arf_crcascii)
		    {
		      if (crc != file_hdr.c_chksum)
			error (0, 0, "%s: checksum error (0x%x, should be 0x%x)",
			       file_hdr.c_name, crc, file_hdr.c_chksum);
		    }
		  /* File is now copied; set attributes.  */
		  if (!no_chown_flag)
		    if ((chown (file_hdr.c_name,
				set_owner_flag ? set_owner : file_hdr.c_uid,
			   set_group_flag ? set_group : file_hdr.c_gid) < 0)
			&& errno != EPERM)
		      error (0, errno, "%s", file_hdr.c_name);
		  /* chown may have turned off some permissions we wanted. */
		  if (chmod (file_hdr.c_name, (int) file_hdr.c_mode) < 0)
		    error (0, errno, "%s", file_hdr.c_name);
		  if (retain_time_flag)
		    {
		      times.actime = times.modtime = file_hdr.c_mtime;
		      if (utime (file_hdr.c_name, &times) < 0)
			error (0, errno, "%s", file_hdr.c_name);
		    }
		  skip_padding (in_file_des, file_hdr.c_filesize);
		}
	      break;

	    case CP_IFDIR:
	      /* 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')
		break;

	      if (!existing_dir)
		res = mkdir (file_hdr.c_name, file_hdr.c_mode);
	      else
		res = 0;
	      if (res < 0 && create_dir_flag)
		{
		  create_all_directories (file_hdr.c_name);
		  res = mkdir (file_hdr.c_name, file_hdr.c_mode);
		}
	      if (res < 0)
		{
		  error (0, errno, "%s", file_hdr.c_name);
		  continue;
		}
	      if (!no_chown_flag)
		if ((chown (file_hdr.c_name,
			    set_owner_flag ? set_owner : file_hdr.c_uid,
			    set_group_flag ? set_group : file_hdr.c_gid) < 0)
		    && errno != EPERM)
		  error (0, errno, "%s", file_hdr.c_name);
	      /* chown may have turned off some permissions we wanted. */
	      if (chmod (file_hdr.c_name, (int) file_hdr.c_mode) < 0)
		error (0, errno, "%s", file_hdr.c_name);
	      if (retain_time_flag)
		{
		  times.actime = times.modtime = file_hdr.c_mtime;
		  if (utime (file_hdr.c_name, &times) < 0)
		    error (0, errno, "%s", file_hdr.c_name);
		}
	      break;

#ifndef __MSDOS__
	    case CP_IFCHR:
	    case CP_IFBLK:
#ifdef CP_IFSOCK
	    case CP_IFSOCK:
#endif
#ifdef CP_IFIFO
	    case CP_IFIFO:
#endif
	      res = mknod (file_hdr.c_name, file_hdr.c_mode,
			makedev (file_hdr.c_rdev_maj, file_hdr.c_rdev_min));
	      if (res < 0 && create_dir_flag)
		{
		  create_all_directories (file_hdr.c_name);
		  res = mknod (file_hdr.c_name, file_hdr.c_mode,
			makedev (file_hdr.c_rdev_maj, file_hdr.c_rdev_min));
		}
	      if (res < 0)
		{
		  error (0, errno, "%s", file_hdr.c_name);
		  continue;
		}
	      if (!no_chown_flag)
		if ((chown (file_hdr.c_name,
			    set_owner_flag ? set_owner : file_hdr.c_uid,
			    set_group_flag ? set_group : file_hdr.c_gid) < 0)
		    && errno != EPERM)
		  error (0, errno, "%s", file_hdr.c_name);
	      /* chown may have turned off some permissions we wanted. */
	      if (chmod (file_hdr.c_name, file_hdr.c_mode) < 0)
		error (0, errno, "%s", file_hdr.c_name);
	      break;
#endif

#ifdef CP_IFLNK
	    case CP_IFLNK:
	      {
		if (archive_format != arf_tar && archive_format != arf_ustar)
		  {
		    link_name = (char *) xmalloc ((unsigned int) file_hdr.c_filesize + 1);
		    link_name[file_hdr.c_filesize] = '\0';
		    copy_in_buf (link_name, in_file_des, file_hdr.c_filesize);
		    skip_padding (in_file_des, file_hdr.c_filesize);
		  }
		else
		  {
		    link_name = xstrdup (file_hdr.c_tar_linkname);
		  }

		res = symlink (link_name, file_hdr.c_name);
		if (res < 0 && create_dir_flag)
		  {
		    create_all_directories (file_hdr.c_name);
		    res = symlink (link_name, file_hdr.c_name);
		  }
		if (res < 0)
		  {
		    error (0, errno, "%s", file_hdr.c_name);
		    free (link_name);
		    link_name = NULL;
		    continue;
		  }
		if (!no_chown_flag)
		  if ((lchown (file_hdr.c_name,
			       set_owner_flag ? set_owner : file_hdr.c_uid,
			   set_group_flag ? set_group : file_hdr.c_gid) < 0)
		      && errno != EPERM)
		    error (0, errno, "%s", file_hdr.c_name);
		free (link_name);
		link_name = NULL;
	      }
	      break;
#endif

	    default:
	      error (0, 0, "%s: unknown file type", file_hdr.c_name);
	      toss_input (in_file_des, file_hdr.c_filesize);
	      skip_padding (in_file_des, file_hdr.c_filesize);
	    }

	  if (verbose_flag)
	    fprintf (stderr, "%s\n", file_hdr.c_name);
	  if (dot_flag)
	    fputc ('.', stderr);
	}
    }

  if (dot_flag)
    fputc ('\n', stderr);

  if (append_flag)
    return;

  res = (input_bytes + io_block_size - 1) / io_block_size;
  if (res == 1)
    fprintf (stderr, "1 block\n");
  else
    fprintf (stderr, "%d blocks\n", res);
}

/* Print the file described by FILE_HDR in long format.
   If LINK_NAME is nonzero, it is the name of the file that
   this file is a symbolic link to.  */

void
long_format (file_hdr, link_name)
     struct new_cpio_header *file_hdr;
     char *link_name;
{
  char mbuf[11];
  char tbuf[40];
  time_t when;

  mode_string (file_hdr->c_mode, mbuf);
  mbuf[10] = '\0';

  /* Get time values ready to print.  */
  when = file_hdr->c_mtime;
  strcpy (tbuf, ctime (&when));
  if (current_time - when > 6L * 30L * 24L * 60L * 60L
      || current_time - when < 0L)
    {
      /* The file is older than 6 months, or in the future.
	 Show the year instead of the time of day.  */
      strcpy (tbuf + 11, tbuf + 19);
    }
  tbuf[16] = '\0';

  printf ("%s %3u ", mbuf, file_hdr->c_nlink);

  if (numeric_uid)
    printf ("%-8u %-8u ", (unsigned int) file_hdr->c_uid,
	    (unsigned int) file_hdr->c_gid);
#ifndef __MSDOS__
  else
    printf ("%-8.8s %-8.8s ", getuser (file_hdr->c_uid),
	    getgroup (file_hdr->c_gid));

  if ((file_hdr->c_mode & CP_IFMT) == CP_IFCHR
      || (file_hdr->c_mode & CP_IFMT) == CP_IFBLK)
    printf ("%3u, %3u ", file_hdr->c_rdev_maj,
	    file_hdr->c_rdev_min);
  else
#endif
    printf ("%8lu ", file_hdr->c_filesize);

  printf ("%s ", tbuf + 4);

  print_name_with_quoting (file_hdr->c_name);
  if (link_name)
    {
      printf (" -> ");
      print_name_with_quoting (link_name);
    }
  putc ('\n', stdout);
}

void
print_name_with_quoting (p)
     register char *p;
{
  register unsigned char c;

  while (c = *p++)
    {
      switch (c)
	{
#ifndef __MSDOS__
	case '\\':
	  printf ("\\\\");
	  break;
#endif

	case '\n':
	  printf ("\\n");
	  break;

	case '\b':
	  printf ("\\b");
	  break;

	case '\r':
	  printf ("\\r");
	  break;

	case '\t':
	  printf ("\\t");
	  break;

	case '\f':
	  printf ("\\f");
	  break;

	case ' ':
	  printf ("\\ ");
	  break;

	case '"':
	  printf ("\\\"");
	  break;

	default:
	  if (c > 040 &&
#ifdef __MSDOS__
	      c < 0377 && c != 0177
#else
	      c < 0177
#endif
	    )
	    putchar (c);
	  else
	    printf ("\\%03o", (unsigned int) c);
	}
    }
}

/* Read a pattern file (for the -E option).  Put a list of
   `num_patterns' elements in `save_patterns'.  Any patterns that were
   already in `save_patterns' (from the command line) are preserved.  */

static void
read_pattern_file ()
{
  int max_new_patterns;
  char **new_save_patterns;
  int new_num_patterns;
  int i;
  dynamic_string pattern_name;
  FILE *pattern_fp;

  if (num_patterns < 0)
    num_patterns = 0;
  max_new_patterns = 1 + num_patterns;
  new_save_patterns = (char **) xmalloc (max_new_patterns * sizeof (char *));
  new_num_patterns = num_patterns;
  ds_init (&pattern_name, 128);

  pattern_fp = fopen (pattern_file_name, "r");
  if (pattern_fp == NULL)
    error (1, errno, "%s", pattern_file_name);
  while (ds_fgetstr (pattern_fp, &pattern_name, '\n') != NULL)
    {
      if (new_num_patterns >= max_new_patterns)
	{
	  max_new_patterns += 1;
	  new_save_patterns = (char **)
	    xrealloc ((char *) new_save_patterns,
		      max_new_patterns * sizeof (char *));
	}
      new_save_patterns[new_num_patterns] = xstrdup (pattern_name.ds_string);
      ++new_num_patterns;
    }
  if (ferror (pattern_fp) || fclose (pattern_fp) == EOF)
    error (1, errno, "%s", pattern_file_name);

  for (i = 0; i < num_patterns; ++i)
    new_save_patterns[i] = save_patterns[i];

  save_patterns = new_save_patterns;
  num_patterns = new_num_patterns;
}

/* 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
skip_padding (in_file_des, offset)
     int in_file_des;
     int offset;
{
  int pad;

  if (archive_format == arf_crcascii || archive_format == arf_newascii)
    pad = (4 - (offset % 4)) % 4;
  else if (archive_format == arf_binary)
    pad = (2 - (offset % 2)) % 2;
  else if (archive_format == arf_tar || archive_format == arf_ustar)
    pad = (512 - (offset % 512)) % 512;
  else
    pad = 0;

  if (pad != 0)
    toss_input (in_file_des, pad);
}
