rm.c (5207B)
- // utils-std: Collection of commonly available Unix tools
- // SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
- // SPDX-License-Identifier: MPL-2.0
- #define _POSIX_C_SOURCE 200809L
- // NetBSD <10 hides fdopendir behind _NETBSD_SOURCE
- #if __NetBSD_Version__ < 1000000000
- #define _NETBSD_SOURCE
- #endif
- #include "../lib/consent.h"
- #include <assert.h>
- #include <ctype.h> // isprint
- #include <dirent.h> // fdopendir, readdir, closedir
- #include <errno.h> // errno
- #include <fcntl.h> // AT_FDCWD
- #include <limits.h> // PATH_MAX
- #include <locale.h> // setlocale
- #include <stdarg.h> // va_list
- #include <stdbool.h>
- #include <stdio.h> // fprintf, getline
- #include <stdlib.h> // free
- #include <string.h> // strerror
- #include <sys/stat.h> // chmod, fstatat, S_ISDIR
- #include <unistd.h> // unlink, isatty
- bool opt_d = false, force = false, recurse = false, verbose = false, opt_i = false;
- const char *argv0 = "rm";
- static int
- do_unlinkat(int fd, char *name, char *acc_path)
- {
- struct stat stats;
- int err = 0;
- assert(errno == 0);
- if(fstatat(fd, name, &stats, AT_SYMLINK_NOFOLLOW) != 0)
- {
- if(force && errno == ENOENT)
- {
- errno = 0;
- return 0;
- }
- fprintf(stderr, "rm: error: Failed getting status for '%s': %s\n", acc_path, strerror(errno));
- errno = 0;
- return 1;
- }
- bool is_dir = S_ISDIR(stats.st_mode);
- if(is_dir && !opt_d)
- {
- if(!recurse)
- {
- fprintf(stderr, "rm: error: Is a directory, pass -r or -d to remove: %s\n", acc_path);
- return 1;
- }
- if(!force && opt_i)
- if(!consentf("rm: Recurse into '%s' ? [y/N] ", acc_path)) return 0;
- assert(errno == 0);
- int dir = openat(fd, name, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
- if(dir == -1)
- {
- fprintf(
- stderr, "rm: error: Couldn't open '%s' as directory: %s\n", acc_path, strerror(errno));
- errno = 0;
- return 1;
- }
- assert(errno == 0);
- DIR *dirp = fdopendir(dir);
- if(dirp == NULL)
- {
- fprintf(stderr,
- "rm: error: Couldn't get DIR entry for opened '%s': %s\n",
- acc_path,
- strerror(errno));
- errno = 0;
- return 1;
- }
- while(true)
- {
- assert(errno == 0);
- struct dirent *dp = readdir(dirp);
- if(dp == NULL)
- {
- if(errno == 0) break;
- fprintf(
- stderr, "rm: error: Failed reading directory '%s': %s\n", acc_path, strerror(errno));
- closedir(dirp); // error ignored
- errno = 0;
- return 1;
- }
- if(strcmp(dp->d_name, ".") == 0) continue;
- if(strcmp(dp->d_name, "..") == 0) continue;
- char new_path[PATH_MAX] = "";
- assert(errno == 0);
- if(snprintf(new_path, PATH_MAX, "%s/%s", acc_path, dp->d_name) < 0)
- {
- fprintf(stderr,
- "rm: error: Couldn't concatenate '%s' into parent '%s', skipping to next entry: %s",
- name,
- acc_path,
- strerror(errno));
- err = 1;
- errno = 0;
- continue;
- }
- // No depth counter for now, unlikely to be a problem
- int ret = do_unlinkat(dir, dp->d_name, new_path);
- if(ret != 0) err = 1;
- }
- // fdopendir allocates memory for DIR, needs closedir
- assert(errno == 0);
- if(closedir(dirp) != 0)
- {
- fprintf(stderr,
- "rm: error: Deallocating directory entry for '%s' failed: %s\n",
- acc_path,
- strerror(errno));
- errno = 0;
- return 1;
- }
- }
- if(!force)
- {
- if(opt_i)
- {
- if(!consentf("rm: Remove '%s' ? [y/N] ", acc_path)) return 0;
- }
- else
- {
- // Don't check symbolic links, would need AT_SYMLINK_NOFOLLOW on faccessat which isn't portable
- // Can assume symbolic links are 0777 anyway
- if(!S_ISLNK(stats.st_mode) && faccessat(fd, name, W_OK, 0) != 0)
- {
- errno = 0;
- if(!consentf("rm: Remove non-writable '%s' ? [y/N] ", acc_path)) return 0;
- }
- }
- }
- assert(errno == 0);
- if(unlinkat(fd, name, is_dir ? AT_REMOVEDIR : 0) != 0)
- {
- fprintf(stderr, "rm: error: Couldn't remove '%s': %s\n", acc_path, strerror(errno));
- errno = 0;
- return 1;
- }
- else if(verbose)
- {
- fprintf(stderr, "rm: Removed: %s\n", acc_path);
- }
- return err;
- }
- static void
- usage(void)
- {
- fprintf(stderr, "Usage: rm [-firRv] [files ...]\n");
- }
- int
- main(int argc, char *argv[])
- {
- int c = -1;
- while((c = getopt(argc, argv, ":dfirRv")) != -1)
- {
- switch(c)
- {
- case 'd':
- opt_d = true;
- break;
- case 'f':
- force = true;
- break;
- case 'i':
- opt_i = true;
- break;
- case 'r':
- recurse = true;
- break;
- case 'R':
- recurse = true;
- break;
- case 'v':
- verbose = true;
- break;
- case ':':
- fprintf(stderr, "rm: error: Missing operand for option: '-%c'\n", optopt);
- usage();
- return 1;
- case '?':
- fprintf(stderr, "rm: error: Unrecognised option: '-%c'\n", optopt);
- usage();
- return 1;
- default:
- abort();
- }
- }
- argc -= optind;
- argv += optind;
- errno = 0;
- setlocale(LC_ALL, "");
- if(errno != 0)
- {
- fprintf(stderr, "%s: warning: Failed to initialize locales: %s\n", argv0, strerror(errno));
- errno = 0;
- }
- consent_init();
- if(argc == 0 && !force)
- {
- fprintf(stderr, "rm: error: missing operand\n");
- usage();
- return 1;
- }
- int err = 0;
- for(int i = 0; i < argc; i++)
- {
- int ret = do_unlinkat(AT_FDCWD, argv[i], argv[i]);
- if(ret != 0) err = 1;
- }
- consent_finish();
- return err;
- }