Windows 上的路径长度限制



在测试 Windows 版 Rtools 的开发版本时,我遇到了几个 CRAN 包的奇怪故障,其中 R 无法找到、读取或写入某些文件。这些文件应该位于自动删除的临时目录中,因此花了一些功夫才发现它们实际上存在并且可以访问。起初这没有任何意义,但最终我得到了这个输出

Warning in gzfile(file, "wb") :
  cannot open compressed file 'C:\msys64\home\tomas\ucrt3\svn\ucrt3\r_packages\pkgcheck\CRAN\ADAPTS\tmp\RtmpKWYapj/gList.Mast.cells_T.cells.follicular.helper_T.cells.CD4.memory.activated_T.cells.CD4.memory.resting_T.cells.CD4.naive_T.cells.C_Plasma.cells_B.cells.memory_B.cells.naive.RData.RData', probable reason 'No such file or directory'
Error in gzfile(file, "wb") : cannot open the connection
Calls: remakeLM22p -> save -> gzfile
Execution halted 

这么长的文件名。警告消息中的整个路径占用了 265 个字节。也许它太长了,并且由于某种原因,它可以创建但无法以特定方式读取?

为了证实这一理论,我创建了一个映射驱动器来摆脱路径的前缀 /msys64/home/tomas/ucrt3/svn/ucrt3/r_packages/pkgcheck。这个包和其他几个包开始通过它们的检查。有趣的是,连接点并没有很好地工作,因为在某些情况下路径规范化会遵循它,再次获得长路径。R 已得到改进,并且更有可能提供提示(警告或错误)表明路径太长,因此诊断问题通常比这更容易,但消息也可能过于悲观(更多内容见下文)。

本文提供了一些关于路径长度限制的背景知识,并就如何解决这些限制提供了建议。它报告了 R 的最新改进,这些改进允许 R 和包在最近的 Windows 10 上使用更长的路径,在这些地方和时间可以覆盖系统限制。

根据 R 中的更改,一些 R 包也必须更新才能使用长路径。建议主要使用代码中 PATH_MAX 甚至 MAX_PATH 的包的作者继续阅读。R 中的更改使包的更新成为可能(它们可以进行测试),但也变得更加重要(它们在看到长路径时可能会崩溃)。因此,不建议在生产系统上启用长路径,但需要将此功能视为 R 生态系统中的实验功能。

背景

在 Windows 中,操作系统曾经对整个路径名称的长度施加限制。它源自常量 MAX_PATH(260,不太可能更改),并限制了包括终止符在内的 UCS-2(16 位,因此仅限于 BMP 字符)字词的数量。根据 API,它可能直接应用于 ANSI 函数中接受的作为路径名称的字节数,例如 259 个 UTF-8 字节加上 1 个字节的终止符。但是,它也可能仅在转换为 UCS-2 后应用一次,然后 259 个带 2 字节终止符的 BMP 字符可能对应于 3*259 个带 1 字节终止符的 UTF-8 字节。

然而,在 Windows 上,更长的路径名称已经存在了很长一段时间。通常使用的文件系统(NTFS)允许这样做。Windows API 开始在一些函数中支持所谓的扩展长度路径语法(\\?\D:\long_path),即使实际上它并没有被广泛使用,但它允许克服该限制。此外,在对应用程序看似安全的情况下,Windows API 开始接受更长的路径名称,即使使用常规语法,主要是在 Unicode 函数变体中。

因此,虽然一些 Windows 应用程序在编写时假设没有路径可以长于 MAX_PATH,但实际上此类路径可能存在并且确实存在。为什么做出此假设的旧应用程序似乎仍然(大部分)可以正常工作呢?

诀窍在于 Windows 在被认为可能造成麻烦的 API 中向旧应用程序隐藏了长路径,这通常意味着将路径返回给应用程序的 API。其想法是长路径本来就很少见,而且用户不太可能尝试在旧应用程序中使用它们。

