Merge "Add (partial) support for Windows long paths"

This commit is contained in:
Treehugger Robot 2017-06-28 19:15:38 +00:00 committed by Gerrit Code Review
commit 9ed76f0acc
3 changed files with 145 additions and 2 deletions

View File

@ -22,6 +22,8 @@
#else
// Bring in prototypes for standard APIs so that we can import them into the utf8 namespace.
#include <fcntl.h> // open
#include <stdio.h> // fopen
#include <sys/stat.h> // mkdir
#include <unistd.h> // unlink
#endif
@ -53,6 +55,19 @@ bool UTF8ToWide(const char* utf8, std::wstring* utf16);
// Convert a UTF-8 std::string (including any embedded NULL characters) to
// UTF-16. Returns whether the conversion was done successfully.
bool UTF8ToWide(const std::string& utf8, std::wstring* utf16);
// Convert a file system path, represented as a NULL-terminated string of
// UTF-8 characters, to a UTF-16 string representing the same file system
// path using the Windows extended-lengh path representation.
//
// See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#MAXPATH:
// ```The Windows API has many functions that also have Unicode versions to
// permit an extended-length path for a maximum total path length of 32,767
// characters. To specify an extended-length path, use the "\\?\" prefix.
// For example, "\\?\D:\very long path".```
//
// Returns whether the conversion was done successfully.
bool UTF8PathToWindowsLongPath(const char* utf8, std::wstring* utf16);
#endif
// The functions in the utf8 namespace take UTF-8 strings. For Windows, these
@ -73,9 +88,13 @@ bool UTF8ToWide(const std::string& utf8, std::wstring* utf16);
namespace utf8 {
#ifdef _WIN32
FILE* fopen(const char* name, const char* mode);
int mkdir(const char* name, mode_t mode);
int open(const char* name, int flags, ...);
int unlink(const char* name);
#else
using ::fopen;
using ::mkdir;
using ::open;
using ::unlink;
#endif

View File

@ -19,7 +19,9 @@
#include "android-base/utf8.h"
#include <fcntl.h>
#include <stdio.h>
#include <algorithm>
#include <string>
#include "android-base/logging.h"
@ -153,12 +155,58 @@ bool UTF8ToWide(const std::string& utf8, std::wstring* utf16) {
return UTF8ToWide(utf8.c_str(), utf8.length(), utf16);
}
static bool isDriveLetter(wchar_t c) {
return (c >= L'a' && c <= L'z') || (c >= L'A' && c <= L'Z');
}
bool UTF8PathToWindowsLongPath(const char* utf8, std::wstring* utf16) {
if (!UTF8ToWide(utf8, utf16)) {
return false;
}
// Note: Although most Win32 File I/O API are limited to MAX_PATH (260
// characters), the CreateDirectory API is limited to 248 characters.
if (utf16->length() >= 248) {
// If path is of the form "x:\" or "x:/"
if (isDriveLetter((*utf16)[0]) && (*utf16)[1] == L':' &&
((*utf16)[2] == L'\\' || (*utf16)[2] == L'/')) {
// Append long path prefix, and make sure there are no unix-style
// separators to ensure a fully compliant Win32 long path string.
utf16->insert(0, LR"(\\?\)");
std::replace(utf16->begin(), utf16->end(), L'/', L'\\');
}
}
return true;
}
// Versions of standard library APIs that support UTF-8 strings.
namespace utf8 {
FILE* fopen(const char* name, const char* mode) {
std::wstring name_utf16;
if (!UTF8PathToWindowsLongPath(name, &name_utf16)) {
return nullptr;
}
std::wstring mode_utf16;
if (!UTF8ToWide(mode, &mode_utf16)) {
return nullptr;
}
return _wfopen(name_utf16.c_str(), mode_utf16.c_str());
}
int mkdir(const char* name, mode_t mode) {
std::wstring name_utf16;
if (!UTF8PathToWindowsLongPath(name, &name_utf16)) {
return -1;
}
return _wmkdir(name_utf16.c_str());
}
int open(const char* name, int flags, ...) {
std::wstring name_utf16;
if (!UTF8ToWide(name, &name_utf16)) {
if (!UTF8PathToWindowsLongPath(name, &name_utf16)) {
return -1;
}
@ -175,7 +223,7 @@ int open(const char* name, int flags, ...) {
int unlink(const char* name) {
std::wstring name_utf16;
if (!UTF8ToWide(name, &name_utf16)) {
if (!UTF8PathToWindowsLongPath(name, &name_utf16)) {
return -1;
}

View File

@ -18,7 +18,12 @@
#include <gtest/gtest.h>
#include <fcntl.h>
#include <stdlib.h>
#include "android-base/macros.h"
#include "android-base/test_utils.h"
#include "android-base/unique_fd.h"
namespace android {
namespace base {
@ -408,5 +413,76 @@ TEST(SysStrings, SysUTF8ToWide) {
EXPECT_EQ(expected_null, SysUTF8ToWide(utf8_null));
}
TEST(UTF8PathToWindowsLongPathTest, DontAddPrefixIfShorterThanMaxPath) {
std::string utf8 = "c:\\mypath\\myfile.txt";
std::wstring wide;
EXPECT_TRUE(UTF8PathToWindowsLongPath(utf8.c_str(), &wide));
EXPECT_EQ(std::string::npos, wide.find(LR"(\\?\)"));
}
TEST(UTF8PathToWindowsLongPathTest, AddPrefixIfLongerThanMaxPath) {
std::string utf8 = "c:\\mypath";
while (utf8.length() < 300 /* MAX_PATH is 260 */) {
utf8 += "\\mypathsegment";
}
std::wstring wide;
EXPECT_TRUE(UTF8PathToWindowsLongPath(utf8.c_str(), &wide));
EXPECT_EQ(0U, wide.find(LR"(\\?\)"));
EXPECT_EQ(std::string::npos, wide.find(L"/"));
}
TEST(UTF8PathToWindowsLongPathTest, AddPrefixAndFixSeparatorsIfLongerThanMaxPath) {
std::string utf8 = "c:/mypath";
while (utf8.length() < 300 /* MAX_PATH is 260 */) {
utf8 += "/mypathsegment";
}
std::wstring wide;
EXPECT_TRUE(UTF8PathToWindowsLongPath(utf8.c_str(), &wide));
EXPECT_EQ(0U, wide.find(LR"(\\?\)"));
EXPECT_EQ(std::string::npos, wide.find(L"/"));
}
namespace utf8 {
TEST(Utf8FilesTest, CanCreateOpenAndDeleteFileWithLongPath) {
TemporaryDir td;
// Create long directory path
std::string utf8 = td.path;
while (utf8.length() < 300 /* MAX_PATH is 260 */) {
utf8 += "\\mypathsegment";
EXPECT_EQ(0, mkdir(utf8.c_str(), 0));
}
// Create file
utf8 += "\\test-file.bin";
int flags = O_WRONLY | O_CREAT | O_TRUNC | O_BINARY;
int mode = 0666;
android::base::unique_fd fd(open(utf8.c_str(), flags, mode));
EXPECT_NE(-1, fd.get());
// Close file
fd.reset();
EXPECT_EQ(-1, fd.get());
// Open file with fopen
FILE* file = fopen(utf8.c_str(), "rb");
EXPECT_NE(nullptr, file);
if (file) {
fclose(file);
}
// Delete file
EXPECT_EQ(0, unlink(utf8.c_str()));
}
} // namespace utf8
} // namespace base
} // namespace android