Annotation of solidite/utils/rm.cpp, revision 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>