/* copy-file-list.c: copy selected files between trees
 *
 ****************************************************************
 * Copyright (C) 2001, 2002  Tom Lord
 * 
 * See the file "COPYING" for further information about
 * the copyright and warranty status of this work.
 */


#include "config-options.h"
#include "hackerlab/os/errno.h"
#include "hackerlab/fs/file-names.h"
#include "hackerlab/hash/hashtree.h"
#include "hackerlab/hash/hash-utils.h"
#include "hackerlab/cmd/main.h"

/* __STDC__ prototypes for static functions */
static int ensure_directory_exists (t_uchar * name);



static t_uchar * program_name = "copy-file-list";
static t_uchar * usage = "file-list old-dir new-dir";
static t_uchar * version_string = (cfg__std__package " from regexps.com\n"
				   "\n"
				   "Copyright 2001, 2002 Tom Lord\n"
				   "\n"
				   "This is free software; see the source for copying conditions.\n"
				   "There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A\n"
				   "PARTICULAR PURPOSE.\n"
				   "\n"
				   "Report bugs to " cfg__file_utils_bug_mail ".\n"
				   "\n");

#define OPTS(OP, OP2) \
  OP (opt_help_msg, "h", "help", 0, \
      "Display a help message and exit.") \
  OP (opt_long_help, "H", 0, 0, \
      "Display a verbose help message and exit.") \
  OP (opt_version, "V", "version", 0, \
      "Display a release identifier string") \
  OP2 (opt_version, 0, 0, 0, "and exit.") \
  OP (opt_no_overwrite, 0, "no-overwrite", 0, \
      "Don't overwrite existing files.") \
  OP (opt_links, 0, "links", 0, \
      "Rather than copy regular files, make links to them.")

static t_uchar long_help[] = ("Copy each file named in FILE-LIST from OLD-DIR to NEW-DIR.\n"
			      "\n"
			      "Intermediate directories are created as necessary.\n");

enum options
{
  OPTS (OPT_ENUM, OPT_IGN)  
};

struct opt_desc opts[] = 
{
  OPTS (OPT_DESC, OPT_DESC)
    {-1, 0, 0, 0, 0}
};



