VirtualBox

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

Last change on this file since 73041 was 69720, checked in by vboxsync, 7 years ago

IPRT: More work on directory relative APIs (NT mainly) and VFS; introducing RTRmDir (test) tool.

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