Skip to content

Commit 0d08963

Browse files
committed
includes_normalize: Fix long path support.
Do not use fixed-size buffers in order to properly support long file paths. This should get rid of the failure described at [1]. This leverages the Win32 Unicode/UTF-8 conversions functions introduced in a previous commit to call GetFullPathnameW() which is required here, since GetFullPathNameA() will fail with long input paths, even when long path support is enabled! [1] #2442 (comment)
1 parent d271ca8 commit 0d08963

File tree

2 files changed

+105
-52
lines changed

2 files changed

+105
-52
lines changed

src/includes_normalize-win32.cc

Lines changed: 97 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -12,32 +12,99 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
#include "includes_normalize.h"
16-
17-
#include "string_piece.h"
18-
#include "string_piece_util.h"
19-
#include "util.h"
15+
#include <string.h>
16+
#include <windows.h>
2017

2118
#include <algorithm>
2219
#include <iterator>
2320
#include <sstream>
2421

25-
#include <windows.h>
22+
#include "includes_normalize.h"
23+
#include "string_piece.h"
24+
#include "string_piece_util.h"
25+
#include "util.h"
2626

2727
namespace {
2828

29-
bool InternalGetFullPathName(const StringPiece& file_name, char* buffer,
30-
size_t buffer_length, std::string* err) {
31-
DWORD result_size = GetFullPathNameA(file_name.AsString().c_str(),
32-
buffer_length, buffer, NULL);
33-
if (result_size == 0) {
34-
*err = "GetFullPathNameA(" + file_name.AsString() + "): " +
35-
GetLastErrorString();
29+
// Get the full path of a given filename. On success set |*path| and return
30+
// true. On failure, clear |path|, set |*err| then result false.
31+
bool InternalGetFullPathName(const StringPiece& file_name, std::string* path,
32+
std::string* err) {
33+
// IMPORTANT: Using GetFullPathNameA() with a long paths will fail with
34+
// "The filename or extension is too long" even if long path supported is
35+
// enabled. GetFullPathNameW() must be used for this function to work!
36+
#if 1
37+
path->clear();
38+
// Convert to wide filename first.
39+
std::string filename_str = file_name.AsString();
40+
std::wstring wide_filename;
41+
if (!ConvertUTF8ToWin32Unicode(filename_str, &wide_filename, err))
42+
return false;
43+
44+
// Call GetFullPathNameW()
45+
DWORD wide_full_size = GetFullPathNameW(wide_filename.c_str(), 0, NULL, NULL);
46+
if (wide_full_size == 0) {
47+
*err = "GetFullPathNameW(" +
48+
std::string(wide_filename.begin(), wide_filename.end()) +
49+
"): " + GetLastErrorString();
50+
return false;
51+
}
52+
53+
// NOTE: wide_full_size includes the null-terminating character.
54+
std::wstring wide_path;
55+
wide_path.resize(static_cast<size_t>(wide_full_size - 1));
56+
DWORD wide_full_size2 =
57+
GetFullPathNameW(wide_filename.c_str(), wide_full_size,
58+
const_cast<wchar_t*>(wide_path.data()), NULL);
59+
if (wide_full_size2 == 0) {
60+
*err = "GetFullPathNameA(" + filename_str + "): " + GetLastErrorString();
61+
path->clear();
62+
return false;
63+
}
64+
65+
// Convert wide_path to Unicode.
66+
return ConvertWin32UnicodeToUTF8(wide_path, path, err);
67+
#else
68+
path->clear();
69+
std::string filename_str = file_name.AsString();
70+
DWORD full_size = GetFullPathNameA(filename_str.c_str(), 0, NULL, NULL);
71+
if (full_size == 0) {
72+
*err = "GetFullPathNameA(" + filename_str + "): " + GetLastErrorString();
3673
return false;
37-
} else if (result_size > buffer_length) {
38-
*err = "path too long";
74+
}
75+
76+
// NOTE: full_size includes the null-terminating character.
77+
path->resize(static_cast<size_t>(full_size - 1));
78+
DWORD result2 = GetFullPathNameA(filename_str.c_str(), full_size,
79+
const_cast<char*>(path->data()), NULL);
80+
if (result2 == 0) {
81+
*err = "GetFullPathNameA(" + filename_str + "): " + GetLastErrorString();
3982
return false;
4083
}
84+
85+
path->resize(static_cast<size_t>(result2));
86+
return true;
87+
#endif
88+
}
89+
90+
// Get the drive prefix of a given filename. On success set |*drive| then return
91+
// true. On failure, clear |*drive|, set |*err| then return false.
92+
bool InternalGetDrive(const StringPiece& file_name, std::string* drive,
93+
std::string* err) {
94+
std::string path;
95+
if (!InternalGetFullPathName(file_name, &path, err))
96+
return false;
97+
98+
char drive_buffer[_MAX_DRIVE];
99+
errno_t ret = _splitpath_s(path.data(), drive_buffer, sizeof(drive_buffer),
100+
NULL, 0, NULL, 0, NULL, 0);
101+
if (ret != 0) {
102+
*err = "_splitpath_s() returned " + std::string(strerror(ret)) +
103+
" for path: " + path;
104+
drive->clear();
105+
return false;
106+
}
107+
drive->assign(drive_buffer);
41108
return true;
42109
}
43110

@@ -74,19 +141,13 @@ bool SameDrive(StringPiece a, StringPiece b, std::string* err) {
74141
return true;
75142
}
76143

77-
char a_absolute[_MAX_PATH];
78-
char b_absolute[_MAX_PATH];
79-
if (!InternalGetFullPathName(a, a_absolute, sizeof(a_absolute), err)) {
80-
return false;
81-
}
82-
if (!InternalGetFullPathName(b, b_absolute, sizeof(b_absolute), err)) {
144+
std::string a_drive;
145+
std::string b_drive;
146+
if (!InternalGetDrive(a, &a_drive, err) ||
147+
!InternalGetDrive(b, &b_drive, err)) {
83148
return false;
84149
}
85-
char a_drive[_MAX_DIR];
86-
char b_drive[_MAX_DIR];
87-
_splitpath(a_absolute, a_drive, NULL, NULL, NULL);
88-
_splitpath(b_absolute, b_drive, NULL, NULL, NULL);
89-
return _stricmp(a_drive, b_drive) == 0;
150+
return _stricmp(a_drive.c_str(), b_drive.c_str()) == 0;
90151
}
91152

92153
// Check path |s| is FullPath style returned by GetFullPathName.
@@ -144,13 +205,14 @@ std::string IncludesNormalize::AbsPath(StringPiece s, std::string* err) {
144205
return result;
145206
}
146207

147-
char result[_MAX_PATH];
148-
if (!InternalGetFullPathName(s, result, sizeof(result), err)) {
208+
std::string result;
209+
if (!InternalGetFullPathName(s, &result, err)) {
149210
return "";
150211
}
151-
for (char* c = result; *c; ++c)
152-
if (*c == '\\')
153-
*c = '/';
212+
for (char& c : result) {
213+
if (c == '\\')
214+
c = '/';
215+
}
154216
return result;
155217
}
156218

@@ -183,24 +245,17 @@ std::string IncludesNormalize::Relativize(
183245

184246
bool IncludesNormalize::Normalize(const std::string& input, std::string* result,
185247
std::string* err) const {
186-
char copy[_MAX_PATH + 1];
187-
size_t len = input.size();
188-
if (len > _MAX_PATH) {
189-
*err = "path too long";
190-
return false;
191-
}
192-
strncpy(copy, input.c_str(), input.size() + 1);
248+
std::string copy = input;
193249
uint64_t slash_bits;
194-
CanonicalizePath(copy, &len, &slash_bits);
195-
StringPiece partially_fixed(copy, len);
196-
std::string abs_input = AbsPath(partially_fixed, err);
250+
CanonicalizePath(&copy, &slash_bits);
251+
std::string abs_input = AbsPath(copy, err);
197252
if (!err->empty())
198253
return false;
199254

200255
if (!SameDrive(abs_input, relative_to_, err)) {
201256
if (!err->empty())
202257
return false;
203-
*result = partially_fixed.AsString();
258+
*result = copy;
204259
return true;
205260
}
206261
*result = Relativize(abs_input, split_relative_to_, err);

src/includes_normalize_test.cc

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@
2424

2525
namespace {
2626

27-
std::string GetCurDir() {
27+
std::string GetCurrentDirName() {
2828
char buf[_MAX_PATH];
2929
_getcwd(buf, sizeof(buf));
3030
std::vector<StringPiece> parts = SplitStringPiece(buf, '\\');
31-
return parts[parts.size() - 1].AsString();
31+
return parts.back().AsString();
3232
}
3333

3434
std::string NormalizeAndCheckNoError(const std::string& input) {
@@ -59,7 +59,7 @@ TEST(IncludesNormalize, Simple) {
5959

6060
TEST(IncludesNormalize, WithRelative) {
6161
std::string err;
62-
std::string currentdir = GetCurDir();
62+
std::string currentdir = GetCurrentDirName();
6363
EXPECT_EQ("c", NormalizeRelativeAndCheckNoError("a/b/c", "a/b"));
6464
EXPECT_EQ("a",
6565
NormalizeAndCheckNoError(IncludesNormalize::AbsPath("a", &err)));
@@ -107,10 +107,9 @@ TEST(IncludesNormalize, LongInvalidPath) {
107107
// Too long, won't be canonicalized. Ensure doesn't crash.
108108
std::string result, err;
109109
IncludesNormalize normalizer(".");
110-
EXPECT_FALSE(
111-
normalizer.Normalize(kLongInputString, &result, &err));
112-
EXPECT_EQ("path too long", err);
113-
110+
EXPECT_TRUE(normalizer.Normalize(kLongInputString, &result, &err));
111+
EXPECT_FALSE(result.empty());
112+
EXPECT_TRUE(err.empty()) << err;
114113

115114
// Construct max size path having cwd prefix.
116115
// kExactlyMaxPath = "$cwd\\a\\aaaa...aaaa\0";
@@ -142,7 +141,7 @@ TEST(IncludesNormalize, LongInvalidPath) {
142141
NormalizeAndCheckNoError(kExactlyMaxPath));
143142
}
144143

145-
TEST(IncludesNormalize, ShortRelativeButTooLongAbsolutePath) {
144+
TEST(IncludesNormalize, ShortRelativeButLongAbsolutePath) {
146145
std::string result, err;
147146
IncludesNormalize normalizer(".");
148147
// A short path should work
@@ -162,6 +161,5 @@ TEST(IncludesNormalize, ShortRelativeButTooLongAbsolutePath) {
162161
EXPECT_EQ(strlen(kExactlyMaxPath), _MAX_PATH);
163162

164163
// Make sure a path that's exactly _MAX_PATH long fails with a proper error.
165-
EXPECT_FALSE(normalizer.Normalize(kExactlyMaxPath, &result, &err));
166-
EXPECT_TRUE(err.find("GetFullPathName") != std::string::npos);
164+
EXPECT_TRUE(normalizer.Normalize(kExactlyMaxPath, &result, &err)) << err;
167165
}

0 commit comments

Comments
 (0)