From 0f0ae8f25f723b7681710cb1f1837f0f8862ad18 Mon Sep 17 00:00:00 2001 From: PMunch Date: Sun, 19 Jan 2025 20:05:48 +0100 Subject: [PATCH 1/4] OS module works in a macro when compiling for non-OS supporting target --- lib/pure/os.nim | 829 ++++++++++++++++----------------- lib/std/cmdline.nim | 2 - lib/std/private/oscommon.nim | 14 +- lib/std/private/osdirs.nim | 13 +- lib/std/private/osfiles.nim | 8 +- lib/std/private/ospaths2.nim | 6 +- lib/std/private/ossymlinks.nim | 6 +- 7 files changed, 433 insertions(+), 445 deletions(-) diff --git a/lib/pure/os.nim b/lib/pure/os.nim index aac53b3664fc1..25e875de92e3d 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -82,8 +82,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".} @@ -255,7 +253,7 @@ proc findExe*(exe: string, followSymlinks: bool = true; for ext in extensions: var x = addFileExt(x, ext) if fileExists(x): - when not (defined(windows) or defined(nintendoswitch)): + when defined(posix): #not (defined(windows) or defined(nintendoswitch)): while followSymlinks: # doubles as if here if x.symlinkExists: var r = newString(maxSymlinkLen) @@ -279,113 +277,197 @@ 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) +when defined(weirdTarget) or defined(posix) or defined(windows) or defined(nintendoswitch): + 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") + + else: + result = geteuid() == 0 + 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) + else: + 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: + if link(src, dest) != 0: + raiseOSError(osLastError(), $(src, dest)) + + 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. @@ -416,43 +498,6 @@ proc execShellCmd*(command: string): int {.rtl, extern: "nos$1", ## ``` 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: - raiseOSError(osLastError(), filename) - elif L > bufsize: - res = newWideCString(L) - bufsize = L - else: - result = res$L - break - # getFullPathName doesn't do case corrections, so we have to use this convoluted - # way of retrieving the true filename - for x in walkFiles(result): - result = x - if not fileExists(result) and not dirExists(result): - # consider using: `raiseOSError(osLastError(), result)` - raise newException(OSError, "file '" & result & "' does not exist") - else: - # according to Posix we don't need to allocate space for result pathname. - # But we need to free return value with free(3). - var r = realpath(filename, nil) - if r.isNil: - raiseOSError(osLastError(), filename) - else: - result = $r - c_free(cast[pointer](r)) - proc getCurrentCompilerExe*(): string {.compileTime.} = result = "" discard "implemented in the vmops" @@ -463,24 +508,6 @@ proc getCurrentCompilerExe*(): string {.compileTime.} = ## inside a nimble program (likewise with other binaries built from ## compiler API). -proc createHardlink*(src, dest: string) {.noWeirdTarget.} = - ## Create a hard link at `dest` which points to the item specified - ## by `src`. - ## - ## .. warning:: Some OS's restrict the creation of hard links to - ## root users (administrators). - ## - ## See also: - ## * `createSymlink proc`_ - when defined(windows): - var wSrc = newWideCString(src) - var wDst = newWideCString(dest) - if createHardLinkW(wDst, wSrc, nil) == 0: - raiseOSError(osLastError(), $(src, dest)) - else: - if link(src, dest) != 0: - raiseOSError(osLastError(), $(src, dest)) - proc inclFilePermissions*(filename: string, permissions: set[FilePermission]) {. rtl, extern: "nos$1", tags: [ReadDirEffect, WriteDirEffect], noWeirdTarget.} = @@ -693,252 +720,253 @@ proc getAppDir*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect], noWeirdT ## * `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 - 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 +995,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..557a7f96b297f 100644 --- a/lib/std/cmdline.nim +++ b/lib/std/cmdline.nim @@ -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..4c684599552a0 100644 --- a/lib/std/private/oscommon.nim +++ b/lib/std/private/oscommon.nim @@ -27,8 +27,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: @@ -101,7 +99,7 @@ proc tryMoveFSObject*(source, dest: string, isDir: bool): bool {.noWeirdTarget.} let s = newWideCString(source) let d = newWideCString(dest) result = moveFileExW(s, d, MOVEFILE_COPY_ALLOWED or MOVEFILE_REPLACE_EXISTING) != 0'i32 - else: + elif defined(posix): result = c_rename(source, dest) == 0'i32 if not result: @@ -110,8 +108,10 @@ proc tryMoveFSObject*(source, dest: string, isDir: bool): bool {.noWeirdTarget.} when defined(windows): const AccessDeniedError = OSErrorCode(5) isDir and err == AccessDeniedError - else: + elif defined(posix): err == EXDEV.OSErrorCode + else: + false if not isAccessDeniedError: raiseOSError(err, $(source, dest)) @@ -131,7 +131,7 @@ proc fileExists*(filename: string): bool {.rtl, extern: "nos$1", wrapUnary(a, getFileAttributesW, filename) if a != -1'i32: result = (a and FILE_ATTRIBUTE_DIRECTORY) == 0'i32 - else: + elif defined(posix): var res: Stat return stat(filename, res) >= 0'i32 and S_ISREG(res.st_mode) @@ -148,7 +148,7 @@ proc dirExists*(dir: string): bool {.rtl, extern: "nos$1", tags: [ReadDirEffect] wrapUnary(a, getFileAttributesW, dir) if a != -1'i32: result = (a and FILE_ATTRIBUTE_DIRECTORY) != 0'i32 - else: + elif defined(posix): var res: Stat result = stat(dir, res) >= 0'i32 and S_ISDIR(res.st_mode) @@ -168,7 +168,7 @@ proc symlinkExists*(link: string): bool {.rtl, extern: "nos$1", # xxx see: bug #16784 (bug9); checking `IO_REPARSE_TAG_SYMLINK` # may also be needed. result = (a and FILE_ATTRIBUTE_REPARSE_POINT) != 0'i32 - else: + elif defined(posix): var res: Stat result = lstat(link, res) >= 0'i32 and S_ISLNK(res.st_mode) diff --git a/lib/std/private/osdirs.nim b/lib/std/private/osdirs.nim index a44cad7d94a15..25ef5e4c83357 100644 --- a/lib/std/private/osdirs.nim +++ b/lib/std/private/osdirs.nim @@ -20,9 +20,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".} @@ -79,7 +76,7 @@ template walkCommon(pattern: string, filter) = let errCode = getLastError() if errCode == ERROR_NO_MORE_FILES: break else: raiseOSError(errCode.OSErrorCode) - else: # here we use glob + elif defined(posix): # here we use glob var f: Glob res: int @@ -223,7 +220,7 @@ iterator walkDir*(dir: string; relative = false, checkDir = false, let errCode = getLastError() if errCode == ERROR_NO_MORE_FILES: break else: raiseOSError(errCode.OSErrorCode) - else: + elif defined(posix): var d = opendir(dir) if d == nil: if checkDir: @@ -333,7 +330,7 @@ proc rawRemoveDir(dir: string) {.noWeirdTarget.} = if res == 0'i32 and lastError.int32 != 3'i32 and lastError.int32 != 18'i32 and lastError.int32 != 2'i32: raiseOSError(lastError, dir) - else: + elif defined(posix): if rmdir(dir) != 0'i32 and errno != ENOENT: raiseOSError(osLastError(), dir) proc removeDir*(dir: string, checkDir = false) {.rtl, extern: "nos$1", tags: [ @@ -392,7 +389,7 @@ proc rawCreateDir(dir: string): bool {.noWeirdTarget.} = else: #echo res raiseOSError(osLastError(), dir) - else: + elif defined(posix): wrapUnary(res, createDirectoryW, dir) if res != 0'i32: @@ -566,5 +563,5 @@ proc setCurrentDir*(newDir: string) {.inline, tags: [], noWeirdTarget.} = when defined(windows): if setCurrentDirectoryW(newWideCString(newDir)) == 0'i32: raiseOSError(osLastError(), newDir) - else: + elif defined(posix): if chdir(newDir) != 0'i32: raiseOSError(osLastError(), newDir) diff --git a/lib/std/private/osfiles.nim b/lib/std/private/osfiles.nim index 37d8eabca220c..129b4c72d8fbf 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: @@ -83,7 +81,7 @@ proc getFilePermissions*(filename: string): set[FilePermission] {. if (a.st_mode and S_IROTH.Mode) != 0.Mode: result.incl(fpOthersRead) if (a.st_mode and S_IWOTH.Mode) != 0.Mode: result.incl(fpOthersWrite) if (a.st_mode and S_IXOTH.Mode) != 0.Mode: result.incl(fpOthersExec) - else: + elif defined(posix): wrapUnary(res, getFileAttributesW, filename) if res == -1'i32: raiseOSError(osLastError(), filename) if (res and FILE_ATTRIBUTE_READONLY) != 0'i32: @@ -132,7 +130,7 @@ proc setFilePermissions*(filename: string, permissions: set[FilePermission], else: if chmod(filename, cast[Mode](p)) != 0: raiseOSError(osLastError(), $(filename, permissions)) - else: + elif defined(posix): wrapUnary(res, getFileAttributesW, filename) if res == -1'i32: raiseOSError(osLastError(), filename) if fpUserWrite in permissions: @@ -363,7 +361,7 @@ proc tryRemoveFile*(file: string): bool {.rtl, extern: "nos$1", tags: [WriteDirE setFileAttributes(f, FILE_ATTRIBUTE_NORMAL) != 0 and deleteFile(f) != 0: result = true - else: + elif defined(posix): if unlink(file) != 0'i32 and errno != ENOENT: result = false diff --git a/lib/std/private/ospaths2.nim b/lib/std/private/ospaths2.nim index b43576424d31c..7c116ca37e0e8 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".} @@ -873,7 +871,7 @@ when not defined(nimscript): else: result = res$L break - else: + elif defined(posix): var bufsize = 1024 # should be enough result = newString(bufsize) while true: @@ -1023,7 +1021,7 @@ proc sameFile*(path1, path2: string): bool {.rtl, extern: "nos$1", discard closeHandle(f2) if not success: raiseOSError(lastErr, $(path1, path2)) - else: + elif defined(posix): var a, b: Stat if stat(path1, a) < 0'i32 or stat(path2, b) < 0'i32: raiseOSError(osLastError(), $(path1, path2)) diff --git a/lib/std/private/ossymlinks.nim b/lib/std/private/ossymlinks.nim index c1760c42ec56a..5fa408ca29eda 100644 --- a/lib/std/private/ossymlinks.nim +++ b/lib/std/private/ossymlinks.nim @@ -13,8 +13,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: @@ -52,7 +50,7 @@ proc createSymlink*(src, dest: string) {.noWeirdTarget.} = var wDst = newWideCString(dest) if createSymbolicLinkW(wDst, wSrc, flag) == 0 or getLastError() != 0: raiseOSError(osLastError(), $(src, dest)) - else: + elif defined(posix): if symlink(src, dest) != 0: raiseOSError(osLastError(), $(src, dest)) @@ -65,7 +63,7 @@ proc expandSymlink*(symlinkPath: string): string {.noWeirdTarget.} = ## * `createSymlink proc`_ when defined(windows) or defined(nintendoswitch): result = symlinkPath - else: + elif defined(posix): var bufLen = 1024 while true: result = newString(bufLen) From c11936e37a52c4d3215c43cdfa7dc7c1f32cc94c Mon Sep 17 00:00:00 2001 From: PMunch Date: Mon, 20 Jan 2025 11:50:02 +0100 Subject: [PATCH 2/4] OS module works and now hides the functions that won't work --- compiler/vmops.nim | 2 +- lib/pure/os.nim | 355 +++++++++++++++++---------------- lib/std/cmdline.nim | 2 +- lib/std/private/oscommon.nim | 230 ++++++++++----------- lib/std/private/osdirs.nim | 19 +- lib/std/private/osfiles.nim | 6 +- lib/std/private/ospaths2.nim | 87 ++++---- lib/std/private/ossymlinks.nim | 7 +- lib/std/staticos.nim | 15 ++ 9 files changed, 364 insertions(+), 359 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 25e875de92e3d..9691d7db75c8b 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 @@ -219,65 +220,65 @@ 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 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 + 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 -when defined(weirdTarget) or defined(posix) or defined(windows) or defined(nintendoswitch): proc getLastModificationTime*(file: string): times.Time {.rtl, extern: "nos$1", noWeirdTarget.} = ## Returns the `file`'s last modification time. ## @@ -469,62 +470,52 @@ when defined(weirdTarget) or defined(posix) or defined(windows) or defined(ninte 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) + 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: - WEXITSTATUS(status) - else: - status - -proc execShellCmd*(command: string): int {.rtl, extern: "nos$1", - tags: [ExecIOEffect], noWeirdTarget.} = - ## Executes a `shell command`:idx:. - ## - ## Command has the form 'program args' where args are the command - ## line arguments given to program. The proc returns the error code - ## of the shell when it has finished (zero if there is no error). - ## The proc does not return until the process has finished. - ## - ## To execute a program without having a shell involved, use `osproc.execProcess proc - ## `_. - ## - ## **Examples:** - ## ```Nim - ## discard execShellCmd("ls -la") - ## ``` - result = exitStatusLikeShell(c_system(command)) - -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). + status -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, @@ -610,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) @@ -654,71 +645,81 @@ 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`_ + 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. +proc getCurrentCompilerExe*(): string {.compileTime.} = + result = "" + discard "implemented in the vmops" + ## Returns the path of the currently running Nim compiler or nimble executable. ## - ## See also: - ## * `getAppFilename proc`_ - result = splitFile(getAppFilename()).dir + ## 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 diff --git a/lib/std/cmdline.nim b/lib/std/cmdline.nim index 557a7f96b297f..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 diff --git a/lib/std/private/oscommon.nim b/lib/std/private/oscommon.nim index 4c684599552a0..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 @@ -63,124 +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 - elif defined(posix): - 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 - elif defined(posix): - err == EXDEV.OSErrorCode - else: - false - 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 - elif defined(posix): - 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 - elif defined(posix): - 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 - elif defined(posix): - 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 25ef5e4c83357..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): @@ -76,7 +78,7 @@ template walkCommon(pattern: string, filter) = let errCode = getLastError() if errCode == ERROR_NO_MORE_FILES: break else: raiseOSError(errCode.OSErrorCode) - elif defined(posix): # here we use glob + else: # here we use glob var f: Glob res: int @@ -149,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].} = @@ -220,7 +218,7 @@ iterator walkDir*(dir: string; relative = false, checkDir = false, let errCode = getLastError() if errCode == ERROR_NO_MORE_FILES: break else: raiseOSError(errCode.OSErrorCode) - elif defined(posix): + else: var d = opendir(dir) if d == nil: if checkDir: @@ -322,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) @@ -330,7 +327,7 @@ proc rawRemoveDir(dir: string) {.noWeirdTarget.} = if res == 0'i32 and lastError.int32 != 3'i32 and lastError.int32 != 18'i32 and lastError.int32 != 2'i32: raiseOSError(lastError, dir) - elif defined(posix): + else: if rmdir(dir) != 0'i32 and errno != ENOENT: raiseOSError(osLastError(), dir) proc removeDir*(dir: string, checkDir = false) {.rtl, extern: "nos$1", tags: [ @@ -389,7 +386,7 @@ proc rawCreateDir(dir: string): bool {.noWeirdTarget.} = else: #echo res raiseOSError(osLastError(), dir) - elif defined(posix): + else: wrapUnary(res, createDirectoryW, dir) if res != 0'i32: @@ -563,5 +560,5 @@ proc setCurrentDir*(newDir: string) {.inline, tags: [], noWeirdTarget.} = when defined(windows): if setCurrentDirectoryW(newWideCString(newDir)) == 0'i32: raiseOSError(osLastError(), newDir) - elif defined(posix): + else: if chdir(newDir) != 0'i32: raiseOSError(osLastError(), newDir) diff --git a/lib/std/private/osfiles.nim b/lib/std/private/osfiles.nim index 129b4c72d8fbf..e166dde981127 100644 --- a/lib/std/private/osfiles.nim +++ b/lib/std/private/osfiles.nim @@ -81,7 +81,7 @@ proc getFilePermissions*(filename: string): set[FilePermission] {. if (a.st_mode and S_IROTH.Mode) != 0.Mode: result.incl(fpOthersRead) if (a.st_mode and S_IWOTH.Mode) != 0.Mode: result.incl(fpOthersWrite) if (a.st_mode and S_IXOTH.Mode) != 0.Mode: result.incl(fpOthersExec) - elif defined(posix): + else: wrapUnary(res, getFileAttributesW, filename) if res == -1'i32: raiseOSError(osLastError(), filename) if (res and FILE_ATTRIBUTE_READONLY) != 0'i32: @@ -130,7 +130,7 @@ proc setFilePermissions*(filename: string, permissions: set[FilePermission], else: if chmod(filename, cast[Mode](p)) != 0: raiseOSError(osLastError(), $(filename, permissions)) - elif defined(posix): + else: wrapUnary(res, getFileAttributesW, filename) if res == -1'i32: raiseOSError(osLastError(), filename) if fpUserWrite in permissions: @@ -361,7 +361,7 @@ proc tryRemoveFile*(file: string): bool {.rtl, extern: "nos$1", tags: [WriteDirE setFileAttributes(f, FILE_ATTRIBUTE_NORMAL) != 0 and deleteFile(f) != 0: result = true - elif defined(posix): + else: if unlink(file) != 0'i32 and errno != ENOENT: result = false diff --git a/lib/std/private/ospaths2.nim b/lib/std/private/ospaths2.nim index 7c116ca37e0e8..43185f50a00ec 100644 --- a/lib/std/private/ospaths2.nim +++ b/lib/std/private/ospaths2.nim @@ -838,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. @@ -871,7 +871,7 @@ when not defined(nimscript): else: result = res$L break - elif defined(posix): + else: var bufsize = 1024 # should be enough result = newString(bufsize) while true: @@ -887,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`. @@ -905,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: [].} = @@ -982,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)) - elif defined(posix): - 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 5fa408ca29eda..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] @@ -50,7 +51,7 @@ proc createSymlink*(src, dest: string) {.noWeirdTarget.} = var wDst = newWideCString(dest) if createSymbolicLinkW(wDst, wSrc, flag) == 0 or getLastError() != 0: raiseOSError(osLastError(), $(src, dest)) - elif defined(posix): + else: if symlink(src, dest) != 0: raiseOSError(osLastError(), $(src, dest)) @@ -63,7 +64,7 @@ proc expandSymlink*(symlinkPath: string): string {.noWeirdTarget.} = ## * `createSymlink proc`_ when defined(windows) or defined(nintendoswitch): result = symlinkPath - elif defined(posix): + else: var bufLen = 1024 while true: result = newString(bufLen) diff --git a/lib/std/staticos.nim b/lib/std/staticos.nim index f9fe265ed2384..4c15a161476ca 100644 --- a/lib/std/staticos.nim +++ b/lib/std/staticos.nim @@ -14,3 +14,18 @@ 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: bool = false): seq[ + tuple[kind: PathComponent, path: string]] = + discard From 31e6171398de4e1068c48cfaaa89ac154b3db381 Mon Sep 17 00:00:00 2001 From: PMunch Date: Mon, 20 Jan 2025 11:55:39 +0100 Subject: [PATCH 3/4] Improve documentation for staticWalkDir --- lib/std/staticos.nim | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/std/staticos.nim b/lib/std/staticos.nim index 4c15a161476ca..423751724060b 100644 --- a/lib/std/staticos.nim +++ b/lib/std/staticos.nim @@ -26,6 +26,13 @@ type pcDir, ## path refers to a directory pcLinkToDir ## path refers to a symbolic link to a directory -proc staticWalkDir*(dir: string; relative: bool = false): seq[ - tuple[kind: PathComponent, path: string]] = - discard +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" From 481fd0e2c7ffb0cef3a8b898ed3f850858408dc0 Mon Sep 17 00:00:00 2001 From: PMunch Date: Thu, 23 Jan 2025 08:45:17 +0100 Subject: [PATCH 4/4] Added back missing supportedSystem check --- lib/pure/os.nim | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/pure/os.nim b/lib/pure/os.nim index 9691d7db75c8b..c96a493ec68cc 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -645,6 +645,7 @@ when defined(haiku): else: result = "" +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.