1 /*
2 * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 #include <memory>
27 #include <algorithm>
28 #include <shlwapi.h>
29 #include <stdlib.h>
30 #include <direct.h>
31
32 #include "FileUtils.h"
33 #include "WinFileUtils.h"
34 #include "WinErrorHandling.h"
35 #include "Log.h"
36
37
38 // Needed by FileUtils::isDirectoryNotEmpty
39 #pragma comment(lib, "shlwapi")
40
41
42 namespace FileUtils {
43
44 namespace {
45
reservedFilenameChars()46 tstring reservedFilenameChars() {
47 tstring buf;
48 for (char charCode = 0; charCode < 32; ++charCode) {
49 buf.append(1, charCode);
50 }
51 buf += _T("<>:\"|?*/\\");
52 return buf;
53 }
54
55 } // namespace
56
isFileExists(const tstring & filePath)57 bool isFileExists(const tstring &filePath) {
58 return GetFileAttributes(filePath.c_str()) != INVALID_FILE_ATTRIBUTES;
59 }
60
61 namespace {
isDirectoryAttrs(const DWORD attrs)62 bool isDirectoryAttrs(const DWORD attrs) {
63 return attrs != INVALID_FILE_ATTRIBUTES
64 && (attrs & FILE_ATTRIBUTE_DIRECTORY) != 0;
65 }
66 } // namespace
67
isDirectory(const tstring & filePath)68 bool isDirectory(const tstring &filePath) {
69 return isDirectoryAttrs(GetFileAttributes(filePath.c_str()));
70 }
71
isDirectoryNotEmpty(const tstring & dirPath)72 bool isDirectoryNotEmpty(const tstring &dirPath) {
73 if (!isDirectory(dirPath)) {
74 return false;
75 }
76 return FALSE == PathIsDirectoryEmpty(dirPath.c_str());
77 }
78
79
toAbsolutePath(const tstring & path)80 tstring toAbsolutePath(const tstring& path) {
81 if (path.empty()) {
82 TCHAR* buf = _tgetcwd(0, 1);
83 if (buf) {
84 const tstring result(buf);
85 free(buf);
86 if (result.empty()) {
87 JP_THROW(tstrings::any() << "_tgetcwd() returned empty string");
88 }
89 return result;
90 }
91
92 JP_THROW(tstrings::any() << "_tgetcwd() failed");
93 }
94
95 TCHAR* buf = _tfullpath(0, path.c_str(), size_t(1));
96 if (buf) {
97 const tstring result(buf);
98 free(buf);
99 return result;
100 }
101
102 JP_THROW(tstrings::any() << "_tfullpath(" << path << ") failed");
103 }
104
105
106 namespace {
107
createNewFile(const tstring & path)108 bool createNewFile(const tstring& path) {
109 HANDLE h = CreateFile(path.c_str(), GENERIC_WRITE, 0, NULL, CREATE_NEW,
110 FILE_ATTRIBUTE_NORMAL, NULL);
111 // if the file exists => h == INVALID_HANDLE_VALUE & GetLastError
112 // returns ERROR_FILE_EXISTS
113 if (h != INVALID_HANDLE_VALUE) {
114 CloseHandle(h);
115 LOG_TRACE(tstrings::any() << "Created [" << path << "] file");
116 return true;
117 }
118 return false;
119 }
120
121 } // namespace
122
createTempFile(const tstring & prefix,const tstring & suffix,const tstring & path)123 tstring createTempFile(const tstring &prefix, const tstring &suffix,
124 const tstring &path) {
125 const tstring invalidChars = reservedFilenameChars();
126
127 if (prefix.find_first_of(invalidChars) != tstring::npos) {
128 JP_THROW(tstrings::any() << "Illegal characters in prefix=" << prefix);
129 }
130
131 if (suffix.find_first_of(invalidChars) != tstring::npos) {
132 JP_THROW(tstrings::any() << "Illegal characters in suffix=" << suffix);
133 }
134
135 int rnd = (int)GetTickCount();
136
137 // do no more than 100 attempts
138 for (int i=0; i<100; i++) {
139 const tstring filePath = mkpath() << path << (prefix
140 + (tstrings::any() << (rnd + i)).tstr() + suffix);
141 if (createNewFile(filePath)) {
142 return filePath;
143 }
144 }
145
146 // 100 attempts failed
147 JP_THROW(tstrings::any() << "createTempFile(" << prefix << ", "
148 << suffix << ", "
149 << path << ") failed");
150 }
151
createTempDirectory(const tstring & prefix,const tstring & suffix,const tstring & basedir)152 tstring createTempDirectory(const tstring &prefix, const tstring &suffix,
153 const tstring &basedir) {
154 const tstring filePath = createTempFile(prefix, suffix, basedir);
155 // delete the file and create directory with the same name
156 deleteFile(filePath);
157 createDirectory(filePath);
158 return filePath;
159 }
160
createUniqueFile(const tstring & prototype)161 tstring createUniqueFile(const tstring &prototype) {
162 if (createNewFile(prototype)) {
163 return prototype;
164 }
165
166 return createTempFile(replaceSuffix(basename(prototype)),
167 suffix(prototype), dirname(prototype));
168 }
169
170 namespace {
171
createDir(const tstring path,LPSECURITY_ATTRIBUTES saAttr,tstring_array * createdDirs=0)172 void createDir(const tstring path, LPSECURITY_ATTRIBUTES saAttr,
173 tstring_array* createdDirs=0) {
174 if (CreateDirectory(path.c_str(), saAttr)) {
175 LOG_TRACE(tstrings::any() << "Created [" << path << "] directory");
176 if (createdDirs) {
177 createdDirs->push_back(removeTrailingSlash(path));
178 }
179 } else {
180 const DWORD createDirectoryErr = GetLastError();
181 // if saAttr is specified, fail even if the directory exists
182 if (saAttr != NULL || !isDirectory(path)) {
183 JP_THROW(SysError(tstrings::any() << "CreateDirectory("
184 << path << ") failed", CreateDirectory, createDirectoryErr));
185 }
186 }
187 }
188
189 }
190
createDirectory(const tstring & path,tstring_array * createdDirs)191 void createDirectory(const tstring &path, tstring_array* createdDirs) {
192 const tstring dirPath = removeTrailingSlash(path) + _T("\\");
193
194 tstring::size_type pos = dirPath.find_first_of(_T("\\/"));
195 while (pos != tstring::npos) {
196 const tstring subdirPath = dirPath.substr(0, pos + 1);
197 createDir(subdirPath, NULL, createdDirs);
198 pos = dirPath.find_first_of(_T("\\/"), pos + 1);
199 }
200 }
201
202
copyFile(const tstring & fromPath,const tstring & toPath,bool failIfExists)203 void copyFile(const tstring& fromPath, const tstring& toPath,
204 bool failIfExists) {
205 createDirectory(dirname(toPath));
206 if (!CopyFile(fromPath.c_str(), toPath.c_str(),
207 (failIfExists ? TRUE : FALSE))) {
208 JP_THROW(SysError(tstrings::any()
209 << "CopyFile(" << fromPath << ", " << toPath << ", "
210 << failIfExists << ") failed", CopyFile));
211 }
212 LOG_TRACE(tstrings::any() << "Copied [" << fromPath << "] file to ["
213 << toPath << "]");
214 }
215
216
217 namespace {
218
moveFileImpl(const tstring & fromPath,const tstring & toPath,DWORD flags)219 void moveFileImpl(const tstring& fromPath, const tstring& toPath,
220 DWORD flags) {
221 const bool isDir = isDirectory(fromPath);
222 if (!MoveFileEx(fromPath.c_str(), toPath.empty() ? NULL : toPath.c_str(),
223 flags)) {
224 JP_THROW(SysError(tstrings::any() << "MoveFileEx(" << fromPath
225 << ", " << toPath << ", " << flags << ") failed", MoveFileEx));
226 }
227
228 const bool onReboot = 0 != (flags & MOVEFILE_DELAY_UNTIL_REBOOT);
229
230 const LPCTSTR label = isDir ? _T("folder") : _T("file");
231
232 tstrings::any msg;
233 if (!toPath.empty()) {
234 if (onReboot) {
235 msg << "Move";
236 } else {
237 msg << "Moved";
238 }
239 msg << " '" << fromPath << "' " << label << " to '" << toPath << "'";
240 } else {
241 if (onReboot) {
242 msg << "Delete";
243 } else {
244 msg << "Deleted";
245 }
246 msg << " '" << fromPath << "' " << label;
247 }
248 if (onReboot) {
249 msg << " on reboot";
250 }
251 LOG_TRACE(msg);
252 }
253
254 } // namespace
255
256
moveFile(const tstring & fromPath,const tstring & toPath,bool failIfExists)257 void moveFile(const tstring& fromPath, const tstring& toPath,
258 bool failIfExists) {
259 createDirectory(dirname(toPath));
260
261 DWORD flags = MOVEFILE_COPY_ALLOWED;
262 if (!failIfExists) {
263 flags |= MOVEFILE_REPLACE_EXISTING;
264 }
265
266 moveFileImpl(fromPath, toPath, flags);
267 }
268
deleteFile(const tstring & path)269 void deleteFile(const tstring &path)
270 {
271 if (!deleteFile(path, std::nothrow)) {
272 JP_THROW(SysError(tstrings::any()
273 << "DeleteFile(" << path << ") failed", DeleteFile));
274 }
275 }
276
277 namespace {
278
notFound(const DWORD status=GetLastError ())279 bool notFound(const DWORD status=GetLastError()) {
280 return status == ERROR_FILE_NOT_FOUND || status == ERROR_PATH_NOT_FOUND;
281 }
282
deleteFileImpl(const std::nothrow_t &,const tstring & path)283 bool deleteFileImpl(const std::nothrow_t &, const tstring &path) {
284 const bool deleted = (DeleteFile(path.c_str()) != 0);
285 if (deleted) {
286 LOG_TRACE(tstrings::any() << "Deleted [" << path << "] file");
287 return true;
288 }
289 return notFound();
290 }
291
292 } // namespace
293
deleteFile(const tstring & path,const std::nothrow_t &)294 bool deleteFile(const tstring &path, const std::nothrow_t &) throw()
295 {
296 bool deleted = deleteFileImpl(std::nothrow, path);
297 const DWORD status = GetLastError();
298 if (!deleted && status == ERROR_ACCESS_DENIED) {
299 DWORD attrs = GetFileAttributes(path.c_str());
300 SetLastError(status);
301 if (attrs == INVALID_FILE_ATTRIBUTES) {
302 return false;
303 }
304 if (attrs & FILE_ATTRIBUTE_READONLY) {
305 // DeleteFile() failed because file is R/O.
306 // Remove R/O attribute and retry DeleteFile().
307 attrs &= ~FILE_ATTRIBUTE_READONLY;
308 if (SetFileAttributes(path.c_str(), attrs)) {
309 LOG_TRACE(tstrings::any() << "Discarded R/O attribute from ["
310 << path << "] file");
311 deleted = deleteFileImpl(std::nothrow, path);
312 } else {
313 LOG_WARNING(SysError(tstrings::any()
314 << "Failed to discard R/O attribute from ["
315 << path << "] file. File will not be deleted",
316 SetFileAttributes).what());
317 SetLastError(status);
318 }
319 }
320 }
321
322 return deleted || notFound();
323 }
324
deleteDirectory(const tstring & path)325 void deleteDirectory(const tstring &path)
326 {
327 if (!deleteDirectory(path, std::nothrow)) {
328 JP_THROW(SysError(tstrings::any()
329 << "RemoveDirectory(" << path << ") failed", RemoveDirectory));
330 }
331 }
332
deleteDirectory(const tstring & path,const std::nothrow_t &)333 bool deleteDirectory(const tstring &path, const std::nothrow_t &) throw()
334 {
335 const bool deleted = (RemoveDirectory(path.c_str()) != 0);
336 if (deleted) {
337 LOG_TRACE(tstrings::any() << "Deleted [" << path << "] directory");
338 }
339 return deleted || notFound();
340 }
341
342 namespace {
343
344 class DeleteFilesCallback: public DirectoryCallback {
345 public:
DeleteFilesCallback(bool ff)346 explicit DeleteFilesCallback(bool ff): failfast(ff), failed(false) {
347 }
348
onFile(const tstring & path)349 virtual bool onFile(const tstring& path) {
350 if (failfast) {
351 deleteFile(path);
352 } else {
353 updateStatus(deleteFile(path, std::nothrow));
354 }
355 return true;
356 }
357
good() const358 bool good() const {
359 return !failed;
360 }
361
362 protected:
updateStatus(bool success)363 void updateStatus(bool success) {
364 if (!success) {
365 failed = true;
366 }
367 }
368
369 const bool failfast;
370 private:
371 bool failed;
372 };
373
374 class DeleteAllCallback: public DeleteFilesCallback {
375 public:
DeleteAllCallback(bool failfast)376 explicit DeleteAllCallback(bool failfast): DeleteFilesCallback(failfast) {
377 }
378
onDirectory(const tstring & path)379 virtual bool onDirectory(const tstring& path) {
380 if (failfast) {
381 deleteDirectoryRecursive(path);
382 } else {
383 updateStatus(deleteDirectoryRecursive(path, std::nothrow));
384 }
385 return true;
386 }
387 };
388
389
390 class BatchDeleter {
391 const tstring dirPath;
392 bool recursive;
393 public:
BatchDeleter(const tstring & path)394 explicit BatchDeleter(const tstring& path): dirPath(path) {
395 deleteSubdirs(false);
396 }
397
deleteSubdirs(bool v)398 BatchDeleter& deleteSubdirs(bool v) {
399 recursive = v;
400 return *this;
401 }
402
execute() const403 void execute() const {
404 if (!isFileExists(dirPath)) {
405 return;
406 }
407 iterateDirectory(true /* fail fast */);
408 if (recursive) {
409 deleteDirectory(dirPath);
410 }
411 }
412
execute(const std::nothrow_t &) const413 bool execute(const std::nothrow_t&) const {
414 if (!isFileExists(dirPath)) {
415 return true;
416 }
417
418 if (!isDirectory(dirPath)) {
419 return false;
420 }
421
422 JP_TRY;
423 if (!iterateDirectory(false /* ignore errors */)) {
424 return false;
425 }
426 if (recursive) {
427 return deleteDirectory(dirPath, std::nothrow);
428 }
429 return true;
430 JP_CATCH_ALL;
431
432 return false;
433 }
434
435 private:
iterateDirectory(bool failfast) const436 bool iterateDirectory(bool failfast) const {
437 std::unique_ptr<DeleteFilesCallback> callback;
438 if (recursive) {
439 callback = std::unique_ptr<DeleteFilesCallback>(
440 new DeleteAllCallback(failfast));
441 } else {
442 callback = std::unique_ptr<DeleteFilesCallback>(
443 new DeleteFilesCallback(failfast));
444 }
445
446 FileUtils::iterateDirectory(dirPath, *callback);
447 return callback->good();
448 }
449 };
450
451 } // namespace
452
deleteFilesInDirectory(const tstring & dirPath)453 void deleteFilesInDirectory(const tstring &dirPath) {
454 BatchDeleter(dirPath).execute();
455 }
456
deleteFilesInDirectory(const tstring & dirPath,const std::nothrow_t &)457 bool deleteFilesInDirectory(const tstring &dirPath,
458 const std::nothrow_t &) throw() {
459 return BatchDeleter(dirPath).execute(std::nothrow);
460 }
461
deleteDirectoryRecursive(const tstring & dirPath)462 void deleteDirectoryRecursive(const tstring &dirPath) {
463 BatchDeleter(dirPath).deleteSubdirs(true).execute();
464 }
465
deleteDirectoryRecursive(const tstring & dirPath,const std::nothrow_t &)466 bool deleteDirectoryRecursive(const tstring &dirPath,
467 const std::nothrow_t &) throw() {
468 return BatchDeleter(dirPath).deleteSubdirs(true).execute(std::nothrow);
469 }
470
471 namespace {
472
473 struct FindFileDeleter {
474 typedef HANDLE pointer;
475
operator ()FileUtils::__anon56e8ce630811::FindFileDeleter476 void operator()(HANDLE h) {
477 if (h && h != INVALID_HANDLE_VALUE) {
478 FindClose(h);
479 }
480 }
481 };
482
483 typedef std::unique_ptr<HANDLE, FindFileDeleter> UniqueFindFileHandle;
484
485 }; // namesace
iterateDirectory(const tstring & dirPath,DirectoryCallback & callback)486 void iterateDirectory(const tstring &dirPath, DirectoryCallback& callback)
487 {
488 const tstring searchString = combinePath(dirPath, _T("*"));
489 WIN32_FIND_DATA findData;
490 UniqueFindFileHandle h(FindFirstFile(searchString.c_str(), &findData));
491 if (h.get() == INVALID_HANDLE_VALUE) {
492 // GetLastError() == ERROR_FILE_NOT_FOUND is OK
493 // - no files in the directory
494 // ERROR_PATH_NOT_FOUND is returned
495 // if the parent directory does not exist
496 if (GetLastError() != ERROR_FILE_NOT_FOUND) {
497 JP_THROW(SysError(tstrings::any() << "FindFirstFile("
498 << dirPath << ") failed", FindFirstFile));
499 }
500 return;
501 }
502
503 do {
504 const tstring fname(findData.cFileName);
505 const tstring filePath = combinePath(dirPath, fname);
506 if (!isDirectoryAttrs(findData.dwFileAttributes)) {
507 if (!callback.onFile(filePath)) {
508 return;
509 }
510 } else if (fname != _T(".") && fname != _T("..")) {
511 if (!callback.onDirectory(filePath)) {
512 return;
513 }
514 }
515 } while (FindNextFile(h.get(), &findData));
516
517 // expect GetLastError() == ERROR_NO_MORE_FILES
518 if (GetLastError() != ERROR_NO_MORE_FILES) {
519 JP_THROW(SysError(tstrings::any() << "FindNextFile("
520 << dirPath << ") failed", FindNextFile));
521 }
522 }
523
524
findItems(tstring_array & v)525 DirectoryIterator& DirectoryIterator::findItems(tstring_array& v) {
526 if (!isDirectory(root)) {
527 return *this;
528 }
529
530 iterateDirectory(root, *this);
531 v.insert(v.end(), items.begin(), items.end());
532 items = tstring_array();
533 return *this;
534 }
535
onFile(const tstring & path)536 bool DirectoryIterator::onFile(const tstring& path) {
537 if (theWithFiles) {
538 items.push_back(path);
539 }
540 return true;
541 }
542
onDirectory(const tstring & path)543 bool DirectoryIterator::onDirectory(const tstring& path) {
544 if (theWithFolders) {
545 items.push_back(path);
546 }
547 if (theRecurse) {
548 DirectoryIterator(path).recurse(theRecurse)
549 .withFiles(theWithFiles)
550 .withFolders(theWithFolders)
551 .findItems(items);
552 }
553 return true;
554 }
555
556
557 namespace {
558
559 struct DeleterFunctor {
560 // Order of items in the following enum is important!
561 // It controls order in which items of particular type will be deleted.
562 // See Deleter::execute().
563 enum {
564 File,
565 FilesInDirectory,
566 RecursiveDirectory,
567 EmptyDirectory
568 };
569
operator ()FileUtils::__anon56e8ce630911::DeleterFunctor570 void operator () (const Deleter::Path& path) const {
571 switch (path.second) {
572 #define DELETE_SOME(o, f)\
573 case o:\
574 f(path.first, std::nothrow);\
575 break
576
577 DELETE_SOME(File, deleteFile);
578 DELETE_SOME(EmptyDirectory, deleteDirectory);
579 DELETE_SOME(FilesInDirectory, deleteFilesInDirectory);
580 DELETE_SOME(RecursiveDirectory, deleteDirectoryRecursive);
581
582 #undef DELETE_SOME
583 default:
584 break;
585 }
586 }
587 };
588
589 } // namespace
590
execute()591 void Deleter::execute() {
592 Paths tmp;
593 tmp.swap(paths);
594
595 // Reorder items to delete.
596 std::stable_sort(tmp.begin(), tmp.end(), [] (const Paths::value_type& a,
597 const Paths::value_type& b) {
598 return a.second < b.second;
599 });
600
601 std::for_each(tmp.begin(), tmp.end(), DeleterFunctor());
602 }
603
appendFile(const tstring & path)604 Deleter& Deleter::appendFile(const tstring& path) {
605 paths.push_back(std::make_pair(path, DeleterFunctor::File));
606 return *this;
607 }
608
appendEmptyDirectory(const Directory & dir)609 Deleter& Deleter::appendEmptyDirectory(const Directory& dir) {
610 tstring path = normalizePath(removeTrailingSlash(dir));
611 const tstring parent = normalizePath(removeTrailingSlash(dir.parent));
612 while(parent != path) {
613 appendEmptyDirectory(path);
614 path = dirname(path);
615 }
616
617 return *this;
618 }
619
appendEmptyDirectory(const tstring & path)620 Deleter& Deleter::appendEmptyDirectory(const tstring& path) {
621 paths.push_back(std::make_pair(path, DeleterFunctor::EmptyDirectory));
622 return *this;
623 }
624
appendAllFilesInDirectory(const tstring & path)625 Deleter& Deleter::appendAllFilesInDirectory(const tstring& path) {
626 paths.push_back(std::make_pair(path, DeleterFunctor::FilesInDirectory));
627 return *this;
628 }
629
appendRecursiveDirectory(const tstring & path)630 Deleter& Deleter::appendRecursiveDirectory(const tstring& path) {
631 paths.push_back(std::make_pair(path, DeleterFunctor::RecursiveDirectory));
632 return *this;
633 }
634
635
FileWriter(const tstring & path)636 FileWriter::FileWriter(const tstring& path): dstPath(path) {
637 tmpFile = FileUtils::createTempFile(_T("jds"), _T(".tmp"),
638 FileUtils::dirname(path));
639
640 cleaner.appendFile(tmpFile);
641
642 // we want to get exception on error
643 tmp.exceptions(std::ifstream::failbit | std::ifstream::badbit);
644 tmp.open(tmpFile, std::ios::binary | std::ios::trunc);
645 }
646
write(const void * buf,size_t bytes)647 FileWriter& FileWriter::write(const void* buf, size_t bytes) {
648 tmp.write(static_cast<const char*>(buf), bytes);
649 return *this;
650 }
651
finalize()652 void FileWriter::finalize() {
653 tmp.close();
654
655 FileUtils::moveFile(tmpFile, dstPath, false);
656
657 // cancel file deletion
658 cleaner.cancel();
659 }
660
stripExeSuffix(const tstring & path)661 tstring stripExeSuffix(const tstring& path) {
662 // for windows - there is a ".exe" suffix to remove
663 // allow for ".*" (last dot beyond the last slash)
664 const tstring::size_type pos = path.rfind(_T("."));
665 const tstring::size_type spos = path.rfind(_T("\\/"));
666 if (pos == tstring::npos || (spos > pos && spos != tstring::npos)) {
667 return path;
668 }
669 return path.substr(0, pos);
670 }
671
672 } // namespace FileUtils
673