一旦某个应用程序更新为使用长路径,它可以通过在其清单(因此在构建时在 .EXE 文件中)中声明它支持长路径来选择查看它们。此外,这需要在系统范围内允许。它自较新的 Windows 10 起受支持,并且默认情况下未启用。

Windows 强制的当前路径长度限制约为 32,767 个 UCS-2 字词。不存在确切的单一限制(文档称其为近似值,并且取决于内部扩展),此外还有前面描述的由于编码和 ANSI 与 Unicode 函数而导致的不确定性。

R 在 Windows 上使用 MinGW-W64,它将 PATH_MAX 定义为与 MAX_PATH 相同的值,即 260,以帮助编译最初为 POSIX 系统编写的代码。这些宏具有相似的含义,但细节不同。

建议对 POSIX 中确切措辞感兴趣的读者查看规范。过去,我没有尝试找出这是否是正确的解释,但如今 PATH_MAX 并不是系统中可能存在的整个路径长度的限制。如果定义了 PATH_MAX,那么它是不允许指定缓冲区大小的函数将存储到用户缓冲区中的最大字节数。此类调用如今很少见(例如,R 使用 realpath),并且 PATH_MAX 会在文档中明确提及。此外,如果定义了 PATH_MAX 并且操作系统限制路径长度,那么它不能将路径长度限制为小于 PATH_MAX 的数字。但是,操作系统可能接受更长的路径,并且可能存在更长的路径。

此外,限制可能因文件系统而异。在 Unix 上,所有文件系统都挂载到同一棵树上,因此限制基本上可能取决于路径。如果确实如此,则 PATH_MAX 应未定义,而用户可以使用 pathconf(或 fpathconf)来查找特定路径的限制。同样,可能未给出限制。此外,可能给出分配过大的限制。某些应用程序倾向于在未定义 PATH_MAX 时将其定义为某个较大的常量,这可能会使代码审查变得复杂(从本质上讲,它随后成为应用程序施加的限制)。

PATH_MAX 的实际值未由标准定义,并且在不同系统上有所不同,在 Linux 上的常见值为 4096,在 macOS 上的常见值为 1024。

总之,在整个路径名称长度上没有(确切地、始终地、在编译时)已知的限制,无论是在 Windows 上还是在 Unix(POSIX)上。施加的实际限制肯定因 R 运行的主要平台(Linux、macOS、Windows)而异,甚至在单台计算机上也可能存在一些差异(在 Windows 上肯定存在,在 Unix POSIX 上允许)。

声明长路径感知

为了让 R 和软件包在 Windows 上处理长度超过 260 个字符的路径(当系统允许时),R 需要具备长路径感知并向 Windows 声明这一点。例如,Python 已经这么做了,并且 Python 安装程序会提供在系统中启用长路径的功能。

要声明它,在所有 R 前端的清单(Rterm.exeRgui.exe 等)中将 longPathAware 设置为 true,即在 R 选择 UTF-8 作为系统和本机编码的相同位置。在进程级别执行此操作意味着嵌入 R 的应用程序也必须执行此操作才能获得支持。一旦 R 执行此操作(R-devel 已执行),它使用的包和库也将接收长路径,因此,它们应该支持长路径,但在未经测试的情况下几乎不可能。

为了解决先有鸡还是先有蛋的问题,Windows 提供了长路径的系统设置。默认情况下,此设置仍然处于禁用状态。热衷的用户可以启用它,真正需要它来满足特定应用程序的人员可以选择承担在选定的包/库中遇到问题的风险,以及这些包的开发人员,他们几乎不可能在无法测试和调试的情况下使其支持长路径。该设置位于注册表中,在 [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem] 下,字段 LongPathsEnabled。它还可以通过组策略(“启用 Win32 长路径”)进行控制。

确保支持长路径

