// $Id: vcopy.c,v 1.5 2005/03/24 12:44:17 ensc Exp $    --*- c -*--

// Copyright (C) 2004 Enrico Scholz <enrico.scholz@informatik.tu-chemnitz.de>
//  
// 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; version 2 of the License.
//  
// 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.


#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include "util.h"
#include "vserver.h"

#include "lib_internal/matchlist.h"
#include "lib_internal/unify.h"

#include <unistd.h>
#include <getopt.h>
#include <fcntl.h>
#include <dirent.h>
#include <errno.h>
#include <assert.h>
#include <utime.h>
#include <libgen.h>
#include <sys/param.h>

#define ENSC_WRAPPERS_UNISTD	1
#define ENSC_WRAPPERS_FCNTL	1
#define ENSC_WRAPPERS_DIRENT	1
#include <wrappers.h>

#define CMD_HELP		0x8000
#define CMD_VERSION		0x8001
#define CMD_MANUALLY		0x8002
#define CMD_STRICT		0x8003

struct WalkdownInfo
{
    PathInfo				state;
    struct MatchList			dst_list;
    struct MatchList			src_list;
};

struct Arguments {
    enum {mdMANUALLY, mdVSERVER}	mode;
    bool				do_dry_run;
    unsigned int			verbosity;
    bool				local_fs;
    bool				is_strict;
};

static struct WalkdownInfo		global_info;
static struct Arguments const *		global_args;

int wrapper_exit_code = 1;

struct option const
CMDLINE_OPTIONS[] = {
  { "help",     no_argument,  0, CMD_HELP },
  { "version",  no_argument,  0, CMD_VERSION },
  { "manually", no_argument,  0, CMD_MANUALLY },
  { "strict",   no_argument,  0, CMD_STRICT },
  { 0,0,0,0 }
};

typedef enum { opUNIFY, opCOPY, opDIR, opSKIP }	Operation;

static void
showHelp(int fd, char const *cmd, int res)
{
  VSERVER_DECLARE_CMD(cmd);
  
  WRITE_MSG(fd, "Usage:\n  ");
  WRITE_STR(fd, cmd);
  WRITE_MSG(fd,
	    " [-nv] [--strict] <dst-vserver> <src-vserver>\n    or\n  ");
  WRITE_STR(fd, cmd);
  WRITE_MSG(fd,
	    " --manually [-nvx] [--] <dst-path> <dst-excludelist> <src-path> <src-excludelist>\n\n"
 	    "  --manually      ...  unify generic paths; excludelists must be generated\n"
	    "                       manually\n"
	    "  --strict        ...  require an existing vserver configuration for dst-vserver;\n"
	    "                       by default, a base skeleton will be created but manual\n"
	    "                       configuration wil be still needed to make the new vserver work\n"
	    "  -n              ...  do not modify anything; just show what there will be\n"
	    "                       done (in combination with '-v')\n"
	    "  -v              ...  verbose mode\n"
	    "  -x              ...  do not cross filesystems; this is valid in manual\n"
	    "                       mode only and will be ignored for vserver unification\n\n"
	    "Please report bugs to " PACKAGE_BUGREPORT "\n");
  exit(res);
}

static void
showVersion()
{
  WRITE_MSG(1,
	    "vcopy " VERSION " -- copies directories and vserver files\n"
	    "This program is part of " PACKAGE_STRING "\n\n"
	    "Copyright (C) 2003,2004 Enrico Scholz\n"
	    VERSION_COPYRIGHT_DISCLAIMER);
  exit(0);
}

int Global_getVerbosity() {
  return global_args->verbosity;
}

bool Global_doRenew() {
  return true;
}

#include "vserver-visitdir.hc"

static Operation
checkDirEntry(PathInfo const *path, struct stat const *st)
{
  struct WalkdownInfo const * const	info = &global_info;
  MatchType				res;

  // when marked as 'skip' in the first excludelist already, we do not need to
  // visit the second one since it could not change that.
  res=MatchList_compare(&info->dst_list, path->d);
  if (res!=stSKIP) {
    MatchType		tmp = MatchList_compare(&info->src_list, path->d);

    // stINCLUDE gets overridden by stEXCLUDE+stSKIP, and stEXCLUDE by stSKIP.
    // Using the MAX() macro is a hack but it works
    res=MAX(res,tmp);
  }

  // non-skipped directories are marked as opDIR
  if (res!=stSKIP && S_ISDIR(st->st_mode))
    return opDIR;

  // non-skipped symlinks will be copied always
  if (res!=stSKIP && S_ISLNK(st->st_mode))
    return opCOPY;

  // skipped files or non regular files (character/block devices) will be skipped
  // always
  if (res==stSKIP || !S_ISREG(st->st_mode))
    return opSKIP;

  switch (res) {
    case stINCLUDE	:  return opUNIFY;
    case stEXCLUDE	:  return opCOPY;
    case stSKIP		:  assert(false); // already handled above
    default		:  assert(false); abort();
  }
}

