From c2b825713c7399fa085470ca3ff4b6a9b573ac7f Mon Sep 17 00:00:00 2001 From: Peter Munch-Ellingsen Date: Fri, 24 Jan 2025 13:02:59 +0100 Subject: [PATCH] Enable macros to use certain things from the OS module when the target OS is not supported (#24639) Essentially this PR removes the `{.error.}` pragmas littered around in the OS module and submodules which prevents them from being imported if the target OS is not supported. This made it impossible to use certain supported features of the OS module in macros from a supported host OS. Instead of the `{.error.}` pragmas the `oscommon` module now has a constant `supportedSystem` which is false in the cases where the `{.error.}` pragmas where generated. All procedures which can't be run by macros is also not declared when `supportedSystem` is false. It would be possible to create dummy versions of the omitted functions with an `{.error.}` pragma that would trigger upon their use, but this is currently not done. This properly fixes #19414 (cherry picked from commit 1f9cac1f5cdeb242e70bf1e058a87213bbee64fb) --- compiler/vmops.nim | 2 +- lib/pure/os.nim | 1171 ++++++++++++++++---------------- lib/std/cmdline.nim | 4 +- lib/std/private/oscommon.nim | 230 +++---- lib/std/private/osdirs.nim | 12 +- lib/std/private/osfiles.nim | 2 - lib/std/private/ospaths2.nim | 87 ++- lib/std/private/ossymlinks.nim | 5 +- lib/std/staticos.nim | 22 + 9 files changed, 768 insertions(+), 767 deletions(-) diff --git a/compiler/vmops.nim b/compiler/vmops.nim index 45194e6338085..8b0b8b5c7cdb2 100644 --- a/compiler/vmops.nim +++ b/compiler/vmops.nim @@ -263,7 +263,7 @@ proc registerAdditionalOps*(c: PCtx) = wrap2si(readLines, ioop) systemop getCurrentExceptionMsg systemop getCurrentException - registerCallback c, "stdlib.osdirs.staticWalkDir", proc (a: VmArgs) {.nimcall.} = + registerCallback c, "stdlib.staticos.staticWalkDir", proc (a: VmArgs) {.nimcall.} = setResult(a, staticWalkDirImpl(getString(a, 0), getBool(a, 1))) registerCallback c, "stdlib.staticos.staticDirExists", proc (a: VmArgs) {.nimcall.} = setResult(a, dirExists(getString(a, 0))) diff --git a/lib/pure/os.nim b/lib/pure/os.nim index aac53b3664fc1..c96a493ec68cc 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -32,20 +32,21 @@ runnableExamples: import std/private/ospaths2 export ospaths2 -import std/private/osfiles -export osfiles +import std/private/oscommon + +when supportedSystem: + import std/private/osfiles + export osfiles -import std/private/osdirs -export osdirs + import std/private/osdirs + export osdirs -import std/private/ossymlinks -export ossymlinks + import std/private/ossymlinks + export ossymlinks import std/private/osappdirs export osappdirs -import std/private/oscommon - include system/inclrtl import std/private/since @@ -82,8 +83,6 @@ elif defined(posix): 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".} @@ -221,283 +220,302 @@ const ## 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 +when supportedSystem: + 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: - 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 + 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 defined(posix): #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: - x = parentDir(x) / r - else: - break - return x - result = "" + break + return x + result = "" -when weirdTarget: - const times = "fake const" - template Time(x: untyped): untyped = string + 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 = default(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 = default(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 `_ for details. - ## - ## See also: - ## * `getLastModificationTime proc`_ - ## * `getLastAccessTime proc`_ - ## * `fileNewer proc`_ - when defined(posix): - var res: Stat = default(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) + 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 = default(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 = default(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 `_ for details. + ## + ## See also: + ## * `getLastModificationTime proc`_ + ## * `getLastAccessTime proc`_ + ## * `fileNewer proc`_ + when defined(posix): + var res: Stat = default(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) - 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") + 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") - else: - result = geteuid() == 0 + 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 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 + result = geteuid() == 0 -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 - ## `_. - ## - ## **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. - result = "" - 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: + 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. + result = "" + 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) - 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) + result = $r + c_free(cast[pointer](r)) + + 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: - result = $r - c_free(cast[pointer](r)) + if link(src, dest) != 0: + raiseOSError(osLastError(), $(src, dest)) -proc getCurrentCompilerExe*(): string {.compileTime.} = - result = "" - discard "implemented in the vmops" - ## Returns the path of the currently running Nim compiler or nimble executable. - ## - ## 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 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: Timespec = default(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 = default(Stat) + if stat(file, rawInfo) < 0'i32: + raiseOSError(osLastError(), file) + rawInfo.st_size + + 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 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) + 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 + ## `_. + ## + ## **Examples:** + ## ```Nim + ## discard execShellCmd("ls -la") + ## ``` + result = exitStatusLikeShell(c_system(command)) + + 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, @@ -583,7 +601,7 @@ when not weirdTarget and defined(openbsd): else: result = "" -when not (defined(windows) or defined(macosx) or weirdTarget): +when not (defined(windows) or defined(macosx) or weirdTarget) and supportedSystem: proc getApplHeuristic(): string = when declared(paramStr): result = paramStr(0) @@ -627,318 +645,330 @@ when defined(haiku): 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`_ +when supportedSystem: + 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//exe - # Solaris: - # /proc//object/a.out (filename only) - # /proc//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: + # Linux: /proc//exe + # Solaris: + # /proc//object/a.out (filename only) + # /proc//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! - 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: + 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 = "" - 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: "" + # 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 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. - ## 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: Timespec = default(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 = default(Stat) - if stat(file, rawInfo) < 0'i32: - raiseOSError(osLastError(), file) - rawInfo.st_size +proc getCurrentCompilerExe*(): string {.compileTime.} = + result = "" + discard "implemented in the vmops" + ## Returns the path of the currently running Nim compiler or nimble executable. + ## + ## 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). when defined(windows) or weirdTarget: type DeviceId* = int32 FileId* = int64 -else: +elif defined(posix): 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) - - 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`_ +when weirdTarget or defined(windows) or defined(posix) or defined(nintendoswitch): + 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} - # Done: ID, Kind, Size, Permissions, Link Count - result = default(FileInfo) - 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 = default(Stat) - if fstat(handle, rawInfo) < 0'i32: - raiseOSError(osLastError(), $handle) - rawToFormalFileInfo(rawInfo, "", result) + # 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) -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`_ - result = default(FileInfo) - 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 = default(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) + 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)) + + 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`_ -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`_ - result = false - var - a, b: File = default(File) - if not open(a, path1): return false - if not open(b, path2): + # Done: ID, Kind, Size, Permissions, Link Count + result = default(FileInfo) + 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 = default(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`_ + result = default(FileInfo) + 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 = default(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`_ + result = false + var + a, b: File = default(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) - 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) + close(b) + + proc getCurrentProcessId*(): int {.noWeirdTarget.} = + ## Return current process ID. + ## + ## See also: + ## * `osproc.processID(p: 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) proc isHidden*(path: string): bool {.noWeirdTarget.} = ## Determines whether ``path`` is hidden or not, using `this @@ -967,35 +997,6 @@ proc isHidden*(path: string): bool {.noWeirdTarget.} = 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) `_ - 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. diff --git a/lib/std/cmdline.nim b/lib/std/cmdline.nim index dcf6e0f4acd2e..140c458f22474 100644 --- a/lib/std/cmdline.nim +++ b/lib/std/cmdline.nim @@ -19,7 +19,7 @@ include system/inclrtl when defined(nimPreviewSlimSystem): import std/widestrs - + when defined(nodejs): from std/private/oscommon import ReadDirEffect @@ -33,8 +33,6 @@ elif defined(windows): import std/winlean elif defined(posix): import std/posix -else: - {.error: "The cmdline module has not been implemented for the target platform.".} # Needed by windows in order to obtain the command line for targets diff --git a/lib/std/private/oscommon.nim b/lib/std/private/oscommon.nim index c49d52ef291ca..6e0214dcb6579 100644 --- a/lib/std/private/oscommon.nim +++ b/lib/std/private/oscommon.nim @@ -5,9 +5,13 @@ import std/[oserrors] when defined(nimPreviewSlimSystem): import std/[syncio, assertions, widestrs] +from std/staticos import PathComponent + ## .. importdoc:: osdirs.nim, os.nim -const weirdTarget* = defined(nimscript) or defined(js) +const + weirdTarget* = defined(nimscript) or defined(js) + supportedSystem* = weirdTarget or defined(windows) or defined(posix) type @@ -27,8 +31,6 @@ elif defined(posix): import std/posix proc c_rename(oldname, newname: cstring): cint {. importc: "rename", header: "".} -else: - {.error: "OS module not ported to your operating system!".} when weirdTarget: @@ -65,122 +67,110 @@ when defined(windows) and not weirdTarget: result = f.cFileName[0].int == dot and (f.cFileName[1].int == 0 or f.cFileName[1].int == dot and f.cFileName[2].int == 0) - -type - PathComponent* = enum ## Enumeration specifying a path component. +when supportedSystem: + when defined(posix) and not weirdTarget: + proc getSymlinkFileKind*(path: string): + tuple[pc: PathComponent, isSpecial: bool] = + # Helper function. + var s: Stat + assert(path != "") + result = (pcLinkToFile, false) + if stat(path, s) == 0'i32: + if S_ISDIR(s.st_mode): + result = (pcLinkToDir, false) + elif not S_ISREG(s.st_mode): + result = (pcLinkToFile, true) + + proc tryMoveFSObject*(source, dest: string, isDir: bool): bool {.noWeirdTarget.} = + ## Moves a file (or directory if `isDir` is true) from `source` to `dest`. + ## + ## Returns false in case of `EXDEV` error or `AccessDeniedError` on Windows (if `isDir` is true). + ## In case of other errors `OSError` is raised. + ## Returns true in case of success. + when defined(windows): + let s = newWideCString(source) + let d = newWideCString(dest) + result = moveFileExW(s, d, MOVEFILE_COPY_ALLOWED or MOVEFILE_REPLACE_EXISTING) != 0'i32 + else: + result = c_rename(source, dest) == 0'i32 + + if not result: + let err = osLastError() + let isAccessDeniedError = + when defined(windows): + const AccessDeniedError = OSErrorCode(5) + isDir and err == AccessDeniedError + else: + err == EXDEV.OSErrorCode + if not isAccessDeniedError: + raiseOSError(err, $(source, dest)) + + when not defined(windows): + const maxSymlinkLen* = 1024 + + proc fileExists*(filename: string): bool {.rtl, extern: "nos$1", + tags: [ReadDirEffect], noNimJs, sideEffect.} = + ## Returns true if `filename` exists and is a regular file or symlink. + ## + ## Directories, device files, named pipes and sockets return false. ## ## See also: - ## * `walkDirRec iterator`_ - ## * `FileInfo object`_ - pcFile, ## path refers to a file - pcLinkToFile, ## path refers to a symbolic link to a file - pcDir, ## path refers to a directory - pcLinkToDir ## path refers to a symbolic link to a directory - - -when defined(posix) and not weirdTarget: - proc getSymlinkFileKind*(path: string): - tuple[pc: PathComponent, isSpecial: bool] = - # Helper function. - var s: Stat - assert(path != "") - result = (pcLinkToFile, false) - if stat(path, s) == 0'i32: - if S_ISDIR(s.st_mode): - result = (pcLinkToDir, false) - elif not S_ISREG(s.st_mode): - result = (pcLinkToFile, true) - -proc tryMoveFSObject*(source, dest: string, isDir: bool): bool {.noWeirdTarget.} = - ## Moves a file (or directory if `isDir` is true) from `source` to `dest`. - ## - ## Returns false in case of `EXDEV` error or `AccessDeniedError` on Windows (if `isDir` is true). - ## In case of other errors `OSError` is raised. - ## Returns true in case of success. - when defined(windows): - let s = newWideCString(source) - let d = newWideCString(dest) - result = moveFileExW(s, d, MOVEFILE_COPY_ALLOWED or MOVEFILE_REPLACE_EXISTING) != 0'i32 - else: - result = c_rename(source, dest) == 0'i32 - - if not result: - let err = osLastError() - let isAccessDeniedError = - when defined(windows): - const AccessDeniedError = OSErrorCode(5) - isDir and err == AccessDeniedError - else: - err == EXDEV.OSErrorCode - if not isAccessDeniedError: - raiseOSError(err, $(source, dest)) - -when not defined(windows): - const maxSymlinkLen* = 1024 - -proc fileExists*(filename: string): bool {.rtl, extern: "nos$1", - tags: [ReadDirEffect], noNimJs, sideEffect.} = - ## Returns true if `filename` exists and is a regular file or symlink. - ## - ## Directories, device files, named pipes and sockets return false. - ## - ## See also: - ## * `dirExists proc`_ - ## * `symlinkExists proc`_ - when defined(windows): - wrapUnary(a, getFileAttributesW, filename) - if a != -1'i32: - result = (a and FILE_ATTRIBUTE_DIRECTORY) == 0'i32 - else: - var res: Stat - return stat(filename, res) >= 0'i32 and S_ISREG(res.st_mode) - - -proc dirExists*(dir: string): bool {.rtl, extern: "nos$1", tags: [ReadDirEffect], - noNimJs, sideEffect.} = - ## Returns true if the directory `dir` exists. If `dir` is a file, false - ## is returned. Follows symlinks. - ## - ## See also: - ## * `fileExists proc`_ - ## * `symlinkExists proc`_ - when defined(windows): - wrapUnary(a, getFileAttributesW, dir) - if a != -1'i32: - result = (a and FILE_ATTRIBUTE_DIRECTORY) != 0'i32 - else: - var res: Stat - result = stat(dir, res) >= 0'i32 and S_ISDIR(res.st_mode) - - -proc symlinkExists*(link: string): bool {.rtl, extern: "nos$1", - tags: [ReadDirEffect], - noWeirdTarget, sideEffect.} = - ## Returns true if the symlink `link` exists. Will return true - ## regardless of whether the link points to a directory or file. - ## - ## See also: - ## * `fileExists proc`_ - ## * `dirExists proc`_ - when defined(windows): - wrapUnary(a, getFileAttributesW, link) - if a != -1'i32: - # xxx see: bug #16784 (bug9); checking `IO_REPARSE_TAG_SYMLINK` - # may also be needed. - result = (a and FILE_ATTRIBUTE_REPARSE_POINT) != 0'i32 - else: - var res: Stat - result = lstat(link, res) >= 0'i32 and S_ISLNK(res.st_mode) - -when defined(windows) and not weirdTarget: - proc openHandle*(path: string, followSymlink=true, writeAccess=false): Handle = - var flags = FILE_FLAG_BACKUP_SEMANTICS or FILE_ATTRIBUTE_NORMAL - if not followSymlink: - flags = flags or FILE_FLAG_OPEN_REPARSE_POINT - let access = if writeAccess: GENERIC_WRITE else: 0'i32 - - result = createFileW( - newWideCString(path), access, - FILE_SHARE_DELETE or FILE_SHARE_READ or FILE_SHARE_WRITE, - nil, OPEN_EXISTING, flags, 0 - ) + ## * `dirExists proc`_ + ## * `symlinkExists proc`_ + when defined(windows): + wrapUnary(a, getFileAttributesW, filename) + if a != -1'i32: + result = (a and FILE_ATTRIBUTE_DIRECTORY) == 0'i32 + else: + var res: Stat + return stat(filename, res) >= 0'i32 and S_ISREG(res.st_mode) + + + proc dirExists*(dir: string): bool {.rtl, extern: "nos$1", tags: [ReadDirEffect], + noNimJs, sideEffect.} = + ## Returns true if the directory `dir` exists. If `dir` is a file, false + ## is returned. Follows symlinks. + ## + ## See also: + ## * `fileExists proc`_ + ## * `symlinkExists proc`_ + when defined(windows): + wrapUnary(a, getFileAttributesW, dir) + if a != -1'i32: + result = (a and FILE_ATTRIBUTE_DIRECTORY) != 0'i32 + else: + var res: Stat + result = stat(dir, res) >= 0'i32 and S_ISDIR(res.st_mode) + + + proc symlinkExists*(link: string): bool {.rtl, extern: "nos$1", + tags: [ReadDirEffect], + noWeirdTarget, sideEffect.} = + ## Returns true if the symlink `link` exists. Will return true + ## regardless of whether the link points to a directory or file. + ## + ## See also: + ## * `fileExists proc`_ + ## * `dirExists proc`_ + when defined(windows): + wrapUnary(a, getFileAttributesW, link) + if a != -1'i32: + # xxx see: bug #16784 (bug9); checking `IO_REPARSE_TAG_SYMLINK` + # may also be needed. + result = (a and FILE_ATTRIBUTE_REPARSE_POINT) != 0'i32 + else: + var res: Stat + result = lstat(link, res) >= 0'i32 and S_ISLNK(res.st_mode) + + when defined(windows) and not weirdTarget: + proc openHandle*(path: string, followSymlink=true, writeAccess=false): Handle = + var flags = FILE_FLAG_BACKUP_SEMANTICS or FILE_ATTRIBUTE_NORMAL + if not followSymlink: + flags = flags or FILE_FLAG_OPEN_REPARSE_POINT + let access = if writeAccess: GENERIC_WRITE else: 0'i32 + + result = createFileW( + newWideCString(path), access, + FILE_SHARE_DELETE or FILE_SHARE_READ or FILE_SHARE_WRITE, + nil, OPEN_EXISTING, flags, 0 + ) diff --git a/lib/std/private/osdirs.nim b/lib/std/private/osdirs.nim index a44cad7d94a15..5c6aa3e4d9514 100644 --- a/lib/std/private/osdirs.nim +++ b/lib/std/private/osdirs.nim @@ -6,7 +6,9 @@ import std/oserrors import ospaths2, osfiles import oscommon -export dirExists, PathComponent +import std/staticos +when supportedSystem: + export dirExists, PathComponent when defined(nimPreviewSlimSystem): @@ -20,9 +22,6 @@ elif defined(windows): elif defined(posix): import std/[posix, times] -else: - {.error: "OS module not ported to your operating system!".} - when weirdTarget: {.pragma: noWeirdTarget, error: "this proc is not available on the NimScript/js target".} @@ -152,10 +151,6 @@ iterator walkDirs*(pattern: string): string {.tags: [ReadDirEffect], noWeirdTarg assert "lib/pure/concurrency".unixToNativePath in paths walkCommon(pattern, isDir) -proc staticWalkDir(dir: string; relative: bool): seq[ - tuple[kind: PathComponent, path: string]] = - discard - iterator walkDir*(dir: string; relative = false, checkDir = false, skipSpecial = false): tuple[kind: PathComponent, path: string] {.tags: [ReadDirEffect].} = @@ -325,7 +320,6 @@ iterator walkDirRec*(dir: string, # continue iteration. # Future work can provide a way to customize this and do error reporting. - proc rawRemoveDir(dir: string) {.noWeirdTarget.} = when defined(windows): wrapUnary(res, removeDirectoryW, dir) diff --git a/lib/std/private/osfiles.nim b/lib/std/private/osfiles.nim index 37d8eabca220c..e166dde981127 100644 --- a/lib/std/private/osfiles.nim +++ b/lib/std/private/osfiles.nim @@ -21,8 +21,6 @@ elif defined(posix): 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: diff --git a/lib/std/private/ospaths2.nim b/lib/std/private/ospaths2.nim index b43576424d31c..43185f50a00ec 100644 --- a/lib/std/private/ospaths2.nim +++ b/lib/std/private/ospaths2.nim @@ -20,8 +20,6 @@ elif defined(windows): import std/winlean elif defined(posix): import std/posix, system/ansi_c -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".} @@ -840,7 +838,7 @@ proc unixToNativePath*(path: string, drive=""): string {. inc(i) -when not defined(nimscript): +when not defined(nimscript) and supportedSystem: proc getCurrentDir*(): string {.rtl, extern: "nos$1", tags: [].} = ## Returns the `current working directory`:idx: i.e. where the built ## binary is run. @@ -889,7 +887,7 @@ when not defined(nimscript): else: raiseOSError(osLastError()) -proc absolutePath*(path: string, root = getCurrentDir()): string = +proc absolutePath*(path: string, root = when supportedSystem: getCurrentDir() else: ""): string = ## Returns the absolute path of `path`, rooted at `root` (which must be absolute; ## default: current directory). ## If `path` is absolute, return it, ignoring `root`. @@ -907,7 +905,7 @@ proc absolutePath*(path: string, root = getCurrentDir()): string = joinPath(root, path) proc absolutePathInternal(path: string): string = - absolutePath(path, getCurrentDir()) + absolutePath(path) proc normalizePath*(path: var string) {.rtl, extern: "nos$1", tags: [].} = @@ -984,48 +982,49 @@ proc normalizeExe*(file: var string) {.since: (1, 3, 5).} = if file.len > 0 and DirSep notin file and file != "." and file != "..": file = "./" & file -proc sameFile*(path1, path2: string): bool {.rtl, extern: "nos$1", - tags: [ReadDirEffect], noWeirdTarget.} = - ## Returns true if both pathname arguments refer to the same physical - ## file or directory. - ## - ## Raises `OSError` 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. - ## - ## See also: - ## * `sameFileContent proc`_ - result = false - when defined(windows): - var success = true - var f1 = openHandle(path1) - var f2 = openHandle(path2) - - var lastErr: OSErrorCode - if f1 != INVALID_HANDLE_VALUE and f2 != INVALID_HANDLE_VALUE: - var fi1, fi2: BY_HANDLE_FILE_INFORMATION - - if getFileInformationByHandle(f1, addr(fi1)) != 0 and - getFileInformationByHandle(f2, addr(fi2)) != 0: - result = fi1.dwVolumeSerialNumber == fi2.dwVolumeSerialNumber and - fi1.nFileIndexHigh == fi2.nFileIndexHigh and - fi1.nFileIndexLow == fi2.nFileIndexLow +when supportedSystem: + proc sameFile*(path1, path2: string): bool {.rtl, extern: "nos$1", + tags: [ReadDirEffect], noWeirdTarget.} = + ## Returns true if both pathname arguments refer to the same physical + ## file or directory. + ## + ## Raises `OSError` 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. + ## + ## See also: + ## * `sameFileContent proc`_ + result = false + when defined(windows): + var success = true + var f1 = openHandle(path1) + var f2 = openHandle(path2) + + var lastErr: OSErrorCode + if f1 != INVALID_HANDLE_VALUE and f2 != INVALID_HANDLE_VALUE: + var fi1, fi2: BY_HANDLE_FILE_INFORMATION + + if getFileInformationByHandle(f1, addr(fi1)) != 0 and + getFileInformationByHandle(f2, addr(fi2)) != 0: + result = fi1.dwVolumeSerialNumber == fi2.dwVolumeSerialNumber and + fi1.nFileIndexHigh == fi2.nFileIndexHigh and + fi1.nFileIndexLow == fi2.nFileIndexLow + else: + lastErr = osLastError() + success = false else: lastErr = osLastError() success = false - else: - lastErr = osLastError() - success = false - discard closeHandle(f1) - discard closeHandle(f2) + discard closeHandle(f1) + discard closeHandle(f2) - if not success: raiseOSError(lastErr, $(path1, path2)) - else: - var a, b: Stat - if stat(path1, a) < 0'i32 or stat(path2, b) < 0'i32: - raiseOSError(osLastError(), $(path1, path2)) + if not success: raiseOSError(lastErr, $(path1, path2)) else: - result = a.st_dev == b.st_dev and a.st_ino == b.st_ino + var a, b: Stat + if stat(path1, a) < 0'i32 or stat(path2, b) < 0'i32: + raiseOSError(osLastError(), $(path1, path2)) + else: + result = a.st_dev == b.st_dev and a.st_ino == b.st_ino diff --git a/lib/std/private/ossymlinks.nim b/lib/std/private/ossymlinks.nim index c1760c42ec56a..9e915a1e4d01d 100644 --- a/lib/std/private/ossymlinks.nim +++ b/lib/std/private/ossymlinks.nim @@ -2,7 +2,8 @@ include system/inclrtl import std/oserrors import oscommon -export symlinkExists +when supportedSystem: + export symlinkExists when defined(nimPreviewSlimSystem): import std/[syncio, assertions, widestrs] @@ -13,8 +14,6 @@ elif defined(windows): import std/[winlean, times] elif defined(posix): import std/posix -else: - {.error: "OS module not ported to your operating system!".} when weirdTarget: diff --git a/lib/std/staticos.nim b/lib/std/staticos.nim index f9fe265ed2384..423751724060b 100644 --- a/lib/std/staticos.nim +++ b/lib/std/staticos.nim @@ -14,3 +14,25 @@ proc staticDirExists*(dir: string): bool {.compileTime.} = ## Returns true if the directory `dir` exists. If `dir` is a file, false ## is returned. Follows symlinks. raiseAssert "implemented in the vmops" + +type + PathComponent* = enum ## Enumeration specifying a path component. + ## + ## See also: + ## * `walkDirRec iterator`_ + ## * `FileInfo object`_ + pcFile, ## path refers to a file + pcLinkToFile, ## path refers to a symbolic link to a file + pcDir, ## path refers to a directory + pcLinkToDir ## path refers to a symbolic link to a directory + +proc staticWalkDir*(dir: string; relative = false): seq[ + tuple[kind: PathComponent, path: string]] {.compileTime.} = + ## Walks over the directory `dir` and returns a seq with each directory or + ## file in `dir`. The component type and full path for each item are returned. + ## + ## Walking is not recursive. + ## * If `relative` is true (default: false) + ## the resulting path is shortened to be relative to ``dir``, + ## otherwise the full path is returned. + raiseAssert "implemented in the vmops"