/* copypass.c - cpio copy pass sub-function.
   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 <sys/types.h>
#include <sys/stat.h>
#include "filetypes.h"
#include "system.h"
#include "cpiohdr.h"
#include "dstring.h"
#include "extern.h"

#ifndef HAVE_LCHOWN
#define lchown chown
#endif

/* Copy files listed on the standard input into directory `directory_name'.
   If `link_flag', link instead of copying.  */

void
process_copy_pass ()
{
  dynamic_string input_name;	/* Name of file from stdin.  */
  dynamic_string output_name;	/* Name of new file.  */
  int dirname_len;		/* Length of `directory_name'.  */
  int res;			/* Result of functions.  */
  char *slash;			/* For moving past slashes in input name.  */
  struct utimbuf times;		/* For resetting file times after copy.  */
  struct stat in_file_stat;	/* Stat record for input file.  */
  struct stat out_file_stat;	/* Stat record for output file.  */
  int in_file_des;		/* Input file descriptor.  */
  int out_file_des;		/* Output file descriptor.  */
  int existing_dir;		/* True if file is a dir & already exists.  */
#ifdef HPUX_CDF
  int cdf_flag;
  int cdf_char;
#endif

  /* Initialize the copy pass.  */
  dirname_len = strlen (directory_name);
  ds_init (&input_name, 128);
  ds_init (&output_name, dirname_len + 2);
  strcpy (output_name.ds_string, directory_name);
  output_name.ds_string[dirname_len] = '/';
  output_is_seekable = TRUE;
  /* Initialize this in case it has members we don't know to set.  */
  bzero (&times, sizeof (struct utimbuf));

  /* Copy files with names read from stdin.  */
  while (ds_fgetstr (stdin, &input_name, name_end) != NULL)
    {
      int link_res = -1;

      /* Check for blank line and ignore it if found.  */
      if (input_name.ds_string[0] == '\0')
	{
	  error (0, 0, "blank line ignored");
	  continue;
	}

      /* Check for current directory and ignore it if found.  */
      if (input_name.ds_string[0] == '.'
	  && (input_name.ds_string[1] == '\0'
	      || (input_name.ds_string[1] == '/'
		  && input_name.ds_string[2] == '\0')))
	continue;

      if ((*xstat) (input_name.ds_string, &in_file_stat) < 0)
	{
	  error (0, errno, "%s", input_name.ds_string);
	  continue;
	}

      /* Make the name of the new file.  */
      for (slash = input_name.ds_string; *slash == '/'; ++slash)
	;
#ifdef HPUX_CDF
      /* For CDF's we add a 2nd `/' after all "hidden" directories.
	 This kind of a kludge, but it's what we do when creating
	 archives, and it's easier to do this than to separately
	 keep track of which directories in a path are "hidden".  */
      slash = add_cdf_double_slashes (slash);
#endif
      ds_resize (&output_name, dirname_len + strlen (slash) + 2);
      strcpy (output_name.ds_string + dirname_len + 1, slash);

      existing_dir = FALSE;
      if (lstat (output_name.ds_string, &out_file_stat) == 0)
	{
	  if (S_ISDIR (out_file_stat.st_mode)
	      && S_ISDIR (in_file_stat.st_mode))
	    {
	      /* 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
		   && in_file_stat.st_mtime <= out_file_stat.st_mtime)
	    {
	      error (0, 0, "%s not created: newer or same age version exists",
		     output_name.ds_string);
	      continue;		/* Go to the next file.  */
	    }
	  else if (S_ISDIR (out_file_stat.st_mode)
			? rmdir (output_name.ds_string)
			: unlink (output_name.ds_string))
	    {
	      error (0, errno, "cannot remove current %s",
		     output_name.ds_string);
	      continue;		/* Go to the next file.  */
	    }
	}

      /* Do the real copy or link.  */
      if (S_ISREG (in_file_stat.st_mode))
	{
#ifndef __MSDOS__
	  /* Can the current file be linked to a another file?
	     Set link_name to the original file name.  */
	  if (link_flag)
	    /* User said to link it if possible.  Try and link to
	       the original copy.  If that fails we'll still try
	       and link to a copy we've already made.  */
	    link_res = link_to_name (output_name.ds_string,
				     input_name.ds_string);
	  if ( (link_res < 0) && (in_file_stat.st_nlink > 1) )
	    link_res = link_to_maj_min_ino (output_name.ds_string,
				major (in_file_stat.st_dev),
				minor (in_file_stat.st_dev),
				in_file_stat.st_ino);
#endif

	  /* If the file was not linked, copy contents of file.  */
	  if (link_res < 0)
	    {
	      in_file_des = open (input_name.ds_string,
				  O_RDONLY | O_BINARY, 0);
	      if (in_file_des < 0)
		{
		  error (0, errno, "%s", input_name.ds_string);
		  continue;
		}
	      out_file_des = open (output_name.ds_string,
				   O_CREAT | O_WRONLY | O_BINARY, 0600);
	      if (out_file_des < 0 && create_dir_flag)
		{
		  create_all_directories (output_name.ds_string);
		  out_file_des = open (output_name.ds_string,
				       O_CREAT | O_WRONLY | O_BINARY, 0600);
		}
	      if (out_file_des < 0)
		{
		  error (0, errno, "%s", output_name.ds_string);
		  close (in_file_des);
		  continue;
		}

	      copy_files (in_file_des, out_file_des, in_file_stat.st_size);
	      empty_output_buffer (out_file_des);
	      if (close (in_file_des) < 0)
		error (0, errno, "%s", input_name.ds_string);
	      if (close (out_file_des) < 0)
		error (0, errno, "%s", output_name.ds_string);

	      /* Set the attributes of the new file.  */
	      if (!no_chown_flag)
		if ((chown (output_name.ds_string,
			    set_owner_flag ? set_owner : in_file_stat.st_uid,
		      set_group_flag ? set_group : in_file_stat.st_gid) < 0)
		    && errno != EPERM)
		  error (0, errno, "%s", output_name.ds_string);
	      /* chown may have turned off some permissions we wanted. */
	      if (chmod (output_name.ds_string, in_file_stat.st_mode) < 0)
		error (0, errno, "%s", output_name.ds_string);
	      if (reset_time_flag)
		{
		  times.actime = in_file_stat.st_atime;
		  times.modtime = in_file_stat.st_mtime;
		  if (utime (input_name.ds_string, &times) < 0)
		    error (0, errno, "%s", input_name.ds_string);
		  if (utime (output_name.ds_string, &times) < 0)
		    error (0, errno, "%s", output_name.ds_string);
		}
	      if (retain_time_flag)
		{
		  times.actime = times.modtime = in_file_stat.st_mtime;
		  if (utime (output_name.ds_string, &times) < 0)
		    error (0, errno, "%s", output_name.ds_string);
		}
	    }
	}
      else if (S_ISDIR (in_file_stat.st_mode))
	{
#ifdef HPUX_CDF
	  cdf_flag = 0;
#endif
	  if (!existing_dir)
	    {
#ifdef HPUX_CDF
	      /* If the directory name ends in a + and is SUID,
		 then it is a CDF.  Strip the trailing + from the name
		 before creating it.  */
	      cdf_char = strlen (output_name.ds_string) - 1;
	      if ( (cdf_char > 0) &&
		   (in_file_stat.st_mode & 04000) &&
		   (output_name.ds_string [cdf_char] == '+') )
		{
		  output_name.ds_string [cdf_char] = '\0';
		  cdf_flag = 1;
		}
#endif
	      res = mkdir (output_name.ds_string, in_file_stat.st_mode);

	    }
	  else
	    res = 0;
	  if (res < 0 && create_dir_flag)
	    {
	      create_all_directories (output_name.ds_string);
	      res = mkdir (output_name.ds_string, in_file_stat.st_mode);
	    }
	  if (res < 0)
	    {
	      error (0, errno, "%s", output_name.ds_string);
	      continue;
	    }
	  if (!no_chown_flag)
	    if ((chown (output_name.ds_string,
			set_owner_flag ? set_owner : in_file_stat.st_uid,
		      set_group_flag ? set_group : in_file_stat.st_gid) < 0)
		&& errno != EPERM)
	      error (0, errno, "%s", output_name.ds_string);
	  /* chown may have turned off some permissions we wanted. */
	  if (chmod (output_name.ds_string, in_file_stat.st_mode) < 0)
	    error (0, errno, "%s", output_name.ds_string);
#ifdef HPUX_CDF
	  if (cdf_flag)
	    /* Once we "hide" the directory with the chmod(),
	       we have to refer to it using name+ isntead of name.  */
	    output_name.ds_string [cdf_char] = '+';
#endif
	  if (retain_time_flag)
	    {
	      times.actime = times.modtime = in_file_stat.st_mtime;
	      if (utime (output_name.ds_string, &times) < 0)
		error (0, errno, "%s", output_name.ds_string);
	    }
	}
#ifndef __MSDOS__
      else if (S_ISCHR (in_file_stat.st_mode) ||
	       S_ISBLK (in_file_stat.st_mode) ||
#ifdef S_ISFIFO
	       S_ISFIFO (in_file_stat.st_mode) ||
#endif
#ifdef S_ISSOCK
	       S_ISSOCK (in_file_stat.st_mode) ||
#endif
	       0)
	{
	  /* Can the current file be linked to a another file?
	     Set link_name to the original file name.  */
	  if (link_flag)
	    /* User said to link it if possible.  */
	    link_res = link_to_name (output_name.ds_string,
				     input_name.ds_string);
	  if ( (link_res < 0) && (in_file_stat.st_nlink > 1) )
	    link_res = link_to_maj_min_ino (output_name.ds_string,
			major (in_file_stat.st_dev),
			minor (in_file_stat.st_dev),
			in_file_stat.st_ino);

	  if (link_res < 0)
	    {
	      res = mknod (output_name.ds_string, in_file_stat.st_mode,
			   in_file_stat.st_rdev);
	      if (res < 0 && create_dir_flag)
		{
		  create_all_directories (output_name.ds_string);
		  res = mknod (output_name.ds_string, in_file_stat.st_mode,
			       in_file_stat.st_rdev);
		}
	      if (res < 0)
		{
		  error (0, errno, "%s", output_name.ds_string);
		  continue;
		}
	      if (!no_chown_flag)
		if ((chown (output_name.ds_string,
			    set_owner_flag ? set_owner : in_file_stat.st_uid,
			  set_group_flag ? set_group : in_file_stat.st_gid) < 0)
		    && errno != EPERM)
		  error (0, errno, "%s", output_name.ds_string);
	      /* chown may have turned off some permissions we wanted. */
	      if (chmod (output_name.ds_string, in_file_stat.st_mode) < 0)
		error (0, errno, "%s", output_name.ds_string);
	      if (retain_time_flag)
		{
		  times.actime = times.modtime = in_file_stat.st_mtime;
		  if (utime (output_name.ds_string, &times) < 0)
		    error (0, errno, "%s", output_name.ds_string);
		}
	    }
	}
#endif

#ifdef S_ISLNK
      else if (S_ISLNK (in_file_stat.st_mode))
	{
	  char *link_name;
	  link_name = (char *) xmalloc ((unsigned int) in_file_stat.st_size + 1);

	  if (readlink (input_name.ds_string, link_name,
			in_file_stat.st_size) < 0)
	    {
	      error (0, errno, "%s", input_name.ds_string);
	      free (link_name);
	      continue;
	    }
	  link_name[in_file_stat.st_size] = '\0';

	  res = UMASKED_SYMLINK (link_name, output_name.ds_string,
				 in_file_stat.st_mode);
	  if (res < 0 && create_dir_flag)
	    {
	      create_all_directories (output_name.ds_string);
	      res = UMASKED_SYMLINK (link_name, output_name.ds_string,
				     in_file_stat.st_mode);
	    }
	  if (res < 0)
	    {
	      error (0, errno, "%s", output_name.ds_string);
	      free (link_name);
	      continue;
	    }

	  /* Set the attributes of the new link.  */
	  if (!no_chown_flag)
	    if ((lchown (output_name.ds_string,
			 set_owner_flag ? set_owner : in_file_stat.st_uid,
		      set_group_flag ? set_group : in_file_stat.st_gid) < 0)
		&& errno != EPERM)
	      error (0, errno, "%s", output_name.ds_string);
	  free (link_name);
	}
#endif
      else
	{
	  error (0, 0, "%s: unknown file type", input_name.ds_string);
	}

      if (verbose_flag)
	fprintf (stderr, "%s\n", output_name.ds_string);
      if (dot_flag)
	fputc ('.', stderr);
    }

  if (dot_flag)
    fputc ('\n', stderr);
  res = (output_bytes + io_block_size - 1) / io_block_size;
  if (res == 1)
    fprintf (stderr, "1 block\n");
  else
    fprintf (stderr, "%d blocks\n", res);
}

