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} /