--- old/src/jdk.jpackage/windows/native/libjpackage/FileUtils.cpp 2019-08-28 09:05:09.942665300 -0400 +++ new/src/jdk.jpackage/windows/native/libjpackage/FileUtils.cpp 2019-08-28 09:04:58.377842500 -0400 @@ -1,702 +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 +/* + * 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