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::__anon3048bf650811::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::__anon3048bf650911::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 
661 } //  namespace FileUtils
662