Annotation of solidite/utils/rm.cpp, revision 1.1.1.1
1.1 user 1: /* Copyright (c) 2026 The Solidite Developers
2: *
3: * This program is free software: you can redistribute it and/or modify it under
4: * the terms of the GNU Affero General Public License as published by the Free
5: * Software Foundation, either version 3 of the License, or (at your option)
6: * any later version.
7: *
8: * This program is distributed in the hope that it will be useful, but WITHOUT
9: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
10: * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
11: * details.
12: *
13: * You should have received a copy of the GNU Affero General Public License
14: * along with this program. If not, see <https://www.gnu.org/licenses/>. */
15:
16: #include <bitset>
17: #include <filesystem>
18: #include <iostream>
19: #include <locale>
20: #include <string>
21: #include <string_view>
22: #include <vector>
23:
24: #include <cstdint>
25:
26: #include <unistd.h>
27:
28: inline int is_writable(const std::string &WHAT) {
29: return access(WHAT.c_str(), W_OK);
30: }
31:
32: int main(int argc, char* argv[]) {
33: // locale work
34: std::locale::global(std::locale(""));
35: std::cout.imbue(std::locale());
36: std::cerr.imbue(std::locale());
37:
38: int ret = 0;
39: std::bitset<7> flags = 0b0000000;
40: /* according to POSIX standards for rm options
41: * [0]=process arguments (not --)
42: * [1]=remove empty directories
43: * [2]=force (no prompt for confirmation, no diagnostic messages, do not
44: * modify exit status if no file operands, do not modify exit status of
45: * nonexistent file operands, previous instances of -i should be ignored
46: * [3]=prompt for confirmation, any previous instances of -f should be ignored
47: * [4]=remove file hierarchies (recursive)
48: * [5]=write message to stdout after each file has been removed (verbose)
49: * [6]=(has force remove message been prompted)
50: */
51: std::vector<std::string> files;
52:
53: for (int i = 1; i < argc; ++i) {
54: const std::string_view ARG = argv[i];
55: if (ARG[0] == '-' && flags.test(0) == 0) {
56: if (ARG == "--") {
57: flags.set(0); // do not further process options
58: }
59: for (char c : ARG) {
60: switch (c) {
61: case 'd':
62: flags.set(1);
63: break;
64: case 'f':
65: flags.reset(3); // ignore previous instance of -i
66: flags.set(2);
67: break;
68: case 'i':
69: flags.reset(2); // ignore previous instance of -r
70: flags.set(3);
71: break;
72: case 'r':
73: case 'R':
74: flags.set(4);
75: break;
76: case 'v':
77: flags.set(5);
78: break;
79: default:
80: break;
81: }
82: }
83: } else {
84: files.push_back(std::string(ARG));
85: }
86: }
87: for (const std::string &FILE : files) {
88: // step1:
89: if (!std::filesystem::exists(FILE)) {
90: // if force is not selected then exit with error and dont operate on file
91: // step1a:
92: if (flags.test(2) == 0) {
93: ret = EXIT_FAILURE;
94: std::cerr << "not found: " + FILE + '\n';
95: }
96: continue; // step1b: continue to next file
97: // step2: check if directory
98: } else if (std::filesystem::is_directory(FILE)) {
99: // step2a:
100: if (flags.test(1) == 0 && flags.test(4) == 0) {
101: std::cerr << "directory: " + FILE + '\n';
102: continue;
103: } else if (flags.test(1) == 1 && flags.test(4) == 0) {
104: goto step3;
105: /* "neither the -R nor the -r option is specified, but -d is specified,
106: * rm shall proceed with step 3" */
107: } else if (is_writable(FILE) == 0 && std::filesystem::is_empty(FILE)) {
108: goto step2d;
109: } else if ((flags.test(6) == 0 && flags.test(2) == 0 &&
110: is_writable(FILE) != 0 && isatty(STDIN_FILENO)) ||
111: (flags.test(3) == 1 && flags.test(6) == 0)) {
112: /* "If the -f option is not specified, and either the permissions of
113: * the file do not permit writing and the standard input is a terminal
114: * or the -i option is specified, rm shall write a prompt to standard
115: * error and read a line from the standard input." */
116: std::string response;
117: get_response_force_remove_directory_0:
118: std::cerr << "remove: " + FILE + " (y/n)";
119: std::getline(std::cin, response);
120: if (response == "n") {
121: flags.set(6);
122: continue;
123: } else if (response == "y")
124: flags.set(6);
125: else {
126: std::cerr << "invalid response\n";
127: goto get_response_force_remove_directory_0;
128: }
129: } // from interpretation of step2c, it seems irrelevant to us
130: step2d:
131: if (flags.test(6) == 0 && flags.test(3) == 1) {
132: std::string response;
133: get_response_force_remove_directory_1:
134: std::cerr << "remove: " + FILE + " (y/n)";
135: std::getline(std::cin, response);
136: if (response == "n") {
137: flags.set(6);
138: continue;
139: }
140: else if (response == "y")
141: flags.set(6);
142: else {
143: std::cerr << "invalid response\n";
144: goto get_response_force_remove_directory_1;
145: }
146: }
147: }
148: step3:
149: std::string response;
150: if ((flags.test(2) == 0 && is_writable(FILE) != 0 &&
151: isatty(STDIN_FILENO) && flags.test(6) == 0) || (flags.test(3) == 1 &&
152: flags.test(6) == 0)) {
153: get_response_remove_0:
154: std::cerr << "remove: " + FILE + " (y/n)";
155: std::getline(std::cin, response);
156: if (response == "n") {
157: flags.set(6);
158: continue;
159: } else if (response == "y")
160: flags.set(6);
161: else {
162: std::cerr << "invalid response\n";
163: goto get_response_remove_0;
164: }
165: }
166: // step4:
167: try {
168: std::filesystem::remove_all(FILE);
169: } catch (...) {
170: ++ret;
171: }
172: if (flags.test(5) == 1)
173: std::cout << "removed: " + FILE + '\n';
174: }
175: return ret;
176: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>