使应用程序支持长路径的关键部分是重写代码,而不假设整个路径名的长度固定。这种假设可能导致为路径静态分配缓冲区,对系统函数的返回值进行有限检查,在构造路径名时对缓冲区溢出进行有限检查,以及现在可能无助于检查用户提供的路径的有效性(打印有关路径过长的警告/错误)。

在我看来,在所有代码中摆脱这一假设是有意义的,而不仅仅是在特定于 Windows 的部分中。这种重写的明显结果是代码将永远或几乎从不使用 PATH_MAXMAX_PATH 宏。

此外,在 Windows 上,即使 API 允许,也有一些系统函数和组件不支持长路径。有必要找到它们并用现代 API 替换它们。并非总是记录限制,因此我们只能进行测试。

这也可能是一个自然的机会,可以通过更新的 Windows API 函数来替换对已弃用 Windows API 函数的调用,即使旧函数支持长路径,因为无论如何都需要重写代码。此更改带来的代码复杂性增加可能需要本地重构。

确定所需的缓冲区大小

大多数返回路径的 Windows API 函数都接受指向用户缓冲区和缓冲区大小的指针。当大小足够时,它们会填充缓冲区并返回已使用的字节数(不包括终止符)。当缓冲区大小太小时,它们会返回所需的字节数(包括终止符)。函数的 Unicode 版本执行相同操作,但单位是 UCS-2(16 位)字。因此,可以调用该函数两次,第一次找出所需的缓冲区大小,第二次使用足够大的缓冲区。

这样的旧代码(不包括错误处理)

char cwd[MAX_PATH];
GetCurrentDirectory(MAX_PATH, cwd);

可以更改为

char *cwd;
DWORD res = GetCurrentDirectory(0, NULL);
cwd = (char *)malloc(res);
GetCurrentDirectory(res, cwd);

可以通过在第一次调用期间使用非空缓冲区来尝试优化代码,以便在“大多数情况下”只需调用一次 GetCurrentDirectory 就足够了。缺点是代码复杂性增加和测试复杂:较长的路径很少见,因此很少测试代码路径。初始大小甚至可以是 MAX_PATH

虽然错误处理从示例中排除,但调用该函数两次会带来(理论上,但仍然)外部条件可能在两者之间发生变化的风险,在这种情况下,另一个线程可以将进程的当前工作目录更改为需要更长缓冲区的值,因此从理论上讲,即使第二次调用也可能由于缓冲区大小不足而失败。

在检查此类函数的返回值时需要小心,因为语义可能略有不同。某些函数返回所需的缓冲区大小不带终止符,例如 DragQueryFile。这与例如 C snprintf 函数的行为相匹配。

某些 Windows API 调用已经返回动态分配的结果,例如

wchar_t *mydocs;
SHGetKnownFolderPath(&FOLDERID_Documents, KF_FLAG_CREATE, NULL, &mydocs);
// copy mydocs
CoTaskMemFree(mydocs);

找到结果长度很容易(例如 wcslen())。可以按照首选方式为结果分配一个缓冲区,复制它,然后使用特定 API 函数的文档中描述的正确 free 函数释放原始数据(稍后将讨论分配)。

有一些 Windows API 调用不返回所需的缓冲区大小,而只返回一个错误信号,表明提供的缓冲区不够大。然后需要多次调用该函数,同时增加缓冲区大小。此示例适用于 GetModuleFileName

 DWORD size = 1;
 char *buf = NULL;
    
 for(;;) {
     buf = (char *)malloc(size);
     if (!buf)
        return NULL;
     DWORD res = GetModuleFileName(NULL, buf, size);
     if (res > 0 && res < size) /* success */
         break;
     free(buf);
     if (res != size) /* error */
         return NULL;
     size *= 2; /* try again with 2x larger buffer */
 }

POSIX getcwd() 函数是另一个示例,其中需要进行迭代以找出所需的缓冲区大小,即使某些扩展允许返回动态分配的结果。

