/* `rm' file deletion utility for GNU.
   Copyright (C) 1988, 1989, 1990 Free Software Foundation, Inc.

   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; either version 1, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */

/* Written by Paul Rubin, David MacKenzie, and Richard Stallman. */

/* MS-DOS port (c) 1990 by Thorsten Ohl, ohl@gnu.ai.mit.edu
   This port is also distributed under the terms of the
   GNU General Public License as published by the
   Free Software Foundation.

   Please note that this file is not identical to the
   original GNU release, you should have received this
   code as patch to the official release.  */

#ifdef MSDOS
static char RCS_Id[] =
  "$Header: e:/gnu/fileutil/RCS/rm.c 1.4.0.3 90/09/20 08:46:14 tho Exp $";

static char Program_Id[] = "rm";
static char RCS_Revision[] = "$Revision: 1.4.0.3 $";

#define VERSION \
  "GNU %s, Version %.*s (compiled %s %s for MS-DOS)\n", Program_Id, \
  (sizeof RCS_Revision - 14), (RCS_Revision + 11), __DATE__, __TIME__

#define COPYING \
  "This is free software, distributed under the terms of the\n" \
  "GNU General Public License.  For details, see the file COPYING.\n"
#endif /* MSDOS */

#include <stdio.h>
#include <getopt.h>
#include <sys/types.h>
#include <errno.h>
#include "system.h"

#ifdef STDC_HEADERS
#include <stdlib.h>
#else
char *malloc ();
char *realloc ();

extern int errno;
#endif

#ifdef MSDOS
#include <string.h>
#include <malloc.h>
#include <io.h>
#include <direct.h>

#include <gnulib.h>

extern  void main (int argc, char **argv);
static  int rm (void);
static  int remove_file (struct stat *statp);
static  int remove_dir (struct stat *statp);
static  int clear_directory (struct stat *statp);
static  int yesno (void);
static  char *basename (char *name);
static  char *stpcpy (char *dest, char *source);
static  void usage (void);

extern  int eaccess (char *path, int mode);
extern  int eaccess_stat (struct stat *statp, int mode);

static  void strip_trailing_slashes (char **path);
#define strip_trailing_slashes(path)	strip_trailing_slashes (&path)
#define check_stack(stck, ino)		0
#define unlink(name)			force_unlink (name)
static int force_unlink (char *filename);

#else /* not MSDOS */

char *basename ();
char *stpcpy ();
char *xmalloc ();
char *xrealloc ();
int check_stack ();
int clear_directory ();
int eaccess_stat ();
int remove_dir ();
int remove_file ();
int rm ();
int yesno ();
void error ();
void strip_trailing_slashes ();
void usage ();

#endif /* not MSDOS */


/* Path of file now being processed; extended as necessary. */
char *pathname;

/* Number of bytes currently allocated for `pathname';
   made larger when necessary, but never smaller.  */
int pnsize;

/* Name this program was run with.  */
char *program_name;

/* If nonzero, display the name of each file removed. */
int verbose;

/* If nonzero, ignore nonexistant files. */
int ignore_missing_files;

/* If nonzero, recursively remove directories. */
int recursive;

/* If nonzero, query the user about whether to remove each file. */
int interactive;

/* If nonzero, remove directories with unlink instead of rmdir, and don't
   require a directory to be empty before trying to unlink it.
   Only works for the super-user. */
int unlink_dirs;

/* Information for detecting attempted removal of `.' and `..'. */
dev_t dot_dev, dotdot_dev;
ino_t dot_ino, dotdot_ino;

struct option long_opts[] =
{
#ifdef MSDOS
  {"copying", 0, NULL, 30},
  {"version", 0, NULL, 31},
#endif
  {"directory", 0, &unlink_dirs, 1},
  {"force", 0, NULL, 'f'},
  {"interactive", 0, NULL, 'i'},
  {"recursive", 0, &recursive, 1},
  {"verbose", 0, &verbose, 1},
  {NULL, 0, NULL, 0}
};

