#include <unistd.h>		/* readlink */
#include <limits.h>		/* PATH_MAX */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>		/* strcmp, memset */
#include <dirent.h>		/* opendir, etc. */
#include <sys/types.h>		/* lstat */
#include <sys/stat.h>		/* lstat */
#include <fcntl.h>		/* open */
#include <errno.h>

#define STORE_LINKS 1
#define RETRIEVE_LINKS 2

char *progname;

typedef struct _filename {
  struct _filename *next;
  char *name;
} filename;

typedef struct {
  filename *first, *current, *last;
} filelist;

void makefilelist(list)
     filelist *list;
{
  list->first = NULL;
  list->current = NULL;
  list->last = NULL;

  return;
}

void freefilelist(list)
     filelist *list;
{
  filename *i, *j = NULL;

  i = list->first;
  while (i != NULL)
    {
      j = i;
      i = i->next;
      free(j->name);
      free(j);
    }
}

void savefile(list, name)
     filelist *list;
     char *name;
{
  filename *new;

  new = malloc(sizeof(filename));
  if (new == NULL)
    {
      fprintf(stderr, "%s: error in savefile during malloc()\n", progname);
      exit(1);
    }

  new->name = malloc(strlen(name) + 1);
  if (new->name == NULL)
    {
      fprintf(stderr, "%s: error in savefile during malloc()\n", progname);
      exit(1);
    }

  strcpy(new->name, name);

  new->next = NULL;
  if (list->first == NULL)
    list->first = new;
  if (list->current == NULL)
    list->current = new;
  if (list->last != NULL)
    list->last->next = new;
  list->last = new;
}

char *getfile(list)
     filelist *list;
{
  char *ptr;

  if (list->current)
    {
      ptr = list->current->name;
      list->current = list->current->next;
      return ptr;
    }
  else
    return NULL;
}

/*
 * Readlink routine that deals with the errors.
 */
char *ereadlink(linkname)
     char *linkname;
{
  static char link[PATH_MAX];
  int length;

  length = readlink(linkname, link, sizeof(link));
  if (length == -1)
    {
      if (errno == ENOENT)
	return NULL;

      fprintf(stderr, "%s: error %d from readlink(%s)\n", progname,
	      errno, linkname);
      exit(1);
    }
  link[length] = '\0'; /* bug? */

  return link;
}

void ewritefile(linkname, value)
     char *linkname, *value;
{
  char fname[PATH_MAX];
  int fd, len;

  sprintf(fname, ".%s,l", linkname);

  chmod(fname, 0644);
  fd = open(fname, O_WRONLY | O_CREAT | O_TRUNC, 0644);
  if (fd == -1)
    {
      fprintf(stderr, "%s: open for write on %s failed with errno %d\n",
	      progname, fname, errno);
      exit(1);
    }

  len = write(fd, value, strlen(value));
  if (len == -1)
    {
      fprintf(stderr, "%s: error %d writing %s\n",
	      progname, errno, fname);
      exit(1);
    }

  if (len < strlen(value))
    {
      fprintf(stderr, "%s: incomplete write (%d/%d bytes) to %s\n",
	      progname, len, strlen(value), fname);
      exit(1);
    }

  if (close(fd))
    {
      fprintf(stderr, "%s: error %d closing %s\n",
	      progname, errno, fname);
      exit(1);
    }
}

char *ereadfile(linkname)
     char *linkname;
{
  static char link[PATH_MAX];
  char fname[PATH_MAX];
  int fd, len;

  sprintf(fname, ".%s,l", linkname);
  fd = open(fname, O_RDONLY);
  if (fd == -1)
    {
      if (errno == ENOENT)
	return NULL;

      fprintf(stderr, "%s: open on %s failed with errno %d\n",
	      progname, fname, errno);
      exit(1);
    }

  len = read(fd, link, sizeof(link));
  close(fd);
  if (len == -1)
    {
      fprintf(stderr, "%s: read of %s failed with errno %d\n",
	      progname, fname, errno);
      exit(1);
    }

  if (len < 1 ||
      (len == 1 && link[0] == '\n'))
    {
      fprintf(stderr, "%s: bad link file: %s\n",
	      progname, fname);
      exit(1);
    }

  if (link[len-1] == '\n')
    len--;
  link[len] = '\0';

  return link;
}

void process(absolute, relative, fangs)
     char *absolute, *relative;
     int fangs;
{
  DIR *dir;
  char abs[PATH_MAX], rel[PATH_MAX];
  char create[PATH_MAX+2]; /* Extra "./"... Is that cool? */
  char *directoryname;
  struct dirent *curent;
  struct stat info;
  filelist dirlist;

  fprintf(stdout, "Processing %s\n", absolute);

  if (chdir(absolute))
    {
      fprintf(stderr, "%s: error %d from chdir()\n", progname, errno);
      exit(1);
    }

  if (fangs)
    {
      if (mkdir(relative, 0755))
	{
	  if (errno == EROFS)
	    fprintf(stderr,
		    "%s: warning from mkdir(%s): Read-only filesystem\n",
		    progname, relative);
	  else
	    if (errno != EEXIST)
	      {
		fprintf(stderr, "%s: error %d from mkdir(%s)\n",
			progname, errno, relative);
		exit(1);
	      }
	}
    }
  else
    fprintf(stdout, "mkdir %s\n", relative);

  makefilelist(&dirlist);

  dir = opendir(".");
  if (dir == NULL)
    {
      fprintf(stderr, "%s: could not open directory %s\n",
	      progname, relative);
      exit(1);
    }

  while (curent = readdir(dir))
    {
      if (strcmp(curent->d_name, ".") && strcmp(curent->d_name, ".."))
	{
	  if (lstat(curent->d_name, &info))
	    {
	      fprintf(stderr, "%s: error %d from lstat(%s)\n", progname,
		      errno, curent->d_name);
	      exit(1);
	    }

	  if ((info.st_mode & S_IFMT) == S_IFDIR)
	    savefile(&dirlist, curent->d_name);
	}
    }

  closedir(dir);

  while (directoryname = getfile(&dirlist))
    {
      sprintf(abs, "%s/%s", absolute, directoryname);
      sprintf(rel, "%s/%s", relative, directoryname);
      process(abs, rel, fangs);
    }

  freefilelist(&dirlist);
}

void usage()
{
  fprintf(stderr,
	  "usage: %s [-n] /sourcedir /destdir\n",
	  progname);
  exit(1);
}

int main(argc, argv)
     int argc;
     char **argv;
{
  char *sourcedir, *destdir;
  int fangs = 1;

  progname = *argv++;

  if (argc != 3 && argc != 4)
    usage();

  if (argc == 4)
    {
      if (strcmp(*argv, "-n"))
        usage();

      fangs = 0;
      argv++;
    }

  sourcedir = *argv++;
  destdir = *argv++;

  process(sourcedir, destdir, fangs);
  exit(0);
}
