
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <sys/dir.h>

char *xmalloc();

typedef struct pathstack PATHSTACK;
struct pathstack {
  PATHSTACK *next;
  char *pathp;
  int inum;
};

int debug = 0;			/* disables unlink and rmdir */
int pathnamesize = 256;		/* max size of pathname, gets increased 
							as needed */
char *pathname;
char *progname;

int fflag, rflag, iflag;

main(argc, argv)
int argc;
char **argv;
{
  int i, j, err = 0;

  pathname = xmalloc(pathnamesize);
  *pathname = '\0';

  for (progname = argv[0] + strlen(argv[0]); progname > argv[0]; progname--)
    if (*progname == '/') {
      progname++;
      break;
    }

  for (i = 1; i < argc; i++) {
    if (argv[i][0] != '-' || argv[i][1] == '\0')
      break;
    for (j = 1; argv[i][j] != '\0'; j++) {
      if (strcmp(progname, "rmdir") == 0) {
	switch (argv[i][j]) {
	case 'D':
	  debug++;
	  break;
	default:
	  goto bad_opt;
	}
      }
      else {
	switch (argv[i][j]) {
	case 'f':
	  fflag++;
	  break;
	case 'r':
	  rflag++;
	  break;
	case 'i':
	  iflag++;
	  break;
	case 'D':
	  debug++;
	  break;
	default:
	bad_opt:
	  fprintf(stderr, "%s: unknown option %s\n", progname, argv[i]);
	  exit (1);
	}
      }
    }
  }

  for (; i < argc; i++) {
    if (strlen(argv[i]) > pathnamesize) {
      free(pathname);
      pathname = xmalloc(pathnamesize = 2 * strlen(argv[i]));
    }
    strcpy(pathname, argv[i]);
    err += rm(argv[i]);
  }

  return err;
}

/*
 * remove file/directory after checking appropriate things.
 * return nonzero if file is not removed.
 */
rm(file)
char *file;
{
  struct stat sbuf;
  int dflag = 0;
  int err;
  extern char *sys_errlist[];
  extern int errno;

  if (lstat(file, &sbuf) < 0) {
    printf("%s: %s nonexistent\n", progname, file);
    return 1;
  }

  if (strcmp(progname, "rmdir") == 0)
    return trmdir(file);

  if ((sbuf.st_mode & S_IFMT) == S_IFDIR) {
    dflag++;
    if (!rflag) {
      printf("%s: %s directory\n", progname, file);
      return 1;
    }
  }
  if (iflag) {
    printf("%s: remove %s%s? ", progname, dflag ? "directory " : "", file);
    if (!yesno())
      return 1;
  }
  
  if (!iflag && !fflag && !dflag && access(file, W_OK) < 0) {
    printf("%s: override protection %o for %s? ", progname,
	   sbuf.st_mode & ~S_IFMT, file);
    if (!yesno())
      return 1;
  }

  if (!dflag) {
    if (tunlink(file) < 0) {
      if (!fflag)
	printf("%s: %s not removed: %s\n", progname, file,
	       sys_errlist[errno]);
      return 1;
    }
    return 0;
  }
  if ((err = clear_directory(file)) == 0)
    if ((err = trmdir(file)) != 0)
      if (!fflag)
	printf("%s: %s: %s\n", progname, file, sys_errlist[errno]);
  return err;
}

trmdir(file)
char *file;
{
  if (debug) {
    printf("trmdir(%s)\n", file);
    return 0;
  } else
    return rmdir(file);
}

tunlink(file)
char *file;
{
  if (debug) {
    printf("tunlink(%s)\n", file);
    return 0;
  } else
    return unlink(file);
}

yesno()
{
  char c, c2;

  c = getchar();
  if (c == '\n')
    return 0;
  while ((c2 = getchar()) != '\n' && c2 != EOF)
    ;

  return (c == 'y');
}

/*
 * read a directory and remove all its entries, avoiding use of chdir.
 * returns 0 for success, error count for failure.
 */
clear_directory(file)
char *file;
{
  DIR *opendir(), *dirp;
  struct direct *readdir(), *dp;
  char *s, *rindex();
  int err = 0;

  static PATHSTACK *pathstack = NULL;
  PATHSTACK pathframe;		/* pathstack is threaded through here */
  PATHSTACK *pp;		/* temporary */

  if ((dirp = opendir(file)) == NULL) {
    printf("%s not changed\n", file);
    return 1;
  }

  /* append '/' and the filename to current pathname, take care of the
     file (which could result in recursive calls), and take the filename
     back off. */

  for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
    if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0)
      continue;
    if (DIRSIZ(dp) + strlen(pathname) > pathnamesize) {
      /* satisfy GNU requirement that filenames can be arbitrarily long. */
      char *p = xmalloc(pathnamesize *= 4);
      strcpy(p, pathname);
      /* update the all the pointers in the stack to use the new area */
      for (pp = pathstack; pp != NULL; pp = pp->next)
	pp->pathp += p - pathname;
      pathname = p;
    }

    /* if i-number has already appeared, there's an error */
    if (check_stack(pathstack, dp) != 0)
      return 1;

    /* put info into current stack frame, and update pointer */
    if ((pathframe.next = pathstack) == NULL)
      pathframe.pathp = pathname + strlen(pathname);
    else
      pathframe.pathp = pathstack->pathp + strlen(pathstack->pathp);
    pathframe.inum = dp->d_ino;
    pathstack = &pathframe;

    strcat(pathstack->pathp, "/");    
    strcat(pathstack->pathp, dp->d_name);

    if (rm(pathname) != 0)
      err++;

    *pathstack->pathp = '\0';
    pathstack = pathstack->next;/* pop stack */
  }
  closedir(dirp);
  return err;
}

check_stack(stack, dp)
PATHSTACK *stack;
struct direct *dp;
{
  PATHSTACK *p;
  char c;			/* for internal check */

  for (p = stack; p != NULL; p = p->next) {
    if (p->inum == dp->d_ino) {
      printf("%s: WARNING: Circular directory structure.\n", progname);
      
      printf("This almost certainly means you have a corrupted file system.\n");
      printf("NOTIFY YOUR SYSTEM MANAGER.\n");
      printf("Cycle detected: %s\nis the same as\n\t", pathname);
      c = *p->pathp;
      *p->pathp = '\0';	/* truncate pathname */
      printf("%s\n", pathname);
      if (c != '/') {
	printf("Internal error in %s: %c should have been '/'\n",
	       progname, c);
	exit (1);
      }
      *p->pathp = '/';	/* put it back */
      printf("Continue with %s? ", progname);
      if (!yesno())
	exit (1);
      return 1;
    }
  }
  return 0;
}

char *xmalloc(size)
int size;
{
  char *p, *malloc();

  if ((p = malloc(size)) == NULL) {
    printf("%s: out of memory\n", progname);
    exit (2);
  }
  return p;
}

/*
Local variables:
compile-command: "cc -o rm -g rm.c"
End:
*/
