From 56b41fc6ae93e43d08b2ac359c96e70b774b8ba9 Mon Sep 17 00:00:00 2001 From: hudson-newey Date: Sat, 2 Nov 2024 22:44:00 +1000 Subject: [PATCH] Add -I flag --- README.md | 12 ++--- src/cli/args.go | 1 + src/cli/docs.go | 21 ++++++-- src/patches/rm.go | 127 +++++++++++++++++++++------------------------- 4 files changed, 83 insertions(+), 78 deletions(-) diff --git a/README.md b/README.md index 0d7bf2f..ebf9284 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ A wrapper for the "rm" command with soft-deletes, config-based deletion, debug i ## "GNU Like" command line arguments - `-i` Interactivly prompt before each deletion request +- `-I` Prompt if deleting more three files - `--help` Display help information (without deletion) - `--version` Display version information (without deletion) @@ -28,10 +29,9 @@ A wrapper for the "rm" command with soft-deletes, config-based deletion, debug i - `-v`, `--verbose` Emit additional verbose information - `--version` Show version information - `--help` Show help information -- `-I` Prompt if deleting more than three files - `--interactive[=WHEN]` Interactive with a custom threshold - `--one-file-system` Do not allow cross-file-system deletes -- `-f`, `--force` (partially implemented) +- `-f`, `--force` Bypass protections ## Features @@ -111,10 +111,10 @@ hard: soft: - "*.bak" # do not allow deleting these files/directories -# without using the `-f` or `--force` flags -# this does not make the file un-deletable -# through other tools, but it does protect -# against accidental deletion through 2rm +# without using the `--bypass-protected` flag this +# does not make the file protected at the system level +# through other tools, but it does protect against +# accidental deletion through 2rm protected: - ".ssh/" ``` diff --git a/src/cli/args.go b/src/cli/args.go index 35cb905..8e52158 100644 --- a/src/cli/args.go +++ b/src/cli/args.go @@ -11,5 +11,6 @@ const NOTIFICATION_CLA = "--notify" // gnu rm CLI arguments const INTERACTIVE_CLA = "-i" +const INTERACTIVE_GROUP_CLA = "-I" const HELP_CLA = "--help" const VERSION_CLA = "--version" diff --git a/src/cli/docs.go b/src/cli/docs.go index a13cd87..0ded80d 100644 --- a/src/cli/docs.go +++ b/src/cli/docs.go @@ -10,15 +10,28 @@ Mark FILE(s) for deletion. "GNU Like" OPTION(s): -i Prompt before every deletion request ---force Bypass 2rm protections + +-I Prompt once before deleting more than three files + +-f, --force Bypass protections 2rm OPTION(s) Flags: + --overwrite Overwrite the disk location location with zeros + --hard Do not soft-delete FILE(s) + --soft Soft delete a file and a store backup (default /tmp/2rm) ---silent Do not print out additional information priduced by 2rm. This is useful for scripting situations ---dry-run Perform a dry run and show all the files that would be deleted ---bypass-protected Using this flag will allow you to delete a file protected by the 2rm config + +--silent Do not print out additional information priduced by 2rm. + This is useful for scripting situations + +--dry-run Perform a dry run and show all the files that would be + deleted if run without the dry-run flag + +--bypass-protected Using this flag will allow you to delete a file + protected by the 2rm config + --notify Send a system notification once deletion is complete By default, 2rm will soft-delete a file and store a backup (default /tmp/2rm) diff --git a/src/patches/rm.go b/src/patches/rm.go index 69889bc..9ab6ce5 100644 --- a/src/patches/rm.go +++ b/src/patches/rm.go @@ -15,22 +15,17 @@ import ( ) const TRASH_DIR_PERMISSIONS = 0755 +const INTERACTIVE_THRESHOLD = 3 func RmPatch(arguments []string, config models.Config) { - forceHardDelete := util.InArray(arguments, cli.HARD_DELETE_CLA) - forceSoftDelete := util.InArray(arguments, cli.SOFT_DELETE_CLA) silent := util.InArray(arguments, cli.SILENT_CLA) dryRun := util.InArray(arguments, cli.DRY_RUN_CLA) - bypassProtected := util.InArray(arguments, cli.BYPASS_PROTECTED_CLA) - overwrite := util.InArray(arguments, cli.OVERWRITE_CLA) shouldNotify := util.InArray(arguments, cli.NOTIFICATION_CLA) requestingHelp := util.InArray(arguments, cli.HELP_CLA) requestingVersion := util.InArray(arguments, cli.VERSION_CLA) - actionedArgs := removeUnNeededArguments( - removeDangerousArguments(arguments), - ) + actionedArgs := removeDangerousArguments(arguments) if requestingHelp { cli.PrintHelp() @@ -54,36 +49,7 @@ func RmPatch(arguments []string, config models.Config) { return } - for _, path := range filePaths { - absolutePath := relativeToAbsolute(path) - isTmp := isTmpPath(absolutePath) - - isProtected := config.IsProtected(absolutePath) - if isProtected && bypassProtected { - fmt.Println("Cannot delete protected file:", absolutePath) - fmt.Println("Use the --bypass-protected flag to force deletion") - continue - } - - isConfigHardDelete := config.ShouldHardDelete(absolutePath) - isConfigSoftDelete := config.ShouldSoftDelete(absolutePath) - - // overwriting a file is not exclusive to hard/soft deletes - // meaning that you can overwrite the contents of a file with zeros and - // also soft delete it - // I have made this decision because I think soft-deleting an - // overwritten file has auditing/logging use cases - // e.g. Who deleted this file? When was it deleted? - // if we hard deleted the file, we would lose this information - isConfigOverwrite := config.ShouldOverwrite(absolutePath) - if overwrite || isConfigOverwrite { - overwriteFile(absolutePath) - } - - shouldHardDelete := isTmp || forceHardDelete || isConfigHardDelete && !isConfigSoftDelete && !forceSoftDelete - - deletePath(absolutePath, shouldHardDelete, config, arguments) - } + deletePaths(filePaths, config, arguments) if shouldNotify { fileNames := strings.Join(filePaths, ", ") @@ -94,28 +60,6 @@ func RmPatch(arguments []string, config models.Config) { } } -func removeUnNeededArguments(arguments []string) []string { - returnedArguments := []string{} - unNeededArguments := []string{ - "-r", - cli.HARD_DELETE_CLA, - cli.SOFT_DELETE_CLA, - cli.SILENT_CLA, - cli.DRY_RUN_CLA, - cli.BYPASS_PROTECTED_CLA, - cli.OVERWRITE_CLA, - cli.NOTIFICATION_CLA, - } - - for _, arg := range arguments { - if !util.InArray(unNeededArguments, arg) { - returnedArguments = append(returnedArguments, arg) - } - } - - return returnedArguments -} - func removeDangerousArguments(arguments []string) []string { // I have excluded the root slash as a forbidden argument just incase // you make a typo like rm ./myDirectory / @@ -165,20 +109,61 @@ func backupFileName(path string) string { return result + ".bak" } -func deletePath(path string, hard bool, config models.Config, arguments []string) { - isInteractive := util.InArray(arguments, cli.INTERACTIVE_CLA) +func deletePaths(paths []string, config models.Config, arguments []string) { + forceHardDelete := util.InArray(arguments, cli.HARD_DELETE_CLA) + forceSoftDelete := util.InArray(arguments, cli.SOFT_DELETE_CLA) + bypassProtected := util.InArray(arguments, cli.BYPASS_PROTECTED_CLA) + overwrite := util.InArray(arguments, cli.OVERWRITE_CLA) - if isInteractive { - fmt.Println("Are you sure you want to delete", path, "? (y/n)") - var response string - fmt.Scanln(&response) + hasInteraciveCla := util.InArray(arguments, cli.INTERACTIVE_CLA) + hasGroupInteractiveCla := util.InArray(arguments, cli.INTERACTIVE_GROUP_CLA) + isInteractiveGroup := hasGroupInteractiveCla && len(paths) >= INTERACTIVE_THRESHOLD + isInteractive := hasInteraciveCla || isInteractiveGroup + + for _, path := range paths { + if isInteractive { + fmt.Println("Are you sure you want to delete", path, "? (y/n)") + var response string + fmt.Scanln(&response) + + if response != "y" && response != "yes" { + fmt.Println("Skipping file", path) + continue + } + } - if response != "y" && response != "yes" { - fmt.Println("Exiting without removing file(s).") - return + absolutePath := relativeToAbsolute(path) + isTmp := isTmpPath(absolutePath) + + isProtected := config.IsProtected(absolutePath) + if isProtected && bypassProtected { + fmt.Println("Cannot delete protected file:", absolutePath) + fmt.Println("Use the --bypass-protected flag to force deletion") + continue + } + + isConfigHardDelete := config.ShouldHardDelete(absolutePath) + isConfigSoftDelete := config.ShouldSoftDelete(absolutePath) + + // overwriting a file is not exclusive to hard/soft deletes + // meaning that you can overwrite the contents of a file with zeros and + // also soft delete it + // I have made this decision because I think soft-deleting an + // overwritten file has auditing/logging use cases + // e.g. Who deleted this file? When was it deleted? + // if we hard deleted the file, we would lose this information + isConfigOverwrite := config.ShouldOverwrite(absolutePath) + if overwrite || isConfigOverwrite { + overwriteFile(absolutePath) } + + shouldHardDelete := isTmp || forceHardDelete || isConfigHardDelete && !isConfigSoftDelete && !forceSoftDelete + + deletePath(absolutePath, shouldHardDelete, config, arguments) } +} +func deletePath(path string, hard bool, config models.Config, arguments []string) { if hard { hardDelete(path) } else { @@ -225,6 +210,12 @@ func softDelete(filePath string, tempDir string) { } err = os.Remove(absoluteSrcPath) + if err != nil { + fmt.Println("Error deleting file:", err) + + // pause the program so the user can see the error message + fmt.Scanln() + } } func hardDelete(filePath string) {