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>