
/* usage: split_xattr options srcdir dstdir
 *    options:  --files-from file
 *              --recycle olddst
 *              --crtime
 * 
 * creates dstdir, a repository of xattr containers from srcdir
 * dstdir should *not* exist prior to invocation.
 * 
 * with the --files-from file option, the contructed repository is 
 * pruned to only include those files and directories listed in file.
 *
 * with the --recycle olddst option, an attempt is made to
 * move existing xattrs containers from olddst.
 * When an xattr container is created, its mtime is set to the ctime
 * of the file.  If the mtime of the xattr container
 * in olddst is equal to the ctime of the file, then it is moved
 * from olddst to dstdir.
 *
 * the --crtime flag causes the creation times of all files to
 * be preserved -- using this flag causes an xattr container
 * file to be generated for *every* file and directory
 *
 * Returns -1 if errors detected, and 0 otherwise.
 *
 */

#include "util.h"
#include "xattr_util.h"



static int return_value = 0;
static int source_name_len = 0;
static char *destination_name = 0;
static char *linkdir_name = 0;

static int crtime = 0;

static char dblname[MAXLEN];
static char linkname[MAXLEN];


void process_xattrs(const char *itemname, const struct stat *itemstat, 
                    const char *dirname, const char *basename)
{
   struct stat linkstat;
   int gotlink;

   if (crtime || has_locks(itemstat) || has_xattr(itemname)) {

      gotlink = 0;

      if (snprintf(dblname, MAXLEN, "%s%s/%s%s", 
         destination_name, 
         dirname + source_name_len, 
         DBL_PREFIX,
         basename) >= MAXLEN) overflow();

      if (linkdir_name) {
         if (snprintf(linkname, MAXLEN, "%s%s/%s%s", 
            linkdir_name, 
            dirname + source_name_len, 
            DBL_PREFIX,
            basename) >= MAXLEN) overflow();

         if (lstat(linkname, &linkstat) == 0 &&
             S_ISREG(linkstat.st_mode) &&
             linkstat.st_mtime == itemstat->st_ctime) {

            if (rename(linkname, dblname)) {
               fprintf(stderr, "split_xattr: could not move %s to %s\n", 
                       linkname, dblname);
               return_value = -1;
            }
            else {
               gotlink = 1;
            }
         }
      }

      if (!gotlink) {

         if ( split_xattr(itemname, itemstat, dblname, crtime) ||
              set_ctime(dblname, itemstat) ) {

               fprintf(stderr,"split_xattr: error making %s\n", dblname);
               return_value = -1;

         }

      }
   }
}


void dirwalk(const char *dirname, const struct stat *dirstat, int walk_state)
{
   char itemname[MAXLEN];
   DIR *dirlist;
   struct dirent *diritem; 
   struct stat itemstat;
   int walk_state1;


   if (snprintf(dblname, MAXLEN, "%s%s", destination_name, 
      dirname + source_name_len) >= MAXLEN) overflow();

   if (mkdir(dblname, 0777)) {
      fprintf(stderr, "split_xattr: failed to create %s\n", dblname);
      return_value = -1;
      return;
   }

   dirlist = opendir(dirname);

   if (!dirlist) {
      fprintf(stderr, "split_xattr: internal error -- opendir failed\n");
      return_value = -1;
      return;
   }

   while ( (diritem = readdir(dirlist)) ) {

      if (strcmp(diritem->d_name, ".") == 0 
          || strcmp(diritem->d_name, "..") == 0)
         continue;

      if (snprintf(itemname, MAXLEN, "%s/%s", 
          dirname, diritem->d_name) >= MAXLEN) overflow();

      if (is_prefix(DBL_PREFIX, diritem->d_name)) {
         fprintf(stderr, 
                 "split_xattr: name conflict: %s\n",
                 itemname);
         return_value = -1;
      }

      walk_state1 = walk_state;

      if (walk_state1 == 0) {
	    walk_state1 = lookup_name(itemname + source_name_len + 1);
	    if (walk_state1 == -1) continue; /* pruning */
      }

      if (lstat(itemname, &itemstat)) {
         fprintf(stderr, "split_xattr: lstat failed on %s\n", itemname);
         return_value = -1;
         continue;
      }


      if (!(S_ISDIR(itemstat.st_mode) || S_ISREG_OR_LNK(itemstat.st_mode)))
         continue;


      if (walk_state1 == 1 && S_ISREG_OR_LNK(itemstat.st_mode)) 
         process_xattrs(itemname, &itemstat, dirname, diritem->d_name);

      if (S_ISDIR(itemstat.st_mode)) {
	 dirwalk(itemname, &itemstat, walk_state1);
      }

   }

   closedir(dirlist);

   if (walk_state == 1)
      process_xattrs(dirname, dirstat, dirname, ".");
}

void usage()
{
   fprintf(stderr, "usage: split_xattr options srcdir dstdir\n");
   fprintf(stderr, "  options:  --files-from file\n");
   fprintf(stderr, "            --recycle olddst\n");
   fprintf(stderr, "            --crtime\n");
}


int main(int argc, char **argv)
{
   char *fname, *lname, *srcname, *dstname;
   struct stat srcstat, dststat, linkstat;
   int srcname_len, dstname_len, lname_len;
   int walk_state;

   int i;

   fname = 0;
   lname = 0;
   
   i = 1;
   while (i < argc) {
      if (strcmp(argv[i], "--crtime") == 0) {
         i++;
         crtime = 1;
      }
      else if (strcmp(argv[i], "--files-from") == 0) {
         if (i == argc-1) {
            usage();
            return -1;
         }
         i++;
         fname = argv[i];
         i++;
      }
      else if (strcmp(argv[i], "--recycle") == 0) {
         if (i == argc-1) {
            usage();
            return -1;
         }
         i++;
         lname = argv[i];
         i++;
      }
      else
         break;
   }

   if (i != argc-2) {
      usage();
      return -1;
   }

   srcname = argv[argc-2];
   dstname = argv[argc-1];

   if (fname) {
      collect_names(fname);
      walk_state = 0;
   }
   else {
      walk_state = 1;
   }

   srcname_len = strlen(srcname);
   if (srcname[srcname_len-1] == '/') {
      srcname[srcname_len-1] = '\0';
      srcname_len--;
   }

   dstname_len = strlen(dstname);
   if (dstname[dstname_len-1] == '/')
      dstname[dstname_len-1] = '\0';

   if (lstat(srcname, &srcstat) || !S_ISDIR(srcstat.st_mode)) {
      usage();
      return -1;
   }

   if (lname) {

      lname_len = strlen(lname);
      if (lname[lname_len-1] == '/')
         lname[lname_len-1] = '\0';

      if (lstat(lname, &linkstat) || !S_ISDIR(linkstat.st_mode)) {
         usage();
         return -1;
      }
   }

   if (!lstat(dstname, &dststat)) {
      fprintf(stderr, "split_xattr: %s already exists\n", dstname);
      return -1;
   }

   source_name_len = srcname_len;

   destination_name = dstname;

   linkdir_name = lname;

   dirwalk(srcname, &srcstat, walk_state);

   return return_value;

}