在所有此类情况下,迭代都不是一个合适的解决方案。例如,GetOpenFileName 函数打开一个对话框,要求用户选择要打开的文件。调用方为文件名和大小提供一个缓冲区。如果缓冲区太小,该函数会报告错误。没错,应用程序可以增加缓冲区大小,再次打开对话框,并再次要求用户做出选择。这不太实用,对于大多数用途,使用硬编码的大限制可能更好。用户通常愿意手动选择多长的路径可能也有一个限制。

动态分配

虽然对路径使用动态分配是自然而然的,因为它们的长度没有有用的上限,但在之前没有使用动态分配的地方引入动态分配时必须小心。

使用 malloc() 需要检查内存分配失败并决定在发生这种情况时该怎么做:将其映射到函数返回的错误代码,或抛出 R 错误。抛出 R 错误需要额外的注意:如果这在以前不可能的函数中引入了可能的 R 错误(因此在任何调用点),它也可能引入资源泄漏(例如,一些打开的文件或其他未安排在长跳转中释放的动态分配对象)。如果在 malloc()free() 调用之间有任何对 R API 的调用,则存在长跳转的风险,因此应安排释放 malloc() 分配的缓冲区。有 API 可以做到这一点,无论是在 R 内部还是对包公开,但处理所有情况可能很繁琐。

引入 malloc() 的另一个问题是由调用者释放内存。如果函数以前返回指向静态分配缓冲区的指针,而我们将其更改为返回由 malloc() 分配的内存,则调用者必须知道如何释放它,并且必须能够访问正确的匹配函数来释放它。这仅对于很少使用或内部函数才容易实现。

在 Windows 上以这种方式更改的函数的一个示例是 getRUser()。它现在返回应由 R 前端和嵌入式应用程序使用 freeRUser() 函数释放的内存。较旧的应用程序不知道如何释放内存,因为以前使用的是静态分配的缓冲区,但此函数通常仅在 R 启动期间调用一次,因此泄漏不是问题。在启动代码中选择 malloc(),因为 R 堆尚不可用。

然而,在典型的包代码中以及通常在基本 R 自身中,当 R 已经在运行时,使用 R_alloc 比使用 malloc 来创建临时缓冲区更加容易。在这些情况下引入 R_alloc 通常不需要修改调用者:内存会在 .Call/.External 结束时自动释放,或者可以通过基于堆栈的方式由 vmaxset/vmaxget 显式管理。当存在在清理之前函数被修改大量调用的风险时,必须小心。此外,在使用缓冲区之前,不得使用 vmaxset 进行不必要的清理。

R_alloc 引入了 R 堆的分配,这意味着可能还会进行垃圾回收。因此,必须小心是否可以安全地引入此项操作,是否不会引入 PROTECT 错误。从理论上讲,R_alloc 还引入了可能的 long-jump,因为存在潜在的分配错误。然而,由 R_alloc 分配的内存会在 long jump 时得到清理(分配堆栈深度在相应上下文中得到恢复),因此无需担心内存泄漏。

在基本 R 中,对 Windows API 的调用大部分已被重写为动态分配,在启动代码中使用 malloc,在其他地方使用 R_alloc。尽管有上述讨论,但决定使用哪个函数来分配内存并不总是困难的:通常已经使用了 R_alloc,因此没有新引入。但仍然存在一些静态分配。

静态分配

在某些情况下,更改现有代码以进行路径的动态分配可能仍然显得过于繁琐或过于侵入。在某些情况下,至少作为临时解决方案,放弃对任意长路径的支持可能会更容易,而采用应用特定的限制(在 Windows 上远大于 260 字节)。仍然有必要处理在假设任何现有路径的长度限制的代码中未处理的事情。

与动态分配相比,无需担心引入垃圾回收(PROTECT 错误)和资源泄漏(客户端未释放内存)。但是,仍然存在引入错误路径的问题,因此存在潜在的资源泄漏。

