#include #include #include #include #include #include #include #include #include #include #include #include const char *PATH_PORTS = "/usr/ports"; const char *prog; const size_t STRLIST_INCR = 16; static uint8_t pretend = 0; struct strlist { /* members cannot be modified directly; exception is to free * unneeded strings in member 'list'; the string must be set * to NULL afterward to prevent a double-free */ size_t capacity; size_t size; char **list; }; static int clean(char [PATH_MAX + 1]); static int clean_rec(char [PATH_MAX + 1], size_t); static int remove_dir(const char *); static int strlist_add(struct strlist *, const char *); static void strlist_compact(struct strlist *); static void strlist_destroy(struct strlist *); static int strlist_incr(struct strlist *); static int strlist_init(struct strlist *); static void strlist_sort(struct strlist *); static int str_compar(const void *, const void *); static int strlist_init(struct strlist *list) { char **l; if (!(l = malloc(sizeof(char *) * STRLIST_INCR))) return -1; list->list = l; list->capacity = STRLIST_INCR; list->size = 0; return 0; } static void strlist_destroy(struct strlist *list) { for (size_t i = 0; i < list->size; i++) if (list->list[i]) free(list->list[i]); free(list->list); list->list = NULL; list->size = list->capacity = 0; } static int strlist_add(struct strlist *list, const char *str) { size_t len; char *copy; len = strlen(str); if (!(copy = malloc(len + 1))) return -1; if (list->size == list->capacity) { if (strlist_incr(list) == -1) { free(copy); return -1; } } memcpy(copy, str, len + 1); list->list[list->size++] = copy; return 0; } static void strlist_compact(struct strlist *list) { if (list->size == list->capacity) return; list->capacity = list->size; list->list = realloc(list->list, sizeof(char *) * list->size); } static void strlist_sort(struct strlist *list) { qsort(list->list, list->size, sizeof(char *), str_compar); } static int strlist_incr(struct strlist *list) { size_t newcap; char **newlist; newcap = list->capacity + STRLIST_INCR; if (!(newlist = realloc(list->list, sizeof(char *) * newcap))) return -1; list->list = newlist; list->capacity = newcap; return 0; } static int str_compar(const void *a, const void *b) { return strcmp(*(const char **)a, *(const char **)b); } /** * Removes a directory recursively by calling 'rm -rf'. * * \retval 0 success * \retval -1 error, errno set * \retval -2 error **/ static int remove_dir(const char *path) { pid_t pid; int status; fflush(stdout); if ((pid = fork()) == -1) return -1; else if (!pid) { execlp("rm", "rm", "-r", "-f", path, (const char *)NULL); perror(prog); exit(0xff); } if (waitpid(pid, &status, 0) == -1) return -1; if (WIFEXITED(status)) { switch (WEXITSTATUS(status)) { case 0: return 0; /* exec failed */ case 0xff: return -2; } } return -2; } static int clean_rec(char pathbuf[PATH_MAX + 1], size_t len) { struct strlist list; struct stat st; size_t newlen; DIR *dir; struct dirent *entry; int retval; if (!(dir = opendir(pathbuf))) { fprintf(stderr, "%s: %s: failed to open directory\n", prog, pathbuf); return -1; } if (strlist_init(&list) == -1) { fprintf(stderr, "%s: malloc failed\n", prog); closedir(dir); return -1; } /* copy directory's sub-directory names into 'list' */ while ((entry = readdir(dir))) { if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue; strlcpy(pathbuf + len, entry->d_name, PATH_MAX + 1 - len); if (stat(pathbuf, &st) == -1) { fprintf(stderr, "%s: %s: %s\n", prog, pathbuf, strerror(errno)); continue; } if (!S_ISDIR(st.st_mode)) continue; if (strlist_add(&list, entry->d_name) == -1) { fprintf(stderr, "%s: %s ... quitting directory read\n", prog, strerror(errno)); break; } } /* close dir, sort list */ closedir(dir); strlist_compact(&list); strlist_sort(&list); /* process each sub-directory */ for (size_t i = 0; i < list.size; i++) { strlcpy(pathbuf + len, list.list[i], PATH_MAX + 1 - len); if (!strncmp(list.list[i], "w-", 2)) { printf("%s\n", pathbuf); fflush(stdout); if (pretend) retval = 0; else retval = remove_dir(pathbuf); switch (retval) { case -1: fprintf(stderr, "%s: %s: %s\n", prog, pathbuf, strerror(errno)); strlist_destroy(&list); return -1; case -2: fprintf(stderr, "%s: %s: failed to remove\n", prog, pathbuf); strlist_destroy(&list); return -1; } } else { newlen = len + strlen(list.list[i]); strlcpy(pathbuf + newlen, "/", PATH_MAX + 1 - newlen); newlen++; if (clean_rec(pathbuf, newlen) == -1) { strlist_destroy(&list); return -1; } } free(list.list[i]); list.list[i] = NULL; } strlist_destroy(&list); return 0; } static int clean(char pathbuf[PATH_MAX + 1]) { size_t len; len = strlen(pathbuf); if (pathbuf[len - 1] != '/') { pathbuf[len++] = '/'; pathbuf[len] = '\0'; } return clean_rec(pathbuf, len); } void usage() { printf("usage: %s [ --help | --pretend ] [DIR]\n", prog); } void unrecognized(const char *opt) { fprintf(stderr, "%s: unrecognized option `%s'\n", prog, opt); usage(); } bool make_absolute(const char *path, char pathbuf[PATH_MAX + 1]) { char *alloc = 0; size_t path_len; path_len = strlen(path); if (*path != '/') { /* store in 'alloc' (cwd '/' path) */ char cwd[PATH_MAX + 1]; size_t cwd_len; if (!getcwd(cwd, PATH_MAX + 1)) { perror(prog); return false; } cwd_len = strlen(cwd); if (!(alloc = malloc(cwd_len + path_len + 2))) { perror(prog); return false; } memcpy(alloc, cwd, cwd_len); alloc[cwd_len] = '/'; memcpy(alloc + cwd_len + 1, path, path_len + 1); /* make path/path_len reflect alloc instead */ path = alloc; path_len += cwd_len + 1; } if (!realpath(path, pathbuf)) { perror(prog); return false; } if (alloc) free(alloc); return true; } int main(int argc, char **argv) { char givenpath[PATH_MAX + 1]; char pathbuf[PATH_MAX + 1]; prog = *argv; *givenpath = '\0'; for (int i = 1; i < argc; i++) { if (!strncmp(argv[i], "--", 2) && argv[i][2] != '-') { /* long options */ if (!strcmp(argv[i] + 2, "help")) { usage(); return 0; } else if (!strcmp(argv[i] + 2, "pretend")) { pretend = 1; } else if (!argv[i][2]) { /* option exactly "--" */ size_t len; if (*givenpath) { fprintf(stderr, "%s: too many directories given\n", prog); return 1; } i++; len = strlen(argv[i]); if (len > PATH_MAX) { fprintf(stderr, "%s: bad directory\n", prog); return 1; } memcpy(givenpath, argv[i], len + 1); } else { /* option is "--<...>" */ unrecognized(argv[i]); return 1; } } else if (argv[i][0] == '-') { /* no short options */ unrecognized(argv[i]); return 1; } else { /* option does not start with '-' */ size_t len; if (*givenpath) { fprintf(stderr, "%s: too many directories given\n", prog); return 1; } len = strlen(argv[i]); if (len > PATH_MAX) { fprintf(stderr, "%s: bad directory\n", prog); return 1; } memcpy(givenpath, argv[i], len + 1); } } if (geteuid() != 0) { fprintf(stderr, "%s: you must be root\n", prog); return 1; } if (!*givenpath) /* no path given */ strlcpy(pathbuf, PATH_PORTS, PATH_MAX + 1); else if (!make_absolute(givenpath, pathbuf)) /* failed to make a relative path absolute */ return 1; printf("cleaning %s\n", pathbuf); if (clean(pathbuf) == -1) return 1; return 0; }