[comp.os.minix] rmdir re-revisited

Steinsbo%hsr.uninett@nac.no (Bjarne Steinsbo) (07/02/89)

I'm sorry about my last patch on `rmdir'. Although it eliminated a serious
bug, it also introduced several new ones. This time I *THINK* I got it
right. It has been tested on PC-Minix 1.4 and the GNU version of ST-Minix.
To get it to run on a vanilla 1.1 ST-Minix, delete the `#include <limits.h>'
and `#define PATH_MAX 512'.
Lots of thanks to Bruce Evans <bruce@ditsyda.oz.au> for valuable comments
on intermediate (not bug-free !! :-) versions.
Expect a few broken lines, our X.400 based mail-system doesn't handle lines
more than 80 characters well.
Install as SUID root.

$ crc rmdir.c
36222   5048 rmdir.c

Bjarne Steinsbo
University Centre of Rogaland
NORWAY

<Steinsbo@hsr.uninett>		BITNET   : <Steinsbo%hsr.uninett@norunix.bitnet>
				INTERNET : <Steinsbo%hsr.uninett@nac.no>
-------------------- cut here ----------------------------------------------
echo x - rmdir.c
sed '/^X/s///' > rmdir.c << '/'
X/* rmdir - remove a directory		Author: Adri Koppes
X * (modified by Paul Polderman)
X * (modified by Bjarne Steinsbo)	Fixed "rmdir ../anything"
X *					Modified style to standard Minix
X *					Added some comments.
X */
X
X#include <signal.h>
X#include <sys/types.h>
X#include <sys/stat.h>
X#include <sys/dir.h>
X#include <limits.h>
X
X#ifdef __GNUC__
X#ifdef ATARI_ST
X#include <std.h>
X#endif
X#endif
X
Xint     error = 0;
X
Xmain (argc, argv)
Xregister int argc;
Xregister char  **argv;
X{
X  if (argc < 2) {
X	prints ("Usage: rmdir dir ...\n");
X	exit (1);
X  }
X  signal (SIGHUP, SIG_IGN);
X  signal (SIGINT, SIG_IGN);
X  signal (SIGQUIT, SIG_IGN);
X  signal (SIGTERM, SIG_IGN);
X  while (--argc)
X	remove (*++argv);
X  if (error)
X	exit (1);
X}
X
Xextern char *rindex();
X
Xremove (dirname)
Xchar   *dirname;
X{
X  struct direct   d;		/* buffer for reading directory */
X  struct stat s, cwd;		/* buffers for `stat' call */
X  int fd = 0;
X  int sl = 0;
X  char dots[PATH_MAX];		/* scratch buffer for dirname */
X  register char *p;
X
X  /* Is the path name too long ? Check once and for all. */
X  if (strlen(dirname) > PATH_MAX - 3) {	/* Need to append `/..' */
X	stderr2("path name too long : ", dirname);
X	std_err("\n");
X	error++;
X	return;
X  }
X
X  /* Does the file exist ? */
X  if (stat(dirname, &s)) {
X	stderr2(dirname, " doesn't exist\n");
X	error++;
X	return;
X  }
X
X  /* Is it a directory ? */
X  if ((s.st_mode & S_IFMT) != S_IFDIR) {
X	stderr2(dirname, " not a directory\n");
X	error++;
X	return;
X  }
X
X  /* Are we trying to remove "." or ".." ? */
X  if (p = rindex(dirname, '/'))
X	p++;
X  else
X	p = dirname;
X  if (strcmp(p, ".") == 0 || strcmp(p, "..") == 0) {
X	stderr2(dirname, " will not remove \".\" or \"..\"\n");
X	error++;
X	return;
X  }
X
X  /* Write permission in parent directory ? */
X  strcpy(dots, dirname);
X  while (dirname[fd])
X	if (dirname[fd++] == '/') sl = fd;
X  dots[sl] = '\0';
X  if (access(dots, 2)) {
X	stderr2(dirname, " no permission\n");
X	error++;
X	return;
X  }
X
X  /* Are we trying to remove current dirctory ? */
X  stat(".", &cwd);
X  if ((s.st_ino == cwd.st_ino) && (s.st_dev == cwd.st_dev)) {
X	std_err("rmdir: can't remove current directory\n");
X	error++;
X	return;
X  }
X
X  /* Is it possible to open the directory ? */
X  if ((fd = open(dirname, 0)) < 0) {
X	stderr2("can't read ", dirname);
X	std_err("\n");
X	error++;
X	return;
X  }
X
X  /* Is the directory empty ? (except "." and "..") */
X  while (read(fd, (char *)&d, sizeof(struct direct)) == sizeof(struct direct)) {
X	if (d.d_ino != 0) {
X		if (strcmp(d.d_name, ".") && strcmp (d.d_name, "..")) {
X		        stderr2(dirname, " not empty\n");
X			close(fd);
X			error++;
X			return;
X		}
X	}
X  }
X  close(fd);
X
X  /* Will the path name be invalidated when dirname/. or dirname/..
X   * is unlinked ? In that case, fix the path-name !
X   */
X  strcpy(dots, dirname);
X  patch_path(dots);
X
X  /* OK, let's unlink dirname/.. dirname/. and dirname */
X  strcat (dots, "/..");
X  for (p = dots; *p; p++) ;	/* find end of dots */
X  unlink(dots);			/* dirname/.. */
X  *(p - 1) = '\0';
X  unlink(dots);			/* dirname/. */
X  *(p - 3) = '\0';
X  /* Check the last unlink, in case rmdir is not SUID, and the unlinking
X   * didn't succeed.
X   */
X  if (unlink(dots)) {		/* dirname */
X	stderr2("can't remove ", dots);
X	std_err("\n");
X	error++;
X	return;
X  }
X}
X
Xstderr2(s1, s2)
Xchar *s1, *s2;
X{
X  std_err("rmdir: ");
X  std_err(s1);
X  std_err(s2);
X}
X
X/* With s pointing to the first char in the next part of the pathname,
X * check if this part is empty (/), dot (./) or dotdot (../)
X */
X#define IS_EMPTY(s)  (*(s) == '/')
X#define IS_DOT(s)    (*(s) == '.' && *((s)+1) == '/')
X#define IS_DOTDOT(s) (*(s) == '.' && *((s)+1) == '.' && *((s)+2) == '/')
X
Xpatch_path(dir)
Xchar *dir;
X{
X/* Check if the path name will be invalidated when `dirname/..' and
X * `dirname/.' is later unlinked. Return a (possibly) patched path.
X * Do this by cleaning the path up, i.e. removing unnecessary parts
X * of the path. `anypath/anything/../' , `anypath/./' and `anypath//'
X * are all considered equal to `anypath/' . This assumption will break
X * when symbolic links are (are they ?) introduced. Don't remove those
X * `../' parts that are essential to the path name.
X */
X
X  register char *p;
X  char *last;
X  int level = 0;
X
X  if (*dir == '/')		/* absolute ? */
X	last = dir + 1;
X  else
X	last = dir;
X
X  p = last;
X  while (*p != '\0') {		/* clean up the path name */
X	if (IS_EMPTY(p)) {	/* reduce `//' to `/' */
X		StrCpy(p, p + 1);
X		continue;
X	}
X	if (IS_DOT(p)) {	/* reduce `/./'  to `/' */
X		StrCpy(p, p + 2);
X		continue;
X	}
X	if (IS_DOTDOT(p)) {	/* reduce `/anything/../' to `/' */
X		if (level > 0) {/* is it possible to reduce? */
X			--level;
X			StrCpy(last, p + 3);
X			p = last;
X			last -= 2;
X			while (*last != '/' && last > dir) --last;
X			if (*last == '/') last++;
X		} else
X			last = p += 3;
X		continue;
X	}
X	last = p;
X	level++;
X	while (*p != '\0' && *p++ != '/') ;	/* get next part of path name */
X    }
X}
X
XStrCpy(s,t)
Xregister char *s,*t;
X{
X/* Overlapping copies are implemetation-dependent in strcpy, so we'll
X * use our own version.
X */
X
X  while (*s++ = *t++) /* do nothing */;
X}
/