summary refs log tree commit diff stats
path: root/lib/pure/os.nim
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pure/os.nim')
-rw-r--r--[-rwxr-xr-x]lib/pure/os.nim2274
1 files changed, 907 insertions, 1367 deletions
diff --git a/lib/pure/os.nim b/lib/pure/os.nim
index 408fbd9d4..78ebb1c88 100755..100644
--- a/lib/pure/os.nim
+++ b/lib/pure/os.nim
@@ -1,1386 +1,604 @@
 #
 #
-#            Nimrod's Runtime Library
-#        (c) Copyright 2012 Andreas Rumpf
+#            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.}
+## retrieving environment variables, working with directories,
+## running shell commands, etc.
 
-{.push debugger: off.}
+## .. importdoc:: symlinks.nim, appdirs.nim, dirs.nim, ospaths2.nim
 
-include "system/inclrtl"
+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 std/[strutils, pathnorm]
 
-import
-  strutils, times
+when defined(nimPreviewSlimSystem):
+  import std/[syncio, assertions, widestrs]
 
-when defined(windows):
-  import winlean
-elif defined(posix): 
-  import posix
+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 std/[winlean, times]
+elif defined(posix):
+  import std/[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!".}
 
-include "system/ansi_c"
+when weirdTarget:
+  {.pragma: noWeirdTarget, error: "this proc is not available on the NimScript/js target".}
+else:
+  {.pragma: noWeirdTarget.}
 
-type
-  FReadEnv* = object of FReadIO   ## effect that denotes a read
-                                  ## from an environment variable
-  FWriteEnv* = object of FWriteIO ## effect that denotes a write
-                                  ## to an environment variable
-                        
-  FReadDir* = object of FReadIO   ## effect that denotes a write operation to
-                                  ## the directory structure
-  FWriteDir* = object of FWriteIO ## effect that denotes a write operation to
-                                  ## the directory structure
+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.}
 
-const
-  doslike = defined(windows) or defined(OS2) or defined(DOS)
-    # DOS-like filesystem
 
-when defined(Nimdoc): # only for proper documentation:
-  const
-    CurDir* = '.'
-      ## The constant string used by the operating system to refer to the
-      ## current directory.
-      ##
-      ## For example: '.' for POSIX or ':' for the classic Macintosh.
-
-    ParDir* = ".."
-      ## The constant string used by the operating system to refer to the
-      ## parent directory.
-      ##
-      ## For example: ".." for POSIX or "::" for the classic Macintosh.
-
-    DirSep* = '/'
-      ## The character used by the operating system to separate pathname
-      ## components, for example, '/' for POSIX or ':' for the classic
-      ## Macintosh.
-
-    AltSep* = '/'
-      ## An alternative character used by the operating system to separate
-      ## pathname components, or the same as `DirSep` if only one separator
-      ## character exists. This is set to '/' on Windows systems where `DirSep`
-      ## is a backslash.
-
-    PathSep* = ':'
-      ## The character conventionally used by the operating system to separate
-      ## search patch components (as in PATH), such as ':' for POSIX or ';' for
-      ## Windows.
-
-    FileSystemCaseSensitive* = True
-      ## True if the file system is case sensitive, false otherwise. Used by
-      ## `cmpPaths` to compare filenames properly.
-
-    ExeExt* = ""
-      ## The file extension of native executables. For example:
-      ## "" for POSIX, "exe" on Windows.
-
-    ScriptExt* = ""
-      ## The file extension of a script file. For example: "" for POSIX,
-      ## "bat" on Windows.
-
-    DynlibFormat* = "lib$1.so"
-      ## The format string to turn a filename into a `DLL`:idx: file (also
-      ## called `shared object`:idx: on some operating systems).
-
-elif defined(macos):
-  const
-    curdir* = ':'
-    pardir* = "::"
-    dirsep* = ':'
-    altsep* = dirsep
-    pathsep* = ','
-    FileSystemCaseSensitive* = false
-    ExeExt* = ""
-    ScriptExt* = ""
-    DynlibFormat* = "$1.dylib"
-
-  #  MacOS paths
-  #  ===========
-  #  MacOS directory separator is a colon ":" which is the only character not
-  #  allowed in filenames.
-  #
-  #  A path containing no colon or which begins with a colon is a partial path.
-  #  E.g. ":kalle:petter" ":kalle" "kalle"
-  #
-  #  All other paths are full (absolute) paths. E.g. "HD:kalle:" "HD:"
-  #  When generating paths, one is safe if one ensures that all partial paths
-  #  begin with a colon, and all full paths end with a colon.
-  #  In full paths the first name (e g HD above) is the name of a mounted
-  #  volume.
-  #  These names are not unique, because, for instance, two diskettes with the
-  #  same names could be inserted. This means that paths on MacOS are not
-  #  waterproof. In case of equal names the first volume found will do.
-  #  Two colons "::" are the relative path to the parent. Three is to the
-  #  grandparent etc.
-elif doslike:
-  const
-    curdir* = '.'
-    pardir* = ".."
-    dirsep* = '\\' # seperator within paths
-    altsep* = '/'
-    pathSep* = ';' # seperator between paths
-    FileSystemCaseSensitive* = false
-    ExeExt* = "exe"
-    ScriptExt* = "bat"
-    DynlibFormat* = "$1.dll"
-elif defined(PalmOS) or defined(MorphOS):
-  const
-    dirsep* = '/'
-    altsep* = dirsep
-    PathSep* = ';'
-    pardir* = ".."
-    FileSystemCaseSensitive* = false
-    ExeExt* = ""
-    ScriptExt* = ""
-    DynlibFormat* = "$1.prc"
-elif defined(RISCOS):
-  const
-    dirsep* = '.'
-    altsep* = '.'
-    pardir* = ".." # is this correct?
-    pathSep* = ','
-    FileSystemCaseSensitive* = true
-    ExeExt* = ""
-    ScriptExt* = ""
-    DynlibFormat* = "lib$1.so"
-else: # UNIX-like operating system
-  const
-    curdir* = '.'
-    pardir* = ".."
-    dirsep* = '/'
-    altsep* = dirsep
-    pathSep* = ':'
-    FileSystemCaseSensitive* = true
-    ExeExt* = ""
-    ScriptExt* = ""
-    DynlibFormat* = when defined(macosx): "lib$1.dylib" else: "lib$1.so"
-
-when defined(macosx) or defined(bsd):
-  var
-    pathMax {.importc: "PATH_MAX", header: "<stdlib.h>".}: cint
+import std/oserrors
+export oserrors
+import std/envvars
+export envvars
+
+import std/private/osseps
+export osseps
 
