/*
* 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 <memory>
#include <algorithm>
#include <shlwapi.h>
#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<DeleteFilesCallback> callback;
if (recursive) {
callback = std::unique_ptr<DeleteFilesCallback>(
new DeleteAllCallback(failfast));
} else {
callback = std::unique_ptr<DeleteFilesCallback>(
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<HANDLE, FindFileDeleter> 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;
}
/**代码未完, 请加载全部代码(NowJava.com).**/