与动态分配不同,需要仔细防止缓冲区溢出,并在出现过长路径时(例如从连接中)检测到。需要将其报告为错误,而不是破坏内存或静默截断。此外,当操作系统 API 引入一个而应用程序引入另一个时,由于必须处理多个路径长度限制,代码可能会变得复杂。

在基本 R 中,静态分配仍然用于少数广泛调用的实用程序函数(其中更改/审查调用者过于困难),对于合并的外部代码(其中更改会使维护复杂化),无论如何都找不到缓冲区大小,以及在 Unix 上也使用的一些代码,其中 PATH_MAX 通常足够大,因此不会造成麻烦。

应避免使用的函数

必须重写一些代码以使用不同的 API 来支持长路径。这里仅给出几个示例来说明问题。

一个旧的 POSIX 函数 getwd()(已于 2008 年从标准中移除)不允许指定用户缓冲区的大小。缓冲区至少需要 PATH_MAX 大小,如果路径长度大于 PATH_MAX,则该函数将返回一个错误。另一个示例是 realpath。这些函数的旧形式在设计上是错误的,因为在当前 POSIX 中,甚至可能未定义 PATH_MAX,或者可能是一个太大而无法分配该大小的缓冲区的数字,等等。不过,此类函数在 Unix 和 Windows 上都很少见。

不幸的是,即使语义允许支持长路径的调用有时也不支持 Windows 上的长路径。

例如,为了找到“文档”文件夹,R 以前使用 SHGetSpecialFolderLocation/SHGetPathFromIDList,但为了支持长路径,已将其更改为 SHGetKnownFolderPath,因为 SHGetPathFromIDList 不支持长路径。这说明,即使 API 已返回动态分配的结果,有时也会存在此类限制。

GetFullPathNameA(ANSI 版本)不适用于长路径,但 GetFullPathNameW 适用。因此,需要用转换和对 Unicode 版本的调用来替换对 ANSI 版本的调用。这没有多大意义,因为 ANSI 版本应该只执行此操作,并且因为 API 允许支持长路径,因为接受缓冲区大小并发出真实大小信号。不过至少它是有记录的。

许多 API 函数记录了 ANSI 版本的限制,并引用 Unicode 版本来克服该限制,但鉴于对 UTF-8 的新支持和使用 ANSI 函数的建议,这似乎令人惊讶(或可能已过时)。通常,ANSI 函数碰巧适用于长路径(在选择加入时)。例如,GetShortPathName 适用,而有记录表明它在 ANSI 版本中也有此限制。

用于选择目录的旧对话框 SHBrowserForFolder 不支持长路径(它在 Rgui 中使用),并且必须用 IFileOpenDialog 替换,这需要几行以上的代码。

目录遍历

R 在内部使用 POSIX opendir/readdir/closedir 函数来列出目录中的文件。这些函数在 Windows 上不可直接使用,但 R 一直在使用 MinGW-W64 实现,包括 ANSI 和 Unicode 变体。

这里应该说一下,单个文件的长度也有限制。幸运的是,此限制在 R 运行的所有系统上都差不多,并且没有改变(至少最近没有改变)。因此,这些函数静态分配单个文件名(d_name)并不是一个问题。

但是,MinGW-W64(版本 10)对这些函数的实现对静态分配的 PATH_MAX 字符缓冲区使用 GetFullPathName;它们在用于启动搜索的输入路径上使用它。因此,R 现在重新实现了 opendir/readdir/closedir 功能的一个子集,该子集支持长路径。

目录遍历函数还必须重新编制,以不假设系统中可能存在的完整路径的限制。此类函数在内部需要不断追加目录名称来构建当前访问的路径。这以前使用静态分配的缓冲区,但现在使用动态分配的字符串缓冲区,该缓冲区在需要时会自动扩展。

返回值检查

一个示例说明了审查旧代码的必要性,旧代码假设没有路径可以比MAX_PATH长,该示例来自Sys.which的实现

