1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630 |
- #
- #
- # Nim's Runtime Library
- # (c) Copyright 2015 Andreas Rumpf
- #
- # See the file "copying.txt", included in this
- # distribution, for details about the copyright.
- #
- ## This module contains basic operating system facilities like
- ## retrieving environment variables, reading command line arguments,
- ## working with directories, running shell commands, etc.
- {.deadCodeElim: on.}
- {.push debugger: off.}
- include "system/inclrtl"
- import
- strutils, times
- when defined(windows):
- import winlean
- elif defined(posix):
- import posix
- else:
- {.error: "OS module not ported to your operating system!".}
- import ospaths
- export ospaths
- proc c_remove(filename: cstring): cint {.
- importc: "remove", header: "<stdio.h>".}
- proc c_rename(oldname, newname: cstring): cint {.
- importc: "rename", header: "<stdio.h>".}
- proc c_system(cmd: cstring): cint {.
- importc: "system", header: "<stdlib.h>".}
- proc c_strlen(a: cstring): cint {.
- importc: "strlen", header: "<string.h>", noSideEffect.}
- proc c_free(p: pointer) {.
- importc: "free", header: "<stdlib.h>".}
- when defined(windows):
- when useWinUnicode:
- template wrapUnary(varname, winApiProc, arg: untyped) =
- var varname = winApiProc(newWideCString(arg))
- template wrapBinary(varname, winApiProc, arg, arg2: untyped) =
- var varname = winApiProc(newWideCString(arg), arg2)
- proc findFirstFile(a: string, b: var WIN32_FIND_DATA): Handle =
- result = findFirstFileW(newWideCString(a), b)
- template findNextFile(a, b: untyped): untyped = findNextFileW(a, b)
- template getCommandLine(): untyped = getCommandLineW()
- template getFilename(f: untyped): untyped =
- $cast[WideCString](addr(f.cFilename[0]))
- else:
- template findFirstFile(a, b: untyped): untyped = findFirstFileA(a, b)
- template findNextFile(a, b: untyped): untyped = findNextFileA(a, b)
- template getCommandLine(): untyped = getCommandLineA()
- template getFilename(f: untyped): untyped = $f.cFilename
- proc skipFindData(f: WIN32_FIND_DATA): bool {.inline.} =
- # Note - takes advantage of null delimiter in the cstring
- const dot = ord('.')
- result = f.cFileName[0].int == dot and (f.cFileName[1].int == 0 or
- f.cFileName[1].int == dot and f.cFileName[2].int == 0)
- proc existsFile*(filename: string): bool {.rtl, extern: "nos$1",
- tags: [ReadDirEffect].} =
- ## Returns true if the file exists, false otherwise.
- when defined(windows):
- when useWinUnicode:
- wrapUnary(a, getFileAttributesW, filename)
- else:
- var a = getFileAttributesA(filename)
- if a != -1'i32:
- result = (a and FILE_ATTRIBUTE_DIRECTORY) == 0'i32
- else:
- var res: Stat
- return stat(filename, res) >= 0'i32 and S_ISREG(res.st_mode)
- proc existsDir*(dir: string): bool {.rtl, extern: "nos$1", tags: [ReadDirEffect].} =
- ## Returns true iff the directory `dir` exists. If `dir` is a file, false
- ## is returned.
- when defined(windows):
- when useWinUnicode:
- wrapUnary(a, getFileAttributesW, dir)
- else:
- var a = getFileAttributesA(dir)
- if a != -1'i32:
- result = (a and FILE_ATTRIBUTE_DIRECTORY) != 0'i32
- else:
- var res: Stat
- return stat(dir, res) >= 0'i32 and S_ISDIR(res.st_mode)
- proc symlinkExists*(link: string): bool {.rtl, extern: "nos$1",
- tags: [ReadDirEffect].} =
- ## Returns true iff the symlink `link` exists. Will return true
- ## regardless of whether the link points to a directory or file.
- when defined(windows):
- when useWinUnicode:
- wrapUnary(a, getFileAttributesW, link)
- else:
- var a = getFileAttributesA(link)
- if a != -1'i32:
- result = (a and FILE_ATTRIBUTE_REPARSE_POINT) != 0'i32
- else:
- var res: Stat
- return lstat(link, res) >= 0'i32 and S_ISLNK(res.st_mode)
- proc fileExists*(filename: string): bool {.inline.} =
- ## Synonym for existsFile
- existsFile(filename)
- proc dirExists*(dir: string): bool {.inline.} =
- ## Synonym for existsDir
- existsDir(dir)
- when not defined(windows):
- proc checkSymlink(path: string): bool =
- var rawInfo: Stat
- if lstat(path, rawInfo) < 0'i32: result = false
- else: result = S_ISLNK(rawInfo.st_mode)
- const
- ExeExts* = when defined(windows): ["exe", "cmd", "bat"] else: [""] ## \
- ## platform specific file extension for executables. On Windows
- ## ``["exe", "cmd", "bat"]``, on Posix ``[""]``.
- proc findExe*(exe: string, followSymlinks: bool = true;
- extensions: openarray[string]=ExeExts): string {.
- tags: [ReadDirEffect, ReadEnvEffect, ReadIOEffect].} =
- ## 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 <#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.
- for ext in extensions:
- result = addFileExt(exe, ext)
- if existsFile(result): return
- var path = string(getEnv("PATH"))
- for candidate in split(path, PathSep):
- 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 existsFile(x):
- when not defined(windows):
- while followSymlinks: # doubles as if here
- if x.checkSymlink:
- var r = newString(256)
- var len = readlink(x, r, 256)
- if len < 0:
- raiseOSError(osLastError())
- if len > 256:
- r = newString(len+1)
- len = readlink(x, r, len)
- setLen(r, len)
- if isAbsolute(r):
- x = r
- else:
- x = parentDir(x) / r
- else:
- break
- return x
- result = ""
- proc getLastModificationTime*(file: string): Time {.rtl, extern: "nos$1".} =
- ## Returns the `file`'s last modification time.
- when defined(posix):
- var res: Stat
- if stat(file, res) < 0'i32: raiseOSError(osLastError())
- return res.st_mtime
- else:
- var f: WIN32_FIND_DATA
- var h = findFirstFile(file, f)
- if h == -1'i32: raiseOSError(osLastError())
- result = winTimeToUnixTime(rdFileTime(f.ftLastWriteTime))
- findClose(h)
- proc getLastAccessTime*(file: string): Time {.rtl, extern: "nos$1".} =
- ## Returns the `file`'s last read or write access time.
- when defined(posix):
- var res: Stat
- if stat(file, res) < 0'i32: raiseOSError(osLastError())
- return res.st_atime
- else:
- var f: WIN32_FIND_DATA
- var h = findFirstFile(file, f)
- if h == -1'i32: raiseOSError(osLastError())
- result = winTimeToUnixTime(rdFileTime(f.ftLastAccessTime))
- findClose(h)
- proc getCreationTime*(file: string): Time {.rtl, extern: "nos$1".} =
- ## Returns the `file`'s creation time.
- ##
- ## **Note:** Under POSIX OS's, the returned time may actually be the time at
- ## which the file's attribute's were last modified. See
- ## `here <https://github.com/nim-lang/Nim/issues/1058>`_ for details.
- when defined(posix):
- var res: Stat
- if stat(file, res) < 0'i32: raiseOSError(osLastError())
- return res.st_ctime
- else:
- var f: WIN32_FIND_DATA
- var h = findFirstFile(file, f)
- if h == -1'i32: raiseOSError(osLastError())
- result = winTimeToUnixTime(rdFileTime(f.ftCreationTime))
- findClose(h)
- proc fileNewer*(a, b: string): bool {.rtl, extern: "nos$1".} =
- ## Returns true if the file `a` is newer than file `b`, i.e. if `a`'s
- ## modification time is later than `b`'s.
- when defined(posix):
- result = getLastModificationTime(a) - getLastModificationTime(b) >= 0
- # Posix's resolution sucks so, we use '>=' for posix.
- else:
- result = getLastModificationTime(a) - getLastModificationTime(b) > 0
- proc getCurrentDir*(): string {.rtl, extern: "nos$1", tags: [].} =
- ## Returns the `current working directory`:idx:.
- when defined(windows):
- var bufsize = MAX_PATH.int32
- when useWinUnicode:
- var res = newWideCString("", bufsize)
- while true:
- var L = getCurrentDirectoryW(bufsize, res)
- if L == 0'i32:
- raiseOSError(osLastError())
- elif L > bufsize:
- res = newWideCString("", L)
- bufsize = L
- else:
- result = res$L
- break
- else:
- result = newString(bufsize)
- while true:
- var L = getCurrentDirectoryA(bufsize, result)
- if L == 0'i32:
- raiseOSError(osLastError())
- elif L > bufsize:
- result = newString(L)
- bufsize = L
- else:
- setLen(result, L)
- break
- else:
- var bufsize = 1024 # should be enough
- result = newString(bufsize)
- while true:
- if getcwd(result, bufsize) != nil:
- setLen(result, c_strlen(result))
- break
- else:
- let err = osLastError()
- if err.int32 == ERANGE:
- bufsize = bufsize shl 1
- doAssert(bufsize >= 0)
- result = newString(bufsize)
- else:
- raiseOSError(osLastError())
- proc setCurrentDir*(newDir: string) {.inline, tags: [].} =
- ## Sets the `current working directory`:idx:; `OSError` is raised if
- ## `newDir` cannot been set.
- when defined(Windows):
- when useWinUnicode:
- if setCurrentDirectoryW(newWideCString(newDir)) == 0'i32:
- raiseOSError(osLastError())
- else:
- if setCurrentDirectoryA(newDir) == 0'i32: raiseOSError(osLastError())
- else:
- if chdir(newDir) != 0'i32: raiseOSError(osLastError())
- proc expandFilename*(filename: string): string {.rtl, extern: "nos$1",
- tags: [ReadDirEffect].} =
- ## Returns the full (`absolute`:idx:) path of the file `filename`,
- ## raises OSError in case of an error.
- when defined(windows):
- var bufsize = MAX_PATH.int32
- when useWinUnicode:
- var unused: WideCString = nil
- var res = newWideCString("", bufsize)
- while true:
- var L = getFullPathNameW(newWideCString(filename), bufsize, res, unused)
- if L == 0'i32:
- raiseOSError(osLastError())
- elif L > bufsize:
- res = newWideCString("", L)
- bufsize = L
- else:
- result = res$L
- break
- else:
- var unused: cstring = nil
- result = newString(bufsize)
- while true:
- var L = getFullPathNameA(filename, bufsize, result, unused)
- if L == 0'i32:
- raiseOSError(osLastError())
- elif L > bufsize:
- result = newString(L)
- bufsize = L
- else:
- setLen(result, L)
- break
- 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())
- else:
- result = $r
- c_free(cast[pointer](r))
- when defined(Windows):
- proc openHandle(path: string, followSymlink=true): Handle =
- var flags = FILE_FLAG_BACKUP_SEMANTICS or FILE_ATTRIBUTE_NORMAL
- if not followSymlink:
- flags = flags or FILE_FLAG_OPEN_REPARSE_POINT
- when useWinUnicode:
- result = createFileW(
- newWideCString(path), 0'i32,
- FILE_SHARE_DELETE or FILE_SHARE_READ or FILE_SHARE_WRITE,
- nil, OPEN_EXISTING, flags, 0
- )
- else:
- result = createFileA(
- path, 0'i32,
- FILE_SHARE_DELETE or FILE_SHARE_READ or FILE_SHARE_WRITE,
- nil, OPEN_EXISTING, flags, 0
- )
- proc sameFile*(path1, path2: string): bool {.rtl, extern: "nos$1",
- tags: [ReadDirEffect].} =
- ## Returns true if both pathname arguments refer to the same physical
- ## file or directory. Raises an exception if any of the files does not
- ## exist or information about it can not be obtained.
- ##
- ## This proc will return true if given two alternative hard-linked or
- ## sym-linked paths to the same file or directory.
- when defined(Windows):
- var success = true
- var f1 = openHandle(path1)
- var f2 = openHandle(path2)
- var lastErr: OSErrorCode
- if f1 != INVALID_HANDLE_VALUE and f2 != INVALID_HANDLE_VALUE:
- var fi1, fi2: BY_HANDLE_FILE_INFORMATION
- if getFileInformationByHandle(f1, addr(fi1)) != 0 and
- getFileInformationByHandle(f2, addr(fi2)) != 0:
- result = fi1.dwVolumeSerialNumber == fi2.dwVolumeSerialNumber and
- fi1.nFileIndexHigh == fi2.nFileIndexHigh and
- fi1.nFileIndexLow == fi2.nFileIndexLow
- else:
- lastErr = osLastError()
- success = false
- else:
- lastErr = osLastError()
- success = false
- discard closeHandle(f1)
- discard closeHandle(f2)
- if not success: raiseOSError(lastErr)
- else:
- var a, b: Stat
- if stat(path1, a) < 0'i32 or stat(path2, b) < 0'i32:
- raiseOSError(osLastError())
- else:
- result = a.st_dev == b.st_dev and a.st_ino == b.st_ino
- proc sameFileContent*(path1, path2: string): bool {.rtl, extern: "nos$1",
- tags: [ReadIOEffect].} =
- ## Returns true if both pathname arguments refer to files with identical
- ## binary content.
- const
- bufSize = 8192 # 8K buffer
- var
- a, b: File
- if not open(a, path1): return false
- if not open(b, path2):
- close(a)
- return false
- var bufA = alloc(bufSize)
- var bufB = alloc(bufSize)
- while true:
- var readA = readBuffer(a, bufA, bufSize)
- var readB = readBuffer(b, bufB, bufSize)
- if readA != readB:
- result = false
- break
- if readA == 0:
- result = true
- break
- result = equalMem(bufA, bufB, readA)
- if not result: break
- if readA != bufSize: break # end of file
- dealloc(bufA)
- dealloc(bufB)
- close(a)
- close(b)
- type
- FilePermission* = enum ## file access permission; modelled after UNIX
- fpUserExec, ## execute access for the file owner
- fpUserWrite, ## write access for the file owner
- fpUserRead, ## read access for the file owner
- fpGroupExec, ## execute access for the group
- fpGroupWrite, ## write access for the group
- fpGroupRead, ## read access for the group
- fpOthersExec, ## execute access for others
- fpOthersWrite, ## write access for others
- fpOthersRead ## read access for others
- {.deprecated: [TFilePermission: FilePermission].}
- proc getFilePermissions*(filename: string): set[FilePermission] {.
- rtl, extern: "nos$1", tags: [ReadDirEffect].} =
- ## retrieves file permissions for `filename`. `OSError` is raised in case of
- ## an error. On Windows, only the ``readonly`` flag is checked, every other
- ## permission is available in any case.
- when defined(posix):
- var a: Stat
- if stat(filename, a) < 0'i32: raiseOSError(osLastError())
- result = {}
- if (a.st_mode and S_IRUSR) != 0'i32: result.incl(fpUserRead)
- if (a.st_mode and S_IWUSR) != 0'i32: result.incl(fpUserWrite)
- if (a.st_mode and S_IXUSR) != 0'i32: result.incl(fpUserExec)
- if (a.st_mode and S_IRGRP) != 0'i32: result.incl(fpGroupRead)
- if (a.st_mode and S_IWGRP) != 0'i32: result.incl(fpGroupWrite)
- if (a.st_mode and S_IXGRP) != 0'i32: result.incl(fpGroupExec)
- if (a.st_mode and S_IROTH) != 0'i32: result.incl(fpOthersRead)
- if (a.st_mode and S_IWOTH) != 0'i32: result.incl(fpOthersWrite)
- if (a.st_mode and S_IXOTH) != 0'i32: result.incl(fpOthersExec)
- else:
- when useWinUnicode:
- wrapUnary(res, getFileAttributesW, filename)
- else:
- var res = getFileAttributesA(filename)
- if res == -1'i32: raiseOSError(osLastError())
- if (res and FILE_ATTRIBUTE_READONLY) != 0'i32:
- result = {fpUserExec, fpUserRead, fpGroupExec, fpGroupRead,
- fpOthersExec, fpOthersRead}
- else:
- result = {fpUserExec..fpOthersRead}
- proc setFilePermissions*(filename: string, permissions: set[FilePermission]) {.
- rtl, extern: "nos$1", tags: [WriteDirEffect].} =
- ## sets the file permissions for `filename`. `OSError` is raised in case of
- ## an error. On Windows, only the ``readonly`` flag is changed, depending on
- ## ``fpUserWrite``.
- when defined(posix):
- var p = 0'i32
- if fpUserRead in permissions: p = p or S_IRUSR
- if fpUserWrite in permissions: p = p or S_IWUSR
- if fpUserExec in permissions: p = p or S_IXUSR
- if fpGroupRead in permissions: p = p or S_IRGRP
- if fpGroupWrite in permissions: p = p or S_IWGRP
- if fpGroupExec in permissions: p = p or S_IXGRP
- if fpOthersRead in permissions: p = p or S_IROTH
- if fpOthersWrite in permissions: p = p or S_IWOTH
- if fpOthersExec in permissions: p = p or S_IXOTH
- if chmod(filename, p) != 0: raiseOSError(osLastError())
- else:
- when useWinUnicode:
- wrapUnary(res, getFileAttributesW, filename)
- else:
- var res = getFileAttributesA(filename)
- if res == -1'i32: raiseOSError(osLastError())
- if fpUserWrite in permissions:
- res = res and not FILE_ATTRIBUTE_READONLY
- else:
- res = res or FILE_ATTRIBUTE_READONLY
- when useWinUnicode:
- wrapBinary(res2, setFileAttributesW, filename, res)
- else:
- var res2 = setFileAttributesA(filename, res)
- if res2 == - 1'i32: raiseOSError(osLastError())
- proc copyFile*(source, dest: string) {.rtl, extern: "nos$1",
- tags: [ReadIOEffect, WriteIOEffect].} =
- ## Copies a file from `source` to `dest`.
- ##
- ## If this fails, `OSError` is raised. On the Windows platform this proc will
- ## copy the source file's attributes into dest. On other platforms you need
- ## to use `getFilePermissions() <#getFilePermissions>`_ and
- ## `setFilePermissions() <#setFilePermissions>`_ to copy them by hand (or use
- ## the convenience `copyFileWithPermissions() <#copyFileWithPermissions>`_
- ## proc), otherwise `dest` will inherit the default permissions of a newly
- ## created file for the user. If `dest` already exists, the file attributes
- ## will be preserved and the content overwritten.
- when defined(Windows):
- when useWinUnicode:
- let s = newWideCString(source)
- let d = newWideCString(dest)
- if copyFileW(s, d, 0'i32) == 0'i32: raiseOSError(osLastError())
- else:
- if copyFileA(source, dest, 0'i32) == 0'i32: raiseOSError(osLastError())
- else:
- # generic version of copyFile which works for any platform:
- const bufSize = 8000 # better for memory manager
- var d, s: File
- if not open(s, source): raiseOSError(osLastError())
- if not open(d, dest, fmWrite):
- close(s)
- raiseOSError(osLastError())
- var buf = alloc(bufSize)
- while true:
- var bytesread = readBuffer(s, buf, bufSize)
- if bytesread > 0:
- var byteswritten = writeBuffer(d, buf, bytesread)
- if bytesread != byteswritten:
- dealloc(buf)
- close(s)
- close(d)
- raiseOSError(osLastError())
- if bytesread != bufSize: break
- dealloc(buf)
- close(s)
- flushFile(d)
- close(d)
- when not declared(ENOENT) and not defined(Windows):
- when NoFakeVars:
- const ENOENT = cint(2) # 2 on most systems including Solaris
- else:
- var ENOENT {.importc, header: "<errno.h>".}: cint
- when defined(Windows):
- when useWinUnicode:
- template deleteFile(file: untyped): untyped = deleteFileW(file)
- template setFileAttributes(file, attrs: untyped): untyped =
- setFileAttributesW(file, attrs)
- else:
- template deleteFile(file: untyped): untyped = deleteFileA(file)
- template setFileAttributes(file, attrs: untyped): untyped =
- setFileAttributesA(file, attrs)
- proc tryRemoveFile*(file: string): bool {.rtl, extern: "nos$1", tags: [WriteDirEffect].} =
- ## Removes the `file`. If this fails, returns `false`. This does not fail
- ## if the file never existed in the first place.
- ## On Windows, ignores the read-only attribute.
- result = true
- when defined(Windows):
- when useWinUnicode:
- let f = newWideCString(file)
- else:
- let f = file
- if deleteFile(f) == 0:
- result = false
- let err = getLastError()
- if err == ERROR_FILE_NOT_FOUND or err == ERROR_PATH_NOT_FOUND:
- result = true
- elif err == ERROR_ACCESS_DENIED and
- setFileAttributes(f, FILE_ATTRIBUTE_NORMAL) != 0 and
- deleteFile(f) != 0:
- result = true
- else:
- if c_remove(file) != 0'i32 and errno != ENOENT:
- result = false
- proc removeFile*(file: string) {.rtl, extern: "nos$1", tags: [WriteDirEffect].} =
- ## Removes the `file`. If this fails, `OSError` is raised. This does not fail
- ## if the file never existed in the first place.
- ## On Windows, ignores the read-only attribute.
- if not tryRemoveFile(file):
- when defined(Windows):
- raiseOSError(osLastError())
- else:
- raiseOSError(osLastError(), $strerror(errno))
- proc tryMoveFSObject(source, dest: string): bool =
- ## Moves a file or directory from `source` to `dest`. Returns false in case
- ## of `EXDEV` error. In case of other errors `OSError` is raised. Returns
- ## true in case of success.
- when defined(Windows):
- when useWinUnicode:
- let s = newWideCString(source)
- let d = newWideCString(dest)
- if moveFileExW(s, d, MOVEFILE_COPY_ALLOWED) == 0'i32: raiseOSError(osLastError())
- else:
- if moveFileExA(source, dest, MOVEFILE_COPY_ALLOWED) == 0'i32: raiseOSError(osLastError())
- else:
- if c_rename(source, dest) != 0'i32:
- let err = osLastError()
- if err == EXDEV.OSErrorCode:
- return false
- else:
- raiseOSError(err, $strerror(errno))
- return true
- proc moveFile*(source, dest: string) {.rtl, extern: "nos$1",
- tags: [ReadIOEffect, WriteIOEffect].} =
- ## Moves a file from `source` to `dest`. If this fails, `OSError` is raised.
- if not tryMoveFSObject(source, dest):
- when not defined(windows):
- # Fallback to copy & del
- copyFile(source, dest)
- try:
- removeFile(source)
- except:
- discard tryRemoveFile(dest)
- raise
- proc execShellCmd*(command: string): int {.rtl, extern: "nos$1",
- tags: [ExecIOEffect].} =
- ## Executes a `shell command`:idx:.
- ##
- ## Command has the form 'program args' where args are the command
- ## line arguments given to program. The proc returns the error code
- ## of the shell when it has finished. The proc does not return until
- ## the process has finished. To execute a program without having a
- ## shell involved, use the `execProcess` proc of the `osproc`
- ## module.
- when defined(linux):
- result = c_system(command) shr 8
- else:
- result = c_system(command)
- # Templates for filtering directories and files
- when defined(windows):
- template isDir(f: WIN32_FIND_DATA): bool =
- (f.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) != 0'i32
- template isFile(f: WIN32_FIND_DATA): bool =
- not isDir(f)
- else:
- template isDir(f: string): bool =
- dirExists(f)
- template isFile(f: string): bool =
- fileExists(f)
- template defaultWalkFilter(item): bool =
- ## Walk filter used to return true on both
- ## files and directories
- true
- template walkCommon(pattern: string, filter) =
- ## Common code for getting the files and directories with the
- ## specified `pattern`
- when defined(windows):
- var
- f: WIN32_FIND_DATA
- res: int
- res = findFirstFile(pattern, f)
- if res != -1:
- defer: findClose(res)
- let dotPos = searchExtPos(pattern)
- while true:
- if not skipFindData(f) and filter(f):
- # Windows bug/gotcha: 't*.nim' matches 'tfoo.nims' -.- so we check
- # that the file extensions have the same length ...
- let ff = getFilename(f)
- let idx = ff.len - pattern.len + dotPos
- if dotPos < 0 or idx >= ff.len or ff[idx] == '.' or
- pattern[dotPos+1] == '*':
- yield splitFile(pattern).dir / extractFilename(ff)
- if findNextFile(res, f) == 0'i32: break
- else: # here we use glob
- var
- f: Glob
- res: int
- f.gl_offs = 0
- f.gl_pathc = 0
- f.gl_pathv = nil
- res = glob(pattern, 0, nil, addr(f))
- defer: globfree(addr(f))
- if res == 0:
- for i in 0.. f.gl_pathc - 1:
- assert(f.gl_pathv[i] != nil)
- let path = $f.gl_pathv[i]
- if filter(path):
- yield path
- iterator walkPattern*(pattern: string): string {.tags: [ReadDirEffect].} =
- ## Iterate over all the files and directories that match the `pattern`.
- ## On POSIX this uses the `glob`:idx: call.
- ##
- ## `pattern` is OS dependent, but at least the "\*.ext"
- ## notation is supported.
- walkCommon(pattern, defaultWalkFilter)
- iterator walkFiles*(pattern: string): string {.tags: [ReadDirEffect].} =
- ## Iterate over all the files that match the `pattern`. On POSIX this uses
- ## the `glob`:idx: call.
- ##
- ## `pattern` is OS dependent, but at least the "\*.ext"
- ## notation is supported.
- walkCommon(pattern, isFile)
- iterator walkDirs*(pattern: string): string {.tags: [ReadDirEffect].} =
- ## Iterate over all the directories that match the `pattern`.
- ## On POSIX this uses the `glob`:idx: call.
- ##
- ## `pattern` is OS dependent, but at least the "\*.ext"
- ## notation is supported.
- walkCommon(pattern, isDir)
- type
- PathComponent* = enum ## Enumeration specifying a path component.
- pcFile, ## path refers to a file
- pcLinkToFile, ## path refers to a symbolic link to a file
- pcDir, ## path refers to a directory
- pcLinkToDir ## path refers to a symbolic link to a directory
- {.deprecated: [TPathComponent: PathComponent].}
- when defined(posix):
- proc getSymlinkFileKind(path: string): PathComponent =
- # Helper function.
- var s: Stat
- assert(path != "")
- if stat(path, s) < 0'i32:
- raiseOSError(osLastError())
- if S_ISDIR(s.st_mode):
- result = pcLinkToDir
- else:
- result = pcLinkToFile
- proc staticWalkDir(dir: string; relative: bool): seq[
- tuple[kind: PathComponent, path: string]] =
- discard
- iterator walkDir*(dir: string; relative=false): tuple[kind: PathComponent, path: string] {.
- tags: [ReadDirEffect].} =
- ## walks over the directory `dir` and yields for each directory or file in
- ## `dir`. The component type and full path for each item is returned.
- ## Walking is not recursive. If ``relative`` is true the resulting path is
- ## shortened to be relative to ``dir``.
- ## Example: This directory structure::
- ## dirA / dirB / fileB1.txt
- ## / dirC
- ## / fileA1.txt
- ## / fileA2.txt
- ##
- ## and this code:
- ##
- ## .. code-block:: Nim
- ## for kind, path in walkDir("dirA"):
- ## echo(path)
- ##
- ## produces this output (but not necessarily in this order!)::
- ## dirA/dirB
- ## dirA/dirC
- ## dirA/fileA1.txt
- ## dirA/fileA2.txt
- when nimvm:
- for k, v in items(staticWalkDir(dir, relative)):
- yield (k, v)
- else:
- when defined(windows):
- var f: WIN32_FIND_DATA
- var h = findFirstFile(dir / "*", f)
- if h != -1:
- defer: findClose(h)
- while true:
- var k = pcFile
- if not skipFindData(f):
- if (f.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) != 0'i32:
- k = pcDir
- if (f.dwFileAttributes and FILE_ATTRIBUTE_REPARSE_POINT) != 0'i32:
- k = succ(k)
- let xx = if relative: extractFilename(getFilename(f))
- else: dir / extractFilename(getFilename(f))
- yield (k, xx)
- if findNextFile(h, f) == 0'i32: break
- else:
- var d = opendir(dir)
- if d != nil:
- defer: discard closedir(d)
- while true:
- var x = readdir(d)
- if x == nil: break
- var y = $x.d_name
- if y != "." and y != "..":
- var s: Stat
- if not relative:
- y = dir / y
- var k = pcFile
- when defined(linux) or defined(macosx) or defined(bsd) or defined(genode):
- if x.d_type != DT_UNKNOWN:
- if x.d_type == DT_DIR: k = pcDir
- if x.d_type == DT_LNK:
- if dirExists(y): k = pcLinkToDir
- else: k = pcLinkToFile
- yield (k, y)
- continue
- if lstat(y, s) < 0'i32: break
- if S_ISDIR(s.st_mode):
- k = pcDir
- elif S_ISLNK(s.st_mode):
- k = getSymlinkFileKind(y)
- yield (k, y)
- iterator walkDirRec*(dir: string, filter={pcFile, pcDir}): string {.
- tags: [ReadDirEffect].} =
- ## Recursively walks over the directory `dir` and yields for each file in `dir`.
- ## The full path for each file is returned. Directories are not returned.
- ## **Warning**:
- ## Modifying the directory structure while the iterator
- ## is traversing may result in undefined behavior!
- ##
- ## Walking is recursive. `filter` controls the behaviour of the iterator:
- ##
- ## --------------------- ---------------------------------------------
- ## filter meaning
- ## --------------------- ---------------------------------------------
- ## ``pcFile`` yield real files
- ## ``pcLinkToFile`` yield symbolic links to files
- ## ``pcDir`` follow real directories
- ## ``pcLinkToDir`` follow symbolic links to directories
- ## --------------------- ---------------------------------------------
- ##
- var stack = @[dir]
- while stack.len > 0:
- for k,p in walkDir(stack.pop()):
- if k in filter:
- case k
- of pcFile, pcLinkToFile: yield p
- of pcDir, pcLinkToDir: stack.add(p)
- proc rawRemoveDir(dir: string) =
- when defined(windows):
- when useWinUnicode:
- wrapUnary(res, removeDirectoryW, dir)
- else:
- var res = removeDirectoryA(dir)
- let lastError = osLastError()
- if res == 0'i32 and lastError.int32 != 3'i32 and
- lastError.int32 != 18'i32 and lastError.int32 != 2'i32:
- raiseOSError(lastError)
- else:
- if rmdir(dir) != 0'i32 and errno != ENOENT: raiseOSError(osLastError())
- proc removeDir*(dir: string) {.rtl, extern: "nos$1", tags: [
- WriteDirEffect, ReadDirEffect], benign.} =
- ## Removes the directory `dir` including all subdirectories and files
- ## in `dir` (recursively).
- ##
- ## If this fails, `OSError` is raised. This does not fail if the directory never
- ## existed in the first place.
- for kind, path in walkDir(dir):
- case kind
- of pcFile, pcLinkToFile, pcLinkToDir: removeFile(path)
- of pcDir: removeDir(path)
- rawRemoveDir(dir)
- proc rawCreateDir(dir: string): bool =
- # Try to create one directory (not the whole path).
- # returns `true` for success, `false` if the path has previously existed
- #
- # This is a thin wrapper over mkDir (or alternatives on other systems),
- # so in case of a pre-existing path we don't check that it is a directory.
- when defined(solaris):
- let res = mkdir(dir, 0o777)
- if res == 0'i32:
- result = true
- elif errno in {EEXIST, ENOSYS}:
- result = false
- else:
- raiseOSError(osLastError())
- elif defined(posix):
- let res = mkdir(dir, 0o777)
- if res == 0'i32:
- result = true
- elif errno == EEXIST:
- result = false
- else:
- echo res
- raiseOSError(osLastError())
- else:
- when useWinUnicode:
- wrapUnary(res, createDirectoryW, dir)
- else:
- let res = createDirectoryA(dir)
- if res != 0'i32:
- result = true
- elif getLastError() == 183'i32:
- result = false
- else:
- raiseOSError(osLastError())
- proc existsOrCreateDir*(dir: string): bool =
- ## Check if a `directory`:idx: `dir` exists, and create it otherwise.
- ##
- ## Does not create parent directories (fails if parent does not exist).
- ## Returns `true` if the directory already exists, and `false`
- ## otherwise.
- result = not rawCreateDir(dir)
- if result:
- # path already exists - need to check that it is indeed a directory
- if not existsDir(dir):
- raise newException(IOError, "Failed to create the directory")
- proc createDir*(dir: string) {.rtl, extern: "nos$1",
- tags: [WriteDirEffect, ReadDirEffect].} =
- ## Creates the `directory`:idx: `dir`.
- ##
- ## The directory may contain several subdirectories that do not exist yet.
- ## The full path is created. If this fails, `OSError` is raised. It does **not**
- ## fail if the directory already exists because for most usages this does not
- ## indicate an error.
- var omitNext = false
- when doslikeFileSystem:
- omitNext = isAbsolute(dir)
- for i in 1.. dir.len-1:
- if dir[i] in {DirSep, AltSep}:
- if omitNext:
- omitNext = false
- else:
- discard existsOrCreateDir(substr(dir, 0, i-1))
- # The loop does not create the dir itself if it doesn't end in separator
- if dir.len > 0 and not omitNext and
- dir[^1] notin {DirSep, AltSep}:
- discard existsOrCreateDir(dir)
- proc copyDir*(source, dest: string) {.rtl, extern: "nos$1",
- tags: [WriteIOEffect, ReadIOEffect], benign.} =
- ## Copies a directory from `source` to `dest`.
- ##
- ## If this fails, `OSError` is raised. On the Windows platform this proc will
- ## copy the attributes from `source` into `dest`. On other platforms created
- ## files and directories will inherit the default permissions of a newly
- ## created file/directory for the user. To preserve attributes recursively on
- ## these platforms use `copyDirWithPermissions() <#copyDirWithPermissions>`_.
- createDir(dest)
- for kind, path in walkDir(source):
- var noSource = path.substr(source.len()+1)
- case kind
- of pcFile:
- copyFile(path, dest / noSource)
- of pcDir:
- copyDir(path, dest / noSource)
- else: discard
- proc createSymlink*(src, dest: string) =
- ## Create a symbolic link at `dest` which points to the item specified
- ## by `src`. On most operating systems, will fail if a link already exists.
- ##
- ## **Warning**:
- ## Some OS's (such as Microsoft Windows) restrict the creation
- ## of symlinks to root users (administrators).
- when defined(Windows):
- # 2 is the SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE. This allows
- # anyone with developer mode on to create a link
- let flag = dirExists(src).int32 or 2
- when useWinUnicode:
- var wSrc = newWideCString(src)
- var wDst = newWideCString(dest)
- if createSymbolicLinkW(wDst, wSrc, flag) == 0 or getLastError() != 0:
- raiseOSError(osLastError())
- else:
- if createSymbolicLinkA(dest, src, flag) == 0 or getLastError() != 0:
- raiseOSError(osLastError())
- else:
- if symlink(src, dest) != 0:
- raiseOSError(osLastError())
- proc createHardlink*(src, dest: string) =
- ## Create a hard link at `dest` which points to the item specified
- ## by `src`.
- ##
- ## **Warning**: Some OS's restrict the creation of hard links to
- ## root users (administrators).
- when defined(Windows):
- when useWinUnicode:
- var wSrc = newWideCString(src)
- var wDst = newWideCString(dest)
- if createHardLinkW(wDst, wSrc, nil) == 0:
- raiseOSError(osLastError())
- else:
- if createHardLinkA(dest, src, nil) == 0:
- raiseOSError(osLastError())
- else:
- if link(src, dest) != 0:
- raiseOSError(osLastError())
- proc parseCmdLine*(c: string): seq[string] {.
- noSideEffect, rtl, extern: "nos$1".} =
- ## Splits a `command line`:idx: into several components;
- ## This proc is only occasionally useful, better use the `parseopt` module.
- ##
- ## On Windows, it uses the following parsing rules
- ## (see http://msdn.microsoft.com/en-us/library/17w5ykft.aspx ):
- ##
- ## * Arguments are delimited by white space, which is either a space or a tab.
- ## * The caret character (^) is not recognized as an escape character or
- ## delimiter. The character is handled completely by the command-line parser
- ## in the operating system before being passed to the argv array in the
- ## program.
- ## * A string surrounded by double quotation marks ("string") is interpreted
- ## as a single argument, regardless of white space contained within. A
- ## quoted string can be embedded in an argument.
- ## * A double quotation mark preceded by a backslash (\") is interpreted as a
- ## literal double quotation mark character (").
- ## * Backslashes are interpreted literally, unless they immediately precede
- ## a double quotation mark.
- ## * If an even number of backslashes is followed by a double quotation mark,
- ## one backslash is placed in the argv array for every pair of backslashes,
- ## and the double quotation mark is interpreted as a string delimiter.
- ## * If an odd number of backslashes is followed by a double quotation mark,
- ## one backslash is placed in the argv array for every pair of backslashes,
- ## and the double quotation mark is "escaped" by the remaining backslash,
- ## causing a literal double quotation mark (") to be placed in argv.
- ##
- ## On Posix systems, it uses the following parsing rules:
- ## Components are separated by whitespace unless the whitespace
- ## occurs within ``"`` or ``'`` quotes.
- result = @[]
- var i = 0
- var a = ""
- while true:
- setLen(a, 0)
- # eat all delimiting whitespace
- while c[i] == ' ' or c[i] == '\t' or c[i] == '\l' or c[i] == '\r' : inc(i)
- when defined(windows):
- # parse a single argument according to the above rules:
- if c[i] == '\0': break
- var inQuote = false
- while true:
- case c[i]
- of '\0': break
- of '\\':
- var j = i
- while c[j] == '\\': inc(j)
- if c[j] == '"':
- for k in 1..(j-i) div 2: a.add('\\')
- if (j-i) mod 2 == 0:
- i = j
- else:
- a.add('"')
- i = j+1
- else:
- a.add(c[i])
- inc(i)
- of '"':
- inc(i)
- if not inQuote: inQuote = true
- elif c[i] == '"':
- a.add(c[i])
- inc(i)
- else:
- inQuote = false
- break
- of ' ', '\t':
- if not inQuote: break
- a.add(c[i])
- inc(i)
- else:
- a.add(c[i])
- inc(i)
- else:
- case c[i]
- of '\'', '\"':
- var delim = c[i]
- inc(i) # skip ' or "
- while c[i] != '\0' and c[i] != delim:
- add a, c[i]
- inc(i)
- if c[i] != '\0': inc(i)
- of '\0': break
- else:
- while c[i] > ' ':
- add(a, c[i])
- inc(i)
- add(result, a)
- proc copyFileWithPermissions*(source, dest: string,
- ignorePermissionErrors = true) =
- ## Copies a file from `source` to `dest` preserving file permissions.
- ##
- ## This is a wrapper proc around `copyFile() <#copyFile>`_,
- ## `getFilePermissions() <#getFilePermissions>`_ and `setFilePermissions()
- ## <#setFilePermissions>`_ on non Windows platform. On Windows this proc is
- ## just a wrapper for `copyFile() <#copyFile>`_ since that proc already
- ## copies attributes.
- ##
- ## On non Windows systems permissions are copied after the file itself has
- ## been copied, which won't happen atomically and could lead to a race
- ## condition. If `ignorePermissionErrors` is true, errors while
- ## reading/setting file attributes will be ignored, otherwise will raise
- ## `OSError`.
- copyFile(source, dest)
- when not defined(Windows):
- try:
- setFilePermissions(dest, getFilePermissions(source))
- except:
- if not ignorePermissionErrors:
- raise
- proc copyDirWithPermissions*(source, dest: string,
- ignorePermissionErrors = true) {.rtl, extern: "nos$1",
- tags: [WriteIOEffect, ReadIOEffect], benign.} =
- ## Copies a directory from `source` to `dest` preserving file permissions.
- ##
- ## If this fails, `OSError` is raised. This is a wrapper proc around `copyDir()
- ## <#copyDir>`_ and `copyFileWithPermissions() <#copyFileWithPermissions>`_
- ## on non Windows platforms. On Windows this proc is just a wrapper for
- ## `copyDir() <#copyDir>`_ since that proc already copies attributes.
- ##
- ## On non Windows systems permissions are copied after the file or directory
- ## itself has been copied, which won't happen atomically and could lead to a
- ## race condition. If `ignorePermissionErrors` is true, errors while
- ## reading/setting file attributes will be ignored, otherwise will raise
- ## `OSError`.
- createDir(dest)
- when not defined(Windows):
- try:
- setFilePermissions(dest, getFilePermissions(source))
- except:
- if not ignorePermissionErrors:
- raise
- for kind, path in walkDir(source):
- var noSource = path.substr(source.len()+1)
- case kind
- of pcFile:
- copyFileWithPermissions(path, dest / noSource, ignorePermissionErrors)
- of pcDir:
- copyDirWithPermissions(path, dest / noSource, ignorePermissionErrors)
- else: discard
- proc inclFilePermissions*(filename: string,
- permissions: set[FilePermission]) {.
- rtl, extern: "nos$1", tags: [ReadDirEffect, WriteDirEffect].} =
- ## a convenience procedure for:
- ##
- ## .. code-block:: nim
- ## setFilePermissions(filename, getFilePermissions(filename)+permissions)
- setFilePermissions(filename, getFilePermissions(filename)+permissions)
- proc exclFilePermissions*(filename: string,
- permissions: set[FilePermission]) {.
- rtl, extern: "nos$1", tags: [ReadDirEffect, WriteDirEffect].} =
- ## a convenience procedure for:
- ##
- ## .. code-block:: nim
- ## setFilePermissions(filename, getFilePermissions(filename)-permissions)
- setFilePermissions(filename, getFilePermissions(filename)-permissions)
- proc moveDir*(source, dest: string) {.tags: [ReadIOEffect, WriteIOEffect].} =
- ## Moves a directory from `source` to `dest`. If this fails, `OSError` is raised.
- if not tryMoveFSObject(source, dest):
- when not defined(windows):
- # Fallback to copy & del
- copyDir(source, dest)
- removeDir(source)
- #include ospaths
- proc expandSymlink*(symlinkPath: string): string =
- ## Returns a string representing the path to which the symbolic link points.
- ##
- ## On Windows this is a noop, ``symlinkPath`` is simply returned.
- when defined(windows):
- result = symlinkPath
- else:
- result = newString(256)
- var len = readlink(symlinkPath, result, 256)
- if len < 0:
- raiseOSError(osLastError())
- if len > 256:
- result = newString(len+1)
- len = readlink(symlinkPath, result, len)
- setLen(result, len)
- when defined(nimdoc):
- # Common forward declaration docstring block for parameter retrieval procs.
- proc paramCount*(): int {.tags: [ReadIOEffect].} =
- ## Returns the number of `command line arguments`:idx: given to the
- ## application.
- ##
- ## If your binary was called without parameters this will return zero. You
- ## can later query each individual paramater with `paramStr() <#paramStr>`_
- ## or retrieve all of them in one go with `commandLineParams()
- ## <#commandLineParams>`_.
- ##
- ## **Availability**: On Posix there is no portable way to get the command
- ## line from a DLL and thus the proc isn't defined in this environment. You
- ## can test for its availability with `declared() <system.html#declared>`_.
- ## Example:
- ##
- ## .. code-block:: nim
- ## when declared(paramCount):
- ## # Use paramCount() here
- ## else:
- ## # Do something else!
- proc paramStr*(i: int): TaintedString {.tags: [ReadIOEffect].} =
- ## Returns the `i`-th `command line argument`:idx: given to the application.
- ##
- ## `i` should be in the range `1..paramCount()`, the `IndexError`
- ## exception will be raised for invalid values. Instead of iterating over
- ## `paramCount() <#paramCount>`_ with this proc you can call the
- ## convenience `commandLineParams() <#commandLineParams>`_.
- ##
- ## It is possible to call ``paramStr(0)`` but this will return OS specific
- ## contents (usually the name of the invoked executable). You should avoid
- ## this and call `getAppFilename() <#getAppFilename>`_ instead.
- ##
- ## **Availability**: On Posix there is no portable way to get the command
- ## line from a DLL and thus the proc isn't defined in this environment. You
- ## can test for its availability with `declared() <system.html#declared>`_.
- ## Example:
- ##
- ## .. code-block:: nim
- ## when declared(paramStr):
- ## # Use paramStr() here
- ## else:
- ## # Do something else!
- elif defined(windows):
- # Since we support GUI applications with Nim, we sometimes generate
- # a WinMain entry proc. But a WinMain proc has no access to the parsed
- # command line arguments. The way to get them differs. Thus we parse them
- # ourselves. This has the additional benefit that the program's behaviour
- # is always the same -- independent of the used C compiler.
- var
- ownArgv {.threadvar.}: seq[string]
- proc paramCount*(): int {.rtl, extern: "nos$1", tags: [ReadIOEffect].} =
- # Docstring in nimdoc block.
- if isNil(ownArgv): ownArgv = parseCmdLine($getCommandLine())
- result = ownArgv.len-1
- proc paramStr*(i: int): TaintedString {.rtl, extern: "nos$1",
- tags: [ReadIOEffect].} =
- # Docstring in nimdoc block.
- if isNil(ownArgv): ownArgv = parseCmdLine($getCommandLine())
- if i < ownArgv.len and i >= 0: return TaintedString(ownArgv[i])
- raise newException(IndexError, "invalid index")
- elif not defined(createNimRtl) and
- not(defined(posix) and appType == "lib") and
- not defined(genode):
- # On Posix, there is no portable way to get the command line from a DLL.
- var
- cmdCount {.importc: "cmdCount".}: cint
- cmdLine {.importc: "cmdLine".}: cstringArray
- proc paramStr*(i: int): TaintedString {.tags: [ReadIOEffect].} =
- # Docstring in nimdoc block.
- if i < cmdCount and i >= 0: return TaintedString($cmdLine[i])
- raise newException(IndexError, "invalid index")
- proc paramCount*(): int {.tags: [ReadIOEffect].} =
- # Docstring in nimdoc block.
- result = cmdCount-1
- when declared(paramCount) or defined(nimdoc):
- proc commandLineParams*(): seq[TaintedString] =
- ## Convenience proc which returns the command line parameters.
- ##
- ## This returns **only** the parameters. If you want to get the application
- ## executable filename, call `getAppFilename() <#getAppFilename>`_.
- ##
- ## **Availability**: On Posix there is no portable way to get the command
- ## line from a DLL and thus the proc isn't defined in this environment. You
- ## can test for its availability with `declared() <system.html#declared>`_.
- ## Example:
- ##
- ## .. code-block:: nim
- ## when declared(commandLineParams):
- ## # Use commandLineParams() here
- ## else:
- ## # Do something else!
- result = @[]
- for i in 1..paramCount():
- result.add(paramStr(i))
- when defined(freebsd) or defined(dragonfly):
- proc sysctl(name: ptr cint, namelen: cuint, oldp: pointer, oldplen: ptr csize,
- newp: pointer, newplen: csize): cint
- {.importc: "sysctl",header: """#include <sys/types.h>
- #include <sys/sysctl.h>"""}
- const
- CTL_KERN = 1
- KERN_PROC = 14
- MAX_PATH = 1024
- when defined(freebsd):
- const KERN_PROC_PATHNAME = 12
- else:
- const KERN_PROC_PATHNAME = 9
- proc getApplFreebsd(): string =
- var pathLength = csize(MAX_PATH)
- result = newString(pathLength)
- var req = [CTL_KERN.cint, KERN_PROC.cint, KERN_PROC_PATHNAME.cint, -1.cint]
- while true:
- let res = sysctl(addr req[0], 4, cast[pointer](addr result[0]),
- addr pathLength, nil, 0)
- if res < 0:
- let err = osLastError()
- if err.int32 == ENOMEM:
- result = newString(pathLength)
- else:
- result.setLen(0) # error!
- break
- else:
- result.setLen(pathLength)
- break
- when defined(linux) or defined(solaris) or defined(bsd) or defined(aix):
- proc getApplAux(procPath: string): string =
- result = newString(256)
- var len = readlink(procPath, result, 256)
- if len > 256:
- result = newString(len+1)
- len = readlink(procPath, result, len)
- setLen(result, len)
- when not (defined(windows) or defined(macosx)):
- proc getApplHeuristic(): string =
- when declared(paramStr):
- result = string(paramStr(0))
- # POSIX guaranties that this contains the executable
- # as it has been executed by the calling process
- if len(result) > 0 and result[0] != DirSep: # not an absolute path?
- # iterate over any path in the $PATH environment variable
- for p in split(string(getEnv("PATH")), {PathSep}):
- var x = joinPath(p, result)
- if existsFile(x): return x
- else:
- result = ""
- when defined(macosx):
- type
- cuint32* {.importc: "unsigned int", nodecl.} = int
- ## This is the same as the type ``uint32_t`` in *C*.
- # a really hacky solution: since we like to include 2 headers we have to
- # define two procs which in reality are the same
- proc getExecPath1(c: cstring, size: var cuint32) {.
- importc: "_NSGetExecutablePath", header: "<sys/param.h>".}
- proc getExecPath2(c: cstring, size: var cuint32): bool {.
- importc: "_NSGetExecutablePath", header: "<mach-o/dyld.h>".}
- proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect].} =
- ## Returns the filename of the application's executable.
- ##
- ## This procedure will resolve symlinks.
- # Linux: /proc/<pid>/exe
- # Solaris:
- # /proc/<pid>/object/a.out (filename only)
- # /proc/<pid>/path/a.out (complete pathname)
- when defined(windows):
- var bufsize = int32(MAX_PATH)
- when useWinUnicode:
- 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
- else:
- result = newString(bufsize)
- while true:
- var L = getModuleFileNameA(0, result, bufsize)
- if L == 0'i32:
- result = "" # error!
- break
- elif L > bufsize:
- result = newString(L)
- bufsize = L
- else:
- setLen(result, L)
- break
- elif defined(macosx):
- var size: cuint32
- getExecPath1(nil, size)
- result = newString(int(size))
- if getExecPath2(result, size):
- result = "" # error!
- if result.len > 0:
- result = result.expandFilename
- else:
- when defined(linux) or defined(aix) or defined(netbsd):
- result = getApplAux("/proc/self/exe")
- elif defined(solaris):
- result = getApplAux("/proc/" & $getpid() & "/path/a.out")
- elif defined(genode):
- raiseOSError("POSIX command line not supported")
- elif defined(freebsd) or defined(dragonfly):
- result = getApplFreebsd()
- # little heuristic that may work on other POSIX-like systems:
- if result.len == 0:
- result = getApplHeuristic()
- proc getApplicationFilename*(): string {.rtl, extern: "nos$1", deprecated.} =
- ## Returns the filename of the application's executable.
- ## **Deprecated since version 0.8.12**: use ``getAppFilename``
- ## instead.
- result = getAppFilename()
- proc getApplicationDir*(): string {.rtl, extern: "nos$1", deprecated.} =
- ## Returns the directory of the application's executable.
- ## **Deprecated since version 0.8.12**: use ``getAppDir``
- ## instead.
- result = splitFile(getAppFilename()).dir
- proc getAppDir*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect].} =
- ## Returns the directory of the application's executable.
- result = splitFile(getAppFilename()).dir
- proc sleep*(milsecs: int) {.rtl, extern: "nos$1", tags: [TimeEffect].} =
- ## sleeps `milsecs` milliseconds.
- when defined(windows):
- winlean.sleep(int32(milsecs))
- else:
- var a, b: Timespec
- a.tv_sec = Time(milsecs div 1000)
- a.tv_nsec = (milsecs mod 1000) * 1000 * 1000
- discard posix.nanosleep(a, b)
- proc getFileSize*(file: string): BiggestInt {.rtl, extern: "nos$1",
- tags: [ReadIOEffect].} =
- ## returns the file size of `file` (in bytes). An ``OSError`` exception 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())
- result = rdFileSize(a)
- findClose(resA)
- else:
- var f: File
- if open(f, file):
- result = getFileSize(f)
- close(f)
- else: raiseOSError(osLastError())
- when defined(Windows):
- type
- DeviceId* = int32
- FileId* = int64
- else:
- type
- DeviceId* = Dev
- FileId* = Ino
- type
- FileInfo* = object
- ## Contains information associated with a file object.
- id*: tuple[device: DeviceId, file: FileId] # Device and file id.
- kind*: PathComponent # Kind of file object - directory, symlink, etc.
- size*: BiggestInt # Size of file.
- permissions*: set[FilePermission] # File permissions
- linkCount*: BiggestInt # Number of hard links the file object has.
- lastAccessTime*: Time # Time file was last accessed.
- lastWriteTime*: Time # Time file was last modified/written to.
- creationTime*: Time # Time file was created. Not supported on all systems!
- template rawToFormalFileInfo(rawInfo, path, formalInfo): untyped =
- ## Transforms the native file info structure into the one nim uses.
- ## 'rawInfo' is either a 'TBY_HANDLE_FILE_INFORMATION' structure on Windows,
- ## or a 'Stat' structure on posix
- when defined(Windows):
- template toTime(e: FILETIME): untyped {.gensym.} = winTimeToUnixTime(rdFileTime(e)) # local templates default to bind semantics
- template merge(a, b): untyped = a or (b shl 32)
- formalInfo.id.device = rawInfo.dwVolumeSerialNumber
- formalInfo.id.file = merge(rawInfo.nFileIndexLow, rawInfo.nFileIndexHigh)
- formalInfo.size = merge(rawInfo.nFileSizeLow, rawInfo.nFileSizeHigh)
- formalInfo.linkCount = rawInfo.nNumberOfLinks
- formalInfo.lastAccessTime = toTime(rawInfo.ftLastAccessTime)
- formalInfo.lastWriteTime = toTime(rawInfo.ftLastWriteTime)
- formalInfo.creationTime = toTime(rawInfo.ftCreationTime)
- # Retrieve basic permissions
- if (rawInfo.dwFileAttributes and FILE_ATTRIBUTE_READONLY) != 0'i32:
- formalInfo.permissions = {fpUserExec, fpUserRead, fpGroupExec,
- fpGroupRead, fpOthersExec, fpOthersRead}
- else:
- result.permissions = {fpUserExec..fpOthersRead}
- # Retrieve basic file kind
- result.kind = pcFile
- if (rawInfo.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) != 0'i32:
- formalInfo.kind = pcDir
- if (rawInfo.dwFileAttributes and FILE_ATTRIBUTE_REPARSE_POINT) != 0'i32:
- formalInfo.kind = succ(result.kind)
- else:
- template checkAndIncludeMode(rawMode, formalMode: untyped) =
- if (rawInfo.st_mode and rawMode) != 0'i32:
- formalInfo.permissions.incl(formalMode)
- formalInfo.id = (rawInfo.st_dev, rawInfo.st_ino)
- formalInfo.size = rawInfo.st_size
- formalInfo.linkCount = rawInfo.st_Nlink.BiggestInt
- formalInfo.lastAccessTime = rawInfo.st_atime
- formalInfo.lastWriteTime = rawInfo.st_mtime
- formalInfo.creationTime = rawInfo.st_ctime
- result.permissions = {}
- checkAndIncludeMode(S_IRUSR, fpUserRead)
- checkAndIncludeMode(S_IWUSR, fpUserWrite)
- checkAndIncludeMode(S_IXUSR, fpUserExec)
- checkAndIncludeMode(S_IRGRP, fpGroupRead)
- checkAndIncludeMode(S_IWGRP, fpGroupWrite)
- checkAndIncludeMode(S_IXGRP, fpGroupExec)
- checkAndIncludeMode(S_IROTH, fpOthersRead)
- checkAndIncludeMode(S_IWOTH, fpOthersWrite)
- checkAndIncludeMode(S_IXOTH, fpOthersExec)
- formalInfo.kind = pcFile
- if S_ISDIR(rawInfo.st_mode):
- formalInfo.kind = pcDir
- elif S_ISLNK(rawInfo.st_mode):
- assert(path != "") # symlinks can't occur for file handles
- formalInfo.kind = getSymlinkFileKind(path)
- proc getFileInfo*(handle: FileHandle): FileInfo =
- ## Retrieves file information for the file object represented by the given
- ## handle.
- ##
- ## If the information cannot be retrieved, such as when the file handle
- ## is invalid, an error will be thrown.
- # Done: ID, Kind, Size, Permissions, Link Count
- when defined(Windows):
- var rawInfo: BY_HANDLE_FILE_INFORMATION
- # We have to use the super special '_get_osfhandle' call (wrapped above)
- # To transform the C file descripter to a native file handle.
- var realHandle = get_osfhandle(handle)
- if getFileInformationByHandle(realHandle, addr rawInfo) == 0:
- raiseOSError(osLastError())
- rawToFormalFileInfo(rawInfo, "", result)
- else:
- var rawInfo: Stat
- if fstat(handle, rawInfo) < 0'i32:
- raiseOSError(osLastError())
- rawToFormalFileInfo(rawInfo, "", result)
- proc getFileInfo*(file: File): FileInfo =
- if file.isNil:
- raise newException(IOError, "File is nil")
- result = getFileInfo(file.getFileHandle())
- proc getFileInfo*(path: string, followSymlink = true): FileInfo =
- ## Retrieves file information for the file object pointed to by `path`.
- ##
- ## Due to intrinsic differences between operating systems, the information
- ## contained by the returned `FileInfo` structure will be slightly different
- ## across platforms, and in some cases, incomplete or inaccurate.
- ##
- ## When `followSymlink` is true, symlinks are followed and the information
- ## retrieved is information related to the symlink's target. Otherwise,
- ## information on the symlink itself is retrieved.
- ##
- ## If the information cannot be retrieved, such as when the path doesn't
- ## exist, or when permission restrictions prevent the program from retrieving
- ## file information, an error will be thrown.
- when defined(Windows):
- var
- handle = openHandle(path, followSymlink)
- rawInfo: BY_HANDLE_FILE_INFORMATION
- if handle == INVALID_HANDLE_VALUE:
- raiseOSError(osLastError())
- if getFileInformationByHandle(handle, addr rawInfo) == 0:
- raiseOSError(osLastError())
- rawToFormalFileInfo(rawInfo, path, result)
- discard closeHandle(handle)
- else:
- var rawInfo: Stat
- if followSymlink:
- if stat(path, rawInfo) < 0'i32:
- raiseOSError(osLastError())
- else:
- if lstat(path, rawInfo) < 0'i32:
- raiseOSError(osLastError())
- rawToFormalFileInfo(rawInfo, path, result)
- proc isHidden*(path: string): bool =
- ## Determines whether a given path is hidden or not. Returns false if the
- ## file doesn't exist. The given path must be accessible from the current
- ## working directory of the program.
- ##
- ## On Windows, a file is hidden if the file's 'hidden' attribute is set.
- ## On Unix-like systems, a file is hidden if it starts with a '.' (period)
- ## and is not *just* '.' or '..' ' ."
- when defined(Windows):
- when useWinUnicode:
- wrapUnary(attributes, getFileAttributesW, path)
- else:
- var attributes = getFileAttributesA(path)
- if attributes != -1'i32:
- result = (attributes and FILE_ATTRIBUTE_HIDDEN) != 0'i32
- else:
- if fileExists(path):
- let
- fileName = extractFilename(path)
- nameLen = len(fileName)
- if nameLen == 2:
- result = (fileName[0] == '.') and (fileName[1] != '.')
- elif nameLen > 2:
- result = (fileName[0] == '.') and (fileName[3] != '.')
- {.pop.}
|