logo

utils-std

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

rm.c (5102B)


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