int iexts = 0;
const char *exts[] = { ".exe" , ".com" , ".cmd" , ".bat" , NULL };
while (exts[iexts]) {
  strcpy(dest, exts[iexts]);  // modifies fl
  if ((d = SearchPath(NULL, fl, NULL, MAX_PATH, fn, &f))) break;
  iexts++ ;
}

该循环尝试使用不同的后缀在 PATH 上查找可执行文件。将SearchPath的非零退出值视为成功。该函数在出错时返回零。它返回大于nBufferLength(收到 MAX_PATH 的值)的值,以指示缓冲区不够大,但旧代码中没有检查该值,因为它被认为是不可能的。

因此,当 PATH 上有一个非常长的路径时(比如在开头),Sys.which()对于实际上在 PATH 上的文件会失败。它不会在 R 4.2.2 及更早版本中失败,因为 Windows 会向 R 隐藏此类长路径组件,SearchPath会跳过它。但它会在启用了长路径的系统上的 R-devel 中失败。

路径长度检查

鉴于系统中已知整个路径长度没有限制,因此预防性检查是否有意义值得怀疑,尤其是在 Windows 上有MAX_PATH限制的情况下。确实,除非在系统中启用了长路径,否则即使是 R-devel 也容易受到此限制,但如前所述,只有某些函数在某些情况下容易受到此限制,其他一些函数可以正常工作。因此,如果应用程序决定施加自己的限制,则错误可能是过早的,而警告可能会令人困惑。当然,如果应用程序决定施加自己的限制,则检查是有意义的:需要保护输入中的静态缓冲区以免溢出。

限制

Windows 中的长路径支持仅在 Windows 10 自 1607 版本(2016 年发布)中可用。在较旧的系统上,R 仍受MAX_PATH限制。

Windows 应用程序(“Win32”)无法在当前目录为长路径的情况下启动,即使启用了长路径支持。这极大地限制了长路径的潜在用途。在 R 包开发中,在检查或构建包时很容易遇到这种情况,而这反过来又经常执行外部命令。这也意味着测试长路径支持很困难。

某些 Windows 组件仍不支持长路径。希望这种情况会随着时间的推移而改变,但距离该功能发布已经过去 6 年多了。例如,无法将文档打印到具有长路径的文件中 - 我在使用长路径测试 Rgui 的不同函数时遇到了这种情况,并且没有找到替代的 API。毕竟,我尝试过的几个 Windows 应用程序都有相同的限制。

不可避免地,许多现有应用程序不支持长路径,有些可能与 R 一起使用,因此 R 支持它们也无济于事。

如前所述,基本 R 中的功能应被视为实验性的,特别是由于尚未更新包。虽然似乎只有 100 个 CRAN 和 Bioconductor 包在代码中使用 PATH_MAX(或 MAX_PATH)常量,但尚不清楚有多少包会受到不良影响。由于从具有长名称的路径执行存在限制,因此无法轻松地对所有 CRAN/Bioconductor 包“运行检查”来测试这一点。因此,包中对长路径支持和测试的级别将主要留给手动工作。

建议

我根据阅读有关此问题的内容并在基本 R 中实现长路径支持来提供建议。

解决方法

在已发布的 R 版本上使用 R 包时遇到长路径问题时,理想情况下,用户应首先检查该包是否允许影响路径长度:是否可以告知它在哪里创建文件或如何命名文件。

如果不是,或者如果已经是最小或默认值,那么值得尝试使用驱动器映射(subst 命令)来摆脱任何目录前缀。毕竟,该包的作者可能在某个目录中对其进行了测试,可能没有启用长路径,因此这应该创建一个限制不那么大的设置。

最后,如果这没有帮助,请尝试确保为涉及的驱动器和目录启用了 8.3 名称(为了混淆问题,它们有时也称为“短名称”)(请参阅 dir /x 命令,fsutil file setshortname)。尝试让该包使用 8.3 名称变体;甚至可以手动设置它们,从而进一步影响它们的长度。让该包使用它们有多困难取决于具体情况:它可能自动发生,它可能通过以简短形式将它们指定给包函数来工作,或者当该包故意规范路径或以其他方式扩展短名称时,它可能根本不起作用。

