os.nim 87 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440
  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, reading command line arguments,
  11. ## working with directories, running shell commands, etc.
  12. {.deadCodeElim: on.} # dce option deprecated
  13. {.push debugger: off.}
  14. include "system/inclrtl"
  15. import
  16. strutils, pathnorm
  17. when defined(nimscript):
  18. discard
  19. elif defined(windows):
  20. import winlean, times
  21. elif defined(posix):
  22. import posix, times
  23. proc toTime(ts: Timespec): times.Time {.inline.} =
  24. result = initTime(ts.tv_sec.int64, ts.tv_nsec.int)
  25. else:
  26. {.error: "OS module not ported to your operating system!".}
  27. when defined(nimscript) and defined(nimErrorProcCanHaveBody):
  28. {.pragma: noNimScript, error: "this proc is not available on the NimScript target".}
  29. else:
  30. {.pragma: noNimScript.}
  31. type
  32. ReadEnvEffect* = object of ReadIOEffect ## effect that denotes a read
  33. ## from an environment variable
  34. WriteEnvEffect* = object of WriteIOEffect ## effect that denotes a write
  35. ## to an environment variable
  36. ReadDirEffect* = object of ReadIOEffect ## effect that denotes a read
  37. ## operation from the directory
  38. ## structure
  39. WriteDirEffect* = object of WriteIOEffect ## effect that denotes a write
  40. ## operation to
  41. ## the directory structure
  42. OSErrorCode* = distinct int32 ## Specifies an OS Error Code.
  43. include "includes/osseps"
  44. proc normalizePathEnd(path: var string, trailingSep = false) =
  45. ## ensures ``path`` has exactly 0 or 1 trailing `DirSep`, depending on
  46. ## ``trailingSep``, and taking care of edge cases: it preservers whether
  47. ## a path is absolute or relative, and makes sure trailing sep is `DirSep`,
  48. ## not `AltSep`.
  49. if path.len == 0: return
  50. var i = path.len
  51. while i >= 1 and path[i-1] in {DirSep, AltSep}: dec(i)
  52. if trailingSep:
  53. # foo// => foo
  54. path.setLen(i)
  55. # foo => foo/
  56. path.add DirSep
  57. elif i > 0:
  58. # foo// => foo
  59. path.setLen(i)
  60. else:
  61. # // => / (empty case was already taken care of)
  62. path = $DirSep
  63. proc normalizePathEnd(path: string, trailingSep = false): string =
  64. result = path
  65. result.normalizePathEnd(trailingSep)
  66. proc joinPath*(head, tail: string): string {.
  67. noSideEffect, rtl, extern: "nos$1".} =
  68. ## Joins two directory names to one.
  69. ##
  70. ## For example on Unix:
  71. ##
  72. ## .. code-block:: nim
  73. ## joinPath("usr", "lib")
  74. ##
  75. ## results in:
  76. ##
  77. ## .. code-block:: nim
  78. ## "usr/lib"
  79. ##
  80. ## If head is the empty string, tail is returned. If tail is the empty
  81. ## string, head is returned with a trailing path separator. If tail starts
  82. ## with a path separator it will be removed when concatenated to head. Other
  83. ## path separators not located on boundaries won't be modified. More
  84. ## examples on Unix:
  85. ##
  86. ## .. code-block:: nim
  87. ## assert joinPath("usr", "") == "usr/"
  88. ## assert joinPath("", "lib") == "lib"
  89. ## assert joinPath("", "/lib") == "/lib"
  90. ## assert joinPath("usr/", "/lib") == "usr/lib"
  91. result = newStringOfCap(head.len + tail.len)
  92. var state = 0
  93. addNormalizePath(head, result, state, DirSep)
  94. if tail.len == 0:
  95. result.add DirSep
  96. else:
  97. addNormalizePath(tail, result, state, DirSep)
  98. when false:
  99. if len(head) == 0:
  100. result = tail
  101. elif head[len(head)-1] in {DirSep, AltSep}:
  102. if tail.len > 0 and tail[0] in {DirSep, AltSep}:
  103. result = head & substr(tail, 1)
  104. else:
  105. result = head & tail
  106. else:
  107. if tail.len > 0 and tail[0] in {DirSep, AltSep}:
  108. result = head & tail
  109. else:
  110. result = head & DirSep & tail
  111. proc joinPath*(parts: varargs[string]): string {.noSideEffect,
  112. rtl, extern: "nos$1OpenArray".} =
  113. ## The same as `joinPath(head, tail)`, but works with any number of
  114. ## directory parts. You need to pass at least one element or the proc
  115. ## will assert in debug builds and crash on release builds.
  116. var estimatedLen = 0
  117. for p in parts: estimatedLen += p.len
  118. result = newStringOfCap(estimatedLen)
  119. var state = 0
  120. for i in 0..high(parts):
  121. addNormalizePath(parts[i], result, state, DirSep)
  122. proc `/`*(head, tail: string): string {.noSideEffect.} =
  123. ## The same as ``joinPath(head, tail)``
  124. ##
  125. ## Here are some examples for Unix:
  126. ##
  127. ## .. code-block:: nim
  128. ## assert "usr" / "" == "usr/"
  129. ## assert "" / "lib" == "lib"
  130. ## assert "" / "/lib" == "/lib"
  131. ## assert "usr/" / "/lib" == "usr/lib"
  132. return joinPath(head, tail)
  133. proc splitPath*(path: string): tuple[head, tail: string] {.
  134. noSideEffect, rtl, extern: "nos$1".} =
  135. ## Splits a directory into (head, tail), so that
  136. ## ``head / tail == path`` (except for edge cases like "/usr").
  137. ##
  138. ## Examples:
  139. ##
  140. ## .. code-block:: nim
  141. ## splitPath("usr/local/bin") -> ("usr/local", "bin")
  142. ## splitPath("usr/local/bin/") -> ("usr/local/bin", "")
  143. ## splitPath("bin") -> ("", "bin")
  144. ## splitPath("/bin") -> ("", "bin")
  145. ## splitPath("") -> ("", "")
  146. var sepPos = -1
  147. for i in countdown(len(path)-1, 0):
  148. if path[i] in {DirSep, AltSep}:
  149. sepPos = i
  150. break
  151. if sepPos >= 0:
  152. result.head = substr(path, 0, sepPos-1)
  153. result.tail = substr(path, sepPos+1)
  154. else:
  155. result.head = ""
  156. result.tail = path
  157. when FileSystemCaseSensitive:
  158. template `!=?`(a, b: char): bool = toLowerAscii(a) != toLowerAscii(b)
  159. else:
  160. template `!=?`(a, b: char): bool = a != b
  161. proc relativePath*(path, base: string; sep = DirSep): string {.
  162. noSideEffect, rtl, extern: "nos$1", raises: [].} =
  163. ## Converts `path` to a path relative to `base`.
  164. ## The `sep` is used for the path normalizations, this can be useful to
  165. ## ensure the relative path only contains '/' so that it can be used for
  166. ## URL constructions.
  167. runnableExamples:
  168. doAssert relativePath("/Users/me/bar/z.nim", "/Users/other/bad", '/') == "../../me/bar/z.nim"
  169. doAssert relativePath("/Users/me/bar/z.nim", "/Users/other", '/') == "../me/bar/z.nim"
  170. doAssert relativePath("/Users///me/bar//z.nim", "//Users/", '/') == "me/bar/z.nim"
  171. doAssert relativePath("/Users/me/bar/z.nim", "/Users/me", '/') == "bar/z.nim"
  172. doAssert relativePath("", "/users/moo", '/') == ""
  173. # Todo: If on Windows, path and base do not agree on the drive letter,
  174. # return `path` as is.
  175. if path.len == 0: return ""
  176. var f, b: PathIter
  177. var ff = (0, -1)
  178. var bb = (0, -1) # (int, int)
  179. result = newStringOfCap(path.len)
  180. # skip the common prefix:
  181. while f.hasNext(path) and b.hasNext(base):
  182. ff = next(f, path)
  183. bb = next(b, base)
  184. let diff = ff[1] - ff[0]
  185. if diff != bb[1] - bb[0]: break
  186. var same = true
  187. for i in 0..diff:
  188. if path[i + ff[0]] !=? base[i + bb[0]]:
  189. same = false
  190. break
  191. if not same: break
  192. ff = (0, -1)
  193. bb = (0, -1)
  194. # for i in 0..diff:
  195. # result.add base[i + bb[0]]
  196. # /foo/bar/xxx/ -- base
  197. # /foo/bar/baz -- path path
  198. # ../baz
  199. # every directory that is in 'base', needs to add '..'
  200. while true:
  201. if bb[1] >= bb[0]:
  202. if result.len > 0 and result[^1] != sep:
  203. result.add sep
  204. result.add ".."
  205. if not b.hasNext(base): break
  206. bb = b.next(base)
  207. # add the rest of 'path':
  208. while true:
  209. if ff[1] >= ff[0]:
  210. if result.len > 0 and result[^1] != sep:
  211. result.add sep
  212. for i in 0..ff[1] - ff[0]:
  213. result.add path[i + ff[0]]
  214. if not f.hasNext(path): break
  215. ff = f.next(path)
  216. proc parentDirPos(path: string): int =
  217. var q = 1
  218. if len(path) >= 1 and path[len(path)-1] in {DirSep, AltSep}: q = 2
  219. for i in countdown(len(path)-q, 0):
  220. if path[i] in {DirSep, AltSep}: return i
  221. result = -1
  222. proc parentDir*(path: string): string {.
  223. noSideEffect, rtl, extern: "nos$1".} =
  224. ## Returns the parent directory of `path`.
  225. ##
  226. ## This is the same as ``splitPath(path).head`` when ``path`` doesn't end
  227. ## in a dir separator.
  228. ## The remainder can be obtained with ``lastPathPart(path)``
  229. runnableExamples:
  230. doAssert parentDir("") == ""
  231. when defined(posix):
  232. doAssert parentDir("/usr/local/bin") == "/usr/local"
  233. doAssert parentDir("foo/bar/") == "foo"
  234. let sepPos = parentDirPos(path)
  235. if sepPos >= 0:
  236. result = substr(path, 0, sepPos-1)
  237. else:
  238. result = ""
  239. proc tailDir*(path: string): string {.
  240. noSideEffect, rtl, extern: "nos$1".} =
  241. ## Returns the tail part of `path`..
  242. ##
  243. ## | Example: ``tailDir("/usr/local/bin") == "local/bin"``.
  244. ## | Example: ``tailDir("usr/local/bin/") == "local/bin"``.
  245. ## | Example: ``tailDir("bin") == ""``.
  246. var q = 1
  247. if len(path) >= 1 and path[len(path)-1] in {DirSep, AltSep}: q = 2
  248. for i in 0..len(path)-q:
  249. if path[i] in {DirSep, AltSep}:
  250. return substr(path, i+1)
  251. result = ""
  252. proc isRootDir*(path: string): bool {.
  253. noSideEffect, rtl, extern: "nos$1".} =
  254. ## Checks whether a given `path` is a root directory
  255. result = parentDirPos(path) < 0
  256. iterator parentDirs*(path: string, fromRoot=false, inclusive=true): string =
  257. ## Walks over all parent directories of a given `path`
  258. ##
  259. ## If `fromRoot` is set, the traversal will start from the file system root
  260. ## diretory. If `inclusive` is set, the original argument will be included
  261. ## in the traversal.
  262. ##
  263. ## Relative paths won't be expanded by this proc. Instead, it will traverse
  264. ## only the directories appearing in the relative path.
  265. if not fromRoot:
  266. var current = path
  267. if inclusive: yield path
  268. while true:
  269. if current.isRootDir: break
  270. current = current.parentDir
  271. yield current
  272. else:
  273. for i in countup(0, path.len - 2): # ignore the last /
  274. # deal with non-normalized paths such as /foo//bar//baz
  275. if path[i] in {DirSep, AltSep} and
  276. (i == 0 or path[i-1] notin {DirSep, AltSep}):
  277. yield path.substr(0, i)
  278. if inclusive: yield path
  279. proc `/../`*(head, tail: string): string {.noSideEffect.} =
  280. ## The same as ``parentDir(head) / tail`` unless there is no parent
  281. ## directory. Then ``head / tail`` is performed instead.
  282. let sepPos = parentDirPos(head)
  283. if sepPos >= 0:
  284. result = substr(head, 0, sepPos-1) / tail
  285. else:
  286. result = head / tail
  287. proc normExt(ext: string): string =
  288. if ext == "" or ext[0] == ExtSep: result = ext # no copy needed here
  289. else: result = ExtSep & ext
  290. proc searchExtPos*(path: string): int =
  291. ## Returns index of the '.' char in `path` if it signifies the beginning
  292. ## of extension. Returns -1 otherwise.
  293. # BUGFIX: do not search until 0! .DS_Store is no file extension!
  294. result = -1
  295. for i in countdown(len(path)-1, 1):
  296. if path[i] == ExtSep:
  297. result = i
  298. break
  299. elif path[i] in {DirSep, AltSep}:
  300. break # do not skip over path
  301. proc splitFile*(path: string): tuple[dir, name, ext: string] {.
  302. noSideEffect, rtl, extern: "nos$1".} =
  303. ## Splits a filename into (dir, filename, extension).
  304. ## `dir` does not end in `DirSep`.
  305. ## `extension` includes the leading dot.
  306. ##
  307. ## Example:
  308. ##
  309. ## .. code-block:: nim
  310. ## var (dir, name, ext) = splitFile("usr/local/nimc.html")
  311. ## assert dir == "usr/local"
  312. ## assert name == "nimc"
  313. ## assert ext == ".html"
  314. ##
  315. ## If `path` has no extension, `ext` is the empty string.
  316. ## If `path` has no directory component, `dir` is the empty string.
  317. ## If `path` has no filename component, `name` and `ext` are empty strings.
  318. if path.len == 0 or path[path.len-1] in {DirSep, AltSep}:
  319. result = (path, "", "")
  320. else:
  321. var sepPos = -1
  322. var dotPos = path.len
  323. for i in countdown(len(path)-1, 0):
  324. if path[i] == ExtSep:
  325. if dotPos == path.len and i > 0 and
  326. path[i-1] notin {DirSep, AltSep}: dotPos = i
  327. elif path[i] in {DirSep, AltSep}:
  328. sepPos = i
  329. break
  330. result.dir = substr(path, 0, sepPos-1)
  331. result.name = substr(path, sepPos+1, dotPos-1)
  332. result.ext = substr(path, dotPos)
  333. proc extractFilename*(path: string): string {.
  334. noSideEffect, rtl, extern: "nos$1".} =
  335. ## Extracts the filename of a given `path`. This is the same as
  336. ## ``name & ext`` from ``splitFile(path)``. See also ``lastPathPart``.
  337. runnableExamples:
  338. when defined(posix):
  339. doAssert extractFilename("foo/bar/") == ""
  340. doAssert extractFilename("foo/bar") == "bar"
  341. if path.len == 0 or path[path.len-1] in {DirSep, AltSep}:
  342. result = ""
  343. else:
  344. result = splitPath(path).tail
  345. proc lastPathPart*(path: string): string {.
  346. noSideEffect, rtl, extern: "nos$1".} =
  347. ## like ``extractFilename``, but ignores trailing dir separator; aka: `baseName`:idx:
  348. ## in some other languages.
  349. runnableExamples:
  350. when defined(posix):
  351. doAssert lastPathPart("foo/bar/") == "bar"
  352. let path = path.normalizePathEnd(trailingSep = false)
  353. result = extractFilename(path)
  354. proc changeFileExt*(filename, ext: string): string {.
  355. noSideEffect, rtl, extern: "nos$1".} =
  356. ## Changes the file extension to `ext`.
  357. ##
  358. ## If the `filename` has no extension, `ext` will be added.
  359. ## If `ext` == "" then any extension is removed.
  360. ## `Ext` should be given without the leading '.', because some
  361. ## filesystems may use a different character. (Although I know
  362. ## of none such beast.)
  363. var extPos = searchExtPos(filename)
  364. if extPos < 0: result = filename & normExt(ext)
  365. else: result = substr(filename, 0, extPos-1) & normExt(ext)
  366. proc addFileExt*(filename, ext: string): string {.
  367. noSideEffect, rtl, extern: "nos$1".} =
  368. ## Adds the file extension `ext` to `filename`, unless
  369. ## `filename` already has an extension.
  370. ##
  371. ## `Ext` should be given without the leading '.', because some
  372. ## filesystems may use a different character.
  373. ## (Although I know of none such beast.)
  374. var extPos = searchExtPos(filename)
  375. if extPos < 0: result = filename & normExt(ext)
  376. else: result = filename
  377. proc cmpPaths*(pathA, pathB: string): int {.
  378. noSideEffect, rtl, extern: "nos$1".} =
  379. ## Compares two paths.
  380. ##
  381. ## On a case-sensitive filesystem this is done
  382. ## case-sensitively otherwise case-insensitively. Returns:
  383. ##
  384. ## | 0 iff pathA == pathB
  385. ## | < 0 iff pathA < pathB
  386. ## | > 0 iff pathA > pathB
  387. runnableExamples:
  388. when defined(macosx):
  389. doAssert cmpPaths("foo", "Foo") == 0
  390. elif defined(posix):
  391. doAssert cmpPaths("foo", "Foo") > 0
  392. let a = normalizePath(pathA)
  393. let b = normalizePath(pathB)
  394. if FileSystemCaseSensitive:
  395. result = cmp(a, b)
  396. else:
  397. when defined(nimscript):
  398. result = cmpic(a, b)
  399. elif defined(nimdoc): discard
  400. else:
  401. result = cmpIgnoreCase(a, b)
  402. proc isAbsolute*(path: string): bool {.rtl, noSideEffect, extern: "nos$1".} =
  403. ## Checks whether a given `path` is absolute.
  404. ##
  405. ## On Windows, network paths are considered absolute too.
  406. runnableExamples:
  407. doAssert(not "".isAbsolute)
  408. doAssert(not ".".isAbsolute)
  409. when defined(posix):
  410. doAssert "/".isAbsolute
  411. doAssert(not "a/".isAbsolute)
  412. if len(path) == 0: return false
  413. when doslikeFileSystem:
  414. var len = len(path)
  415. result = (path[0] in {'/', '\\'}) or
  416. (len > 1 and path[0] in {'a'..'z', 'A'..'Z'} and path[1] == ':')
  417. elif defined(macos):
  418. # according to https://perldoc.perl.org/File/Spec/Mac.html `:a` is a relative path
  419. result = path[0] != ':'
  420. elif defined(RISCOS):
  421. result = path[0] == '$'
  422. elif defined(posix):
  423. result = path[0] == '/'
  424. proc unixToNativePath*(path: string, drive=""): string {.
  425. noSideEffect, rtl, extern: "nos$1".} =
  426. ## Converts an UNIX-like path to a native one.
  427. ##
  428. ## On an UNIX system this does nothing. Else it converts
  429. ## '/', '.', '..' to the appropriate things.
  430. ##
  431. ## On systems with a concept of "drives", `drive` is used to determine
  432. ## which drive label to use during absolute path conversion.
  433. ## `drive` defaults to the drive of the current working directory, and is
  434. ## ignored on systems that do not have a concept of "drives".
  435. when defined(unix):
  436. result = path
  437. else:
  438. if path.len == 0: return ""
  439. var start: int
  440. if path[0] == '/':
  441. # an absolute path
  442. when doslikeFileSystem:
  443. if drive != "":
  444. result = drive & ":" & DirSep
  445. else:
  446. result = $DirSep
  447. elif defined(macos):
  448. result = "" # must not start with ':'
  449. else:
  450. result = $DirSep
  451. start = 1
  452. elif path[0] == '.' and (path.len == 1 or path[1] == '/'):
  453. # current directory
  454. result = $CurDir
  455. start = when doslikeFileSystem: 1 else: 2
  456. else:
  457. result = ""
  458. start = 0
  459. var i = start
  460. while i < len(path): # ../../../ --> ::::
  461. if i+2 < path.len and path[i] == '.' and path[i+1] == '.' and path[i+2] == '/':
  462. # parent directory
  463. when defined(macos):
  464. if result[high(result)] == ':':
  465. add result, ':'
  466. else:
  467. add result, ParDir
  468. else:
  469. add result, ParDir & DirSep
  470. inc(i, 3)
  471. elif path[i] == '/':
  472. add result, DirSep
  473. inc(i)
  474. else:
  475. add result, path[i]
  476. inc(i)
  477. include "includes/oserr"
  478. when not defined(nimscript):
  479. include "includes/osenv"
  480. proc getHomeDir*(): string {.rtl, extern: "nos$1",
  481. tags: [ReadEnvEffect, ReadIOEffect].} =
  482. ## Returns the home directory of the current user.
  483. ##
  484. ## This proc is wrapped by the expandTilde proc for the convenience of
  485. ## processing paths coming from user configuration files.
  486. when defined(windows): return string(getEnv("USERPROFILE")) & "\\"
  487. else: return string(getEnv("HOME")) & "/"
  488. proc getConfigDir*(): string {.rtl, extern: "nos$1",
  489. tags: [ReadEnvEffect, ReadIOEffect].} =
  490. ## Returns the config directory of the current user for applications.
  491. ##
  492. ## On non-Windows OSs, this proc conforms to the XDG Base Directory
  493. ## spec. Thus, this proc returns the value of the XDG_CONFIG_HOME environment
  494. ## variable if it is set, and returns the default configuration directory,
  495. ## "~/.config/", otherwise.
  496. ##
  497. ## An OS-dependent trailing slash is always present at the end of the
  498. ## returned string; `\` on Windows and `/` on all other OSs.
  499. when defined(windows):
  500. result = getEnv("APPDATA").string
  501. else:
  502. result = getEnv("XDG_CONFIG_HOME", getEnv("HOME").string / ".config").string
  503. result.normalizePathEnd(trailingSep = true)
  504. proc getTempDir*(): string {.rtl, extern: "nos$1",
  505. tags: [ReadEnvEffect, ReadIOEffect].} =
  506. ## Returns the temporary directory of the current user for applications to
  507. ## save temporary files in.
  508. ##
  509. ## **Please do not use this**: On Android, it currently
  510. ## returns ``getHomeDir()``, and on other Unix based systems it can cause
  511. ## security problems too. That said, you can override this implementation
  512. ## by adding ``-d:tempDir=mytempname`` to your compiler invokation.
  513. when defined(tempDir):
  514. const tempDir {.strdefine.}: string = nil
  515. return tempDir
  516. elif defined(windows): return string(getEnv("TEMP")) & "\\"
  517. elif defined(android): return getHomeDir()
  518. else: return "/tmp/"
  519. proc expandTilde*(path: string): string {.
  520. tags: [ReadEnvEffect, ReadIOEffect].} =
  521. ## Expands ``~`` or a path starting with ``~/`` to a full path, replacing
  522. ## ``~`` with ``getHomeDir()`` (otherwise returns ``path`` unmodified).
  523. ##
  524. ## Windows: this is still supported despite Windows platform not having this
  525. ## convention; also, both ``~/`` and ``~\`` are handled.
  526. runnableExamples:
  527. doAssert expandTilde("~" / "appname.cfg") == getHomeDir() / "appname.cfg"
  528. if len(path) == 0 or path[0] != '~':
  529. result = path
  530. elif len(path) == 1:
  531. result = getHomeDir()
  532. elif (path[1] in {DirSep, AltSep}):
  533. result = getHomeDir() / path.substr(2)
  534. else:
  535. # TODO: handle `~bob` and `~bob/` which means home of bob
  536. result = path
  537. # TODO: consider whether quoteShellPosix, quoteShellWindows, quoteShell, quoteShellCommand
  538. # belong in `strutils` instead; they are not specific to paths
  539. proc quoteShellWindows*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} =
  540. ## Quote s, so it can be safely passed to Windows API.
  541. ## Based on Python's subprocess.list2cmdline
  542. ## See http://msdn.microsoft.com/en-us/library/17w5ykft.aspx
  543. let needQuote = {' ', '\t'} in s or s.len == 0
  544. result = ""
  545. var backslashBuff = ""
  546. if needQuote:
  547. result.add("\"")
  548. for c in s:
  549. if c == '\\':
  550. backslashBuff.add(c)
  551. elif c == '\"':
  552. result.add(backslashBuff)
  553. result.add(backslashBuff)
  554. backslashBuff.setLen(0)
  555. result.add("\\\"")
  556. else:
  557. if backslashBuff.len != 0:
  558. result.add(backslashBuff)
  559. backslashBuff.setLen(0)
  560. result.add(c)
  561. if needQuote:
  562. result.add("\"")
  563. proc quoteShellPosix*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} =
  564. ## Quote ``s``, so it can be safely passed to POSIX shell.
  565. ## Based on Python's pipes.quote
  566. const safeUnixChars = {'%', '+', '-', '.', '/', '_', ':', '=', '@',
  567. '0'..'9', 'A'..'Z', 'a'..'z'}
  568. if s.len == 0:
  569. return "''"
  570. let safe = s.allCharsInSet(safeUnixChars)
  571. if safe:
  572. return s
  573. else:
  574. return "'" & s.replace("'", "'\"'\"'") & "'"
  575. when defined(windows) or defined(posix) or defined(nintendoswitch):
  576. proc quoteShell*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} =
  577. ## Quote ``s``, so it can be safely passed to shell.
  578. when defined(windows):
  579. return quoteShellWindows(s)
  580. else:
  581. return quoteShellPosix(s)
  582. proc quoteShellCommand*(args: openArray[string]): string =
  583. ## Concatenates and quotes shell arguments `args`
  584. runnableExamples:
  585. when defined(posix):
  586. assert quoteShellCommand(["aaa", "", "c d"]) == "aaa '' 'c d'"
  587. when defined(windows):
  588. assert quoteShellCommand(["aaa", "", "c d"]) == "aaa \"\" \"c d\""
  589. # can't use `map` pending https://github.com/nim-lang/Nim/issues/8303
  590. for i in 0..<args.len:
  591. if i > 0: result.add " "
  592. result.add quoteShell(args[i])
  593. when not defined(nimscript):
  594. proc c_rename(oldname, newname: cstring): cint {.
  595. importc: "rename", header: "<stdio.h>".}
  596. proc c_system(cmd: cstring): cint {.
  597. importc: "system", header: "<stdlib.h>".}
  598. proc c_strlen(a: cstring): cint {.
  599. importc: "strlen", header: "<string.h>", noSideEffect.}
  600. proc c_free(p: pointer) {.
  601. importc: "free", header: "<stdlib.h>".}
  602. when defined(windows) and not defined(nimscript):
  603. when useWinUnicode:
  604. template wrapUnary(varname, winApiProc, arg: untyped) =
  605. var varname = winApiProc(newWideCString(arg))
  606. template wrapBinary(varname, winApiProc, arg, arg2: untyped) =
  607. var varname = winApiProc(newWideCString(arg), arg2)
  608. proc findFirstFile(a: string, b: var WIN32_FIND_DATA): Handle =
  609. result = findFirstFileW(newWideCString(a), b)
  610. template findNextFile(a, b: untyped): untyped = findNextFileW(a, b)
  611. template getCommandLine(): untyped = getCommandLineW()
  612. template getFilename(f: untyped): untyped =
  613. $cast[WideCString](addr(f.cFilename[0]))
  614. else:
  615. template findFirstFile(a, b: untyped): untyped = findFirstFileA(a, b)
  616. template findNextFile(a, b: untyped): untyped = findNextFileA(a, b)
  617. template getCommandLine(): untyped = getCommandLineA()
  618. template getFilename(f: untyped): untyped = $f.cFilename
  619. proc skipFindData(f: WIN32_FIND_DATA): bool {.inline.} =
  620. # Note - takes advantage of null delimiter in the cstring
  621. const dot = ord('.')
  622. result = f.cFileName[0].int == dot and (f.cFileName[1].int == 0 or
  623. f.cFileName[1].int == dot and f.cFileName[2].int == 0)
  624. proc existsFile*(filename: string): bool {.rtl, extern: "nos$1",
  625. tags: [ReadDirEffect], noNimScript.} =
  626. ## Returns true if `filename` exists and is a regular file or symlink.
  627. ## (directories, device files, named pipes and sockets return false)
  628. ## This proc is not available for NimScript.
  629. when defined(windows):
  630. when useWinUnicode:
  631. wrapUnary(a, getFileAttributesW, filename)
  632. else:
  633. var a = getFileAttributesA(filename)
  634. if a != -1'i32:
  635. result = (a and FILE_ATTRIBUTE_DIRECTORY) == 0'i32
  636. else:
  637. var res: Stat
  638. return stat(filename, res) >= 0'i32 and S_ISREG(res.st_mode)
  639. proc existsDir*(dir: string): bool {.rtl, extern: "nos$1", tags: [ReadDirEffect],
  640. noNimScript.} =
  641. ## Returns true iff the directory `dir` exists. If `dir` is a file, false
  642. ## is returned. Follows symlinks.
  643. when defined(windows):
  644. when useWinUnicode:
  645. wrapUnary(a, getFileAttributesW, dir)
  646. else:
  647. var a = getFileAttributesA(dir)
  648. if a != -1'i32:
  649. result = (a and FILE_ATTRIBUTE_DIRECTORY) != 0'i32
  650. else:
  651. var res: Stat
  652. return stat(dir, res) >= 0'i32 and S_ISDIR(res.st_mode)
  653. proc symlinkExists*(link: string): bool {.rtl, extern: "nos$1",
  654. tags: [ReadDirEffect],
  655. noNimScript.} =
  656. ## Returns true iff the symlink `link` exists. Will return true
  657. ## regardless of whether the link points to a directory or file.
  658. when defined(windows):
  659. when useWinUnicode:
  660. wrapUnary(a, getFileAttributesW, link)
  661. else:
  662. var a = getFileAttributesA(link)
  663. if a != -1'i32:
  664. result = (a and FILE_ATTRIBUTE_REPARSE_POINT) != 0'i32
  665. else:
  666. var res: Stat
  667. return lstat(link, res) >= 0'i32 and S_ISLNK(res.st_mode)
  668. proc fileExists*(filename: string): bool {.inline, noNimScript.} =
  669. ## Synonym for existsFile
  670. existsFile(filename)
  671. proc dirExists*(dir: string): bool {.inline, noNimScript.} =
  672. ## Synonym for existsDir
  673. existsDir(dir)
  674. when not defined(windows) and not defined(nimscript):
  675. proc checkSymlink(path: string): bool =
  676. var rawInfo: Stat
  677. if lstat(path, rawInfo) < 0'i32: result = false
  678. else: result = S_ISLNK(rawInfo.st_mode)
  679. const
  680. ExeExts* = when defined(windows): ["exe", "cmd", "bat"] else: [""] ## \
  681. ## platform specific file extension for executables. On Windows
  682. ## ``["exe", "cmd", "bat"]``, on Posix ``[""]``.
  683. proc findExe*(exe: string, followSymlinks: bool = true;
  684. extensions: openarray[string]=ExeExts): string {.
  685. tags: [ReadDirEffect, ReadEnvEffect, ReadIOEffect], noNimScript.} =
  686. ## Searches for `exe` in the current working directory and then
  687. ## in directories listed in the ``PATH`` environment variable.
  688. ## Returns "" if the `exe` cannot be found. `exe`
  689. ## is added the `ExeExts <#ExeExts>`_ file extensions if it has none.
  690. ## If the system supports symlinks it also resolves them until it
  691. ## meets the actual file. This behavior can be disabled if desired.
  692. if exe.len == 0: return
  693. template checkCurrentDir() =
  694. for ext in extensions:
  695. result = addFileExt(exe, ext)
  696. if existsFile(result): return
  697. when defined(posix):
  698. if '/' in exe: checkCurrentDir()
  699. else:
  700. checkCurrentDir()
  701. let path = string(getEnv("PATH"))
  702. for candidate in split(path, PathSep):
  703. if candidate.len == 0: continue
  704. when defined(windows):
  705. var x = (if candidate[0] == '"' and candidate[^1] == '"':
  706. substr(candidate, 1, candidate.len-2) else: candidate) /
  707. exe
  708. else:
  709. var x = expandTilde(candidate) / exe
  710. for ext in extensions:
  711. var x = addFileExt(x, ext)
  712. if existsFile(x):
  713. when not defined(windows):
  714. while followSymlinks: # doubles as if here
  715. if x.checkSymlink:
  716. var r = newString(256)
  717. var len = readlink(x, r, 256)
  718. if len < 0:
  719. raiseOSError(osLastError())
  720. if len > 256:
  721. r = newString(len+1)
  722. len = readlink(x, r, len)
  723. setLen(r, len)
  724. if isAbsolute(r):
  725. x = r
  726. else:
  727. x = parentDir(x) / r
  728. else:
  729. break
  730. return x
  731. result = ""
  732. when defined(nimscript):
  733. const times = "fake const"
  734. template Time(x: untyped): untyped = string
  735. proc getLastModificationTime*(file: string): times.Time {.rtl, extern: "nos$1", noNimScript.} =
  736. ## Returns the `file`'s last modification time.
  737. when defined(posix):
  738. var res: Stat
  739. if stat(file, res) < 0'i32: raiseOSError(osLastError())
  740. result = res.st_mtim.toTime
  741. else:
  742. var f: WIN32_FIND_DATA
  743. var h = findFirstFile(file, f)
  744. if h == -1'i32: raiseOSError(osLastError())
  745. result = fromWinTime(rdFileTime(f.ftLastWriteTime))
  746. findClose(h)
  747. proc getLastAccessTime*(file: string): times.Time {.rtl, extern: "nos$1", noNimScript.} =
  748. ## Returns the `file`'s last read or write access time.
  749. when defined(posix):
  750. var res: Stat
  751. if stat(file, res) < 0'i32: raiseOSError(osLastError())
  752. result = res.st_atim.toTime
  753. else:
  754. var f: WIN32_FIND_DATA
  755. var h = findFirstFile(file, f)
  756. if h == -1'i32: raiseOSError(osLastError())
  757. result = fromWinTime(rdFileTime(f.ftLastAccessTime))
  758. findClose(h)
  759. proc getCreationTime*(file: string): times.Time {.rtl, extern: "nos$1", noNimScript.} =
  760. ## Returns the `file`'s creation time.
  761. ##
  762. ## **Note:** Under POSIX OS's, the returned time may actually be the time at
  763. ## which the file's attribute's were last modified. See
  764. ## `here <https://github.com/nim-lang/Nim/issues/1058>`_ for details.
  765. when defined(posix):
  766. var res: Stat
  767. if stat(file, res) < 0'i32: raiseOSError(osLastError())
  768. result = res.st_ctim.toTime
  769. else:
  770. var f: WIN32_FIND_DATA
  771. var h = findFirstFile(file, f)
  772. if h == -1'i32: raiseOSError(osLastError())
  773. result = fromWinTime(rdFileTime(f.ftCreationTime))
  774. findClose(h)
  775. proc fileNewer*(a, b: string): bool {.rtl, extern: "nos$1", noNimScript.} =
  776. ## Returns true if the file `a` is newer than file `b`, i.e. if `a`'s
  777. ## modification time is later than `b`'s.
  778. when defined(posix):
  779. # If we don't have access to nanosecond resolution, use '>='
  780. when not StatHasNanoseconds:
  781. result = getLastModificationTime(a) >= getLastModificationTime(b)
  782. else:
  783. result = getLastModificationTime(a) > getLastModificationTime(b)
  784. else:
  785. result = getLastModificationTime(a) > getLastModificationTime(b)
  786. proc getCurrentDir*(): string {.rtl, extern: "nos$1", tags: [], noNimScript.} =
  787. ## Returns the `current working directory`:idx:.
  788. when defined(windows):
  789. var bufsize = MAX_PATH.int32
  790. when useWinUnicode:
  791. var res = newWideCString("", bufsize)
  792. while true:
  793. var L = getCurrentDirectoryW(bufsize, res)
  794. if L == 0'i32:
  795. raiseOSError(osLastError())
  796. elif L > bufsize:
  797. res = newWideCString("", L)
  798. bufsize = L
  799. else:
  800. result = res$L
  801. break
  802. else:
  803. result = newString(bufsize)
  804. while true:
  805. var L = getCurrentDirectoryA(bufsize, result)
  806. if L == 0'i32:
  807. raiseOSError(osLastError())
  808. elif L > bufsize:
  809. result = newString(L)
  810. bufsize = L
  811. else:
  812. setLen(result, L)
  813. break
  814. else:
  815. var bufsize = 1024 # should be enough
  816. result = newString(bufsize)
  817. while true:
  818. if getcwd(result, bufsize) != nil:
  819. setLen(result, c_strlen(result))
  820. break
  821. else:
  822. let err = osLastError()
  823. if err.int32 == ERANGE:
  824. bufsize = bufsize shl 1
  825. doAssert(bufsize >= 0)
  826. result = newString(bufsize)
  827. else:
  828. raiseOSError(osLastError())
  829. proc setCurrentDir*(newDir: string) {.inline, tags: [], noNimScript.} =
  830. ## Sets the `current working directory`:idx:; `OSError` is raised if
  831. ## `newDir` cannot been set.
  832. when defined(Windows):
  833. when useWinUnicode:
  834. if setCurrentDirectoryW(newWideCString(newDir)) == 0'i32:
  835. raiseOSError(osLastError())
  836. else:
  837. if setCurrentDirectoryA(newDir) == 0'i32: raiseOSError(osLastError())
  838. else:
  839. if chdir(newDir) != 0'i32: raiseOSError(osLastError())
  840. when not defined(nimscript):
  841. proc absolutePath*(path: string, root = getCurrentDir()): string {.noNimScript.} =
  842. ## Returns the absolute path of `path`, rooted at `root` (which must be absolute)
  843. ## if `path` is absolute, return it, ignoring `root`
  844. runnableExamples:
  845. doAssert absolutePath("a") == getCurrentDir() / "a"
  846. if isAbsolute(path): path
  847. else:
  848. if not root.isAbsolute:
  849. raise newException(ValueError, "The specified root is not absolute: " & root)
  850. joinPath(root, path)
  851. proc expandFilename*(filename: string): string {.rtl, extern: "nos$1",
  852. tags: [ReadDirEffect], noNimScript.} =
  853. ## Returns the full (`absolute`:idx:) path of an existing file `filename`,
  854. ## raises OSError in case of an error. Follows symlinks.
  855. when defined(windows):
  856. var bufsize = MAX_PATH.int32
  857. when useWinUnicode:
  858. var unused: WideCString = nil
  859. var res = newWideCString("", bufsize)
  860. while true:
  861. var L = getFullPathNameW(newWideCString(filename), bufsize, res, unused)
  862. if L == 0'i32:
  863. raiseOSError(osLastError())
  864. elif L > bufsize:
  865. res = newWideCString("", L)
  866. bufsize = L
  867. else:
  868. result = res$L
  869. break
  870. else:
  871. var unused: cstring = nil
  872. result = newString(bufsize)
  873. while true:
  874. var L = getFullPathNameA(filename, bufsize, result, unused)
  875. if L == 0'i32:
  876. raiseOSError(osLastError())
  877. elif L > bufsize:
  878. result = newString(L)
  879. bufsize = L
  880. else:
  881. setLen(result, L)
  882. break
  883. else:
  884. # according to Posix we don't need to allocate space for result pathname.
  885. # But we need to free return value with free(3).
  886. var r = realpath(filename, nil)
  887. if r.isNil:
  888. raiseOSError(osLastError())
  889. else:
  890. result = $r
  891. c_free(cast[pointer](r))
  892. proc normalizePath*(path: var string) {.rtl, extern: "nos$1", tags: [], noNimScript.} =
  893. ## Normalize a path.
  894. ##
  895. ## Consecutive directory separators are collapsed, including an initial double slash.
  896. ##
  897. ## On relative paths, double dot (..) sequences are collapsed if possible.
  898. ## On absolute paths they are always collapsed.
  899. ##
  900. ## Warning: URL-encoded and Unicode attempts at directory traversal are not detected.
  901. ## Triple dot is not handled.
  902. path = pathnorm.normalizePath(path)
  903. when false:
  904. let isAbs = isAbsolute(path)
  905. var stack: seq[string] = @[]
  906. for p in split(path, {DirSep}):
  907. case p
  908. of "", ".":
  909. continue
  910. of "..":
  911. if stack.len == 0:
  912. if isAbs:
  913. discard # collapse all double dots on absoluta paths
  914. else:
  915. stack.add(p)
  916. elif stack[^1] == "..":
  917. stack.add(p)
  918. else:
  919. discard stack.pop()
  920. else:
  921. stack.add(p)
  922. if isAbs:
  923. path = DirSep & join(stack, $DirSep)
  924. elif stack.len > 0:
  925. path = join(stack, $DirSep)
  926. else:
  927. path = "."
  928. proc normalizedPath*(path: string): string {.rtl, extern: "nos$1", tags: [], noNimScript.} =
  929. ## Returns a normalized path for the current OS. See `<#normalizePath>`_
  930. result = pathnorm.normalizePath(path)
  931. when defined(Windows) and not defined(nimscript):
  932. proc openHandle(path: string, followSymlink=true, writeAccess=false): Handle =
  933. var flags = FILE_FLAG_BACKUP_SEMANTICS or FILE_ATTRIBUTE_NORMAL
  934. if not followSymlink:
  935. flags = flags or FILE_FLAG_OPEN_REPARSE_POINT
  936. let access = if writeAccess: GENERIC_WRITE else: 0'i32
  937. when useWinUnicode:
  938. result = createFileW(
  939. newWideCString(path), access,
  940. FILE_SHARE_DELETE or FILE_SHARE_READ or FILE_SHARE_WRITE,
  941. nil, OPEN_EXISTING, flags, 0
  942. )
  943. else:
  944. result = createFileA(
  945. path, access,
  946. FILE_SHARE_DELETE or FILE_SHARE_READ or FILE_SHARE_WRITE,
  947. nil, OPEN_EXISTING, flags, 0
  948. )
  949. proc sameFile*(path1, path2: string): bool {.rtl, extern: "nos$1",
  950. tags: [ReadDirEffect], noNimScript.} =
  951. ## Returns true if both pathname arguments refer to the same physical
  952. ## file or directory. Raises an exception if any of the files does not
  953. ## exist or information about it can not be obtained.
  954. ##
  955. ## This proc will return true if given two alternative hard-linked or
  956. ## sym-linked paths to the same file or directory.
  957. when defined(Windows):
  958. var success = true
  959. var f1 = openHandle(path1)
  960. var f2 = openHandle(path2)
  961. var lastErr: OSErrorCode
  962. if f1 != INVALID_HANDLE_VALUE and f2 != INVALID_HANDLE_VALUE:
  963. var fi1, fi2: BY_HANDLE_FILE_INFORMATION
  964. if getFileInformationByHandle(f1, addr(fi1)) != 0 and
  965. getFileInformationByHandle(f2, addr(fi2)) != 0:
  966. result = fi1.dwVolumeSerialNumber == fi2.dwVolumeSerialNumber and
  967. fi1.nFileIndexHigh == fi2.nFileIndexHigh and
  968. fi1.nFileIndexLow == fi2.nFileIndexLow
  969. else:
  970. lastErr = osLastError()
  971. success = false
  972. else:
  973. lastErr = osLastError()
  974. success = false
  975. discard closeHandle(f1)
  976. discard closeHandle(f2)
  977. if not success: raiseOSError(lastErr)
  978. else:
  979. var a, b: Stat
  980. if stat(path1, a) < 0'i32 or stat(path2, b) < 0'i32:
  981. raiseOSError(osLastError())
  982. else:
  983. result = a.st_dev == b.st_dev and a.st_ino == b.st_ino
  984. proc sameFileContent*(path1, path2: string): bool {.rtl, extern: "nos$1",
  985. tags: [ReadIOEffect], noNimScript.} =
  986. ## Returns true if both pathname arguments refer to files with identical
  987. ## binary content.
  988. const
  989. bufSize = 8192 # 8K buffer
  990. var
  991. a, b: File
  992. if not open(a, path1): return false
  993. if not open(b, path2):
  994. close(a)
  995. return false
  996. var bufA = alloc(bufSize)
  997. var bufB = alloc(bufSize)
  998. while true:
  999. var readA = readBuffer(a, bufA, bufSize)
  1000. var readB = readBuffer(b, bufB, bufSize)
  1001. if readA != readB:
  1002. result = false
  1003. break
  1004. if readA == 0:
  1005. result = true
  1006. break
  1007. result = equalMem(bufA, bufB, readA)
  1008. if not result: break
  1009. if readA != bufSize: break # end of file
  1010. dealloc(bufA)
  1011. dealloc(bufB)
  1012. close(a)
  1013. close(b)
  1014. type
  1015. FilePermission* = enum ## file access permission; modelled after UNIX
  1016. fpUserExec, ## execute access for the file owner
  1017. fpUserWrite, ## write access for the file owner
  1018. fpUserRead, ## read access for the file owner
  1019. fpGroupExec, ## execute access for the group
  1020. fpGroupWrite, ## write access for the group
  1021. fpGroupRead, ## read access for the group
  1022. fpOthersExec, ## execute access for others
  1023. fpOthersWrite, ## write access for others
  1024. fpOthersRead ## read access for others
  1025. proc getFilePermissions*(filename: string): set[FilePermission] {.
  1026. rtl, extern: "nos$1", tags: [ReadDirEffect], noNimScript.} =
  1027. ## retrieves file permissions for `filename`. `OSError` is raised in case of
  1028. ## an error. On Windows, only the ``readonly`` flag is checked, every other
  1029. ## permission is available in any case.
  1030. when defined(posix):
  1031. var a: Stat
  1032. if stat(filename, a) < 0'i32: raiseOSError(osLastError())
  1033. result = {}
  1034. if (a.st_mode and S_IRUSR) != 0'i32: result.incl(fpUserRead)
  1035. if (a.st_mode and S_IWUSR) != 0'i32: result.incl(fpUserWrite)
  1036. if (a.st_mode and S_IXUSR) != 0'i32: result.incl(fpUserExec)
  1037. if (a.st_mode and S_IRGRP) != 0'i32: result.incl(fpGroupRead)
  1038. if (a.st_mode and S_IWGRP) != 0'i32: result.incl(fpGroupWrite)
  1039. if (a.st_mode and S_IXGRP) != 0'i32: result.incl(fpGroupExec)
  1040. if (a.st_mode and S_IROTH) != 0'i32: result.incl(fpOthersRead)
  1041. if (a.st_mode and S_IWOTH) != 0'i32: result.incl(fpOthersWrite)
  1042. if (a.st_mode and S_IXOTH) != 0'i32: result.incl(fpOthersExec)
  1043. else:
  1044. when useWinUnicode:
  1045. wrapUnary(res, getFileAttributesW, filename)
  1046. else:
  1047. var res = getFileAttributesA(filename)
  1048. if res == -1'i32: raiseOSError(osLastError())
  1049. if (res and FILE_ATTRIBUTE_READONLY) != 0'i32:
  1050. result = {fpUserExec, fpUserRead, fpGroupExec, fpGroupRead,
  1051. fpOthersExec, fpOthersRead}
  1052. else:
  1053. result = {fpUserExec..fpOthersRead}
  1054. proc setFilePermissions*(filename: string, permissions: set[FilePermission]) {.
  1055. rtl, extern: "nos$1", tags: [WriteDirEffect], noNimScript.} =
  1056. ## sets the file permissions for `filename`. `OSError` is raised in case of
  1057. ## an error. On Windows, only the ``readonly`` flag is changed, depending on
  1058. ## ``fpUserWrite``.
  1059. when defined(posix):
  1060. var p = 0'i32
  1061. if fpUserRead in permissions: p = p or S_IRUSR
  1062. if fpUserWrite in permissions: p = p or S_IWUSR
  1063. if fpUserExec in permissions: p = p or S_IXUSR
  1064. if fpGroupRead in permissions: p = p or S_IRGRP
  1065. if fpGroupWrite in permissions: p = p or S_IWGRP
  1066. if fpGroupExec in permissions: p = p or S_IXGRP
  1067. if fpOthersRead in permissions: p = p or S_IROTH
  1068. if fpOthersWrite in permissions: p = p or S_IWOTH
  1069. if fpOthersExec in permissions: p = p or S_IXOTH
  1070. if chmod(filename, p) != 0: raiseOSError(osLastError())
  1071. else:
  1072. when useWinUnicode:
  1073. wrapUnary(res, getFileAttributesW, filename)
  1074. else:
  1075. var res = getFileAttributesA(filename)
  1076. if res == -1'i32: raiseOSError(osLastError())
  1077. if fpUserWrite in permissions:
  1078. res = res and not FILE_ATTRIBUTE_READONLY
  1079. else:
  1080. res = res or FILE_ATTRIBUTE_READONLY
  1081. when useWinUnicode:
  1082. wrapBinary(res2, setFileAttributesW, filename, res)
  1083. else:
  1084. var res2 = setFileAttributesA(filename, res)
  1085. if res2 == - 1'i32: raiseOSError(osLastError())
  1086. proc copyFile*(source, dest: string) {.rtl, extern: "nos$1",
  1087. tags: [ReadIOEffect, WriteIOEffect], noNimScript.} =
  1088. ## Copies a file from `source` to `dest`.
  1089. ##
  1090. ## If this fails, `OSError` is raised. On the Windows platform this proc will
  1091. ## copy the source file's attributes into dest. On other platforms you need
  1092. ## to use `getFilePermissions() <#getFilePermissions>`_ and
  1093. ## `setFilePermissions() <#setFilePermissions>`_ to copy them by hand (or use
  1094. ## the convenience `copyFileWithPermissions() <#copyFileWithPermissions>`_
  1095. ## proc), otherwise `dest` will inherit the default permissions of a newly
  1096. ## created file for the user. If `dest` already exists, the file attributes
  1097. ## will be preserved and the content overwritten.
  1098. when defined(Windows):
  1099. when useWinUnicode:
  1100. let s = newWideCString(source)
  1101. let d = newWideCString(dest)
  1102. if copyFileW(s, d, 0'i32) == 0'i32: raiseOSError(osLastError())
  1103. else:
  1104. if copyFileA(source, dest, 0'i32) == 0'i32: raiseOSError(osLastError())
  1105. else:
  1106. # generic version of copyFile which works for any platform:
  1107. const bufSize = 8000 # better for memory manager
  1108. var d, s: File
  1109. if not open(s, source): raiseOSError(osLastError())
  1110. if not open(d, dest, fmWrite):
  1111. close(s)
  1112. raiseOSError(osLastError())
  1113. var buf = alloc(bufSize)
  1114. while true:
  1115. var bytesread = readBuffer(s, buf, bufSize)
  1116. if bytesread > 0:
  1117. var byteswritten = writeBuffer(d, buf, bytesread)
  1118. if bytesread != byteswritten:
  1119. dealloc(buf)
  1120. close(s)
  1121. close(d)
  1122. raiseOSError(osLastError())
  1123. if bytesread != bufSize: break
  1124. dealloc(buf)
  1125. close(s)
  1126. flushFile(d)
  1127. close(d)
  1128. when not declared(ENOENT) and not defined(Windows):
  1129. when NoFakeVars:
  1130. when not defined(haiku):
  1131. const ENOENT = cint(2) # 2 on most systems including Solaris
  1132. else:
  1133. const ENOENT = cint(-2147459069)
  1134. else:
  1135. var ENOENT {.importc, header: "<errno.h>".}: cint
  1136. when defined(Windows) and not defined(nimscript):
  1137. when useWinUnicode:
  1138. template deleteFile(file: untyped): untyped = deleteFileW(file)
  1139. template setFileAttributes(file, attrs: untyped): untyped =
  1140. setFileAttributesW(file, attrs)
  1141. else:
  1142. template deleteFile(file: untyped): untyped = deleteFileA(file)
  1143. template setFileAttributes(file, attrs: untyped): untyped =
  1144. setFileAttributesA(file, attrs)
  1145. proc tryRemoveFile*(file: string): bool {.rtl, extern: "nos$1", tags: [WriteDirEffect], noNimScript.} =
  1146. ## Removes the `file`. If this fails, returns `false`. This does not fail
  1147. ## if the file never existed in the first place.
  1148. ## On Windows, ignores the read-only attribute.
  1149. result = true
  1150. when defined(Windows):
  1151. when useWinUnicode:
  1152. let f = newWideCString(file)
  1153. else:
  1154. let f = file
  1155. if deleteFile(f) == 0:
  1156. result = false
  1157. let err = getLastError()
  1158. if err == ERROR_FILE_NOT_FOUND or err == ERROR_PATH_NOT_FOUND:
  1159. result = true
  1160. elif err == ERROR_ACCESS_DENIED and
  1161. setFileAttributes(f, FILE_ATTRIBUTE_NORMAL) != 0 and
  1162. deleteFile(f) != 0:
  1163. result = true
  1164. else:
  1165. if unlink(file) != 0'i32 and errno != ENOENT:
  1166. result = false
  1167. proc removeFile*(file: string) {.rtl, extern: "nos$1", tags: [WriteDirEffect], noNimScript.} =
  1168. ## Removes the `file`. If this fails, `OSError` is raised. This does not fail
  1169. ## if the file never existed in the first place.
  1170. ## On Windows, ignores the read-only attribute.
  1171. if not tryRemoveFile(file):
  1172. when defined(Windows):
  1173. raiseOSError(osLastError())
  1174. else:
  1175. raiseOSError(osLastError(), $strerror(errno))
  1176. proc tryMoveFSObject(source, dest: string): bool {.noNimScript.} =
  1177. ## Moves a file or directory from `source` to `dest`. Returns false in case
  1178. ## of `EXDEV` error. In case of other errors `OSError` is raised. Returns
  1179. ## true in case of success.
  1180. when defined(Windows):
  1181. when useWinUnicode:
  1182. let s = newWideCString(source)
  1183. let d = newWideCString(dest)
  1184. if moveFileExW(s, d, MOVEFILE_COPY_ALLOWED) == 0'i32: raiseOSError(osLastError())
  1185. else:
  1186. if moveFileExA(source, dest, MOVEFILE_COPY_ALLOWED) == 0'i32: raiseOSError(osLastError())
  1187. else:
  1188. if c_rename(source, dest) != 0'i32:
  1189. let err = osLastError()
  1190. if err == EXDEV.OSErrorCode:
  1191. return false
  1192. else:
  1193. raiseOSError(err, $strerror(errno))
  1194. return true
  1195. proc moveFile*(source, dest: string) {.rtl, extern: "nos$1",
  1196. tags: [ReadIOEffect, WriteIOEffect], noNimScript.} =
  1197. ## Moves a file from `source` to `dest`. If this fails, `OSError` is raised.
  1198. ## Can be used to `rename files`:idx:
  1199. if not tryMoveFSObject(source, dest):
  1200. when not defined(windows):
  1201. # Fallback to copy & del
  1202. copyFile(source, dest)
  1203. try:
  1204. removeFile(source)
  1205. except:
  1206. discard tryRemoveFile(dest)
  1207. raise
  1208. proc execShellCmd*(command: string): int {.rtl, extern: "nos$1",
  1209. tags: [ExecIOEffect], noNimScript.} =
  1210. ## Executes a `shell command`:idx:.
  1211. ##
  1212. ## Command has the form 'program args' where args are the command
  1213. ## line arguments given to program. The proc returns the error code
  1214. ## of the shell when it has finished. The proc does not return until
  1215. ## the process has finished. To execute a program without having a
  1216. ## shell involved, use the `execProcess` proc of the `osproc`
  1217. ## module.
  1218. when defined(posix):
  1219. result = c_system(command) shr 8
  1220. else:
  1221. result = c_system(command)
  1222. # Templates for filtering directories and files
  1223. when defined(windows) and not defined(nimscript):
  1224. template isDir(f: WIN32_FIND_DATA): bool =
  1225. (f.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) != 0'i32
  1226. template isFile(f: WIN32_FIND_DATA): bool =
  1227. not isDir(f)
  1228. else:
  1229. template isDir(f: string): bool =
  1230. dirExists(f)
  1231. template isFile(f: string): bool =
  1232. fileExists(f)
  1233. template defaultWalkFilter(item): bool =
  1234. ## Walk filter used to return true on both
  1235. ## files and directories
  1236. true
  1237. template walkCommon(pattern: string, filter) =
  1238. ## Common code for getting the files and directories with the
  1239. ## specified `pattern`
  1240. when defined(windows):
  1241. var
  1242. f: WIN32_FIND_DATA
  1243. res: int
  1244. res = findFirstFile(pattern, f)
  1245. if res != -1:
  1246. defer: findClose(res)
  1247. let dotPos = searchExtPos(pattern)
  1248. while true:
  1249. if not skipFindData(f) and filter(f):
  1250. # Windows bug/gotcha: 't*.nim' matches 'tfoo.nims' -.- so we check
  1251. # that the file extensions have the same length ...
  1252. let ff = getFilename(f)
  1253. let idx = ff.len - pattern.len + dotPos
  1254. if dotPos < 0 or idx >= ff.len or ff[idx] == '.' or
  1255. pattern[dotPos+1] == '*':
  1256. yield splitFile(pattern).dir / extractFilename(ff)
  1257. if findNextFile(res, f) == 0'i32:
  1258. let errCode = getLastError()
  1259. if errCode == ERROR_NO_MORE_FILES: break
  1260. else: raiseOSError(errCode.OSErrorCode)
  1261. else: # here we use glob
  1262. var
  1263. f: Glob
  1264. res: int
  1265. f.gl_offs = 0
  1266. f.gl_pathc = 0
  1267. f.gl_pathv = nil
  1268. res = glob(pattern, 0, nil, addr(f))
  1269. defer: globfree(addr(f))
  1270. if res == 0:
  1271. for i in 0.. f.gl_pathc - 1:
  1272. assert(f.gl_pathv[i] != nil)
  1273. let path = $f.gl_pathv[i]
  1274. if filter(path):
  1275. yield path
  1276. iterator walkPattern*(pattern: string): string {.tags: [ReadDirEffect], noNimScript.} =
  1277. ## Iterate over all the files and directories that match the `pattern`.
  1278. ## On POSIX this uses the `glob`:idx: call.
  1279. ##
  1280. ## `pattern` is OS dependent, but at least the "\*.ext"
  1281. ## notation is supported.
  1282. walkCommon(pattern, defaultWalkFilter)
  1283. iterator walkFiles*(pattern: string): string {.tags: [ReadDirEffect], noNimScript.} =
  1284. ## Iterate over all the files that match the `pattern`. On POSIX this uses
  1285. ## the `glob`:idx: call.
  1286. ##
  1287. ## `pattern` is OS dependent, but at least the "\*.ext"
  1288. ## notation is supported.
  1289. walkCommon(pattern, isFile)
  1290. iterator walkDirs*(pattern: string): string {.tags: [ReadDirEffect], noNimScript.} =
  1291. ## Iterate over all the directories that match the `pattern`.
  1292. ## On POSIX this uses the `glob`:idx: call.
  1293. ##
  1294. ## `pattern` is OS dependent, but at least the "\*.ext"
  1295. ## notation is supported.
  1296. walkCommon(pattern, isDir)
  1297. type
  1298. PathComponent* = enum ## Enumeration specifying a path component.
  1299. pcFile, ## path refers to a file
  1300. pcLinkToFile, ## path refers to a symbolic link to a file
  1301. pcDir, ## path refers to a directory
  1302. pcLinkToDir ## path refers to a symbolic link to a directory
  1303. when defined(posix) and not defined(nimscript):
  1304. proc getSymlinkFileKind(path: string): PathComponent =
  1305. # Helper function.
  1306. var s: Stat
  1307. assert(path != "")
  1308. if stat(path, s) == 0'i32 and S_ISDIR(s.st_mode):
  1309. result = pcLinkToDir
  1310. else:
  1311. result = pcLinkToFile
  1312. proc staticWalkDir(dir: string; relative: bool): seq[
  1313. tuple[kind: PathComponent, path: string]] =
  1314. discard
  1315. iterator walkDir*(dir: string; relative=false): tuple[kind: PathComponent, path: string] {.
  1316. tags: [ReadDirEffect].} =
  1317. ## walks over the directory `dir` and yields for each directory or file in
  1318. ## `dir`. The component type and full path for each item is returned.
  1319. ## Walking is not recursive. If ``relative`` is true the resulting path is
  1320. ## shortened to be relative to ``dir``.
  1321. ## Example: This directory structure::
  1322. ## dirA / dirB / fileB1.txt
  1323. ## / dirC
  1324. ## / fileA1.txt
  1325. ## / fileA2.txt
  1326. ##
  1327. ## and this code:
  1328. ##
  1329. ## .. code-block:: Nim
  1330. ## for kind, path in walkDir("dirA"):
  1331. ## echo(path)
  1332. ##
  1333. ## produces this output (but not necessarily in this order!)::
  1334. ## dirA/dirB
  1335. ## dirA/dirC
  1336. ## dirA/fileA1.txt
  1337. ## dirA/fileA2.txt
  1338. when nimvm:
  1339. for k, v in items(staticWalkDir(dir, relative)):
  1340. yield (k, v)
  1341. else:
  1342. when defined(nimscript):
  1343. for k, v in items(staticWalkDir(dir, relative)):
  1344. yield (k, v)
  1345. elif defined(windows):
  1346. var f: WIN32_FIND_DATA
  1347. var h = findFirstFile(dir / "*", f)
  1348. if h != -1:
  1349. defer: findClose(h)
  1350. while true:
  1351. var k = pcFile
  1352. if not skipFindData(f):
  1353. if (f.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) != 0'i32:
  1354. k = pcDir
  1355. if (f.dwFileAttributes and FILE_ATTRIBUTE_REPARSE_POINT) != 0'i32:
  1356. k = succ(k)
  1357. let xx = if relative: extractFilename(getFilename(f))
  1358. else: dir / extractFilename(getFilename(f))
  1359. yield (k, xx)
  1360. if findNextFile(h, f) == 0'i32:
  1361. let errCode = getLastError()
  1362. if errCode == ERROR_NO_MORE_FILES: break
  1363. else: raiseOSError(errCode.OSErrorCode)
  1364. else:
  1365. var d = opendir(dir)
  1366. if d != nil:
  1367. defer: discard closedir(d)
  1368. while true:
  1369. var x = readdir(d)
  1370. if x == nil: break
  1371. when defined(nimNoArrayToCstringConversion):
  1372. var y = $cstring(addr x.d_name)
  1373. else:
  1374. var y = $x.d_name.cstring
  1375. if y != "." and y != "..":
  1376. var s: Stat
  1377. if not relative:
  1378. y = dir / y
  1379. var k = pcFile
  1380. when defined(linux) or defined(macosx) or
  1381. defined(bsd) or defined(genode) or defined(nintendoswitch):
  1382. if x.d_type != DT_UNKNOWN:
  1383. if x.d_type == DT_DIR: k = pcDir
  1384. if x.d_type == DT_LNK:
  1385. if dirExists(y): k = pcLinkToDir
  1386. else: k = pcLinkToFile
  1387. yield (k, y)
  1388. continue
  1389. if lstat(y, s) < 0'i32: break
  1390. if S_ISDIR(s.st_mode):
  1391. k = pcDir
  1392. elif S_ISLNK(s.st_mode):
  1393. k = getSymlinkFileKind(y)
  1394. yield (k, y)
  1395. iterator walkDirRec*(dir: string,
  1396. yieldFilter = {pcFile}, followFilter = {pcDir},
  1397. relative = false): string {.tags: [ReadDirEffect].} =
  1398. ## Recursively walks over the directory `dir` and yields for each file
  1399. ## or directory in `dir`.
  1400. ## If ``relative`` is true the resulting path is
  1401. ## shortened to be relative to ``dir``, otherwise the full path is returned.
  1402. ##
  1403. ## **Warning**:
  1404. ## Modifying the directory structure while the iterator
  1405. ## is traversing may result in undefined behavior!
  1406. ##
  1407. ## Walking is recursive. `filters` controls the behaviour of the iterator:
  1408. ##
  1409. ## --------------------- ---------------------------------------------
  1410. ## yieldFilter meaning
  1411. ## --------------------- ---------------------------------------------
  1412. ## ``pcFile`` yield real files
  1413. ## ``pcLinkToFile`` yield symbolic links to files
  1414. ## ``pcDir`` yield real directories
  1415. ## ``pcLinkToDir`` yield symbolic links to directories
  1416. ## --------------------- ---------------------------------------------
  1417. ##
  1418. ## --------------------- ---------------------------------------------
  1419. ## followFilter meaning
  1420. ## --------------------- ---------------------------------------------
  1421. ## ``pcDir`` follow real directories
  1422. ## ``pcLinkToDir`` follow symbolic links to directories
  1423. ## --------------------- ---------------------------------------------
  1424. ##
  1425. var stack = @[""]
  1426. while stack.len > 0:
  1427. let d = stack.pop()
  1428. for k, p in walkDir(dir / d, relative = true):
  1429. let rel = d / p
  1430. if k in {pcDir, pcLinkToDir} and k in followFilter:
  1431. stack.add rel
  1432. if k in yieldFilter:
  1433. yield if relative: rel else: dir / rel
  1434. proc rawRemoveDir(dir: string) {.noNimScript.} =
  1435. when defined(windows):
  1436. when useWinUnicode:
  1437. wrapUnary(res, removeDirectoryW, dir)
  1438. else:
  1439. var res = removeDirectoryA(dir)
  1440. let lastError = osLastError()
  1441. if res == 0'i32 and lastError.int32 != 3'i32 and
  1442. lastError.int32 != 18'i32 and lastError.int32 != 2'i32:
  1443. raiseOSError(lastError)
  1444. else:
  1445. if rmdir(dir) != 0'i32 and errno != ENOENT: raiseOSError(osLastError())
  1446. proc removeDir*(dir: string) {.rtl, extern: "nos$1", tags: [
  1447. WriteDirEffect, ReadDirEffect], benign, noNimScript.} =
  1448. ## Removes the directory `dir` including all subdirectories and files
  1449. ## in `dir` (recursively).
  1450. ##
  1451. ## If this fails, `OSError` is raised. This does not fail if the directory never
  1452. ## existed in the first place.
  1453. for kind, path in walkDir(dir):
  1454. case kind
  1455. of pcFile, pcLinkToFile, pcLinkToDir: removeFile(path)
  1456. of pcDir: removeDir(path)
  1457. rawRemoveDir(dir)
  1458. proc rawCreateDir(dir: string): bool {.noNimScript.} =
  1459. # Try to create one directory (not the whole path).
  1460. # returns `true` for success, `false` if the path has previously existed
  1461. #
  1462. # This is a thin wrapper over mkDir (or alternatives on other systems),
  1463. # so in case of a pre-existing path we don't check that it is a directory.
  1464. when defined(solaris):
  1465. let res = mkdir(dir, 0o777)
  1466. if res == 0'i32:
  1467. result = true
  1468. elif errno in {EEXIST, ENOSYS}:
  1469. result = false
  1470. else:
  1471. raiseOSError(osLastError(), dir)
  1472. elif defined(haiku):
  1473. let res = mkdir(dir, 0o777)
  1474. if res == 0'i32:
  1475. result = true
  1476. elif errno == EEXIST or errno == EROFS:
  1477. result = false
  1478. else:
  1479. raiseOSError(osLastError(), dir)
  1480. elif defined(posix):
  1481. let res = mkdir(dir, 0o777)
  1482. if res == 0'i32:
  1483. result = true
  1484. elif errno == EEXIST:
  1485. result = false
  1486. else:
  1487. #echo res
  1488. raiseOSError(osLastError(), dir)
  1489. else:
  1490. when useWinUnicode:
  1491. wrapUnary(res, createDirectoryW, dir)
  1492. else:
  1493. let res = createDirectoryA(dir)
  1494. if res != 0'i32:
  1495. result = true
  1496. elif getLastError() == 183'i32:
  1497. result = false
  1498. else:
  1499. raiseOSError(osLastError(), dir)
  1500. proc existsOrCreateDir*(dir: string): bool {.rtl, extern: "nos$1",
  1501. tags: [WriteDirEffect, ReadDirEffect], noNimScript.} =
  1502. ## Check if a `directory`:idx: `dir` exists, and create it otherwise.
  1503. ##
  1504. ## Does not create parent directories (fails if parent does not exist).
  1505. ## Returns `true` if the directory already exists, and `false`
  1506. ## otherwise.
  1507. result = not rawCreateDir(dir)
  1508. if result:
  1509. # path already exists - need to check that it is indeed a directory
  1510. if not existsDir(dir):
  1511. raise newException(IOError, "Failed to create '" & dir & "'")
  1512. proc createDir*(dir: string) {.rtl, extern: "nos$1",
  1513. tags: [WriteDirEffect, ReadDirEffect], noNimScript.} =
  1514. ## Creates the `directory`:idx: `dir`.
  1515. ##
  1516. ## The directory may contain several subdirectories that do not exist yet.
  1517. ## The full path is created. If this fails, `OSError` is raised. It does **not**
  1518. ## fail if the directory already exists because for most usages this does not
  1519. ## indicate an error.
  1520. var omitNext = false
  1521. when doslikeFileSystem:
  1522. omitNext = isAbsolute(dir)
  1523. for i in 1.. dir.len-1:
  1524. if dir[i] in {DirSep, AltSep}:
  1525. if omitNext:
  1526. omitNext = false
  1527. else:
  1528. discard existsOrCreateDir(substr(dir, 0, i-1))
  1529. # The loop does not create the dir itself if it doesn't end in separator
  1530. if dir.len > 0 and not omitNext and
  1531. dir[^1] notin {DirSep, AltSep}:
  1532. discard existsOrCreateDir(dir)
  1533. proc copyDir*(source, dest: string) {.rtl, extern: "nos$1",
  1534. tags: [WriteIOEffect, ReadIOEffect], benign, noNimScript.} =
  1535. ## Copies a directory from `source` to `dest`.
  1536. ##
  1537. ## If this fails, `OSError` is raised. On the Windows platform this proc will
  1538. ## copy the attributes from `source` into `dest`. On other platforms created
  1539. ## files and directories will inherit the default permissions of a newly
  1540. ## created file/directory for the user. To preserve attributes recursively on
  1541. ## these platforms use `copyDirWithPermissions() <#copyDirWithPermissions>`_.
  1542. createDir(dest)
  1543. for kind, path in walkDir(source):
  1544. var noSource = splitPath(path).tail
  1545. case kind
  1546. of pcFile:
  1547. copyFile(path, dest / noSource)
  1548. of pcDir:
  1549. copyDir(path, dest / noSource)
  1550. else: discard
  1551. proc createSymlink*(src, dest: string) {.noNimScript.} =
  1552. ## Create a symbolic link at `dest` which points to the item specified
  1553. ## by `src`. On most operating systems, will fail if a link already exists.
  1554. ##
  1555. ## **Warning**:
  1556. ## Some OS's (such as Microsoft Windows) restrict the creation
  1557. ## of symlinks to root users (administrators).
  1558. when defined(Windows):
  1559. # 2 is the SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE. This allows
  1560. # anyone with developer mode on to create a link
  1561. let flag = dirExists(src).int32 or 2
  1562. when useWinUnicode:
  1563. var wSrc = newWideCString(src)
  1564. var wDst = newWideCString(dest)
  1565. if createSymbolicLinkW(wDst, wSrc, flag) == 0 or getLastError() != 0:
  1566. raiseOSError(osLastError())
  1567. else:
  1568. if createSymbolicLinkA(dest, src, flag) == 0 or getLastError() != 0:
  1569. raiseOSError(osLastError())
  1570. else:
  1571. if symlink(src, dest) != 0:
  1572. raiseOSError(osLastError())
  1573. proc createHardlink*(src, dest: string) {.noNimScript.} =
  1574. ## Create a hard link at `dest` which points to the item specified
  1575. ## by `src`.
  1576. ##
  1577. ## **Warning**: Some OS's restrict the creation of hard links to
  1578. ## root users (administrators).
  1579. when defined(Windows):
  1580. when useWinUnicode:
  1581. var wSrc = newWideCString(src)
  1582. var wDst = newWideCString(dest)
  1583. if createHardLinkW(wDst, wSrc, nil) == 0:
  1584. raiseOSError(osLastError())
  1585. else:
  1586. if createHardLinkA(dest, src, nil) == 0:
  1587. raiseOSError(osLastError())
  1588. else:
  1589. if link(src, dest) != 0:
  1590. raiseOSError(osLastError())
  1591. proc parseCmdLine*(c: string): seq[string] {.
  1592. noSideEffect, rtl, extern: "nos$1".} =
  1593. ## Splits a `command line`:idx: into several components;
  1594. ## This proc is only occasionally useful, better use the `parseopt` module.
  1595. ##
  1596. ## On Windows, it uses the following parsing rules
  1597. ## (see http://msdn.microsoft.com/en-us/library/17w5ykft.aspx ):
  1598. ##
  1599. ## * Arguments are delimited by white space, which is either a space or a tab.
  1600. ## * The caret character (^) is not recognized as an escape character or
  1601. ## delimiter. The character is handled completely by the command-line parser
  1602. ## in the operating system before being passed to the argv array in the
  1603. ## program.
  1604. ## * A string surrounded by double quotation marks ("string") is interpreted
  1605. ## as a single argument, regardless of white space contained within. A
  1606. ## quoted string can be embedded in an argument.
  1607. ## * A double quotation mark preceded by a backslash (\") is interpreted as a
  1608. ## literal double quotation mark character (").
  1609. ## * Backslashes are interpreted literally, unless they immediately precede
  1610. ## a double quotation mark.
  1611. ## * If an even number of backslashes is followed by a double quotation mark,
  1612. ## one backslash is placed in the argv array for every pair of backslashes,
  1613. ## and the double quotation mark is interpreted as a string delimiter.
  1614. ## * If an odd number of backslashes is followed by a double quotation mark,
  1615. ## one backslash is placed in the argv array for every pair of backslashes,
  1616. ## and the double quotation mark is "escaped" by the remaining backslash,
  1617. ## causing a literal double quotation mark (") to be placed in argv.
  1618. ##
  1619. ## On Posix systems, it uses the following parsing rules:
  1620. ## Components are separated by whitespace unless the whitespace
  1621. ## occurs within ``"`` or ``'`` quotes.
  1622. result = @[]
  1623. var i = 0
  1624. var a = ""
  1625. while true:
  1626. setLen(a, 0)
  1627. # eat all delimiting whitespace
  1628. while i < c.len and c[i] in {' ', '\t', '\l', '\r'}: inc(i)
  1629. if i >= c.len: break
  1630. when defined(windows):
  1631. # parse a single argument according to the above rules:
  1632. var inQuote = false
  1633. while i < c.len:
  1634. case c[i]
  1635. of '\\':
  1636. var j = i
  1637. while j < c.len and c[j] == '\\': inc(j)
  1638. if j < c.len and c[j] == '"':
  1639. for k in 1..(j-i) div 2: a.add('\\')
  1640. if (j-i) mod 2 == 0:
  1641. i = j
  1642. else:
  1643. a.add('"')
  1644. i = j+1
  1645. else:
  1646. a.add(c[i])
  1647. inc(i)
  1648. of '"':
  1649. inc(i)
  1650. if not inQuote: inQuote = true
  1651. elif i < c.len and c[i] == '"':
  1652. a.add(c[i])
  1653. inc(i)
  1654. else:
  1655. inQuote = false
  1656. break
  1657. of ' ', '\t':
  1658. if not inQuote: break
  1659. a.add(c[i])
  1660. inc(i)
  1661. else:
  1662. a.add(c[i])
  1663. inc(i)
  1664. else:
  1665. case c[i]
  1666. of '\'', '\"':
  1667. var delim = c[i]
  1668. inc(i) # skip ' or "
  1669. while i < c.len and c[i] != delim:
  1670. add a, c[i]
  1671. inc(i)
  1672. if i < c.len: inc(i)
  1673. else:
  1674. while i < c.len and c[i] > ' ':
  1675. add(a, c[i])
  1676. inc(i)
  1677. add(result, a)
  1678. proc copyFileWithPermissions*(source, dest: string,
  1679. ignorePermissionErrors = true) {.noNimScript.} =
  1680. ## Copies a file from `source` to `dest` preserving file permissions.
  1681. ##
  1682. ## This is a wrapper proc around `copyFile() <#copyFile>`_,
  1683. ## `getFilePermissions() <#getFilePermissions>`_ and `setFilePermissions()
  1684. ## <#setFilePermissions>`_ on non Windows platform. On Windows this proc is
  1685. ## just a wrapper for `copyFile() <#copyFile>`_ since that proc already
  1686. ## copies attributes.
  1687. ##
  1688. ## On non Windows systems permissions are copied after the file itself has
  1689. ## been copied, which won't happen atomically and could lead to a race
  1690. ## condition. If `ignorePermissionErrors` is true, errors while
  1691. ## reading/setting file attributes will be ignored, otherwise will raise
  1692. ## `OSError`.
  1693. copyFile(source, dest)
  1694. when not defined(Windows):
  1695. try:
  1696. setFilePermissions(dest, getFilePermissions(source))
  1697. except:
  1698. if not ignorePermissionErrors:
  1699. raise
  1700. proc copyDirWithPermissions*(source, dest: string,
  1701. ignorePermissionErrors = true) {.rtl, extern: "nos$1",
  1702. tags: [WriteIOEffect, ReadIOEffect], benign, noNimScript.} =
  1703. ## Copies a directory from `source` to `dest` preserving file permissions.
  1704. ##
  1705. ## If this fails, `OSError` is raised. This is a wrapper proc around `copyDir()
  1706. ## <#copyDir>`_ and `copyFileWithPermissions() <#copyFileWithPermissions>`_
  1707. ## on non Windows platforms. On Windows this proc is just a wrapper for
  1708. ## `copyDir() <#copyDir>`_ since that proc already copies attributes.
  1709. ##
  1710. ## On non Windows systems permissions are copied after the file or directory
  1711. ## itself has been copied, which won't happen atomically and could lead to a
  1712. ## race condition. If `ignorePermissionErrors` is true, errors while
  1713. ## reading/setting file attributes will be ignored, otherwise will raise
  1714. ## `OSError`.
  1715. createDir(dest)
  1716. when not defined(Windows):
  1717. try:
  1718. setFilePermissions(dest, getFilePermissions(source))
  1719. except:
  1720. if not ignorePermissionErrors:
  1721. raise
  1722. for kind, path in walkDir(source):
  1723. var noSource = splitPath(path).tail
  1724. case kind
  1725. of pcFile:
  1726. copyFileWithPermissions(path, dest / noSource, ignorePermissionErrors)
  1727. of pcDir:
  1728. copyDirWithPermissions(path, dest / noSource, ignorePermissionErrors)
  1729. else: discard
  1730. proc inclFilePermissions*(filename: string,
  1731. permissions: set[FilePermission]) {.
  1732. rtl, extern: "nos$1", tags: [ReadDirEffect, WriteDirEffect], noNimScript.} =
  1733. ## a convenience procedure for:
  1734. ##
  1735. ## .. code-block:: nim
  1736. ## setFilePermissions(filename, getFilePermissions(filename)+permissions)
  1737. setFilePermissions(filename, getFilePermissions(filename)+permissions)
  1738. proc exclFilePermissions*(filename: string,
  1739. permissions: set[FilePermission]) {.
  1740. rtl, extern: "nos$1", tags: [ReadDirEffect, WriteDirEffect], noNimScript.} =
  1741. ## a convenience procedure for:
  1742. ##
  1743. ## .. code-block:: nim
  1744. ## setFilePermissions(filename, getFilePermissions(filename)-permissions)
  1745. setFilePermissions(filename, getFilePermissions(filename)-permissions)
  1746. proc moveDir*(source, dest: string) {.tags: [ReadIOEffect, WriteIOEffect], noNimScript.} =
  1747. ## Moves a directory from `source` to `dest`. If this fails, `OSError` is raised.
  1748. if not tryMoveFSObject(source, dest):
  1749. when not defined(windows):
  1750. # Fallback to copy & del
  1751. copyDir(source, dest)
  1752. removeDir(source)
  1753. proc expandSymlink*(symlinkPath: string): string {.noNimScript.} =
  1754. ## Returns a string representing the path to which the symbolic link points.
  1755. ##
  1756. ## On Windows this is a noop, ``symlinkPath`` is simply returned.
  1757. when defined(windows):
  1758. result = symlinkPath
  1759. else:
  1760. result = newString(256)
  1761. var len = readlink(symlinkPath, result, 256)
  1762. if len < 0:
  1763. raiseOSError(osLastError())
  1764. if len > 256:
  1765. result = newString(len+1)
  1766. len = readlink(symlinkPath, result, len)
  1767. setLen(result, len)
  1768. when defined(nimdoc):
  1769. # Common forward declaration docstring block for parameter retrieval procs.
  1770. proc paramCount*(): int {.tags: [ReadIOEffect].} =
  1771. ## Returns the number of `command line arguments`:idx: given to the
  1772. ## application.
  1773. ##
  1774. ## Unlike `argc`:idx: in C, if your binary was called without parameters this
  1775. ## will return zero.
  1776. ## You can query each individual paramater with `paramStr() <#paramStr>`_
  1777. ## or retrieve all of them in one go with `commandLineParams()
  1778. ## <#commandLineParams>`_.
  1779. ##
  1780. ## **Availability**: When generating a dynamic library (see --app:lib) on
  1781. ## Posix this proc is not defined.
  1782. ## Test for availability using `declared() <system.html#declared>`_.
  1783. ## Example:
  1784. ##
  1785. ## .. code-block:: nim
  1786. ## when declared(paramCount):
  1787. ## # Use paramCount() here
  1788. ## else:
  1789. ## # Do something else!
  1790. proc paramStr*(i: int): TaintedString {.tags: [ReadIOEffect].} =
  1791. ## Returns the `i`-th `command line argument`:idx: given to the application.
  1792. ##
  1793. ## `i` should be in the range `1..paramCount()`, the `IndexError`
  1794. ## exception will be raised for invalid values. Instead of iterating over
  1795. ## `paramCount() <#paramCount>`_ with this proc you can call the
  1796. ## convenience `commandLineParams() <#commandLineParams>`_.
  1797. ##
  1798. ## Similarly to `argv`:idx: in C,
  1799. ## it is possible to call ``paramStr(0)`` but this will return OS specific
  1800. ## contents (usually the name of the invoked executable). You should avoid
  1801. ## this and call `getAppFilename() <#getAppFilename>`_ instead.
  1802. ##
  1803. ## **Availability**: When generating a dynamic library (see --app:lib) on
  1804. ## Posix this proc is not defined.
  1805. ## Test for availability using `declared() <system.html#declared>`_.
  1806. ## Example:
  1807. ##
  1808. ## .. code-block:: nim
  1809. ## when declared(paramStr):
  1810. ## # Use paramStr() here
  1811. ## else:
  1812. ## # Do something else!
  1813. elif defined(nintendoswitch) or defined(nimscript):
  1814. proc paramStr*(i: int): TaintedString {.tags: [ReadIOEffect].} =
  1815. raise newException(OSError, "paramStr is not implemented on Nintendo Switch")
  1816. proc paramCount*(): int {.tags: [ReadIOEffect].} =
  1817. raise newException(OSError, "paramCount is not implemented on Nintendo Switch")
  1818. elif defined(windows):
  1819. # Since we support GUI applications with Nim, we sometimes generate
  1820. # a WinMain entry proc. But a WinMain proc has no access to the parsed
  1821. # command line arguments. The way to get them differs. Thus we parse them
  1822. # ourselves. This has the additional benefit that the program's behaviour
  1823. # is always the same -- independent of the used C compiler.
  1824. var
  1825. ownArgv {.threadvar.}: seq[string]
  1826. ownParsedArgv {.threadvar.}: bool
  1827. proc paramCount*(): int {.rtl, extern: "nos$1", tags: [ReadIOEffect].} =
  1828. # Docstring in nimdoc block.
  1829. if not ownParsedArgv:
  1830. ownArgv = parseCmdLine($getCommandLine())
  1831. ownParsedArgv = true
  1832. result = ownArgv.len-1
  1833. proc paramStr*(i: int): TaintedString {.rtl, extern: "nos$1",
  1834. tags: [ReadIOEffect].} =
  1835. # Docstring in nimdoc block.
  1836. if not ownParsedArgv:
  1837. ownArgv = parseCmdLine($getCommandLine())
  1838. ownParsedArgv = true
  1839. if i < ownArgv.len and i >= 0: return TaintedString(ownArgv[i])
  1840. raise newException(IndexError, "invalid index")
  1841. elif defined(genode):
  1842. proc paramStr*(i: int): TaintedString =
  1843. raise newException(OSError, "paramStr is not implemented on Genode")
  1844. proc paramCount*(): int =
  1845. raise newException(OSError, "paramCount is not implemented on Genode")
  1846. elif not defined(createNimRtl) and
  1847. not(defined(posix) and appType == "lib"):
  1848. # On Posix, there is no portable way to get the command line from a DLL.
  1849. var
  1850. cmdCount {.importc: "cmdCount".}: cint
  1851. cmdLine {.importc: "cmdLine".}: cstringArray
  1852. proc paramStr*(i: int): TaintedString {.tags: [ReadIOEffect].} =
  1853. # Docstring in nimdoc block.
  1854. if i < cmdCount and i >= 0: return TaintedString($cmdLine[i])
  1855. raise newException(IndexError, "invalid index")
  1856. proc paramCount*(): int {.tags: [ReadIOEffect].} =
  1857. # Docstring in nimdoc block.
  1858. result = cmdCount-1
  1859. when declared(paramCount) or defined(nimdoc):
  1860. proc commandLineParams*(): seq[TaintedString] =
  1861. ## Convenience proc which returns the command line parameters.
  1862. ##
  1863. ## This returns **only** the parameters. If you want to get the application
  1864. ## executable filename, call `getAppFilename() <#getAppFilename>`_.
  1865. ##
  1866. ## **Availability**: On Posix there is no portable way to get the command
  1867. ## line from a DLL and thus the proc isn't defined in this environment. You
  1868. ## can test for its availability with `declared() <system.html#declared>`_.
  1869. ## Example:
  1870. ##
  1871. ## .. code-block:: nim
  1872. ## when declared(commandLineParams):
  1873. ## # Use commandLineParams() here
  1874. ## else:
  1875. ## # Do something else!
  1876. result = @[]
  1877. for i in 1..paramCount():
  1878. result.add(paramStr(i))
  1879. when not defined(nimscript) and (defined(freebsd) or defined(dragonfly)):
  1880. proc sysctl(name: ptr cint, namelen: cuint, oldp: pointer, oldplen: ptr csize,
  1881. newp: pointer, newplen: csize): cint
  1882. {.importc: "sysctl",header: """#include <sys/types.h>
  1883. #include <sys/sysctl.h>"""}
  1884. const
  1885. CTL_KERN = 1
  1886. KERN_PROC = 14
  1887. MAX_PATH = 1024
  1888. when defined(freebsd):
  1889. const KERN_PROC_PATHNAME = 12
  1890. else:
  1891. const KERN_PROC_PATHNAME = 9
  1892. proc getApplFreebsd(): string =
  1893. var pathLength = csize(MAX_PATH)
  1894. result = newString(pathLength)
  1895. var req = [CTL_KERN.cint, KERN_PROC.cint, KERN_PROC_PATHNAME.cint, -1.cint]
  1896. while true:
  1897. let res = sysctl(addr req[0], 4, cast[pointer](addr result[0]),
  1898. addr pathLength, nil, 0)
  1899. if res < 0:
  1900. let err = osLastError()
  1901. if err.int32 == ENOMEM:
  1902. result = newString(pathLength)
  1903. else:
  1904. result.setLen(0) # error!
  1905. break
  1906. else:
  1907. result.setLen(pathLength)
  1908. break
  1909. when not defined(nimscript) and (defined(linux) or defined(solaris) or defined(bsd) or defined(aix)):
  1910. proc getApplAux(procPath: string): string =
  1911. result = newString(256)
  1912. var len = readlink(procPath, result, 256)
  1913. if len > 256:
  1914. result = newString(len+1)
  1915. len = readlink(procPath, result, len)
  1916. setLen(result, len)
  1917. when not (defined(windows) or defined(macosx) or defined(nimscript)):
  1918. proc getApplHeuristic(): string =
  1919. when declared(paramStr):
  1920. result = string(paramStr(0))
  1921. # POSIX guaranties that this contains the executable
  1922. # as it has been executed by the calling process
  1923. if len(result) > 0 and result[0] != DirSep: # not an absolute path?
  1924. # iterate over any path in the $PATH environment variable
  1925. for p in split(string(getEnv("PATH")), {PathSep}):
  1926. var x = joinPath(p, result)
  1927. if existsFile(x): return x
  1928. else:
  1929. result = ""
  1930. when defined(macosx):
  1931. type
  1932. cuint32* {.importc: "unsigned int", nodecl.} = int
  1933. ## This is the same as the type ``uint32_t`` in *C*.
  1934. # a really hacky solution: since we like to include 2 headers we have to
  1935. # define two procs which in reality are the same
  1936. proc getExecPath1(c: cstring, size: var cuint32) {.
  1937. importc: "_NSGetExecutablePath", header: "<sys/param.h>".}
  1938. proc getExecPath2(c: cstring, size: var cuint32): bool {.
  1939. importc: "_NSGetExecutablePath", header: "<mach-o/dyld.h>".}
  1940. when defined(haiku):
  1941. const
  1942. PATH_MAX = 1024
  1943. B_FIND_PATH_IMAGE_PATH = 1000
  1944. proc find_path(codePointer: pointer, baseDirectory: cint, subPath: cstring,
  1945. pathBuffer: cstring, bufferSize: csize): int32
  1946. {.importc, header: "<FindDirectory.h>".}
  1947. proc getApplHaiku(): string =
  1948. result = newString(PATH_MAX)
  1949. if find_path(nil, B_FIND_PATH_IMAGE_PATH, nil, result, PATH_MAX) == 0:
  1950. let realLen = len(cstring(result))
  1951. setLen(result, realLen)
  1952. else:
  1953. result = ""
  1954. proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect], noNimScript.} =
  1955. ## Returns the filename of the application's executable.
  1956. ##
  1957. ## This procedure will resolve symlinks.
  1958. # Linux: /proc/<pid>/exe
  1959. # Solaris:
  1960. # /proc/<pid>/object/a.out (filename only)
  1961. # /proc/<pid>/path/a.out (complete pathname)
  1962. when defined(windows):
  1963. var bufsize = int32(MAX_PATH)
  1964. when useWinUnicode:
  1965. var buf = newWideCString("", bufsize)
  1966. while true:
  1967. var L = getModuleFileNameW(0, buf, bufsize)
  1968. if L == 0'i32:
  1969. result = "" # error!
  1970. break
  1971. elif L > bufsize:
  1972. buf = newWideCString("", L)
  1973. bufsize = L
  1974. else:
  1975. result = buf$L
  1976. break
  1977. else:
  1978. result = newString(bufsize)
  1979. while true:
  1980. var L = getModuleFileNameA(0, result, bufsize)
  1981. if L == 0'i32:
  1982. result = "" # error!
  1983. break
  1984. elif L > bufsize:
  1985. result = newString(L)
  1986. bufsize = L
  1987. else:
  1988. setLen(result, L)
  1989. break
  1990. elif defined(macosx):
  1991. var size: cuint32
  1992. getExecPath1(nil, size)
  1993. result = newString(int(size))
  1994. if getExecPath2(result, size):
  1995. result = "" # error!
  1996. if result.len > 0:
  1997. result = result.expandFilename
  1998. else:
  1999. when defined(linux) or defined(aix) or defined(netbsd):
  2000. result = getApplAux("/proc/self/exe")
  2001. elif defined(solaris):
  2002. result = getApplAux("/proc/" & $getpid() & "/path/a.out")
  2003. elif defined(genode) or defined(nintendoswitch):
  2004. raiseOSError(OSErrorCode(-1), "POSIX command line not supported")
  2005. elif defined(freebsd) or defined(dragonfly):
  2006. result = getApplFreebsd()
  2007. elif defined(haiku):
  2008. result = getApplHaiku()
  2009. # little heuristic that may work on other POSIX-like systems:
  2010. if result.len == 0:
  2011. result = getApplHeuristic()
  2012. proc getAppDir*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect], noNimScript.} =
  2013. ## Returns the directory of the application's executable.
  2014. result = splitFile(getAppFilename()).dir
  2015. proc sleep*(milsecs: int) {.rtl, extern: "nos$1", tags: [TimeEffect], noNimScript.} =
  2016. ## sleeps `milsecs` milliseconds.
  2017. when defined(windows):
  2018. winlean.sleep(int32(milsecs))
  2019. else:
  2020. var a, b: Timespec
  2021. a.tv_sec = posix.Time(milsecs div 1000)
  2022. a.tv_nsec = (milsecs mod 1000) * 1000 * 1000
  2023. discard posix.nanosleep(a, b)
  2024. proc getFileSize*(file: string): BiggestInt {.rtl, extern: "nos$1",
  2025. tags: [ReadIOEffect], noNimScript.} =
  2026. ## returns the file size of `file` (in bytes). An ``OSError`` exception is
  2027. ## raised in case of an error.
  2028. when defined(windows):
  2029. var a: WIN32_FIND_DATA
  2030. var resA = findFirstFile(file, a)
  2031. if resA == -1: raiseOSError(osLastError())
  2032. result = rdFileSize(a)
  2033. findClose(resA)
  2034. else:
  2035. var f: File
  2036. if open(f, file):
  2037. result = getFileSize(f)
  2038. close(f)
  2039. else: raiseOSError(osLastError())
  2040. when defined(Windows) or defined(nimscript):
  2041. type
  2042. DeviceId* = int32
  2043. FileId* = int64
  2044. else:
  2045. type
  2046. DeviceId* = Dev
  2047. FileId* = Ino
  2048. type
  2049. FileInfo* = object
  2050. ## Contains information associated with a file object.
  2051. id*: tuple[device: DeviceId, file: FileId] # Device and file id.
  2052. kind*: PathComponent # Kind of file object - directory, symlink, etc.
  2053. size*: BiggestInt # Size of file.
  2054. permissions*: set[FilePermission] # File permissions
  2055. linkCount*: BiggestInt # Number of hard links the file object has.
  2056. lastAccessTime*: times.Time # Time file was last accessed.
  2057. lastWriteTime*: times.Time # Time file was last modified/written to.
  2058. creationTime*: times.Time # Time file was created. Not supported on all systems!
  2059. template rawToFormalFileInfo(rawInfo, path, formalInfo): untyped =
  2060. ## Transforms the native file info structure into the one nim uses.
  2061. ## 'rawInfo' is either a 'BY_HANDLE_FILE_INFORMATION' structure on Windows,
  2062. ## or a 'Stat' structure on posix
  2063. when defined(Windows):
  2064. template merge(a, b): untyped = a or (b shl 32)
  2065. formalInfo.id.device = rawInfo.dwVolumeSerialNumber
  2066. formalInfo.id.file = merge(rawInfo.nFileIndexLow, rawInfo.nFileIndexHigh)
  2067. formalInfo.size = merge(rawInfo.nFileSizeLow, rawInfo.nFileSizeHigh)
  2068. formalInfo.linkCount = rawInfo.nNumberOfLinks
  2069. formalInfo.lastAccessTime = fromWinTime(rdFileTime(rawInfo.ftLastAccessTime))
  2070. formalInfo.lastWriteTime = fromWinTime(rdFileTime(rawInfo.ftLastWriteTime))
  2071. formalInfo.creationTime = fromWinTime(rdFileTime(rawInfo.ftCreationTime))
  2072. # Retrieve basic permissions
  2073. if (rawInfo.dwFileAttributes and FILE_ATTRIBUTE_READONLY) != 0'i32:
  2074. formalInfo.permissions = {fpUserExec, fpUserRead, fpGroupExec,
  2075. fpGroupRead, fpOthersExec, fpOthersRead}
  2076. else:
  2077. result.permissions = {fpUserExec..fpOthersRead}
  2078. # Retrieve basic file kind
  2079. result.kind = pcFile
  2080. if (rawInfo.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) != 0'i32:
  2081. formalInfo.kind = pcDir
  2082. if (rawInfo.dwFileAttributes and FILE_ATTRIBUTE_REPARSE_POINT) != 0'i32:
  2083. formalInfo.kind = succ(result.kind)
  2084. else:
  2085. template checkAndIncludeMode(rawMode, formalMode: untyped) =
  2086. if (rawInfo.st_mode and rawMode) != 0'i32:
  2087. formalInfo.permissions.incl(formalMode)
  2088. formalInfo.id = (rawInfo.st_dev, rawInfo.st_ino)
  2089. formalInfo.size = rawInfo.st_size
  2090. formalInfo.linkCount = rawInfo.st_Nlink.BiggestInt
  2091. formalInfo.lastAccessTime = rawInfo.st_atim.toTime
  2092. formalInfo.lastWriteTime = rawInfo.st_mtim.toTime
  2093. formalInfo.creationTime = rawInfo.st_ctim.toTime
  2094. result.permissions = {}
  2095. checkAndIncludeMode(S_IRUSR, fpUserRead)
  2096. checkAndIncludeMode(S_IWUSR, fpUserWrite)
  2097. checkAndIncludeMode(S_IXUSR, fpUserExec)
  2098. checkAndIncludeMode(S_IRGRP, fpGroupRead)
  2099. checkAndIncludeMode(S_IWGRP, fpGroupWrite)
  2100. checkAndIncludeMode(S_IXGRP, fpGroupExec)
  2101. checkAndIncludeMode(S_IROTH, fpOthersRead)
  2102. checkAndIncludeMode(S_IWOTH, fpOthersWrite)
  2103. checkAndIncludeMode(S_IXOTH, fpOthersExec)
  2104. formalInfo.kind = pcFile
  2105. if S_ISDIR(rawInfo.st_mode):
  2106. formalInfo.kind = pcDir
  2107. elif S_ISLNK(rawInfo.st_mode):
  2108. assert(path != "") # symlinks can't occur for file handles
  2109. formalInfo.kind = getSymlinkFileKind(path)
  2110. proc getFileInfo*(handle: FileHandle): FileInfo {.noNimScript.} =
  2111. ## Retrieves file information for the file object represented by the given
  2112. ## handle.
  2113. ##
  2114. ## If the information cannot be retrieved, such as when the file handle
  2115. ## is invalid, an error will be thrown.
  2116. # Done: ID, Kind, Size, Permissions, Link Count
  2117. when defined(Windows):
  2118. var rawInfo: BY_HANDLE_FILE_INFORMATION
  2119. # We have to use the super special '_get_osfhandle' call (wrapped above)
  2120. # To transform the C file descripter to a native file handle.
  2121. var realHandle = get_osfhandle(handle)
  2122. if getFileInformationByHandle(realHandle, addr rawInfo) == 0:
  2123. raiseOSError(osLastError())
  2124. rawToFormalFileInfo(rawInfo, "", result)
  2125. else:
  2126. var rawInfo: Stat
  2127. if fstat(handle, rawInfo) < 0'i32:
  2128. raiseOSError(osLastError())
  2129. rawToFormalFileInfo(rawInfo, "", result)
  2130. proc getFileInfo*(file: File): FileInfo {.noNimScript.} =
  2131. if file.isNil:
  2132. raise newException(IOError, "File is nil")
  2133. result = getFileInfo(file.getFileHandle())
  2134. proc getFileInfo*(path: string, followSymlink = true): FileInfo {.noNimScript.} =
  2135. ## Retrieves file information for the file object pointed to by `path`.
  2136. ##
  2137. ## Due to intrinsic differences between operating systems, the information
  2138. ## contained by the returned `FileInfo` structure will be slightly different
  2139. ## across platforms, and in some cases, incomplete or inaccurate.
  2140. ##
  2141. ## When `followSymlink` is true, symlinks are followed and the information
  2142. ## retrieved is information related to the symlink's target. Otherwise,
  2143. ## information on the symlink itself is retrieved.
  2144. ##
  2145. ## If the information cannot be retrieved, such as when the path doesn't
  2146. ## exist, or when permission restrictions prevent the program from retrieving
  2147. ## file information, an error will be thrown.
  2148. when defined(Windows):
  2149. var
  2150. handle = openHandle(path, followSymlink)
  2151. rawInfo: BY_HANDLE_FILE_INFORMATION
  2152. if handle == INVALID_HANDLE_VALUE:
  2153. raiseOSError(osLastError())
  2154. if getFileInformationByHandle(handle, addr rawInfo) == 0:
  2155. raiseOSError(osLastError())
  2156. rawToFormalFileInfo(rawInfo, path, result)
  2157. discard closeHandle(handle)
  2158. else:
  2159. var rawInfo: Stat
  2160. if followSymlink:
  2161. if stat(path, rawInfo) < 0'i32:
  2162. raiseOSError(osLastError())
  2163. else:
  2164. if lstat(path, rawInfo) < 0'i32:
  2165. raiseOSError(osLastError())
  2166. rawToFormalFileInfo(rawInfo, path, result)
  2167. proc isHidden*(path: string): bool {.noNimScript.} =
  2168. ## Determines whether ``path`` is hidden or not, using this
  2169. ## reference https://en.wikipedia.org/wiki/Hidden_file_and_hidden_directory
  2170. ##
  2171. ## On Windows: returns true if it exists and its "hidden" attribute is set.
  2172. ##
  2173. ## On posix: returns true if ``lastPathPart(path)`` starts with ``.`` and is
  2174. ## not ``.`` or ``..``. Note: paths are not normalized to determine `isHidden`.
  2175. runnableExamples:
  2176. when defined(posix):
  2177. doAssert ".foo".isHidden
  2178. doAssert: not ".foo/bar".isHidden
  2179. doAssert: not ".".isHidden
  2180. doAssert: not "..".isHidden
  2181. doAssert: not "".isHidden
  2182. doAssert ".foo/".isHidden
  2183. when defined(Windows):
  2184. when useWinUnicode:
  2185. wrapUnary(attributes, getFileAttributesW, path)
  2186. else:
  2187. var attributes = getFileAttributesA(path)
  2188. if attributes != -1'i32:
  2189. result = (attributes and FILE_ATTRIBUTE_HIDDEN) != 0'i32
  2190. else:
  2191. let fileName = lastPathPart(path)
  2192. result = len(fileName) >= 2 and fileName[0] == '.' and fileName != ".."
  2193. proc getCurrentProcessId*(): int {.noNimScript.} =
  2194. ## return current process ID. See also ``osproc.processID(p: Process)``.
  2195. when defined(windows):
  2196. proc GetCurrentProcessId(): DWORD {.stdcall, dynlib: "kernel32",
  2197. importc: "GetCurrentProcessId".}
  2198. result = GetCurrentProcessId().int
  2199. else:
  2200. result = getpid()
  2201. {.pop.}
  2202. proc setLastModificationTime*(file: string, t: times.Time) {.noNimScript.} =
  2203. ## Sets the `file`'s last modification time. `OSError` is raised in case of
  2204. ## an error.
  2205. when defined(posix):
  2206. let unixt = posix.Time(t.toUnix)
  2207. let micro = convert(Nanoseconds, Microseconds, t.nanosecond)
  2208. var timevals = [Timeval(tv_sec: unixt, tv_usec: micro),
  2209. Timeval(tv_sec: unixt, tv_usec: micro)] # [last access, last modification]
  2210. if utimes(file, timevals.addr) != 0: raiseOSError(osLastError())
  2211. else:
  2212. let h = openHandle(path = file, writeAccess = true)
  2213. if h == INVALID_HANDLE_VALUE: raiseOSError(osLastError())
  2214. var ft = t.toWinTime.toFILETIME
  2215. let res = setFileTime(h, nil, nil, ft.addr)
  2216. discard h.closeHandle
  2217. if res == 0'i32: raiseOSError(osLastError())
  2218. when isMainModule:
  2219. assert quoteShellWindows("aaa") == "aaa"
  2220. assert quoteShellWindows("aaa\"") == "aaa\\\""
  2221. assert quoteShellWindows("") == "\"\""
  2222. assert quoteShellPosix("aaa") == "aaa"
  2223. assert quoteShellPosix("aaa a") == "'aaa a'"
  2224. assert quoteShellPosix("") == "''"
  2225. assert quoteShellPosix("a'a") == "'a'\"'\"'a'"
  2226. when defined(posix):
  2227. assert quoteShell("") == "''"
  2228. block normalizePathEndTest:
  2229. # handle edge cases correctly: shouldn't affect whether path is
  2230. # absolute/relative
  2231. doAssert "".normalizePathEnd(true) == ""
  2232. doAssert "".normalizePathEnd(false) == ""
  2233. doAssert "/".normalizePathEnd(true) == $DirSep
  2234. doAssert "/".normalizePathEnd(false) == $DirSep
  2235. when defined(posix):
  2236. doAssert "//".normalizePathEnd(false) == "/"
  2237. doAssert "foo.bar//".normalizePathEnd == "foo.bar"
  2238. doAssert "bar//".normalizePathEnd(trailingSep = true) == "bar/"
  2239. when defined(Windows):
  2240. doAssert r"C:\foo\\".normalizePathEnd == r"C:\foo"
  2241. doAssert r"C:\foo".normalizePathEnd(trailingSep = true) == r"C:\foo\"
  2242. # this one is controversial: we could argue for returning `D:\` instead,
  2243. # but this is simplest.
  2244. doAssert r"D:\".normalizePathEnd == r"D:"
  2245. doAssert r"E:/".normalizePathEnd(trailingSep = true) == r"E:\"
  2246. doAssert "/".normalizePathEnd == r"\"