main
cpp 466 lines 15.6 KB
Raw
1 // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3 #include "precomp.h"
4
5
6 // Exit macros
7 #define DirExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_DIRUTIL, x, s, __VA_ARGS__)
8 #define DirExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_DIRUTIL, x, s, __VA_ARGS__)
9 #define DirExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_DIRUTIL, x, s, __VA_ARGS__)
10 #define DirExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_DIRUTIL, x, s, __VA_ARGS__)
11 #define DirExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_DIRUTIL, x, s, __VA_ARGS__)
12 #define DirExitWithRootFailure(x, e, s, ...) ExitWithRootFailureSource(DUTIL_SOURCE_DIRUTIL, x, e, s, __VA_ARGS__)
13 #define DirExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_DIRUTIL, x, s, __VA_ARGS__)
14 #define DirExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_DIRUTIL, p, x, e, s, __VA_ARGS__)
15 #define DirExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_DIRUTIL, p, x, s, __VA_ARGS__)
16 #define DirExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_DIRUTIL, p, x, e, s, __VA_ARGS__)
17 #define DirExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_DIRUTIL, p, x, s, __VA_ARGS__)
18 #define DirExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_DIRUTIL, e, x, s, __VA_ARGS__)
19 #define DirExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_DIRUTIL, g, x, s, __VA_ARGS__)
20 #define DirExitOnPathFailure(x, b, s, ...) ExitOnPathFailureSource(DUTIL_SOURCE_DIRUTIL, x, b, s, __VA_ARGS__)
21 #define DirExitWithPathLastError(x, s, ...) ExitWithPathLastErrorSource(DUTIL_SOURCE_DIRUTIL, x, s, __VA_ARGS__)
22
23
24 /*******************************************************************
25 DirExists
26
27 *******************************************************************/
28 extern "C" BOOL DAPI DirExists(
29 __in_z LPCWSTR wzPath,
30 __out_opt DWORD *pdwAttributes
31 )
32 {
33 Assert(wzPath);
34
35 BOOL fExists = FALSE;
36
37 DWORD dwAttributes = ::GetFileAttributesW(wzPath);
38 if (0xFFFFFFFF == dwAttributes) // TODO: figure out why "INVALID_FILE_ATTRIBUTES" can't be used here
39 {
40 ExitFunction();
41 }
42
43 if (dwAttributes & FILE_ATTRIBUTE_DIRECTORY)
44 {
45 if (pdwAttributes)
46 {
47 *pdwAttributes = dwAttributes;
48 }
49
50 fExists = TRUE;
51 }
52
53 LExit:
54 return fExists;
55 }
56
57
58 /*******************************************************************
59 DirCreateTempPath
60
61 *******************************************************************/
62 extern "C" HRESULT DAPI DirCreateTempPath(
63 __in_z LPCWSTR wzPrefix,
64 __out_opt LPWSTR* psczTempFile
65 )
66 {
67 return PathCreateTempFile(NULL, NULL, 0, wzPrefix, 0, psczTempFile, NULL);
68 }
69
70
71 /*******************************************************************
72 DirEnsureExists
73
74 *******************************************************************/
75 extern "C" HRESULT DAPI DirEnsureExists(
76 __in_z LPCWSTR wzPath,
77 __in_opt LPSECURITY_ATTRIBUTES psa
78 )
79 {
80 HRESULT hr = S_OK;
81 UINT er;
82
83 // try to create this directory
84 if (!::CreateDirectoryW(wzPath, psa))
85 {
86 // if the directory already exists, bail
87 er = ::GetLastError();
88 if (ERROR_ALREADY_EXISTS == er)
89 {
90 ExitFunction1(hr = S_OK);
91 }
92 else if (ERROR_PATH_NOT_FOUND != er && DirExists(wzPath, NULL)) // if the directory happens to exist (don't check if CreateDirectory said it doesn't), declare success.
93 {
94 ExitFunction1(hr = S_OK);
95 }
96
97 // get the parent path and try to create it
98 LPWSTR pwzLastSlash = NULL;
99 for (LPWSTR pwz = const_cast<LPWSTR>(wzPath); *pwz; ++pwz)
100 {
101 if (*pwz == L'\\')
102 {
103 pwzLastSlash = pwz;
104 }
105 }
106
107 // if there is no parent directory fail
108 DirExitOnNullDebugTrace(pwzLastSlash, hr, HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND), "cannot find parent path");
109
110 *pwzLastSlash = L'\0'; // null terminate the parent path
111 hr = DirEnsureExists(wzPath, psa); // recurse!
112 *pwzLastSlash = L'\\'; // put the slash back
113 DirExitOnFailureDebugTrace(hr, "failed to create path: %ls", wzPath);
114
115 // try to create the directory now that all parents are created
116 if (!::CreateDirectoryW(wzPath, psa))
117 {
118 // if the directory already exists for some reason no error
119 er = ::GetLastError();
120 if (ERROR_ALREADY_EXISTS == er)
121 {
122 hr = S_FALSE;
123 }
124 else
125 {
126 hr = HRESULT_FROM_WIN32(er);
127 }
128 }
129 else
130 {
131 hr = S_OK;
132 }
133 }
134
135 LExit:
136 return hr;
137 }
138
139
140 /*******************************************************************
141 DirEnsureDelete - removes an entire directory structure
142
143 *******************************************************************/
144 extern "C" HRESULT DAPI DirEnsureDelete(
145 __in_z LPCWSTR wzPath,
146 __in BOOL fDeleteFiles,
147 __in BOOL fRecurse
148 )
149 {
150 HRESULT hr = S_OK;
151 DWORD dwDeleteFlags = 0;
152
153 dwDeleteFlags |= fDeleteFiles ? DIR_DELETE_FILES : 0;
154 dwDeleteFlags |= fRecurse ? DIR_DELETE_RECURSE : 0;
155
156 hr = DirEnsureDeleteEx(wzPath, dwDeleteFlags);
157 return hr;
158 }
159
160
161 /*******************************************************************
162 DirEnsureDeleteEx - removes an entire directory structure
163
164 *******************************************************************/
165 extern "C" HRESULT DAPI DirEnsureDeleteEx(
166 __in_z LPCWSTR wzPath,
167 __in DWORD dwFlags
168 )
169 {
170 Assert(wzPath && *wzPath);
171
172 HRESULT hr = S_OK;
173 DWORD er = ERROR_SUCCESS;
174
175 DWORD dwAttrib = 0;
176 HANDLE hFind = INVALID_HANDLE_VALUE;
177 LPWSTR sczDelete = NULL;
178 WIN32_FIND_DATAW wfd = { };
179
180 BOOL fDeleteFiles = (DIR_DELETE_FILES == (dwFlags & DIR_DELETE_FILES));
181 BOOL fRecurse = (DIR_DELETE_RECURSE == (dwFlags & DIR_DELETE_RECURSE));
182 BOOL fScheduleDelete = (DIR_DELETE_SCHEDULE == (dwFlags & DIR_DELETE_SCHEDULE));
183 WCHAR wzSafeFileName[MAX_PATH + 1] = { };
184 LPWSTR sczTempDirectory = NULL;
185 LPWSTR sczTempPath = NULL;
186
187 if (-1 == (dwAttrib = ::GetFileAttributesW(wzPath)))
188 {
189 DirExitWithPathLastError(hr, "Failed to get attributes for path: %ls", wzPath);
190
191 ExitFunction1(hr = E_PATHNOTFOUND);
192 }
193
194 if (dwAttrib & FILE_ATTRIBUTE_DIRECTORY)
195 {
196 if (dwAttrib & FILE_ATTRIBUTE_READONLY)
197 {
198 if (!::SetFileAttributesW(wzPath, FILE_ATTRIBUTE_NORMAL))
199 {
200 DirExitWithPathLastError(hr, "Failed to remove read-only attribute from path: %ls", wzPath);
201
202 ExitFunction1(hr = E_PATHNOTFOUND);
203 }
204 }
205
206 // If we're deleting files and/or child directories loop through the contents of the directory, but skip junctions.
207 if ((fDeleteFiles || fRecurse) && (0 == (dwAttrib & FILE_ATTRIBUTE_REPARSE_POINT)))
208 {
209 if (fScheduleDelete)
210 {
211 hr = PathGetTempPath(&sczTempDirectory, NULL);
212 DirExitOnFailure(hr, "Failed to get temp directory.");
213 }
214
215 // Delete everything in this directory.
216 hr = PathConcat(wzPath, L"*.*", &sczDelete);
217 DirExitOnFailure(hr, "Failed to concat wild cards to string: %ls", wzPath);
218
219 hFind = ::FindFirstFileW(sczDelete, &wfd);
220 if (INVALID_HANDLE_VALUE == hFind)
221 {
222 DirExitWithLastError(hr, "failed to get first file in directory: %ls", wzPath);
223 }
224
225 do
226 {
227 // Skip the dot directories.
228 if (L'.' == wfd.cFileName[0] && (L'\0' == wfd.cFileName[1] || (L'.' == wfd.cFileName[1] && L'\0' == wfd.cFileName[2])))
229 {
230 continue;
231 }
232
233 // For extra safety and to silence OACR.
234 hr = ::StringCchCopyNExW(wzSafeFileName, countof(wzSafeFileName), wfd.cFileName, countof(wfd.cFileName), NULL, NULL, STRSAFE_FILL_BEHIND_NULL | STRSAFE_NULL_ON_FAILURE);
235 DirExitOnFailure(hr, "Failed to ensure file name was null terminated.");
236
237 hr = PathConcat(wzPath, wzSafeFileName, &sczDelete);
238 DirExitOnFailure(hr, "Failed to concat filename '%ls' to directory: %ls", wzSafeFileName, wzPath);
239
240 if (fRecurse && wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
241 {
242 hr = PathBackslashTerminate(&sczDelete);
243 DirExitOnFailure(hr, "Failed to ensure path is backslash terminated: %ls", sczDelete);
244
245 hr = DirEnsureDeleteEx(sczDelete, dwFlags); // recursive call
246 if (FAILED(hr))
247 {
248 // if we failed to delete a subdirectory, keep trying to finish any remaining files
249 if (E_PATHNOTFOUND != hr)
250 {
251 ExitTraceSource(DUTIL_SOURCE_DIRUTIL, hr, "Failed to delete subdirectory; continuing: %ls", sczDelete);
252 }
253 hr = S_OK;
254 }
255 }
256 else if (fDeleteFiles) // this is a file, just delete it
257 {
258 if (wfd.dwFileAttributes & FILE_ATTRIBUTE_READONLY || wfd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN || wfd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)
259 {
260 if (!::SetFileAttributesW(sczDelete, FILE_ATTRIBUTE_NORMAL))
261 {
262 DirExitWithPathLastError(hr, "Failed to remove attributes from file: %ls", sczDelete);
263 continue;
264 }
265 }
266
267 if (!::DeleteFileW(sczDelete))
268 {
269 if (fScheduleDelete)
270 {
271 hr = PathGetTempFileName(sczTempDirectory, L"DEL", 0, &sczTempPath);
272 DirExitOnFailure(hr, "Failed to get temp file to move to.");
273
274 // Try to move the file to the temp directory then schedule for delete,
275 // otherwise just schedule for delete.
276 if (::MoveFileExW(sczDelete, sczTempPath, MOVEFILE_REPLACE_EXISTING))
277 {
278 ::MoveFileExW(sczTempPath, NULL, MOVEFILE_DELAY_UNTIL_REBOOT);
279 }
280 else
281 {
282 ::MoveFileExW(sczDelete, NULL, MOVEFILE_DELAY_UNTIL_REBOOT);
283 }
284 }
285 else
286 {
287 DirExitWithPathLastError(hr, "Failed to delete file: %ls", sczDelete);
288 }
289 }
290 }
291 } while (::FindNextFileW(hFind, &wfd));
292
293 er = ::GetLastError();
294 if (ERROR_NO_MORE_FILES == er)
295 {
296 hr = S_OK;
297 }
298 else
299 {
300 DirExitWithLastError(hr, "Failed while looping through files in directory: %ls", wzPath);
301 }
302 }
303
304 if (!::RemoveDirectoryW(wzPath))
305 {
306 hr = HRESULT_FROM_WIN32(::GetLastError());
307 if (HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION) == hr && fScheduleDelete)
308 {
309 if (::MoveFileExW(wzPath, NULL, MOVEFILE_DELAY_UNTIL_REBOOT))
310 {
311 hr = S_OK;
312 }
313 }
314
315 if (E_PATHNOTFOUND == hr || E_FILENOTFOUND == hr)
316 {
317 ExitFunction1(hr = E_PATHNOTFOUND);
318 }
319 else if (HRESULT_FROM_WIN32(ERROR_DIR_NOT_EMPTY) == hr && !fDeleteFiles && !fRecurse)
320 {
321 ExitFunction();
322 }
323
324 DirExitOnRootFailure(hr, "Failed to remove directory: %ls", wzPath);
325 }
326 }
327 else
328 {
329 DirExitWithRootFailure(hr, E_UNEXPECTED, "Directory delete cannot delete file: %ls", wzPath);
330 }
331
332 Assert(S_OK == hr);
333
334 LExit:
335 ReleaseFileFindHandle(hFind);
336 ReleaseStr(sczDelete);
337 ReleaseStr(sczTempDirectory);
338 ReleaseStr(sczTempPath);
339
340 return hr;
341 }
342
343
344 /*******************************************************************
345 DirDeleteEmptyDirectoriesToRoot - removes an empty directory and as many
346 of its parents as possible.
347
348 Returns: count of directories deleted.
349 *******************************************************************/
350 extern "C" DWORD DAPI DirDeleteEmptyDirectoriesToRoot(
351 __in_z LPCWSTR wzPath,
352 __in DWORD /*dwFlags*/
353 )
354 {
355 HRESULT hr = S_OK;
356 DWORD cDeletedDirs = 0;
357 LPWSTR sczPath = NULL;
358 LPCWSTR wzPastRoot = NULL;
359 SIZE_T cchRoot = 0;
360
361 // Make sure the path is normalized and prefixed.
362 hr = PathExpand(&sczPath, wzPath, PATH_EXPAND_FULLPATH);
363 DirExitOnFailure(hr, "Failed to get full path for: %ls", wzPath);
364
365 wzPastRoot = PathSkipPastRoot(sczPath, NULL, NULL, NULL);
366 DirExitOnNull(wzPastRoot, hr, E_INVALIDARG, "Full path was not rooted: %ls", sczPath);
367
368 cchRoot = wzPastRoot - sczPath;
369
370 while (sczPath && sczPath[cchRoot] && ::RemoveDirectoryW(sczPath))
371 {
372 ++cDeletedDirs;
373
374 hr = PathGetParentPath(sczPath, &sczPath, &cchRoot);
375 DirExitOnFailure(hr, "Failed to get parent directory for path: %ls", sczPath);
376 }
377
378 LExit:
379 ReleaseStr(sczPath);
380
381 return cDeletedDirs;
382 }
383
384
385 /*******************************************************************
386 DirGetCurrent - gets the current directory.
387
388 *******************************************************************/
389 extern "C" HRESULT DAPI DirGetCurrent(
390 __deref_out_z LPWSTR* psczCurrentDirectory,
391 __out_opt SIZE_T* pcch
392 )
393 {
394 Assert(psczCurrentDirectory);
395
396 HRESULT hr = S_OK;
397 SIZE_T cchMax = 0;
398 DWORD cch = 0;
399 DWORD cchBuffer = 0;
400 DWORD dwAttempts = 0;
401 const DWORD dwMaxAttempts = 10;
402
403 if (*psczCurrentDirectory)
404 {
405 hr = StrMaxLength(*psczCurrentDirectory, &cchMax);
406 DirExitOnFailure(hr, "Failed to get max length of input buffer.");
407
408 cchBuffer = (DWORD)min(DWORD_MAX, cchMax);
409 }
410 else
411 {
412 cchBuffer = MAX_PATH + 1;
413
414 hr = StrAlloc(psczCurrentDirectory, cchBuffer);
415 DirExitOnFailure(hr, "Failed to allocate space for current directory.");
416 }
417
418 for (; dwAttempts < dwMaxAttempts; ++dwAttempts)
419 {
420 cch = ::GetCurrentDirectoryW(cchBuffer, *psczCurrentDirectory);
421 DirExitOnNullWithLastError(cch, hr, "Failed to get current directory.");
422
423 if (cch < cchBuffer)
424 {
425 break;
426 }
427
428 hr = StrAlloc(psczCurrentDirectory, cch);
429 DirExitOnFailure(hr, "Failed to reallocate space for current directory.");
430
431 cchBuffer = cch;
432 }
433
434 if (dwMaxAttempts == dwAttempts)
435 {
436 DirExitWithRootFailure(hr, E_INSUFFICIENT_BUFFER, "GetCurrentDirectoryW results never converged.");
437 }
438
439 if (pcch)
440 {
441 *pcch = cch;
442 }
443
444 LExit:
445 return hr;
446 }
447
448
449 /*******************************************************************
450 DirSetCurrent - sets the current directory.
451
452 *******************************************************************/
453 extern "C" HRESULT DAPI DirSetCurrent(
454 __in_z LPCWSTR wzDirectory
455 )
456 {
457 HRESULT hr = S_OK;
458
459 if (!::SetCurrentDirectoryW(wzDirectory))
460 {
461 DirExitWithLastError(hr, "Failed to set current directory to: %ls", wzDirectory);
462 }
463
464 LExit:
465 return hr;
466 }