Fix Windows MAX_PATH limitation for absolute paths in io2 module. (#169)

* Fix Windows MAX_PATH limitation for absolute paths.

* Update algorithm to be more compatible with both directory and file paths.

* Add test.
This commit is contained in:
Eugene Kabanov 2023-02-02 10:30:40 +02:00 committed by GitHub
parent 11c8893cc6
commit 407a598836
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 66 additions and 13 deletions

View File

@ -361,6 +361,23 @@ proc normPathEnd(path: var string, trailingSep: bool) =
else: else:
path = $DirSep path = $DirSep
when defined(windows):
proc fixPath(path: string): string =
## If ``path`` is absolute path and length of ``path`` exceedes
## MAX_PATH number of characeters - ``path`` will be prefixed with ``\\?\``
## value which disable all string parsing and send the string that follows
## prefix straight to the file system.
##
## MAX_PATH limitation has different meaning for directory paths, because
## when creating directory 12 characters will be reserved for 8.3 filename,
## that's why we going to apply prefix for all paths which are bigger than
## MAX_PATH - 12.
if len(path) < MAX_PATH - 12: return path
if ((path[0] in {'a' .. 'z', 'A' .. 'Z'}) and path[1] == ':'):
"\\\\?\\" & path
else:
path
proc splitDrive*(path: string): tuple[head: string, tail: string] = proc splitDrive*(path: string): tuple[head: string, tail: string] =
## Split the pathname ``path`` into drive/UNC sharepoint and relative path ## Split the pathname ``path`` into drive/UNC sharepoint and relative path
## specifiers. ## specifiers.
@ -533,7 +550,7 @@ proc rawCreateDir(dir: string, mode: int = 0o755,
lpSecurityDescriptor: secDescriptor, lpSecurityDescriptor: secDescriptor,
bInheritHandle: 0 bInheritHandle: 0
) )
let res = createDirectoryW(newWideCString(dir), sa) let res = createDirectoryW(newWideCString(fixPath(dir)), sa)
if res != 0'i32: if res != 0'i32:
ok(true) ok(true)
else: else:
@ -557,7 +574,7 @@ proc removeDir*(dir: string): IoResult[void] =
else: else:
return err(errCode) return err(errCode)
elif defined(windows): elif defined(windows):
let res = removeDirectoryW(newWideCString(dir)) let res = removeDirectoryW(newWideCString(fixPath(dir)))
if res != 0'i32: if res != 0'i32:
ok() ok()
else: else:
@ -577,7 +594,7 @@ proc removeFile*(path: string): IoResult[void] =
else: else:
ok() ok()
elif defined(windows): elif defined(windows):
if deleteFileW(newWideCString(path)) == 0: if deleteFileW(newWideCString(fixPath(path))) == 0:
let errCode = ioLastError() let errCode = ioLastError()
if errCode == ERROR_FILE_NOT_FOUND: if errCode == ERROR_FILE_NOT_FOUND:
ok() ok()
@ -596,7 +613,7 @@ proc isFile*(path: string): bool =
else: else:
posix.S_ISREG(a.st_mode) posix.S_ISREG(a.st_mode)
elif defined(windows): elif defined(windows):
let res = getFileAttributes(newWideCString(path)) let res = getFileAttributes(newWideCString(fixPath(path)))
if res == INVALID_FILE_ATTRIBUTES: if res == INVALID_FILE_ATTRIBUTES:
false false
else: else:
@ -612,7 +629,7 @@ proc isDir*(path: string): bool =
else: else:
posix.S_ISDIR(a.st_mode) posix.S_ISDIR(a.st_mode)
elif defined(windows): elif defined(windows):
let res = getFileAttributes(newWideCString(path)) let res = getFileAttributes(newWideCString(fixPath(path)))
if res == INVALID_FILE_ATTRIBUTES: if res == INVALID_FILE_ATTRIBUTES:
false false
else: else:
@ -748,7 +765,7 @@ proc getPermissions*(pathName: string): IoResult[int] =
else: else:
err(ioLastError()) err(ioLastError())
elif defined(windows): elif defined(windows):
let res = getFileAttributes(newWideCString(pathName)) let res = getFileAttributes(newWideCString(fixPath(pathName)))
if res == INVALID_FILE_ATTRIBUTES: if res == INVALID_FILE_ATTRIBUTES:
err(ioLastError()) err(ioLastError())
else: else:
@ -802,7 +819,7 @@ proc getPermissionsSet*(handle: IoHandle): IoResult[Permissions] =
proc setPermissions*(pathName: string, mask: int): IoResult[void] = proc setPermissions*(pathName: string, mask: int): IoResult[void] =
## Set permissions for file/folder ``pathame``. ## Set permissions for file/folder ``pathame``.
when defined(windows): when defined(windows):
let gres = getFileAttributes(newWideCString(pathName)) let gres = getFileAttributes(newWideCString(fixPath(pathName)))
if gres == INVALID_FILE_ATTRIBUTES: if gres == INVALID_FILE_ATTRIBUTES:
err(ioLastError()) err(ioLastError())
else: else:
@ -811,7 +828,8 @@ proc setPermissions*(pathName: string, mask: int): IoResult[void] =
gres or uint32(FILE_ATTRIBUTE_READONLY) gres or uint32(FILE_ATTRIBUTE_READONLY)
else: else:
gres and not(FILE_ATTRIBUTE_READONLY) gres and not(FILE_ATTRIBUTE_READONLY)
let sres = setFileAttributes(newWideCString(pathName), nmask) let sres = setFileAttributes(newWideCString(fixPath(pathName)),
nmask)
if sres == 0: if sres == 0:
err(ioLastError()) err(ioLastError())
else: else:
@ -895,7 +913,7 @@ proc fileAccessible*(pathName: string, mask: set[AccessFlags]): bool =
else: else:
false false
elif defined(windows): elif defined(windows):
let res = getFileAttributes(newWideCString(pathName)) let res = getFileAttributes(newWideCString(fixPath(pathName)))
if res == INVALID_FILE_ATTRIBUTES: if res == INVALID_FILE_ATTRIBUTES:
return false return false
if AccessFlags.Write in mask: if AccessFlags.Write in mask:
@ -1068,8 +1086,8 @@ proc openFile*(pathName: string, flags: set[OpenFlags],
if OpenFlags.Inherit in flags: if OpenFlags.Inherit in flags:
sa.bInheritHandle = 1 sa.bInheritHandle = 1
let res = createFileW(newWideCString(pathName), dwAccess, dwShareMode, let res = createFileW(newWideCString(fixPath(pathName)), dwAccess,
sa, dwCreation, dwFlags, 0'u32) dwShareMode, sa, dwCreation, dwFlags, 0'u32)
if res == INVALID_HANDLE_VALUE: if res == INVALID_HANDLE_VALUE:
err(ioLastError()) err(ioLastError())
else: else:
@ -1224,7 +1242,7 @@ proc getFileSize*(pathName: string): IoResult[int64] =
ok(int64(a.st_size)) ok(int64(a.st_size))
elif defined(windows): elif defined(windows):
var wfd: WIN32_FIND_DATAW var wfd: WIN32_FIND_DATAW
let res = findFirstFileW(newWideCString(pathName), wfd) let res = findFirstFileW(newWideCString(fixPath(pathName)), wfd)
if res == INVALID_HANDLE_VALUE: if res == INVALID_HANDLE_VALUE:
err(ioLastError()) err(ioLastError())
else: else:

View File

@ -7,7 +7,7 @@
{.used.} {.used.}
import unittest2 import unittest2
import std/[osproc, strutils] import std/[osproc, strutils, algorithm]
import ../stew/io2 import ../stew/io2
from os import getAppDir from os import getAppDir
@ -733,3 +733,38 @@ suite "OS Input/Output procedures test suite":
ok() ok()
check performTest().isOk() check performTest().isOk()
test "Long path directory/file management test":
const MAX_PATH = 260 - 12
var
parentName = newString(MAX_PATH)
directoryName = newString(MAX_PATH)
fileName = newString(MAX_PATH)
parentName.fill('1')
directoryName.fill('2')
fileName.fill('3')
let workingDir = getAppDir()
let
firstDir = workingDir & DirSep & parentName
destDir = firstDir & DirSep & directoryName
destFile = firstDir & DirSep & fileName
check:
createPath(firstDir).isOk() == true
createPath(destDir).isOk() == true
io2.writeFile(destFile, "test").isOk() == true
isDir(destDir) == true
isDir(firstDir) == true
isFile(destFile) == true
let data = readAllChars(destFile).tryGet()
check:
data == "test"
removeFile(destFile).isOk() == true
removeDir(destDir).isOk() == true
removeDir(firstDir).isOk() == true
isDir(destDir) == false
isDir(firstDir) == false
isFile(destFile) == false