#ident "$Id: walk.c,v 1.2 2001/07/27 15:37:08 hpa Exp $"
/* ----------------------------------------------------------------------- *
 *   
 *   Copyright 2001 H. Peter Anvin - All Rights Reserved
 *
 *   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, Inc., 675 Mass Ave, Cambridge MA 02139,
 *   USA; either version 2 of the License, or (at your option) any later
 *   version; incorporated herein by reference.
 *
 * ----------------------------------------------------------------------- */

/*
 * walk.c
 *
 * Functions to walk the file tree
 */

#include <stdlib.h>
#include <stdio.h>
#include <limits.h>
#include <utime.h>
#include <string.h>
#include <errno.h>
#include <dirent.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "mkzftree.h"

static int munge_path(const char *inpath, const char *outpath, struct stat *st)
{
  FILE *in, *out;
  int err = 0;
  struct utimbuf ut;

  in = fopen(inpath, "rb");
  if ( !in )
    return EX_NOINPUT;
  out = fopen(outpath, "wb");
  if ( !out ) {
    fclose(in);
    return EX_CANTCREAT;
  }

  if ( spawn_worker() ) {
    err = munger(in, out, st->st_size);
    fclose(in);
    fclose(out);
    
#ifdef HAVE_LCHOWN
    lchown(outpath, st->st_uid, st->st_gid);
#endif
    if ( !S_ISLNK(st->st_mode) ) {
#ifndef HAVE_LCHOWN
      chown(outpath, st->st_uid, st->st_gid);
#endif
      chmod(outpath, st->st_mode);
      ut.actime  = st->st_atime;
      ut.modtime = st->st_mtime;
      utime(outpath, &ut);
    }

    end_worker(err);
  } else {
    fclose(in);
    fclose(out);
  }

  return err;
}

int munge_tree(const char *intree, const char *outtree)
{
  char *in_path, *out_path, *in_file, *out_file;
  DIR *thisdir;
  struct dirent *dirent;
  struct stat dirst;
  int err = 0;
  
  /* Construct buffers with the common filename prefix, and point to the end */

  in_path = xmalloc(strlen(intree) + NAME_MAX + 2);
  out_path = xmalloc(strlen(outtree) + NAME_MAX + 2);

  strcpy(in_path, intree);
  strcpy(out_path, outtree);

  in_file = strchr(in_path, '\0');
  out_file = strchr(out_path, '\0');

  *in_file++ = '/';
  *out_file++ = '/';

  /* Get directory information */
  if ( stat(intree, &dirst) ) {
    message(vl_error, "%s: Failed to stat directory %s: %s\n",
	    program, intree, strerror(errno));
    return EX_NOINPUT;
  }

  /* Open the directory */
  thisdir = opendir(intree);
  if ( !thisdir ) {
    message(vl_error, "%s: Failed to open directory %s: %s\n",
	    program, intree, strerror(errno));
    return EX_NOINPUT;
  }

  /* Create output directory */
  if ( mkdir(outtree, 0700) ) {
    message(vl_error, "%s: Cannot create output directory %s: %s\n",
	    program, outtree, strerror(errno));
    return EX_CANTCREAT;
  }

  while ( (dirent = readdir(thisdir)) != NULL ) {
    if ( !strcmp(dirent->d_name, ".") ||
	 !strcmp(dirent->d_name, "..") )
      continue;			/* Ignore . and .. */
    
    strcpy(in_file, dirent->d_name);
    strcpy(out_file, dirent->d_name);
    
    err = munge_entry(in_path, out_path, &dirst);
    if ( err )
      break;
  }
  closedir(thisdir);
  
  free(in_path);
  free(out_path);
  
  return err;
}