使用相当短的名称

实践中的路径长度是一种共享资源,路径的不同组件由不同的实体和软件命名。在我的示例中,msys64\home 来自 Msys2 约定,tomas 是我的用户名,ucrt3\svn 是我在系统 ucrt3\r_packages 上的本地决策,是颠倒存储库的结构,pkgcheck\CRAN 是包检查脚本中的设计决策(CRAN 实际上是包存储库的名称),ADAPTS 是包的名称,RtmpKWYapj 由 R 命名(一个临时会话目录),最后

gList.Mast.cells_T.cells.follicular.helper_T.cells.CD4.memory.activated_T.cells.CD4.memory.resting_T.cells.CD4.naive_T.cells.C_Plasma.cells_B.cells.memory_B.cells.naive.RData.RData

是由包创建的名称。

路径长度作为共享资源,负责方应选择合理的浅层嵌套级别,尤其是合理短的组件名称、文件和目录名称。此示例是一个极端情况,其中文件名显然占据了太多空间。文件名应与输入的大小保持一致。有人可能会认为我的前缀也有些太深了。

尽管 Windows 中有长路径支持和类似的努力,但在 Windows 上可靠地依赖超过 260 个字符的路径之前,“至少”还需要很长时间。因此,预防措施可能会在很长一段时间内仍然是解决方案的关键部分。

编写对任意长名称具有鲁棒性的代码

根据当前的标准和实现,对于当前系统中整个路径名的长度没有(已知的、合理的小的)限制。

至少,代码应在对路径名长度施加自己的限制时明确说明。它应具有对超过该长度的路径的鲁棒性:报告错误或可能跳过它们,但绝对不要让代码崩溃或静默截断。任何自我施加的限制理想情况下至少应为 Linux 上当前的 PATH_MAX(4096)。

不过,在大多数情况下,使用动态分配并支持任意长名称的路径名似乎很自然。对于新代码,这可能是一个自然的解决方案。

使包支持长路径

首先查看代码中所有 MAX_PATHPATH_MAX 的用法是有意义的。这会识别出需要重写以支持长路径的位置。理想情况下,这些常量仅与明确依赖它们的 API 一起使用(例如 realpath,非常罕见)。当限制是由应用程序施加时,应将其替换为不同的常量以使其明确。

我建议修改代码,以便对短名称和长名称采用相同的代码路径。这样,代码将使用当前可用的测试和通过常规常规使用进行测试。仅在需要时才优化,这在文件系统操作中可能很少见,但并非不可能。理想情况下,在测试时应有一个开关来使用长路径,例如在迭代以查找所需的缓冲区大小时将初始大小设置为非常小的值。

测试对于发现任何剩余问题至关重要,包括所用库和 Windows 本身的限制。不能依赖文档。此外,即使代码尝试检查路径长度,在没有测试的情况下也很容易忽视问题。最初,在启用长路径时,我看到了很多 R 基础崩溃。

要检查 R 包,可以运行 R CMD check --output=DIR 以选择输出目录,从而避免从长目录运行。可以在短目录中启动 R,然后在有助于测试时将当前工作目录更改为长目录。现在应该能够从源代码和二进制版本将包安装到长目录中。

Msys2 中的 Bash 以及 cmd.exe 和 Powershell 都可以处理长目录。

总结

更新 R 以支持 Windows 上的长路径花费了一个多月的时间,在 70 个文件中更改了约 4300 行(添加或删除)。因此,投资相当大,并且存在引入错误的风险。特别欢迎有关 Windows 和 Unix 上文件系统操作行为可疑更改的错误报告,越早越好,以便在 4.3 版本发布之前修复这些错误。

一些特定于 Windows 的代码已在避免使用已弃用的函数的过程中得到更新,因此即使不管长路径,它们也可能有一些维护好处。