os.nim 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2015 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## This module contains basic operating system facilities like
  10. ## retrieving environment variables, working with directories,
  11. ## running shell commands, etc.
  12. ## .. importdoc:: symlinks.nim, appdirs.nim, dirs.nim, ospaths2.nim
  13. runnableExamples:
  14. let myFile = "/path/to/my/file.nim"
  15. assert splitPath(myFile) == (head: "/path/to/my", tail: "file.nim")
  16. when defined(posix):
  17. assert parentDir(myFile) == "/path/to/my"
  18. assert splitFile(myFile) == (dir: "/path/to/my", name: "file", ext: ".nim")
  19. assert myFile.changeFileExt("c") == "/path/to/my/file.c"
  20. ## **See also:**
  21. ## * `paths <paths.html>`_ and `files <files.html>`_ modules for high-level file manipulation
  22. ## * `osproc module <osproc.html>`_ for process communication beyond
  23. ## `execShellCmd proc`_
  24. ## * `uri module <uri.html>`_
  25. ## * `distros module <distros.html>`_
  26. ## * `dynlib module <dynlib.html>`_
  27. ## * `streams module <streams.html>`_
  28. import std/private/ospaths2
  29. export ospaths2
  30. import std/private/osfiles
  31. export osfiles
  32. import std/private/osdirs
  33. export osdirs
  34. import std/private/ossymlinks
  35. export ossymlinks
  36. import std/private/osappdirs
  37. export osappdirs
  38. import std/private/oscommon
  39. include system/inclrtl
  40. import std/private/since
  41. import std/cmdline
  42. export cmdline
  43. import std/[strutils, pathnorm]
  44. when defined(nimPreviewSlimSystem):
  45. import std/[syncio, assertions, widestrs]
  46. const weirdTarget = defined(nimscript) or defined(js)
  47. since (1, 1):
  48. const
  49. invalidFilenameChars* = {'/', '\\', ':', '*', '?', '"', '<', '>', '|', '^', '\0'} ## \
  50. ## Characters that may produce invalid filenames across Linux, Windows and Mac.
  51. ## You can check if your filename contains any of these chars and strip them for safety.
  52. ## Mac bans ``':'``, Linux bans ``'/'``, Windows bans all others.
  53. invalidFilenames* = [
  54. "CON", "PRN", "AUX", "NUL",
  55. "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
  56. "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"] ## \
  57. ## Filenames that may be invalid across Linux, Windows, Mac, etc.
  58. ## You can check if your filename match these and rename it for safety
  59. ## (Currently all invalid filenames are from Windows only).
  60. when weirdTarget:
  61. discard
  62. elif defined(windows):
  63. import std/[winlean, times]
  64. elif defined(posix):
  65. import std/[posix, times]
  66. proc toTime(ts: Timespec): times.Time {.inline.} =
  67. result = initTime(ts.tv_sec.int64, ts.tv_nsec.int)
  68. else:
  69. {.error: "OS module not ported to your operating system!".}
  70. when weirdTarget:
  71. {.pragma: noWeirdTarget, error: "this proc is not available on the NimScript/js target".}
  72. else:
  73. {.pragma: noWeirdTarget.}
  74. when defined(nimscript):
  75. # for procs already defined in scriptconfig.nim
  76. template noNimJs(body): untyped = discard
  77. elif defined(js):
  78. {.pragma: noNimJs, error: "this proc is not available on the js target".}
  79. else:
  80. {.pragma: noNimJs.}
  81. import std/oserrors
  82. export oserrors
  83. import std/envvars
  84. export envvars
  85. import std/private/osseps
  86. export osseps
  87. proc expandTilde*(path: string): string {.
  88. tags: [ReadEnvEffect, ReadIOEffect].} =
  89. ## Expands ``~`` or a path starting with ``~/`` to a full path, replacing
  90. ## ``~`` with `getHomeDir()`_ (otherwise returns ``path`` unmodified).
  91. ##
  92. ## Windows: this is still supported despite the Windows platform not having this
  93. ## convention; also, both ``~/`` and ``~\`` are handled.
  94. ##
  95. ## See also:
  96. ## * `getHomeDir proc`_
  97. ## * `getConfigDir proc`_
  98. ## * `getTempDir proc`_
  99. ## * `getCurrentDir proc`_
  100. ## * `setCurrentDir proc`_
  101. runnableExamples:
  102. assert expandTilde("~" / "appname.cfg") == getHomeDir() / "appname.cfg"
  103. assert expandTilde("~/foo/bar") == getHomeDir() / "foo/bar"
  104. assert expandTilde("/foo/bar") == "/foo/bar"
  105. if len(path) == 0 or path[0] != '~':
  106. result = path
  107. elif len(path) == 1:
  108. result = getHomeDir()
  109. elif (path[1] in {DirSep, AltSep}):
  110. result = getHomeDir() / path.substr(2)
  111. else:
  112. # TODO: handle `~bob` and `~bob/` which means home of bob
  113. result = path
  114. proc quoteShellWindows*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} =
  115. ## Quote `s`, so it can be safely passed to Windows API.
  116. ##
  117. ## Based on Python's `subprocess.list2cmdline`.
  118. ## See `this link <https://msdn.microsoft.com/en-us/library/17w5ykft.aspx>`_
  119. ## for more details.
  120. let needQuote = {' ', '\t'} in s or s.len == 0
  121. result = ""
  122. var backslashBuff = ""
  123. if needQuote:
  124. result.add("\"")
  125. for c in s:
  126. if c == '\\':
  127. backslashBuff.add(c)
  128. elif c == '\"':
  129. for i in 0..<backslashBuff.len*2:
  130. result.add('\\')
  131. backslashBuff.setLen(0)
  132. result.add("\\\"")
  133. else:
  134. if backslashBuff.len != 0:
  135. result.add(backslashBuff)
  136. backslashBuff.setLen(0)
  137. result.add(c)
  138. if backslashBuff.len > 0:
  139. result.add(backslashBuff)
  140. if needQuote:
  141. result.add(backslashBuff)
  142. result.add("\"")
  143. proc quoteShellPosix*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} =
  144. ## Quote ``s``, so it can be safely passed to POSIX shell.
  145. const safeUnixChars = {'%', '+', '-', '.', '/', '_', ':', '=', '@',
  146. '0'..'9', 'A'..'Z', 'a'..'z'}
  147. if s.len == 0:
  148. result = "''"
  149. elif s.allCharsInSet(safeUnixChars):
  150. result = s
  151. else:
  152. result = "'" & s.replace("'", "'\"'\"'") & "'"
  153. when defined(windows) or defined(posix) or defined(nintendoswitch):
  154. proc quoteShell*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} =
  155. ## Quote ``s``, so it can be safely passed to shell.
  156. ##
  157. ## When on Windows, it calls `quoteShellWindows proc`_.
  158. ## Otherwise, calls `quoteShellPosix proc`_.
  159. when defined(windows):
  160. result = quoteShellWindows(s)
  161. else:
  162. result = quoteShellPosix(s)
  163. proc quoteShellCommand*(args: openArray[string]): string =
  164. ## Concatenates and quotes shell arguments `args`.
  165. runnableExamples:
  166. when defined(posix):
  167. assert quoteShellCommand(["aaa", "", "c d"]) == "aaa '' 'c d'"
  168. when defined(windows):
  169. assert quoteShellCommand(["aaa", "", "c d"]) == "aaa \"\" \"c d\""
  170. # can't use `map` pending https://github.com/nim-lang/Nim/issues/8303
  171. result = ""
  172. for i in 0..<args.len:
  173. if i > 0: result.add " "
  174. result.add quoteShell(args[i])
  175. when not weirdTarget:
  176. proc c_system(cmd: cstring): cint {.
  177. importc: "system", header: "<stdlib.h>".}
  178. when not defined(windows):
  179. proc c_free(p: pointer) {.
  180. importc: "free", header: "<stdlib.h>".}
  181. const
  182. ExeExts* = ## Platform specific file extension for executables.
  183. ## On Windows ``["exe", "cmd", "bat"]``, on Posix ``[""]``.
  184. when defined(windows): ["exe", "cmd", "bat"] else: [""]
  185. proc findExe*(exe: string, followSymlinks: bool = true;
  186. extensions: openArray[string]=ExeExts): string {.
  187. tags: [ReadDirEffect, ReadEnvEffect, ReadIOEffect], noNimJs.} =
  188. ## Searches for `exe` in the current working directory and then
  189. ## in directories listed in the ``PATH`` environment variable.
  190. ##
  191. ## Returns `""` if the `exe` cannot be found. `exe`
  192. ## is added the `ExeExts`_ file extensions if it has none.
  193. ##
  194. ## If the system supports symlinks it also resolves them until it
  195. ## meets the actual file. This behavior can be disabled if desired
  196. ## by setting `followSymlinks = false`.
  197. if exe.len == 0: return
  198. template checkCurrentDir() =
  199. for ext in extensions:
  200. result = addFileExt(exe, ext)
  201. if fileExists(result): return
  202. when defined(posix):
  203. if '/' in exe: checkCurrentDir()
  204. else:
  205. checkCurrentDir()
  206. let path = getEnv("PATH")
  207. for candidate in split(path, PathSep):
  208. if candidate.len == 0: continue
  209. when defined(windows):
  210. var x = (if candidate[0] == '"' and candidate[^1] == '"':
  211. substr(candidate, 1, candidate.len-2) else: candidate) /
  212. exe
  213. else:
  214. var x = expandTilde(candidate) / exe
  215. for ext in extensions:
  216. var x = addFileExt(x, ext)
  217. if fileExists(x):
  218. when not (defined(windows) or defined(nintendoswitch)):
  219. while followSymlinks: # doubles as if here
  220. if x.symlinkExists:
  221. var r = newString(maxSymlinkLen)
  222. var len = readlink(x.cstring, r.cstring, maxSymlinkLen)
  223. if len < 0:
  224. raiseOSError(osLastError(), exe)
  225. if len > maxSymlinkLen:
  226. r = newString(len+1)
  227. len = readlink(x.cstring, r.cstring, len)
  228. setLen(r, len)
  229. if isAbsolute(r):
  230. x = r
  231. else:
  232. x = parentDir(x) / r
  233. else:
  234. break
  235. return x
  236. result = ""
  237. when weirdTarget:
  238. const times = "fake const"
  239. template Time(x: untyped): untyped = string
  240. proc getLastModificationTime*(file: string): times.Time {.rtl, extern: "nos$1", noWeirdTarget.} =
  241. ## Returns the `file`'s last modification time.
  242. ##
  243. ## See also:
  244. ## * `getLastAccessTime proc`_
  245. ## * `getCreationTime proc`_
  246. ## * `fileNewer proc`_
  247. when defined(posix):
  248. var res: Stat = default(Stat)
  249. if stat(file, res) < 0'i32: raiseOSError(osLastError(), file)
  250. result = res.st_mtim.toTime
  251. else:
  252. var f: WIN32_FIND_DATA
  253. var h = findFirstFile(file, f)
  254. if h == -1'i32: raiseOSError(osLastError(), file)
  255. result = fromWinTime(rdFileTime(f.ftLastWriteTime))
  256. findClose(h)
  257. proc getLastAccessTime*(file: string): times.Time {.rtl, extern: "nos$1", noWeirdTarget.} =
  258. ## Returns the `file`'s last read or write access time.
  259. ##
  260. ## See also:
  261. ## * `getLastModificationTime proc`_
  262. ## * `getCreationTime proc`_
  263. ## * `fileNewer proc`_
  264. when defined(posix):
  265. var res: Stat = default(Stat)
  266. if stat(file, res) < 0'i32: raiseOSError(osLastError(), file)
  267. result = res.st_atim.toTime
  268. else:
  269. var f: WIN32_FIND_DATA
  270. var h = findFirstFile(file, f)
  271. if h == -1'i32: raiseOSError(osLastError(), file)
  272. result = fromWinTime(rdFileTime(f.ftLastAccessTime))
  273. findClose(h)
  274. proc getCreationTime*(file: string): times.Time {.rtl, extern: "nos$1", noWeirdTarget.} =
  275. ## Returns the `file`'s creation time.
  276. ##
  277. ## **Note:** Under POSIX OS's, the returned time may actually be the time at
  278. ## which the file's attribute's were last modified. See
  279. ## `here <https://github.com/nim-lang/Nim/issues/1058>`_ for details.
  280. ##
  281. ## See also:
  282. ## * `getLastModificationTime proc`_
  283. ## * `getLastAccessTime proc`_
  284. ## * `fileNewer proc`_
  285. when defined(posix):
  286. var res: Stat = default(Stat)
  287. if stat(file, res) < 0'i32: raiseOSError(osLastError(), file)
  288. result = res.st_ctim.toTime
  289. else:
  290. var f: WIN32_FIND_DATA
  291. var h = findFirstFile(file, f)
  292. if h == -1'i32: raiseOSError(osLastError(), file)
  293. result = fromWinTime(rdFileTime(f.ftCreationTime))
  294. findClose(h)
  295. proc fileNewer*(a, b: string): bool {.rtl, extern: "nos$1", noWeirdTarget.} =
  296. ## Returns true if the file `a` is newer than file `b`, i.e. if `a`'s
  297. ## modification time is later than `b`'s.
  298. ##
  299. ## See also:
  300. ## * `getLastModificationTime proc`_
  301. ## * `getLastAccessTime proc`_
  302. ## * `getCreationTime proc`_
  303. when defined(posix):
  304. # If we don't have access to nanosecond resolution, use '>='
  305. when not StatHasNanoseconds:
  306. result = getLastModificationTime(a) >= getLastModificationTime(b)
  307. else:
  308. result = getLastModificationTime(a) > getLastModificationTime(b)
  309. else:
  310. result = getLastModificationTime(a) > getLastModificationTime(b)
  311. proc isAdmin*: bool {.noWeirdTarget.} =
  312. ## Returns whether the caller's process is a member of the Administrators local
  313. ## group (on Windows) or a root (on POSIX), via `geteuid() == 0`.
  314. when defined(windows):
  315. # Rewrite of the example from Microsoft Docs:
  316. # https://docs.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-checktokenmembership#examples
  317. # and corresponding PostgreSQL function:
  318. # https://doxygen.postgresql.org/win32security_8c.html#ae6b61e106fa5d6c5d077a9d14ee80569
  319. var ntAuthority = SID_IDENTIFIER_AUTHORITY(value: SECURITY_NT_AUTHORITY)
  320. var administratorsGroup: PSID
  321. if not isSuccess(allocateAndInitializeSid(addr ntAuthority,
  322. BYTE(2),
  323. SECURITY_BUILTIN_DOMAIN_RID,
  324. DOMAIN_ALIAS_RID_ADMINS,
  325. 0, 0, 0, 0, 0, 0,
  326. addr administratorsGroup)):
  327. raiseOSError(osLastError(), "could not get SID for Administrators group")
  328. try:
  329. var b: WINBOOL
  330. if not isSuccess(checkTokenMembership(0, administratorsGroup, addr b)):
  331. raiseOSError(osLastError(), "could not check access token membership")
  332. result = isSuccess(b)
  333. finally:
  334. if freeSid(administratorsGroup) != nil:
  335. raiseOSError(osLastError(), "failed to free SID for Administrators group")
  336. else:
  337. result = geteuid() == 0
  338. proc exitStatusLikeShell*(status: cint): cint =
  339. ## Converts exit code from `c_system` into a shell exit code.
  340. when defined(posix) and not weirdTarget:
  341. if WIFSIGNALED(status):
  342. # like the shell!
  343. 128 + WTERMSIG(status)
  344. else:
  345. WEXITSTATUS(status)
  346. else:
  347. status
  348. proc execShellCmd*(command: string): int {.rtl, extern: "nos$1",
  349. tags: [ExecIOEffect], noWeirdTarget.} =
  350. ## Executes a `shell command`:idx:.
  351. ##
  352. ## Command has the form 'program args' where args are the command
  353. ## line arguments given to program. The proc returns the error code
  354. ## of the shell when it has finished (zero if there is no error).
  355. ## The proc does not return until the process has finished.
  356. ##
  357. ## To execute a program without having a shell involved, use `osproc.execProcess proc
  358. ## <osproc.html#execProcess,string,string,openArray[string],StringTableRef,set[ProcessOption]>`_.
  359. ##
  360. ## **Examples:**
  361. ## ```Nim
  362. ## discard execShellCmd("ls -la")
  363. ## ```
  364. result = exitStatusLikeShell(c_system(command))
  365. proc expandFilename*(filename: string): string {.rtl, extern: "nos$1",
  366. tags: [ReadDirEffect], noWeirdTarget.} =
  367. ## Returns the full (`absolute`:idx:) path of an existing file `filename`.
  368. ##
  369. ## Raises `OSError` in case of an error. Follows symlinks.
  370. result = ""
  371. when defined(windows):
  372. var bufsize = MAX_PATH.int32
  373. var unused: WideCString = nil
  374. var res = newWideCString(bufsize)
  375. while true:
  376. var L = getFullPathNameW(newWideCString(filename), bufsize, res, unused)
  377. if L == 0'i32:
  378. raiseOSError(osLastError(), filename)
  379. elif L > bufsize:
  380. res = newWideCString(L)
  381. bufsize = L
  382. else:
  383. result = res$L
  384. break
  385. # getFullPathName doesn't do case corrections, so we have to use this convoluted
  386. # way of retrieving the true filename
  387. for x in walkFiles(result):
  388. result = x
  389. if not fileExists(result) and not dirExists(result):
  390. # consider using: `raiseOSError(osLastError(), result)`
  391. raise newException(OSError, "file '" & result & "' does not exist")
  392. else:
  393. # according to Posix we don't need to allocate space for result pathname.
  394. # But we need to free return value with free(3).
  395. var r = realpath(filename, nil)
  396. if r.isNil:
  397. raiseOSError(osLastError(), filename)
  398. else:
  399. result = $r
  400. c_free(cast[pointer](r))
  401. proc getCurrentCompilerExe*(): string {.compileTime.} =
  402. result = ""
  403. discard "implemented in the vmops"
  404. ## Returns the path of the currently running Nim compiler or nimble executable.
  405. ##
  406. ## Can be used to retrieve the currently executing
  407. ## Nim compiler from a Nim or nimscript program, or the nimble binary
  408. ## inside a nimble program (likewise with other binaries built from
  409. ## compiler API).
  410. proc createHardlink*(src, dest: string) {.noWeirdTarget.} =
  411. ## Create a hard link at `dest` which points to the item specified
  412. ## by `src`.
  413. ##
  414. ## .. warning:: Some OS's restrict the creation of hard links to
  415. ## root users (administrators).
  416. ##
  417. ## See also:
  418. ## * `createSymlink proc`_
  419. when defined(windows):
  420. var wSrc = newWideCString(src)
  421. var wDst = newWideCString(dest)
  422. if createHardLinkW(wDst, wSrc, nil) == 0:
  423. raiseOSError(osLastError(), $(src, dest))
  424. else:
  425. if link(src, dest) != 0:
  426. raiseOSError(osLastError(), $(src, dest))
  427. proc inclFilePermissions*(filename: string,
  428. permissions: set[FilePermission]) {.
  429. rtl, extern: "nos$1", tags: [ReadDirEffect, WriteDirEffect], noWeirdTarget.} =
  430. ## A convenience proc for:
  431. ## ```nim
  432. ## setFilePermissions(filename, getFilePermissions(filename)+permissions)
  433. ## ```
  434. setFilePermissions(filename, getFilePermissions(filename)+permissions)
  435. proc exclFilePermissions*(filename: string,
  436. permissions: set[FilePermission]) {.
  437. rtl, extern: "nos$1", tags: [ReadDirEffect, WriteDirEffect], noWeirdTarget.} =
  438. ## A convenience proc for:
  439. ## ```nim
  440. ## setFilePermissions(filename, getFilePermissions(filename)-permissions)
  441. ## ```
  442. setFilePermissions(filename, getFilePermissions(filename)-permissions)
  443. when not weirdTarget and (defined(freebsd) or defined(dragonfly) or defined(netbsd)):
  444. proc sysctl(name: ptr cint, namelen: cuint, oldp: pointer, oldplen: ptr csize_t,
  445. newp: pointer, newplen: csize_t): cint
  446. {.importc: "sysctl",header: """#include <sys/types.h>
  447. #include <sys/sysctl.h>""".}
  448. const
  449. CTL_KERN = 1
  450. KERN_PROC = 14
  451. MAX_PATH = 1024
  452. when defined(freebsd):
  453. const KERN_PROC_PATHNAME = 12
  454. elif defined(netbsd):
  455. const KERN_PROC_ARGS = 48
  456. const KERN_PROC_PATHNAME = 5
  457. else:
  458. const KERN_PROC_PATHNAME = 9
  459. proc getApplFreebsd(): string =
  460. var pathLength = csize_t(0)
  461. when defined(netbsd):
  462. var req = [CTL_KERN.cint, KERN_PROC_ARGS.cint, -1.cint, KERN_PROC_PATHNAME.cint]
  463. else:
  464. var req = [CTL_KERN.cint, KERN_PROC.cint, KERN_PROC_PATHNAME.cint, -1.cint]
  465. # first call to get the required length
  466. var res = sysctl(addr req[0], 4, nil, addr pathLength, nil, 0)
  467. if res < 0:
  468. return ""
  469. result.setLen(pathLength)
  470. res = sysctl(addr req[0], 4, addr result[0], addr pathLength, nil, 0)
  471. if res < 0:
  472. return ""
  473. let realLen = len(cstring(result))
  474. setLen(result, realLen)
  475. when not weirdTarget and (defined(linux) or defined(solaris) or defined(bsd) or defined(aix)):
  476. proc getApplAux(procPath: string): string =
  477. result = newString(maxSymlinkLen)
  478. var len = readlink(procPath, result.cstring, maxSymlinkLen)
  479. if len > maxSymlinkLen:
  480. result = newString(len+1)
  481. len = readlink(procPath, result.cstring, len)
  482. setLen(result, len)
  483. when not weirdTarget and defined(openbsd):
  484. proc getApplOpenBsd(): string =
  485. # similar to getApplHeuristic, but checks current working directory
  486. when declared(paramStr):
  487. result = ""
  488. # POSIX guaranties that this contains the executable
  489. # as it has been executed by the calling process
  490. let exePath = paramStr(0)
  491. if len(exePath) == 0:
  492. return ""
  493. if exePath[0] == DirSep:
  494. # path is absolute
  495. result = exePath
  496. else:
  497. # not an absolute path, check if it's relative to the current working directory
  498. for i in 1..<len(exePath):
  499. if exePath[i] == DirSep:
  500. result = joinPath(getCurrentDir(), exePath)
  501. break
  502. if len(result) > 0:
  503. return expandFilename(result)
  504. # search in path
  505. for p in split(getEnv("PATH"), {PathSep}):
  506. var x = joinPath(p, exePath)
  507. if fileExists(x):
  508. return expandFilename(x)
  509. else:
  510. result = ""
  511. when not (defined(windows) or defined(macosx) or weirdTarget):
  512. proc getApplHeuristic(): string =
  513. when declared(paramStr):
  514. result = paramStr(0)
  515. # POSIX guaranties that this contains the executable
  516. # as it has been executed by the calling process
  517. if len(result) > 0 and result[0] != DirSep: # not an absolute path?
  518. # iterate over any path in the $PATH environment variable
  519. for p in split(getEnv("PATH"), {PathSep}):
  520. var x = joinPath(p, result)
  521. if fileExists(x): return x
  522. else:
  523. result = ""
  524. when defined(macosx):
  525. type
  526. cuint32* {.importc: "unsigned int", nodecl.} = int
  527. ## This is the same as the type ``uint32_t`` in *C*.
  528. # a really hacky solution: since we like to include 2 headers we have to
  529. # define two procs which in reality are the same
  530. proc getExecPath1(c: cstring, size: var cuint32) {.
  531. importc: "_NSGetExecutablePath", header: "<sys/param.h>".}
  532. proc getExecPath2(c: cstring, size: var cuint32): bool {.
  533. importc: "_NSGetExecutablePath", header: "<mach-o/dyld.h>".}
  534. when defined(haiku):
  535. const
  536. PATH_MAX = 1024
  537. B_FIND_PATH_IMAGE_PATH = 1000
  538. proc find_path(codePointer: pointer, baseDirectory: cint, subPath: cstring,
  539. pathBuffer: cstring, bufferSize: csize_t): int32
  540. {.importc, header: "<FindDirectory.h>".}
  541. proc getApplHaiku(): string =
  542. result = newString(PATH_MAX)
  543. if find_path(nil, B_FIND_PATH_IMAGE_PATH, nil, result, PATH_MAX) == 0:
  544. let realLen = len(cstring(result))
  545. setLen(result, realLen)
  546. else:
  547. result = ""
  548. proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect], noWeirdTarget, raises: [].} =
  549. ## Returns the filename of the application's executable.
  550. ## This proc will resolve symlinks.
  551. ##
  552. ## Returns empty string when name is unavailable
  553. ##
  554. ## See also:
  555. ## * `getAppDir proc`_
  556. ## * `getCurrentCompilerExe proc`_
  557. # Linux: /proc/<pid>/exe
  558. # Solaris:
  559. # /proc/<pid>/object/a.out (filename only)
  560. # /proc/<pid>/path/a.out (complete pathname)
  561. when defined(windows):
  562. var bufsize = int32(MAX_PATH)
  563. var buf = newWideCString(bufsize)
  564. while true:
  565. var L = getModuleFileNameW(0, buf, bufsize)
  566. if L == 0'i32:
  567. result = "" # error!
  568. break
  569. elif L > bufsize:
  570. buf = newWideCString(L)
  571. bufsize = L
  572. else:
  573. result = buf$L
  574. break
  575. elif defined(macosx):
  576. var size = cuint32(0)
  577. getExecPath1(nil, size)
  578. result = newString(int(size))
  579. if getExecPath2(result.cstring, size):
  580. result = "" # error!
  581. if result.len > 0:
  582. try:
  583. result = result.expandFilename
  584. except OSError:
  585. result = ""
  586. else:
  587. when defined(linux) or defined(aix):
  588. result = getApplAux("/proc/self/exe")
  589. elif defined(solaris):
  590. result = getApplAux("/proc/" & $getpid() & "/path/a.out")
  591. elif defined(genode):
  592. result = "" # Not supported
  593. elif defined(freebsd) or defined(dragonfly) or defined(netbsd):
  594. result = getApplFreebsd()
  595. elif defined(haiku):
  596. result = getApplHaiku()
  597. elif defined(openbsd):
  598. result = try: getApplOpenBsd() except OSError: ""
  599. elif defined(nintendoswitch):
  600. result = ""
  601. # little heuristic that may work on other POSIX-like systems:
  602. if result.len == 0:
  603. result = try: getApplHeuristic() except OSError: ""
  604. proc getAppDir*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect], noWeirdTarget.} =
  605. ## Returns the directory of the application's executable.
  606. ##
  607. ## See also:
  608. ## * `getAppFilename proc`_
  609. result = splitFile(getAppFilename()).dir
  610. proc sleep*(milsecs: int) {.rtl, extern: "nos$1", tags: [TimeEffect], noWeirdTarget.} =
  611. ## Sleeps `milsecs` milliseconds.
  612. ## A negative `milsecs` causes sleep to return immediately.
  613. when defined(windows):
  614. if milsecs < 0:
  615. return # fixes #23732
  616. winlean.sleep(int32(milsecs))
  617. else:
  618. var a, b: Timespec = default(Timespec)
  619. a.tv_sec = posix.Time(milsecs div 1000)
  620. a.tv_nsec = (milsecs mod 1000) * 1000 * 1000
  621. discard posix.nanosleep(a, b)
  622. proc getFileSize*(file: string): BiggestInt {.rtl, extern: "nos$1",
  623. tags: [ReadIOEffect], noWeirdTarget.} =
  624. ## Returns the file size of `file` (in bytes). ``OSError`` is
  625. ## raised in case of an error.
  626. when defined(windows):
  627. var a: WIN32_FIND_DATA
  628. var resA = findFirstFile(file, a)
  629. if resA == -1: raiseOSError(osLastError(), file)
  630. result = rdFileSize(a)
  631. findClose(resA)
  632. else:
  633. var rawInfo: Stat = default(Stat)
  634. if stat(file, rawInfo) < 0'i32:
  635. raiseOSError(osLastError(), file)
  636. rawInfo.st_size
  637. when defined(windows) or weirdTarget:
  638. type
  639. DeviceId* = int32
  640. FileId* = int64
  641. else:
  642. type
  643. DeviceId* = Dev
  644. FileId* = Ino
  645. type
  646. FileInfo* = object
  647. ## Contains information associated with a file object.
  648. ##
  649. ## See also:
  650. ## * `getFileInfo(handle) proc`_
  651. ## * `getFileInfo(file) proc`_
  652. ## * `getFileInfo(path, followSymlink) proc`_
  653. id*: tuple[device: DeviceId, file: FileId] ## Device and file id.
  654. kind*: PathComponent ## Kind of file object - directory, symlink, etc.
  655. size*: BiggestInt ## Size of file.
  656. permissions*: set[FilePermission] ## File permissions
  657. linkCount*: BiggestInt ## Number of hard links the file object has.
  658. lastAccessTime*: times.Time ## Time file was last accessed.
  659. lastWriteTime*: times.Time ## Time file was last modified/written to.
  660. creationTime*: times.Time ## Time file was created. Not supported on all systems!
  661. blockSize*: int ## Preferred I/O block size for this object.
  662. ## In some filesystems, this may vary from file to file.
  663. isSpecial*: bool ## Is file special? (on Unix some "files"
  664. ## can be special=non-regular like FIFOs,
  665. ## devices); for directories `isSpecial`
  666. ## is always `false`, for symlinks it is
  667. ## the same as for the link's target.
  668. template rawToFormalFileInfo(rawInfo, path, formalInfo): untyped =
  669. ## Transforms the native file info structure into the one nim uses.
  670. ## 'rawInfo' is either a 'BY_HANDLE_FILE_INFORMATION' structure on Windows,
  671. ## or a 'Stat' structure on posix
  672. when defined(windows):
  673. template merge[T](a, b): untyped =
  674. cast[T](
  675. (uint64(cast[uint32](a))) or
  676. (uint64(cast[uint32](b)) shl 32)
  677. )
  678. formalInfo.id.device = rawInfo.dwVolumeSerialNumber
  679. formalInfo.id.file = merge[FileId](rawInfo.nFileIndexLow, rawInfo.nFileIndexHigh)
  680. formalInfo.size = merge[BiggestInt](rawInfo.nFileSizeLow, rawInfo.nFileSizeHigh)
  681. formalInfo.linkCount = rawInfo.nNumberOfLinks
  682. formalInfo.lastAccessTime = fromWinTime(rdFileTime(rawInfo.ftLastAccessTime))
  683. formalInfo.lastWriteTime = fromWinTime(rdFileTime(rawInfo.ftLastWriteTime))
  684. formalInfo.creationTime = fromWinTime(rdFileTime(rawInfo.ftCreationTime))
  685. formalInfo.blockSize = 8192 # xxx use Windows API instead of hardcoding
  686. # Retrieve basic permissions
  687. if (rawInfo.dwFileAttributes and FILE_ATTRIBUTE_READONLY) != 0'i32:
  688. formalInfo.permissions = {fpUserExec, fpUserRead, fpGroupExec,
  689. fpGroupRead, fpOthersExec, fpOthersRead}
  690. else:
  691. formalInfo.permissions = {fpUserExec..fpOthersRead}
  692. # Retrieve basic file kind
  693. if (rawInfo.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) != 0'i32:
  694. formalInfo.kind = pcDir
  695. else:
  696. formalInfo.kind = pcFile
  697. if (rawInfo.dwFileAttributes and FILE_ATTRIBUTE_REPARSE_POINT) != 0'i32:
  698. formalInfo.kind = succ(formalInfo.kind)
  699. else:
  700. template checkAndIncludeMode(rawMode, formalMode: untyped) =
  701. if (rawInfo.st_mode and rawMode.Mode) != 0.Mode:
  702. formalInfo.permissions.incl(formalMode)
  703. formalInfo.id = (rawInfo.st_dev, rawInfo.st_ino)
  704. formalInfo.size = rawInfo.st_size
  705. formalInfo.linkCount = rawInfo.st_nlink.BiggestInt
  706. formalInfo.lastAccessTime = rawInfo.st_atim.toTime
  707. formalInfo.lastWriteTime = rawInfo.st_mtim.toTime
  708. formalInfo.creationTime = rawInfo.st_ctim.toTime
  709. formalInfo.blockSize = rawInfo.st_blksize
  710. formalInfo.permissions = {}
  711. checkAndIncludeMode(S_IRUSR, fpUserRead)
  712. checkAndIncludeMode(S_IWUSR, fpUserWrite)
  713. checkAndIncludeMode(S_IXUSR, fpUserExec)
  714. checkAndIncludeMode(S_IRGRP, fpGroupRead)
  715. checkAndIncludeMode(S_IWGRP, fpGroupWrite)
  716. checkAndIncludeMode(S_IXGRP, fpGroupExec)
  717. checkAndIncludeMode(S_IROTH, fpOthersRead)
  718. checkAndIncludeMode(S_IWOTH, fpOthersWrite)
  719. checkAndIncludeMode(S_IXOTH, fpOthersExec)
  720. (formalInfo.kind, formalInfo.isSpecial) =
  721. if S_ISDIR(rawInfo.st_mode):
  722. (pcDir, false)
  723. elif S_ISLNK(rawInfo.st_mode):
  724. assert(path != "") # symlinks can't occur for file handles
  725. getSymlinkFileKind(path)
  726. else:
  727. (pcFile, not S_ISREG(rawInfo.st_mode))
  728. when defined(js):
  729. when not declared(FileHandle):
  730. type FileHandle = distinct int32
  731. when not declared(File):
  732. type File = object
  733. proc getFileInfo*(handle: FileHandle): FileInfo {.noWeirdTarget.} =
  734. ## Retrieves file information for the file object represented by the given
  735. ## handle.
  736. ##
  737. ## If the information cannot be retrieved, such as when the file handle
  738. ## is invalid, `OSError` is raised.
  739. ##
  740. ## See also:
  741. ## * `getFileInfo(file) proc`_
  742. ## * `getFileInfo(path, followSymlink) proc`_
  743. # Done: ID, Kind, Size, Permissions, Link Count
  744. result = default(FileInfo)
  745. when defined(windows):
  746. var rawInfo: BY_HANDLE_FILE_INFORMATION
  747. # We have to use the super special '_get_osfhandle' call (wrapped above)
  748. # To transform the C file descriptor to a native file handle.
  749. var realHandle = get_osfhandle(handle)
  750. if getFileInformationByHandle(realHandle, addr rawInfo) == 0:
  751. raiseOSError(osLastError(), $handle)
  752. rawToFormalFileInfo(rawInfo, "", result)
  753. else:
  754. var rawInfo: Stat = default(Stat)
  755. if fstat(handle, rawInfo) < 0'i32:
  756. raiseOSError(osLastError(), $handle)
  757. rawToFormalFileInfo(rawInfo, "", result)
  758. proc getFileInfo*(file: File): FileInfo {.noWeirdTarget.} =
  759. ## Retrieves file information for the file object.
  760. ##
  761. ## See also:
  762. ## * `getFileInfo(handle) proc`_
  763. ## * `getFileInfo(path, followSymlink) proc`_
  764. if file.isNil:
  765. raise newException(IOError, "File is nil")
  766. result = getFileInfo(file.getFileHandle())
  767. proc getFileInfo*(path: string, followSymlink = true): FileInfo {.noWeirdTarget.} =
  768. ## Retrieves file information for the file object pointed to by `path`.
  769. ##
  770. ## Due to intrinsic differences between operating systems, the information
  771. ## contained by the returned `FileInfo object`_ will be slightly
  772. ## different across platforms, and in some cases, incomplete or inaccurate.
  773. ##
  774. ## When `followSymlink` is true (default), symlinks are followed and the
  775. ## information retrieved is information related to the symlink's target.
  776. ## Otherwise, information on the symlink itself is retrieved (however,
  777. ## field `isSpecial` is still determined from the target on Unix).
  778. ##
  779. ## If the information cannot be retrieved, such as when the path doesn't
  780. ## exist, or when permission restrictions prevent the program from retrieving
  781. ## file information, `OSError` is raised.
  782. ##
  783. ## See also:
  784. ## * `getFileInfo(handle) proc`_
  785. ## * `getFileInfo(file) proc`_
  786. result = default(FileInfo)
  787. when defined(windows):
  788. var
  789. handle = openHandle(path, followSymlink)
  790. rawInfo: BY_HANDLE_FILE_INFORMATION
  791. if handle == INVALID_HANDLE_VALUE:
  792. raiseOSError(osLastError(), path)
  793. if getFileInformationByHandle(handle, addr rawInfo) == 0:
  794. raiseOSError(osLastError(), path)
  795. rawToFormalFileInfo(rawInfo, path, result)
  796. discard closeHandle(handle)
  797. else:
  798. var rawInfo: Stat = default(Stat)
  799. if followSymlink:
  800. if stat(path, rawInfo) < 0'i32:
  801. raiseOSError(osLastError(), path)
  802. else:
  803. if lstat(path, rawInfo) < 0'i32:
  804. raiseOSError(osLastError(), path)
  805. rawToFormalFileInfo(rawInfo, path, result)
  806. proc sameFileContent*(path1, path2: string): bool {.rtl, extern: "nos$1",
  807. tags: [ReadIOEffect], noWeirdTarget.} =
  808. ## Returns true if both pathname arguments refer to files with identical
  809. ## binary content.
  810. ##
  811. ## See also:
  812. ## * `sameFile proc`_
  813. result = false
  814. var
  815. a, b: File = default(File)
  816. if not open(a, path1): return false
  817. if not open(b, path2):
  818. close(a)
  819. return false
  820. let bufSize = getFileInfo(a).blockSize
  821. var bufA = alloc(bufSize)
  822. var bufB = alloc(bufSize)
  823. while true:
  824. var readA = readBuffer(a, bufA, bufSize)
  825. var readB = readBuffer(b, bufB, bufSize)
  826. if readA != readB:
  827. result = false
  828. break
  829. if readA == 0:
  830. result = true
  831. break
  832. result = equalMem(bufA, bufB, readA)
  833. if not result: break
  834. if readA != bufSize: break # end of file
  835. dealloc(bufA)
  836. dealloc(bufB)
  837. close(a)
  838. close(b)
  839. proc isHidden*(path: string): bool {.noWeirdTarget.} =
  840. ## Determines whether ``path`` is hidden or not, using `this
  841. ## reference <https://en.wikipedia.org/wiki/Hidden_file_and_hidden_directory>`_.
  842. ##
  843. ## On Windows: returns true if it exists and its "hidden" attribute is set.
  844. ##
  845. ## On posix: returns true if ``lastPathPart(path)`` starts with ``.`` and is
  846. ## not ``.`` or ``..``.
  847. ##
  848. ## **Note**: paths are not normalized to determine `isHidden`.
  849. runnableExamples:
  850. when defined(posix):
  851. assert ".foo".isHidden
  852. assert not ".foo/bar".isHidden
  853. assert not ".".isHidden
  854. assert not "..".isHidden
  855. assert not "".isHidden
  856. assert ".foo/".isHidden
  857. when defined(windows):
  858. wrapUnary(attributes, getFileAttributesW, path)
  859. if attributes != -1'i32:
  860. result = (attributes and FILE_ATTRIBUTE_HIDDEN) != 0'i32
  861. else:
  862. let fileName = lastPathPart(path)
  863. result = len(fileName) >= 2 and fileName[0] == '.' and fileName != ".."
  864. proc getCurrentProcessId*(): int {.noWeirdTarget.} =
  865. ## Return current process ID.
  866. ##
  867. ## See also:
  868. ## * `osproc.processID(p: Process) <osproc.html#processID,Process>`_
  869. when defined(windows):
  870. proc GetCurrentProcessId(): DWORD {.stdcall, dynlib: "kernel32",
  871. importc: "GetCurrentProcessId".}
  872. result = GetCurrentProcessId().int
  873. else:
  874. result = getpid()
  875. proc setLastModificationTime*(file: string, t: times.Time) {.noWeirdTarget.} =
  876. ## Sets the `file`'s last modification time. `OSError` is raised in case of
  877. ## an error.
  878. when defined(posix):
  879. let unixt = posix.Time(t.toUnix)
  880. let micro = convert(Nanoseconds, Microseconds, t.nanosecond)
  881. var timevals = [Timeval(tv_sec: unixt, tv_usec: micro),
  882. Timeval(tv_sec: unixt, tv_usec: micro)] # [last access, last modification]
  883. if utimes(file, timevals.addr) != 0: raiseOSError(osLastError(), file)
  884. else:
  885. let h = openHandle(path = file, writeAccess = true)
  886. if h == INVALID_HANDLE_VALUE: raiseOSError(osLastError(), file)
  887. var ft = t.toWinTime.toFILETIME
  888. let res = setFileTime(h, nil, nil, ft.addr)
  889. discard h.closeHandle
  890. if res == 0'i32: raiseOSError(osLastError(), file)
  891. func isValidFilename*(filename: string, maxLen = 259.Positive): bool {.since: (1, 1).} =
  892. ## Returns `true` if `filename` is valid for crossplatform use.
  893. ##
  894. ## This is useful if you want to copy or save files across Windows, Linux, Mac, etc.
  895. ## It uses `invalidFilenameChars`, `invalidFilenames` and `maxLen` to verify the specified `filename`.
  896. ##
  897. ## See also:
  898. ##
  899. ## * https://docs.microsoft.com/en-us/dotnet/api/system.io.pathtoolongexception
  900. ## * https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
  901. ## * https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
  902. ##
  903. ## .. warning:: This only checks filenames, not whole paths
  904. ## (because basically you can mount anything as a path on Linux).
  905. runnableExamples:
  906. assert not isValidFilename(" foo") # Leading white space
  907. assert not isValidFilename("foo ") # Trailing white space
  908. assert not isValidFilename("foo.") # Ends with dot
  909. assert not isValidFilename("con.txt") # "CON" is invalid (Windows)
  910. assert not isValidFilename("OwO:UwU") # ":" is invalid (Mac)
  911. assert not isValidFilename("aux.bat") # "AUX" is invalid (Windows)
  912. assert not isValidFilename("") # Empty string
  913. assert not isValidFilename("foo/") # Filename is empty
  914. result = true
  915. let f = filename.splitFile()
  916. if unlikely(f.name.len + f.ext.len > maxLen or f.name.len == 0 or
  917. f.name[0] == ' ' or f.name[^1] == ' ' or f.name[^1] == '.' or
  918. find(f.name, invalidFilenameChars) != -1): return false
  919. for invalid in invalidFilenames:
  920. if cmpIgnoreCase(f.name, invalid) == 0: return false
  921. # deprecated declarations
  922. when not weirdTarget:
  923. template existsFile*(args: varargs[untyped]): untyped {.deprecated: "use fileExists".} =
  924. fileExists(args)
  925. template existsDir*(args: varargs[untyped]): untyped {.deprecated: "use dirExists".} =
  926. dirExists(args)