Nim/lib/pure/os.nim
coffeepots d9ed61fa62 Deprecate timeToTimeInfo, add str->time procs
* depreciate timeToTimeInfo, add str->time procs

initTime allows creating times from strings. initTimeFromStr offers a default format string compatible with $Time.
timeToTimeInterval changed to work properly.

* Spell {.deprecated.} correctly

* deprecated timeToTimeInfo, removed initTime procs

Is it even worth depreciating timeToTimeInfo rather than just removing it, considering it's just wrong and we have getLocalTime and getGMTime that actually work? Also, if I'm renaming timeToTimeInfo -> toTimeInfo etc, isn't the deprecated tag kind of redundant?

* Updated names for toTime and toTimeInterval procs

* Added type to toTime template

This should allow compilation of os.nim now that toTime is defined in times.nim.

* Updated toTime template to use {.gensym.}

Local templates default to 'bind' semantics, not 'gensym'. This was causing a failure to 'see' the local toTime template.
2016-04-06 10:36:17 +01:00

1593 lines
58 KiB
Nim

#
#
# Nim's Runtime Library
# (c) Copyright 2015 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module contains basic operating system facilities like
## retrieving environment variables, reading command line arguments,
## working with directories, running shell commands, etc.
{.deadCodeElim: on.}
{.push debugger: off.}
include "system/inclrtl"
import
strutils, times
when defined(windows):
import winlean
elif defined(posix):
import posix
else:
{.error: "OS module not ported to your operating system!".}
include "system/ansi_c"
include ospaths
when defined(posix):
when NoFakeVars:
const pathMax = 5000 # doesn't matter really. The concept of PATH_MAX
# doesn't work anymore on modern OSes.
else:
var
pathMax {.importc: "PATH_MAX", header: "<stdlib.h>".}: cint
proc osErrorMsg*(): string {.rtl, extern: "nos$1", deprecated.} =
## Retrieves the operating system's error flag, ``errno``.
## On Windows ``GetLastError`` is checked before ``errno``.
## Returns "" if no error occurred.
##
## **Deprecated since version 0.9.4**: use the other ``osErrorMsg`` proc.
result = ""
when defined(Windows):
var err = getLastError()
if err != 0'i32:
when useWinUnicode:
var msgbuf: WideCString
if formatMessageW(0x00000100 or 0x00001000 or 0x00000200,
nil, err, 0, addr(msgbuf), 0, nil) != 0'i32:
result = $msgbuf
if msgbuf != nil: localFree(cast[pointer](msgbuf))
else:
var msgbuf: cstring
if formatMessageA(0x00000100 or 0x00001000 or 0x00000200,
nil, err, 0, addr(msgbuf), 0, nil) != 0'i32:
result = $msgbuf
if msgbuf != nil: localFree(msgbuf)
if errno != 0'i32:
result = $os.strerror(errno)
{.push warning[deprecated]: off.}
proc raiseOSError*(msg: string = "") {.noinline, rtl, extern: "nos$1",
deprecated.} =
## raises an OSError exception with the given message ``msg``.
## If ``msg == ""``, the operating system's error flag
## (``errno``) is converted to a readable error message. On Windows
## ``GetLastError`` is checked before ``errno``.
## If no error flag is set, the message ``unknown OS error`` is used.
##
## **Deprecated since version 0.9.4**: use the other ``raiseOSError`` proc.
if len(msg) == 0:
var m = osErrorMsg()
raise newException(OSError, if m.len > 0: m else: "unknown OS error")
else:
raise newException(OSError, msg)
{.pop.}
when not defined(nimfix):
{.deprecated: [osError: raiseOSError].}
proc `==`*(err1, err2: OSErrorCode): bool {.borrow.}
proc `$`*(err: OSErrorCode): string {.borrow.}
proc osErrorMsg*(errorCode: OSErrorCode): string =
## Converts an OS error code into a human readable string.
##
## The error code can be retrieved using the ``osLastError`` proc.
##
## If conversion fails, or ``errorCode`` is ``0`` then ``""`` will be
## returned.
##
## On Windows, the ``-d:useWinAnsi`` compilation flag can be used to
## make this procedure use the non-unicode Win API calls to retrieve the
## message.
result = ""
when defined(Windows):
if errorCode != OSErrorCode(0'i32):
when useWinUnicode:
var msgbuf: WideCString
if formatMessageW(0x00000100 or 0x00001000 or 0x00000200,
nil, errorCode.int32, 0, addr(msgbuf), 0, nil) != 0'i32:
result = $msgbuf
if msgbuf != nil: localFree(cast[pointer](msgbuf))
else:
var msgbuf: cstring
if formatMessageA(0x00000100 or 0x00001000 or 0x00000200,
nil, errorCode.int32, 0, addr(msgbuf), 0, nil) != 0'i32:
result = $msgbuf
if msgbuf != nil: localFree(msgbuf)
else:
if errorCode != OSErrorCode(0'i32):
result = $os.strerror(errorCode.int32)
proc raiseOSError*(errorCode: OSErrorCode; additionalInfo = "") {.noinline.} =
## Raises an ``OSError`` exception. The ``errorCode`` will determine the
## message, ``osErrorMsg`` will be used to get this message.
##
## The error code can be retrieved using the ``osLastError`` proc.
##
## If the error code is ``0`` or an error message could not be retrieved,
## the message ``unknown OS error`` will be used.
var e: ref OSError; new(e)
e.errorCode = errorCode.int32
if additionalInfo.len == 0:
e.msg = osErrorMsg(errorCode)
else:
e.msg = osErrorMsg(errorCode) & "\nAdditional info: " & additionalInfo
if e.msg == "":
e.msg = "unknown OS error"
raise e
{.push stackTrace:off.}
proc osLastError*(): OSErrorCode =
## Retrieves the last operating system error code.
##
## This procedure is useful in the event when an OS call fails. In that case
## this procedure will return the error code describing the reason why the
## OS call failed. The ``OSErrorMsg`` procedure can then be used to convert
## this code into a string.
##
## **Warning**:
## The behaviour of this procedure varies between Windows and POSIX systems.
## On Windows some OS calls can reset the error code to ``0`` causing this
## procedure to return ``0``. It is therefore advised to call this procedure
## immediately after an OS call fails. On POSIX systems this is not a problem.
when defined(windows):
result = OSErrorCode(getLastError())
else:
result = OSErrorCode(errno)
{.pop.}
when defined(windows):
when useWinUnicode:
template wrapUnary(varname, winApiProc, arg: expr) {.immediate.} =
var varname = winApiProc(newWideCString(arg))
template wrapBinary(varname, winApiProc, arg, arg2: expr) {.immediate.} =
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: expr): expr = findNextFileW(a, b)
template getCommandLine(): expr = getCommandLineW()
template getFilename(f: expr): expr =
$cast[WideCString](addr(f.cFilename[0]))
else:
template findFirstFile(a, b: expr): expr = findFirstFileA(a, b)
template findNextFile(a, b: expr): expr = findNextFileA(a, b)
template getCommandLine(): expr = getCommandLineA()
template getFilename(f: expr): expr = $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)
proc existsFile*(filename: string): bool {.rtl, extern: "nos$1",
tags: [ReadDirEffect].} =
## Returns true if the file exists, false otherwise.
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 existsDir*(dir: string): bool {.rtl, extern: "nos$1", tags: [ReadDirEffect].} =
## Returns true iff the directory `dir` exists. If `dir` is a file, false
## is returned.
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
return stat(dir, res) >= 0'i32 and S_ISDIR(res.st_mode)
proc symlinkExists*(link: string): bool {.rtl, extern: "nos$1",
tags: [ReadDirEffect].} =
## Returns true iff the symlink `link` exists. Will return true
## regardless of whether the link points to a directory or file.
when defined(windows):
when useWinUnicode:
wrapUnary(a, getFileAttributesW, link)
else:
var a = getFileAttributesA(link)
if a != -1'i32:
result = (a and FILE_ATTRIBUTE_REPARSE_POINT) != 0'i32
else:
var res: Stat
return lstat(link, res) >= 0'i32 and S_ISLNK(res.st_mode)
proc fileExists*(filename: string): bool {.inline.} =
## Synonym for existsFile
existsFile(filename)
proc dirExists*(dir: string): bool {.inline.} =
## Synonym for existsDir
existsDir(dir)
proc getLastModificationTime*(file: string): Time {.rtl, extern: "nos$1".} =
## Returns the `file`'s last modification time.
when defined(posix):
var res: Stat
if stat(file, res) < 0'i32: raiseOSError(osLastError())
return res.st_mtime
else:
var f: WIN32_FIND_DATA
var h = findFirstFile(file, f)
if h == -1'i32: raiseOSError(osLastError())
result = winTimeToUnixTime(rdFileTime(f.ftLastWriteTime))
findClose(h)
proc getLastAccessTime*(file: string): Time {.rtl, extern: "nos$1".} =
## Returns the `file`'s last read or write access time.
when defined(posix):
var res: Stat
if stat(file, res) < 0'i32: raiseOSError(osLastError())
return res.st_atime
else:
var f: WIN32_FIND_DATA
var h = findFirstFile(file, f)
if h == -1'i32: raiseOSError(osLastError())
result = winTimeToUnixTime(rdFileTime(f.ftLastAccessTime))
findClose(h)
proc getCreationTime*(file: string): Time {.rtl, extern: "nos$1".} =
## Returns the `file`'s creation time.
##
## **Note:** Under POSIX OS's, the returned time may actually be the time at
## which the file's attribute's were last modified. See
## `here <https://github.com/nim-lang/Nim/issues/1058>`_ for details.
when defined(posix):
var res: Stat
if stat(file, res) < 0'i32: raiseOSError(osLastError())
return res.st_ctime
else:
var f: WIN32_FIND_DATA
var h = findFirstFile(file, f)
if h == -1'i32: raiseOSError(osLastError())
result = winTimeToUnixTime(rdFileTime(f.ftCreationTime))
findClose(h)
proc fileNewer*(a, b: string): bool {.rtl, extern: "nos$1".} =
## Returns true if the file `a` is newer than file `b`, i.e. if `a`'s
## modification time is later than `b`'s.
when defined(posix):
result = getLastModificationTime(a) - getLastModificationTime(b) >= 0
# Posix's resolution sucks so, we use '>=' for posix.
else:
result = getLastModificationTime(a) - getLastModificationTime(b) > 0
proc getCurrentDir*(): string {.rtl, extern: "nos$1", tags: [].} =
## Returns the `current working directory`:idx:.
const bufsize = 512 # should be enough
when defined(windows):
when useWinUnicode:
var res = newWideCString("", bufsize)
var L = getCurrentDirectoryW(bufsize, res)
if L == 0'i32: raiseOSError(osLastError())
result = res$L
else:
result = newString(bufsize)
var L = getCurrentDirectoryA(bufsize, result)
if L == 0'i32: raiseOSError(osLastError())
setLen(result, L)
else:
result = newString(bufsize)
if getcwd(result, bufsize) != nil:
setLen(result, c_strlen(result))
else:
raiseOSError(osLastError())
proc setCurrentDir*(newDir: string) {.inline, tags: [].} =
## Sets the `current working directory`:idx:; `OSError` is raised if
## `newDir` cannot been set.
when defined(Windows):
when useWinUnicode:
if setCurrentDirectoryW(newWideCString(newDir)) == 0'i32:
raiseOSError(osLastError())
else:
if setCurrentDirectoryA(newDir) == 0'i32: raiseOSError(osLastError())
else:
if chdir(newDir) != 0'i32: raiseOSError(osLastError())
proc expandFilename*(filename: string): string {.rtl, extern: "nos$1",
tags: [ReadDirEffect].} =
## Returns the full path of `filename`, raises OSError in case of an error.
when defined(windows):
const bufsize = 3072'i32
when useWinUnicode:
var unused: WideCString
var res = newWideCString("", bufsize div 2)
var L = getFullPathNameW(newWideCString(filename), bufsize, res, unused)
if L <= 0'i32 or L >= bufsize:
raiseOSError(osLastError())
result = res$L
else:
var unused: cstring
result = newString(bufsize)
var L = getFullPathNameA(filename, bufsize, result, unused)
if L <= 0'i32 or L >= bufsize: raiseOSError(osLastError())
setLen(result, L)
else:
# careful, realpath needs to take an allocated buffer according to Posix:
result = newString(pathMax)
var r = realpath(filename, result)
if r.isNil: raiseOSError(osLastError())
setLen(result, c_strlen(result))
when defined(Windows):
proc openHandle(path: string, followSymlink=true): Handle =
var flags = FILE_FLAG_BACKUP_SEMANTICS or FILE_ATTRIBUTE_NORMAL
if not followSymlink:
flags = flags or FILE_FLAG_OPEN_REPARSE_POINT
when useWinUnicode:
result = createFileW(
newWideCString(path), 0'i32,
FILE_SHARE_DELETE or FILE_SHARE_READ or FILE_SHARE_WRITE,
nil, OPEN_EXISTING, flags, 0
)
else:
result = createFileA(
path, 0'i32,
FILE_SHARE_DELETE or FILE_SHARE_READ or FILE_SHARE_WRITE,
nil, OPEN_EXISTING, flags, 0
)
proc sameFile*(path1, path2: string): bool {.rtl, extern: "nos$1",
tags: [ReadDirEffect].} =
## Returns true if both pathname arguments refer to the same physical
## file or directory. Raises an exception if any of the files does not
## exist or information about it can not be obtained.
##
## This proc will return true if given two alternative hard-linked or
## sym-linked paths to the same file or directory.
when defined(Windows):
var success = true
var f1 = openHandle(path1)
var f2 = openHandle(path2)
var lastErr: OSErrorCode
if f1 != INVALID_HANDLE_VALUE and f2 != INVALID_HANDLE_VALUE:
var fi1, fi2: BY_HANDLE_FILE_INFORMATION
if getFileInformationByHandle(f1, addr(fi1)) != 0 and
getFileInformationByHandle(f2, addr(fi2)) != 0:
result = fi1.dwVolumeSerialNumber == fi2.dwVolumeSerialNumber and
fi1.nFileIndexHigh == fi2.nFileIndexHigh and
fi1.nFileIndexLow == fi2.nFileIndexLow
else:
lastErr = osLastError()
success = false
else:
lastErr = osLastError()
success = false
discard closeHandle(f1)
discard closeHandle(f2)
if not success: raiseOSError(lastErr)
else:
var a, b: Stat
if stat(path1, a) < 0'i32 or stat(path2, b) < 0'i32:
raiseOSError(osLastError())
else:
result = a.st_dev == b.st_dev and a.st_ino == b.st_ino
proc sameFileContent*(path1, path2: string): bool {.rtl, extern: "nos$1",
tags: [ReadIOEffect].} =
## Returns true if both pathname arguments refer to files with identical
## binary content.
const
bufSize = 8192 # 8K buffer
var
a, b: File
if not open(a, path1): return false
if not open(b, path2):
close(a)
return false
var bufA = alloc(bufSize)
var bufB = alloc(bufSize)
while true:
var readA = readBuffer(a, bufA, bufSize)
var readB = readBuffer(b, bufB, bufSize)
if readA != readB:
result = false
break
if readA == 0:
result = true
break
result = equalMem(bufA, bufB, readA)
if not result: break
if readA != bufSize: break # end of file
dealloc(bufA)
dealloc(bufB)
close(a)
close(b)
type
FilePermission* = enum ## file access permission; modelled after UNIX
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
{.deprecated: [TFilePermission: FilePermission].}
proc getFilePermissions*(filename: string): set[FilePermission] {.
rtl, extern: "nos$1", tags: [ReadDirEffect].} =
## 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.
when defined(posix):
var a: Stat
if stat(filename, a) < 0'i32: raiseOSError(osLastError())
result = {}
if (a.st_mode and S_IRUSR) != 0'i32: result.incl(fpUserRead)
if (a.st_mode and S_IWUSR) != 0'i32: result.incl(fpUserWrite)
if (a.st_mode and S_IXUSR) != 0'i32: result.incl(fpUserExec)
if (a.st_mode and S_IRGRP) != 0'i32: result.incl(fpGroupRead)
if (a.st_mode and S_IWGRP) != 0'i32: result.incl(fpGroupWrite)
if (a.st_mode and S_IXGRP) != 0'i32: result.incl(fpGroupExec)
if (a.st_mode and S_IROTH) != 0'i32: result.incl(fpOthersRead)
if (a.st_mode and S_IWOTH) != 0'i32: result.incl(fpOthersWrite)
if (a.st_mode and S_IXOTH) != 0'i32: result.incl(fpOthersExec)
else:
when useWinUnicode:
wrapUnary(res, getFileAttributesW, filename)
else:
var res = getFileAttributesA(filename)
if res == -1'i32: raiseOSError(osLastError())
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]) {.
rtl, extern: "nos$1", tags: [WriteDirEffect].} =
## sets the file permissions for `filename`. `OSError` is raised in case of
## an error. On Windows, only the ``readonly`` flag is changed, depending on
## ``fpUserWrite``.
when defined(posix):
var p = 0'i32
if fpUserRead in permissions: p = p or S_IRUSR
if fpUserWrite in permissions: p = p or S_IWUSR
if fpUserExec in permissions: p = p or S_IXUSR
if fpGroupRead in permissions: p = p or S_IRGRP
if fpGroupWrite in permissions: p = p or S_IWGRP
if fpGroupExec in permissions: p = p or S_IXGRP
if fpOthersRead in permissions: p = p or S_IROTH
if fpOthersWrite in permissions: p = p or S_IWOTH
if fpOthersExec in permissions: p = p or S_IXOTH
if chmod(filename, p) != 0: raiseOSError(osLastError())
else:
when useWinUnicode:
wrapUnary(res, getFileAttributesW, filename)
else:
var res = getFileAttributesA(filename)
if res == -1'i32: raiseOSError(osLastError())
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())
proc copyFile*(source, dest: string) {.rtl, extern: "nos$1",
tags: [ReadIOEffect, WriteIOEffect].} =
## Copies a file from `source` to `dest`.
##
## 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() <#getFilePermissions>`_ and
## `setFilePermissions() <#setFilePermissions>`_ to copy them by hand (or use
## the convenience `copyFileWithPermissions() <#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.
when defined(Windows):
when useWinUnicode:
let s = newWideCString(source)
let d = newWideCString(dest)
if copyFileW(s, d, 0'i32) == 0'i32: raiseOSError(osLastError())
else:
if copyFileA(source, dest, 0'i32) == 0'i32: raiseOSError(osLastError())
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())
if not open(d, dest, fmWrite):
close(s)
raiseOSError(osLastError())
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())
if bytesread != bufSize: break
dealloc(buf)
close(s)
close(d)
proc moveFile*(source, dest: string) {.rtl, extern: "nos$1",
tags: [ReadIOEffect, WriteIOEffect].} =
## Moves a file from `source` to `dest`. If this fails, `OSError` is raised.
when defined(Windows):
when useWinUnicode:
let s = newWideCString(source)
let d = newWideCString(dest)
if moveFileW(s, d, 0'i32) == 0'i32: raiseOSError(osLastError())
else:
if moveFileA(source, dest, 0'i32) == 0'i32: raiseOSError(osLastError())
else:
if c_rename(source, dest) != 0'i32:
raiseOSError(osLastError(), $strerror(errno))
when not declared(ENOENT) and not defined(Windows):
when NoFakeVars:
const ENOENT = cint(2) # 2 on most systems including Solaris
else:
var ENOENT {.importc, header: "<errno.h>".}: cint
when defined(Windows):
when useWinUnicode:
template deleteFile(file: expr): expr {.immediate.} = deleteFileW(file)
template setFileAttributes(file, attrs: expr): expr {.immediate.} =
setFileAttributesW(file, attrs)
else:
template deleteFile(file: expr): expr {.immediate.} = deleteFileA(file)
template setFileAttributes(file, attrs: expr): expr {.immediate.} =
setFileAttributesA(file, attrs)
proc removeFile*(file: string) {.rtl, extern: "nos$1", tags: [WriteDirEffect].} =
## 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.
when defined(Windows):
when useWinUnicode:
let f = newWideCString(file)
else:
let f = file
if deleteFile(f) == 0:
if getLastError() == ERROR_ACCESS_DENIED:
if setFileAttributes(f, FILE_ATTRIBUTE_NORMAL) == 0:
raiseOSError(osLastError())
if deleteFile(f) == 0:
raiseOSError(osLastError())
else:
if c_remove(file) != 0'i32 and errno != ENOENT:
raiseOSError(osLastError(), $strerror(errno))
proc execShellCmd*(command: string): int {.rtl, extern: "nos$1",
tags: [ExecIOEffect].} =
## Executes a `shell command`:idx:.
##
## Command has the form 'program args' where args are the command
## line arguments given to program. The proc returns the error code
## of the shell when it has finished. The proc does not return until
## the process has finished. To execute a program without having a
## shell involved, use the `execProcess` proc of the `osproc`
## module.
when defined(linux):
result = c_system(command) shr 8
else:
result = c_system(command)
# Environment handling cannot be put into RTL, because the ``envPairs``
# iterator depends on ``environment``.
var
envComputed {.threadvar.}: bool
environment {.threadvar.}: seq[string]
when defined(windows):
# because we support Windows GUI applications, things get really
# messy here...
when useWinUnicode:
when defined(cpp):
proc strEnd(cstr: WideCString, c = 0'i32): WideCString {.
importcpp: "(NI16*)wcschr((const wchar_t *)#, #)", header: "<string.h>".}
else:
proc strEnd(cstr: WideCString, c = 0'i32): WideCString {.
importc: "wcschr", header: "<string.h>".}
else:
proc strEnd(cstr: cstring, c = 0'i32): cstring {.
importc: "strchr", header: "<string.h>".}
proc getEnvVarsC() =
if not envComputed:
environment = @[]
when useWinUnicode:
var
env = getEnvironmentStringsW()
e = env
if e == nil: return # an error occurred
while true:
var eend = strEnd(e)
add(environment, $e)
e = cast[WideCString](cast[ByteAddress](eend)+2)
if eend[1].int == 0: break
discard freeEnvironmentStringsW(env)
else:
var
env = getEnvironmentStringsA()
e = env
if e == nil: return # an error occurred
while true:
var eend = strEnd(e)
add(environment, $e)
e = cast[cstring](cast[ByteAddress](eend)+1)
if eend[1] == '\0': break
discard freeEnvironmentStringsA(env)
envComputed = true
else:
const
useNSGetEnviron = defined(macosx) and not defined(ios)
when useNSGetEnviron:
# From the manual:
# Shared libraries and bundles don't have direct access to environ,
# which is only available to the loader ld(1) when a complete program
# is being linked.
# The environment routines can still be used, but if direct access to
# environ is needed, the _NSGetEnviron() routine, defined in
# <crt_externs.h>, can be used to retrieve the address of environ
# at runtime.
proc NSGetEnviron(): ptr cstringArray {.
importc: "_NSGetEnviron", header: "<crt_externs.h>".}
else:
var gEnv {.importc: "environ".}: cstringArray
proc getEnvVarsC() =
# retrieves the variables of char** env of C's main proc
if not envComputed:
environment = @[]
when useNSGetEnviron:
var gEnv = NSGetEnviron()[]
var i = 0
while true:
if gEnv[i] == nil: break
add environment, $gEnv[i]
inc(i)
envComputed = true
proc findEnvVar(key: string): int =
getEnvVarsC()
var temp = key & '='
for i in 0..high(environment):
if startsWith(environment[i], temp): return i
return -1
proc getEnv*(key: string): TaintedString {.tags: [ReadEnvEffect].} =
## Returns the value of the `environment variable`:idx: named `key`.
##
## If the variable does not exist, "" is returned. To distinguish
## whether a variable exists or it's value is just "", call
## `existsEnv(key)`.
var i = findEnvVar(key)
if i >= 0:
return TaintedString(substr(environment[i], find(environment[i], '=')+1))
else:
var env = c_getenv(key)
if env == nil: return TaintedString("")
result = TaintedString($env)
proc existsEnv*(key: string): bool {.tags: [ReadEnvEffect].} =
## Checks whether the environment variable named `key` exists.
## Returns true if it exists, false otherwise.
if c_getenv(key) != nil: return true
else: return findEnvVar(key) >= 0
proc putEnv*(key, val: string) {.tags: [WriteEnvEffect].} =
## Sets the value of the `environment variable`:idx: named `key` to `val`.
## If an error occurs, `EInvalidEnvVar` is raised.
# Note: by storing the string in the environment sequence,
# we guarantee that we don't free the memory before the program
# ends (this is needed for POSIX compliance). It is also needed so that
# the process itself may access its modified environment variables!
var indx = findEnvVar(key)
if indx >= 0:
environment[indx] = key & '=' & val
else:
add environment, (key & '=' & val)
indx = high(environment)
when defined(unix):
if c_putenv(environment[indx]) != 0'i32:
raiseOSError(osLastError())
else:
when useWinUnicode:
var k = newWideCString(key)
var v = newWideCString(val)
if setEnvironmentVariableW(k, v) == 0'i32: raiseOSError(osLastError())
else:
if setEnvironmentVariableA(key, val) == 0'i32: raiseOSError(osLastError())
iterator envPairs*(): tuple[key, value: TaintedString] {.tags: [ReadEnvEffect].} =
## Iterate over all `environments variables`:idx:. In the first component
## of the tuple is the name of the current variable stored, in the second
## its value.
getEnvVarsC()
for i in 0..high(environment):
var p = find(environment[i], '=')
yield (TaintedString(substr(environment[i], 0, p-1)),
TaintedString(substr(environment[i], p+1)))
iterator walkFiles*(pattern: string): string {.tags: [ReadDirEffect].} =
## 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.
when defined(windows):
var
f: WIN32_FIND_DATA
res: int
res = findFirstFile(pattern, f)
if res != -1:
defer: findClose(res)
while true:
if not skipFindData(f) and
(f.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) == 0'i32:
# Windows bug/gotcha: 't*.nim' matches 'tfoo.nims' -.- so we check
# that the file extensions have the same length ...
let ff = getFilename(f)
let dotPos = searchExtPos(pattern)
let idx = ff.len - pattern.len + dotPos
if dotPos < 0 or idx >= ff.len or ff[idx] == '.' or
pattern[dotPos+1] == '*':
yield splitFile(pattern).dir / extractFilename(ff)
if findNextFile(res, f) == 0'i32: break
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)
yield $f.gl_pathv[i]
type
PathComponent* = enum ## Enumeration specifying a path component.
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
{.deprecated: [TPathComponent: PathComponent].}
proc staticWalkDir(dir: string; relative: bool): seq[
tuple[kind: PathComponent, path: string]] =
discard
iterator walkDir*(dir: string; relative=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 is returned.
## Walking is not recursive. If ``relative`` is true the resulting path is
## shortened to be relative to ``dir``.
## Example: This directory structure::
## dirA / dirB / fileB1.txt
## / dirC
## / fileA1.txt
## / fileA2.txt
##
## and this code:
##
## .. code-block:: Nim
## for kind, path in walkDir("dirA"):
## echo(path)
##
## produces this output (but not necessarily in this order!)::
## dirA/dirB
## dirA/dirC
## dirA/fileA1.txt
## dirA/fileA2.txt
when nimvm:
for k, v in items(staticWalkDir(dir, relative)):
yield (k, v)
else:
when defined(windows):
var f: WIN32_FIND_DATA
var h = findFirstFile(dir / "*", f)
if h != -1:
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: break
else:
var d = opendir(dir)
if d != nil:
defer: discard closedir(d)
while true:
var x = readdir(d)
if x == nil: break
var y = $x.d_name
if y != "." and y != "..":
var s: Stat
if not relative:
y = dir / y
var k = pcFile
when defined(linux) or defined(macosx) or defined(bsd):
if x.d_type != DT_UNKNOWN:
if x.d_type == DT_DIR: k = pcDir
if x.d_type == DT_LNK:
if dirExists(y): k = pcLinkToDir
else: k = succ(k)
yield (k, y)
continue
if lstat(y, s) < 0'i32: break
if S_ISDIR(s.st_mode): k = pcDir
if S_ISLNK(s.st_mode): k = succ(k)
yield (k, y)
iterator walkDirRec*(dir: string, filter={pcFile, pcDir}): string {.
tags: [ReadDirEffect].} =
## walks over the directory `dir` and yields for each file in `dir`. The
## full path for each file is returned.
## **Warning**:
## Modifying the directory structure while the iterator
## is traversing may result in undefined behavior!
##
## Walking is recursive. `filter` controls the behaviour of the iterator:
##
## --------------------- ---------------------------------------------
## filter meaning
## --------------------- ---------------------------------------------
## ``pcFile`` yield real files
## ``pcLinkToFile`` yield symbolic links to files
## ``pcDir`` follow real directories
## ``pcLinkToDir`` follow symbolic links to directories
## --------------------- ---------------------------------------------
##
var stack = @[dir]
while stack.len > 0:
for k,p in walkDir(stack.pop()):
if k in filter:
case k
of pcFile, pcLinkToFile: yield p
of pcDir, pcLinkToDir: stack.add(p)
proc rawRemoveDir(dir: string) =
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)
else:
if rmdir(dir) != 0'i32 and errno != ENOENT: raiseOSError(osLastError())
proc removeDir*(dir: string) {.rtl, extern: "nos$1", tags: [
WriteDirEffect, ReadDirEffect], benign.} =
## 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.
for kind, path in walkDir(dir):
case kind
of pcFile, pcLinkToFile, pcLinkToDir: removeFile(path)
of pcDir: removeDir(path)
rawRemoveDir(dir)
proc rawCreateDir(dir: string) =
when defined(solaris):
if mkdir(dir, 0o777) != 0'i32 and errno != EEXIST and errno != ENOSYS:
raiseOSError(osLastError())
elif defined(unix):
if mkdir(dir, 0o777) != 0'i32 and errno != EEXIST:
raiseOSError(osLastError())
else:
when useWinUnicode:
wrapUnary(res, createDirectoryW, dir)
else:
var res = createDirectoryA(dir)
if res == 0'i32 and getLastError() != 183'i32:
raiseOSError(osLastError())
proc createDir*(dir: string) {.rtl, extern: "nos$1", tags: [WriteDirEffect].} =
## 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 path already exists because for most usages this does not
## indicate an error.
var omitNext = false
when doslike:
omitNext = isAbsolute(dir)
for i in 1.. dir.len-1:
if dir[i] in {DirSep, AltSep}:
if omitNext:
omitNext = false
else:
rawCreateDir(substr(dir, 0, i-1))
rawCreateDir(dir)
proc copyDir*(source, dest: string) {.rtl, extern: "nos$1",
tags: [WriteIOEffect, ReadIOEffect], benign.} =
## Copies a directory from `source` to `dest`.
##
## 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. To preserve attributes recursively on
## these platforms use `copyDirWithPermissions() <#copyDirWithPermissions>`_.
createDir(dest)
for kind, path in walkDir(source):
var noSource = path.substr(source.len()+1)
case kind
of pcFile:
copyFile(path, dest / noSource)
of pcDir:
copyDir(path, dest / noSource)
else: discard
proc createSymlink*(src, dest: string) =
## Create a symbolic link at `dest` which points to the item specified
## by `src`. On most operating systems, will fail if a lonk
##
## **Warning**:
## Some OS's (such as Microsoft Windows) restrict the creation
## of symlinks to root users (administrators).
when defined(Windows):
let flag = dirExists(src).int32
when useWinUnicode:
var wSrc = newWideCString(src)
var wDst = newWideCString(dest)
if createSymbolicLinkW(wDst, wSrc, flag) == 0 or getLastError() != 0:
raiseOSError(osLastError())
else:
if createSymbolicLinkA(dest, src, flag) == 0 or getLastError() != 0:
raiseOSError(osLastError())
else:
if symlink(src, dest) != 0:
raiseOSError(osLastError())
proc createHardlink*(src, dest: string) =
## Create a hard link at `dest` which points to the item specified
## by `src`.
##
## **Warning**: Most OS's restrict the creation of hard links to
## root users (administrators) .
when defined(Windows):
when useWinUnicode:
var wSrc = newWideCString(src)
var wDst = newWideCString(dest)
if createHardLinkW(wDst, wSrc, nil) == 0:
raiseOSError(osLastError())
else:
if createHardLinkA(dest, src, nil) == 0:
raiseOSError(osLastError())
else:
if link(src, dest) != 0:
raiseOSError(osLastError())
proc parseCmdLine*(c: string): seq[string] {.
noSideEffect, rtl, extern: "nos$1".} =
## Splits a command line into several components;
## This proc is only occasionally useful, better use the `parseopt` module.
##
## On Windows, it uses the following parsing rules
## (see http://msdn.microsoft.com/en-us/library/17w5ykft.aspx ):
##
## * Arguments are delimited by white space, which is either a space or a tab.
## * The caret character (^) is not recognized as an escape character or
## delimiter. The character is handled completely by the command-line parser
## in the operating system before being passed to the argv array in the
## program.
## * A string surrounded by double quotation marks ("string") is interpreted
## as a single argument, regardless of white space contained within. A
## quoted string can be embedded in an argument.
## * A double quotation mark preceded by a backslash (\") is interpreted as a
## literal double quotation mark character (").
## * Backslashes are interpreted literally, unless they immediately precede
## a double quotation mark.
## * If an even number of backslashes is followed by a double quotation mark,
## one backslash is placed in the argv array for every pair of backslashes,
## and the double quotation mark is interpreted as a string delimiter.
## * If an odd number of backslashes is followed by a double quotation mark,
## one backslash is placed in the argv array for every pair of backslashes,
## and the double quotation mark is "escaped" by the remaining backslash,
## causing a literal double quotation mark (") to be placed in argv.
##
## On Posix systems, it uses the following parsing rules:
## Components are separated by whitespace unless the whitespace
## occurs within ``"`` or ``'`` quotes.
result = @[]
var i = 0
var a = ""
while true:
setLen(a, 0)
# eat all delimiting whitespace
while c[i] == ' ' or c[i] == '\t' or c [i] == '\l' or c [i] == '\r' : inc(i)
when defined(windows):
# parse a single argument according to the above rules:
if c[i] == '\0': break
var inQuote = false
while true:
case c[i]
of '\0': break
of '\\':
var j = i
while c[j] == '\\': inc(j)
if c[j] == '"':
for k in 1..(j-i) div 2: a.add('\\')
if (j-i) mod 2 == 0:
i = j
else:
a.add('"')
i = j+1
else:
a.add(c[i])
inc(i)
of '"':
inc(i)
if not inQuote: inQuote = true
elif c[i] == '"':
a.add(c[i])
inc(i)
else:
inQuote = false
break
of ' ', '\t':
if not inQuote: break
a.add(c[i])
inc(i)
else:
a.add(c[i])
inc(i)
else:
case c[i]
of '\'', '\"':
var delim = c[i]
inc(i) # skip ' or "
while c[i] != '\0' and c[i] != delim:
add a, c[i]
inc(i)
if c[i] != '\0': inc(i)
of '\0': break
else:
while c[i] > ' ':
add(a, c[i])
inc(i)
add(result, a)
proc copyFileWithPermissions*(source, dest: string,
ignorePermissionErrors = true) =
## Copies a file from `source` to `dest` preserving file permissions.
##
## This is a wrapper proc around `copyFile() <#copyFile>`_,
## `getFilePermissions() <#getFilePermissions>`_ and `setFilePermissions()
## <#setFilePermissions>`_ on non Windows platform. On Windows this proc is
## just a wrapper for `copyFile() <#copyFile>`_ 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, errors while
## reading/setting file attributes will be ignored, otherwise will raise
## `OSError`.
copyFile(source, dest)
when not defined(Windows):
try:
setFilePermissions(dest, getFilePermissions(source))
except:
if not ignorePermissionErrors:
raise
proc copyDirWithPermissions*(source, dest: string,
ignorePermissionErrors = true) {.rtl, extern: "nos$1",
tags: [WriteIOEffect, ReadIOEffect], benign.} =
## Copies a directory from `source` to `dest` preserving file permissions.
##
## If this fails, `OSError` is raised. This is a wrapper proc around `copyDir()
## <#copyDir>`_ and `copyFileWithPermissions() <#copyFileWithPermissions>`_
## on non Windows platforms. On Windows this proc is just a wrapper for
## `copyDir() <#copyDir>`_ 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, errors while
## reading/setting file attributes will be ignored, otherwise will raise
## `OSError`.
createDir(dest)
when not defined(Windows):
try:
setFilePermissions(dest, getFilePermissions(source))
except:
if not ignorePermissionErrors:
raise
for kind, path in walkDir(source):
var noSource = path.substr(source.len()+1)
case kind
of pcFile:
copyFileWithPermissions(path, dest / noSource, ignorePermissionErrors)
of pcDir:
copyDirWithPermissions(path, dest / noSource, ignorePermissionErrors)
else: discard
proc inclFilePermissions*(filename: string,
permissions: set[FilePermission]) {.
rtl, extern: "nos$1", tags: [ReadDirEffect, WriteDirEffect].} =
## a convenience procedure for:
##
## .. code-block:: nim
## setFilePermissions(filename, getFilePermissions(filename)+permissions)
setFilePermissions(filename, getFilePermissions(filename)+permissions)
proc exclFilePermissions*(filename: string,
permissions: set[FilePermission]) {.
rtl, extern: "nos$1", tags: [ReadDirEffect, WriteDirEffect].} =
## a convenience procedure for:
##
## .. code-block:: nim
## setFilePermissions(filename, getFilePermissions(filename)-permissions)
setFilePermissions(filename, getFilePermissions(filename)-permissions)
include ospaths
proc expandSymlink*(symlinkPath: string): string =
## Returns a string representing the path to which the symbolic link points.
##
## On Windows this is a noop, ``symlinkPath`` is simply returned.
when defined(windows):
result = symlinkPath
else:
result = newString(256)
var len = readlink(symlinkPath, result, 256)
if len < 0:
raiseOSError(osLastError())
if len > 256:
result = newString(len+1)
len = readlink(symlinkPath, result, len)
setLen(result, len)
when defined(nimdoc):
# Common forward declaration docstring block for parameter retrieval procs.
proc paramCount*(): int {.tags: [ReadIOEffect].} =
## Returns the number of `command line arguments`:idx: given to the
## application.
##
## If your binary was called without parameters this will return zero. You
## can later query each individual paramater with `paramStr() <#paramStr>`_
## or retrieve all of them in one go with `commandLineParams()
## <#commandLineParams>`_.
##
## **Availability**: On Posix there is no portable way to get the command
## line from a DLL and thus the proc isn't defined in this environment. You
## can test for its availability with `declared() <system.html#declared>`_.
## Example:
##
## .. code-block:: nim
## when declared(paramCount):
## # Use paramCount() here
## else:
## # Do something else!
proc paramStr*(i: int): TaintedString {.tags: [ReadIOEffect].} =
## Returns the `i`-th `command line argument`:idx: given to the application.
##
## `i` should be in the range `1..paramCount()`, the `EInvalidIndex`
## exception will be raised for invalid values. Instead of iterating over
## `paramCount() <#paramCount>`_ with this proc you can call the
## convenience `commandLineParams() <#commandLineParams>`_.
##
## It is possible to call ``paramStr(0)`` but this will return OS specific
## contents (usually the name of the invoked executable). You should avoid
## this and call `getAppFilename() <#getAppFilename>`_ instead.
##
## **Availability**: On Posix there is no portable way to get the command
## line from a DLL and thus the proc isn't defined in this environment. You
## can test for its availability with `declared() <system.html#declared>`_.
## Example:
##
## .. code-block:: nim
## when declared(paramStr):
## # Use paramStr() here
## else:
## # Do something else!
elif defined(windows):
# Since we support GUI applications with Nim, we sometimes generate
# a WinMain entry proc. But a WinMain proc has no access to the parsed
# command line arguments. The way to get them differs. Thus we parse them
# ourselves. This has the additional benefit that the program's behaviour
# is always the same -- independent of the used C compiler.
var
ownArgv {.threadvar.}: seq[string]
proc paramCount*(): int {.rtl, extern: "nos$1", tags: [ReadIOEffect].} =
# Docstring in nimdoc block.
if isNil(ownArgv): ownArgv = parseCmdLine($getCommandLine())
result = ownArgv.len-1
proc paramStr*(i: int): TaintedString {.rtl, extern: "nos$1",
tags: [ReadIOEffect].} =
# Docstring in nimdoc block.
if isNil(ownArgv): ownArgv = parseCmdLine($getCommandLine())
return TaintedString(ownArgv[i])
elif not defined(createNimRtl):
# On Posix, there is no portable way to get the command line from a DLL.
var
cmdCount {.importc: "cmdCount".}: cint
cmdLine {.importc: "cmdLine".}: cstringArray
proc paramStr*(i: int): TaintedString {.tags: [ReadIOEffect].} =
# Docstring in nimdoc block.
if i < cmdCount and i >= 0: return TaintedString($cmdLine[i])
raise newException(IndexError, "invalid index")
proc paramCount*(): int {.tags: [ReadIOEffect].} =
# Docstring in nimdoc block.
result = cmdCount-1
when declared(paramCount) or defined(nimdoc):
proc commandLineParams*(): seq[TaintedString] =
## Convenience proc which returns the command line parameters.
##
## This returns **only** the parameters. If you want to get the application
## executable filename, call `getAppFilename() <#getAppFilename>`_.
##
## **Availability**: On Posix there is no portable way to get the command
## line from a DLL and thus the proc isn't defined in this environment. You
## can test for its availability with `declared() <system.html#declared>`_.
## Example:
##
## .. code-block:: nim
## when declared(commandLineParams):
## # Use commandLineParams() here
## else:
## # Do something else!
result = @[]
for i in 1..paramCount():
result.add(paramStr(i))
when defined(linux) or defined(solaris) or defined(bsd) or defined(aix):
proc getApplAux(procPath: string): string =
result = newString(256)
var len = readlink(procPath, result, 256)
if len > 256:
result = newString(len+1)
len = readlink(procPath, result, len)
setLen(result, len)
when not (defined(windows) or defined(macosx)):
proc getApplHeuristic(): string =
when declared(paramStr):
result = string(paramStr(0))
# POSIX guaranties that this contains the executable
# as it has been executed by the calling process
if len(result) > 0 and result[0] != DirSep: # not an absolute path?
# iterate over any path in the $PATH environment variable
for p in split(string(getEnv("PATH")), {PathSep}):
var x = joinPath(p, result)
if existsFile(x): return x
else:
result = ""
when defined(macosx):
type
cuint32* {.importc: "unsigned int", nodecl.} = int
## This is the same as the type ``uint32_t`` in *C*.
# a really hacky solution: since we like to include 2 headers we have to
# define two procs which in reality are the same
proc getExecPath1(c: cstring, size: var cuint32) {.
importc: "_NSGetExecutablePath", header: "<sys/param.h>".}
proc getExecPath2(c: cstring, size: var cuint32): bool {.
importc: "_NSGetExecutablePath", header: "<mach-o/dyld.h>".}
proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect].} =
## Returns the filename of the application's executable.
##
## This procedure will resolve symlinks.
# Linux: /proc/<pid>/exe
# Solaris:
# /proc/<pid>/object/a.out (filename only)
# /proc/<pid>/path/a.out (complete pathname)
# FreeBSD: /proc/<pid>/file
when defined(windows):
when useWinUnicode:
var buf = newWideCString("", 256)
var len = getModuleFileNameW(0, buf, 256)
result = buf$len
else:
result = newString(256)
var len = getModuleFileNameA(0, result, 256)
setlen(result, int(len))
elif defined(macosx):
var size: cuint32
getExecPath1(nil, size)
result = newString(int(size))
if getExecPath2(result, size):
result = "" # error!
if result.len > 0:
result = result.expandFilename
else:
when defined(linux) or defined(aix) or defined(netbsd):
result = getApplAux("/proc/self/exe")
elif defined(solaris):
result = getApplAux("/proc/" & $getpid() & "/path/a.out")
elif defined(freebsd):
result = getApplAux("/proc/" & $getpid() & "/file")
# little heuristic that may work on other POSIX-like systems:
if result.len == 0:
result = getApplHeuristic()
proc getApplicationFilename*(): string {.rtl, extern: "nos$1", deprecated.} =
## Returns the filename of the application's executable.
## **Deprecated since version 0.8.12**: use ``getAppFilename``
## instead.
result = getAppFilename()
proc getApplicationDir*(): string {.rtl, extern: "nos$1", deprecated.} =
## Returns the directory of the application's executable.
## **Deprecated since version 0.8.12**: use ``getAppDir``
## instead.
result = splitFile(getAppFilename()).dir
proc getAppDir*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect].} =
## Returns the directory of the application's executable.
result = splitFile(getAppFilename()).dir
proc sleep*(milsecs: int) {.rtl, extern: "nos$1", tags: [TimeEffect].} =
## sleeps `milsecs` milliseconds.
when defined(windows):
winlean.sleep(int32(milsecs))
else:
var a, b: Timespec
a.tv_sec = Time(milsecs div 1000)
a.tv_nsec = (milsecs mod 1000) * 1000 * 1000
discard posix.nanosleep(a, b)
proc getFileSize*(file: string): BiggestInt {.rtl, extern: "nos$1",
tags: [ReadIOEffect].} =
## returns the file size of `file`. Can raise ``OSError``.
when defined(windows):
var a: WIN32_FIND_DATA
var resA = findFirstFile(file, a)
if resA == -1: raiseOSError(osLastError())
result = rdFileSize(a)
findClose(resA)
else:
var f: File
if open(f, file):
result = getFileSize(f)
close(f)
else: raiseOSError(osLastError())
when defined(Windows):
type
DeviceId* = int32
FileId* = int64
else:
type
DeviceId* = Dev
FileId* = Ino
type
FileInfo* = object
## Contains information associated with a file object.
id*: tuple[device: DeviceId, file: FileId] # Device and file id.
kind*: PathComponent # Kind of file object - directory, symlink, etc.
size*: BiggestInt # Size of file.
permissions*: set[FilePermission] # File permissions
linkCount*: BiggestInt # Number of hard links the file object has.
lastAccessTime*: Time # Time file was last accessed.
lastWriteTime*: Time # Time file was last modified/written to.
creationTime*: Time # Time file was created. Not supported on all systems!
template rawToFormalFileInfo(rawInfo, formalInfo): expr =
## Transforms the native file info structure into the one nim uses.
## 'rawInfo' is either a 'TBY_HANDLE_FILE_INFORMATION' structure on Windows,
## or a 'Stat' structure on posix
when defined(Windows):
template toTime(e: FILETIME): expr {.gensym.} = winTimeToUnixTime(rdFileTime(e)) # local templates default to bind semantics
template merge(a, b): expr = a or (b shl 32)
formalInfo.id.device = rawInfo.dwVolumeSerialNumber
formalInfo.id.file = merge(rawInfo.nFileIndexLow, rawInfo.nFileIndexHigh)
formalInfo.size = merge(rawInfo.nFileSizeLow, rawInfo.nFileSizeHigh)
formalInfo.linkCount = rawInfo.nNumberOfLinks
formalInfo.lastAccessTime = toTime(rawInfo.ftLastAccessTime)
formalInfo.lastWriteTime = toTime(rawInfo.ftLastWriteTime)
formalInfo.creationTime = toTime(rawInfo.ftCreationTime)
# Retrieve basic permissions
if (rawInfo.dwFileAttributes and FILE_ATTRIBUTE_READONLY) != 0'i32:
formalInfo.permissions = {fpUserExec, fpUserRead, fpGroupExec,
fpGroupRead, fpOthersExec, fpOthersRead}
else:
result.permissions = {fpUserExec..fpOthersRead}
# Retrieve basic file kind
result.kind = pcFile
if (rawInfo.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) != 0'i32:
formalInfo.kind = pcDir
if (rawInfo.dwFileAttributes and FILE_ATTRIBUTE_REPARSE_POINT) != 0'i32:
formalInfo.kind = succ(result.kind)
else:
template checkAndIncludeMode(rawMode, formalMode: expr) =
if (rawInfo.st_mode and rawMode) != 0'i32:
formalInfo.permissions.incl(formalMode)
formalInfo.id = (rawInfo.st_dev, rawInfo.st_ino)
formalInfo.size = rawInfo.st_size
formalInfo.linkCount = rawInfo.st_Nlink
formalInfo.lastAccessTime = rawInfo.st_atime
formalInfo.lastWriteTime = rawInfo.st_mtime
formalInfo.creationTime = rawInfo.st_ctime
result.permissions = {}
checkAndIncludeMode(S_IRUSR, fpUserRead)
checkAndIncludeMode(S_IWUSR, fpUserWrite)
checkAndIncludeMode(S_IXUSR, fpUserExec)
checkAndIncludeMode(S_IRGRP, fpGroupRead)
checkAndIncludeMode(S_IWGRP, fpGroupWrite)
checkAndIncludeMode(S_IXGRP, fpGroupExec)
checkAndIncludeMode(S_IROTH, fpOthersRead)
checkAndIncludeMode(S_IWOTH, fpOthersWrite)
checkAndIncludeMode(S_IXOTH, fpOthersExec)
formalInfo.kind = pcFile
if S_ISDIR(rawInfo.st_mode): formalInfo.kind = pcDir
if S_ISLNK(rawInfo.st_mode): formalInfo.kind.inc()
proc getFileInfo*(handle: FileHandle): FileInfo =
## Retrieves file information for the file object represented by the given
## handle.
##
## If the information cannot be retrieved, such as when the file handle
## is invalid, an error will be thrown.
# Done: ID, Kind, Size, Permissions, Link Count
when defined(Windows):
var rawInfo: BY_HANDLE_FILE_INFORMATION
# We have to use the super special '_get_osfhandle' call (wrapped above)
# To transform the C file descripter to a native file handle.
var realHandle = get_osfhandle(handle)
if getFileInformationByHandle(realHandle, addr rawInfo) == 0:
raiseOSError(osLastError())
rawToFormalFileInfo(rawInfo, result)
else:
var rawInfo: Stat
if fstat(handle, rawInfo) < 0'i32:
raiseOSError(osLastError())
rawToFormalFileInfo(rawInfo, result)
proc getFileInfo*(file: File): FileInfo =
if file.isNil:
raise newException(IOError, "File is nil")
result = getFileInfo(file.getFileHandle())
proc getFileInfo*(path: string, followSymlink = true): FileInfo =
## Retrieves file information for the file object pointed to by `path`.
##
## Due to intrinsic differences between operating systems, the information
## contained by the returned `FileInfo` structure will be slightly different
## across platforms, and in some cases, incomplete or inaccurate.
##
## When `followSymlink` is true, symlinks are followed and the information
## retrieved is information related to the symlink's target. Otherwise,
## information on the symlink itself is retrieved.
##
## If the information cannot be retrieved, such as when the path doesn't
## exist, or when permission restrictions prevent the program from retrieving
## file information, an error will be thrown.
when defined(Windows):
var
handle = openHandle(path, followSymlink)
rawInfo: BY_HANDLE_FILE_INFORMATION
if handle == INVALID_HANDLE_VALUE:
raiseOSError(osLastError())
if getFileInformationByHandle(handle, addr rawInfo) == 0:
raiseOSError(osLastError())
rawToFormalFileInfo(rawInfo, result)
discard closeHandle(handle)
else:
var rawInfo: Stat
if followSymlink:
if stat(path, rawInfo) < 0'i32:
raiseOSError(osLastError())
else:
if lstat(path, rawInfo) < 0'i32:
raiseOSError(osLastError())
rawToFormalFileInfo(rawInfo, result)
proc isHidden*(path: string): bool =
## Determines whether a given path is hidden or not. Returns false if the
## file doesn't exist. The given path must be accessible from the current
## working directory of the program.
##
## On Windows, a file is hidden if the file's 'hidden' attribute is set.
## On Unix-like systems, a file is hidden if it starts with a '.' (period)
## and is not *just* '.' or '..' ' ."
when defined(Windows):
when useWinUnicode:
wrapUnary(attributes, getFileAttributesW, path)
else:
var attributes = getFileAttributesA(path)
if attributes != -1'i32:
result = (attributes and FILE_ATTRIBUTE_HIDDEN) != 0'i32
else:
if fileExists(path):
let
fileName = extractFilename(path)
nameLen = len(fileName)
if nameLen == 2:
result = (fileName[0] == '.') and (fileName[1] != '.')
elif nameLen > 2:
result = (fileName[0] == '.') and (fileName[3] != '.')
{.pop.}