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>