os.nim 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030
  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 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 winlean, times
  64. elif defined(posix):
  65. import 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 <http://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
  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
  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
  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. when defined(windows):
  371. var bufsize = MAX_PATH.int32
  372. var unused: WideCString = nil
  373. var res = newWideCString("", bufsize)
  374. while true:
  375. var L = getFullPathNameW(newWideCString(filename), bufsize, res, unused)
  376. if L == 0'i32:
  377. raiseOSError(osLastError(), filename)
  378. elif L > bufsize:
  379. res = newWideCString("", L)
  380. bufsize = L
  381. else:
  382. result = res$L
  383. break
  384. # getFullPathName doesn't do case corrections, so we have to use this convoluted
  385. # way of retrieving the true filename
  386. for x in walkFiles(result):
  387. result = x
  388. if not fileExists(result) and not dirExists(result):
  389. # consider using: `raiseOSError(osLastError(), result)`
  390. raise newException(OSError, "file '" & result & "' does not exist")
  391. else:
  392. # according to Posix we don't need to allocate space for result pathname.
  393. # But we need to free return value with free(3).
  394. var r = realpath(filename, nil)
  395. if r.isNil:
  396. raiseOSError(osLastError(), filename)
  397. else:
  398. result = $r
  399. c_free(cast[pointer](r))
  400. proc getCurrentCompilerExe*(): string {.compileTime.} = discard
  401. ## This is `getAppFilename()`_ at compile time.
  402. ##
  403. ## Can be used to retrieve the currently executing
  404. ## Nim compiler from a Nim or nimscript program, or the nimble binary
  405. ## inside a nimble program (likewise with other binaries built from
  406. ## compiler API).
  407. proc createHardlink*(src, dest: string) {.noWeirdTarget.} =
  408. ## Create a hard link at `dest` which points to the item specified
  409. ## by `src`.
  410. ##
  411. ## .. warning:: Some OS's restrict the creation of hard links to
  412. ## root users (administrators).
  413. ##
  414. ## See also:
  415. ## * `createSymlink proc`_
  416. when defined(windows):
  417. var wSrc = newWideCString(src)
  418. var wDst = newWideCString(dest)
  419. if createHardLinkW(wDst, wSrc, nil) == 0:
  420. raiseOSError(osLastError(), $(src, dest))
  421. else:
  422. if link(src, dest) != 0:
  423. raiseOSError(osLastError(), $(src, dest))
  424. proc inclFilePermissions*(filename: string,
  425. permissions: set[FilePermission]) {.
  426. rtl, extern: "nos$1", tags: [ReadDirEffect, WriteDirEffect], noWeirdTarget.} =
  427. ## A convenience proc for:
  428. ## ```nim
  429. ## setFilePermissions(filename, getFilePermissions(filename)+permissions)
  430. ## ```
  431. setFilePermissions(filename, getFilePermissions(filename)+permissions)
  432. proc exclFilePermissions*(filename: string,
  433. permissions: set[FilePermission]) {.
  434. rtl, extern: "nos$1", tags: [ReadDirEffect, WriteDirEffect], noWeirdTarget.} =
  435. ## A convenience proc for:
  436. ## ```nim
  437. ## setFilePermissions(filename, getFilePermissions(filename)-permissions)
  438. ## ```
  439. setFilePermissions(filename, getFilePermissions(filename)-permissions)
  440. when not weirdTarget and (defined(freebsd) or defined(dragonfly) or defined(netbsd)):
  441. proc sysctl(name: ptr cint, namelen: cuint, oldp: pointer, oldplen: ptr csize_t,
  442. newp: pointer, newplen: csize_t): cint
  443. {.importc: "sysctl",header: """#include <sys/types.h>
  444. #include <sys/sysctl.h>""".}
  445. const
  446. CTL_KERN = 1
  447. KERN_PROC = 14
  448. MAX_PATH = 1024
  449. when defined(freebsd):
  450. const KERN_PROC_PATHNAME = 12
  451. elif defined(netbsd):
  452. const KERN_PROC_ARGS = 48
  453. const KERN_PROC_PATHNAME = 5
  454. else:
  455. const KERN_PROC_PATHNAME = 9
  456. proc getApplFreebsd(): string =
  457. var pathLength = csize_t(0)
  458. when defined(netbsd):
  459. var req = [CTL_KERN.cint, KERN_PROC_ARGS.cint, -1.cint, KERN_PROC_PATHNAME.cint]
  460. else:
  461. var req = [CTL_KERN.cint, KERN_PROC.cint, KERN_PROC_PATHNAME.cint, -1.cint]
  462. # first call to get the required length
  463. var res = sysctl(addr req[0], 4, nil, addr pathLength, nil, 0)
  464. if res < 0:
  465. return ""
  466. result.setLen(pathLength)
  467. res = sysctl(addr req[0], 4, addr result[0], addr pathLength, nil, 0)
  468. if res < 0:
  469. return ""
  470. let realLen = len(cstring(result))
  471. setLen(result, realLen)
  472. when not weirdTarget and (defined(linux) or defined(solaris) or defined(bsd) or defined(aix)):
  473. proc getApplAux(procPath: string): string =
  474. result = newString(maxSymlinkLen)
  475. var len = readlink(procPath, result.cstring, maxSymlinkLen)
  476. if len > maxSymlinkLen:
  477. result = newString(len+1)
  478. len = readlink(procPath, result.cstring, len)
  479. setLen(result, len)
  480. when not weirdTarget and defined(openbsd):
  481. proc getApplOpenBsd(): string =
  482. # similar to getApplHeuristic, but checks current working directory
  483. when declared(paramStr):
  484. result = ""
  485. # POSIX guaranties that this contains the executable
  486. # as it has been executed by the calling process
  487. let exePath = paramStr(0)
  488. if len(exePath) == 0:
  489. return ""
  490. if exePath[0] == DirSep:
  491. # path is absolute
  492. result = exePath
  493. else:
  494. # not an absolute path, check if it's relative to the current working directory
  495. for i in 1..<len(exePath):
  496. if exePath[i] == DirSep:
  497. result = joinPath(getCurrentDir(), exePath)
  498. break
  499. if len(result) > 0:
  500. return expandFilename(result)
  501. # search in path
  502. for p in split(getEnv("PATH"), {PathSep}):
  503. var x = joinPath(p, exePath)
  504. if fileExists(x):
  505. return expandFilename(x)
  506. else:
  507. result = ""
  508. when not (defined(windows) or defined(macosx) or weirdTarget):
  509. proc getApplHeuristic(): string =
  510. when declared(paramStr):
  511. result = paramStr(0)
  512. # POSIX guaranties that this contains the executable
  513. # as it has been executed by the calling process
  514. if len(result) > 0 and result[0] != DirSep: # not an absolute path?
  515. # iterate over any path in the $PATH environment variable
  516. for p in split(getEnv("PATH"), {PathSep}):
  517. var x = joinPath(p, result)
  518. if fileExists(x): return x
  519. else:
  520. result = ""
  521. when defined(macosx):
  522. type
  523. cuint32* {.importc: "unsigned int", nodecl.} = int
  524. ## This is the same as the type ``uint32_t`` in *C*.
  525. # a really hacky solution: since we like to include 2 headers we have to
  526. # define two procs which in reality are the same
  527. proc getExecPath1(c: cstring, size: var cuint32) {.
  528. importc: "_NSGetExecutablePath", header: "<sys/param.h>".}
  529. proc getExecPath2(c: cstring, size: var cuint32): bool {.
  530. importc: "_NSGetExecutablePath", header: "<mach-o/dyld.h>".}
  531. when defined(haiku):
  532. const
  533. PATH_MAX = 1024
  534. B_FIND_PATH_IMAGE_PATH = 1000
  535. proc find_path(codePointer: pointer, baseDirectory: cint, subPath: cstring,
  536. pathBuffer: cstring, bufferSize: csize_t): int32
  537. {.importc, header: "<FindDirectory.h>".}
  538. proc getApplHaiku(): string =
  539. result = newString(PATH_MAX)
  540. if find_path(nil, B_FIND_PATH_IMAGE_PATH, nil, result, PATH_MAX) == 0:
  541. let realLen = len(cstring(result))
  542. setLen(result, realLen)
  543. else:
  544. result = ""
  545. proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect], noWeirdTarget, raises: [].} =
  546. ## Returns the filename of the application's executable.
  547. ## This proc will resolve symlinks.
  548. ##
  549. ## Returns empty string when name is unavailable
  550. ##
  551. ## See also:
  552. ## * `getAppDir proc`_
  553. ## * `getCurrentCompilerExe proc`_
  554. # Linux: /proc/<pid>/exe
  555. # Solaris:
  556. # /proc/<pid>/object/a.out (filename only)
  557. # /proc/<pid>/path/a.out (complete pathname)
  558. when defined(windows):
  559. var bufsize = int32(MAX_PATH)
  560. var buf = newWideCString(bufsize)
  561. while true:
  562. var L = getModuleFileNameW(0, buf, bufsize)
  563. if L == 0'i32:
  564. result = "" # error!
  565. break
  566. elif L > bufsize:
  567. buf = newWideCString(L)
  568. bufsize = L
  569. else:
  570. result = buf$L
  571. break
  572. elif defined(macosx):
  573. var size = cuint32(0)
  574. getExecPath1(nil, size)
  575. result = newString(int(size))
  576. if getExecPath2(result.cstring, size):
  577. result = "" # error!
  578. if result.len > 0:
  579. try:
  580. result = result.expandFilename
  581. except OSError:
  582. result = ""
  583. else:
  584. when defined(linux) or defined(aix):
  585. result = getApplAux("/proc/self/exe")
  586. elif defined(solaris):
  587. result = getApplAux("/proc/" & $getpid() & "/path/a.out")
  588. elif defined(genode):
  589. result = "" # Not supported
  590. elif defined(freebsd) or defined(dragonfly) or defined(netbsd):
  591. result = getApplFreebsd()
  592. elif defined(haiku):
  593. result = getApplHaiku()
  594. elif defined(openbsd):
  595. result = try: getApplOpenBsd() except OSError: ""
  596. elif defined(nintendoswitch):
  597. result = ""
  598. # little heuristic that may work on other POSIX-like systems:
  599. if result.len == 0:
  600. result = try: getApplHeuristic() except OSError: ""
  601. proc getAppDir*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect], noWeirdTarget.} =
  602. ## Returns the directory of the application's executable.
  603. ##
  604. ## See also:
  605. ## * `getAppFilename proc`_
  606. result = splitFile(getAppFilename()).dir
  607. proc sleep*(milsecs: int) {.rtl, extern: "nos$1", tags: [TimeEffect], noWeirdTarget.} =
  608. ## Sleeps `milsecs` milliseconds.
  609. when defined(windows):
  610. winlean.sleep(int32(milsecs))
  611. else:
  612. var a, b: Timespec
  613. a.tv_sec = posix.Time(milsecs div 1000)
  614. a.tv_nsec = (milsecs mod 1000) * 1000 * 1000
  615. discard posix.nanosleep(a, b)
  616. proc getFileSize*(file: string): BiggestInt {.rtl, extern: "nos$1",
  617. tags: [ReadIOEffect], noWeirdTarget.} =
  618. ## Returns the file size of `file` (in bytes). ``OSError`` is
  619. ## raised in case of an error.
  620. when defined(windows):
  621. var a: WIN32_FIND_DATA
  622. var resA = findFirstFile(file, a)
  623. if resA == -1: raiseOSError(osLastError(), file)
  624. result = rdFileSize(a)
  625. findClose(resA)
  626. else:
  627. var rawInfo: Stat
  628. if stat(file, rawInfo) < 0'i32:
  629. raiseOSError(osLastError(), file)
  630. rawInfo.st_size
  631. when defined(windows) or weirdTarget:
  632. type
  633. DeviceId* = int32
  634. FileId* = int64
  635. else:
  636. type
  637. DeviceId* = Dev
  638. FileId* = Ino
  639. type
  640. FileInfo* = object
  641. ## Contains information associated with a file object.
  642. ##
  643. ## See also:
  644. ## * `getFileInfo(handle) proc`_
  645. ## * `getFileInfo(file) proc`_
  646. ## * `getFileInfo(path, followSymlink) proc`_
  647. id*: tuple[device: DeviceId, file: FileId] ## Device and file id.
  648. kind*: PathComponent ## Kind of file object - directory, symlink, etc.
  649. size*: BiggestInt ## Size of file.
  650. permissions*: set[FilePermission] ## File permissions
  651. linkCount*: BiggestInt ## Number of hard links the file object has.
  652. lastAccessTime*: times.Time ## Time file was last accessed.
  653. lastWriteTime*: times.Time ## Time file was last modified/written to.
  654. creationTime*: times.Time ## Time file was created. Not supported on all systems!
  655. blockSize*: int ## Preferred I/O block size for this object.
  656. ## In some filesystems, this may vary from file to file.
  657. isSpecial*: bool ## Is file special? (on Unix some "files"
  658. ## can be special=non-regular like FIFOs,
  659. ## devices); for directories `isSpecial`
  660. ## is always `false`, for symlinks it is
  661. ## the same as for the link's target.
  662. template rawToFormalFileInfo(rawInfo, path, formalInfo): untyped =
  663. ## Transforms the native file info structure into the one nim uses.
  664. ## 'rawInfo' is either a 'BY_HANDLE_FILE_INFORMATION' structure on Windows,
  665. ## or a 'Stat' structure on posix
  666. when defined(windows):
  667. template merge(a, b): untyped =
  668. int64(
  669. (uint64(cast[uint32](a))) or
  670. (uint64(cast[uint32](b)) shl 32)
  671. )
  672. formalInfo.id.device = rawInfo.dwVolumeSerialNumber
  673. formalInfo.id.file = merge(rawInfo.nFileIndexLow, rawInfo.nFileIndexHigh)
  674. formalInfo.size = merge(rawInfo.nFileSizeLow, rawInfo.nFileSizeHigh)
  675. formalInfo.linkCount = rawInfo.nNumberOfLinks
  676. formalInfo.lastAccessTime = fromWinTime(rdFileTime(rawInfo.ftLastAccessTime))
  677. formalInfo.lastWriteTime = fromWinTime(rdFileTime(rawInfo.ftLastWriteTime))
  678. formalInfo.creationTime = fromWinTime(rdFileTime(rawInfo.ftCreationTime))
  679. formalInfo.blockSize = 8192 # xxx use Windows API instead of hardcoding
  680. # Retrieve basic permissions
  681. if (rawInfo.dwFileAttributes and FILE_ATTRIBUTE_READONLY) != 0'i32:
  682. formalInfo.permissions = {fpUserExec, fpUserRead, fpGroupExec,
  683. fpGroupRead, fpOthersExec, fpOthersRead}
  684. else:
  685. formalInfo.permissions = {fpUserExec..fpOthersRead}
  686. # Retrieve basic file kind
  687. if (rawInfo.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) != 0'i32:
  688. formalInfo.kind = pcDir
  689. else:
  690. formalInfo.kind = pcFile
  691. if (rawInfo.dwFileAttributes and FILE_ATTRIBUTE_REPARSE_POINT) != 0'i32:
  692. formalInfo.kind = succ(formalInfo.kind)
  693. else:
  694. template checkAndIncludeMode(rawMode, formalMode: untyped) =
  695. if (rawInfo.st_mode and rawMode.Mode) != 0.Mode:
  696. formalInfo.permissions.incl(formalMode)
  697. formalInfo.id = (rawInfo.st_dev, rawInfo.st_ino)
  698. formalInfo.size = rawInfo.st_size
  699. formalInfo.linkCount = rawInfo.st_nlink.BiggestInt
  700. formalInfo.lastAccessTime = rawInfo.st_atim.toTime
  701. formalInfo.lastWriteTime = rawInfo.st_mtim.toTime
  702. formalInfo.creationTime = rawInfo.st_ctim.toTime
  703. formalInfo.blockSize = rawInfo.st_blksize
  704. formalInfo.permissions = {}
  705. checkAndIncludeMode(S_IRUSR, fpUserRead)
  706. checkAndIncludeMode(S_IWUSR, fpUserWrite)
  707. checkAndIncludeMode(S_IXUSR, fpUserExec)
  708. checkAndIncludeMode(S_IRGRP, fpGroupRead)
  709. checkAndIncludeMode(S_IWGRP, fpGroupWrite)
  710. checkAndIncludeMode(S_IXGRP, fpGroupExec)
  711. checkAndIncludeMode(S_IROTH, fpOthersRead)
  712. checkAndIncludeMode(S_IWOTH, fpOthersWrite)
  713. checkAndIncludeMode(S_IXOTH, fpOthersExec)
  714. (formalInfo.kind, formalInfo.isSpecial) =
  715. if S_ISDIR(rawInfo.st_mode):
  716. (pcDir, false)
  717. elif S_ISLNK(rawInfo.st_mode):
  718. assert(path != "") # symlinks can't occur for file handles
  719. getSymlinkFileKind(path)
  720. else:
  721. (pcFile, not S_ISREG(rawInfo.st_mode))
  722. when defined(js):
  723. when not declared(FileHandle):
  724. type FileHandle = distinct int32
  725. when not declared(File):
  726. type File = object
  727. proc getFileInfo*(handle: FileHandle): FileInfo {.noWeirdTarget.} =
  728. ## Retrieves file information for the file object represented by the given
  729. ## handle.
  730. ##
  731. ## If the information cannot be retrieved, such as when the file handle
  732. ## is invalid, `OSError` is raised.
  733. ##
  734. ## See also:
  735. ## * `getFileInfo(file) proc`_
  736. ## * `getFileInfo(path, followSymlink) proc`_
  737. # Done: ID, Kind, Size, Permissions, Link Count
  738. when defined(windows):
  739. var rawInfo: BY_HANDLE_FILE_INFORMATION
  740. # We have to use the super special '_get_osfhandle' call (wrapped above)
  741. # To transform the C file descriptor to a native file handle.
  742. var realHandle = get_osfhandle(handle)
  743. if getFileInformationByHandle(realHandle, addr rawInfo) == 0:
  744. raiseOSError(osLastError(), $handle)
  745. rawToFormalFileInfo(rawInfo, "", result)
  746. else:
  747. var rawInfo: Stat
  748. if fstat(handle, rawInfo) < 0'i32:
  749. raiseOSError(osLastError(), $handle)
  750. rawToFormalFileInfo(rawInfo, "", result)
  751. proc getFileInfo*(file: File): FileInfo {.noWeirdTarget.} =
  752. ## Retrieves file information for the file object.
  753. ##
  754. ## See also:
  755. ## * `getFileInfo(handle) proc`_
  756. ## * `getFileInfo(path, followSymlink) proc`_
  757. if file.isNil:
  758. raise newException(IOError, "File is nil")
  759. result = getFileInfo(file.getFileHandle())
  760. proc getFileInfo*(path: string, followSymlink = true): FileInfo {.noWeirdTarget.} =
  761. ## Retrieves file information for the file object pointed to by `path`.
  762. ##
  763. ## Due to intrinsic differences between operating systems, the information
  764. ## contained by the returned `FileInfo object`_ will be slightly
  765. ## different across platforms, and in some cases, incomplete or inaccurate.
  766. ##
  767. ## When `followSymlink` is true (default), symlinks are followed and the
  768. ## information retrieved is information related to the symlink's target.
  769. ## Otherwise, information on the symlink itself is retrieved (however,
  770. ## field `isSpecial` is still determined from the target on Unix).
  771. ##
  772. ## If the information cannot be retrieved, such as when the path doesn't
  773. ## exist, or when permission restrictions prevent the program from retrieving
  774. ## file information, `OSError` is raised.
  775. ##
  776. ## See also:
  777. ## * `getFileInfo(handle) proc`_
  778. ## * `getFileInfo(file) proc`_
  779. when defined(windows):
  780. var
  781. handle = openHandle(path, followSymlink)
  782. rawInfo: BY_HANDLE_FILE_INFORMATION
  783. if handle == INVALID_HANDLE_VALUE:
  784. raiseOSError(osLastError(), path)
  785. if getFileInformationByHandle(handle, addr rawInfo) == 0:
  786. raiseOSError(osLastError(), path)
  787. rawToFormalFileInfo(rawInfo, path, result)
  788. discard closeHandle(handle)
  789. else:
  790. var rawInfo: Stat
  791. if followSymlink:
  792. if stat(path, rawInfo) < 0'i32:
  793. raiseOSError(osLastError(), path)
  794. else:
  795. if lstat(path, rawInfo) < 0'i32:
  796. raiseOSError(osLastError(), path)
  797. rawToFormalFileInfo(rawInfo, path, result)
  798. proc sameFileContent*(path1, path2: string): bool {.rtl, extern: "nos$1",
  799. tags: [ReadIOEffect], noWeirdTarget.} =
  800. ## Returns true if both pathname arguments refer to files with identical
  801. ## binary content.
  802. ##
  803. ## See also:
  804. ## * `sameFile proc`_
  805. var
  806. a, b: File
  807. if not open(a, path1): return false
  808. if not open(b, path2):
  809. close(a)
  810. return false
  811. let bufSize = getFileInfo(a).blockSize
  812. var bufA = alloc(bufSize)
  813. var bufB = alloc(bufSize)
  814. while true:
  815. var readA = readBuffer(a, bufA, bufSize)
  816. var readB = readBuffer(b, bufB, bufSize)
  817. if readA != readB:
  818. result = false
  819. break
  820. if readA == 0:
  821. result = true
  822. break
  823. result = equalMem(bufA, bufB, readA)
  824. if not result: break
  825. if readA != bufSize: break # end of file
  826. dealloc(bufA)
  827. dealloc(bufB)
  828. close(a)
  829. close(b)
  830. proc isHidden*(path: string): bool {.noWeirdTarget.} =
  831. ## Determines whether ``path`` is hidden or not, using `this
  832. ## reference <https://en.wikipedia.org/wiki/Hidden_file_and_hidden_directory>`_.
  833. ##
  834. ## On Windows: returns true if it exists and its "hidden" attribute is set.
  835. ##
  836. ## On posix: returns true if ``lastPathPart(path)`` starts with ``.`` and is
  837. ## not ``.`` or ``..``.
  838. ##
  839. ## **Note**: paths are not normalized to determine `isHidden`.
  840. runnableExamples:
  841. when defined(posix):
  842. assert ".foo".isHidden
  843. assert not ".foo/bar".isHidden
  844. assert not ".".isHidden
  845. assert not "..".isHidden
  846. assert not "".isHidden
  847. assert ".foo/".isHidden
  848. when defined(windows):
  849. wrapUnary(attributes, getFileAttributesW, path)
  850. if attributes != -1'i32:
  851. result = (attributes and FILE_ATTRIBUTE_HIDDEN) != 0'i32
  852. else:
  853. let fileName = lastPathPart(path)
  854. result = len(fileName) >= 2 and fileName[0] == '.' and fileName != ".."
  855. proc getCurrentProcessId*(): int {.noWeirdTarget.} =
  856. ## Return current process ID.
  857. ##
  858. ## See also:
  859. ## * `osproc.processID(p: Process) <osproc.html#processID,Process>`_
  860. when defined(windows):
  861. proc GetCurrentProcessId(): DWORD {.stdcall, dynlib: "kernel32",
  862. importc: "GetCurrentProcessId".}
  863. result = GetCurrentProcessId().int
  864. else:
  865. result = getpid()
  866. proc setLastModificationTime*(file: string, t: times.Time) {.noWeirdTarget.} =
  867. ## Sets the `file`'s last modification time. `OSError` is raised in case of
  868. ## an error.
  869. when defined(posix):
  870. let unixt = posix.Time(t.toUnix)
  871. let micro = convert(Nanoseconds, Microseconds, t.nanosecond)
  872. var timevals = [Timeval(tv_sec: unixt, tv_usec: micro),
  873. Timeval(tv_sec: unixt, tv_usec: micro)] # [last access, last modification]
  874. if utimes(file, timevals.addr) != 0: raiseOSError(osLastError(), file)
  875. else:
  876. let h = openHandle(path = file, writeAccess = true)
  877. if h == INVALID_HANDLE_VALUE: raiseOSError(osLastError(), file)
  878. var ft = t.toWinTime.toFILETIME
  879. let res = setFileTime(h, nil, nil, ft.addr)
  880. discard h.closeHandle
  881. if res == 0'i32: raiseOSError(osLastError(), file)
  882. func isValidFilename*(filename: string, maxLen = 259.Positive): bool {.since: (1, 1).} =
  883. ## Returns `true` if `filename` is valid for crossplatform use.
  884. ##
  885. ## This is useful if you want to copy or save files across Windows, Linux, Mac, etc.
  886. ## It uses `invalidFilenameChars`, `invalidFilenames` and `maxLen` to verify the specified `filename`.
  887. ##
  888. ## See also:
  889. ##
  890. ## * https://docs.microsoft.com/en-us/dotnet/api/system.io.pathtoolongexception
  891. ## * https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
  892. ## * https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
  893. ##
  894. ## .. warning:: This only checks filenames, not whole paths
  895. ## (because basically you can mount anything as a path on Linux).
  896. runnableExamples:
  897. assert not isValidFilename(" foo") # Leading white space
  898. assert not isValidFilename("foo ") # Trailing white space
  899. assert not isValidFilename("foo.") # Ends with dot
  900. assert not isValidFilename("con.txt") # "CON" is invalid (Windows)
  901. assert not isValidFilename("OwO:UwU") # ":" is invalid (Mac)
  902. assert not isValidFilename("aux.bat") # "AUX" is invalid (Windows)
  903. assert not isValidFilename("") # Empty string
  904. assert not isValidFilename("foo/") # Filename is empty
  905. result = true
  906. let f = filename.splitFile()
  907. if unlikely(f.name.len + f.ext.len > maxLen or f.name.len == 0 or
  908. f.name[0] == ' ' or f.name[^1] == ' ' or f.name[^1] == '.' or
  909. find(f.name, invalidFilenameChars) != -1): return false
  910. for invalid in invalidFilenames:
  911. if cmpIgnoreCase(f.name, invalid) == 0: return false
  912. # deprecated declarations
  913. when not weirdTarget:
  914. template existsFile*(args: varargs[untyped]): untyped {.deprecated: "use fileExists".} =
  915. fileExists(args)
  916. template existsDir*(args: varargs[untyped]): untyped {.deprecated: "use dirExists".} =
  917. dirExists(args)