VirtualBox

source: vbox/trunk/src/VBox/Runtime/tools/RTRmDir.cpp

Last change on this file was 106061, checked in by vboxsync, 3 months ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 13.2 KB
Line 
1/* $Id: RTRmDir.cpp 106061 2024-09-16 14:03:52Z vboxsync $ */
2/** @file
3 * IPRT - Removes directory.
4 */
5
6/*
7 * Copyright (C) 2013-2024 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * The contents of this file may alternatively be used under the terms
26 * of the Common Development and Distribution License Version 1.0
27 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
28 * in the VirtualBox distribution, in which case the provisions of the
29 * CDDL are applicable instead of those of the GPL.
30 *
31 * You may elect to license modified versions of this file under the
32 * terms and conditions of either the GPL or the CDDL or both.
33 *
34 * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
35 */
36
37
38/*********************************************************************************************************************************
39* Header Files *
40*********************************************************************************************************************************/
41#include <iprt/path.h>
42#include <iprt/err.h>
43#include <iprt/initterm.h>
44#include <iprt/message.h>
45
46#include <iprt/vfs.h>
47#include <iprt/string.h>
48#include <iprt/stream.h>
49#include <iprt/getopt.h>
50#include <iprt/buildconfig.h>
51
52
53/*********************************************************************************************************************************
54* Structures and Typedefs *
55*********************************************************************************************************************************/
56typedef struct RTCMDRMDIROPTS
57{
58 /** -v, --verbose */
59 bool fVerbose;
60 /** -p, --parents */
61 bool fParents;
62 /** Don't fail if directories that aren't empty. */
63 bool fIgnoreNotEmpty;
64 /** Don't fail a directory doesn't exist (i.e. has already been removed). */
65 bool fIgnoreNonExisting;
66 /** Whether to always use the VFS chain API (for testing). */
67 bool fAlwaysUseChainApi;
68} RTCMDRMDIROPTS;
69
70
71/**
72 * Create one directory and any missing parent directories.
73 *
74 * @returns exit code
75 * @param pOpts The mkdir option.
76 * @param pszDir The path to the new directory.
77 */
78static int rtCmdRmDirOneWithParents(RTCMDRMDIROPTS const *pOpts, const char *pszDir)
79{
80 /* We need a copy we can work with here. */
81 char *pszCopy = RTStrDup(pszDir);
82 if (!pszCopy)
83 return RTMsgErrorExitFailure("Out of string memory!");
84
85 int rc;
86 if (!pOpts->fAlwaysUseChainApi && !RTVfsChainIsSpec(pszDir) )
87 {
88 size_t cchCopy = strlen(pszCopy);
89 do
90 {
91 rc = RTDirRemove(pszCopy);
92 if (RT_SUCCESS(rc))
93 {
94 if (pOpts->fVerbose)
95 RTPrintf("%s\n", pszCopy);
96 }
97 else if ((rc == VERR_PATH_NOT_FOUND || rc == VERR_FILE_NOT_FOUND) && pOpts->fIgnoreNonExisting)
98 rc = VINF_SUCCESS;
99 else
100 {
101 if ((rc == VERR_DIR_NOT_EMPTY || rc == VERR_SHARING_VIOLATION) && pOpts->fIgnoreNotEmpty)
102 rc = VINF_SUCCESS;
103 else
104 RTMsgError("Failed to remove directory '%s': %Rrc", pszCopy, rc);
105 break;
106 }
107
108 /* Strip off a component. */
109 while (cchCopy > 0 && RTPATH_IS_SLASH(pszCopy[cchCopy - 1]))
110 cchCopy--;
111 while (cchCopy > 0 && !RTPATH_IS_SLASH(pszCopy[cchCopy - 1]))
112 cchCopy--;
113 while (cchCopy > 0 && RTPATH_IS_SLASH(pszCopy[cchCopy - 1]))
114 cchCopy--;
115 pszCopy[cchCopy] = '\0';
116 } while (cchCopy > 0);
117 }
118 else
119 {
120 /*
121 * Strip the final path element from the pszDir spec.
122 */
123 char *pszFinalPath;
124 char *pszSpec;
125 uint32_t offError;
126 rc = RTVfsChainSplitOffFinalPath(pszCopy, &pszSpec, &pszFinalPath, &offError);
127 if (RT_SUCCESS(rc))
128 {
129 /*
130 * Open the root director/whatever.
131 */
132 RTERRINFOSTATIC ErrInfo;
133 RTVFSDIR hVfsBaseDir;
134 if (pszSpec)
135 {
136 rc = RTVfsChainOpenDir(pszSpec, 0 /*fOpen*/, &hVfsBaseDir, &offError, RTErrInfoInitStatic(&ErrInfo));
137 if (RT_FAILURE(rc))
138 RTVfsChainMsgError("RTVfsChainOpenDir", pszSpec, rc, offError, &ErrInfo.Core);
139 else if (!pszFinalPath)
140 pszFinalPath = RTStrEnd(pszSpec, RTSTR_MAX);
141 }
142 else if (!RTPathStartsWithRoot(pszFinalPath))
143 {
144 rc = RTVfsDirOpenNormal(".", 0 /*fOpen*/, &hVfsBaseDir);
145 if (RT_FAILURE(rc))
146 RTMsgError("Failed to open '.' (for %s): %Rrc", rc, pszFinalPath);
147 }
148 else
149 {
150 char *pszRoot = pszFinalPath;
151 pszFinalPath = RTPathSkipRootSpec(pszFinalPath);
152 char const chSaved = *pszFinalPath;
153 *pszFinalPath = '\0';
154 rc = RTVfsDirOpenNormal(pszRoot, 0 /*fOpen*/, &hVfsBaseDir);
155 *pszFinalPath = chSaved;
156 if (RT_FAILURE(rc))
157 RTMsgError("Failed to open root dir for '%s': %Rrc", rc, pszRoot);
158 }
159
160 /*
161 * Walk the path component by component, starting at the end.
162 */
163 if (RT_SUCCESS(rc))
164 {
165 size_t cchFinalPath = strlen(pszFinalPath);
166 while (RT_SUCCESS(rc) && cchFinalPath > 0)
167 {
168 rc = RTVfsDirRemoveDir(hVfsBaseDir, pszFinalPath, 0 /*fFlags*/);
169 if (RT_SUCCESS(rc))
170 {
171 if (pOpts->fVerbose)
172 RTPrintf("%s\n", pszCopy);
173 }
174 else if ((rc == VERR_PATH_NOT_FOUND || rc == VERR_FILE_NOT_FOUND) && pOpts->fIgnoreNonExisting)
175 rc = VINF_SUCCESS;
176 else
177 {
178 if ((rc == VERR_DIR_NOT_EMPTY || rc == VERR_SHARING_VIOLATION) && pOpts->fIgnoreNotEmpty)
179 rc = VINF_SUCCESS;
180 else if (pszSpec)
181 RTMsgError("Failed to remove directory '%s:%s': %Rrc", pszSpec, pszFinalPath, rc);
182 else
183 RTMsgError("Failed to remove directory '%s': %Rrc", pszFinalPath, rc);
184 break;
185 }
186
187 /* Strip off a component. */
188 while (cchFinalPath > 0 && RTPATH_IS_SLASH(pszFinalPath[cchFinalPath - 1]))
189 cchFinalPath--;
190 while (cchFinalPath > 0 && !RTPATH_IS_SLASH(pszFinalPath[cchFinalPath - 1]))
191 cchFinalPath--;
192 while (cchFinalPath > 0 && RTPATH_IS_SLASH(pszFinalPath[cchFinalPath - 1]))
193 cchFinalPath--;
194 pszFinalPath[cchFinalPath] = '\0';
195 }
196
197 RTVfsDirRelease(hVfsBaseDir);
198 }
199 }
200 else
201 RTVfsChainMsgError("RTVfsChainOpenParentDir", pszCopy, rc, offError, NULL);
202 }
203 RTStrFree(pszCopy);
204 return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
205}
206
207
208/**
209 * Removes one directory.
210 *
211 * @returns exit code
212 * @param pOpts The mkdir option.
213 * @param pszDir The path to the new directory.
214 */
215static RTEXITCODE rtCmdRmDirOne(RTCMDRMDIROPTS const *pOpts, const char *pszDir)
216{
217 int rc;
218 if (!pOpts->fAlwaysUseChainApi && !RTVfsChainIsSpec(pszDir) )
219 rc = RTDirRemove(pszDir);
220 else
221 {
222 RTVFSDIR hVfsDir;
223 const char *pszChild;
224 uint32_t offError;
225 RTERRINFOSTATIC ErrInfo;
226 rc = RTVfsChainOpenParentDir(pszDir, 0 /*fOpen*/, &hVfsDir, &pszChild, &offError, RTErrInfoInitStatic(&ErrInfo));
227 if (RT_SUCCESS(rc))
228 {
229 rc = RTVfsDirRemoveDir(hVfsDir, pszChild, 0 /*fFlags*/);
230 RTVfsDirRelease(hVfsDir);
231 }
232 else
233 return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenParentDir", pszDir, rc, offError, &ErrInfo.Core);
234 }
235 if (RT_SUCCESS(rc))
236 {
237 if (pOpts->fVerbose)
238 RTPrintf("%s\n", pszDir);
239 return RTEXITCODE_SUCCESS;
240 }
241 if ((rc == VERR_DIR_NOT_EMPTY || rc == VERR_SHARING_VIOLATION) && pOpts->fIgnoreNotEmpty)
242 return RTEXITCODE_SUCCESS; /** @todo be verbose about this? */
243 if ((rc == VERR_PATH_NOT_FOUND || rc == VERR_FILE_NOT_FOUND) && pOpts->fIgnoreNonExisting)
244 return RTEXITCODE_SUCCESS; /** @todo be verbose about this? */
245 return RTMsgErrorExitFailure("Failed to remove '%s': %Rrc", pszDir, rc);
246}
247
248
249static RTEXITCODE RTCmdRmDir(unsigned cArgs, char **papszArgs)
250{
251 /*
252 * Parse the command line.
253 */
254 static const RTGETOPTDEF s_aOptions[] =
255 {
256 /* operations */
257 { "--parents", 'p', RTGETOPT_REQ_NOTHING },
258 { "--ignore-fail-on-non-empty", 'F', RTGETOPT_REQ_NOTHING },
259 { "--ignore-non-existing", 'E', RTGETOPT_REQ_NOTHING },
260 { "--always-use-vfs-chain-api", 'A', RTGETOPT_REQ_NOTHING },
261 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
262 };
263
264 RTGETOPTSTATE GetState;
265 int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1,
266 RTGETOPTINIT_FLAGS_OPTS_FIRST);
267 if (RT_FAILURE(rc))
268 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOpt failed: %Rrc", rc);
269
270 RTCMDRMDIROPTS Opts;
271 Opts.fVerbose = false;
272 Opts.fParents = false;
273 Opts.fIgnoreNotEmpty = false;
274 Opts.fIgnoreNonExisting = false;
275 Opts.fAlwaysUseChainApi = false;
276
277 RTGETOPTUNION ValueUnion;
278 while ( (rc = RTGetOpt(&GetState, &ValueUnion)) != 0
279 && rc != VINF_GETOPT_NOT_OPTION)
280 {
281 switch (rc)
282 {
283 case 'p':
284 Opts.fParents = true;
285 break;
286
287 case 'v':
288 Opts.fVerbose = true;
289 break;
290
291 case 'A':
292 Opts.fAlwaysUseChainApi = true;
293 break;
294
295 case 'E':
296 Opts.fIgnoreNonExisting = true;
297 break;
298
299 case 'F':
300 Opts.fIgnoreNotEmpty = true;
301 break;
302
303 case 'h':
304 RTPrintf("Usage: %s [options] <dir> [..]\n"
305 "\n"
306 "Removes empty directories.\n"
307 "\n"
308 "Options:\n"
309 " -p, --parent\n"
310 " Remove specified parent directories too.\n"
311 " -F, --ignore-fail-on-non-empty\n"
312 " Do not fail if a directory is not empty, just ignore it.\n"
313 " This is really handy with the -p option.\n"
314 " -E, --ignore-non-existing\n"
315 " Do not fail if a specified directory is not there.\n"
316 " -v, --verbose\n"
317 " Tell which directories get remove.\n"
318 " -A, --always-use-vfs-chain-api\n"
319 " Always use the VFS API.\n"
320 , papszArgs[0]);
321 return RTEXITCODE_SUCCESS;
322
323 case 'V':
324 RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
325 return RTEXITCODE_SUCCESS;
326
327 default:
328 return RTGetOptPrintError(rc, &ValueUnion);
329 }
330 }
331
332
333 /*
334 * No files means error.
335 */
336 if (rc != VINF_GETOPT_NOT_OPTION)
337 return RTMsgErrorExit(RTEXITCODE_FAILURE, "No directories specified.\n");
338
339 /*
340 * Work thru the specified dirs.
341 */
342 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
343 while (rc == VINF_GETOPT_NOT_OPTION)
344 {
345 if (Opts.fParents)
346 rc = rtCmdRmDirOneWithParents(&Opts, ValueUnion.psz);
347 else
348 rc = rtCmdRmDirOne(&Opts, ValueUnion.psz);
349 if (RT_FAILURE(rc))
350 rcExit = RTEXITCODE_FAILURE;
351
352 /* next */
353 rc = RTGetOpt(&GetState, &ValueUnion);
354 }
355 if (rc != 0)
356 rcExit = RTGetOptPrintError(rc, &ValueUnion);
357
358 return rcExit;
359}
360
361
362int main(int argc, char **argv)
363{
364 int rc = RTR3InitExe(argc, &argv, 0);
365 if (RT_FAILURE(rc))
366 return RTMsgInitFailure(rc);
367 return RTCmdRmDir(argc, argv);
368}
369
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette