--- /dev/null 2019-11-20 11:06:00.000000000 -0500 +++ new/src/jdk.incubator.jpackage/windows/native/libjpackage/FileUtils.cpp 2019-11-20 11:05:58.124876100 -0500 @@ -0,0 +1,709 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include +#include +#include + +#include "FileUtils.h" +#include "WinErrorHandling.h" +#include "Log.h" + + +// Needed by FileUtils::isDirectoryNotEmpty +#pragma comment(lib, "shlwapi") + + +namespace FileUtils { + +namespace { + + +tstring reservedFilenameChars() { + tstring buf; + for (char charCode = 0; charCode < 32; ++charCode) { + buf.append(1, charCode); + } + buf += _T("<>:\"|?*/\\"); + return buf; +} + +} // namespace + +bool isDirSeparator(const tstring::value_type c) { + return (c == '/' || c == '\\'); +} + +bool isFileExists(const tstring &filePath) { + return GetFileAttributes(filePath.c_str()) != INVALID_FILE_ATTRIBUTES; +} + +namespace { +bool isDirectoryAttrs(const DWORD attrs) { + return attrs != INVALID_FILE_ATTRIBUTES + && (attrs & FILE_ATTRIBUTE_DIRECTORY) != 0; +} +} // namespace + +bool isDirectory(const tstring &filePath) { + return isDirectoryAttrs(GetFileAttributes(filePath.c_str())); +} + +bool isDirectoryNotEmpty(const tstring &dirPath) { + if (!isDirectory(dirPath)) { + return false; + } + return FALSE == PathIsDirectoryEmpty(dirPath.c_str()); +} + +tstring dirname(const tstring &path) { + tstring::size_type pos = path.find_last_of(_T("\\/")); + if (pos != tstring::npos) { + pos = path.find_last_not_of(_T("\\/"), pos); // skip trailing slashes + } + return pos == tstring::npos ? tstring() : path.substr(0, pos + 1); +} + +tstring basename(const tstring &path) { + const tstring::size_type pos = path.find_last_of(_T("\\/")); + if (pos == tstring::npos) { + return path; + } + return path.substr(pos + 1); +} + +tstring suffix(const tstring &path) { + const tstring::size_type pos = path.rfind('.'); + if (pos == tstring::npos) { + return tstring(); + } + const tstring::size_type dirSepPos = path.find_first_of(_T("\\/"), + pos + 1); + if (dirSepPos != tstring::npos) { + return tstring(); + } + // test for '/..' and '..' cases + if (pos != 0 && path[pos - 1] == '.' + && (pos == 1 || isDirSeparator(path[pos - 2]))) { + return tstring(); + } + return path.substr(pos); +} + +tstring combinePath(const tstring& parent, const tstring& child) { + if (parent.empty()) { + return child; + } + if (child.empty()) { + return parent; + } + + tstring parentWOSlash = removeTrailingSlash(parent); + // also handle the case when child contains starting slash + bool childHasSlash = isDirSeparator(child.front()); + tstring childWOSlash = childHasSlash ? child.substr(1) : child; + + return parentWOSlash + _T("\\") + childWOSlash; +} + +tstring removeTrailingSlash(const tstring& path) { + if (path.empty()) { + return path; + } + tstring::const_reverse_iterator it = path.rbegin(); + tstring::const_reverse_iterator end = path.rend(); + + while (it != end && isDirSeparator(*it)) { + ++it; + } + return path.substr(0, end - it); +} + +tstring normalizePath(tstring v) { + std::replace(v.begin(), v.end(), '/', '\\'); + return tstrings::toLower(v); +} + +namespace { + +bool createNewFile(const tstring& path) { + HANDLE h = CreateFile(path.c_str(), GENERIC_WRITE, 0, NULL, CREATE_NEW, + FILE_ATTRIBUTE_NORMAL, NULL); + // if the file exists => h == INVALID_HANDLE_VALUE & GetLastError + // returns ERROR_FILE_EXISTS + if (h != INVALID_HANDLE_VALUE) { + CloseHandle(h); + LOG_TRACE(tstrings::any() << "Created [" << path << "] file"); + return true; + } + return false; +} + +} // namespace + +tstring createTempFile(const tstring &prefix, const tstring &suffix, + const tstring &path) { + const tstring invalidChars = reservedFilenameChars(); + + if (prefix.find_first_of(invalidChars) != tstring::npos) { + JP_THROW(tstrings::any() << "Illegal characters in prefix=" << prefix); + } + + if (suffix.find_first_of(invalidChars) != tstring::npos) { + JP_THROW(tstrings::any() << "Illegal characters in suffix=" << suffix); + } + + int rnd = (int)GetTickCount(); + + // do no more than 100 attempts + for (int i=0; i<100; i++) { + const tstring filePath = mkpath() << path << (prefix + + (tstrings::any() << (rnd + i)).tstr() + suffix); + if (createNewFile(filePath)) { + return filePath; + } + } + + // 100 attempts failed + JP_THROW(tstrings::any() << "createTempFile(" << prefix << ", " + << suffix << ", " + << path << ") failed"); +} + +tstring createTempDirectory(const tstring &prefix, const tstring &suffix, + const tstring &basedir) { + const tstring filePath = createTempFile(prefix, suffix, basedir); + // delete the file and create directory with the same name + deleteFile(filePath); + createDirectory(filePath); + return filePath; +} + +tstring createUniqueFile(const tstring &prototype) { + if (createNewFile(prototype)) { + return prototype; + } + + return createTempFile(replaceSuffix(basename(prototype)), + suffix(prototype), dirname(prototype)); +} + +namespace { + +void createDir(const tstring path, LPSECURITY_ATTRIBUTES saAttr, + tstring_array* createdDirs=0) { + if (CreateDirectory(path.c_str(), saAttr)) { + LOG_TRACE(tstrings::any() << "Created [" << path << "] directory"); + if (createdDirs) { + createdDirs->push_back(removeTrailingSlash(path)); + } + } else { + const DWORD createDirectoryErr = GetLastError(); + // if saAttr is specified, fail even if the directory exists + if (saAttr != NULL || !isDirectory(path)) { + JP_THROW(SysError(tstrings::any() << "CreateDirectory(" + << path << ") failed", CreateDirectory, createDirectoryErr)); + } + } +} + +} + +void createDirectory(const tstring &path, tstring_array* createdDirs) { + const tstring dirPath = removeTrailingSlash(path) + _T("\\"); + + tstring::size_type pos = dirPath.find_first_of(_T("\\/")); + while (pos != tstring::npos) { + const tstring subdirPath = dirPath.substr(0, pos + 1); + createDir(subdirPath, NULL, createdDirs); + pos = dirPath.find_first_of(_T("\\/"), pos + 1); + } +} + + +void copyFile(const tstring& fromPath, const tstring& toPath, + bool failIfExists) { + createDirectory(dirname(toPath)); + if (!CopyFile(fromPath.c_str(), toPath.c_str(), + (failIfExists ? TRUE : FALSE))) { + JP_THROW(SysError(tstrings::any() + << "CopyFile(" << fromPath << ", " << toPath << ", " + << failIfExists << ") failed", CopyFile)); + } + LOG_TRACE(tstrings::any() << "Copied [" << fromPath << "] file to [" + << toPath << "]"); +} + + +namespace { + +void moveFileImpl(const tstring& fromPath, const tstring& toPath, + DWORD flags) { + const bool isDir = isDirectory(fromPath); + if (!MoveFileEx(fromPath.c_str(), toPath.empty() ? NULL : toPath.c_str(), + flags)) { + JP_THROW(SysError(tstrings::any() << "MoveFileEx(" << fromPath + << ", " << toPath << ", " << flags << ") failed", MoveFileEx)); + } + + const bool onReboot = 0 != (flags & MOVEFILE_DELAY_UNTIL_REBOOT); + + const LPCTSTR label = isDir ? _T("folder") : _T("file"); + + tstrings::any msg; + if (!toPath.empty()) { + if (onReboot) { + msg << "Move"; + } else { + msg << "Moved"; + } + msg << " '" << fromPath << "' " << label << " to '" << toPath << "'"; + } else { + if (onReboot) { + msg << "Delete"; + } else { + msg << "Deleted"; + } + msg << " '" << fromPath << "' " << label; + } + if (onReboot) { + msg << " on reboot"; + } + LOG_TRACE(msg); +} + +} // namespace + + +void moveFile(const tstring& fromPath, const tstring& toPath, + bool failIfExists) { + createDirectory(dirname(toPath)); + + DWORD flags = MOVEFILE_COPY_ALLOWED; + if (!failIfExists) { + flags |= MOVEFILE_REPLACE_EXISTING; + } + + moveFileImpl(fromPath, toPath, flags); +} + +void deleteFile(const tstring &path) +{ + if (!deleteFile(path, std::nothrow)) { + JP_THROW(SysError(tstrings::any() + << "DeleteFile(" << path << ") failed", DeleteFile)); + } +} + +namespace { + +bool notFound(const DWORD status=GetLastError()) { + return status == ERROR_FILE_NOT_FOUND || status == ERROR_PATH_NOT_FOUND; +} + +bool deleteFileImpl(const std::nothrow_t &, const tstring &path) { + const bool deleted = (DeleteFile(path.c_str()) != 0); + if (deleted) { + LOG_TRACE(tstrings::any() << "Deleted [" << path << "] file"); + return true; + } + return notFound(); +} + +} // namespace + +bool deleteFile(const tstring &path, const std::nothrow_t &) throw() +{ + bool deleted = deleteFileImpl(std::nothrow, path); + const DWORD status = GetLastError(); + if (!deleted && status == ERROR_ACCESS_DENIED) { + DWORD attrs = GetFileAttributes(path.c_str()); + SetLastError(status); + if (attrs == INVALID_FILE_ATTRIBUTES) { + return false; + } + if (attrs & FILE_ATTRIBUTE_READONLY) { + // DeleteFile() failed because file is R/O. + // Remove R/O attribute and retry DeleteFile(). + attrs &= ~FILE_ATTRIBUTE_READONLY; + if (SetFileAttributes(path.c_str(), attrs)) { + LOG_TRACE(tstrings::any() << "Discarded R/O attribute from [" + << path << "] file"); + deleted = deleteFileImpl(std::nothrow, path); + } else { + LOG_WARNING(SysError(tstrings::any() + << "Failed to discard R/O attribute from [" + << path << "] file. File will not be deleted", + SetFileAttributes).what()); + SetLastError(status); + } + } + } + + return deleted || notFound(); +} + +void deleteDirectory(const tstring &path) +{ + if (!deleteDirectory(path, std::nothrow)) { + JP_THROW(SysError(tstrings::any() + << "RemoveDirectory(" << path << ") failed", RemoveDirectory)); + } +} + +bool deleteDirectory(const tstring &path, const std::nothrow_t &) throw() +{ + const bool deleted = (RemoveDirectory(path.c_str()) != 0); + if (deleted) { + LOG_TRACE(tstrings::any() << "Deleted [" << path << "] directory"); + } + return deleted || notFound(); +} + +namespace { + +class DeleteFilesCallback: public DirectoryCallback { +public: + explicit DeleteFilesCallback(bool ff): failfast(ff), failed(false) { + } + + virtual bool onFile(const tstring& path) { + if (failfast) { + deleteFile(path); + } else { + updateStatus(deleteFile(path, std::nothrow)); + } + return true; + } + + bool good() const { + return !failed; + } + +protected: + void updateStatus(bool success) { + if (!success) { + failed = true; + } + } + + const bool failfast; +private: + bool failed; +}; + +class DeleteAllCallback: public DeleteFilesCallback { +public: + explicit DeleteAllCallback(bool failfast): DeleteFilesCallback(failfast) { + } + + virtual bool onDirectory(const tstring& path) { + if (failfast) { + deleteDirectoryRecursive(path); + } else { + updateStatus(deleteDirectoryRecursive(path, std::nothrow)); + } + return true; + } +}; + + +class BatchDeleter { + const tstring dirPath; + bool recursive; +public: + explicit BatchDeleter(const tstring& path): dirPath(path) { + deleteSubdirs(false); + } + + BatchDeleter& deleteSubdirs(bool v) { + recursive = v; + return *this; + } + + void execute() const { + if (!isFileExists(dirPath)) { + return; + } + iterateDirectory(true /* fail fast */); + if (recursive) { + deleteDirectory(dirPath); + } + } + + bool execute(const std::nothrow_t&) const { + if (!isFileExists(dirPath)) { + return true; + } + + if (!isDirectory(dirPath)) { + return false; + } + + JP_TRY; + if (!iterateDirectory(false /* ignore errors */)) { + return false; + } + if (recursive) { + return deleteDirectory(dirPath, std::nothrow); + } + return true; + JP_CATCH_ALL; + + return false; + } + +private: + bool iterateDirectory(bool failfast) const { + std::unique_ptr callback; + if (recursive) { + callback = std::unique_ptr( + new DeleteAllCallback(failfast)); + } else { + callback = std::unique_ptr( + new DeleteFilesCallback(failfast)); + } + + FileUtils::iterateDirectory(dirPath, *callback); + return callback->good(); + } +}; + +} // namespace + +void deleteFilesInDirectory(const tstring &dirPath) { + BatchDeleter(dirPath).execute(); +} + +bool deleteFilesInDirectory(const tstring &dirPath, + const std::nothrow_t &) throw() { + return BatchDeleter(dirPath).execute(std::nothrow); +} + +void deleteDirectoryRecursive(const tstring &dirPath) { + BatchDeleter(dirPath).deleteSubdirs(true).execute(); +} + +bool deleteDirectoryRecursive(const tstring &dirPath, + const std::nothrow_t &) throw() { + return BatchDeleter(dirPath).deleteSubdirs(true).execute(std::nothrow); +} + +namespace { + +struct FindFileDeleter { + typedef HANDLE pointer; + + void operator()(HANDLE h) { + if (h && h != INVALID_HANDLE_VALUE) { + FindClose(h); + } + } +}; + +typedef std::unique_ptr UniqueFindFileHandle; + +}; // namesace +void iterateDirectory(const tstring &dirPath, DirectoryCallback& callback) +{ + const tstring searchString = combinePath(dirPath, _T("*")); + WIN32_FIND_DATA findData; + UniqueFindFileHandle h(FindFirstFile(searchString.c_str(), &findData)); + if (h.get() == INVALID_HANDLE_VALUE) { + // GetLastError() == ERROR_FILE_NOT_FOUND is OK + // - no files in the directory + // ERROR_PATH_NOT_FOUND is returned + // if the parent directory does not exist + if (GetLastError() != ERROR_FILE_NOT_FOUND) { + JP_THROW(SysError(tstrings::any() << "FindFirstFile(" + << dirPath << ") failed", FindFirstFile)); + } + return; + } + + do { + const tstring fname(findData.cFileName); + const tstring filePath = combinePath(dirPath, fname); + if (!isDirectoryAttrs(findData.dwFileAttributes)) { + if (!callback.onFile(filePath)) { + return; + } + } else if (fname != _T(".") && fname != _T("..")) { + if (!callback.onDirectory(filePath)) { + return; + } + } + } while (FindNextFile(h.get(), &findData)); + + // expect GetLastError() == ERROR_NO_MORE_FILES + if (GetLastError() != ERROR_NO_MORE_FILES) { + JP_THROW(SysError(tstrings::any() << "FindNextFile(" + << dirPath << ") failed", FindNextFile)); + } +} + + +tstring replaceSuffix(const tstring& path, const tstring& newSuffix) { + return (path.substr(0, path.size() - suffix(path).size()) + newSuffix); +} + + +DirectoryIterator& DirectoryIterator::findItems(tstring_array& v) { + if (!isDirectory(root)) { + return *this; + } + + iterateDirectory(root, *this); + v.insert(v.end(), items.begin(), items.end()); + items = tstring_array(); + return *this; +} + +bool DirectoryIterator::onFile(const tstring& path) { + if (theWithFiles) { + items.push_back(path); + } + return true; +} + +bool DirectoryIterator::onDirectory(const tstring& path) { + if (theWithFolders) { + items.push_back(path); + } + if (theRecurse) { + DirectoryIterator(path).recurse(theRecurse) + .withFiles(theWithFiles) + .withFolders(theWithFolders) + .findItems(items); + } + return true; +} + + +namespace { + +struct DeleterFunctor { + // Order of items in the following enum is important! + // It controls order in which items of particular type will be deleted. + // See Deleter::execute(). + enum { + File, + FilesInDirectory, + RecursiveDirectory, + EmptyDirectory + }; + + void operator () (const Deleter::Path& path) const { + switch (path.second) { +#define DELETE_SOME(o, f)\ + case o:\ + f(path.first, std::nothrow);\ + break + + DELETE_SOME(File, deleteFile); + DELETE_SOME(EmptyDirectory, deleteDirectory); + DELETE_SOME(FilesInDirectory, deleteFilesInDirectory); + DELETE_SOME(RecursiveDirectory, deleteDirectoryRecursive); + +#undef DELETE_SOME + default: + break; + } + } +}; + +} // namespace + +void Deleter::execute() { + Paths tmp; + tmp.swap(paths); + + // Reorder items to delete. + std::stable_sort(tmp.begin(), tmp.end(), [] (const Paths::value_type& a, + const Paths::value_type& b) { + return a.second < b.second; + }); + + std::for_each(tmp.begin(), tmp.end(), DeleterFunctor()); +} + +Deleter& Deleter::appendFile(const tstring& path) { + paths.push_back(std::make_pair(path, DeleterFunctor::File)); + return *this; +} + +Deleter& Deleter::appendEmptyDirectory(const Directory& dir) { + tstring path = normalizePath(removeTrailingSlash(dir)); + const tstring parent = normalizePath(removeTrailingSlash(dir.parent)); + while(parent != path) { + appendEmptyDirectory(path); + path = dirname(path); + } + + return *this; +} + +Deleter& Deleter::appendEmptyDirectory(const tstring& path) { + paths.push_back(std::make_pair(path, DeleterFunctor::EmptyDirectory)); + return *this; +} + +Deleter& Deleter::appendAllFilesInDirectory(const tstring& path) { + paths.push_back(std::make_pair(path, DeleterFunctor::FilesInDirectory)); + return *this; +} + +Deleter& Deleter::appendRecursiveDirectory(const tstring& path) { + paths.push_back(std::make_pair(path, DeleterFunctor::RecursiveDirectory)); + return *this; +} + + +FileWriter::FileWriter(const tstring& path): dstPath(path) { + tmpFile = FileUtils::createTempFile(_T("jds"), _T(".tmp"), + FileUtils::dirname(path)); + + cleaner.appendFile(tmpFile); + + // we want to get exception on error + tmp.exceptions(std::ifstream::failbit | std::ifstream::badbit); + tmp.open(tmpFile, std::ios::binary | std::ios::trunc); +} + +FileWriter& FileWriter::write(const void* buf, size_t bytes) { + tmp.write(static_cast(buf), bytes); + return *this; +} + +void FileWriter::finalize() { + tmp.close(); + + FileUtils::moveFile(tmpFile, dstPath, false); + + // cancel file deletion + cleaner.cancel(); +} + +} // namespace FileUtils