-const
-  ExtSep* = '.'
-    ## The character which separates the base filename from the extension;
-    ## for example, the '.' in ``os.nim``.
-
-proc OSErrorMsg*(): string {.rtl, extern: "nos$1".} =
-  ## Retrieves the operating system's error flag, ``errno``.
-  ## On Windows ``GetLastError`` is checked before ``errno``.
-  ## Returns "" if no error occured.
-  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(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)
-
-proc OSError*(msg: string = "") {.noinline, rtl, extern: "nos$1".} =
-  ## raises an EOS 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.
-  if len(msg) == 0:
-    var m = OSErrorMsg()
-    raise newException(EOS, if m.len > 0: m else: "unknown OS error")
-  else:
-    raise newException(EOS, msg)
 
-proc UnixToNativePath*(path: string): string {.
-  noSideEffect, rtl, extern: "nos$1".} =
-  ## Converts an UNIX-like path to a native one.
+
+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.
   ##
-  ## On an UNIX system this does nothing. Else it converts
-  ## '/', '.', '..' to the appropriate things.
-  when defined(unix):
+  ## 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:
-    var start: int
-    if path[0] == '/':
-      # an absolute path
-      when doslike:
-        result = r"C:\"
-      elif defined(macos):
-        result = "" # must not start with ':'
-      else:
-        result = $dirSep
-      start = 1
-    elif path[0] == '.' and path[1] == '/':
-      # current directory
-      result = $curdir
-      start = 2
-    else:
-      result = ""
-      start = 0
-
-    var i = start
-    while i < len(path): # ../../../ --> ::::
-      if path[i] == '.' and path[i+1] == '.' and path[i+2] == '/':
-        # parent directory
-        when defined(macos):
-          if result[high(result)] == ':':
-            add result, ':'
-          else:
-            add result, pardir
-        else:
-          add result, pardir & dirSep
-        inc(i, 3)
-      elif path[i] == '/':
-        add result, dirSep
-        inc(i)
-      else:
-        add result, path[i]
-        inc(i)
-
-when defined(windows):
-  template wrapUnary(varname, winApiProc, arg: expr) {.immediate.} =
-    var tmp = allocWideCString(arg)
-    var varname = winApiProc(tmp)
-    dealloc tmp
-
-  template wrapBinary(varname, winApiProc, arg, arg2: expr) {.immediate.} =
-    var tmp2 = allocWideCString(arg)
-    var varname = winApiProc(tmp2, arg2)
-    dealloc tmp2
-
-  when useWinUnicode:
-    proc FindFirstFile(a: string, b: var TWIN32_FIND_DATA): THandle =
-      var aa = allocWideCString(a)
-      result = FindFirstFileW(aa, b)
-      dealloc aa
-    template FindNextFile(a, b: expr): expr = FindNextFileW(a, b)
-    template getCommandLine(): expr = getCommandLineW()
-
-    proc skipFindData(f: TWIN32_FIND_DATA): bool {.inline.} =
-      result = f.cFilename[0].int == ord('.')
-
-    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()
-
-    proc skipFindData(f: TWIN32_FIND_DATA): bool {.inline.} =
-      result = f.cFilename[0] == '.'
-
-    template getFilename(f: expr): expr = $f.cFilename
-    
-proc existsFile*(filename: string): bool {.rtl, extern: "nos$1", 
-                                          tags: [FReadDir].} =
-  ## Returns true if the file exists, false otherwise.
-  when defined(windows):
-    when useWinUnicode:
-      wrapUnary(a, GetFileAttributesW, filename)
+    # 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:
-      var a = GetFileAttributesA(filename)
-    if a != -1'i32:
-      result = (a and FILE_ATTRIBUTE_DIRECTORY) == 0'i32
+      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:
-    var res: TStat
-    return stat(filename, res) >= 0'i32 and S_ISREG(res.st_mode)
+    result = "'" & s.replace("'", "'\"'\"'") & "'"
 
-proc existsDir*(dir: string): bool {.rtl, extern: "nos$1", tags: [FReadDir].} =
-  ## Returns true iff the directory `dir` exists. If `dir` is a file, false
-  ## is returned.
-  when defined(windows):
-    when useWinUnicode:
-      wrapUnary(a, GetFileAttributesW, dir)
+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:
-      var a = GetFileAttributesA(dir)
-    if a != -1'i32:
-      result = (a and FILE_ATTRIBUTE_DIRECTORY) != 0'i32
-  else:
-    var res: TStat
-    return stat(dir, res) >= 0'i32 and S_ISDIR(res.st_mode)
+      result = quoteShellPosix(s)
 
