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

Commit e3e7813e authored by Renaud Paquay's avatar Renaud Paquay
Browse files

Add (partial) support for Windows long paths

* Update android::base::utf8::open/unlink to support Windows long
  paths

* Add android::base::utf8::fopen, also with support for Windows long
  paths

* Upcoming CLs will add additional APIs to support additional use cases

Test: Added tests to utf8_test
Bug: 38268753
Change-Id: If72af327f3487766f5370a2f43ee9cabd4a8a810
parent 3f4cea94
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