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