diff options
| author | Ron Yorston <rmy@pobox.com> | 2026-02-27 13:57:40 +0000 |
|---|---|---|
| committer | Ron Yorston <rmy@pobox.com> | 2026-02-27 14:27:40 +0000 |
| commit | ed525e2db10081f095d2fccc1b02830654470401 (patch) | |
| tree | 4467394a255c596be9c3573d8d0b5e4f3eb3dcea | |
| parent | 05219c1f97be1dc7a3f825a5a658a137d90b03a2 (diff) | |
| download | busybox-w32-long_paths.tar.gz busybox-w32-long_paths.tar.bz2 busybox-w32-long_paths.zip | |
win32: add (some) long path supportlong_paths
Use the longPathAware manifest entry to handle path lengths above
260. This requires the LongPathsEnabled registry key to be set
at run-time.
Modify (some) WIN32 API calls to use the wide-character variant.
These features are controlled by the build-time configuration
option FEATURE_LONG_PATHS, which is not enabled by default.
| -rw-r--r-- | Config.in | 9 | ||||
| -rw-r--r-- | libbb/appletlib.c | 3 | ||||
| -rw-r--r-- | win32/dirent.c | 74 | ||||
| -rw-r--r-- | win32/mingw.c | 25 | ||||
| -rw-r--r-- | win32/resources/resources.rc | 12 |
5 files changed, 109 insertions, 14 deletions
| @@ -494,6 +494,15 @@ config FEATURE_PNG_ALL | |||
| 494 | 494 | ||
| 495 | endchoice | 495 | endchoice |
| 496 | 496 | ||
| 497 | config FEATURE_LONG_PATHS | ||
| 498 | bool "Support for long paths" | ||
| 499 | default n | ||
| 500 | depends on FEATURE_RESOURCES | ||
| 501 | help | ||
| 502 | Allow paths longer than 260 characters. This is only supported | ||
| 503 | on Windows 10+. The registry key LongPathsEnabled must be set | ||
| 504 | to 1. | ||
| 505 | |||
| 497 | config FEATURE_EURO | 506 | config FEATURE_EURO |
| 498 | bool "Support the euro currency symbol" | 507 | bool "Support the euro currency symbol" |
| 499 | default y | 508 | default y |
diff --git a/libbb/appletlib.c b/libbb/appletlib.c index 481cc278e..12de2b610 100644 --- a/libbb/appletlib.c +++ b/libbb/appletlib.c | |||
| @@ -928,6 +928,9 @@ int busybox_main(int argc UNUSED_PARAM, char **argv) | |||
| 928 | # if ENABLE_FEATURE_UTF8_MANIFEST | 928 | # if ENABLE_FEATURE_UTF8_MANIFEST |
| 929 | full_write1_str("; Unicode"); | 929 | full_write1_str("; Unicode"); |
| 930 | # endif | 930 | # endif |
| 931 | # if ENABLE_FEATURE_LONG_PATHS | ||
| 932 | full_write1_str("; long paths"); | ||
| 933 | # endif | ||
| 931 | full_write1_str(")\n\n"); | 934 | full_write1_str(")\n\n"); |
| 932 | # else | 935 | # else |
| 933 | full_write1_str(" multi-call binary.\n"); /* reuse */ | 936 | full_write1_str(" multi-call binary.\n"); /* reuse */ |
diff --git a/win32/dirent.c b/win32/dirent.c index f0e8deae2..872f44fbc 100644 --- a/win32/dirent.c +++ b/win32/dirent.c | |||
| @@ -8,26 +8,44 @@ struct DIR { | |||
| 8 | int got_dotdot; | 8 | int got_dotdot; |
| 9 | }; | 9 | }; |
| 10 | 10 | ||
| 11 | static inline unsigned char get_dtype(DWORD attr, DWORD reserved0) | ||
| 12 | { | ||
| 13 | if ((attr & FILE_ATTRIBUTE_REPARSE_POINT) && | ||
| 14 | (reserved0 == IO_REPARSE_TAG_SYMLINK || | ||
| 15 | reserved0 == IO_REPARSE_TAG_MOUNT_POINT || | ||
| 16 | reserved0 == IO_REPARSE_TAG_APPEXECLINK)) | ||
| 17 | return DT_LNK; | ||
| 18 | if (attr & FILE_ATTRIBUTE_DIRECTORY) | ||
| 19 | return DT_DIR; | ||
| 20 | return DT_REG; | ||
| 21 | } | ||
| 22 | |||
| 23 | #if !ENABLE_FEATURE_LONG_PATHS | ||
| 11 | static inline void finddata2dirent(struct dirent *ent, WIN32_FIND_DATAA *fdata) | 24 | static inline void finddata2dirent(struct dirent *ent, WIN32_FIND_DATAA *fdata) |
| 12 | { | 25 | { |
| 13 | /* copy file name from WIN32_FIND_DATA to dirent */ | 26 | /* copy file name from WIN32_FIND_DATA to dirent */ |
| 14 | strcpy(ent->d_name, fdata->cFileName); | 27 | strcpy(ent->d_name, fdata->cFileName); |
| 15 | 28 | ent->d_type = get_dtype(fdata->dwFileAttributes, fdata->dwReserved0); | |
| 16 | if ((fdata->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) && | ||
| 17 | (fdata->dwReserved0 == IO_REPARSE_TAG_SYMLINK || | ||
| 18 | fdata->dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT || | ||
| 19 | fdata->dwReserved0 == IO_REPARSE_TAG_APPEXECLINK)) | ||
| 20 | ent->d_type = DT_LNK; | ||
| 21 | else if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) | ||
| 22 | ent->d_type = DT_DIR; | ||
| 23 | else | ||
| 24 | ent->d_type = DT_REG; | ||
| 25 | } | 29 | } |
| 30 | #else | ||
| 31 | static inline void finddata2dirent(struct dirent *ent, WIN32_FIND_DATAW *fdata) | ||
| 32 | { | ||
| 33 | /* copy file name from WIN32_FIND_DATAW to dirent */ | ||
| 34 | WideCharToMultiByte(CP_ACP, 0, fdata->cFileName, -1, | ||
| 35 | ent->d_name, PATH_MAX, NULL, NULL); | ||
| 36 | ent->d_type = get_dtype(fdata->dwFileAttributes, fdata->dwReserved0); | ||
| 37 | } | ||
| 38 | #endif | ||
| 26 | 39 | ||
| 27 | DIR *opendir(const char *name) | 40 | DIR *opendir(const char *name) |
| 28 | { | 41 | { |
| 42 | #if !ENABLE_FEATURE_LONG_PATHS | ||
| 29 | char pattern[MAX_PATH]; | 43 | char pattern[MAX_PATH]; |
| 30 | WIN32_FIND_DATAA fdata; | 44 | WIN32_FIND_DATAA fdata; |
| 45 | #else | ||
| 46 | wchar_t wpattern[32768]; | ||
| 47 | WIN32_FIND_DATAW fdata; | ||
| 48 | #endif | ||
| 31 | HANDLE h; | 49 | HANDLE h; |
| 32 | int len; | 50 | int len; |
| 33 | DIR *dir; | 51 | DIR *dir; |
| @@ -37,6 +55,8 @@ DIR *opendir(const char *name) | |||
| 37 | errno = EINVAL; | 55 | errno = EINVAL; |
| 38 | return NULL; | 56 | return NULL; |
| 39 | } | 57 | } |
| 58 | |||
| 59 | #if !ENABLE_FEATURE_LONG_PATHS | ||
| 40 | /* check that the pattern won't be too long for FindFirstFileA */ | 60 | /* check that the pattern won't be too long for FindFirstFileA */ |
| 41 | len = strlen(name); | 61 | len = strlen(name); |
| 42 | if (len + 2 >= MAX_PATH) { | 62 | if (len + 2 >= MAX_PATH) { |
| @@ -59,6 +79,30 @@ DIR *opendir(const char *name) | |||
| 59 | errno = (err == ERROR_DIRECTORY) ? ENOTDIR : err_win_to_posix(); | 79 | errno = (err == ERROR_DIRECTORY) ? ENOTDIR : err_win_to_posix(); |
| 60 | return NULL; | 80 | return NULL; |
| 61 | } | 81 | } |
| 82 | #else /* ENABLE_FEATURE_LONG_PATHS */ | ||
| 83 | /* Convert to wide string. CP_ACP is CP_UTF8 when the UTF-8 manifest | ||
| 84 | * is active, else the system ANSI code page. */ | ||
| 85 | len = MultiByteToWideChar(CP_ACP, 0, name, -1, wpattern, 32760); | ||
| 86 | if (len == 0) { | ||
| 87 | errno = EINVAL; | ||
| 88 | return NULL; | ||
| 89 | } | ||
| 90 | len--; /* exclude null terminator from length */ | ||
| 91 | |||
| 92 | /* append optional '\' and wildcard '*' */ | ||
| 93 | if (len && wpattern[len - 1] != L'\\' && wpattern[len - 1] != L'/') | ||
| 94 | wpattern[len++] = L'\\'; | ||
| 95 | wpattern[len++] = L'*'; | ||
| 96 | wpattern[len] = 0; | ||
| 97 | |||
| 98 | /* open find handle using wide API */ | ||
| 99 | h = FindFirstFileW(wpattern, &fdata); | ||
| 100 | if (h == INVALID_HANDLE_VALUE) { | ||
| 101 | DWORD err = GetLastError(); | ||
| 102 | errno = (err == ERROR_DIRECTORY) ? ENOTDIR : err_win_to_posix(); | ||
| 103 | return NULL; | ||
| 104 | } | ||
| 105 | #endif | ||
| 62 | 106 | ||
| 63 | /* initialize DIR structure and copy first dir entry */ | 107 | /* initialize DIR structure and copy first dir entry */ |
| 64 | dir = xzalloc(sizeof(DIR)); | 108 | dir = xzalloc(sizeof(DIR)); |
| @@ -80,8 +124,14 @@ struct dirent *readdir(DIR *dir) | |||
| 80 | /* if first entry, dirent has already been set up by opendir */ | 124 | /* if first entry, dirent has already been set up by opendir */ |
| 81 | if (dir->not_first) { | 125 | if (dir->not_first) { |
| 82 | /* get next entry and convert from WIN32_FIND_DATA to dirent */ | 126 | /* get next entry and convert from WIN32_FIND_DATA to dirent */ |
| 127 | #if !ENABLE_FEATURE_LONG_PATHS | ||
| 83 | WIN32_FIND_DATAA fdata; | 128 | WIN32_FIND_DATAA fdata; |
| 84 | if (FindNextFileA(dir->dd_handle, &fdata)) { | 129 | if (FindNextFileA(dir->dd_handle, &fdata)) |
| 130 | #else | ||
| 131 | WIN32_FIND_DATAW fdata; | ||
| 132 | if (FindNextFileW(dir->dd_handle, &fdata)) | ||
| 133 | #endif | ||
| 134 | { | ||
| 85 | finddata2dirent(&dir->dd_dir, &fdata); | 135 | finddata2dirent(&dir->dd_dir, &fdata); |
| 86 | } else if (!dir->got_dot) { | 136 | } else if (!dir->got_dot) { |
| 87 | strcpy(dir->dd_dir.d_name, "."); | 137 | strcpy(dir->dd_dir.d_name, "."); |
diff --git a/win32/mingw.c b/win32/mingw.c index 122337d19..0c28d87fe 100644 --- a/win32/mingw.c +++ b/win32/mingw.c | |||
| @@ -396,6 +396,9 @@ static inline mode_t file_attr_to_st_mode(DWORD attr) | |||
| 396 | 396 | ||
| 397 | static int get_file_attr(const char *fname, WIN32_FILE_ATTRIBUTE_DATA *fdata) | 397 | static int get_file_attr(const char *fname, WIN32_FILE_ATTRIBUTE_DATA *fdata) |
| 398 | { | 398 | { |
| 399 | #if ENABLE_FEATURE_LONG_PATHS | ||
| 400 | wchar_t wpath[32768]; | ||
| 401 | #endif | ||
| 399 | char *want_dir; | 402 | char *want_dir; |
| 400 | int dev = get_dev_type(fname); | 403 | int dev = get_dev_type(fname); |
| 401 | 404 | ||
| @@ -412,7 +415,18 @@ static int get_file_attr(const char *fname, WIN32_FILE_ATTRIBUTE_DATA *fdata) | |||
| 412 | } | 415 | } |
| 413 | 416 | ||
| 414 | want_dir = last_char_is_dir_sep(fname); | 417 | want_dir = last_char_is_dir_sep(fname); |
| 415 | if (GetFileAttributesExA(fname, GetFileExInfoStandard, fdata)) { | 418 | |
| 419 | #if !ENABLE_FEATURE_LONG_PATHS | ||
| 420 | if (GetFileAttributesExA(fname, GetFileExInfoStandard, fdata)) | ||
| 421 | #else | ||
| 422 | /* Convert to wide string for long path support. CP_ACP is CP_UTF8 | ||
| 423 | * when the UTF-8 manifest is active, else the system ANSI code page. */ | ||
| 424 | if (MultiByteToWideChar(CP_ACP, 0, fname, -1, wpath, 32768) == 0) | ||
| 425 | return EINVAL; | ||
| 426 | |||
| 427 | if (GetFileAttributesExW(wpath, GetFileExInfoStandard, fdata)) | ||
| 428 | #endif | ||
| 429 | { | ||
| 416 | if (!(fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && want_dir) | 430 | if (!(fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && want_dir) |
| 417 | return ENOTDIR; | 431 | return ENOTDIR; |
| 418 | fdata->dwFileAttributes &= ~FILE_ATTRIBUTE_DEVICE; | 432 | fdata->dwFileAttributes &= ~FILE_ATTRIBUTE_DEVICE; |
| @@ -421,9 +435,16 @@ static int get_file_attr(const char *fname, WIN32_FILE_ATTRIBUTE_DATA *fdata) | |||
| 421 | 435 | ||
| 422 | if (GetLastError() == ERROR_SHARING_VIOLATION) { | 436 | if (GetLastError() == ERROR_SHARING_VIOLATION) { |
| 423 | HANDLE hnd; | 437 | HANDLE hnd; |
| 438 | #if !ENABLE_FEATURE_LONG_PATHS | ||
| 424 | WIN32_FIND_DATA fd; | 439 | WIN32_FIND_DATA fd; |
| 425 | 440 | ||
| 426 | if ((hnd=FindFirstFile(fname, &fd)) != INVALID_HANDLE_VALUE) { | 441 | if ((hnd=FindFirstFile(fname, &fd)) != INVALID_HANDLE_VALUE) |
| 442 | #else | ||
| 443 | WIN32_FIND_DATAW fd; | ||
| 444 | |||
| 445 | if ((hnd=FindFirstFileW(wpath, &fd)) != INVALID_HANDLE_VALUE) | ||
| 446 | #endif | ||
| 447 | { | ||
| 427 | fdata->dwFileAttributes = | 448 | fdata->dwFileAttributes = |
| 428 | fd.dwFileAttributes & ~FILE_ATTRIBUTE_DEVICE; | 449 | fd.dwFileAttributes & ~FILE_ATTRIBUTE_DEVICE; |
| 429 | fdata->ftCreationTime = fd.ftCreationTime; | 450 | fdata->ftCreationTime = fd.ftCreationTime; |
diff --git a/win32/resources/resources.rc b/win32/resources/resources.rc index b76f1af23..50642831c 100644 --- a/win32/resources/resources.rc +++ b/win32/resources/resources.rc | |||
| @@ -52,6 +52,9 @@ BEGIN | |||
| 52 | "<asmv3:application>" | 52 | "<asmv3:application>" |
| 53 | "<asmv3:windowsSettings>" | 53 | "<asmv3:windowsSettings>" |
| 54 | "<activeCodePage xmlns=""http://schemas.microsoft.com/SMI/2019/WindowsSettings"">UTF-8</activeCodePage>" | 54 | "<activeCodePage xmlns=""http://schemas.microsoft.com/SMI/2019/WindowsSettings"">UTF-8</activeCodePage>" |
| 55 | # if ENABLE_FEATURE_LONG_PATHS | ||
| 56 | "<longPathAware xmlns=""http://schemas.microsoft.com/SMI/2016/WindowsSettings"">true</longPathAware>" | ||
| 57 | # endif | ||
| 55 | "</asmv3:windowsSettings>" | 58 | "</asmv3:windowsSettings>" |
| 56 | "</asmv3:application>" | 59 | "</asmv3:application>" |
| 57 | "<trustInfo xmlns=""urn:schemas-microsoft-com:asm.v3"">" | 60 | "<trustInfo xmlns=""urn:schemas-microsoft-com:asm.v3"">" |
| @@ -78,7 +81,16 @@ END | |||
| 78 | 1 24 | 81 | 1 24 |
| 79 | BEGIN | 82 | BEGIN |
| 80 | "<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?>" | 83 | "<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?>" |
| 84 | # if !ENABLE_FEATURE_LONG_PATHS | ||
| 81 | "<assembly xmlns=""urn:schemas-microsoft-com:asm.v1"" manifestVersion=""1.0"">" | 85 | "<assembly xmlns=""urn:schemas-microsoft-com:asm.v1"" manifestVersion=""1.0"">" |
| 86 | # else | ||
| 87 | "<assembly xmlns=""urn:schemas-microsoft-com:asm.v1"" xmlns:asmv3=""urn:schemas-microsoft-com:asm.v3"" manifestVersion=""1.0"">" | ||
| 88 | "<asmv3:application>" | ||
| 89 | "<asmv3:windowsSettings>" | ||
| 90 | "<longPathAware xmlns=""http://schemas.microsoft.com/SMI/2016/WindowsSettings"">true</longPathAware>" | ||
| 91 | "</asmv3:windowsSettings>" | ||
| 92 | "</asmv3:application>" | ||
| 93 | # endif | ||
| 82 | "<trustInfo xmlns=""urn:schemas-microsoft-com:asm.v3"">" | 94 | "<trustInfo xmlns=""urn:schemas-microsoft-com:asm.v3"">" |
| 83 | "<security>" | 95 | "<security>" |
| 84 | "<requestedPrivileges>" | 96 | "<requestedPrivileges>" |