/* 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 (file_name, st_dev_maj, st_dev_min, st_ino)
  char *file_name;
  int st_dev_maj;
  int st_dev_min;
  int st_ino;
{
  int	link_res;
  char *link_name;
  link_res = -1;
#ifndef __MSDOS__
  /* 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);
#endif
  return link_res;
}

/* Try and create a hard link from LINK_NAME to LINK_TARGET.  If
   `create_dir_flag' is set, any non-existent (parent) directories
   needed by LINK_NAME will be created.  If the link is successfully
   created and `verbose_flag' is set, print "LINK_TARGET linked to LINK_NAME\n".
   If the link can not be created and `link_flag' is set, print
   "cannot link LINK_TARGET to LINK_NAME\n".  Return 0 if the link
   is created, -1 otherwise.  */

int
link_to_name (link_name, link_target)
  char *link_name;
  char *link_target;
{
  int res;
#ifdef __MSDOS__
  res = -1;
#else /* not __MSDOS__ */
  res = link (link_target, link_name);
  if (res < 0 && create_dir_flag)
    {
      create_all_directories (link_name);
      res = link (link_target, link_name);
    }
  if (res == 0)
    {
      if (verbose_flag)
	error (0, 0, "%s linked to %s",
	       link_target, link_name);
    }
  else if (link_flag)
    {
      error (0, errno, "cannot link %s to %s",
	     link_target, link_name);
    }
#endif /* not __MSDOS__ */
  return res;
}
