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:
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] =
## Split the pathname ``path`` into drive/UNC sharepoint and relative path
## specifiers.
@ -533,7 +550,7 @@ proc rawCreateDir(dir: string, mode: int = 0o755,
lpSecurityDescriptor: secDescriptor,
bInheritHandle: 0
)
let res = createDirectoryW(newWideCString(dir), sa)
let res = createDirectoryW(newWideCString(fixPath(dir)), sa)
if res != 0'i32:
ok(true)
else:
@ -557,7 +574,7 @@ proc removeDir*(dir: string): IoResult[void] =
else:
return err(errCode)
elif defined(windows):
let res = removeDirectoryW(newWideCString(dir))
let res = removeDirectoryW(newWideCString(fixPath(dir)))
if res != 0'i32:
ok()
else:
@ -577,7 +594,7 @@ proc removeFile*(path: string): IoResult[void] =
else:
ok()
elif defined(windows):
if deleteFileW(newWideCString(path)) == 0:
if deleteFileW(newWideCString(fixPath(path))) == 0:
let errCode = ioLastError()
if errCode == ERROR_FILE_NOT_FOUND:
ok()
@ -596,7 +613,7 @@ proc isFile*(path: string): bool =
else:
posix.S_ISREG(a.st_mode)
elif defined(windows):
let res = getFileAttributes(newWideCString(path))
let res = getFileAttributes(newWideCString(fixPath(path)))
if res == INVALID_FILE_ATTRIBUTES:
false
else:
@ -612,7 +629,7 @@ proc isDir*(path: string): bool =
else:
posix.S_ISDIR(a.st_mode)
elif defined(windows):
let res = getFileAttributes(newWideCString(path))
let res = getFileAttributes(newWideCString(fixPath(path)))
if res == INVALID_FILE_ATTRIBUTES:
false
else:
@ -748,7 +765,7 @@ proc getPermissions*(pathName: string): IoResult[int] =
else:
err(ioLastError())
elif defined(windows):
let res = getFileAttributes(newWideCString(pathName))
let res = getFileAttributes(newWideCString(fixPath(pathName)))
if res == INVALID_FILE_ATTRIBUTES:
err(ioLastError())
else:
@ -802,7 +819,7 @@ proc getPermissionsSet*(handle: IoHandle): IoResult[Permissions] =
proc setPermissions*(pathName: string, mask: int): IoResult[void] =
## Set permissions for file/folder ``pathame``.
when defined(windows):
let gres = getFileAttributes(newWideCString(pathName))
let gres = getFileAttributes(newWideCString(fixPath(pathName)))
if gres == INVALID_FILE_ATTRIBUTES:
err(ioLastError())
else:
@ -811,7 +828,8 @@ proc setPermissions*(pathName: string, mask: int): IoResult[void] =
gres or uint32(FILE_ATTRIBUTE_READONLY)
else:
gres and not(FILE_ATTRIBUTE_READONLY)
let sres = setFileAttributes(newWideCString(pathName), nmask)
let sres = setFileAttributes(newWideCString(fixPath(pathName)),
nmask)
if sres == 0:
err(ioLastError())
else:
@ -895,7 +913,7 @@ proc fileAccessible*(pathName: string, mask: set[AccessFlags]): bool =
else:
false
elif defined(windows):
let res = getFileAttributes(newWideCString(pathName))
let res = getFileAttributes(newWideCString(fixPath(pathName)))
if res == INVALID_FILE_ATTRIBUTES:
return false
if AccessFlags.Write in mask:
@ -1068,8 +1086,8 @@ proc openFile*(pathName: string, flags: set[OpenFlags],
if OpenFlags.Inherit in flags:
sa.bInheritHandle = 1
let res = createFileW(newWideCString(pathName), dwAccess, dwShareMode,
sa, dwCreation, dwFlags, 0'u32)
let res = createFileW(newWideCString(fixPath(pathName)), dwAccess,
dwShareMode, sa, dwCreation, dwFlags, 0'u32)
if res == INVALID_HANDLE_VALUE:
err(ioLastError())
else:
@ -1224,7 +1242,7 @@ proc getFileSize*(pathName: string): IoResult[int64] =
ok(int64(a.st_size))
elif defined(windows):
var wfd: WIN32_FIND_DATAW
let res = findFirstFileW(newWideCString(pathName), wfd)
let res = findFirstFileW(newWideCString(fixPath(pathName)), wfd)
if res == INVALID_HANDLE_VALUE:
err(ioLastError())
else:

View File

@ -7,7 +7,7 @@
{.used.}
import unittest2
import std/[osproc, strutils]
import std/[osproc, strutils, algorithm]
import ../stew/io2
from os import getAppDir
@ -733,3 +733,38 @@ suite "OS Input/Output procedures test suite":
ok()
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