-proc getLastModificationTime*(file: string): TTime {.rtl, extern: "nos$1".} =
-  ## Returns the `file`'s last modification time.
-  when defined(posix):
-    var res: TStat
-    if stat(file, res) < 0'i32: OSError()
-    return res.st_mtime
-  else:
-    var f: TWIN32_Find_Data
-    var h = findfirstFile(file, f)
-    if h == -1'i32: OSError()
-    result = winTimeToUnixTime(rdFileTime(f.ftLastWriteTime))
-    findclose(h)
+  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\""
 
-proc getLastAccessTime*(file: string): TTime {.rtl, extern: "nos$1".} =
-  ## Returns the `file`'s last read or write access time.
-  when defined(posix):
-    var res: TStat
-    if stat(file, res) < 0'i32: OSError()
-    return res.st_atime
-  else:
-    var f: TWIN32_Find_Data
-    var h = findfirstFile(file, f)
-    if h == -1'i32: OSError()
-    result = winTimeToUnixTime(rdFileTime(f.ftLastAccessTime))
-    findclose(h)
+    # 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])
 
-proc getCreationTime*(file: string): TTime {.rtl, extern: "nos$1".} = 
-  ## Returns the `file`'s creation time.
-  when defined(posix):
-    var res: TStat
-    if stat(file, res) < 0'i32: OSError()
-    return res.st_ctime
-  else:
-    var f: TWIN32_Find_Data
-    var h = findfirstFile(file, f)
-    if h == -1'i32: OSError()
-    result = winTimeToUnixTime(rdFileTime(f.ftCreationTime))
-    findclose(h)
+when not weirdTarget:
+  proc c_system(cmd: cstring): cint {.
+    importc: "system", header: "<stdlib.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.
-  result = getLastModificationTime(a) - getLastModificationTime(b) > 0
+  when not defined(windows):
+    proc c_free(p: pointer) {.
+      importc: "free", header: "<stdlib.h>".}
 
-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 = cast[wideCString](alloc0(bufsize+1))
-      var L = GetCurrentDirectoryW(bufsize, res)
-      result = res$L
-      dealloc res
-      if L == 0'i32: OSError()
-    else:
-      result = newString(bufsize)
-      var L = GetCurrentDirectoryA(bufsize, result)
-      if L == 0'i32: OSError()
-      setLen(result, L)
-  else:
-    result = newString(bufsize)
-    if getcwd(result, bufsize) != nil:
-      setlen(result, c_strlen(result))
-    else:
-      OSError()
-
-proc setCurrentDir*(newDir: string) {.inline, tags: [].} =
-  ## Sets the `current working directory`:idx:; `EOS` is raised if
-  ## `newDir` cannot been set.
-  when defined(Windows):
-    when useWinUnicode:
-      var x = allocWideCString(newDir)
-      let res = SetCurrentDirectoryW(x)
-      dealloc x
-      if res == 0'i32: OSError()
-    else:
-      if SetCurrentDirectoryA(newDir) == 0'i32: OSError()
-  else:
-    if chdir(newDir) != 0'i32: OSError()
 
-proc JoinPath*(head, tail: string): string {.
-  noSideEffect, rtl, extern: "nos$1".} =
-  ## Joins two directory names to one.
-  ##
-  ## For example on Unix:
-  ##
-  ## .. code-block:: nimrod
-  ##   JoinPath("usr", "lib")
-  ##
-  ## results in:
+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.
   ##
-  ## .. code-block:: nimrod
-  ##   "usr/lib"
+  ## Returns `""` if the `exe` cannot be found. `exe`
+  ## is added the `ExeExts`_ file extensions if it has none.
   ##
-  ## If head is the empty string, tail is returned.
-  ## If tail is the empty string, head is returned.
-  if len(head) == 0:
-    result = tail
-  elif head[len(head)-1] in {DirSep, AltSep}:
-    if tail[0] in {DirSep, AltSep}:
-      result = head & substr(tail, 1)
-    else:
-      result = head & tail
+  ## 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:
-    if tail[0] in {DirSep, AltSep}:
-      result = head & tail
+    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:
-      result = head & DirSep & tail
-
-proc JoinPath*(parts: varargs[string]): string {.noSideEffect,
-  rtl, extern: "nos$1OpenArray".} =
-  ## The same as `JoinPath(head, tail)`, but works with any number
-  ## of directory parts.
-  result = parts[0]
-  for i in 1..high(parts):
-    result = JoinPath(result, parts[i])
-
-proc `/` * (head, tail: string): string {.noSideEffect.} =
-  ## The same as ``joinPath(head, tail)``
-  return joinPath(head, tail)
-
-proc SplitPath*(path: string): tuple[head, tail: string] {.
-  noSideEffect, rtl, extern: "nos$1".} =
-  ## Splits a directory into (head, tail), so that
-  ## ``JoinPath(head, tail) == path``.
-  ##
-  ## Examples: 
+      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.
   ##
-  ## .. code-block:: nimrod
-  ##   SplitPath("usr/local/bin") -> ("usr/local", "bin")
-  ##   SplitPath("usr/local/bin/") -> ("usr/local/bin", "")
-  ##   SplitPath("bin") -> ("", "bin")
-  ##   SplitPath("/bin") -> ("", "bin")
-  ##   SplitPath("") -> ("", "")
-  var sepPos = -1
-  for i in countdown(len(path)-1, 0):
-    if path[i] in {dirsep, altsep}:
-      sepPos = i
-      break
-  if sepPos >= 0:
-    result.head = substr(path, 0, sepPos-1)
-    result.tail = substr(path, sepPos+1)
+  ## 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:
-    result.head = ""
-    result.tail = path
+    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 parentDir*(path: string): string {.
-  noSideEffect, rtl, extern: "nos$1".} =
-  ## Returns the parent directory of `path`.
+proc getLastAccessTime*(file: string): times.Time {.rtl, extern: "nos$1", noWeirdTarget.} =
+  ## Returns the `file`'s last read or write access time.
   ##
-  ## This is often the same as the ``head`` result of ``splitPath``.
-  ## If there is no parent, ``path`` is returned.
-  ## | Example: ``parentDir("/usr/local/bin") == "/usr/local"``.
-  ## | Example: ``parentDir("/usr/local/bin/") == "/usr/local"``.
-  var
-    sepPos = -1
-    q = 1
-  if path[len(path)-1] in {dirsep, altsep}:
-    q = 2
-  for i in countdown(len(path)-q, 0):
-    if path[i] in {dirsep, altsep}:
-      sepPos = i
-      break
-  if sepPos >= 0:
-    result = substr(path, 0, sepPos-1)
+  ## 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:
-    result = path
+    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 isRootDir*(path: string): bool {.
-  noSideEffect, rtl, extern: "nos$1".} =
-  ## Checks whether a given `path` is a root directory 
-  var p = parentDir(path)
-  result = p == path or p.len == 0
-
-iterator parentDirs*(path: string, fromRoot=false, inclusive=true): string =
-  ## Walks over all parent directories of a given `path`
+proc getCreationTime*(file: string): times.Time {.rtl, extern: "nos$1", noWeirdTarget.} =
+  ## Returns the `file`'s creation time.
   ##
-  ## If `fromRoot` is set, the traversal will start from the file system root
-  ## diretory. If `inclusive` is set, the original argument will be included
-  ## in the traversal.
+  ## **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.
   ##
-  ## Relative paths won't be expanded by this proc. Instead, it will traverse
-  ## only the directories appearing in the relative path.
-  if not fromRoot:
-    var current = path
-    if inclusive: yield path
-    while true:
-      if current.isRootDir: break
-      current = current.parentDir
-      yield current
+  ## 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:
-    for i in countup(0, path.len - 2): # ignore the last /
-      # deal with non-normalized paths such as /foo//bar//baz
-      if path[i] in {dirsep, altsep} and (i == 0 or path[i-1] notin {dirsep, altsep}):
-        yield path.substr(0, i)
-
-    if inclusive: yield path
-
-proc `/../` * (head, tail: string): string {.noSideEffect.} =
-  ## The same as ``parentDir(head) / tail``
-  return parentDir(head) / tail
-
-proc normExt(ext: string): string =
-  if ext == "" or ext[0] == extSep: result = ext # no copy needed here
-  else: result = extSep & ext
-
-proc searchExtPos(s: string): int =
-  # BUGFIX: do not search until 0! .DS_Store is no file extension!
-  result = -1
-  for i in countdown(len(s)-1, 1):
-    if s[i] == extsep:
-      result = i
-      break
-    elif s[i] in {dirsep, altsep}:
-      break # do not skip over path
-
-proc splitFile*(path: string): tuple[dir, name, ext: string] {.
-  noSideEffect, rtl, extern: "nos$1".} =
-  ## Splits a filename into (dir, filename, extension).
-  ## `dir` does not end in `DirSep`.
-  ## `extension` includes the leading dot.
-  ##
-  ## Example:
-  ##
-  ## .. code-block:: nimrod
-  ##   var (dir, name, ext) = splitFile("usr/local/nimrodc.html")
-  ##   assert dir == "usr/local"
-  ##   assert name == "nimrodc"
-  ##   assert ext == ".html"
+    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.
   ##
-  ## If `path` has no extension, `ext` is the empty string.
-  ## If `path` has no directory component, `dir` is the empty string.
-  ## If `path` has no filename component, `name` and `ext` are empty strings.
-  if path.len == 0 or path[path.len-1] in {dirSep, altSep}:
-    result = (path, "", "")
-  else:
-    var sepPos = -1
-    var dotPos = path.len
-    for i in countdown(len(path)-1, 0):
-      if path[i] == ExtSep:
-        if dotPos == path.len and i > 0 and 
-            path[i-1] notin {dirsep, altsep}: dotPos = i
-      elif path[i] in {dirsep, altsep}:
-        sepPos = i
-        break
-    result.dir = substr(path, 0, sepPos-1)
-    result.name = substr(path, sepPos+1, dotPos-1)
-    result.ext = substr(path, dotPos)
-
-proc extractFilename*(path: string): string {.
-  noSideEffect, rtl, extern: "nos$1".} =
-  ## Extracts the filename of a given `path`. This is the same as 
-  ## ``name & ext`` from ``splitFile(path)``.
-  if path.len == 0 or path[path.len-1] in {dirSep, altSep}:
-    result = ""
+  ## 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 = splitPath(path).tail
+    result = getLastModificationTime(a) > getLastModificationTime(b)
 
-proc expandFilename*(filename: string): string {.rtl, extern: "nos$1",
-  tags: [FReadDir].} =
-  ## Returns the full path of `filename`, raises EOS in case of an error.
+
+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):
-    const bufsize = 3072'i32
-    when useWinUnicode:
-      var unused: widecstring
-      var res = cast[widecstring](alloc(bufsize*2+2))
-      var f = allocWideCString(filename)
-      var L = GetFullPathNameW(f, bufsize, res, unused)
-      dealloc f
-      if L <= 0'i32 or L >= bufsize: 
-        dealloc res
-        OSError()
-      result = res$L
-      dealloc res
-    else:
-      var unused: cstring
-      result = newString(bufsize)
-      var L = GetFullPathNameA(filename, bufsize, result, unused)
-      if L <= 0'i32 or L >= bufsize: OSError()
-      setLen(result, L)
-  elif defined(macosx) or defined(bsd):
-    # On Mac OS X 10.5, realpath does not allocate the buffer on its own
-    var pathBuffer: cstring = newString(pathMax)
-    var resultBuffer = realpath(filename, pathBuffer)
-    if resultBuffer == nil: OSError()
-    result = $resultBuffer
-  else:
-    var res = realpath(filename, nil)
-    if res == nil: OSError()
-    result = $res
-    c_free(res)
- 
-proc ChangeFileExt*(filename, ext: string): string {.
-  noSideEffect, rtl, extern: "nos$1".} =
-  ## Changes the file extension to `ext`.
-  ##
-  ## If the `filename` has no extension, `ext` will be added.
-  ## If `ext` == "" then any extension is removed.
-  ## `Ext` should be given without the leading '.', because some
-  ## filesystems may use a different character. (Although I know
-  ## of none such beast.)
-  var extPos = searchExtPos(filename)
-  if extPos < 0: result = filename & normExt(ext)
-  else: result = substr(filename, 0, extPos-1) & normExt(ext)
-
-proc addFileExt*(filename, ext: string): string {.
-  noSideEffect, rtl, extern: "nos$1".} =
-  ## Adds the file extension `ext` to `filename`, unless
-  ## `filename` already has an extension.
-  ##
-  ## `Ext` should be given without the leading '.', because some
-  ## filesystems may use a different character.
-  ## (Although I know of none such beast.)
-  var extPos = searchExtPos(filename)
-  if extPos < 0: result = filename & normExt(ext)
-  else: result = filename
-
-proc cmpPaths*(pathA, pathB: string): int {.
-  noSideEffect, rtl, extern: "nos$1".} =
-  ## Compares two paths.
-  ##
-  ## On a case-sensitive filesystem this is done
-  ## case-sensitively otherwise case-insensitively. Returns:
-  ##
-  ## | 0 iff pathA == pathB
-  ## | < 0 iff pathA < pathB
-  ## | > 0 iff pathA > pathB
-  if FileSystemCaseSensitive:
-    result = cmp(pathA, pathB)
-  else:
-    result = cmpIgnoreCase(pathA, pathB)
+    # 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")
 
