#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, recursive, mode, fangs, rcs)
     char *absolute, *relative;
     int recursive, mode, fangs, rcs;
{
  FILE *linkfile = NULL;
  DIR *dir;
  char abs[PATH_MAX], rel[PATH_MAX];
  char *directoryname, *linkname, *fname;
  char *lname, *sname;
  char nextold[PATH_MAX], nextnew[PATH_MAX];
  struct dirent *curent;
  struct stat info;
  filelist dirlist, linklist, storelist;
  int length;

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

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

  makefilelist(&dirlist);
  makefilelist(&linklist);
  makefilelist(&storelist);

  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, "..") &&
	  (strcmp(curent->d_name, "RCS") || rcs))
	{
	  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_IFLNK)
	    savefile(&linklist, curent->d_name);
	  else
	    if ((info.st_mode & S_IFMT) == S_IFDIR)
	      savefile(&dirlist, curent->d_name);
	    else
	      {
		length = strlen(curent->d_name);
		if (length > 3 &&
		    curent->d_name[0] == '.' &&
		    curent->d_name[length-2] == ',' &&
		    curent->d_name[length-1] == 'l')
		  savefile(&storelist, curent->d_name);
	      }
	}
    }

  closedir(dir);

  if (mode == STORE_LINKS)
    {
      while (linkname = getfile(&linklist))
	{
	  lname = ereadlink(linkname); /* no trap for NULL; "can't happen" */
	  sname = ereadfile(linkname);

	  if (sname == NULL || strcmp(sname, lname))
	      {
		fprintf(stdout, "%s store out of date; updating\n", linkname);
		if (fangs)
		  ewritefile(linkname, lname);
	      }
	  else
	    fprintf(stdout, "%s store up to date.\n", linkname);
	}
    }
  else /* RETRIEVE_LINKS */
    {
      while (fname = getfile(&storelist))
	{
	  fname++; 				/* strip "." */
	  fname[strlen(fname)-2] = '\0';	/* strip ",l" */

	  lname = ereadlink(fname);
	  sname = ereadfile(fname); /* no trap for NULL; "can't happen" */
	  if (lname == NULL || strcmp(sname, lname))
	    {
	      fprintf(stdout, "%s link out of date; updating\n", fname);

	      if (fangs)
		{
		  if (lname != NULL)
		    if (unlink(fname))
		      {
			fprintf(stderr, "%s: error %d from unlink(%s)\n",
				progname, errno, fname);
			exit(1);
		      }

		  if (symlink(sname, fname))
		    {
		      fprintf(stderr, "%s: error %d from symlink(%s)\n",
			      progname, errno, fname);
		      exit(1);
		    }
		}
	    }
	  else
	    fprintf(stdout, "%s link up to date\n", fname);
	}
    }

  freefilelist(&linklist);
  freefilelist(&storelist);

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

  freefilelist(&dirlist);
}

void usage()
{
  fprintf(stderr,
	  "usage: cd top; %s [-store | -retrieve] [-r] [-n] [-rcs]\n",
	  progname);
  exit(1);
}

int main(argc, argv)
     int argc;
     char **argv;
{
  char curdir[PATH_MAX];
  char *partial = ".";
  int mode = 0, recursive = 0, fangs = 1, rcs = 0;

  progname = argv[0];

  if (getcwd(curdir, sizeof(curdir)) == NULL)
    {
      fprintf(stderr, "%s: getcwd() failed with error %d\n", progname, errno);
      exit(1);
    }

  argv++;
  while (*argv)
    {
      if (!strncmp("-store", *argv, 3))
	mode = STORE_LINKS;
      else
	if (!strncmp("-retrieve", *argv, 3))
	  mode = RETRIEVE_LINKS;
	else
	  if (!strcmp("-r", *argv))
	    recursive = 1;
	  else
	    if (!strcmp("-n", *argv))
	      fangs = 0;
	    else
	      if (!strcmp("-rcs", *argv))
		rcs = 1;
	      else
		usage();
      argv++;
    }

  if (mode == 0)
    {
      fprintf(stderr, "usage: -store or -retrieve must be specified\n");
      usage();
    }

  process(curdir, partial, recursive, mode, fangs, rcs);
  exit(0);
}
