Merge "Add (partial) support for Windows long paths"
am: 9ed76f0acc
Change-Id: Iaa5ea4cb9bc8c045ac33c014f1283f2ef1936e56
This commit is contained in:
commit
d82ff9fb87
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue