mirror of
https://github.com/nim-lang/Nim.git
synced 2026-02-14 15:23:27 +00:00
[std/os] split and re-export (#20593)
* [std/os] split and export * move to private modules * fixes docs and tests Co-authored-by: xflywind <43030857+xflywind@users.noreply.github.com>
This commit is contained in:
@@ -22,8 +22,9 @@ when declared(math.signbit):
|
||||
# ditto
|
||||
from std/math as math3 import signbit
|
||||
|
||||
|
||||
from std/envvars import getEnv, existsEnv, delEnv, putEnv, envPairs
|
||||
from std/os import dirExists, fileExists, walkDir, getAppFilename
|
||||
from std/os import walkDir, getAppFilename, dirExists, fileExists
|
||||
|
||||
from std/times import cpuTime
|
||||
from std/hashes import hash
|
||||
@@ -44,6 +45,8 @@ template mathop(op) {.dirty.} =
|
||||
template osop(op) {.dirty.} =
|
||||
registerCallback(c, "stdlib.os." & astToStr(op), `op Wrapper`)
|
||||
|
||||
template oscommonop(op) {.dirty.} =
|
||||
registerCallback(c, "stdlib.oscommon." & astToStr(op), `op Wrapper`)
|
||||
template envvarsop(op) {.dirty.} =
|
||||
registerCallback(c, "stdlib.envvars." & astToStr(op), `op Wrapper`)
|
||||
|
||||
@@ -226,8 +229,8 @@ proc registerAdditionalOps*(c: PCtx) =
|
||||
wrap1s(existsEnv, envvarsop)
|
||||
wrap2svoid(putEnv, envvarsop)
|
||||
wrap1svoid(delEnv, envvarsop)
|
||||
wrap1s(dirExists, osop)
|
||||
wrap1s(fileExists, osop)
|
||||
wrap1s(dirExists, oscommonop)
|
||||
wrap1s(fileExists, oscommonop)
|
||||
wrapDangerous(writeFile, ioop)
|
||||
wrap1s(readFile, ioop)
|
||||
wrap2si(readLines, ioop)
|
||||
|
||||
2061
lib/pure/os.nim
2061
lib/pure/os.nim
File diff suppressed because it is too large
Load Diff
180
lib/std/private/oscommon.nim
Normal file
180
lib/std/private/oscommon.nim
Normal file
@@ -0,0 +1,180 @@
|
||||
include system/inclrtl
|
||||
|
||||
import ospaths2
|
||||
import std/[oserrors]
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/[syncio, assertions, widestrs]
|
||||
|
||||
const weirdTarget* = defined(nimscript) or defined(js)
|
||||
|
||||
|
||||
when weirdTarget:
|
||||
discard
|
||||
elif defined(windows):
|
||||
import winlean, times
|
||||
elif defined(posix):
|
||||
import posix
|
||||
proc c_rename(oldname, newname: cstring): cint {.
|
||||
importc: "rename", header: "<stdio.h>".}
|
||||
else:
|
||||
{.error: "OS module not ported to your operating system!".}
|
||||
|
||||
|
||||
when weirdTarget:
|
||||
{.pragma: noWeirdTarget, error: "this proc is not available on the NimScript/js target".}
|
||||
else:
|
||||
{.pragma: noWeirdTarget.}
|
||||
|
||||
|
||||
when defined(nimscript):
|
||||
# for procs already defined in scriptconfig.nim
|
||||
template noNimJs(body): untyped = discard
|
||||
elif defined(js):
|
||||
{.pragma: noNimJs, error: "this proc is not available on the js target".}
|
||||
else:
|
||||
{.pragma: noNimJs.}
|
||||
|
||||
|
||||
when defined(windows) and not weirdTarget:
|
||||
when useWinUnicode:
|
||||
template wrapUnary*(varname, winApiProc, arg: untyped) =
|
||||
var varname = winApiProc(newWideCString(arg))
|
||||
|
||||
template wrapBinary*(varname, winApiProc, arg, arg2: untyped) =
|
||||
var varname = winApiProc(newWideCString(arg), arg2)
|
||||
proc findFirstFile*(a: string, b: var WIN32_FIND_DATA): Handle =
|
||||
result = findFirstFileW(newWideCString(a), b)
|
||||
template findNextFile*(a, b: untyped): untyped = findNextFileW(a, b)
|
||||
template getCommandLine*(): untyped = getCommandLineW()
|
||||
|
||||
template getFilename*(f: untyped): untyped =
|
||||
$cast[WideCString](addr(f.cFileName[0]))
|
||||
else:
|
||||
template findFirstFile*(a, b: untyped): untyped = findFirstFileA(a, b)
|
||||
template findNextFile*(a, b: untyped): untyped = findNextFileA(a, b)
|
||||
template getCommandLine*(): untyped = getCommandLineA()
|
||||
|
||||
template getFilename*(f: untyped): untyped = $cstring(addr f.cFileName)
|
||||
|
||||
proc skipFindData*(f: WIN32_FIND_DATA): bool {.inline.} =
|
||||
# Note - takes advantage of null delimiter in the cstring
|
||||
const dot = ord('.')
|
||||
result = f.cFileName[0].int == dot and (f.cFileName[1].int == 0 or
|
||||
f.cFileName[1].int == dot and f.cFileName[2].int == 0)
|
||||
|
||||
|
||||
type
|
||||
PathComponent* = enum ## Enumeration specifying a path component.
|
||||
##
|
||||
## See also:
|
||||
## * `walkDirRec iterator`_
|
||||
## * `FileInfo object`_
|
||||
pcFile, ## path refers to a file
|
||||
pcLinkToFile, ## path refers to a symbolic link to a file
|
||||
pcDir, ## path refers to a directory
|
||||
pcLinkToDir ## path refers to a symbolic link to a directory
|
||||
|
||||
|
||||
when defined(posix) and not weirdTarget:
|
||||
proc getSymlinkFileKind*(path: string): PathComponent =
|
||||
# Helper function.
|
||||
var s: Stat
|
||||
assert(path != "")
|
||||
if stat(path, s) == 0'i32 and S_ISDIR(s.st_mode):
|
||||
result = pcLinkToDir
|
||||
else:
|
||||
result = pcLinkToFile
|
||||
|
||||
proc tryMoveFSObject*(source, dest: string, isDir: bool): bool {.noWeirdTarget.} =
|
||||
## Moves a file (or directory if `isDir` is true) from `source` to `dest`.
|
||||
##
|
||||
## Returns false in case of `EXDEV` error or `AccessDeniedError` on Windows (if `isDir` is true).
|
||||
## In case of other errors `OSError` is raised.
|
||||
## Returns true in case of success.
|
||||
when defined(windows):
|
||||
when useWinUnicode:
|
||||
let s = newWideCString(source)
|
||||
let d = newWideCString(dest)
|
||||
result = moveFileExW(s, d, MOVEFILE_COPY_ALLOWED or MOVEFILE_REPLACE_EXISTING) != 0'i32
|
||||
else:
|
||||
result = moveFileExA(source, dest, MOVEFILE_COPY_ALLOWED or MOVEFILE_REPLACE_EXISTING) != 0'i32
|
||||
else:
|
||||
result = c_rename(source, dest) == 0'i32
|
||||
|
||||
if not result:
|
||||
let err = osLastError()
|
||||
let isAccessDeniedError =
|
||||
when defined(windows):
|
||||
const AccessDeniedError = OSErrorCode(5)
|
||||
isDir and err == AccessDeniedError
|
||||
else:
|
||||
err == EXDEV.OSErrorCode
|
||||
if not isAccessDeniedError:
|
||||
raiseOSError(err, $(source, dest))
|
||||
|
||||
when not defined(windows):
|
||||
const maxSymlinkLen* = 1024
|
||||
|
||||
proc fileExists*(filename: string): bool {.rtl, extern: "nos$1",
|
||||
tags: [ReadDirEffect], noNimJs.} =
|
||||
## Returns true if `filename` exists and is a regular file or symlink.
|
||||
##
|
||||
## Directories, device files, named pipes and sockets return false.
|
||||
##
|
||||
## See also:
|
||||
## * `dirExists proc`_
|
||||
## * `symlinkExists proc`_
|
||||
when defined(windows):
|
||||
when useWinUnicode:
|
||||
wrapUnary(a, getFileAttributesW, filename)
|
||||
else:
|
||||
var a = getFileAttributesA(filename)
|
||||
if a != -1'i32:
|
||||
result = (a and FILE_ATTRIBUTE_DIRECTORY) == 0'i32
|
||||
else:
|
||||
var res: Stat
|
||||
return stat(filename, res) >= 0'i32 and S_ISREG(res.st_mode)
|
||||
|
||||
|
||||
proc dirExists*(dir: string): bool {.rtl, extern: "nos$1", tags: [ReadDirEffect],
|
||||
noNimJs.} =
|
||||
## Returns true if the directory `dir` exists. If `dir` is a file, false
|
||||
## is returned. Follows symlinks.
|
||||
##
|
||||
## See also:
|
||||
## * `fileExists proc`_
|
||||
## * `symlinkExists proc`_
|
||||
when defined(windows):
|
||||
when useWinUnicode:
|
||||
wrapUnary(a, getFileAttributesW, dir)
|
||||
else:
|
||||
var a = getFileAttributesA(dir)
|
||||
if a != -1'i32:
|
||||
result = (a and FILE_ATTRIBUTE_DIRECTORY) != 0'i32
|
||||
else:
|
||||
var res: Stat
|
||||
result = stat(dir, res) >= 0'i32 and S_ISDIR(res.st_mode)
|
||||
|
||||
|
||||
proc symlinkExists*(link: string): bool {.rtl, extern: "nos$1",
|
||||
tags: [ReadDirEffect],
|
||||
noWeirdTarget.} =
|
||||
## Returns true if the symlink `link` exists. Will return true
|
||||
## regardless of whether the link points to a directory or file.
|
||||
##
|
||||
## See also:
|
||||
## * `fileExists proc`_
|
||||
## * `dirExists proc`_
|
||||
when defined(windows):
|
||||
when useWinUnicode:
|
||||
wrapUnary(a, getFileAttributesW, link)
|
||||
else:
|
||||
var a = getFileAttributesA(link)
|
||||
if a != -1'i32:
|
||||
# xxx see: bug #16784 (bug9); checking `IO_REPARSE_TAG_SYMLINK`
|
||||
# may also be needed.
|
||||
result = (a and FILE_ATTRIBUTE_REPARSE_POINT) != 0'i32
|
||||
else:
|
||||
var res: Stat
|
||||
result = lstat(link, res) >= 0'i32 and S_ISLNK(res.st_mode)
|
||||
540
lib/std/private/osdirs.nim
Normal file
540
lib/std/private/osdirs.nim
Normal file
@@ -0,0 +1,540 @@
|
||||
include system/inclrtl
|
||||
import std/oserrors
|
||||
|
||||
|
||||
import ospaths2, osfiles
|
||||
import oscommon
|
||||
export dirExists, PathComponent
|
||||
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/[syncio, assertions, widestrs]
|
||||
|
||||
|
||||
when weirdTarget:
|
||||
discard
|
||||
elif defined(windows):
|
||||
import winlean, times
|
||||
elif defined(posix):
|
||||
import posix, times
|
||||
|
||||
else:
|
||||
{.error: "OS module not ported to your operating system!".}
|
||||
|
||||
|
||||
when weirdTarget:
|
||||
{.pragma: noWeirdTarget, error: "this proc is not available on the NimScript/js target".}
|
||||
else:
|
||||
{.pragma: noWeirdTarget.}
|
||||
|
||||
|
||||
when defined(nimscript):
|
||||
# for procs already defined in scriptconfig.nim
|
||||
template noNimJs(body): untyped = discard
|
||||
elif defined(js):
|
||||
{.pragma: noNimJs, error: "this proc is not available on the js target".}
|
||||
else:
|
||||
{.pragma: noNimJs.}
|
||||
|
||||
# Templates for filtering directories and files
|
||||
when defined(windows) and not weirdTarget:
|
||||
template isDir(f: WIN32_FIND_DATA): bool =
|
||||
(f.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) != 0'i32
|
||||
template isFile(f: WIN32_FIND_DATA): bool =
|
||||
not isDir(f)
|
||||
else:
|
||||
template isDir(f: string): bool {.dirty.} =
|
||||
dirExists(f)
|
||||
template isFile(f: string): bool {.dirty.} =
|
||||
fileExists(f)
|
||||
|
||||
template defaultWalkFilter(item): bool =
|
||||
## Walk filter used to return true on both
|
||||
## files and directories
|
||||
true
|
||||
|
||||
template walkCommon(pattern: string, filter) =
|
||||
## Common code for getting the files and directories with the
|
||||
## specified `pattern`
|
||||
when defined(windows):
|
||||
var
|
||||
f: WIN32_FIND_DATA
|
||||
res: int
|
||||
res = findFirstFile(pattern, f)
|
||||
if res != -1:
|
||||
defer: findClose(res)
|
||||
let dotPos = searchExtPos(pattern)
|
||||
while true:
|
||||
if not skipFindData(f) and filter(f):
|
||||
# Windows bug/gotcha: 't*.nim' matches 'tfoo.nims' -.- so we check
|
||||
# that the file extensions have the same length ...
|
||||
let ff = getFilename(f)
|
||||
let idx = ff.len - pattern.len + dotPos
|
||||
if dotPos < 0 or idx >= ff.len or (idx >= 0 and ff[idx] == '.') or
|
||||
(dotPos >= 0 and dotPos+1 < pattern.len and pattern[dotPos+1] == '*'):
|
||||
yield splitFile(pattern).dir / extractFilename(ff)
|
||||
if findNextFile(res, f) == 0'i32:
|
||||
let errCode = getLastError()
|
||||
if errCode == ERROR_NO_MORE_FILES: break
|
||||
else: raiseOSError(errCode.OSErrorCode)
|
||||
else: # here we use glob
|
||||
var
|
||||
f: Glob
|
||||
res: int
|
||||
f.gl_offs = 0
|
||||
f.gl_pathc = 0
|
||||
f.gl_pathv = nil
|
||||
res = glob(pattern, 0, nil, addr(f))
|
||||
defer: globfree(addr(f))
|
||||
if res == 0:
|
||||
for i in 0.. f.gl_pathc - 1:
|
||||
assert(f.gl_pathv[i] != nil)
|
||||
let path = $f.gl_pathv[i]
|
||||
if filter(path):
|
||||
yield path
|
||||
|
||||
iterator walkPattern*(pattern: string): string {.tags: [ReadDirEffect], noWeirdTarget.} =
|
||||
## Iterate over all the files and directories that match the `pattern`.
|
||||
##
|
||||
## On POSIX this uses the `glob`:idx: call.
|
||||
## `pattern` is OS dependent, but at least the `"*.ext"`
|
||||
## notation is supported.
|
||||
##
|
||||
## See also:
|
||||
## * `walkFiles iterator`_
|
||||
## * `walkDirs iterator`_
|
||||
## * `walkDir iterator`_
|
||||
## * `walkDirRec iterator`_
|
||||
runnableExamples:
|
||||
import std/os
|
||||
import std/sequtils
|
||||
let paths = toSeq(walkPattern("lib/pure/*")) # works on Windows too
|
||||
assert "lib/pure/concurrency".unixToNativePath in paths
|
||||
assert "lib/pure/os.nim".unixToNativePath in paths
|
||||
walkCommon(pattern, defaultWalkFilter)
|
||||
|
||||
iterator walkFiles*(pattern: string): string {.tags: [ReadDirEffect], noWeirdTarget.} =
|
||||
## Iterate over all the files that match the `pattern`.
|
||||
##
|
||||
## On POSIX this uses the `glob`:idx: call.
|
||||
## `pattern` is OS dependent, but at least the `"*.ext"`
|
||||
## notation is supported.
|
||||
##
|
||||
## See also:
|
||||
## * `walkPattern iterator`_
|
||||
## * `walkDirs iterator`_
|
||||
## * `walkDir iterator`_
|
||||
## * `walkDirRec iterator`_
|
||||
runnableExamples:
|
||||
import std/os
|
||||
import std/sequtils
|
||||
assert "lib/pure/os.nim".unixToNativePath in toSeq(walkFiles("lib/pure/*.nim")) # works on Windows too
|
||||
walkCommon(pattern, isFile)
|
||||
|
||||
iterator walkDirs*(pattern: string): string {.tags: [ReadDirEffect], noWeirdTarget.} =
|
||||
## Iterate over all the directories that match the `pattern`.
|
||||
##
|
||||
## On POSIX this uses the `glob`:idx: call.
|
||||
## `pattern` is OS dependent, but at least the `"*.ext"`
|
||||
## notation is supported.
|
||||
##
|
||||
## See also:
|
||||
## * `walkPattern iterator`_
|
||||
## * `walkFiles iterator`_
|
||||
## * `walkDir iterator`_
|
||||
## * `walkDirRec iterator`_
|
||||
runnableExamples:
|
||||
import std/os
|
||||
import std/sequtils
|
||||
let paths = toSeq(walkDirs("lib/pure/*")) # works on Windows too
|
||||
assert "lib/pure/concurrency".unixToNativePath in paths
|
||||
walkCommon(pattern, isDir)
|
||||
|
||||
proc staticWalkDir(dir: string; relative: bool): seq[
|
||||
tuple[kind: PathComponent, path: string]] =
|
||||
discard
|
||||
|
||||
iterator walkDir*(dir: string; relative = false, checkDir = false):
|
||||
tuple[kind: PathComponent, path: string] {.tags: [ReadDirEffect].} =
|
||||
## Walks over the directory `dir` and yields for each directory or file in
|
||||
## `dir`. The component type and full path for each item are returned.
|
||||
##
|
||||
## Walking is not recursive. If ``relative`` is true (default: false)
|
||||
## the resulting path is shortened to be relative to ``dir``.
|
||||
##
|
||||
## If `checkDir` is true, `OSError` is raised when `dir`
|
||||
## doesn't exist.
|
||||
##
|
||||
## **Example:**
|
||||
##
|
||||
## This directory structure:
|
||||
##
|
||||
## dirA / dirB / fileB1.txt
|
||||
## / dirC
|
||||
## / fileA1.txt
|
||||
## / fileA2.txt
|
||||
##
|
||||
## and this code:
|
||||
runnableExamples("-r:off"):
|
||||
import std/[strutils, sugar]
|
||||
# note: order is not guaranteed
|
||||
# this also works at compile time
|
||||
assert collect(for k in walkDir("dirA"): k.path).join(" ") ==
|
||||
"dirA/dirB dirA/dirC dirA/fileA2.txt dirA/fileA1.txt"
|
||||
## See also:
|
||||
## * `walkPattern iterator`_
|
||||
## * `walkFiles iterator`_
|
||||
## * `walkDirs iterator`_
|
||||
## * `walkDirRec iterator`_
|
||||
|
||||
when nimvm:
|
||||
for k, v in items(staticWalkDir(dir, relative)):
|
||||
yield (k, v)
|
||||
else:
|
||||
when weirdTarget:
|
||||
for k, v in items(staticWalkDir(dir, relative)):
|
||||
yield (k, v)
|
||||
elif defined(windows):
|
||||
var f: WIN32_FIND_DATA
|
||||
var h = findFirstFile(dir / "*", f)
|
||||
if h == -1:
|
||||
if checkDir:
|
||||
raiseOSError(osLastError(), dir)
|
||||
else:
|
||||
defer: findClose(h)
|
||||
while true:
|
||||
var k = pcFile
|
||||
if not skipFindData(f):
|
||||
if (f.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) != 0'i32:
|
||||
k = pcDir
|
||||
if (f.dwFileAttributes and FILE_ATTRIBUTE_REPARSE_POINT) != 0'i32:
|
||||
k = succ(k)
|
||||
let xx = if relative: extractFilename(getFilename(f))
|
||||
else: dir / extractFilename(getFilename(f))
|
||||
yield (k, xx)
|
||||
if findNextFile(h, f) == 0'i32:
|
||||
let errCode = getLastError()
|
||||
if errCode == ERROR_NO_MORE_FILES: break
|
||||
else: raiseOSError(errCode.OSErrorCode)
|
||||
else:
|
||||
var d = opendir(dir)
|
||||
if d == nil:
|
||||
if checkDir:
|
||||
raiseOSError(osLastError(), dir)
|
||||
else:
|
||||
defer: discard closedir(d)
|
||||
while true:
|
||||
var x = readdir(d)
|
||||
if x == nil: break
|
||||
var y = $cstring(addr x.d_name)
|
||||
if y != "." and y != "..":
|
||||
var s: Stat
|
||||
let path = dir / y
|
||||
if not relative:
|
||||
y = path
|
||||
var k = pcFile
|
||||
|
||||
template kSetGeneric() = # pure Posix component `k` resolution
|
||||
if lstat(path.cstring, s) < 0'i32: continue # don't yield
|
||||
elif S_ISDIR(s.st_mode):
|
||||
k = pcDir
|
||||
elif S_ISLNK(s.st_mode):
|
||||
k = getSymlinkFileKind(path)
|
||||
|
||||
when defined(linux) or defined(macosx) or
|
||||
defined(bsd) or defined(genode) or defined(nintendoswitch):
|
||||
case x.d_type
|
||||
of DT_DIR: k = pcDir
|
||||
of DT_LNK:
|
||||
if dirExists(path): k = pcLinkToDir
|
||||
else: k = pcLinkToFile
|
||||
of DT_UNKNOWN:
|
||||
kSetGeneric()
|
||||
else: # e.g. DT_REG etc
|
||||
discard # leave it as pcFile
|
||||
else: # assuming that field `d_type` is not present
|
||||
kSetGeneric()
|
||||
|
||||
yield (k, y)
|
||||
|
||||
iterator walkDirRec*(dir: string,
|
||||
yieldFilter = {pcFile}, followFilter = {pcDir},
|
||||
relative = false, checkDir = false): string {.tags: [ReadDirEffect].} =
|
||||
## Recursively walks over the directory `dir` and yields for each file
|
||||
## or directory in `dir`.
|
||||
##
|
||||
## If ``relative`` is true (default: false) the resulting path is
|
||||
## shortened to be relative to ``dir``, otherwise the full path is returned.
|
||||
##
|
||||
## If `checkDir` is true, `OSError` is raised when `dir`
|
||||
## doesn't exist.
|
||||
##
|
||||
## .. warning:: Modifying the directory structure while the iterator
|
||||
## is traversing may result in undefined behavior!
|
||||
##
|
||||
## Walking is recursive. `followFilter` controls the behaviour of the iterator:
|
||||
##
|
||||
## ===================== =============================================
|
||||
## yieldFilter meaning
|
||||
## ===================== =============================================
|
||||
## ``pcFile`` yield real files (default)
|
||||
## ``pcLinkToFile`` yield symbolic links to files
|
||||
## ``pcDir`` yield real directories
|
||||
## ``pcLinkToDir`` yield symbolic links to directories
|
||||
## ===================== =============================================
|
||||
##
|
||||
## ===================== =============================================
|
||||
## followFilter meaning
|
||||
## ===================== =============================================
|
||||
## ``pcDir`` follow real directories (default)
|
||||
## ``pcLinkToDir`` follow symbolic links to directories
|
||||
## ===================== =============================================
|
||||
##
|
||||
##
|
||||
## See also:
|
||||
## * `walkPattern iterator`_
|
||||
## * `walkFiles iterator`_
|
||||
## * `walkDirs iterator`_
|
||||
## * `walkDir iterator`_
|
||||
|
||||
var stack = @[""]
|
||||
var checkDir = checkDir
|
||||
while stack.len > 0:
|
||||
let d = stack.pop()
|
||||
for k, p in walkDir(dir / d, relative = true, checkDir = checkDir):
|
||||
let rel = d / p
|
||||
if k in {pcDir, pcLinkToDir} and k in followFilter:
|
||||
stack.add rel
|
||||
if k in yieldFilter:
|
||||
yield if relative: rel else: dir / rel
|
||||
checkDir = false
|
||||
# We only check top-level dir, otherwise if a subdir is invalid (eg. wrong
|
||||
# permissions), it'll abort iteration and there would be no way to
|
||||
# continue iteration.
|
||||
# Future work can provide a way to customize this and do error reporting.
|
||||
|
||||
|
||||
proc rawRemoveDir(dir: string) {.noWeirdTarget.} =
|
||||
when defined(windows):
|
||||
when useWinUnicode:
|
||||
wrapUnary(res, removeDirectoryW, dir)
|
||||
else:
|
||||
var res = removeDirectoryA(dir)
|
||||
let lastError = osLastError()
|
||||
if res == 0'i32 and lastError.int32 != 3'i32 and
|
||||
lastError.int32 != 18'i32 and lastError.int32 != 2'i32:
|
||||
raiseOSError(lastError, dir)
|
||||
else:
|
||||
if rmdir(dir) != 0'i32 and errno != ENOENT: raiseOSError(osLastError(), dir)
|
||||
|
||||
proc removeDir*(dir: string, checkDir = false) {.rtl, extern: "nos$1", tags: [
|
||||
WriteDirEffect, ReadDirEffect], benign, noWeirdTarget.} =
|
||||
## Removes the directory `dir` including all subdirectories and files
|
||||
## in `dir` (recursively).
|
||||
##
|
||||
## If this fails, `OSError` is raised. This does not fail if the directory never
|
||||
## existed in the first place, unless `checkDir` = true.
|
||||
##
|
||||
## See also:
|
||||
## * `tryRemoveFile proc`_
|
||||
## * `removeFile proc`_
|
||||
## * `existsOrCreateDir proc`_
|
||||
## * `createDir proc`_
|
||||
## * `copyDir proc`_
|
||||
## * `copyDirWithPermissions proc`_
|
||||
## * `moveDir proc`_
|
||||
for kind, path in walkDir(dir, checkDir = checkDir):
|
||||
case kind
|
||||
of pcFile, pcLinkToFile, pcLinkToDir: removeFile(path)
|
||||
of pcDir: removeDir(path, true)
|
||||
# for subdirectories there is no benefit in `checkDir = false`
|
||||
# (unless perhaps for edge case of concurrent processes also deleting
|
||||
# the same files)
|
||||
rawRemoveDir(dir)
|
||||
|
||||
proc rawCreateDir(dir: string): bool {.noWeirdTarget.} =
|
||||
# Try to create one directory (not the whole path).
|
||||
# returns `true` for success, `false` if the path has previously existed
|
||||
#
|
||||
# This is a thin wrapper over mkDir (or alternatives on other systems),
|
||||
# so in case of a pre-existing path we don't check that it is a directory.
|
||||
when defined(solaris):
|
||||
let res = mkdir(dir, 0o777)
|
||||
if res == 0'i32:
|
||||
result = true
|
||||
elif errno in {EEXIST, ENOSYS}:
|
||||
result = false
|
||||
else:
|
||||
raiseOSError(osLastError(), dir)
|
||||
elif defined(haiku):
|
||||
let res = mkdir(dir, 0o777)
|
||||
if res == 0'i32:
|
||||
result = true
|
||||
elif errno == EEXIST or errno == EROFS:
|
||||
result = false
|
||||
else:
|
||||
raiseOSError(osLastError(), dir)
|
||||
elif defined(posix):
|
||||
let res = mkdir(dir, 0o777)
|
||||
if res == 0'i32:
|
||||
result = true
|
||||
elif errno == EEXIST:
|
||||
result = false
|
||||
else:
|
||||
#echo res
|
||||
raiseOSError(osLastError(), dir)
|
||||
else:
|
||||
when useWinUnicode:
|
||||
wrapUnary(res, createDirectoryW, dir)
|
||||
else:
|
||||
let res = createDirectoryA(dir)
|
||||
|
||||
if res != 0'i32:
|
||||
result = true
|
||||
elif getLastError() == 183'i32:
|
||||
result = false
|
||||
else:
|
||||
raiseOSError(osLastError(), dir)
|
||||
|
||||
proc existsOrCreateDir*(dir: string): bool {.rtl, extern: "nos$1",
|
||||
tags: [WriteDirEffect, ReadDirEffect], noWeirdTarget.} =
|
||||
## Checks if a `directory`:idx: `dir` exists, and creates it otherwise.
|
||||
##
|
||||
## Does not create parent directories (raises `OSError` if parent directories do not exist).
|
||||
## Returns `true` if the directory already exists, and `false` otherwise.
|
||||
##
|
||||
## See also:
|
||||
## * `removeDir proc`_
|
||||
## * `createDir proc`_
|
||||
## * `copyDir proc`_
|
||||
## * `copyDirWithPermissions proc`_
|
||||
## * `moveDir proc`_
|
||||
result = not rawCreateDir(dir)
|
||||
if result:
|
||||
# path already exists - need to check that it is indeed a directory
|
||||
if not dirExists(dir):
|
||||
raise newException(IOError, "Failed to create '" & dir & "'")
|
||||
|
||||
proc createDir*(dir: string) {.rtl, extern: "nos$1",
|
||||
tags: [WriteDirEffect, ReadDirEffect], noWeirdTarget.} =
|
||||
## Creates the `directory`:idx: `dir`.
|
||||
##
|
||||
## The directory may contain several subdirectories that do not exist yet.
|
||||
## The full path is created. If this fails, `OSError` is raised.
|
||||
##
|
||||
## It does **not** fail if the directory already exists because for
|
||||
## most usages this does not indicate an error.
|
||||
##
|
||||
## See also:
|
||||
## * `removeDir proc`_
|
||||
## * `existsOrCreateDir proc`_
|
||||
## * `copyDir proc`_
|
||||
## * `copyDirWithPermissions proc`_
|
||||
## * `moveDir proc`_
|
||||
if dir == "":
|
||||
return
|
||||
var omitNext = isAbsolute(dir)
|
||||
for p in parentDirs(dir, fromRoot=true):
|
||||
if omitNext:
|
||||
omitNext = false
|
||||
else:
|
||||
discard existsOrCreateDir(p)
|
||||
|
||||
proc copyDir*(source, dest: string) {.rtl, extern: "nos$1",
|
||||
tags: [ReadDirEffect, WriteIOEffect, ReadIOEffect], benign, noWeirdTarget.} =
|
||||
## Copies a directory from `source` to `dest`.
|
||||
##
|
||||
## On non-Windows OSes, symlinks are copied as symlinks. On Windows, symlinks
|
||||
## are skipped.
|
||||
##
|
||||
## If this fails, `OSError` is raised.
|
||||
##
|
||||
## On the Windows platform this proc will copy the attributes from
|
||||
## `source` into `dest`.
|
||||
##
|
||||
## On other platforms created files and directories will inherit the
|
||||
## default permissions of a newly created file/directory for the user.
|
||||
## Use `copyDirWithPermissions proc`_
|
||||
## to preserve attributes recursively on these platforms.
|
||||
##
|
||||
## See also:
|
||||
## * `copyDirWithPermissions proc`_
|
||||
## * `copyFile proc`_
|
||||
## * `copyFileWithPermissions proc`_
|
||||
## * `removeDir proc`_
|
||||
## * `existsOrCreateDir proc`_
|
||||
## * `createDir proc`_
|
||||
## * `moveDir proc`_
|
||||
createDir(dest)
|
||||
for kind, path in walkDir(source):
|
||||
var noSource = splitPath(path).tail
|
||||
if kind == pcDir:
|
||||
copyDir(path, dest / noSource)
|
||||
else:
|
||||
copyFile(path, dest / noSource, {cfSymlinkAsIs})
|
||||
|
||||
|
||||
proc copyDirWithPermissions*(source, dest: string,
|
||||
ignorePermissionErrors = true)
|
||||
{.rtl, extern: "nos$1", tags: [ReadDirEffect, WriteIOEffect, ReadIOEffect],
|
||||
benign, noWeirdTarget.} =
|
||||
## Copies a directory from `source` to `dest` preserving file permissions.
|
||||
##
|
||||
## On non-Windows OSes, symlinks are copied as symlinks. On Windows, symlinks
|
||||
## are skipped.
|
||||
##
|
||||
## If this fails, `OSError` is raised. This is a wrapper proc around
|
||||
## `copyDir`_ and `copyFileWithPermissions`_ procs
|
||||
## on non-Windows platforms.
|
||||
##
|
||||
## On Windows this proc is just a wrapper for `copyDir proc`_ since
|
||||
## that proc already copies attributes.
|
||||
##
|
||||
## On non-Windows systems permissions are copied after the file or directory
|
||||
## itself has been copied, which won't happen atomically and could lead to a
|
||||
## race condition. If `ignorePermissionErrors` is true (default), errors while
|
||||
## reading/setting file attributes will be ignored, otherwise will raise
|
||||
## `OSError`.
|
||||
##
|
||||
## See also:
|
||||
## * `copyDir proc`_
|
||||
## * `copyFile proc`_
|
||||
## * `copyFileWithPermissions proc`_
|
||||
## * `removeDir proc`_
|
||||
## * `moveDir proc`_
|
||||
## * `existsOrCreateDir proc`_
|
||||
## * `createDir proc`_
|
||||
createDir(dest)
|
||||
when not defined(windows):
|
||||
try:
|
||||
setFilePermissions(dest, getFilePermissions(source), followSymlinks =
|
||||
false)
|
||||
except:
|
||||
if not ignorePermissionErrors:
|
||||
raise
|
||||
for kind, path in walkDir(source):
|
||||
var noSource = splitPath(path).tail
|
||||
if kind == pcDir:
|
||||
copyDirWithPermissions(path, dest / noSource, ignorePermissionErrors)
|
||||
else:
|
||||
copyFileWithPermissions(path, dest / noSource, ignorePermissionErrors, {cfSymlinkAsIs})
|
||||
|
||||
proc moveDir*(source, dest: string) {.tags: [ReadIOEffect, WriteIOEffect], noWeirdTarget.} =
|
||||
## Moves a directory from `source` to `dest`.
|
||||
##
|
||||
## Symlinks are not followed: if `source` contains symlinks, they themself are
|
||||
## moved, not their target.
|
||||
##
|
||||
## If this fails, `OSError` is raised.
|
||||
##
|
||||
## See also:
|
||||
## * `moveFile proc`_
|
||||
## * `copyDir proc`_
|
||||
## * `copyDirWithPermissions proc`_
|
||||
## * `removeDir proc`_
|
||||
## * `existsOrCreateDir proc`_
|
||||
## * `createDir proc`_
|
||||
if not tryMoveFSObject(source, dest, isDir = true):
|
||||
# Fallback to copy & del
|
||||
copyDir(source, dest)
|
||||
removeDir(source)
|
||||
423
lib/std/private/osfiles.nim
Normal file
423
lib/std/private/osfiles.nim
Normal file
@@ -0,0 +1,423 @@
|
||||
include system/inclrtl
|
||||
import std/private/since
|
||||
import std/oserrors
|
||||
|
||||
import oscommon
|
||||
export fileExists
|
||||
|
||||
import ospaths2, ossymlinks
|
||||
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/[syncio, assertions, widestrs]
|
||||
|
||||
when weirdTarget:
|
||||
discard
|
||||
elif defined(windows):
|
||||
import winlean
|
||||
elif defined(posix):
|
||||
import posix, times
|
||||
|
||||
proc toTime(ts: Timespec): times.Time {.inline.} =
|
||||
result = initTime(ts.tv_sec.int64, ts.tv_nsec.int)
|
||||
else:
|
||||
{.error: "OS module not ported to your operating system!".}
|
||||
|
||||
|
||||
when weirdTarget:
|
||||
{.pragma: noWeirdTarget, error: "this proc is not available on the NimScript/js target".}
|
||||
else:
|
||||
{.pragma: noWeirdTarget.}
|
||||
|
||||
|
||||
when defined(nimscript):
|
||||
# for procs already defined in scriptconfig.nim
|
||||
template noNimJs(body): untyped = discard
|
||||
elif defined(js):
|
||||
{.pragma: noNimJs, error: "this proc is not available on the js target".}
|
||||
else:
|
||||
{.pragma: noNimJs.}
|
||||
|
||||
|
||||
type
|
||||
FilePermission* = enum ## File access permission, modelled after UNIX.
|
||||
##
|
||||
## See also:
|
||||
## * `getFilePermissions`_
|
||||
## * `setFilePermissions`_
|
||||
## * `FileInfo object`_
|
||||
fpUserExec, ## execute access for the file owner
|
||||
fpUserWrite, ## write access for the file owner
|
||||
fpUserRead, ## read access for the file owner
|
||||
fpGroupExec, ## execute access for the group
|
||||
fpGroupWrite, ## write access for the group
|
||||
fpGroupRead, ## read access for the group
|
||||
fpOthersExec, ## execute access for others
|
||||
fpOthersWrite, ## write access for others
|
||||
fpOthersRead ## read access for others
|
||||
|
||||
proc getFilePermissions*(filename: string): set[FilePermission] {.
|
||||
rtl, extern: "nos$1", tags: [ReadDirEffect], noWeirdTarget.} =
|
||||
## Retrieves file permissions for `filename`.
|
||||
##
|
||||
## `OSError` is raised in case of an error.
|
||||
## On Windows, only the ``readonly`` flag is checked, every other
|
||||
## permission is available in any case.
|
||||
##
|
||||
## See also:
|
||||
## * `setFilePermissions proc`_
|
||||
## * `FilePermission enum`_
|
||||
when defined(posix):
|
||||
var a: Stat
|
||||
if stat(filename, a) < 0'i32: raiseOSError(osLastError(), filename)
|
||||
result = {}
|
||||
if (a.st_mode and S_IRUSR.Mode) != 0.Mode: result.incl(fpUserRead)
|
||||
if (a.st_mode and S_IWUSR.Mode) != 0.Mode: result.incl(fpUserWrite)
|
||||
if (a.st_mode and S_IXUSR.Mode) != 0.Mode: result.incl(fpUserExec)
|
||||
|
||||
if (a.st_mode and S_IRGRP.Mode) != 0.Mode: result.incl(fpGroupRead)
|
||||
if (a.st_mode and S_IWGRP.Mode) != 0.Mode: result.incl(fpGroupWrite)
|
||||
if (a.st_mode and S_IXGRP.Mode) != 0.Mode: result.incl(fpGroupExec)
|
||||
|
||||
if (a.st_mode and S_IROTH.Mode) != 0.Mode: result.incl(fpOthersRead)
|
||||
if (a.st_mode and S_IWOTH.Mode) != 0.Mode: result.incl(fpOthersWrite)
|
||||
if (a.st_mode and S_IXOTH.Mode) != 0.Mode: result.incl(fpOthersExec)
|
||||
else:
|
||||
when useWinUnicode:
|
||||
wrapUnary(res, getFileAttributesW, filename)
|
||||
else:
|
||||
var res = getFileAttributesA(filename)
|
||||
if res == -1'i32: raiseOSError(osLastError(), filename)
|
||||
if (res and FILE_ATTRIBUTE_READONLY) != 0'i32:
|
||||
result = {fpUserExec, fpUserRead, fpGroupExec, fpGroupRead,
|
||||
fpOthersExec, fpOthersRead}
|
||||
else:
|
||||
result = {fpUserExec..fpOthersRead}
|
||||
|
||||
proc setFilePermissions*(filename: string, permissions: set[FilePermission],
|
||||
followSymlinks = true)
|
||||
{.rtl, extern: "nos$1", tags: [ReadDirEffect, WriteDirEffect],
|
||||
noWeirdTarget.} =
|
||||
## Sets the file permissions for `filename`.
|
||||
##
|
||||
## If `followSymlinks` set to true (default) and ``filename`` points to a
|
||||
## symlink, permissions are set to the file symlink points to.
|
||||
## `followSymlinks` set to false is a noop on Windows and some POSIX
|
||||
## systems (including Linux) on which `lchmod` is either unavailable or always
|
||||
## fails, given that symlinks permissions there are not observed.
|
||||
##
|
||||
## `OSError` is raised in case of an error.
|
||||
## On Windows, only the ``readonly`` flag is changed, depending on
|
||||
## ``fpUserWrite`` permission.
|
||||
##
|
||||
## See also:
|
||||
## * `getFilePermissions proc`_
|
||||
## * `FilePermission enum`_
|
||||
when defined(posix):
|
||||
var p = 0.Mode
|
||||
if fpUserRead in permissions: p = p or S_IRUSR.Mode
|
||||
if fpUserWrite in permissions: p = p or S_IWUSR.Mode
|
||||
if fpUserExec in permissions: p = p or S_IXUSR.Mode
|
||||
|
||||
if fpGroupRead in permissions: p = p or S_IRGRP.Mode
|
||||
if fpGroupWrite in permissions: p = p or S_IWGRP.Mode
|
||||
if fpGroupExec in permissions: p = p or S_IXGRP.Mode
|
||||
|
||||
if fpOthersRead in permissions: p = p or S_IROTH.Mode
|
||||
if fpOthersWrite in permissions: p = p or S_IWOTH.Mode
|
||||
if fpOthersExec in permissions: p = p or S_IXOTH.Mode
|
||||
|
||||
if not followSymlinks and filename.symlinkExists:
|
||||
when declared(lchmod):
|
||||
if lchmod(filename, cast[Mode](p)) != 0:
|
||||
raiseOSError(osLastError(), $(filename, permissions))
|
||||
else:
|
||||
if chmod(filename, cast[Mode](p)) != 0:
|
||||
raiseOSError(osLastError(), $(filename, permissions))
|
||||
else:
|
||||
when useWinUnicode:
|
||||
wrapUnary(res, getFileAttributesW, filename)
|
||||
else:
|
||||
var res = getFileAttributesA(filename)
|
||||
if res == -1'i32: raiseOSError(osLastError(), filename)
|
||||
if fpUserWrite in permissions:
|
||||
res = res and not FILE_ATTRIBUTE_READONLY
|
||||
else:
|
||||
res = res or FILE_ATTRIBUTE_READONLY
|
||||
when useWinUnicode:
|
||||
wrapBinary(res2, setFileAttributesW, filename, res)
|
||||
else:
|
||||
var res2 = setFileAttributesA(filename, res)
|
||||
if res2 == - 1'i32: raiseOSError(osLastError(), $(filename, permissions))
|
||||
|
||||
|
||||
const hasCCopyfile = defined(osx) and not defined(nimLegacyCopyFile)
|
||||
# xxx instead of `nimLegacyCopyFile`, support something like: `when osxVersion >= (10, 5)`
|
||||
|
||||
when hasCCopyfile:
|
||||
# `copyfile` API available since osx 10.5.
|
||||
{.push nodecl, header: "<copyfile.h>".}
|
||||
type
|
||||
copyfile_state_t {.nodecl.} = pointer
|
||||
copyfile_flags_t = cint
|
||||
proc copyfile_state_alloc(): copyfile_state_t
|
||||
proc copyfile_state_free(state: copyfile_state_t): cint
|
||||
proc c_copyfile(src, dst: cstring, state: copyfile_state_t, flags: copyfile_flags_t): cint {.importc: "copyfile".}
|
||||
# replace with `let` pending bootstrap >= 1.4.0
|
||||
var
|
||||
COPYFILE_DATA {.nodecl.}: copyfile_flags_t
|
||||
COPYFILE_XATTR {.nodecl.}: copyfile_flags_t
|
||||
{.pop.}
|
||||
|
||||
type
|
||||
CopyFlag* = enum ## Copy options.
|
||||
cfSymlinkAsIs, ## Copy symlinks as symlinks
|
||||
cfSymlinkFollow, ## Copy the files symlinks point to
|
||||
cfSymlinkIgnore ## Ignore symlinks
|
||||
|
||||
const copyFlagSymlink = {cfSymlinkAsIs, cfSymlinkFollow, cfSymlinkIgnore}
|
||||
|
||||
proc copyFile*(source, dest: string, options = {cfSymlinkFollow}) {.rtl,
|
||||
extern: "nos$1", tags: [ReadDirEffect, ReadIOEffect, WriteIOEffect],
|
||||
noWeirdTarget.} =
|
||||
## Copies a file from `source` to `dest`, where `dest.parentDir` must exist.
|
||||
##
|
||||
## On non-Windows OSes, `options` specify the way file is copied; by default,
|
||||
## if `source` is a symlink, copies the file symlink points to. `options` is
|
||||
## ignored on Windows: symlinks are skipped.
|
||||
##
|
||||
## If this fails, `OSError` is raised.
|
||||
##
|
||||
## On the Windows platform this proc will
|
||||
## copy the source file's attributes into dest.
|
||||
##
|
||||
## On other platforms you need
|
||||
## to use `getFilePermissions`_ and
|
||||
## `setFilePermissions`_
|
||||
## procs
|
||||
## to copy them by hand (or use the convenience `copyFileWithPermissions
|
||||
## proc`_),
|
||||
## otherwise `dest` will inherit the default permissions of a newly
|
||||
## created file for the user.
|
||||
##
|
||||
## If `dest` already exists, the file attributes
|
||||
## will be preserved and the content overwritten.
|
||||
##
|
||||
## On OSX, `copyfile` C api will be used (available since OSX 10.5) unless
|
||||
## `-d:nimLegacyCopyFile` is used.
|
||||
##
|
||||
## See also:
|
||||
## * `CopyFlag enum`_
|
||||
## * `copyDir proc`_
|
||||
## * `copyFileWithPermissions proc`_
|
||||
## * `tryRemoveFile proc`_
|
||||
## * `removeFile proc`_
|
||||
## * `moveFile proc`_
|
||||
|
||||
doAssert card(copyFlagSymlink * options) == 1, "There should be exactly " &
|
||||
"one cfSymlink* in options"
|
||||
let isSymlink = source.symlinkExists
|
||||
if isSymlink and (cfSymlinkIgnore in options or defined(windows)):
|
||||
return
|
||||
when defined(windows):
|
||||
when useWinUnicode:
|
||||
let s = newWideCString(source)
|
||||
let d = newWideCString(dest)
|
||||
if copyFileW(s, d, 0'i32) == 0'i32:
|
||||
raiseOSError(osLastError(), $(source, dest))
|
||||
else:
|
||||
if copyFileA(source, dest, 0'i32) == 0'i32:
|
||||
raiseOSError(osLastError(), $(source, dest))
|
||||
else:
|
||||
if isSymlink and cfSymlinkAsIs in options:
|
||||
createSymlink(expandSymlink(source), dest)
|
||||
else:
|
||||
when hasCCopyfile:
|
||||
let state = copyfile_state_alloc()
|
||||
# xxx `COPYFILE_STAT` could be used for one-shot
|
||||
# `copyFileWithPermissions`.
|
||||
let status = c_copyfile(source.cstring, dest.cstring, state,
|
||||
COPYFILE_DATA)
|
||||
if status != 0:
|
||||
let err = osLastError()
|
||||
discard copyfile_state_free(state)
|
||||
raiseOSError(err, $(source, dest))
|
||||
let status2 = copyfile_state_free(state)
|
||||
if status2 != 0: raiseOSError(osLastError(), $(source, dest))
|
||||
else:
|
||||
# generic version of copyFile which works for any platform:
|
||||
const bufSize = 8000 # better for memory manager
|
||||
var d, s: File
|
||||
if not open(s, source):raiseOSError(osLastError(), source)
|
||||
if not open(d, dest, fmWrite):
|
||||
close(s)
|
||||
raiseOSError(osLastError(), dest)
|
||||
var buf = alloc(bufSize)
|
||||
while true:
|
||||
var bytesread = readBuffer(s, buf, bufSize)
|
||||
if bytesread > 0:
|
||||
var byteswritten = writeBuffer(d, buf, bytesread)
|
||||
if bytesread != byteswritten:
|
||||
dealloc(buf)
|
||||
close(s)
|
||||
close(d)
|
||||
raiseOSError(osLastError(), dest)
|
||||
if bytesread != bufSize: break
|
||||
dealloc(buf)
|
||||
close(s)
|
||||
flushFile(d)
|
||||
close(d)
|
||||
|
||||
proc copyFileToDir*(source, dir: string, options = {cfSymlinkFollow})
|
||||
{.noWeirdTarget, since: (1,3,7).} =
|
||||
## Copies a file `source` into directory `dir`, which must exist.
|
||||
##
|
||||
## On non-Windows OSes, `options` specify the way file is copied; by default,
|
||||
## if `source` is a symlink, copies the file symlink points to. `options` is
|
||||
## ignored on Windows: symlinks are skipped.
|
||||
##
|
||||
## See also:
|
||||
## * `CopyFlag enum`_
|
||||
## * `copyFile proc`_
|
||||
if dir.len == 0: # treating "" as "." is error prone
|
||||
raise newException(ValueError, "dest is empty")
|
||||
copyFile(source, dir / source.lastPathPart, options)
|
||||
|
||||
|
||||
proc copyFileWithPermissions*(source, dest: string,
|
||||
ignorePermissionErrors = true,
|
||||
options = {cfSymlinkFollow}) {.noWeirdTarget.} =
|
||||
## Copies a file from `source` to `dest` preserving file permissions.
|
||||
##
|
||||
## On non-Windows OSes, `options` specify the way file is copied; by default,
|
||||
## if `source` is a symlink, copies the file symlink points to. `options` is
|
||||
## ignored on Windows: symlinks are skipped.
|
||||
##
|
||||
## This is a wrapper proc around `copyFile`_,
|
||||
## `getFilePermissions`_ and `setFilePermissions`_
|
||||
## procs on non-Windows platforms.
|
||||
##
|
||||
## On Windows this proc is just a wrapper for `copyFile proc`_ since
|
||||
## that proc already copies attributes.
|
||||
##
|
||||
## On non-Windows systems permissions are copied after the file itself has
|
||||
## been copied, which won't happen atomically and could lead to a race
|
||||
## condition. If `ignorePermissionErrors` is true (default), errors while
|
||||
## reading/setting file attributes will be ignored, otherwise will raise
|
||||
## `OSError`.
|
||||
##
|
||||
## See also:
|
||||
## * `CopyFlag enum`_
|
||||
## * `copyFile proc`_
|
||||
## * `copyDir proc`_
|
||||
## * `tryRemoveFile proc`_
|
||||
## * `removeFile proc`_
|
||||
## * `moveFile proc`_
|
||||
## * `copyDirWithPermissions proc`_
|
||||
copyFile(source, dest, options)
|
||||
when not defined(windows):
|
||||
try:
|
||||
setFilePermissions(dest, getFilePermissions(source), followSymlinks =
|
||||
(cfSymlinkFollow in options))
|
||||
except:
|
||||
if not ignorePermissionErrors:
|
||||
raise
|
||||
|
||||
when not declared(ENOENT) and not defined(windows):
|
||||
when defined(nimscript):
|
||||
when not defined(haiku):
|
||||
const ENOENT = cint(2) # 2 on most systems including Solaris
|
||||
else:
|
||||
const ENOENT = cint(-2147459069)
|
||||
else:
|
||||
var ENOENT {.importc, header: "<errno.h>".}: cint
|
||||
|
||||
when defined(windows) and not weirdTarget:
|
||||
when useWinUnicode:
|
||||
template deleteFile(file: untyped): untyped = deleteFileW(file)
|
||||
template setFileAttributes(file, attrs: untyped): untyped =
|
||||
setFileAttributesW(file, attrs)
|
||||
else:
|
||||
template deleteFile(file: untyped): untyped = deleteFileA(file)
|
||||
template setFileAttributes(file, attrs: untyped): untyped =
|
||||
setFileAttributesA(file, attrs)
|
||||
|
||||
proc tryRemoveFile*(file: string): bool {.rtl, extern: "nos$1", tags: [WriteDirEffect], noWeirdTarget.} =
|
||||
## Removes the `file`.
|
||||
##
|
||||
## If this fails, returns `false`. This does not fail
|
||||
## if the file never existed in the first place.
|
||||
##
|
||||
## On Windows, ignores the read-only attribute.
|
||||
##
|
||||
## See also:
|
||||
## * `copyFile proc`_
|
||||
## * `copyFileWithPermissions proc`_
|
||||
## * `removeFile proc`_
|
||||
## * `moveFile proc`_
|
||||
result = true
|
||||
when defined(windows):
|
||||
when useWinUnicode:
|
||||
let f = newWideCString(file)
|
||||
else:
|
||||
let f = file
|
||||
if deleteFile(f) == 0:
|
||||
result = false
|
||||
let err = getLastError()
|
||||
if err == ERROR_FILE_NOT_FOUND or err == ERROR_PATH_NOT_FOUND:
|
||||
result = true
|
||||
elif err == ERROR_ACCESS_DENIED and
|
||||
setFileAttributes(f, FILE_ATTRIBUTE_NORMAL) != 0 and
|
||||
deleteFile(f) != 0:
|
||||
result = true
|
||||
else:
|
||||
if unlink(file) != 0'i32 and errno != ENOENT:
|
||||
result = false
|
||||
|
||||
proc removeFile*(file: string) {.rtl, extern: "nos$1", tags: [WriteDirEffect], noWeirdTarget.} =
|
||||
## Removes the `file`.
|
||||
##
|
||||
## If this fails, `OSError` is raised. This does not fail
|
||||
## if the file never existed in the first place.
|
||||
##
|
||||
## On Windows, ignores the read-only attribute.
|
||||
##
|
||||
## See also:
|
||||
## * `removeDir proc`_
|
||||
## * `copyFile proc`_
|
||||
## * `copyFileWithPermissions proc`_
|
||||
## * `tryRemoveFile proc`_
|
||||
## * `moveFile proc`_
|
||||
if not tryRemoveFile(file):
|
||||
raiseOSError(osLastError(), file)
|
||||
|
||||
proc moveFile*(source, dest: string) {.rtl, extern: "nos$1",
|
||||
tags: [ReadDirEffect, ReadIOEffect, WriteIOEffect], noWeirdTarget.} =
|
||||
## Moves a file from `source` to `dest`.
|
||||
##
|
||||
## Symlinks are not followed: if `source` is a symlink, it is itself moved,
|
||||
## not its target.
|
||||
##
|
||||
## If this fails, `OSError` is raised.
|
||||
## If `dest` already exists, it will be overwritten.
|
||||
##
|
||||
## Can be used to `rename files`:idx:.
|
||||
##
|
||||
## See also:
|
||||
## * `moveDir proc`_
|
||||
## * `copyFile proc`_
|
||||
## * `copyFileWithPermissions proc`_
|
||||
## * `removeFile proc`_
|
||||
## * `tryRemoveFile proc`_
|
||||
|
||||
if not tryMoveFSObject(source, dest, isDir = false):
|
||||
when defined(windows):
|
||||
doAssert false
|
||||
else:
|
||||
# Fallback to copy & del
|
||||
copyFile(source, dest, {cfSymlinkAsIs})
|
||||
try:
|
||||
removeFile(source)
|
||||
except:
|
||||
discard tryRemoveFile(dest)
|
||||
raise
|
||||
1006
lib/std/private/ospaths2.nim
Normal file
1006
lib/std/private/ospaths2.nim
Normal file
File diff suppressed because it is too large
Load Diff
79
lib/std/private/ossymlinks.nim
Normal file
79
lib/std/private/ossymlinks.nim
Normal file
@@ -0,0 +1,79 @@
|
||||
include system/inclrtl
|
||||
import std/oserrors
|
||||
|
||||
import oscommon
|
||||
export symlinkExists
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/[syncio, assertions, widestrs]
|
||||
|
||||
when weirdTarget:
|
||||
discard
|
||||
elif defined(windows):
|
||||
import winlean, times
|
||||
elif defined(posix):
|
||||
import posix
|
||||
else:
|
||||
{.error: "OS module not ported to your operating system!".}
|
||||
|
||||
|
||||
when weirdTarget:
|
||||
{.pragma: noWeirdTarget, error: "this proc is not available on the NimScript/js target".}
|
||||
else:
|
||||
{.pragma: noWeirdTarget.}
|
||||
|
||||
|
||||
when defined(nimscript):
|
||||
# for procs already defined in scriptconfig.nim
|
||||
template noNimJs(body): untyped = discard
|
||||
elif defined(js):
|
||||
{.pragma: noNimJs, error: "this proc is not available on the js target".}
|
||||
else:
|
||||
{.pragma: noNimJs.}
|
||||
|
||||
|
||||
proc createSymlink*(src, dest: string) {.noWeirdTarget.} =
|
||||
## Create a symbolic link at `dest` which points to the item specified
|
||||
## by `src`. On most operating systems, will fail if a link already exists.
|
||||
##
|
||||
## .. warning:: Some OS's (such as Microsoft Windows) restrict the creation
|
||||
## of symlinks to root users (administrators) or users with developer mode enabled.
|
||||
##
|
||||
## See also:
|
||||
## * `createHardlink proc`_
|
||||
## * `expandSymlink proc`_
|
||||
|
||||
when defined(windows):
|
||||
const SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE = 2
|
||||
# allows anyone with developer mode on to create a link
|
||||
let flag = dirExists(src).int32 or SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
|
||||
when useWinUnicode:
|
||||
var wSrc = newWideCString(src)
|
||||
var wDst = newWideCString(dest)
|
||||
if createSymbolicLinkW(wDst, wSrc, flag) == 0 or getLastError() != 0:
|
||||
raiseOSError(osLastError(), $(src, dest))
|
||||
else:
|
||||
if createSymbolicLinkA(dest, src, flag) == 0 or getLastError() != 0:
|
||||
raiseOSError(osLastError(), $(src, dest))
|
||||
else:
|
||||
if symlink(src, dest) != 0:
|
||||
raiseOSError(osLastError(), $(src, dest))
|
||||
|
||||
proc expandSymlink*(symlinkPath: string): string {.noWeirdTarget.} =
|
||||
## Returns a string representing the path to which the symbolic link points.
|
||||
##
|
||||
## On Windows this is a noop, `symlinkPath` is simply returned.
|
||||
##
|
||||
## See also:
|
||||
## * `createSymlink proc`_
|
||||
when defined(windows):
|
||||
result = symlinkPath
|
||||
else:
|
||||
result = newString(maxSymlinkLen)
|
||||
var len = readlink(symlinkPath, result.cstring, maxSymlinkLen)
|
||||
if len < 0:
|
||||
raiseOSError(osLastError(), symlinkPath)
|
||||
if len > maxSymlinkLen:
|
||||
result = newString(len+1)
|
||||
len = readlink(symlinkPath, result.cstring, len)
|
||||
setLen(result, len)
|
||||
Reference in New Issue
Block a user