int munge_entry(const char *in_path, const char *out_path, const struct stat *dirst)
{
  struct stat st;
  struct utimbuf ut;
  int err = 0;

  message(vl_filename, "%s -> %s\n", in_path, out_path);

  if ( lstat(in_path, &st) ) {
    message(vl_error, "%s: Failed to stat file %s: %s\n",
	    program, in_path, strerror(errno));
    return EX_NOINPUT;
  }
  
  if ( S_ISREG(st.st_mode) ) {
    if ( st.st_nlink > 1 ) {
      /* Hard link. */
      const char *linkname;
      
      if ( (linkname = hash_find_file(&st)) != NULL ) {
	/* We've seen it before, hard link it */
	
	if ( link(linkname, out_path) ) {
	  message(vl_error, "%s: hard link %s -> %s failed: %s\n",
		  program, out_path, linkname, strerror(errno));
	  return EX_CANTCREAT;
	}
      } else {
	/* First encounter, compress and enter into hash */
	if ( (err = munge_path(in_path, out_path, &st)) != 0 ) {
	  message(vl_error, "%s: %s: %s", program, in_path, strerror(errno));
	  return err;
	}
	hash_insert_file(&st, out_path);
      }
    } else {
      /* Singleton file; no funnies */
      if ( (err = munge_path(in_path, out_path, &st)) != 0 ) {
	message(vl_error, "%s: %s: %s", program, in_path, strerror(errno));
	return err;
      }
    }
  } else if ( S_ISDIR(st.st_mode) ) {
    /* Recursion: see recursion */
    if ( !onedir && (!onefs || (dirst && dirst->st_dev == st.st_dev)) ) {
      if ( (err = munge_tree(in_path, out_path)) != 0 )
	return err;
    } else if ( do_mkdir ) {
      /* Create stub directories */
      if ( mkdir(out_path, st.st_mode) ) {
	message(vl_error, "%s: %s: %s", program, out_path, strerror(errno));
	return EX_CANTCREAT;
      }
    }
  } else if ( S_ISLNK(st.st_mode) ) {
    int chars;
#ifdef PATH_MAX
#define BUFFER_SLACK PATH_MAX
#else
#define BUFFER_SLACK BUFSIZ
#endif
    int buffer_len = st.st_size + BUFFER_SLACK + 1;
    char *buffer = xmalloc(buffer_len);
    if ( (chars = readlink(in_path, buffer, buffer_len)) < 0 ) {
      message(vl_error, "%s: readlink failed for %s: %s\n",
	      program, in_path, strerror(errno));
      return EX_NOINPUT;
    }
    buffer[chars] = '\0';
    if ( symlink(buffer, out_path) ) {
      message(vl_error, "%s: symlink %s -> %s failed: %s\n",
	      program, out_path, buffer, strerror(errno));
      return EX_CANTCREAT;
    }
  } else {
    if ( st.st_nlink > 1 ) {
      /* Hard link. */
      const char *linkname;
      
      if ( (linkname = hash_find_file(&st)) != NULL ) {
	/* We've seen it before, hard link it */
	
	if ( link(linkname, out_path) ) {
	  message(vl_error, "%s: hard link %s -> %s failed: %s\n",
		  program, out_path, linkname, strerror(errno));
	  return EX_CANTCREAT;
	}
      } else {
	/* First encounter, create and enter into hash */
	if ( mknod(out_path, st.st_mode, st.st_rdev) ) {
	  message(vl_error, "%s: mknod failed for %s: %s\n",
		  program, out_path, strerror(errno));
	  return EX_CANTCREAT;
	}
	hash_insert_file(&st, out_path);
      }
    } else {
      /* Singleton node; no funnies */
      if ( mknod(out_path, st.st_mode, st.st_rdev) ) {
	message(vl_error, "%s: mknod failed for %s: %s\n",
		program, out_path, strerror(errno));
	return EX_CANTCREAT;
      }
    }
  }
  
  /* This is done by munge_path() for files */
  if ( !S_ISREG(st.st_mode) ) {
#ifdef HAVE_LCHOWN
    lchown(out_path, st.st_uid, st.st_gid);
#endif
    if ( !S_ISLNK(st.st_mode) ) {
#ifndef HAVE_LCHOWN
      chown(out_path, st.st_uid, st.st_gid);
#endif
      chmod(out_path, st.st_mode);
      ut.actime  = st.st_atime;
      ut.modtime = st.st_mtime;
      utime(out_path, &ut);
    }
  }

  return err;
}

