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