void
main (argc, argv)
     int argc;
     char **argv;
{
  int err = 0;
  int c;
  int ind;
  struct stat stats;

  verbose = ignore_missing_files = recursive = interactive
    = unlink_dirs = 0;
  pnsize = 256;
  program_name = argv[0];
  pathname = xmalloc (pnsize);

  while ((c = getopt_long (argc, argv, "dfirvR", long_opts, &ind)) != EOF)
    {
      switch (c)
	{
	case 0:			/* Long option. */
	  break;
	case 'd':
	  unlink_dirs = 1;
	  break;
	case 'f':
	  ignore_missing_files = 1;
	  interactive = 0;
	  break;
	case 'i':
	  ignore_missing_files = 0;
	  interactive = 1;
	  break;
	case 'r':
	case 'R':
	  recursive = 1;
	  break;
	case 'v':
	  verbose = 1;
	  break;
#ifdef MSDOS
	case 30:
	  fprintf (stderr, COPYING);
	  exit (0);
	  break;
	case 31:
	  fprintf (stderr, VERSION);
	  exit (0);
	  break;
#endif
	default:
	  usage ();
	}
    }

  if (optind == argc)
    usage ();

#ifndef MSDOS			/* this fails at the root ... */
  if (lstat (".", &stats))
    error (1, errno, ".");
  dot_dev = stats.st_dev;
  dot_ino = stats.st_ino;
  if (lstat ("..", &stats))
    error (1, errno, "..");
  dotdot_dev = stats.st_dev;
  dotdot_ino = stats.st_ino;
#endif /* not MSDOS */

  for (; optind < argc; optind++)
    {
      int len;

      strip_trailing_slashes (argv[optind]);
      len = strlen (argv[optind]);
      if (len + 1 > pnsize)
	{
	  free (pathname);
	  pnsize = 2 * (len + 1);
	  pathname = xmalloc (pnsize);
	}
      strcpy (pathname, argv[optind]);
      err += rm ();
    }

  exit (err > 0);
}

/* Remove file or directory `pathname' after checking appropriate things.
   Return 0 if `pathname' is removed, 1 if not. */

int
rm ()
{
  struct stat path_stats;

  if (lstat (pathname, &path_stats))
    {
      if (errno == ENOENT && ignore_missing_files)
	return 0;
      error (0, errno, "%s", pathname);
      return 1;
    }

#ifndef MSDOS
  if ((path_stats.st_dev == dot_dev && path_stats.st_ino == dot_ino)
      || (path_stats.st_dev == dotdot_dev && path_stats.st_ino == dotdot_ino))
    {
      error (0, 0, "%s: cannot remove current directory or parent", pathname);
      return 1;
    }
#endif /* not MSDOS */

  if ((path_stats.st_mode & S_IFMT) == S_IFDIR && !unlink_dirs)
    return remove_dir (&path_stats);
  else
    return remove_file (&path_stats);
}

/* Query the user if appropriate, and if ok try to remove the
   non-directory `pathname', which STATP contains info about.
   Return 0 if `pathname' is removed, 1 if not. */

int
remove_file (statp)
     struct stat *statp;
{
  if (interactive)
    {
      fprintf (stderr, "%s: remove %s`%s'? ", program_name,
	       (statp->st_mode & S_IFMT) == S_IFDIR ? "directory " : "",
	       pathname);
      if (!yesno ())
	return 1;
    }

  if (verbose)
    printf ("  %s\n", pathname);

  if (unlink (pathname))
    {
      error (0, errno, "%s", pathname);
      return 1;
    }
  return 0;
}

/* If not in recursive mode, print an error message and return 1.
   Otherwise, query the user if appropriate, then try to recursively
   remove directory `pathname', which STATP contains info about.
   Return 0 if `pathname' is removed, 1 if not. */

int
remove_dir (statp)
     struct stat *statp;
{
  int err;
  int writable;

  if (!recursive)
    {
      error (0, 0, "%s: is a directory", pathname);
      return 1;
    }

#ifdef S_IFLNK
  if ((statp->st_mode & S_IFMT) == S_IFLNK)
    writable = 1;
  else
#endif
    writable = eaccess_stat (statp, W_OK) == 0;

  if (!writable)
    {
      error (0, 0, "%s: no write permission for directory", pathname);
      return 1;
    }

  if (interactive)
    {
      fprintf (stderr, "%s: recursively descend directory `%s'? ",
	       program_name, pathname);
      if (!yesno ())
	return 1;
    }

  if (verbose)
    printf ("  %s\n", pathname);

  err = clear_directory (statp);
  if (err == 0)
    {
      if (interactive)
	{
	  fprintf (stderr, "%s: remove directory `%s'? ",
		   program_name, pathname);
	  if (!yesno ())
	    return 1;
	}
      err = rmdir (pathname) != 0;
      if (err != 0)
	error (0, errno, "%s", pathname);
    }
  return err;
}