-proc isAbsolute*(path: string): bool {.rtl, noSideEffect, extern: "nos$1".} =
-  ## Checks whether a given `path` is absolute.
-  ##
-  ## On Windows, network paths are considered absolute too.
-  when doslike:
-    var len = len(path)
-    result = (len > 1 and path[0] in {'/', '\\'}) or
-             (len > 2 and path[0] in Letters and path[1] == ':')
-  elif defined(macos):
-    result = path.len > 0 and path[0] != ':'
-  elif defined(RISCOS):
-    result = path[0] == '$'
-  elif defined(posix):
-    result = path[0] == '/'
-
-proc sameFile*(path1, path2: string): bool {.rtl, extern: "nos$1", 
-  tags: [FReadDir].} =
-  ## 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
-
-    when useWinUnicode:
-      var p1 = allocWideCString(path1)
-      var p2 = allocWideCString(path2)
-      template OpenHandle(path: expr): expr =
-        CreateFileW(path, 0'i32, FILE_SHARE_DELETE or FILE_SHARE_READ or
-          FILE_SHARE_WRITE, nil, OPEN_EXISTING,
-          FILE_FLAG_BACKUP_SEMANTICS or FILE_ATTRIBUTE_NORMAL, 0)
-
-      var f1 = OpenHandle(p1)
-      var f2 = OpenHandle(p2)
-      
-    else:
-      template OpenHandle(path: expr): expr =
-        CreateFileA(path, 0'i32, FILE_SHARE_DELETE or FILE_SHARE_READ or
-          FILE_SHARE_WRITE, nil, OPEN_EXISTING,
-          FILE_FLAG_BACKUP_SEMANTICS or FILE_ATTRIBUTE_NORMAL, 0)
-
-      var f1 = OpenHandle(path1)
-      var f2 = OpenHandle(path2)
-
-    if f1 != INVALID_HANDLE_VALUE and f2 != INVALID_HANDLE_VALUE:
-      var fi1, fi2: TBY_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: success = false
-    else: success = false
-
-    discard CloseHandle(f1)
-    discard CloseHandle(f2)
-    when useWinUnicode:
-      dealloc p1
-      dealloc p2
-
-    if not success: OSError()
   else:
-    var a, b: TStat
-    if stat(path1, a) < 0'i32 or stat(path2, b) < 0'i32:
-      OSError()
-    else:
-      result = a.st_dev == b.st_dev and a.st_ino == b.st_ino
+    result = geteuid() == 0
 
-proc sameFileContent*(path1, path2: string): bool {.rtl, extern: "nos$1",
-  tags: [FReadIO].} =
-  ## Returns True if both pathname arguments refer to files with identical
-  ## binary content.
-  const
-    bufSize = 8192 # 8K buffer
-  var
-    a, b: TFile
-  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)
 
-proc copyFile*(source, dest: string) {.rtl, extern: "nos$1", 
-  tags: [FReadIO, FWriteIO].} =
-  ## Copies a file from `source` to `dest`. If this fails,
-  ## `EOS` is raised.
-  when defined(Windows):
-    when useWinUnicode:
-      var s = allocWideCString(source)
-      var d = allocWideCString(dest)
-      let res = CopyFileW(s, d, 0'i32)
-      dealloc s
-      dealloc d
-      if res == 0'i32: OSError()
+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:
-      if CopyFileA(source, dest, 0'i32) == 0'i32: OSError()
+      WEXITSTATUS(status)
   else:
-    # generic version of copyFile which works for any platform:
-    const bufSize = 8000 # better for memory manager
-    var d, s: TFile
-    if not open(s, source): OSError()
-    if not open(d, dest, fmWrite):
-      close(s)
-      OSError()
-    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)
-          OSError()
-      if bytesread != bufSize: break
-    dealloc(buf)
-    close(s)
-    close(d)
-
-proc moveFile*(source, dest: string) {.rtl, extern: "nos$1", 
-  tags: [FReadIO, FWriteIO].} =
-  ## Moves a file from `source` to `dest`. If this fails, `EOS` is raised.
-  if crename(source, dest) != 0'i32: OSError()
-
-when not defined(ENOENT):
-  var ENOENT {.importc, header: "<errno.h>".}: cint
-
-proc removeFile*(file: string) {.rtl, extern: "nos$1", tags: [FWriteDir].} =
-  ## Removes the `file`. If this fails, `EOS` is raised. This does not fail
-  ## if the file never existed in the first place.
-  if cremove(file) != 0'i32 and errno != ENOENT: OSError()
-
-proc execShellCmd*(command: string): int {.rtl, extern: "nos$1", 
-  tags: [FExecIO].} =
+    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. 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.
-  result = csystem(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:
-    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 occured
-        while True:
-          var eend = strEnd(e)
-          add(environment, $e)
-          e = cast[wideCString](cast[TAddress](eend)+2)
-          if eend[1].int == 0: break
-        discard FreeEnvironmentStringsW(env)
-      else:
-        var
-          env = getEnvironmentStringsA()
-          e = env
-        if e == nil: return # an error occured
-        while True:
-          var eend = strEnd(e)
-          add(environment, $e)
-          e = cast[CString](cast[TAddress](eend)+1)
-          if eend[1] == '\0': break
-        discard FreeEnvironmentStringsA(env)
-      envComputed = true
-
-else:
-  const
-    useNSGetEnviron = defined(macosx) and 
-      (defined(createNimRtl) or defined(useNimRtl))
-  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: [FReadEnv].} =
-  ## Returns the value of the `environment variable`:idx: named `key`.
+  ## of the shell when it has finished (zero if there is no error).
+  ## The proc does not return until the process has finished.
   ##
-  ## 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 = cgetenv(key)
-    if env == nil: return TaintedString("")
-    result = TaintedString($env)
-
-proc existsEnv*(key: string): bool {.tags: [FReadEnv].} =
-  ## Checks whether the environment variable named `key` exists.
-  ## Returns true if it exists, false otherwise.
-  if cgetenv(key) != nil: return true
-  else: return findEnvVar(key) >= 0
-
-proc putEnv*(key, val: string) {.tags: [FWriteEnv].} =
-  ## 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 gurantee 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 cputenv(environment[indx]) != 0'i32:
-      OSError()
-  else:
-    when useWinUnicode:
-      var k = allocWideCString(key)
-      var v = allocWideCString(val)
-      let res = SetEnvironmentVariableW(k, v)
-      dealloc k
-      dealloc v
-      if res == 0'i32: OSError()
-    else:
-      if SetEnvironmentVariableA(key, val) == 0'i32: OSError()
-
-iterator envPairs*(): tuple[key, value: TaintedString] {.tags: [FReadEnv].} =
-  ## 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: [FReadDir].} =
-  ## Iterate over all the files that match the `pattern`. On POSIX this uses
-  ## the `glob`:idx: call.
+  ## To execute a program without having a shell involved, use `osproc.execProcess proc
+  ## <osproc.html#execProcess,string,string,openArray[string],StringTableRef,set[ProcessOption]>`_.
   ##
-  ## `pattern` is OS dependent, but at least the "\*.ext"
-  ## notation is supported.
-  when defined(windows):
-    var
-      f: TWin32FindData
-      res: int
-    res = findfirstFile(pattern, f)
-    if res != -1:
-      while true:
-        if not skipFindData(f):
-          yield splitFile(pattern).dir / extractFilename(getFilename(f))
-        if findnextFile(res, f) == 0'i32: break
-      findclose(res)
-  else: # here we use glob
-    var
-      f: TGlob
-      res: int
-    f.gl_offs = 0
-    f.gl_pathc = 0
-    f.gl_pathv = nil
-    res = glob(pattern, 0, nil, addr(f))
-    if res == 0:
-      for i in 0.. f.gl_pathc - 1:
-        assert(f.gl_pathv[i] != nil)
-        yield $f.gl_pathv[i]
-    globfree(addr(f))
+  ## **Examples:**
+  ##   ```Nim
+  ##   discard execShellCmd("ls -la")
+  ##   ```
+  result = exitStatusLikeShell(c_system(command))
 
-type
-  TPathComponent* = 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
-
-iterator walkDir*(dir: string): tuple[kind: TPathComponent, path: string] {.
-  tags: [FReadDir].} =
-  ## 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.
-  ## Example: This directory structure::
-  ##   dirA / dirB / fileB1.txt
-  ##        / dirC
-  ##        / fileA1.txt
-  ##        / fileA2.txt
-  ##
-  ## and this code:
-  ##
-  ## .. code-block:: Nimrod
-  ##     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 defined(windows):
-    var f: TWIN32_Find_Data
-    var h = findfirstFile(dir / "*", f)
-    if h != -1:
-      while true:
-        var k = pcFile
-        if not skipFindData(f):
-          if (f.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) != 0'i32:
-            k = pcDir
-          yield (k, dir / extractFilename(getFilename(f)))
-        if findnextFile(h, f) == 0'i32: break
-      findclose(h)
-  else:
-    var d = openDir(dir)
-    if d != nil:
-      while true:
-        var x = readDir(d)
-        if x == nil: break
-        var y = $x.d_name
-        if y != "." and y != "..":
-          var s: TStat
-          y = dir / y
-          if lstat(y, s) < 0'i32: break
-          var k = pcFile
-          if S_ISDIR(s.st_mode): k = pcDir
-          if S_ISLNK(s.st_mode): k = succ(k)
-          yield (k, y)
-      discard closeDir(d)
-
-iterator walkDirRec*(dir: string, filter={pcFile, pcDir}): string {.
-  tags: [FReadDir].} =
-  ## walks over the directory `dir` and yields for each file in `dir`. The 
-  ## full path for each file is returned.
-  ## Walking is recursive. `filter` controls the behaviour of the iterator:
+proc expandFilename*(filename: string): string {.rtl, extern: "nos$1",
+  tags: [ReadDirEffect], noWeirdTarget.} =
+  ## Returns the full (`absolute`:idx:) path of an existing file `filename`.
   ##
-  ## ---------------------   ---------------------------------------------
-  ## 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) = 
+  ## Raises `OSError` in case of an error. Follows symlinks.
   when defined(windows):
-    when useWinUnicode:
-      wrapUnary(res, RemoveDirectoryW, dir)
-    else:
-      var res = RemoveDirectoryA(dir)
-    if res == 0'i32 and GetLastError() != 3'i32 and 
-        GetLastError() != 18'i32: OSError()
-  else:
-    if rmdir(dir) != 0'i32 and errno != ENOENT: OSError()
-
-proc removeDir*(dir: string) {.rtl, extern: "nos$1", tags: [
-  FWriteDir, FReadDir].} =
-  ## Removes the directory `dir` including all subdirectories and files
-  ## in `dir` (recursively). If this fails, `EOS` 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(unix):
-    if mkdir(dir, 0o711) != 0'i32 and errno != EEXIST:
-      OSError()
+    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:
-    when useWinUnicode:
-      wrapUnary(res, CreateDirectoryW, dir)
+    # 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:
-      var res = CreateDirectoryA(dir)
-    if res == 0'i32 and GetLastError() != 183'i32:
-      OSError()
+      result = $r
+      c_free(cast[pointer](r))
 
-proc createDir*(dir: string) {.rtl, extern: "nos$1", tags: [FWriteDir].} =
-  ## Creates the `directory`:idx: `dir`.
+proc getCurrentCompilerExe*(): string {.compileTime.} = discard
+  ## Returns the path of the currently running Nim compiler or nimble executable.
   ##
-  ## The directory may contain several subdirectories that do not exist yet.
-  ## The full path is created. If this fails, `EOS` 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 defined(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: [FWriteIO, FReadIO].} =
-  ## Copies a directory from `source` to `dest`. If this fails, `EOS` is raised.
-  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: nil
-
-proc parseCmdLine*(c: string): seq[string] {.
-  noSideEffect, rtl, extern: "nos$1".} =
-  ## Splits a command line into several components;  
-  ## This proc is only occassionally useful, better use the `parseopt` module.
+  ## 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`.
   ##
-  ## On Windows, it uses the following parsing rules
-  ## (see http://msdn.microsoft.com/en-us/library/17w5ykft.aspx ):
+  ## .. warning:: Some OS's restrict the creation of hard links to
+  ##   root users (administrators).
   ##
-  ## * 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)
-    while c[i] == ' ' or c[i] == '\t': 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)
-
-type
-  TFilePermission* = 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
-
-proc getFilePermissions*(filename: string): set[TFilePermission] {.
-  rtl, extern: "nos$1", tags: [FReadDir].} =
-  ## 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: TStat
-    if stat(filename, a) < 0'i32: OSError()
-    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: OSError()
-    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[TFilePermission]) {.
-  rtl, extern: "nos$1", tags: [FWriteDir].} =
-  ## 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: OSError()
+  ## 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:
-    when useWinUnicode:
-      wrapUnary(res, GetFileAttributesW, filename)
-    else:
-      var res = GetFileAttributesA(filename)
-    if res == -1'i32: OSError()
-    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: OSError()
-  
-proc inclFilePermissions*(filename: string, 
-                          permissions: set[TFilePermission]) {.
-  rtl, extern: "nos$1", tags: [FReadDir, FWriteDir].} =
-  ## a convenience procedure for: 
-  ##
-  ## .. code-block:: nimrod
+    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[TFilePermission]) {.
-  rtl, extern: "nos$1", tags: [FReadDir, FWriteDir].} =
-  ## a convenience procedure for: 
-  ##
-  ## .. code-block:: nimrod
+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)
 
