File:  [Local Repository] / solidite / utils / rm.cpp
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs
Tue Sep 8 00:37:30 2026 UTC (11 days, 2 hours ago) by user
Branches: none, MAIN
CVS tags: notag, HEAD
Migrate host

/* Copyright (c) 2026 The Solidite Developers
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>. */

#include <bitset>
#include <filesystem>
#include <iostream>
#include <locale>
#include <string>
#include <string_view>
#include <vector>

#include <cstdint>

#include <unistd.h>

inline int is_writable(const std::string &WHAT) {
  return access(WHAT.c_str(), W_OK);
}

int main(int argc, char* argv[]) {
  // locale work
  std::locale::global(std::locale(""));
  std::cout.imbue(std::locale()); 
  std::cerr.imbue(std::locale()); 

  int ret = 0;
  std::bitset<7> flags = 0b0000000;
  /* according to POSIX standards for rm options
   * [0]=process arguments (not --)
   * [1]=remove empty directories
   * [2]=force (no prompt for confirmation, no diagnostic messages, do not
   * modify exit status if no file operands, do not modify exit status of
   * nonexistent file operands, previous instances of -i should be ignored
   * [3]=prompt for confirmation, any previous instances of -f should be ignored
   * [4]=remove file hierarchies (recursive)
   * [5]=write message to stdout after each file has been removed (verbose)
   * [6]=(has force remove message been prompted)
  */
 std::vector<std::string> files;

  for (int i = 1; i < argc; ++i) {
    const std::string_view ARG = argv[i];
    if (ARG[0] == '-' && flags.test(0) == 0) {
      if (ARG == "--") {
        flags.set(0); // do not further process options
      }
      for (char c : ARG) {
        switch (c) {
          case 'd':
            flags.set(1);
            break;
          case 'f': 
            flags.reset(3); // ignore previous instance of -i
            flags.set(2);
            break;
          case 'i': 
            flags.reset(2); // ignore previous instance of -r
            flags.set(3);
            break;
          case 'r':
          case 'R':
            flags.set(4);
            break;
          case 'v':
            flags.set(5);
            break;
          default:
            break;
        }
      }
    } else {
      files.push_back(std::string(ARG));
    }
  }
  for (const std::string &FILE : files) {
// step1:
    if (!std::filesystem::exists(FILE)) {
      // if force is not selected then exit with error and dont operate on file
// step1a:
      if (flags.test(2) == 0) {
        ret = EXIT_FAILURE;
        std::cerr << "not found: " + FILE + '\n';
      }
      continue; // step1b: continue to next file
                // step2: check if directory
    } else if (std::filesystem::is_directory(FILE)) {
// step2a:
      if (flags.test(1) == 0 && flags.test(4) == 0) {
        std::cerr << "directory: " + FILE + '\n';
        continue;  
      } else if (flags.test(1) == 1 && flags.test(4) == 0) {
        goto step3;
        /* "neither the -R nor the -r option is specified, but -d is specified,
         * rm shall proceed with step 3" */
      } else if (is_writable(FILE) == 0 && std::filesystem::is_empty(FILE)) {
        goto step2d;
      } else if ((flags.test(6) == 0 && flags.test(2) == 0 &&
                 is_writable(FILE) != 0 && isatty(STDIN_FILENO)) ||
                 (flags.test(3) == 1 && flags.test(6) == 0)) {
        /* "If the -f option is not specified, and either the permissions of
         * the file do not permit writing and the standard input is a terminal
         * or the -i option is specified, rm shall write a prompt to standard
         * error and read a line from the standard input." */
        std::string response;
get_response_force_remove_directory_0:
        std::cerr << "remove: " + FILE + " (y/n)";
        std::getline(std::cin, response);
        if (response == "n") {
          flags.set(6);
          continue;
        } else if (response == "y")
          flags.set(6);
        else {
          std::cerr << "invalid response\n";
          goto get_response_force_remove_directory_0;
        }
      } // from interpretation of step2c, it seems irrelevant to us
step2d:
      if (flags.test(6) == 0 && flags.test(3) == 1) {
        std::string response;
get_response_force_remove_directory_1:
        std::cerr << "remove: " + FILE + " (y/n)";
        std::getline(std::cin, response);
        if (response == "n") {
          flags.set(6);
          continue;
        }
        else if (response == "y")
          flags.set(6);
        else {
          std::cerr << "invalid response\n";
          goto get_response_force_remove_directory_1;
        }
      }
    }
step3:
    std::string response;
    if ((flags.test(2) == 0 && is_writable(FILE) != 0 &&
        isatty(STDIN_FILENO) && flags.test(6) == 0) || (flags.test(3) == 1 &&
        flags.test(6) == 0)) {
get_response_remove_0:
      std::cerr << "remove: " + FILE + " (y/n)";
      std::getline(std::cin, response);
      if (response == "n") {
        flags.set(6);
        continue;
      } else if (response == "y")
        flags.set(6);
      else {
        std::cerr << "invalid response\n";
        goto get_response_remove_0;
      }
    }
// step4:
    try {
      std::filesystem::remove_all(FILE);
    } catch (...) {
      ++ret;
    }
    if (flags.test(5) == 1)
      std::cout << "removed: " + FILE + '\n';
  }
  return ret;
}

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>