int
main (int argc, char * argv[])
{
  int o;
  struct opt_parsed * option;
  int links;
  int no_overwrite;

  option = 0;

  safe_buffer_fd (0, 0, O_RDONLY, 0);
  safe_buffer_fd (1, 0, O_WRONLY, 0);
  vu_push_dash_handler (0);

  links = 0;
  no_overwrite = 0;

  while (1)
    {
      o = opt_standard (lim_use_must_malloc, &option, opts, &argc, argv, program_name, usage, version_string, long_help, opt_help_msg, opt_long_help, opt_version);
      if (o == opt_none)
	break;
      switch (o)
	{
	default:
	  safe_printfmt (2, "unhandled option `%s'\n", option->opt_string);
	  panic ("internal error parsing arguments");

	usage_error:
	  opt_usage (2, argv[0], program_name, usage, 1);
	  exit (1);

	/* bogus_arg: */
	  safe_printfmt (2, "ill-formed argument for `%s' (`%s')\n", option->opt_string, option->arg_string);
	  goto usage_error;

	case opt_double_dash:
	  goto no_more_options;

	case opt_links:
	  links = 1;
	  break;

	case opt_no_overwrite:
	  no_overwrite = 1;
	  break;
	  
	}
    }

 no_more_options:
  if (argc != 4)
    goto usage_error;


  {
    int file_list_fd;
    t_uchar * src_dir;
    t_uchar * dest_dir;
    t_uchar * file_path;
    long file_path_len;

    file_list_fd = safe_open (argv[1], O_RDONLY, 0);
    src_dir = argv[2];
    dest_dir = argv[3];

    while (1)
      {
	safe_next_line (&file_path, &file_path_len, file_list_fd);
	if (!file_path_len)
	  break;

	{
	  int has_nl;
	  has_nl = (file_path[file_path_len - 1] == '\n');
	  file_path = str_save_n (0, file_path, file_path_len - has_nl);
	}

	if (file_name_is_absolute (file_path))
	  {
	    safe_printfmt (2, "\n");
	    safe_printfmt (2, "copy-file-list: input error -- absolute file name\n");
	    safe_printfmt (2, "  file: %s\n", file_path);
	    safe_printfmt (2, "\n");
	    exit (1);
	  }

	{
	  t_uchar * src_path;
	  t_uchar * dest_path;
	  t_uchar * dest_dirname;
	  t_uchar * tmp;
	  int mode;
	  struct stat sb;
	  int errn;

	  tmp = file_name_in_vicinity (0, dest_dir, file_path);
	  dest_path = file_name_from_directory (0, tmp);
	  lim_free (0, tmp);

	  errn = 0;
	  if (no_overwrite && (!vu_lstat (&errn, dest_path, &sb) || (errn != ENOENT)))
	    {
	      if (errn && (errn != ENOENT))
		{
		  safe_printfmt (2, "\n");
		  safe_printfmt (2, "copy-file-list: i/o error for %s\n", dest_path);
		  safe_printfmt (2, "   %s\n", errno_to_string (errn));
		  safe_printfmt (2, "\n");
		  exit (1);
		}
	      lim_free (0, dest_path);
	      lim_free (0, file_path);
	      continue;
	    }

	  tmp = file_name_directory (0, dest_path);
	  if (!tmp)
	    {
	      safe_printfmt (2, "\n");
	      safe_printfmt (2, "copy-file-list: input error -- empty dest dir name\n");
	      safe_printfmt (2, "\n");
	      exit (1);
	    }
	  dest_dirname = file_name_from_directory (0, tmp);
	  lim_free (0, tmp);

	  src_path = file_name_in_vicinity (0, src_dir, file_path);

	  safe_lstat (src_path, &sb);

	  mode = (sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO));

	  if (ensure_directory_exists (dest_dirname))
	    {
	      safe_printfmt (2, "\n");
	      safe_printfmt (2, "copy-file-list: unable to create needed directory\n");
	      safe_printfmt (2, "  dir: %s\n", dest_dirname);
	      safe_printfmt (2, "\n");
	    }

	  if (S_ISDIR (sb.st_mode))
	    {
	      if (!safe_file_is_directory (dest_path))
		safe_mkdir (dest_path, mode);
	      else
		safe_chmod (dest_path, mode);
	    }
	  else if (S_ISLNK (sb.st_mode))
	    {
	      char * link_buf;
	      int link_buf_size;
	      int link_size;

	      link_buf = lim_malloc (0, 1024);
	      link_buf_size = 1024;

	      while (1)
		{
		  link_size = safe_readlink (src_path, link_buf, link_buf_size);
		  
		  if (link_size < link_buf_size)
		    break;

		  link_buf = lim_realloc (0, link_buf, link_buf_size * 2);
		  link_buf_size *= 2;
		}

	      if (link_size == link_buf_size)
		{
		  link_buf = lim_realloc (0, link_buf, link_buf_size * 2);
		  link_buf_size *= 2;
		}

	      link_buf[link_size] = 0;

	      safe_symlink (link_buf, dest_path);
	    }
	  else if (links)
	    {
	      safe_link (src_path, dest_path);
	    }
	  else
	    {
	      int in_fd;
	      int out_fd;
	      t_uchar buf[65536];
	      long amt;
	      

	      in_fd = safe_open (src_path, O_RDONLY, 0);
	      out_fd = safe_open (dest_path, O_WRONLY | O_CREAT | O_EXCL, mode);

	      while (1)
		{
		  amt = safe_read_retry (in_fd, buf, sizeof (buf));
		  if (amt == 0)
		    break;
		  safe_write_retry (out_fd, buf, amt);
		}

	      safe_close (in_fd);
	      safe_close (out_fd);
	    }


	  lim_free (0, src_path);
	  lim_free (0, dest_path);
	  lim_free (0, dest_dirname);
	}

	lim_free (0, file_path);
      }
  }
  return 0;
}


static int
hashtree_str_eq (void * va, void * vb, struct hashtree_rules * r)
{
  t_uchar * a;
  t_uchar * b;

  a = (t_uchar *)va;
  b = (t_uchar *)vb;

  return !str_cmp (a, b);
}

static int
ensure_directory_exists (t_uchar * name)
{
  static struct hashtree_rules rules = { hashtree_str_eq, 0, };
  static struct hashtree * tree = 0;
  t_ulong hash;

  if (!tree)
    {
      tree = hashtree_alloc (&rules);
    }

  hash = hash_mem (name, str_length (name));

  if (hashtree_find (tree, hash, name, &rules))
    return 0;

  if (safe_file_is_directory (name))
    return 0;

  {
    t_uchar * tmp;
    t_uchar * parent;

    tmp = file_name_directory (0, name);
    if (tmp)
      {
	parent = file_name_from_directory (0, tmp);
	lim_free (0, tmp);

	if (ensure_directory_exists (parent))
	  {
	    lim_free (0, parent);
	    return -1;
	  }
      }

    if (!safe_file_is_directory (name))
      safe_mkdir (name, 0777);

    hashtree_store (tree, hash, str_save (0, name), &rules);

    return 0;
  }
}

/* tag: Tom Lord Fri Dec 28 22:49:44 2001 (copy-file-list.c)
 */