/* An element in a stack of pointers into `pathname'.
   `pathp' points to where in `pathname' the terminating '\0' goes
   for this level's directory name. */
struct pathstack
{
  struct pathstack *next;
  char *pathp;
  ino_t inum;
};

/* Linked list of pathnames of directories in progress in recursive rm.
   The entries actually contain pointers into `pathname'.
   `pathstack' is the current deepest level. */
static struct pathstack *pathstack = NULL;

/* Read directory `pathname' and remove all of its entries,
   avoiding use of chdir.
   On entry, STATP points to the results of stat on `pathname'.
   Return 0 for success, error count for failure.
   Upon return, `pathname' will have the same contents as before,
   but its address might be different; in that case, `pnsize' will
   be larger, as well. */

int
clear_directory (statp)
     struct stat *statp;
{
  DIR *dirp;
  struct direct *dp;
  char *name_space;		/* Copy of directory's filenames. */
  char *namep;			/* Current entry in `name_space'. */
  unsigned name_size;		/* Bytes allocated for `name_space'. */
  ino_t *inode_space;		/* Copy of directory's inodes. */
  ino_t *inodep;		/* Current entry in `inode_space'. */
  unsigned inode_size;		/* Bytes allocated for `inode_space'. */
  int name_length;		/* Length of filename in `namep' plus '\0'. */
  int pathname_length;		/* Length of `pathname'. */
  int err = 0;			/* Return status. */
  struct pathstack pathframe;	/* New top of stack. */
  struct pathstack *pp;		/* Temporary. */

  errno = 0;
  dirp = opendir (pathname);
  if (dirp == NULL)
    {
      error (0, errno, "%s", pathname);
      return 1;
    }

#ifdef MSDOS				/* stat () it ourselves ... */
  statp->st_size = 0L;
  for (dp = readdir (dirp); dp != NULL; dp = readdir (dirp))
	statp->st_size += strlen(dp->d_name) + 1;
  seekdir(dirp, 0L);
#endif /* MSDOS */

  name_size = statp->st_size;
  name_space = (char *) xmalloc (name_size);
  namep = name_space;

#ifndef MSDOS
  inode_size = statp->st_size;
  inode_space = (ino_t *) xmalloc (inode_size);
  inodep = inode_space;
#endif /* not MSDOS */

  while ((dp = readdir (dirp)) != NULL)
    {
      /* Skip "." and ".." (some NFS filesystems' directories lack them). */
      if (dp->d_name[0] != '.'
	  || (dp->d_name[1] != '\0'
	      && (dp->d_name[1] != '.' || dp->d_name[2] != '\0')))
	{
	  unsigned size_needed = (namep - name_space) + NLENGTH (dp) + 2;

	  if (size_needed > name_size)
	    {
	      char *new_name_space;

	      while (size_needed > name_size)
		name_size += 1024;

	      new_name_space = xrealloc (name_space, name_size);
	      namep += new_name_space - name_space;
	      name_space = new_name_space;
	    }
	  namep = stpcpy (namep, dp->d_name) + 1;

#ifndef MSDOS
	  if (inodep == inode_space + inode_size)
	    {
	      ino_t *new_inode_space;

	      inode_size += 1024;
	      new_inode_space = (ino_t *) xrealloc (inode_space, inode_size);
	      inodep += new_inode_space - inode_space;
	      inode_space = new_inode_space;
	    }
	  *inodep++ = dp->d_ino;
#endif /* not MSDOS */
	}
    }
  *namep = '\0';
  closedir (dirp);
  
  pathname_length = strlen (pathname);

  for (namep = name_space, inodep = inode_space; *namep != '\0';
       namep += name_length, inodep++)
    {
      name_length = strlen (namep) + 1;

      /* Satisfy GNU requirement that filenames can be arbitrarily long. */
      if (pathname_length + 1 + name_length > pnsize)
	{
	  char *new_pathname;

	  pnsize = (pathname_length + 1 + name_length) * 2;
	  new_pathname = xrealloc (pathname, pnsize);
	  /* Update the all the pointers in the stack to use the new area. */
	  for (pp = pathstack; pp != NULL; pp = pp->next)
	    pp->pathp += new_pathname - pathname;
	  pathname = new_pathname;
	}

      /* Add a new frame to the top of the path stack. */
      pathframe.pathp = pathname + pathname_length;
      pathframe.inum = *inodep;
      pathframe.next = pathstack;
      pathstack = &pathframe;

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

      *pathstack->pathp = '/';
      strcpy (pathstack->pathp + 1, namep);

      /* If the i-number has already appeared, there's an error. */
      if (check_stack (pathstack->next, pathstack->inum) || rm ())
	err++;

      *pathstack->pathp = '\0';
      pathstack = pathstack->next;	/* Pop the stack. */
    }
  free (name_space);
#ifndef MSDOS
  free (inode_space);
#endif /* not MSDOS */
  return err;
}

