logo

utils-std

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

rm.c (5445B)


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