1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030 |
- #
- #
- # 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, working with directories,
- ## running shell commands, etc.
- ## .. importdoc:: symlinks.nim, appdirs.nim, dirs.nim, ospaths2.nim
- runnableExamples:
- let myFile = "/path/to/my/file.nim"
- assert splitPath(myFile) == (head: "/path/to/my", tail: "file.nim")
- when defined(posix):
- assert parentDir(myFile) == "/path/to/my"
- assert splitFile(myFile) == (dir: "/path/to/my", name: "file", ext: ".nim")
- assert myFile.changeFileExt("c") == "/path/to/my/file.c"
- ## **See also:**
- ## * `paths <paths.html>`_ and `files <files.html>`_ modules for high-level file manipulation
- ## * `osproc module <osproc.html>`_ for process communication beyond
- ## `execShellCmd proc`_
- ## * `uri module <uri.html>`_
- ## * `distros module <distros.html>`_
- ## * `dynlib module <dynlib.html>`_
- ## * `streams module <streams.html>`_
- import std/private/ospaths2
- export ospaths2
- import std/private/osfiles
- export osfiles
- import std/private/osdirs
- export osdirs
- import std/private/ossymlinks
- export ossymlinks
- import std/private/osappdirs
- export osappdirs
- import std/private/oscommon
- include system/inclrtl
- import std/private/since
- import std/cmdline
- export cmdline
- import strutils, pathnorm
- when defined(nimPreviewSlimSystem):
- import std/[syncio, assertions, widestrs]
- const weirdTarget = defined(nimscript) or defined(js)
- since (1, 1):
- const
- invalidFilenameChars* = {'/', '\\', ':', '*', '?', '"', '<', '>', '|', '^', '\0'} ## \
- ## Characters that may produce invalid filenames across Linux, Windows and Mac.
- ## You can check if your filename contains any of these chars and strip them for safety.
- ## Mac bans ``':'``, Linux bans ``'/'``, Windows bans all others.
- invalidFilenames* = [
- "CON", "PRN", "AUX", "NUL",
- "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
- "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"] ## \
- ## Filenames that may be invalid across Linux, Windows, Mac, etc.
- ## You can check if your filename match these and rename it for safety
- ## (Currently all invalid filenames are from Windows only).
- when weirdTarget:
- discard
- elif defined(windows):
- import winlean, times
- 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.}
- import std/oserrors
- export oserrors
- import std/envvars
- export envvars
- import std/private/osseps
- export osseps
- proc expandTilde*(path: string): string {.
- tags: [ReadEnvEffect, ReadIOEffect].} =
- ## Expands ``~`` or a path starting with ``~/`` to a full path, replacing
- ## ``~`` with `getHomeDir()`_ (otherwise returns ``path`` unmodified).
- ##
- ## Windows: this is still supported despite the Windows platform not having this
- ## convention; also, both ``~/`` and ``~\`` are handled.
- ##
- ## See also:
- ## * `getHomeDir proc`_
- ## * `getConfigDir proc`_
- ## * `getTempDir proc`_
- ## * `getCurrentDir proc`_
- ## * `setCurrentDir proc`_
- runnableExamples:
- assert expandTilde("~" / "appname.cfg") == getHomeDir() / "appname.cfg"
- assert expandTilde("~/foo/bar") == getHomeDir() / "foo/bar"
- assert expandTilde("/foo/bar") == "/foo/bar"
- if len(path) == 0 or path[0] != '~':
- result = path
- elif len(path) == 1:
- result = getHomeDir()
- elif (path[1] in {DirSep, AltSep}):
- result = getHomeDir() / path.substr(2)
- else:
- # TODO: handle `~bob` and `~bob/` which means home of bob
- result = path
- proc quoteShellWindows*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} =
- ## Quote `s`, so it can be safely passed to Windows API.
- ##
- ## Based on Python's `subprocess.list2cmdline`.
- ## See `this link <http://msdn.microsoft.com/en-us/library/17w5ykft.aspx>`_
- ## for more details.
- let needQuote = {' ', '\t'} in s or s.len == 0
- result = ""
- var backslashBuff = ""
- if needQuote:
- result.add("\"")
- for c in s:
- if c == '\\':
- backslashBuff.add(c)
- elif c == '\"':
- for i in 0..<backslashBuff.len*2:
- result.add('\\')
- backslashBuff.setLen(0)
- result.add("\\\"")
- else:
- if backslashBuff.len != 0:
- result.add(backslashBuff)
- backslashBuff.setLen(0)
- result.add(c)
- if backslashBuff.len > 0:
- result.add(backslashBuff)
- if needQuote:
- result.add(backslashBuff)
- result.add("\"")
- proc quoteShellPosix*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} =
- ## Quote ``s``, so it can be safely passed to POSIX shell.
- const safeUnixChars = {'%', '+', '-', '.', '/', '_', ':', '=', '@',
- '0'..'9', 'A'..'Z', 'a'..'z'}
- if s.len == 0:
- result = "''"
- elif s.allCharsInSet(safeUnixChars):
- result = s
- else:
- result = "'" & s.replace("'", "'\"'\"'") & "'"
- when defined(windows) or defined(posix) or defined(nintendoswitch):
- proc quoteShell*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} =
- ## Quote ``s``, so it can be safely passed to shell.
- ##
- ## When on Windows, it calls `quoteShellWindows proc`_.
- ## Otherwise, calls `quoteShellPosix proc`_.
- when defined(windows):
- result = quoteShellWindows(s)
- else:
- result = quoteShellPosix(s)
- proc quoteShellCommand*(args: openArray[string]): string =
- ## Concatenates and quotes shell arguments `args`.
- runnableExamples:
- when defined(posix):
- assert quoteShellCommand(["aaa", "", "c d"]) == "aaa '' 'c d'"
- when defined(windows):
- assert quoteShellCommand(["aaa", "", "c d"]) == "aaa \"\" \"c d\""
- # can't use `map` pending https://github.com/nim-lang/Nim/issues/8303
- result = ""
- for i in 0..<args.len:
- if i > 0: result.add " "
- result.add quoteShell(args[i])
- when not weirdTarget:
- proc c_system(cmd: cstring): cint {.
- importc: "system", header: "<stdlib.h>".}
- when not defined(windows):
- proc c_free(p: pointer) {.
- importc: "free", header: "<stdlib.h>".}
- const
- ExeExts* = ## Platform specific file extension for executables.
- ## On Windows ``["exe", "cmd", "bat"]``, on Posix ``[""]``.
- when defined(windows): ["exe", "cmd", "bat"] else: [""]
- proc findExe*(exe: string, followSymlinks: bool = true;
- extensions: openArray[string]=ExeExts): string {.
- tags: [ReadDirEffect, ReadEnvEffect, ReadIOEffect], noNimJs.} =
- ## Searches for `exe` in the current working directory and then
- ## in directories listed in the ``PATH`` environment variable.
- ##
- ## Returns `""` if the `exe` cannot be found. `exe`
- ## is added the `ExeExts`_ file extensions if it has none.
- ##
- ## If the system supports symlinks it also resolves them until it
- ## meets the actual file. This behavior can be disabled if desired
- ## by setting `followSymlinks = false`.
- if exe.len == 0: return
- template checkCurrentDir() =
- for ext in extensions:
- result = addFileExt(exe, ext)
- if fileExists(result): return
- when defined(posix):
- if '/' in exe: checkCurrentDir()
- else:
- checkCurrentDir()
- let path = getEnv("PATH")
- for candidate in split(path, PathSep):
- if candidate.len == 0: continue
- when defined(windows):
- var x = (if candidate[0] == '"' and candidate[^1] == '"':
- substr(candidate, 1, candidate.len-2) else: candidate) /
- exe
- else:
- var x = expandTilde(candidate) / exe
- for ext in extensions:
- var x = addFileExt(x, ext)
- if fileExists(x):
- when not (defined(windows) or defined(nintendoswitch)):
- while followSymlinks: # doubles as if here
- if x.symlinkExists:
- var r = newString(maxSymlinkLen)
- var len = readlink(x.cstring, r.cstring, maxSymlinkLen)
- if len < 0:
- raiseOSError(osLastError(), exe)
- if len > maxSymlinkLen:
- r = newString(len+1)
- len = readlink(x.cstring, r.cstring, len)
- setLen(r, len)
- if isAbsolute(r):
- x = r
- else:
- x = parentDir(x) / r
- else:
- break
- return x
- result = ""
- when weirdTarget:
- const times = "fake const"
- template Time(x: untyped): untyped = string
- proc getLastModificationTime*(file: string): times.Time {.rtl, extern: "nos$1", noWeirdTarget.} =
- ## Returns the `file`'s last modification time.
- ##
- ## See also:
- ## * `getLastAccessTime proc`_
- ## * `getCreationTime proc`_
- ## * `fileNewer proc`_
- when defined(posix):
- var res: Stat
- if stat(file, res) < 0'i32: raiseOSError(osLastError(), file)
- result = res.st_mtim.toTime
- else:
- var f: WIN32_FIND_DATA
- var h = findFirstFile(file, f)
- if h == -1'i32: raiseOSError(osLastError(), file)
- result = fromWinTime(rdFileTime(f.ftLastWriteTime))
- findClose(h)
- proc getLastAccessTime*(file: string): times.Time {.rtl, extern: "nos$1", noWeirdTarget.} =
- ## Returns the `file`'s last read or write access time.
- ##
- ## See also:
- ## * `getLastModificationTime proc`_
- ## * `getCreationTime proc`_
- ## * `fileNewer proc`_
- when defined(posix):
- var res: Stat
- if stat(file, res) < 0'i32: raiseOSError(osLastError(), file)
- result = res.st_atim.toTime
- else:
- var f: WIN32_FIND_DATA
- var h = findFirstFile(file, f)
- if h == -1'i32: raiseOSError(osLastError(), file)
- result = fromWinTime(rdFileTime(f.ftLastAccessTime))
- findClose(h)
- proc getCreationTime*(file: string): times.Time {.rtl, extern: "nos$1", noWeirdTarget.} =
- ## 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.
- ##
- ## See also:
- ## * `getLastModificationTime proc`_
- ## * `getLastAccessTime proc`_
- ## * `fileNewer proc`_
- when defined(posix):
- var res: Stat
- if stat(file, res) < 0'i32: raiseOSError(osLastError(), file)
- result = res.st_ctim.toTime
- else:
- var f: WIN32_FIND_DATA
- var h = findFirstFile(file, f)
- if h == -1'i32: raiseOSError(osLastError(), file)
- result = fromWinTime(rdFileTime(f.ftCreationTime))
- findClose(h)
- proc fileNewer*(a, b: string): bool {.rtl, extern: "nos$1", noWeirdTarget.} =
- ## Returns true if the file `a` is newer than file `b`, i.e. if `a`'s
- ## modification time is later than `b`'s.
- ##
- ## See also:
- ## * `getLastModificationTime proc`_
- ## * `getLastAccessTime proc`_
- ## * `getCreationTime proc`_
- when defined(posix):
- # If we don't have access to nanosecond resolution, use '>='
- when not StatHasNanoseconds:
- result = getLastModificationTime(a) >= getLastModificationTime(b)
- else:
- result = getLastModificationTime(a) > getLastModificationTime(b)
- else:
- result = getLastModificationTime(a) > getLastModificationTime(b)
- proc isAdmin*: bool {.noWeirdTarget.} =
- ## Returns whether the caller's process is a member of the Administrators local
- ## group (on Windows) or a root (on POSIX), via `geteuid() == 0`.
- when defined(windows):
- # Rewrite of the example from Microsoft Docs:
- # https://docs.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-checktokenmembership#examples
- # and corresponding PostgreSQL function:
- # https://doxygen.postgresql.org/win32security_8c.html#ae6b61e106fa5d6c5d077a9d14ee80569
- var ntAuthority = SID_IDENTIFIER_AUTHORITY(value: SECURITY_NT_AUTHORITY)
- var administratorsGroup: PSID
- if not isSuccess(allocateAndInitializeSid(addr ntAuthority,
- BYTE(2),
- SECURITY_BUILTIN_DOMAIN_RID,
- DOMAIN_ALIAS_RID_ADMINS,
- 0, 0, 0, 0, 0, 0,
- addr administratorsGroup)):
- raiseOSError(osLastError(), "could not get SID for Administrators group")
- try:
- var b: WINBOOL
- if not isSuccess(checkTokenMembership(0, administratorsGroup, addr b)):
- raiseOSError(osLastError(), "could not check access token membership")
- result = isSuccess(b)
- finally:
- if freeSid(administratorsGroup) != nil:
- raiseOSError(osLastError(), "failed to free SID for Administrators group")
- else:
- result = geteuid() == 0
- proc exitStatusLikeShell*(status: cint): cint =
- ## Converts exit code from `c_system` into a shell exit code.
- when defined(posix) and not weirdTarget:
- if WIFSIGNALED(status):
- # like the shell!
- 128 + WTERMSIG(status)
- else:
- WEXITSTATUS(status)
- else:
- status
- proc execShellCmd*(command: string): int {.rtl, extern: "nos$1",
- tags: [ExecIOEffect], noWeirdTarget.} =
- ## 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 (zero if there is no error).
- ## The proc does not return until the process has finished.
- ##
- ## To execute a program without having a shell involved, use `osproc.execProcess proc
- ## <osproc.html#execProcess,string,string,openArray[string],StringTableRef,set[ProcessOption]>`_.
- ##
- ## **Examples:**
- ## ```Nim
- ## discard execShellCmd("ls -la")
- ## ```
- result = exitStatusLikeShell(c_system(command))
- proc expandFilename*(filename: string): string {.rtl, extern: "nos$1",
- tags: [ReadDirEffect], noWeirdTarget.} =
- ## Returns the full (`absolute`:idx:) path of an existing file `filename`.
- ##
- ## Raises `OSError` in case of an error. Follows symlinks.
- when defined(windows):
- var bufsize = MAX_PATH.int32
- var unused: WideCString = nil
- var res = newWideCString("", bufsize)
- while true:
- var L = getFullPathNameW(newWideCString(filename), bufsize, res, unused)
- if L == 0'i32:
- raiseOSError(osLastError(), filename)
- elif L > bufsize:
- res = newWideCString("", L)
- bufsize = L
- else:
- result = res$L
- break
- # getFullPathName doesn't do case corrections, so we have to use this convoluted
- # way of retrieving the true filename
- for x in walkFiles(result):
- result = x
- if not fileExists(result) and not dirExists(result):
- # consider using: `raiseOSError(osLastError(), result)`
- raise newException(OSError, "file '" & result & "' does not exist")
- else:
- # according to Posix we don't need to allocate space for result pathname.
- # But we need to free return value with free(3).
- var r = realpath(filename, nil)
- if r.isNil:
- raiseOSError(osLastError(), filename)
- else:
- result = $r
- c_free(cast[pointer](r))
- proc getCurrentCompilerExe*(): string {.compileTime.} = discard
- ## This is `getAppFilename()`_ at compile time.
- ##
- ## Can be used to retrieve the currently executing
- ## Nim compiler from a Nim or nimscript program, or the nimble binary
- ## inside a nimble program (likewise with other binaries built from
- ## compiler API).
- proc createHardlink*(src, dest: string) {.noWeirdTarget.} =
- ## Create a hard link at `dest` which points to the item specified
- ## by `src`.
- ##
- ## .. warning:: Some OS's restrict the creation of hard links to
- ## root users (administrators).
- ##
- ## See also:
- ## * `createSymlink proc`_
- when defined(windows):
- var wSrc = newWideCString(src)
- var wDst = newWideCString(dest)
- if createHardLinkW(wDst, wSrc, nil) == 0:
- raiseOSError(osLastError(), $(src, dest))
- else:
- if link(src, dest) != 0:
- raiseOSError(osLastError(), $(src, dest))
- proc inclFilePermissions*(filename: string,
- permissions: set[FilePermission]) {.
- rtl, extern: "nos$1", tags: [ReadDirEffect, WriteDirEffect], noWeirdTarget.} =
- ## A convenience proc for:
- ## ```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], noWeirdTarget.} =
- ## A convenience proc for:
- ## ```nim
- ## setFilePermissions(filename, getFilePermissions(filename)-permissions)
- ## ```
- setFilePermissions(filename, getFilePermissions(filename)-permissions)
- when not weirdTarget and (defined(freebsd) or defined(dragonfly) or defined(netbsd)):
- proc sysctl(name: ptr cint, namelen: cuint, oldp: pointer, oldplen: ptr csize_t,
- newp: pointer, newplen: csize_t): cint
- {.importc: "sysctl",header: """#include <sys/types.h>
- #include <sys/sysctl.h>""".}
- const
- CTL_KERN = 1
- KERN_PROC = 14
- MAX_PATH = 1024
- when defined(freebsd):
- const KERN_PROC_PATHNAME = 12
- elif defined(netbsd):
- const KERN_PROC_ARGS = 48
- const KERN_PROC_PATHNAME = 5
- else:
- const KERN_PROC_PATHNAME = 9
- proc getApplFreebsd(): string =
- var pathLength = csize_t(0)
- when defined(netbsd):
- var req = [CTL_KERN.cint, KERN_PROC_ARGS.cint, -1.cint, KERN_PROC_PATHNAME.cint]
- else:
- var req = [CTL_KERN.cint, KERN_PROC.cint, KERN_PROC_PATHNAME.cint, -1.cint]
- # first call to get the required length
- var res = sysctl(addr req[0], 4, nil, addr pathLength, nil, 0)
- if res < 0:
- return ""
- result.setLen(pathLength)
- res = sysctl(addr req[0], 4, addr result[0], addr pathLength, nil, 0)
- if res < 0:
- return ""
- let realLen = len(cstring(result))
- setLen(result, realLen)
- when not weirdTarget and (defined(linux) or defined(solaris) or defined(bsd) or defined(aix)):
- proc getApplAux(procPath: string): string =
- result = newString(maxSymlinkLen)
- var len = readlink(procPath, result.cstring, maxSymlinkLen)
- if len > maxSymlinkLen:
- result = newString(len+1)
- len = readlink(procPath, result.cstring, len)
- setLen(result, len)
- when not weirdTarget and defined(openbsd):
- proc getApplOpenBsd(): string =
- # similar to getApplHeuristic, but checks current working directory
- when declared(paramStr):
- result = ""
- # POSIX guaranties that this contains the executable
- # as it has been executed by the calling process
- let exePath = paramStr(0)
- if len(exePath) == 0:
- return ""
- if exePath[0] == DirSep:
- # path is absolute
- result = exePath
- else:
- # not an absolute path, check if it's relative to the current working directory
- for i in 1..<len(exePath):
- if exePath[i] == DirSep:
- result = joinPath(getCurrentDir(), exePath)
- break
- if len(result) > 0:
- return expandFilename(result)
- # search in path
- for p in split(getEnv("PATH"), {PathSep}):
- var x = joinPath(p, exePath)
- if fileExists(x):
- return expandFilename(x)
- else:
- result = ""
- when not (defined(windows) or defined(macosx) or weirdTarget):
- proc getApplHeuristic(): string =
- when declared(paramStr):
- result = 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(getEnv("PATH"), {PathSep}):
- var x = joinPath(p, result)
- if fileExists(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>".}
- when defined(haiku):
- const
- PATH_MAX = 1024
- B_FIND_PATH_IMAGE_PATH = 1000
- proc find_path(codePointer: pointer, baseDirectory: cint, subPath: cstring,
- pathBuffer: cstring, bufferSize: csize_t): int32
- {.importc, header: "<FindDirectory.h>".}
- proc getApplHaiku(): string =
- result = newString(PATH_MAX)
- if find_path(nil, B_FIND_PATH_IMAGE_PATH, nil, result, PATH_MAX) == 0:
- let realLen = len(cstring(result))
- setLen(result, realLen)
- else:
- result = ""
- proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect], noWeirdTarget, raises: [].} =
- ## Returns the filename of the application's executable.
- ## This proc will resolve symlinks.
- ##
- ## Returns empty string when name is unavailable
- ##
- ## See also:
- ## * `getAppDir proc`_
- ## * `getCurrentCompilerExe proc`_
- # Linux: /proc/<pid>/exe
- # Solaris:
- # /proc/<pid>/object/a.out (filename only)
- # /proc/<pid>/path/a.out (complete pathname)
- when defined(windows):
- var bufsize = int32(MAX_PATH)
- var buf = newWideCString(bufsize)
- while true:
- var L = getModuleFileNameW(0, buf, bufsize)
- if L == 0'i32:
- result = "" # error!
- break
- elif L > bufsize:
- buf = newWideCString(L)
- bufsize = L
- else:
- result = buf$L
- break
- elif defined(macosx):
- var size = cuint32(0)
- getExecPath1(nil, size)
- result = newString(int(size))
- if getExecPath2(result.cstring, size):
- result = "" # error!
- if result.len > 0:
- try:
- result = result.expandFilename
- except OSError:
- result = ""
- else:
- when defined(linux) or defined(aix):
- result = getApplAux("/proc/self/exe")
- elif defined(solaris):
- result = getApplAux("/proc/" & $getpid() & "/path/a.out")
- elif defined(genode):
- result = "" # Not supported
- elif defined(freebsd) or defined(dragonfly) or defined(netbsd):
- result = getApplFreebsd()
- elif defined(haiku):
- result = getApplHaiku()
- elif defined(openbsd):
- result = try: getApplOpenBsd() except OSError: ""
- elif defined(nintendoswitch):
- result = ""
- # little heuristic that may work on other POSIX-like systems:
- if result.len == 0:
- result = try: getApplHeuristic() except OSError: ""
- proc getAppDir*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect], noWeirdTarget.} =
- ## Returns the directory of the application's executable.
- ##
- ## See also:
- ## * `getAppFilename proc`_
- result = splitFile(getAppFilename()).dir
- proc sleep*(milsecs: int) {.rtl, extern: "nos$1", tags: [TimeEffect], noWeirdTarget.} =
- ## Sleeps `milsecs` milliseconds.
- when defined(windows):
- winlean.sleep(int32(milsecs))
- else:
- var a, b: Timespec
- a.tv_sec = posix.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], noWeirdTarget.} =
- ## Returns the file size of `file` (in bytes). ``OSError`` is
- ## raised in case of an error.
- when defined(windows):
- var a: WIN32_FIND_DATA
- var resA = findFirstFile(file, a)
- if resA == -1: raiseOSError(osLastError(), file)
- result = rdFileSize(a)
- findClose(resA)
- else:
- var rawInfo: Stat
- if stat(file, rawInfo) < 0'i32:
- raiseOSError(osLastError(), file)
- rawInfo.st_size
- when defined(windows) or weirdTarget:
- type
- DeviceId* = int32
- FileId* = int64
- else:
- type
- DeviceId* = Dev
- FileId* = Ino
- type
- FileInfo* = object
- ## Contains information associated with a file object.
- ##
- ## See also:
- ## * `getFileInfo(handle) proc`_
- ## * `getFileInfo(file) proc`_
- ## * `getFileInfo(path, followSymlink) proc`_
- 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*: times.Time ## Time file was last accessed.
- lastWriteTime*: times.Time ## Time file was last modified/written to.
- creationTime*: times.Time ## Time file was created. Not supported on all systems!
- blockSize*: int ## Preferred I/O block size for this object.
- ## In some filesystems, this may vary from file to file.
- isSpecial*: bool ## Is file special? (on Unix some "files"
- ## can be special=non-regular like FIFOs,
- ## devices); for directories `isSpecial`
- ## is always `false`, for symlinks it is
- ## the same as for the link's target.
- template rawToFormalFileInfo(rawInfo, path, formalInfo): untyped =
- ## Transforms the native file info structure into the one nim uses.
- ## 'rawInfo' is either a 'BY_HANDLE_FILE_INFORMATION' structure on Windows,
- ## or a 'Stat' structure on posix
- when defined(windows):
- template merge(a, b): untyped =
- int64(
- (uint64(cast[uint32](a))) or
- (uint64(cast[uint32](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 = fromWinTime(rdFileTime(rawInfo.ftLastAccessTime))
- formalInfo.lastWriteTime = fromWinTime(rdFileTime(rawInfo.ftLastWriteTime))
- formalInfo.creationTime = fromWinTime(rdFileTime(rawInfo.ftCreationTime))
- formalInfo.blockSize = 8192 # xxx use Windows API instead of hardcoding
- # Retrieve basic permissions
- if (rawInfo.dwFileAttributes and FILE_ATTRIBUTE_READONLY) != 0'i32:
- formalInfo.permissions = {fpUserExec, fpUserRead, fpGroupExec,
- fpGroupRead, fpOthersExec, fpOthersRead}
- else:
- formalInfo.permissions = {fpUserExec..fpOthersRead}
- # Retrieve basic file kind
- if (rawInfo.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) != 0'i32:
- formalInfo.kind = pcDir
- else:
- formalInfo.kind = pcFile
- if (rawInfo.dwFileAttributes and FILE_ATTRIBUTE_REPARSE_POINT) != 0'i32:
- formalInfo.kind = succ(formalInfo.kind)
- else:
- template checkAndIncludeMode(rawMode, formalMode: untyped) =
- if (rawInfo.st_mode and rawMode.Mode) != 0.Mode:
- formalInfo.permissions.incl(formalMode)
- formalInfo.id = (rawInfo.st_dev, rawInfo.st_ino)
- formalInfo.size = rawInfo.st_size
- formalInfo.linkCount = rawInfo.st_nlink.BiggestInt
- formalInfo.lastAccessTime = rawInfo.st_atim.toTime
- formalInfo.lastWriteTime = rawInfo.st_mtim.toTime
- formalInfo.creationTime = rawInfo.st_ctim.toTime
- formalInfo.blockSize = rawInfo.st_blksize
- formalInfo.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, formalInfo.isSpecial) =
- if S_ISDIR(rawInfo.st_mode):
- (pcDir, false)
- elif S_ISLNK(rawInfo.st_mode):
- assert(path != "") # symlinks can't occur for file handles
- getSymlinkFileKind(path)
- else:
- (pcFile, not S_ISREG(rawInfo.st_mode))
- when defined(js):
- when not declared(FileHandle):
- type FileHandle = distinct int32
- when not declared(File):
- type File = object
- proc getFileInfo*(handle: FileHandle): FileInfo {.noWeirdTarget.} =
- ## 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, `OSError` is raised.
- ##
- ## See also:
- ## * `getFileInfo(file) proc`_
- ## * `getFileInfo(path, followSymlink) proc`_
- # 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 descriptor to a native file handle.
- var realHandle = get_osfhandle(handle)
- if getFileInformationByHandle(realHandle, addr rawInfo) == 0:
- raiseOSError(osLastError(), $handle)
- rawToFormalFileInfo(rawInfo, "", result)
- else:
- var rawInfo: Stat
- if fstat(handle, rawInfo) < 0'i32:
- raiseOSError(osLastError(), $handle)
- rawToFormalFileInfo(rawInfo, "", result)
- proc getFileInfo*(file: File): FileInfo {.noWeirdTarget.} =
- ## Retrieves file information for the file object.
- ##
- ## See also:
- ## * `getFileInfo(handle) proc`_
- ## * `getFileInfo(path, followSymlink) proc`_
- if file.isNil:
- raise newException(IOError, "File is nil")
- result = getFileInfo(file.getFileHandle())
- proc getFileInfo*(path: string, followSymlink = true): FileInfo {.noWeirdTarget.} =
- ## 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 object`_ will be slightly
- ## different across platforms, and in some cases, incomplete or inaccurate.
- ##
- ## When `followSymlink` is true (default), symlinks are followed and the
- ## information retrieved is information related to the symlink's target.
- ## Otherwise, information on the symlink itself is retrieved (however,
- ## field `isSpecial` is still determined from the target on Unix).
- ##
- ## 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, `OSError` is raised.
- ##
- ## See also:
- ## * `getFileInfo(handle) proc`_
- ## * `getFileInfo(file) proc`_
- when defined(windows):
- var
- handle = openHandle(path, followSymlink)
- rawInfo: BY_HANDLE_FILE_INFORMATION
- if handle == INVALID_HANDLE_VALUE:
- raiseOSError(osLastError(), path)
- if getFileInformationByHandle(handle, addr rawInfo) == 0:
- raiseOSError(osLastError(), path)
- rawToFormalFileInfo(rawInfo, path, result)
- discard closeHandle(handle)
- else:
- var rawInfo: Stat
- if followSymlink:
- if stat(path, rawInfo) < 0'i32:
- raiseOSError(osLastError(), path)
- else:
- if lstat(path, rawInfo) < 0'i32:
- raiseOSError(osLastError(), path)
- rawToFormalFileInfo(rawInfo, path, result)
- proc sameFileContent*(path1, path2: string): bool {.rtl, extern: "nos$1",
- tags: [ReadIOEffect], noWeirdTarget.} =
- ## Returns true if both pathname arguments refer to files with identical
- ## binary content.
- ##
- ## See also:
- ## * `sameFile proc`_
- var
- a, b: File
- if not open(a, path1): return false
- if not open(b, path2):
- close(a)
- return false
- let bufSize = getFileInfo(a).blockSize
- 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)
- proc isHidden*(path: string): bool {.noWeirdTarget.} =
- ## Determines whether ``path`` is hidden or not, using `this
- ## reference <https://en.wikipedia.org/wiki/Hidden_file_and_hidden_directory>`_.
- ##
- ## On Windows: returns true if it exists and its "hidden" attribute is set.
- ##
- ## On posix: returns true if ``lastPathPart(path)`` starts with ``.`` and is
- ## not ``.`` or ``..``.
- ##
- ## **Note**: paths are not normalized to determine `isHidden`.
- runnableExamples:
- when defined(posix):
- assert ".foo".isHidden
- assert not ".foo/bar".isHidden
- assert not ".".isHidden
- assert not "..".isHidden
- assert not "".isHidden
- assert ".foo/".isHidden
- when defined(windows):
- wrapUnary(attributes, getFileAttributesW, path)
- if attributes != -1'i32:
- result = (attributes and FILE_ATTRIBUTE_HIDDEN) != 0'i32
- else:
- let fileName = lastPathPart(path)
- result = len(fileName) >= 2 and fileName[0] == '.' and fileName != ".."
- proc getCurrentProcessId*(): int {.noWeirdTarget.} =
- ## Return current process ID.
- ##
- ## See also:
- ## * `osproc.processID(p: Process) <osproc.html#processID,Process>`_
- when defined(windows):
- proc GetCurrentProcessId(): DWORD {.stdcall, dynlib: "kernel32",
- importc: "GetCurrentProcessId".}
- result = GetCurrentProcessId().int
- else:
- result = getpid()
- proc setLastModificationTime*(file: string, t: times.Time) {.noWeirdTarget.} =
- ## Sets the `file`'s last modification time. `OSError` is raised in case of
- ## an error.
- when defined(posix):
- let unixt = posix.Time(t.toUnix)
- let micro = convert(Nanoseconds, Microseconds, t.nanosecond)
- var timevals = [Timeval(tv_sec: unixt, tv_usec: micro),
- Timeval(tv_sec: unixt, tv_usec: micro)] # [last access, last modification]
- if utimes(file, timevals.addr) != 0: raiseOSError(osLastError(), file)
- else:
- let h = openHandle(path = file, writeAccess = true)
- if h == INVALID_HANDLE_VALUE: raiseOSError(osLastError(), file)
- var ft = t.toWinTime.toFILETIME
- let res = setFileTime(h, nil, nil, ft.addr)
- discard h.closeHandle
- if res == 0'i32: raiseOSError(osLastError(), file)
- func isValidFilename*(filename: string, maxLen = 259.Positive): bool {.since: (1, 1).} =
- ## Returns `true` if `filename` is valid for crossplatform use.
- ##
- ## This is useful if you want to copy or save files across Windows, Linux, Mac, etc.
- ## It uses `invalidFilenameChars`, `invalidFilenames` and `maxLen` to verify the specified `filename`.
- ##
- ## See also:
- ##
- ## * https://docs.microsoft.com/en-us/dotnet/api/system.io.pathtoolongexception
- ## * https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
- ## * https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
- ##
- ## .. warning:: This only checks filenames, not whole paths
- ## (because basically you can mount anything as a path on Linux).
- runnableExamples:
- assert not isValidFilename(" foo") # Leading white space
- assert not isValidFilename("foo ") # Trailing white space
- assert not isValidFilename("foo.") # Ends with dot
- assert not isValidFilename("con.txt") # "CON" is invalid (Windows)
- assert not isValidFilename("OwO:UwU") # ":" is invalid (Mac)
- assert not isValidFilename("aux.bat") # "AUX" is invalid (Windows)
- assert not isValidFilename("") # Empty string
- assert not isValidFilename("foo/") # Filename is empty
- result = true
- let f = filename.splitFile()
- if unlikely(f.name.len + f.ext.len > maxLen or f.name.len == 0 or
- f.name[0] == ' ' or f.name[^1] == ' ' or f.name[^1] == '.' or
- find(f.name, invalidFilenameChars) != -1): return false
- for invalid in invalidFilenames:
- if cmpIgnoreCase(f.name, invalid) == 0: return false
- # deprecated declarations
- when not weirdTarget:
- template existsFile*(args: varargs[untyped]): untyped {.deprecated: "use fileExists".} =
- fileExists(args)
- template existsDir*(args: varargs[untyped]): untyped {.deprecated: "use dirExists".} =
- dirExists(args)
|