#ifndef MSDOS
/* If STACK does not already have an entry with the same i-number as INUM,
   return 0. Otherwise, ask the user whether to continue;
   if yes, return 1, and if no, exit.
   This assumes that no one tries to remove filesystem mount points;
   doing so could cause duplication of i-numbers that would not indicate
   a corrupted file system. */

int
check_stack (stack, inum)
     struct pathstack *stack;
     ino_t inum;
{
  struct pathstack *p;

  for (p = stack; p != NULL; p = p->next)
    {
      if (p->inum == inum)
	{
	  fprintf (stderr, "\
%s: WARNING: Circular directory structure.\n\
This almost certainly means that you have a corrupted file system.\n\
NOTIFY YOUR SYSTEM MANAGER.\n\
Cycle detected:\n\
%s\n\
is the same file as\n", program_name, pathname);
	  *p->pathp = '\0';	/* Truncate pathname. */
	  fprintf (stderr, "%s\n", pathname);
	  *p->pathp = '/';	/* Put it back. */
	  fprintf (stderr, "%s: continue? ", program_name);
	  if (!yesno ())
	    exit (1);
	  return 1;
	}
    }
  return 0;
}
#endif /* !MSDOS */

/* Query the user for a line from the keyboard;
   return 1 if yes, 0 otherwise. */

int
yesno ()
{
  int c, c2;

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

  return c == 'y' || c == 'Y';
}

/* Remove trailing slashes from PATH; they cause some system calls to fail. */

#ifdef MSDOS
#undef strip_trailing_slashes

void
strip_trailing_slashes (char **path)
{
  char *new_path = _fullpath (NULL, *path, 0);
  free (*path);
  *path = msdos_format_filename (new_path);
}

#else /* not MSDOS */

void
strip_trailing_slashes (path)
     char *path;
{
  int last;

  last = strlen (path) - 1;
  while (last > 0 && path[last] == '/')
    path[last--] = '\0';
}

char *
xmalloc (n)
     unsigned n;
{
  char *p;

  p = malloc (n);
  if (p == 0)
    error (2, 0, "virtual memory exhausted");
  return p;
}

char *
xrealloc (p, n)
     char *p;
     unsigned n;
{
  p = realloc (p, n);
  if (p == 0)
    error (2, 0, "virtual memory exhausted");
  return p;
}

#endif /* not MSDOS */

/* Return NAME with any leading path stripped off.  */

char *
basename (name)
     char *name;
{
  char *base;

  base = rindex (name, '/');
  return base ? base + 1 : name;
}

/* Copy SOURCE into DEST, stopping after copying the first '\0', and
   return a pointer to the '\0' at the end of DEST;
   in other words, return DEST + strlen (SOURCE). */

char *
stpcpy (dest, source)
     char *dest;
     char *source;
{
  while ((*dest++ = *source++) != 0)
    /* Do nothing. */ ;
  return dest - 1;
}

void
usage ()
{
#ifdef MSDOS
  fprintf (stderr, "\
Usage: %s [-dfirvR] [+directory] [+force] [+interactive] [+recursive]\n\
       [+verbose] [+copying] [+version] path...\n",
#else /* not MSDOS */
  fprintf (stderr, "\
Usage: %s [-dfirvR] [+directory] [+force] [+interactive] [+recursive]\n\
       [+verbose] path...\n",
#endif /* not MSDOS */
	   program_name);
  exit (1);
}

#ifdef MSDOS
int
force_unlink(char *filename)
{
  if (access( filename, 2))			/* read only */
    if (chmod( filename, S_IREAD|S_IWRITE))
      error (0, errno, "can't force write permission for %s", filename);

#undef unlink					/* nasty tricks ... */
  return unlink (filename);
}
#endif /* MSDOS */