-proc getHomeDir*(): string {.rtl, extern: "nos$1", tags: [FReadEnv].} =
-  ## Returns the home directory of the current user.
-  when defined(windows): return string(getEnv("USERPROFILE")) & "\\"
-  else: return string(getEnv("HOME")) & "/"
-
-proc getConfigDir*(): string {.rtl, extern: "nos$1", tags: [FReadEnv].} =
-  ## Returns the config directory of the current user for applications.
-  when defined(windows): return string(getEnv("APPDATA")) & "\\"
-  else: return string(getEnv("HOME")) & "/.config/"
-
-proc getTempDir*(): string {.rtl, extern: "nos$1", tags: [FReadEnv].} =
-  ## Returns the temporary directory of the current user for applications to
-  ## save temporary files in.
-  when defined(windows): return string(getEnv("TEMP")) & "\\"
-  else: return "/tmp/"
-
-when defined(windows):
-  # Since we support GUI applications with Nimrod, 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: seq[string]
-
-  proc paramCount*(): int {.rtl, extern: "nos$1", tags: [FReadIO].} =
-    ## Returns the number of `command line arguments`:idx: given to the
-    ## application.
-    if isNil(ownArgv): ownArgv = parseCmdLine($getCommandLine())
-    result = ownArgv.len-1
-
-  proc paramStr*(i: int): TaintedString {.rtl, extern: "nos$1", 
-    tags: [FReadIO].} =
-    ## Returns the `i`-th `command line argument`:idx: given to the
-    ## application.
-    ##
-    ## `i` should be in the range `1..paramCount()`, else
-    ## the `EOutOfIndex` exception is raised.
-    if isNil(ownArgv): ownArgv = parseCmdLine($getCommandLine())
-    return TaintedString(ownArgv[i])
+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
 
