diff options
Diffstat (limited to 'src/VBox/Runtime/tools/RTRmDir.cpp')
-rw-r--r-- | src/VBox/Runtime/tools/RTRmDir.cpp | 369 |
1 files changed, 369 insertions, 0 deletions
diff --git a/src/VBox/Runtime/tools/RTRmDir.cpp b/src/VBox/Runtime/tools/RTRmDir.cpp new file mode 100644 index 00000000..6d9dd26f --- /dev/null +++ b/src/VBox/Runtime/tools/RTRmDir.cpp @@ -0,0 +1,369 @@ +/* $Id: RTRmDir.cpp $ */ +/** @file + * IPRT - Removes directory. + */ + +/* + * Copyright (C) 2013-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/path.h> +#include <iprt/err.h> +#include <iprt/initterm.h> +#include <iprt/message.h> + +#include <iprt/vfs.h> +#include <iprt/string.h> +#include <iprt/stream.h> +#include <iprt/getopt.h> +#include <iprt/buildconfig.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct RTCMDRMDIROPTS +{ + /** -v, --verbose */ + bool fVerbose; + /** -p, --parents */ + bool fParents; + /** Don't fail if directories that aren't empty. */ + bool fIgnoreNotEmpty; + /** Don't fail a directory doesn't exist (i.e. has already been removed). */ + bool fIgnoreNonExisting; + /** Whether to always use the VFS chain API (for testing). */ + bool fAlwaysUseChainApi; +} RTCMDRMDIROPTS; + + +/** + * Create one directory and any missing parent directories. + * + * @returns exit code + * @param pOpts The mkdir option. + * @param pszDir The path to the new directory. + */ +static int rtCmdRmDirOneWithParents(RTCMDRMDIROPTS const *pOpts, const char *pszDir) +{ + /* We need a copy we can work with here. */ + char *pszCopy = RTStrDup(pszDir); + if (!pszCopy) + return RTMsgErrorExitFailure("Out of string memory!"); + + int rc; + if (!pOpts->fAlwaysUseChainApi && !RTVfsChainIsSpec(pszDir) ) + { + size_t cchCopy = strlen(pszCopy); + do + { + rc = RTDirRemove(pszCopy); + if (RT_SUCCESS(rc)) + { + if (pOpts->fVerbose) + RTPrintf("%s\n", pszCopy); + } + else if ((rc == VERR_PATH_NOT_FOUND || rc == VERR_FILE_NOT_FOUND) && pOpts->fIgnoreNonExisting) + rc = VINF_SUCCESS; + else + { + if ((rc == VERR_DIR_NOT_EMPTY || rc == VERR_SHARING_VIOLATION) && pOpts->fIgnoreNotEmpty) + rc = VINF_SUCCESS; + else + RTMsgError("Failed to remove directory '%s': %Rrc", pszCopy, rc); + break; + } + + /* Strip off a component. */ + while (cchCopy > 0 && RTPATH_IS_SLASH(pszCopy[cchCopy - 1])) + cchCopy--; + while (cchCopy > 0 && !RTPATH_IS_SLASH(pszCopy[cchCopy - 1])) + cchCopy--; + while (cchCopy > 0 && RTPATH_IS_SLASH(pszCopy[cchCopy - 1])) + cchCopy--; + pszCopy[cchCopy] = '\0'; + } while (cchCopy > 0); + } + else + { + /* + * Strip the final path element from the pszDir spec. + */ + char *pszFinalPath; + char *pszSpec; + uint32_t offError; + rc = RTVfsChainSplitOffFinalPath(pszCopy, &pszSpec, &pszFinalPath, &offError); + if (RT_SUCCESS(rc)) + { + /* + * Open the root director/whatever. + */ + RTERRINFOSTATIC ErrInfo; + RTVFSDIR hVfsBaseDir; + if (pszSpec) + { + rc = RTVfsChainOpenDir(pszSpec, 0 /*fOpen*/, &hVfsBaseDir, &offError, RTErrInfoInitStatic(&ErrInfo)); + if (RT_FAILURE(rc)) + RTVfsChainMsgError("RTVfsChainOpenDir", pszSpec, rc, offError, &ErrInfo.Core); + else if (!pszFinalPath) + pszFinalPath = RTStrEnd(pszSpec, RTSTR_MAX); + } + else if (!RTPathStartsWithRoot(pszFinalPath)) + { + rc = RTVfsDirOpenNormal(".", 0 /*fOpen*/, &hVfsBaseDir); + if (RT_FAILURE(rc)) + RTMsgError("Failed to open '.' (for %s): %Rrc", rc, pszFinalPath); + } + else + { + char *pszRoot = pszFinalPath; + pszFinalPath = RTPathSkipRootSpec(pszFinalPath); + char const chSaved = *pszFinalPath; + *pszFinalPath = '\0'; + rc = RTVfsDirOpenNormal(pszRoot, 0 /*fOpen*/, &hVfsBaseDir); + *pszFinalPath = chSaved; + if (RT_FAILURE(rc)) + RTMsgError("Failed to open root dir for '%s': %Rrc", rc, pszRoot); + } + + /* + * Walk the path component by component, starting at the end. + */ + if (RT_SUCCESS(rc)) + { + size_t cchFinalPath = strlen(pszFinalPath); + while (RT_SUCCESS(rc) && cchFinalPath > 0) + { + rc = RTVfsDirRemoveDir(hVfsBaseDir, pszFinalPath, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + { + if (pOpts->fVerbose) + RTPrintf("%s\n", pszCopy); + } + else if ((rc == VERR_PATH_NOT_FOUND || rc == VERR_FILE_NOT_FOUND) && pOpts->fIgnoreNonExisting) + rc = VINF_SUCCESS; + else + { + if ((rc == VERR_DIR_NOT_EMPTY || rc == VERR_SHARING_VIOLATION) && pOpts->fIgnoreNotEmpty) + rc = VINF_SUCCESS; + else if (pszSpec) + RTMsgError("Failed to remove directory '%s:%s': %Rrc", pszSpec, pszFinalPath, rc); + else + RTMsgError("Failed to remove directory '%s': %Rrc", pszFinalPath, rc); + break; + } + + /* Strip off a component. */ + while (cchFinalPath > 0 && RTPATH_IS_SLASH(pszFinalPath[cchFinalPath - 1])) + cchFinalPath--; + while (cchFinalPath > 0 && !RTPATH_IS_SLASH(pszFinalPath[cchFinalPath - 1])) + cchFinalPath--; + while (cchFinalPath > 0 && RTPATH_IS_SLASH(pszFinalPath[cchFinalPath - 1])) + cchFinalPath--; + pszFinalPath[cchFinalPath] = '\0'; + } + + RTVfsDirRelease(hVfsBaseDir); + } + } + else + RTVfsChainMsgError("RTVfsChainOpenParentDir", pszCopy, rc, offError, NULL); + } + RTStrFree(pszCopy); + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +/** + * Removes one directory. + * + * @returns exit code + * @param pOpts The mkdir option. + * @param pszDir The path to the new directory. + */ +static RTEXITCODE rtCmdRmDirOne(RTCMDRMDIROPTS const *pOpts, const char *pszDir) +{ + int rc; + if (!pOpts->fAlwaysUseChainApi && !RTVfsChainIsSpec(pszDir) ) + rc = RTDirRemove(pszDir); + else + { + RTVFSDIR hVfsDir; + const char *pszChild; + uint32_t offError; + RTERRINFOSTATIC ErrInfo; + rc = RTVfsChainOpenParentDir(pszDir, 0 /*fOpen*/, &hVfsDir, &pszChild, &offError, RTErrInfoInitStatic(&ErrInfo)); + if (RT_SUCCESS(rc)) + { + rc = RTVfsDirRemoveDir(hVfsDir, pszChild, 0 /*fFlags*/); + RTVfsDirRelease(hVfsDir); + } + else + return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenParentDir", pszDir, rc, offError, &ErrInfo.Core); + } + if (RT_SUCCESS(rc)) + { + if (pOpts->fVerbose) + RTPrintf("%s\n", pszDir); + return RTEXITCODE_SUCCESS; + } + if ((rc == VERR_DIR_NOT_EMPTY || rc == VERR_SHARING_VIOLATION) && pOpts->fIgnoreNotEmpty) + return RTEXITCODE_SUCCESS; /** @todo be verbose about this? */ + if ((rc == VERR_PATH_NOT_FOUND || rc == VERR_FILE_NOT_FOUND) && pOpts->fIgnoreNonExisting) + return RTEXITCODE_SUCCESS; /** @todo be verbose about this? */ + return RTMsgErrorExitFailure("Failed to remove '%s': %Rrc", pszDir, rc); +} + + +static RTEXITCODE RTCmdRmDir(unsigned cArgs, char **papszArgs) +{ + /* + * Parse the command line. + */ + static const RTGETOPTDEF s_aOptions[] = + { + /* operations */ + { "--parents", 'p', RTGETOPT_REQ_NOTHING }, + { "--ignore-fail-on-non-empty", 'F', RTGETOPT_REQ_NOTHING }, + { "--ignore-non-existing", 'E', RTGETOPT_REQ_NOTHING }, + { "--always-use-vfs-chain-api", 'A', RTGETOPT_REQ_NOTHING }, + { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, + }; + + RTGETOPTSTATE GetState; + int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1, + RTGETOPTINIT_FLAGS_OPTS_FIRST); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOpt failed: %Rrc", rc); + + RTCMDRMDIROPTS Opts; + Opts.fVerbose = false; + Opts.fParents = false; + Opts.fIgnoreNotEmpty = false; + Opts.fIgnoreNonExisting = false; + Opts.fAlwaysUseChainApi = false; + + RTGETOPTUNION ValueUnion; + while ( (rc = RTGetOpt(&GetState, &ValueUnion)) != 0 + && rc != VINF_GETOPT_NOT_OPTION) + { + switch (rc) + { + case 'p': + Opts.fParents = true; + break; + + case 'v': + Opts.fVerbose = true; + break; + + case 'A': + Opts.fAlwaysUseChainApi = true; + break; + + case 'E': + Opts.fIgnoreNonExisting = true; + break; + + case 'F': + Opts.fIgnoreNotEmpty = true; + break; + + case 'h': + RTPrintf("Usage: %s [options] <dir> [..]\n" + "\n" + "Removes empty directories.\n" + "\n" + "Options:\n" + " -p, --parent\n" + " Remove specified parent directories too.\n" + " -F, --ignore-fail-on-non-empty\n" + " Do not fail if a directory is not empty, just ignore it.\n" + " This is really handy with the -p option.\n" + " -E, --ignore-non-existing\n" + " Do not fail if a specified directory is not there.\n" + " -v, --verbose\n" + " Tell which directories get remove.\n" + " -A, --always-use-vfs-chain-api\n" + " Always use the VFS API.\n" + , papszArgs[0]); + return RTEXITCODE_SUCCESS; + + case 'V': + RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision()); + return RTEXITCODE_SUCCESS; + + default: + return RTGetOptPrintError(rc, &ValueUnion); + } + } + + + /* + * No files means error. + */ + if (rc != VINF_GETOPT_NOT_OPTION) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "No directories specified.\n"); + + /* + * Work thru the specified dirs. + */ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + while (rc == VINF_GETOPT_NOT_OPTION) + { + if (Opts.fParents) + rc = rtCmdRmDirOneWithParents(&Opts, ValueUnion.psz); + else + rc = rtCmdRmDirOne(&Opts, ValueUnion.psz); + if (RT_FAILURE(rc)) + rcExit = RTEXITCODE_FAILURE; + + /* next */ + rc = RTGetOpt(&GetState, &ValueUnion); + } + if (rc != 0) + rcExit = RTGetOptPrintError(rc, &ValueUnion); + + return rcExit; +} + + +int main(int argc, char **argv) +{ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + return RTCmdRmDir(argc, argv); +} + |