static bool
doit(Operation op,
     PathInfo const *dst_path,
     PathInfo const *src_path, struct stat const *exp_stat,
     PathInfo const *show_path)
{
#if 0
  struct stat		st;

  if (lstat(dst_path->d, &st)!=-1) {
    if (global_args->do_keep &&
	(!S_ISDIR(exp_stat->st_mode) || S_ISDIR(st.st_mode))) {
      // when keep-mode is enable and, do nothing
      if (global_args->do_dry_run || global_args->verbosity>1) {
	WRITE_MSG(1, "keeping  '");
	write(1, show_path->d, show_path->l);
	WRITE_MSG(1, "'\n");
      }
      return true;
    }
    
  }
#endif
  
  if (global_args->do_dry_run || global_args->verbosity>1) {
    if      (op==opUNIFY)   WRITE_MSG(1, "linking  '");
    else if (op==opCOPY)    WRITE_MSG(1, "copying  '");
    else if (op==opDIR)     WRITE_MSG(1, "creating '");
    else if (op==opSKIP)    WRITE_MSG(1, "skipping '");
    else { assert(false); abort(); }

    Vwrite(1, show_path->d, show_path->l);
    WRITE_MSG(1, "'\n");
  }

  return (global_args->do_dry_run ||
	  ( op==opSKIP) ||
	  ( op==opUNIFY && Unify_unify(src_path->d, exp_stat, dst_path->d, false)) ||
	  ((op==opCOPY  ||
	    op==opDIR)  && Unify_copy (src_path->d, exp_stat, dst_path->d)));
}
     
static uint64_t
visitDirEntry(struct dirent const *ent)
{
  char const *			dirname  = ent->d_name;
  if (isDotfile(dirname)) return 0;

  uint64_t			res      = 1;
  PathInfo			src_path = global_info.state;
  PathInfo			src_d_path = {
    .d = dirname,
    .l = strlen(dirname)
  };
  char				path_buf[ENSC_PI_APPSZ(src_path, src_d_path)];
  struct stat			f_stat = { .st_dev = 0 };

  PathInfo_append(&src_path, &src_d_path, path_buf);

  
  if (lstat(dirname, &f_stat)==-1)
    perror("lstat()");
  else {
    Operation		op       = checkDirEntry(&src_path, &f_stat);
    PathInfo		dst_path = global_info.dst_list.root;
    char		dst_path_buf[ENSC_PI_APPSZ(dst_path, src_path)];

    PathInfo_append(&dst_path, &src_path, dst_path_buf);
    if (!doit(op, &dst_path, &src_d_path, &f_stat, &src_path))
      perror(src_path.d);
    else if (op==opDIR) {
      res = visitDir(dirname, &f_stat);
      if (!global_args->do_dry_run &&
	  !Unify_setTime(dst_path.d, &f_stat))
	perror("utime()");
    }
    else
      res = 0;
  }

  return res;
}

#include "vcopy-init.hc"

int main(int argc, char *argv[])
{
  struct Arguments	args = {
    .mode		=  mdVSERVER,
    .do_dry_run		=  false,
    .verbosity		=  0,
    .local_fs		=  false,
  };
  uint64_t		res;

  global_args = &args;
  while (1) {
    int		c = getopt_long(argc, argv, "nvcx",
				CMDLINE_OPTIONS, 0);
    if (c==-1) break;

    switch (c) {
      case CMD_HELP		:  showHelp(1, argv[0], 0);
      case CMD_VERSION		:  showVersion();
      case CMD_MANUALLY		:  args.mode       = mdMANUALLY; break;
      case CMD_STRICT		:  args.is_strict  = true; break;
      case 'n'			:  args.do_dry_run = true; break;
      case 'x'			:  args.local_fs   = true; break;
      case 'v'			:  ++args.verbosity;       break;
      default		:
	WRITE_MSG(2, "Try '");
	WRITE_STR(2, argv[0]);
	WRITE_MSG(2, " --help\" for more information.\n");
	return EXIT_FAILURE;
	break;
    }
  }

  if (argc==optind) {
    WRITE_MSG(2, "No directory/vserver given\n");
    return EXIT_FAILURE;
  }

  switch (args.mode) {
    case mdMANUALLY	:  initModeManually(argc-optind, argv+optind); break;
    case mdVSERVER	:  initModeVserver (argc-optind, argv+optind); break;
    default		:  assert(false); return EXIT_FAILURE;
  }

  if (global_args->verbosity>3)
    WRITE_MSG(1, "Starting to traverse directories...\n");

  Echdir(global_info.src_list.root.d);
  res = visitDir("/", 0);
  
#ifndef NDEBUG
  {
    MatchList_destroy(&global_info.dst_list);
    MatchList_destroy(&global_info.src_list);
  }
#endif

  return res>0 ? 1 : 0;
}