-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 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)
 
-  proc paramStr*(i: int): TaintedString {.tags: [FReadIO].} =
-    if i < cmdCount and i >= 0: return TaintedString($cmdLine[i])
-    raise newException(EInvalidIndex, "invalid index")
+    if res < 0:
+      return ""
 
-  proc paramCount*(): int {.tags: [FReadIO].} = return cmdCount-1
+    result.setLen(pathLength)
+    res = sysctl(addr req[0], 4, addr result[0], addr pathLength, nil, 0)
 
-when defined(linux) or defined(solaris) or defined(bsd) or defined(aix):
+    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(256)
-    var len = readlink(procPath, result, 256)
-    if len > 256:
+    result = newString(maxSymlinkLen)
+    var len = readlink(procPath, result.cstring, maxSymlinkLen)
+    if len > maxSymlinkLen:
       result = newString(len+1)
-      len = readlink(procPath, result, len)
-    setlen(result, len)
+      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) {.
@@ -1388,105 +606,427 @@ when defined(macosx):
   proc getExecPath2(c: cstring, size: var cuint32): bool {.
     importc: "_NSGetExecutablePath", header: "<mach-o/dyld.h>".}
 
-proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [FReadIO].} =
+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.
-  ## **Note**: This does not work reliably on BSD.
+  ## 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)
-  # *BSD (and maybe Darwin too):
-  # /proc/<pid>/file
   when defined(windows):
