rm.c 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. /* rm.c - remove files
  2. *
  3. * Copyright 2012 Rob Landley <rob@landley.net>
  4. *
  5. * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/rm.html
  6. USE_RM(NEWTOY(rm, "fiRrv[-fi]", TOYFLAG_BIN))
  7. config RM
  8. bool "rm"
  9. default y
  10. help
  11. usage: rm [-fiRrv] FILE...
  12. Remove each argument from the filesystem.
  13. -f Force: remove without confirmation, no error if it doesn't exist
  14. -i Interactive: prompt for confirmation
  15. -rR Recursive: remove directory contents
  16. -v Verbose
  17. */
  18. #define FOR_rm
  19. #include "toys.h"
  20. static int do_rm(struct dirtree *try)
  21. {
  22. int fd=dirtree_parentfd(try), dir=S_ISDIR(try->st.st_mode), or=0, using=0;
  23. // Skip . and .. (yes, even explicitly on the command line: posix says to)
  24. if (isdotdot(try->name)) return 0;
  25. // Intentionally fail non-recursive attempts to remove even an empty dir
  26. // (via wrong flags to unlinkat) because POSIX says to.
  27. if (dir && !(toys.optflags & (FLAG_r|FLAG_R))) goto skip;
  28. // This is either the posix section 2(b) prompt or the section 3 prompt.
  29. if (!FLAG(f)
  30. && (!S_ISLNK(try->st.st_mode) && faccessat(fd, try->name, W_OK, 0))) or++;
  31. // Posix section 1(a), don't prompt for nonexistent.
  32. if (or && errno == ENOENT) goto skip;
  33. if (!(dir && try->again) && ((or && isatty(0)) || FLAG(i))) {
  34. char *s = dirtree_path(try, 0);
  35. fprintf(stderr, "rm %s%s%s", or ? "ro " : "", dir ? "dir " : "", s);
  36. free(s);
  37. or = yesno(0);
  38. if (!or) goto nodelete;
  39. }
  40. // handle directory recursion
  41. if (dir) {
  42. using = AT_REMOVEDIR;
  43. // Handle chmod 000 directories when -f
  44. if (faccessat(fd, try->name, R_OK, 0)) {
  45. if (FLAG(f)) wfchmodat(fd, try->name, 0700);
  46. else goto skip;
  47. }
  48. if (!try->again) return DIRTREE_COMEAGAIN;
  49. if (try->symlink) goto skip;
  50. if (FLAG(i)) {
  51. char *s = dirtree_path(try, 0);
  52. // This is the section 2(d) prompt. (Yes, posix says to prompt twice.)
  53. fprintf(stderr, "rmdir %s", s);
  54. free(s);
  55. or = yesno(0);
  56. if (!or) goto nodelete;
  57. }
  58. }
  59. skip:
  60. if (!unlinkat(fd, try->name, using)) {
  61. if (FLAG(v)) {
  62. char *s = dirtree_path(try, 0);
  63. printf("%s%s '%s'\n", toys.which->name, dir ? "dir" : "", s);
  64. free(s);
  65. }
  66. } else {
  67. if (!dir || try->symlink != (char *)2) perror_msg_raw(try->name);
  68. nodelete:
  69. if (try->parent) try->parent->symlink = (char *)2;
  70. }
  71. return 0;
  72. }
  73. void rm_main(void)
  74. {
  75. char **s;
  76. // Can't use <1 in optstring because zero arguments with -f isn't an error
  77. if (!toys.optc && !FLAG(f)) help_exit("Needs 1 argument");
  78. for (s = toys.optargs; *s; s++) {
  79. if (!strcmp(*s, "/")) {
  80. error_msg("rm /. if you mean it");
  81. continue;
  82. }
  83. // "rm dir/.*" can expand to include .. which generally isn't what you want
  84. if (!strcmp("..", basename(*s))) {
  85. error_msg("bad path %s", *s);
  86. continue;
  87. }
  88. // Files that already don't exist aren't errors for -f. Use lstat() instead
  89. // of faccessat() because bionic doesn't support AT_SYMLINK_NOFOLLOW
  90. if (FLAG(f) && lstat(*s, (void *)toybuf) && errno == ENOENT) continue;
  91. // There's a race here where a file removed between the above check and
  92. // dirtree's stat would report the nonexistence as an error, but that's
  93. // not a normal "it didn't exist" so I'm ok with it.
  94. dirtree_read(*s, do_rm);
  95. }
  96. }