Fix - Makemsix pack fails when directory given ends with "\" (#361)

This commit is contained in:
Ruben Guerrero 2020-07-28 14:14:05 -07:00 коммит произвёл GitHub
Родитель da77ac2910
Коммит ebad9ca20f
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
4 изменённых файлов: 290 добавлений и 252 удалений

Просмотреть файл

@ -58,7 +58,7 @@ namespace MSIX {
ComPtr<IStream> OpenFile(const std::string& fileName, MSIX::FileStream::Mode mode) override;
std::multimap<std::uint64_t, std::string> GetFilesByLastModDate() override;
static const char* GetPathSeparator();
char GetPathSeparator() const;
protected:
std::string m_root;

Просмотреть файл

@ -13,38 +13,58 @@
#include <dirent.h>
#include <map>
namespace MSIX {
template<class Lambda>
void WalkDirectory(const std::string& root, Lambda& visitor)
namespace MSIX
{
namespace
{
static std::string dot(".");
static std::string dotdot("..");
std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(root.c_str()), closedir);
ThrowErrorIf(Error::FileNotFound, dir.get() == nullptr, "Invalid directory");
struct dirent* dp;
// TODO: handle junction loops
while((dp = readdir(dir.get())) != nullptr)
template<class Lambda>
void WalkDirectory(const std::string& root, Lambda& visitor)
{
std::string fileName = std::string(dp->d_name);
std::string child = root + "/" + fileName;
if (dp->d_type == DT_DIR)
static std::string dot(".");
static std::string dotdot("..");
std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(root.c_str()), closedir);
ThrowErrorIf(Error::FileNotFound, dir.get() == nullptr, "Invalid directory");
struct dirent* dp;
// TODO: handle junction loops
while((dp = readdir(dir.get())) != nullptr)
{
if ((fileName != dot) && (fileName != dotdot))
std::string fileName = std::string(dp->d_name);
std::string child = root + "/" + fileName;
if (dp->d_type == DT_DIR)
{
WalkDirectory(child, visitor);
if ((fileName != dot) && (fileName != dotdot))
{
WalkDirectory(child, visitor);
}
}
else
{
// TODO: ignore .DS_STORE for mac?
struct stat sb;
ThrowErrorIf(Error::Unexpected, stat(child.c_str(), &sb) == -1, std::string("stat call failed" + std::to_string(errno)).c_str());
if (!visitor(root, std::move(fileName), static_cast<std::uint64_t>(sb.st_mtime)))
{
break;
}
}
}
else
}
#define DEFAULT_MODE S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH
void mkdirp(std::string& path, size_t startPos = 0, mode_t mode = DEFAULT_MODE)
{
char* p = &path[startPos];
if (*p == '/') { p++; }
while (*p != '\0')
{
// TODO: ignore .DS_STORE for mac?
struct stat sb;
ThrowErrorIf(Error::Unexpected, stat(child.c_str(), &sb) == -1, std::string("stat call failed" + std::to_string(errno)).c_str());
if (!visitor(root, std::move(fileName), static_cast<std::uint64_t>(sb.st_mtime)))
{
break;
}
while (*p != '\0' && *p != '/') { p++; }
char v = *p;
*p = '\0';
ThrowErrorIfNot(Error::FileCreateDirectory,(mkdir(path.c_str(), mode) != -1 || errno == EEXIST), path.c_str());
*p = v;
if (*p != '\0') {p++;}
}
}
}
@ -55,27 +75,15 @@ namespace MSIX {
NOTIMPLEMENTED;
}
#define DEFAULT_MODE S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH
void mkdirp(std::string& path, size_t startPos = 0, mode_t mode = DEFAULT_MODE)
{
char* p = &path[startPos];
if (*p == '/') { p++; }
while (*p != '\0')
{
while (*p != '\0' && *p != '/') { p++; }
char v = *p;
*p = '\0';
ThrowErrorIfNot(Error::FileCreateDirectory,(mkdir(path.c_str(), mode) != -1 || errno == EEXIST), path.c_str());
*p = v;
if (*p != '\0') {p++;}
}
}
const char* DirectoryObject::GetPathSeparator() { return "/"; }
char DirectoryObject::GetPathSeparator() const { return '/'; }
DirectoryObject::DirectoryObject(const std::string& root, bool createRootIfNecessary) : m_root(root)
{
if (!m_root.empty() && m_root.back() == GetPathSeparator())
{
m_root = m_root.substr(0, m_root.length() - 1);
}
if (createRootIfNecessary)
{
mkdirp(m_root);

Просмотреть файл

@ -19,263 +19,277 @@
#include <codecvt>
#include <queue>
namespace MSIX {
enum class WalkOptions : std::uint16_t
namespace MSIX
{
namespace
{
Files = 1, // Enumerate files within the specified directory
Directories = 2, // Enumerate directories
Recursive = 4 // Enumerate recursively
};
inline constexpr WalkOptions operator& (WalkOptions a, WalkOptions b)
{
return static_cast<WalkOptions>(static_cast<uint16_t>(a) & static_cast<uint16_t>(b));
}
inline constexpr WalkOptions operator| (WalkOptions a, WalkOptions b)
{
return static_cast<WalkOptions>(static_cast<uint16_t>(a) | static_cast<uint16_t>(b));
}
template <class Lambda>
void WalkDirectory(const std::string& root, WalkOptions options, Lambda& visitor)
{
static std::string dot(".");
static std::string dotdot("..");
std::wstring utf16Name = utf8_to_wstring(root);
if ((options & WalkOptions::Files) == WalkOptions::Files)
enum class WalkOptions : std::uint16_t
{
utf16Name += L"\\*";
Files = 1, // Enumerate files within the specified directory
Directories = 2, // Enumerate directories
Recursive = 4 // Enumerate recursively
};
inline constexpr WalkOptions operator& (WalkOptions a, WalkOptions b)
{
return static_cast<WalkOptions>(static_cast<uint16_t>(a) & static_cast<uint16_t>(b));
}
WIN32_FIND_DATA findFileData = {};
std::unique_ptr<std::remove_pointer<HANDLE>::type, decltype(&::FindClose)> find(
FindFirstFile(reinterpret_cast<LPCWSTR>(utf16Name.c_str()), &findFileData),
&FindClose);
if (INVALID_HANDLE_VALUE == find.get())
inline constexpr WalkOptions operator| (WalkOptions a, WalkOptions b)
{
DWORD lastError = GetLastError();
if (lastError == ERROR_FILE_NOT_FOUND)
return static_cast<WalkOptions>(static_cast<uint16_t>(a) | static_cast<uint16_t>(b));
}
template <class Lambda>
void WalkDirectory(const std::string& root, WalkOptions options, Lambda& visitor)
{
static std::string dot(".");
static std::string dotdot("..");
std::wstring utf16Name = utf8_to_wstring(root);
if ((options & WalkOptions::Files) == WalkOptions::Files)
{
return;
utf16Name += L"\\*";
}
ThrowWin32ErrorIfNot(lastError, false, "FindFirstFile failed.");
}
// TODO: handle junction loops
do
{
utf16Name = std::wstring(findFileData.cFileName);
auto utf8Name = wstring_to_utf8(utf16Name);
if (findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
WIN32_FIND_DATA findFileData = {};
std::unique_ptr<std::remove_pointer<HANDLE>::type, decltype(&::FindClose)> find(
FindFirstFile(reinterpret_cast<LPCWSTR>(utf16Name.c_str()), &findFileData),
&FindClose);
if (INVALID_HANDLE_VALUE == find.get())
{
if (dot != utf8Name && dotdot != utf8Name)
DWORD lastError = GetLastError();
if (lastError == ERROR_FILE_NOT_FOUND)
{
std::string child = root + "\\" + utf8Name;
if ((options & WalkOptions::Directories) == WalkOptions::Directories &&
!visitor(root, WalkOptions::Directories, std::move(utf8Name), 0))
return;
}
ThrowWin32ErrorIfNot(lastError, false, "FindFirstFile failed.");
}
// TODO: handle junction loops
do
{
utf16Name = std::wstring(findFileData.cFileName);
auto utf8Name = wstring_to_utf8(utf16Name);
if (findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
if (dot != utf8Name && dotdot != utf8Name)
{
std::string child = root + "\\" + utf8Name;
if ((options & WalkOptions::Directories) == WalkOptions::Directories &&
!visitor(root, WalkOptions::Directories, std::move(utf8Name), 0))
{
break;
}
if ((options & WalkOptions::Recursive) == WalkOptions::Recursive)
{
WalkDirectory(child, options, visitor);
}
}
}
else if ((options & WalkOptions::Files) == WalkOptions::Files)
{
ULARGE_INTEGER fileTime;
fileTime.HighPart = findFileData.ftLastWriteTime.dwHighDateTime;
fileTime.LowPart = findFileData.ftLastWriteTime.dwLowDateTime;
if (!visitor(root, WalkOptions::Files, std::move(utf8Name), static_cast<std::uint64_t>(fileTime.QuadPart)))
{
break;
}
if ((options & WalkOptions::Recursive) == WalkOptions::Recursive)
{
WalkDirectory(child, options, visitor);
}
}
}
else if ((options & WalkOptions::Files) == WalkOptions::Files)
while (FindNextFile(find.get(), &findFileData));
std::uint32_t lastError = static_cast<std::uint32_t>(GetLastError());
ThrowWin32ErrorIfNot(lastError,
((lastError == ERROR_NO_MORE_FILES) ||
(lastError == ERROR_SUCCESS) ||
(lastError == ERROR_ALREADY_EXISTS)),
"FindNextFile");
}
std::string GetFullPath(const std::string& path)
{
const std::wstring longPathPrefix = LR"(\\?\)";
auto pathWide = utf8_to_wstring(path);
std::wstring result = longPathPrefix;
size_t prefixChars = longPathPrefix.size();
if (pathWide.substr(0, longPathPrefix.size()) == longPathPrefix)
{
ULARGE_INTEGER fileTime;
fileTime.HighPart = findFileData.ftLastWriteTime.dwHighDateTime;
fileTime.LowPart = findFileData.ftLastWriteTime.dwLowDateTime;
if (!visitor(root, WalkOptions::Files, std::move(utf8Name), static_cast<std::uint64_t>(fileTime.QuadPart)))
// Already begins with long path prefix, so don't add it
result = L"";
prefixChars = 0;
}
// We aren't going to go out of our way to support crazy incoming paths.
// This means that path here is limited to MAX_PATH, but the resulting path won't be.
DWORD length = GetFullPathNameW(pathWide.c_str(), 0, nullptr, nullptr);
// Any errors result in 0
ThrowLastErrorIf(length == 0, "Failed to get necessary char count for GetFullPathNameW");
// When requesting size, length accounts for null char
result.resize(prefixChars + length, L' ');
DWORD newLength = GetFullPathNameW(pathWide.c_str(), length, &result[prefixChars], nullptr);
// On success, newLength does not account for null char
ThrowLastErrorIf(newLength == 0, "Failed to get necessary char count for GetFullPathNameW");
// The normal scenario is that length - 1 == newLength, but for relative paths, GetFullPathName
// doesn't return the correct case size and there's no guarantee it will be always bigger than needed.
// If we are in a case that length > newlenght there's no harm, just resize the string. Otherwise, it means
// that the first length was not correct and the path didn't get resolved correctly. Try until previous length
// is lower than the next call.
if (length < newLength)
{
DWORD retry = 1;
DWORD previousLength = 0;
do
{
break;
}
}
}
while (FindNextFile(find.get(), &findFileData));
std::uint32_t lastError = static_cast<std::uint32_t>(GetLastError());
ThrowWin32ErrorIfNot(lastError,
((lastError == ERROR_NO_MORE_FILES) ||
(lastError == ERROR_SUCCESS) ||
(lastError == ERROR_ALREADY_EXISTS)),
"FindNextFile");
}
std::string GetFullPath(const std::string& path)
{
const std::wstring longPathPrefix = LR"(\\?\)";
auto pathWide = utf8_to_wstring(path);
std::wstring result = longPathPrefix;
size_t prefixChars = longPathPrefix.size();
if (pathWide.substr(0, longPathPrefix.size()) == longPathPrefix)
{
// Already begins with long path prefix, so don't add it
result = L"";
prefixChars = 0;
}
// We aren't going to go out of our way to support crazy incoming paths.
// This means that path here is limited to MAX_PATH, but the resulting path won't be.
DWORD length = GetFullPathNameW(pathWide.c_str(), 0, nullptr, nullptr);
// Any errors result in 0
ThrowLastErrorIf(length == 0, "Failed to get necessary char count for GetFullPathNameW");
// When requesting size, length accounts for null char
result.resize(prefixChars + length, L' ');
DWORD newLength = GetFullPathNameW(pathWide.c_str(), length, &result[prefixChars], nullptr);
// On success, newLength does not account for null char
ThrowLastErrorIf(newLength == 0, "Failed to get necessary char count for GetFullPathNameW");
// The normal scenario is that length - 1 == newLength, but for relative paths, GetFullPathName
// doesn't return the correct case size and there's no guarantee it will be always bigger than needed.
// If we are in a case that length > newlenght there's no harm, just resize the string. Otherwise, it means
// that the first length was not correct and the path didn't get resolved correctly. Try until previous length
// is lower than the next call.
if (length < newLength)
{
DWORD retry = 1;
DWORD previousLength = 0;
do
{
previousLength = newLength + retry;
result.resize(prefixChars + previousLength, L' ');
newLength = GetFullPathNameW(pathWide.c_str(), previousLength, &result[prefixChars], nullptr);
retry++;
} while ((previousLength < newLength) && retry <= 10);
}
result.resize(prefixChars + newLength);
return wstring_to_utf8(result);
}
struct DirectoryInfo
{
std::string Name;
bool Create;
DirectoryInfo(std::string&& name, bool create) : Name(std::move(name)), Create(create) {}
};
static void SplitDirectories(const std::string& path, std::queue<DirectoryInfo>& directories, bool forCreate)
{
static char const* const Delims = "\\/";
const std::string longPathPrefix = R"(\\?\)";
size_t copyPos = 0;
size_t searchPos = 0;
size_t lastPos = 0;
if (path.substr(0, longPathPrefix.size()) == longPathPrefix)
{
// Absolute path, we need to skip it
searchPos = longPathPrefix.size();
}
while (lastPos != std::string::npos)
{
lastPos = path.find_first_of(Delims, searchPos);
std::string temp = path.substr(copyPos, lastPos - copyPos);
if (!temp.empty())
{
directories.emplace(std::move(temp), forCreate);
previousLength = newLength + retry;
result.resize(prefixChars + previousLength, L' ');
newLength = GetFullPathNameW(pathWide.c_str(), previousLength, &result[prefixChars], nullptr);
retry++;
} while ((previousLength < newLength) && retry <= 10);
}
copyPos = searchPos = lastPos + 1;
result.resize(prefixChars + newLength);
return wstring_to_utf8(result);
}
}
// Destroys directories
static void EnsureDirectoryStructureExists(const std::string& root, std::queue<DirectoryInfo>& directories, bool lastIsFile, std::string* resultingPath = nullptr)
{
ThrowErrorIf(Error::Unexpected, directories.empty(), "Some path must be given");
auto PopFirst = [&directories]()
struct DirectoryInfo
{
auto result = directories.front();
directories.pop();
return result;
std::string Name;
bool Create;
DirectoryInfo(std::string&& name, bool create) : Name(std::move(name)), Create(create) {}
};
std::string path = root;
bool isFirst = true;
while (!directories.empty())
void SplitDirectories(const std::string& path, std::queue<DirectoryInfo>& directories, bool forCreate)
{
auto dirInfo = PopFirst();
if (!path.empty())
{
path += DirectoryObject::GetPathSeparator();
}
path += dirInfo.Name;
static char const* const Delims = "\\/";
const std::string longPathPrefix = R"(\\?\)";
bool shouldWeCreateDir = dirInfo.Create;
size_t copyPos = 0;
size_t searchPos = 0;
size_t lastPos = 0;
// When the last entry is a file, and we are on the last entry, never create
if (lastIsFile && directories.empty())
if (path.substr(0, longPathPrefix.size()) == longPathPrefix)
{
shouldWeCreateDir = false;
}
// If this is a rooted list of directories, the first one will be a device
else if (root.empty() && isFirst)
{
shouldWeCreateDir = false;
// Absolute path, we need to skip it
searchPos = longPathPrefix.size();
}
if (shouldWeCreateDir)
while (lastPos != std::string::npos)
{
bool found = false;
lastPos = path.find_first_of(Delims, searchPos);
std::wstring utf16Name = utf8_to_wstring(path);
DWORD attr = GetFileAttributesW(utf16Name.c_str());
if (attr == INVALID_FILE_ATTRIBUTES)
std::string temp = path.substr(copyPos, lastPos - copyPos);
if (!temp.empty())
{
if (!CreateDirectory(utf16Name.c_str(), nullptr))
{
auto lastError = GetLastError();
ThrowWin32ErrorIfNot(lastError, (lastError == ERROR_ALREADY_EXISTS), std::string("Call to CreateDirectory failed creating: " + path).c_str());
}
directories.emplace(std::move(temp), forCreate);
}
else
{
ThrowWin32ErrorIfNot(ERROR_ALREADY_EXISTS, attr & FILE_ATTRIBUTE_DIRECTORY, ("A file at this path already exists: " + path).c_str());
}
}
isFirst = false;
copyPos = searchPos = lastPos + 1;
}
}
if (resultingPath)
// Destroys directories
void EnsureDirectoryStructureExists(
const std::string& root,
std::queue<DirectoryInfo>& directories,
bool lastIsFile,
char pathSeparator,
std::string* resultingPath = nullptr)
{
*resultingPath = std::move(path);
ThrowErrorIf(Error::Unexpected, directories.empty(), "Some path must be given");
auto PopFirst = [&directories]()
{
auto result = directories.front();
directories.pop();
return result;
};
std::string path = root;
bool isFirst = true;
while (!directories.empty())
{
auto dirInfo = PopFirst();
if (!path.empty())
{
path += pathSeparator;
}
path += dirInfo.Name;
bool shouldWeCreateDir = dirInfo.Create;
// When the last entry is a file, and we are on the last entry, never create
if (lastIsFile && directories.empty())
{
shouldWeCreateDir = false;
}
// If this is a rooted list of directories, the first one will be a device
else if (root.empty() && isFirst)
{
shouldWeCreateDir = false;
}
if (shouldWeCreateDir)
{
bool found = false;
std::wstring utf16Name = utf8_to_wstring(path);
DWORD attr = GetFileAttributesW(utf16Name.c_str());
if (attr == INVALID_FILE_ATTRIBUTES)
{
if (!CreateDirectory(utf16Name.c_str(), nullptr))
{
auto lastError = GetLastError();
ThrowWin32ErrorIfNot(lastError, (lastError == ERROR_ALREADY_EXISTS), std::string("Call to CreateDirectory failed creating: " + path).c_str());
}
}
else
{
ThrowWin32ErrorIfNot(ERROR_ALREADY_EXISTS, attr & FILE_ATTRIBUTE_DIRECTORY, ("A file at this path already exists: " + path).c_str());
}
}
isFirst = false;
}
if (resultingPath)
{
*resultingPath = std::move(path);
}
}
}
const char* DirectoryObject::GetPathSeparator() { return "\\"; }
char DirectoryObject::GetPathSeparator() const { return '\\'; }
DirectoryObject::DirectoryObject(const std::string& root, bool createRootIfNecessary)
{
m_root = GetFullPath(root);
if (!m_root.empty() && m_root.back() == GetPathSeparator())
{
m_root = m_root.substr(0, m_root.length() - 1);
}
if (createRootIfNecessary)
{
std::queue<DirectoryInfo> directories;
SplitDirectories(m_root, directories, true);
EnsureDirectoryStructureExists({}, directories, false);
EnsureDirectoryStructureExists({}, directories, false, GetPathSeparator());
}
}
@ -296,7 +310,7 @@ namespace MSIX {
SplitDirectories(fileName, directories, modeWillCreateFile);
std::string path;
EnsureDirectoryStructureExists(m_root, directories, true, &path);
EnsureDirectoryStructureExists(m_root, directories, true, GetPathSeparator(), &path);
auto result = ComPtr<IStream>::Make<FileStream>(std::move(utf8_to_wstring(path)), mode);
return result;

Просмотреть файл

@ -45,6 +45,22 @@ TEST_CASE("Pack_Good", "[pack]")
MsixTest::Pack::ValidatePackageStream(outputPackage);
}
TEST_CASE("Pack_Good_EndsWithSeparator", "[pack]")
{
HRESULT expected = S_OK;
#ifdef WIN32
std::string directory = "input\\";
#else
std::string directory = "input/";
#endif
RunPackTest(expected, directory);
// Verify output package
MsixTest::Pack::ValidatePackageStream(outputPackage);
}
// Fail if there's no AppxManifest.xml
TEST_CASE("Pack_AppxManifestNotPresent", "[pack]")
{