-    when useWinUnicode:
-      var buf = cast[wideCString](alloc(256*2))
-      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(linux) or defined(aix):
-    result = getApplAux("/proc/self/exe")
-  elif defined(solaris):
-    result = getApplAux("/proc/" & $getpid() & "/path/a.out")
-  elif defined(freebsd):
-    result = getApplAux("/proc/" & $getpid() & "/file")
+    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
+    var size = cuint32(0)
     getExecPath1(nil, size)
     result = newString(int(size))
-    if getExecPath2(result, size):
+    if getExecPath2(result.cstring, size):
       result = "" # error!
+    if result.len > 0:
+      try:
+        result = result.expandFilename
+      except OSError:
+        result = ""
   else:
-    # little heuristic that may work on other POSIX-like systems:
-    result = string(getEnv("_"))
-    if len(result) == 0:
-      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
-
-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()
+    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 = ""
 
-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
+    # 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: [FReadIO].} =
+proc getAppDir*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect], noWeirdTarget.} =
   ## Returns the directory of the application's executable.
-  ## **Note**: This does not work reliably on BSD.
+  ##
+  ## See also:
+  ## * `getAppFilename proc`_
   result = splitFile(getAppFilename()).dir
 
-proc sleep*(milsecs: int) {.rtl, extern: "nos$1", tags: [FTime].} =
-  ## sleeps `milsecs` milliseconds.
+proc sleep*(milsecs: int) {.rtl, extern: "nos$1", tags: [TimeEffect], noWeirdTarget.} =
+  ## Sleeps `milsecs` milliseconds.
+  ## A negative `milsecs` causes sleep to return immediately.
   when defined(windows):
+    if milsecs < 0:
+      return  # fixes #23732
     winlean.sleep(int32(milsecs))
   else:
-    var a, b: Ttimespec
-    a.tv_sec = TTime(milsecs div 1000)
+    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: [FReadIO].} =
-  ## returns the file size of `file`. Can raise ``EOS``. 
+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: TWin32FindData
-    var resA = findfirstFile(file, a)
-    if resA == -1: OSError()
+    var a: WIN32_FIND_DATA
+    var resA = findFirstFile(file, a)
+    if resA == -1: raiseOSError(osLastError(), file)
     result = rdFileSize(a)
-    findclose(resA)
+    findClose(resA)
   else:
-    var f: TFile
-    if open(f, file): 
-      result = getFileSize(f)
-      close(f)
-    else: OSError()
+    var rawInfo: Stat
+    if stat(file, rawInfo) < 0'i32:
+      raiseOSError(osLastError(), file)
+    rawInfo.st_size
 
-proc findExe*(exe: string): string {.tags: [FReadDir, FReadEnv].} = 
-  ## 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. On DOS-like platforms, `exe` 
-  ## is added an ``.exe`` file extension if it has no extension.
-  result = addFileExt(exe, os.exeExt)
-  if ExistsFile(result): return
-  var path = string(os.getEnv("PATH"))
-  for candidate in split(path, pathSep): 
-    var x = candidate / result
-    if ExistsFile(x): return x
-  result = ""
+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[T](a, b): untyped =
+       cast[T](
+        (uint64(cast[uint32](a))) or
+        (uint64(cast[uint32](b)) shl 32)
+       )
+    formalInfo.id.device = rawInfo.dwVolumeSerialNumber
+    formalInfo.id.file = merge[FileId](rawInfo.nFileIndexLow, rawInfo.nFileIndexHigh)
+    formalInfo.size = merge[BiggestInt](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)
 
-{.pop.}
+  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)