Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9ed76f0a authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

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

parents 3f4cea94 e3e7813e
Loading
Loading
Loading
Loading
+19 −0
Original line number Diff line number Diff line
@@ -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
+50 −2
Original line number Diff line number Diff line
@@ -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;
  }

+76 −0
Original line number Diff line number Diff line
@@ -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