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:
parent
11c8893cc6
commit
407a598836
42
stew/io2.nim
42
stew/io2.nim
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue