logo

utils-std

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

rm.c (4904B)


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