Skip to content

Commit

Permalink
Use *W versions of win32 file functions
Browse files Browse the repository at this point in the history
So that the long paths work on windows with registry LongPathsEnabled=1
  • Loading branch information
parbo committed Apr 21, 2023
1 parent c266b21 commit 5e85f4d
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 19 deletions.
73 changes: 55 additions & 18 deletions src/disk_interface.cc
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
#ifdef _WIN32
#include <sstream>
#include <windows.h>
#include <direct.h> // _mkdir
#include <fileapi.h>
#else
#include <unistd.h>
#endif
Expand Down Expand Up @@ -56,7 +56,8 @@ string DirName(const string& path) {

int MakeDir(const string& path) {
#ifdef _WIN32
return _mkdir(path.c_str());
std::wstring wpath = ::toWide(path);
return CreateDirectoryW((LPCWSTR)wpath.c_str(), NULL) ? 0 : -1;
#else
return mkdir(path.c_str(), 0777);
#endif
Expand All @@ -68,14 +69,15 @@ TimeStamp TimeStampFromFileTime(const FILETIME& filetime) {
// We don't much care about epoch correctness but we do want the
// resulting value to fit in a 64-bit integer.
uint64_t mtime = ((uint64_t)filetime.dwHighDateTime << 32) |
((uint64_t)filetime.dwLowDateTime);
((uint64_t)filetime.dwLowDateTime);
// 1600 epoch -> 2000 epoch (subtract 400 years).
return (TimeStamp)mtime - 12622770400LL * (1000000000LL / 100);
}

TimeStamp StatSingleFile(const string& path, string* err) {
WIN32_FILE_ATTRIBUTE_DATA attrs;
if (!GetFileAttributesExA(path.c_str(), GetFileExInfoStandard, &attrs)) {
std::wstring wpath = ::toWide(path);
if (!GetFileAttributesExW(wpath.c_str(), GetFileExInfoStandard, &attrs)) {
DWORD win_err = GetLastError();
if (win_err == ERROR_FILE_NOT_FOUND || win_err == ERROR_PATH_NOT_FOUND)
return 0;
Expand Down Expand Up @@ -218,43 +220,77 @@ TimeStamp RealDiskInterface::Stat(const string& path, string* err) const {
#elif defined(__APPLE__)
return ((int64_t)st.st_mtimespec.tv_sec * 1000000000LL +
st.st_mtimespec.tv_nsec);
#elif defined(st_mtime) // A macro, so we're likely on modern POSIX.
return (int64_t)st.st_mtim.tv_sec * 1000000000LL + st.st_mtim.tv_nsec;
#elif defined(st_mtime) // A macro, so we're likely on modern POSIX.
return (int64_t)st.st_mtim.tv_sec * 1000000000LL + st.st_mtim.tv_nsec;
#else
return (int64_t)st.st_mtime * 1000000000LL + st.st_mtimensec;
return (int64_t)st.st_mtime * 1000000000LL + st.st_mtimensec;
#endif
#endif
}

bool RealDiskInterface::WriteFile(const string& path, const string& contents) {
#ifdef _WIN32
std::wstring wpath = ::toWide(path);
HANDLE h = CreateFileW(wpath.c_str(), (GENERIC_READ | GENERIC_WRITE), 0, NULL,
OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (h == INVALID_HANDLE_VALUE) {
Error("WriteFile(%s): Unable to create file. %s", path.c_str(),
GetLastErrorString().c_str());
return false;
}
DWORD written = 0;
bool result = ::WriteFile(h, (LPCVOID)contents.data(), contents.length(),
&written, NULL);
if (!result || written != contents.length()) {
Error("WriteFile(%s): Unable to write to the file. %s", path.c_str(),
GetLastErrorString().c_str());
CloseHandle(h);
return false;
}

if (!CloseHandle(h)) {
Error("WriteFile(%s): Unable to close the file. %s", path.c_str(),
GetLastErrorString().c_str());
return false;
}

return true;
#else
FILE* fp = fopen(path.c_str(), "w");
if (fp == NULL) {
Error("WriteFile(%s): Unable to create file. %s",
path.c_str(), strerror(errno));
Error("WriteFile(%s): Unable to create file. %s", path.c_str(),
strerror(errno));
return false;
}

if (fwrite(contents.data(), 1, contents.length(), fp) < contents.length()) {
Error("WriteFile(%s): Unable to write to the file. %s",
path.c_str(), strerror(errno));
if (fwrite(contents.data(), 1, contents.length(), fp) < contents.length()) {
Error("WriteFile(%s): Unable to write to the file. %s", path.c_str(),
strerror(errno));
fclose(fp);
return false;
}

if (fclose(fp) == EOF) {
Error("WriteFile(%s): Unable to close the file. %s",
path.c_str(), strerror(errno));
Error("WriteFile(%s): Unable to close the file. %s", path.c_str(),
strerror(errno));
return false;
}

return true;
#endif
}

bool RealDiskInterface::MakeDir(const string& path) {
if (::MakeDir(path) < 0) {
#if _WIN32
if (GetLastError() == ERROR_ALREADY_EXISTS) {
return true;
}
#else
if (errno == EEXIST) {
return true;
}
#endif
Error("mkdir(%s): %s", path.c_str(), strerror(errno));
return false;
}
Expand All @@ -273,7 +309,8 @@ FileReader::Status RealDiskInterface::ReadFile(const string& path,

int RealDiskInterface::RemoveFile(const string& path) {
#ifdef _WIN32
DWORD attributes = GetFileAttributesA(path.c_str());
std::wstring wpath = ::toWide(path);
DWORD attributes = GetFileAttributesW(wpath.c_str());
if (attributes == INVALID_FILE_ATTRIBUTES) {
DWORD win_err = GetLastError();
if (win_err == ERROR_FILE_NOT_FOUND || win_err == ERROR_PATH_NOT_FOUND) {
Expand All @@ -284,15 +321,15 @@ int RealDiskInterface::RemoveFile(const string& path) {
// On Windows Ninja should behave the same:
// https://github.com/ninja-build/ninja/issues/1886
// Skip error checking. If this fails, accept whatever happens below.
SetFileAttributesA(path.c_str(), attributes & ~FILE_ATTRIBUTE_READONLY);
SetFileAttributesW(wpath.c_str(), attributes & ~FILE_ATTRIBUTE_READONLY);
}
if (attributes & FILE_ATTRIBUTE_DIRECTORY) {
// remove() deletes both files and directories. On Windows we have to
// select the correct function (DeleteFile will yield Permission Denied when
// used on a directory)
// This fixes the behavior of ninja -t clean in some cases
// https://github.com/ninja-build/ninja/issues/828
if (!RemoveDirectoryA(path.c_str())) {
if (!RemoveDirectoryW(wpath.c_str())) {
DWORD win_err = GetLastError();
if (win_err == ERROR_FILE_NOT_FOUND || win_err == ERROR_PATH_NOT_FOUND) {
return 1;
Expand All @@ -302,7 +339,7 @@ int RealDiskInterface::RemoveFile(const string& path) {
return -1;
}
} else {
if (!DeleteFileA(path.c_str())) {
if (!DeleteFileW(wpath.c_str())) {
DWORD win_err = GetLastError();
if (win_err == ERROR_FILE_NOT_FOUND || win_err == ERROR_PATH_NOT_FOUND) {
return 1;
Expand Down
10 changes: 9 additions & 1 deletion src/util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,8 @@ int ReadFile(const string& path, string* contents, string* err) {
// This makes a ninja run on a set of 1500 manifest files about 4% faster
// than using the generic fopen code below.
err->clear();
HANDLE f = ::CreateFileA(path.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL,
std::wstring wpath = ::toWide(path);
HANDLE f = ::CreateFileW(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
if (f == INVALID_HANDLE_VALUE) {
err->assign(GetLastErrorString());
Expand Down Expand Up @@ -477,6 +478,13 @@ void Win32Fatal(const char* function, const char* hint) {
Fatal("%s: %s", function, GetLastErrorString().c_str());
}
}

std::wstring toWide(const string& s) {
int wchars_num = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, NULL, 0);
std::wstring w(wchars_num, ' ');
MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, (LPWSTR)w.data(), w.size());
return w;
}
#endif

bool islatinalpha(int c) {
Expand Down
3 changes: 3 additions & 0 deletions src/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ std::string GetLastErrorString();

/// Calls Fatal() with a function name and GetLastErrorString.
NORETURN void Win32Fatal(const char* function, const char* hint = NULL);

/// Converts utf-8 std::string to wide char wstring
std::wstring toWide(const std::string& s);
#endif

#endif // NINJA_UTIL_H_

0 comments on commit 5e85f4d

Please sign in to comment.