logo

utils-std

Collection of commonly available Unix tools git clone https://anongit.hacktivis.me/git/utils-std.git/

rm.c (6061B)


  1. // utils-std: Collection of commonly available Unix tools
  2. // SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
  3. // SPDX-License-Identifier: MPL-2.0
  4. #define _POSIX_C_SOURCE 200809L
  5. // NetBSD <10 hides fdopendir behind _NETBSD_SOURCE
  6. #if __NetBSD_Version__ < 1000000000
  7. #define _NETBSD_SOURCE
  8. #endif
  9. #include "../config.h"
  10. #include "../libutils/consent.h"
  11. #include "../libutils/getopt_nolong.h"
  12. #include <ctype.h> // isprint
  13. #include <dirent.h> // fdopendir, readdir, closedir
  14. #include <errno.h> // errno
  15. #include <fcntl.h> // AT_FDCWD
  16. #include <limits.h> // PATH_MAX
  17. #include <locale.h> // setlocale
  18. #include <stdarg.h> // va_list
  19. #include <stdbool.h>
  20. #include <stdio.h> // fprintf, getline
  21. #include <stdlib.h> // free
  22. #include <string.h> // strerror
  23. #include <sys/stat.h> // chmod, fstatat, S_ISDIR
  24. #include <unistd.h> // unlink, isatty
  25. #ifdef HAS_GETOPT_LONG
  26. #include <getopt.h>
  27. #endif
  28. bool opt_d = false, force = false, recurse = false, verbose = false, opt_i = false;
  29. const char *argv0 = "rm";
  30. struct stat root_stats;
  31. static int
  32. do_unlinkat(int fd, char *name, char *acc_path)
  33. {
  34. struct stat stats;
  35. int err = 0;
  36. if(fstatat(fd, name, &stats, AT_SYMLINK_NOFOLLOW) != 0)
  37. {
  38. if(force && errno == ENOENT)
  39. {
  40. errno = 0;
  41. return 0;
  42. }
  43. fprintf(stderr, "rm: error: Failed getting status for '%s': %s\n", acc_path, strerror(errno));
  44. errno = 0;
  45. return 1;
  46. }
  47. if(stats.st_dev == root_stats.st_dev && stats.st_ino == root_stats.st_ino)
  48. {
  49. fprintf(
  50. stderr, "rm: error: Target '%s' is the same inode+device as root, bailing out\n", acc_path);
  51. return 1;
  52. }
  53. bool is_dir = S_ISDIR(stats.st_mode);
  54. if(is_dir && !opt_d)
  55. {
  56. if(!recurse)
  57. {
  58. fprintf(stderr, "rm: error: Is a directory, pass -r or -d to remove: %s\n", acc_path);
  59. return 1;
  60. }
  61. if(!force && opt_i)
  62. if(!consentf("rm: Recurse into '%s' ? [y/N] ", acc_path)) return 0;
  63. int dir = openat(fd, name, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
  64. if(dir == -1)
  65. {
  66. fprintf(
  67. stderr, "rm: error: Couldn't open '%s' as directory: %s\n", acc_path, strerror(errno));
  68. errno = 0;
  69. return 1;
  70. }
  71. DIR *dirp = fdopendir(dir);
  72. if(dirp == NULL)
  73. {
  74. fprintf(stderr,
  75. "rm: error: Couldn't get DIR entry for opened '%s': %s\n",
  76. acc_path,
  77. strerror(errno));
  78. errno = 0;
  79. return 1;
  80. }
  81. while(true)
  82. {
  83. struct dirent *dp = readdir(dirp);
  84. if(dp == NULL)
  85. {
  86. if(errno == 0) break;
  87. fprintf(
  88. stderr, "rm: error: Failed reading directory '%s': %s\n", acc_path, strerror(errno));
  89. closedir(dirp); // error ignored
  90. errno = 0;
  91. return 1;
  92. }
  93. if(strcmp(dp->d_name, ".") == 0) continue;
  94. if(strcmp(dp->d_name, "..") == 0) continue;
  95. char new_path[PATH_MAX] = "";
  96. if(snprintf(new_path, PATH_MAX, "%s/%s", acc_path, dp->d_name) < 0)
  97. {
  98. fprintf(stderr,
  99. "rm: error: Couldn't concatenate '%s' into parent '%s', skipping to next entry: %s",
  100. name,
  101. acc_path,
  102. strerror(errno));
  103. err = 1;
  104. errno = 0;
  105. continue;
  106. }
  107. // No depth counter for now, unlikely to be a problem
  108. int ret = do_unlinkat(dir, dp->d_name, new_path);
  109. if(ret != 0) err = 1;
  110. }
  111. // fdopendir allocates memory for DIR, needs closedir
  112. if(closedir(dirp) != 0)
  113. {
  114. fprintf(stderr,
  115. "rm: error: Deallocating directory entry for '%s' failed: %s\n",
  116. acc_path,
  117. strerror(errno));
  118. errno = 0;
  119. return 1;
  120. }
  121. }
  122. if(!force)
  123. {
  124. if(opt_i)
  125. {
  126. if(!consentf("rm: Remove '%s' ? [y/N] ", acc_path)) return 0;
  127. }
  128. else
  129. {
  130. // Don't check symbolic links, would need AT_SYMLINK_NOFOLLOW on faccessat which isn't portable
  131. // Can assume symbolic links are 0777 anyway
  132. if(!S_ISLNK(stats.st_mode) && faccessat(fd, name, W_OK, 0) != 0)
  133. {
  134. errno = 0;
  135. if(!consentf("rm: Remove non-writable '%s' ? [y/N] ", acc_path)) return 0;
  136. }
  137. }
  138. }
  139. if(unlinkat(fd, name, is_dir ? AT_REMOVEDIR : 0) != 0)
  140. {
  141. fprintf(stderr, "rm: error: Couldn't remove '%s': %s\n", acc_path, strerror(errno));
  142. errno = 0;
  143. return 1;
  144. }
  145. else if(verbose)
  146. {
  147. fprintf(stderr, "rm: Removed: %s\n", acc_path);
  148. }
  149. return err;
  150. }
  151. static void
  152. usage(void)
  153. {
  154. fprintf(stderr, "Usage: rm [-dfirRv] [files ...]\n");
  155. }
  156. int
  157. main(int argc, char *argv[])
  158. {
  159. #ifdef HAS_GETOPT_LONG
  160. // Strictly for GNUisms compatibility so no long-only options
  161. // clang-format off
  162. static struct option opts[] = {
  163. {"dir", no_argument, NULL, 'd'},
  164. {"force", no_argument, NULL, 'f'},
  165. {"interactive", no_argument, NULL, 'i'},
  166. {"recursive", no_argument, NULL, 'r'},
  167. {"verbose", no_argument, NULL, 'v'},
  168. {0, 0, 0, 0},
  169. };
  170. // clang-format on
  171. // Need + as first character to get POSIX-style option parsing
  172. for(int c = -1; (c = getopt_long(argc, argv, "+:dfirRv", opts, NULL)) != -1;)
  173. #else
  174. for(int c = -1; (c = getopt_nolong(argc, argv, ":dfirRv")) != -1;)
  175. #endif
  176. {
  177. switch(c)
  178. {
  179. case 'd':
  180. opt_d = true;
  181. break;
  182. case 'f':
  183. force = true;
  184. break;
  185. case 'i':
  186. opt_i = true;
  187. break;
  188. case 'r':
  189. recurse = true;
  190. break;
  191. case 'R':
  192. recurse = true;
  193. break;
  194. case 'v':
  195. verbose = true;
  196. break;
  197. case ':':
  198. fprintf(stderr, "rm: error: Missing operand for option: '-%c'\n", optopt);
  199. usage();
  200. return 1;
  201. case '?':
  202. GETOPT_UNKNOWN_OPT
  203. usage();
  204. return 1;
  205. default:
  206. abort();
  207. }
  208. }
  209. argc -= optind;
  210. argv += optind;
  211. char *lc_all = setlocale(LC_ALL, "");
  212. if(lc_all == NULL)
  213. {
  214. fprintf(stderr,
  215. "%s: warning: Failed loading locales. setlocale(LC_ALL, \"\"): %s\n",
  216. argv0,
  217. strerror(errno));
  218. }
  219. errno = 0;
  220. consent_init();
  221. if(argc == 0 && !force)
  222. {
  223. fprintf(stderr, "rm: error: missing operand\n");
  224. usage();
  225. return 1;
  226. }
  227. if(stat("/", &root_stats) != 0)
  228. {
  229. fprintf(stderr, "rm: error: Failed getting status for '/': %s\n", strerror(errno));
  230. return 1;
  231. }
  232. int err = 0;
  233. for(int i = 0; i < argc; i++)
  234. {
  235. int ret = do_unlinkat(AT_FDCWD, argv[i], argv[i]);
  236. if(ret != 0) err = 1;
  237. }
  238. consent_finish();
  239. return err;
  240. }