os.nim 58 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630
  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.}
  13. {.push debugger: off.}
  14. include "system/inclrtl"
  15. import
  16. strutils, times
  17. when defined(windows):
  18. import winlean
  19. elif defined(posix):
  20. import posix
  21. else:
  22. {.error: "OS module not ported to your operating system!".}
  23. import ospaths
  24. export ospaths
  25. proc c_remove(filename: cstring): cint {.
  26. importc: "remove", header: "<stdio.h>".}
  27. proc c_rename(oldname, newname: cstring): cint {.
  28. importc: "rename", header: "<stdio.h>".}
  29. proc c_system(cmd: cstring): cint {.
  30. importc: "system", header: "<stdlib.h>".}
  31. proc c_strlen(a: cstring): cint {.
  32. importc: "strlen", header: "<string.h>", noSideEffect.}
  33. proc c_free(p: pointer) {.
  34. importc: "free", header: "<stdlib.h>".}
  35. when defined(windows):
  36. when useWinUnicode:
  37. template wrapUnary(varname, winApiProc, arg: untyped) =
  38. var varname = winApiProc(newWideCString(arg))
  39. template wrapBinary(varname, winApiProc, arg, arg2: untyped) =
  40. var varname = winApiProc(newWideCString(arg), arg2)
  41. proc findFirstFile(a: string, b: var WIN32_FIND_DATA): Handle =
  42. result = findFirstFileW(newWideCString(a), b)
  43. template findNextFile(a, b: untyped): untyped = findNextFileW(a, b)
  44. template getCommandLine(): untyped = getCommandLineW()
  45. template getFilename(f: untyped): untyped =
  46. $cast[WideCString](addr(f.cFilename[0]))
  47. else:
  48. template findFirstFile(a, b: untyped): untyped = findFirstFileA(a, b)
  49. template findNextFile(a, b: untyped): untyped = findNextFileA(a, b)
  50. template getCommandLine(): untyped = getCommandLineA()
  51. template getFilename(f: untyped): untyped = $f.cFilename
  52. proc skipFindData(f: WIN32_FIND_DATA): bool {.inline.} =
  53. # Note - takes advantage of null delimiter in the cstring
  54. const dot = ord('.')
  55. result = f.cFileName[0].int == dot and (f.cFileName[1].int == 0 or
  56. f.cFileName[1].int == dot and f.cFileName[2].int == 0)
  57. proc existsFile*(filename: string): bool {.rtl, extern: "nos$1",
  58. tags: [ReadDirEffect].} =
  59. ## Returns true if the file exists, false otherwise.
  60. when defined(windows):
  61. when useWinUnicode:
  62. wrapUnary(a, getFileAttributesW, filename)
  63. else:
  64. var a = getFileAttributesA(filename)
  65. if a != -1'i32:
  66. result = (a and FILE_ATTRIBUTE_DIRECTORY) == 0'i32
  67. else:
  68. var res: Stat
  69. return stat(filename, res) >= 0'i32 and S_ISREG(res.st_mode)
  70. proc existsDir*(dir: string): bool {.rtl, extern: "nos$1", tags: [ReadDirEffect].} =
  71. ## Returns true iff the directory `dir` exists. If `dir` is a file, false
  72. ## is returned.
  73. when defined(windows):
  74. when useWinUnicode:
  75. wrapUnary(a, getFileAttributesW, dir)
  76. else:
  77. var a = getFileAttributesA(dir)
  78. if a != -1'i32:
  79. result = (a and FILE_ATTRIBUTE_DIRECTORY) != 0'i32
  80. else:
  81. var res: Stat
  82. return stat(dir, res) >= 0'i32 and S_ISDIR(res.st_mode)
  83. proc symlinkExists*(link: string): bool {.rtl, extern: "nos$1",
  84. tags: [ReadDirEffect].} =
  85. ## Returns true iff the symlink `link` exists. Will return true
  86. ## regardless of whether the link points to a directory or file.
  87. when defined(windows):
  88. when useWinUnicode:
  89. wrapUnary(a, getFileAttributesW, link)
  90. else:
  91. var a = getFileAttributesA(link)
  92. if a != -1'i32:
  93. result = (a and FILE_ATTRIBUTE_REPARSE_POINT) != 0'i32
  94. else:
  95. var res: Stat
  96. return lstat(link, res) >= 0'i32 and S_ISLNK(res.st_mode)
  97. proc fileExists*(filename: string): bool {.inline.} =
  98. ## Synonym for existsFile
  99. existsFile(filename)
  100. proc dirExists*(dir: string): bool {.inline.} =
  101. ## Synonym for existsDir
  102. existsDir(dir)
  103. when not defined(windows):
  104. proc checkSymlink(path: string): bool =
  105. var rawInfo: Stat
  106. if lstat(path, rawInfo) < 0'i32: result = false
  107. else: result = S_ISLNK(rawInfo.st_mode)
  108. const
  109. ExeExts* = when defined(windows): ["exe", "cmd", "bat"] else: [""] ## \
  110. ## platform specific file extension for executables. On Windows
  111. ## ``["exe", "cmd", "bat"]``, on Posix ``[""]``.
  112. proc findExe*(exe: string, followSymlinks: bool = true;
  113. extensions: openarray[string]=ExeExts): string {.
  114. tags: [ReadDirEffect, ReadEnvEffect, ReadIOEffect].} =
  115. ## Searches for `exe` in the current working directory and then
  116. ## in directories listed in the ``PATH`` environment variable.
  117. ## Returns "" if the `exe` cannot be found. `exe`
  118. ## is added the `ExeExts <#ExeExts>`_ file extensions if it has none.
  119. ## If the system supports symlinks it also resolves them until it
  120. ## meets the actual file. This behavior can be disabled if desired.
  121. for ext in extensions:
  122. result = addFileExt(exe, ext)
  123. if existsFile(result): return
  124. var path = string(getEnv("PATH"))
  125. for candidate in split(path, PathSep):
  126. when defined(windows):
  127. var x = (if candidate[0] == '"' and candidate[^1] == '"':
  128. substr(candidate, 1, candidate.len-2) else: candidate) /
  129. exe
  130. else:
  131. var x = expandTilde(candidate) / exe
  132. for ext in extensions:
  133. var x = addFileExt(x, ext)
  134. if existsFile(x):
  135. when not defined(windows):
  136. while followSymlinks: # doubles as if here
  137. if x.checkSymlink:
  138. var r = newString(256)
  139. var len = readlink(x, r, 256)
  140. if len < 0:
  141. raiseOSError(osLastError())
  142. if len > 256:
  143. r = newString(len+1)
  144. len = readlink(x, r, len)
  145. setLen(r, len)
  146. if isAbsolute(r):
  147. x = r
  148. else:
  149. x = parentDir(x) / r
  150. else:
  151. break
  152. return x
  153. result = ""
  154. proc getLastModificationTime*(file: string): Time {.rtl, extern: "nos$1".} =
  155. ## Returns the `file`'s last modification time.
  156. when defined(posix):
  157. var res: Stat
  158. if stat(file, res) < 0'i32: raiseOSError(osLastError())
  159. return res.st_mtime
  160. else:
  161. var f: WIN32_FIND_DATA
  162. var h = findFirstFile(file, f)
  163. if h == -1'i32: raiseOSError(osLastError())
  164. result = winTimeToUnixTime(rdFileTime(f.ftLastWriteTime))
  165. findClose(h)
  166. proc getLastAccessTime*(file: string): Time {.rtl, extern: "nos$1".} =
  167. ## Returns the `file`'s last read or write access time.
  168. when defined(posix):
  169. var res: Stat
  170. if stat(file, res) < 0'i32: raiseOSError(osLastError())
  171. return res.st_atime
  172. else:
  173. var f: WIN32_FIND_DATA
  174. var h = findFirstFile(file, f)
  175. if h == -1'i32: raiseOSError(osLastError())
  176. result = winTimeToUnixTime(rdFileTime(f.ftLastAccessTime))
  177. findClose(h)
  178. proc getCreationTime*(file: string): Time {.rtl, extern: "nos$1".} =
  179. ## Returns the `file`'s creation time.
  180. ##
  181. ## **Note:** Under POSIX OS's, the returned time may actually be the time at
  182. ## which the file's attribute's were last modified. See
  183. ## `here <https://github.com/nim-lang/Nim/issues/1058>`_ for details.
  184. when defined(posix):
  185. var res: Stat
  186. if stat(file, res) < 0'i32: raiseOSError(osLastError())
  187. return res.st_ctime
  188. else:
  189. var f: WIN32_FIND_DATA
  190. var h = findFirstFile(file, f)
  191. if h == -1'i32: raiseOSError(osLastError())
  192. result = winTimeToUnixTime(rdFileTime(f.ftCreationTime))
  193. findClose(h)
  194. proc fileNewer*(a, b: string): bool {.rtl, extern: "nos$1".} =
  195. ## Returns true if the file `a` is newer than file `b`, i.e. if `a`'s
  196. ## modification time is later than `b`'s.
  197. when defined(posix):
  198. result = getLastModificationTime(a) - getLastModificationTime(b) >= 0
  199. # Posix's resolution sucks so, we use '>=' for posix.
  200. else:
  201. result = getLastModificationTime(a) - getLastModificationTime(b) > 0
  202. proc getCurrentDir*(): string {.rtl, extern: "nos$1", tags: [].} =
  203. ## Returns the `current working directory`:idx:.
  204. when defined(windows):
  205. var bufsize = MAX_PATH.int32
  206. when useWinUnicode:
  207. var res = newWideCString("", bufsize)
  208. while true:
  209. var L = getCurrentDirectoryW(bufsize, res)
  210. if L == 0'i32:
  211. raiseOSError(osLastError())
  212. elif L > bufsize:
  213. res = newWideCString("", L)
  214. bufsize = L
  215. else:
  216. result = res$L
  217. break
  218. else:
  219. result = newString(bufsize)
  220. while true:
  221. var L = getCurrentDirectoryA(bufsize, result)
  222. if L == 0'i32:
  223. raiseOSError(osLastError())
  224. elif L > bufsize:
  225. result = newString(L)
  226. bufsize = L
  227. else:
  228. setLen(result, L)
  229. break
  230. else:
  231. var bufsize = 1024 # should be enough
  232. result = newString(bufsize)
  233. while true:
  234. if getcwd(result, bufsize) != nil:
  235. setLen(result, c_strlen(result))
  236. break
  237. else:
  238. let err = osLastError()
  239. if err.int32 == ERANGE:
  240. bufsize = bufsize shl 1
  241. doAssert(bufsize >= 0)
  242. result = newString(bufsize)
  243. else:
  244. raiseOSError(osLastError())
  245. proc setCurrentDir*(newDir: string) {.inline, tags: [].} =
  246. ## Sets the `current working directory`:idx:; `OSError` is raised if
  247. ## `newDir` cannot been set.
  248. when defined(Windows):
  249. when useWinUnicode:
  250. if setCurrentDirectoryW(newWideCString(newDir)) == 0'i32:
  251. raiseOSError(osLastError())
  252. else:
  253. if setCurrentDirectoryA(newDir) == 0'i32: raiseOSError(osLastError())
  254. else:
  255. if chdir(newDir) != 0'i32: raiseOSError(osLastError())
  256. proc expandFilename*(filename: string): string {.rtl, extern: "nos$1",
  257. tags: [ReadDirEffect].} =
  258. ## Returns the full (`absolute`:idx:) path of the file `filename`,
  259. ## raises OSError in case of an error.
  260. when defined(windows):
  261. var bufsize = MAX_PATH.int32
  262. when useWinUnicode:
  263. var unused: WideCString = nil
  264. var res = newWideCString("", bufsize)
  265. while true:
  266. var L = getFullPathNameW(newWideCString(filename), bufsize, res, unused)
  267. if L == 0'i32:
  268. raiseOSError(osLastError())
  269. elif L > bufsize:
  270. res = newWideCString("", L)
  271. bufsize = L
  272. else:
  273. result = res$L
  274. break
  275. else:
  276. var unused: cstring = nil
  277. result = newString(bufsize)
  278. while true:
  279. var L = getFullPathNameA(filename, bufsize, result, unused)
  280. if L == 0'i32:
  281. raiseOSError(osLastError())
  282. elif L > bufsize:
  283. result = newString(L)
  284. bufsize = L
  285. else:
  286. setLen(result, L)
  287. break
  288. else:
  289. # according to Posix we don't need to allocate space for result pathname.
  290. # But we need to free return value with free(3).
  291. var r = realpath(filename, nil)
  292. if r.isNil:
  293. raiseOSError(osLastError())
  294. else:
  295. result = $r
  296. c_free(cast[pointer](r))
  297. when defined(Windows):
  298. proc openHandle(path: string, followSymlink=true): Handle =
  299. var flags = FILE_FLAG_BACKUP_SEMANTICS or FILE_ATTRIBUTE_NORMAL
  300. if not followSymlink:
  301. flags = flags or FILE_FLAG_OPEN_REPARSE_POINT
  302. when useWinUnicode:
  303. result = createFileW(
  304. newWideCString(path), 0'i32,
  305. FILE_SHARE_DELETE or FILE_SHARE_READ or FILE_SHARE_WRITE,
  306. nil, OPEN_EXISTING, flags, 0
  307. )
  308. else:
  309. result = createFileA(
  310. path, 0'i32,
  311. FILE_SHARE_DELETE or FILE_SHARE_READ or FILE_SHARE_WRITE,
  312. nil, OPEN_EXISTING, flags, 0
  313. )
  314. proc sameFile*(path1, path2: string): bool {.rtl, extern: "nos$1",
  315. tags: [ReadDirEffect].} =
  316. ## Returns true if both pathname arguments refer to the same physical
  317. ## file or directory. Raises an exception if any of the files does not
  318. ## exist or information about it can not be obtained.
  319. ##
  320. ## This proc will return true if given two alternative hard-linked or
  321. ## sym-linked paths to the same file or directory.
  322. when defined(Windows):
  323. var success = true
  324. var f1 = openHandle(path1)
  325. var f2 = openHandle(path2)
  326. var lastErr: OSErrorCode
  327. if f1 != INVALID_HANDLE_VALUE and f2 != INVALID_HANDLE_VALUE:
  328. var fi1, fi2: BY_HANDLE_FILE_INFORMATION
  329. if getFileInformationByHandle(f1, addr(fi1)) != 0 and
  330. getFileInformationByHandle(f2, addr(fi2)) != 0:
  331. result = fi1.dwVolumeSerialNumber == fi2.dwVolumeSerialNumber and
  332. fi1.nFileIndexHigh == fi2.nFileIndexHigh and
  333. fi1.nFileIndexLow == fi2.nFileIndexLow
  334. else:
  335. lastErr = osLastError()
  336. success = false
  337. else:
  338. lastErr = osLastError()
  339. success = false
  340. discard closeHandle(f1)
  341. discard closeHandle(f2)
  342. if not success: raiseOSError(lastErr)
  343. else:
  344. var a, b: Stat
  345. if stat(path1, a) < 0'i32 or stat(path2, b) < 0'i32:
  346. raiseOSError(osLastError())
  347. else:
  348. result = a.st_dev == b.st_dev and a.st_ino == b.st_ino
  349. proc sameFileContent*(path1, path2: string): bool {.rtl, extern: "nos$1",
  350. tags: [ReadIOEffect].} =
  351. ## Returns true if both pathname arguments refer to files with identical
  352. ## binary content.
  353. const
  354. bufSize = 8192 # 8K buffer
  355. var
  356. a, b: File
  357. if not open(a, path1): return false
  358. if not open(b, path2):
  359. close(a)
  360. return false
  361. var bufA = alloc(bufSize)
  362. var bufB = alloc(bufSize)
  363. while true:
  364. var readA = readBuffer(a, bufA, bufSize)
  365. var readB = readBuffer(b, bufB, bufSize)
  366. if readA != readB:
  367. result = false
  368. break
  369. if readA == 0:
  370. result = true
  371. break
  372. result = equalMem(bufA, bufB, readA)
  373. if not result: break
  374. if readA != bufSize: break # end of file
  375. dealloc(bufA)
  376. dealloc(bufB)
  377. close(a)
  378. close(b)
  379. type
  380. FilePermission* = enum ## file access permission; modelled after UNIX
  381. fpUserExec, ## execute access for the file owner
  382. fpUserWrite, ## write access for the file owner
  383. fpUserRead, ## read access for the file owner
  384. fpGroupExec, ## execute access for the group
  385. fpGroupWrite, ## write access for the group
  386. fpGroupRead, ## read access for the group
  387. fpOthersExec, ## execute access for others
  388. fpOthersWrite, ## write access for others
  389. fpOthersRead ## read access for others
  390. {.deprecated: [TFilePermission: FilePermission].}
  391. proc getFilePermissions*(filename: string): set[FilePermission] {.
  392. rtl, extern: "nos$1", tags: [ReadDirEffect].} =
  393. ## retrieves file permissions for `filename`. `OSError` is raised in case of
  394. ## an error. On Windows, only the ``readonly`` flag is checked, every other
  395. ## permission is available in any case.
  396. when defined(posix):
  397. var a: Stat
  398. if stat(filename, a) < 0'i32: raiseOSError(osLastError())
  399. result = {}
  400. if (a.st_mode and S_IRUSR) != 0'i32: result.incl(fpUserRead)
  401. if (a.st_mode and S_IWUSR) != 0'i32: result.incl(fpUserWrite)
  402. if (a.st_mode and S_IXUSR) != 0'i32: result.incl(fpUserExec)
  403. if (a.st_mode and S_IRGRP) != 0'i32: result.incl(fpGroupRead)
  404. if (a.st_mode and S_IWGRP) != 0'i32: result.incl(fpGroupWrite)
  405. if (a.st_mode and S_IXGRP) != 0'i32: result.incl(fpGroupExec)
  406. if (a.st_mode and S_IROTH) != 0'i32: result.incl(fpOthersRead)
  407. if (a.st_mode and S_IWOTH) != 0'i32: result.incl(fpOthersWrite)
  408. if (a.st_mode and S_IXOTH) != 0'i32: result.incl(fpOthersExec)
  409. else:
  410. when useWinUnicode:
  411. wrapUnary(res, getFileAttributesW, filename)
  412. else:
  413. var res = getFileAttributesA(filename)
  414. if res == -1'i32: raiseOSError(osLastError())
  415. if (res and FILE_ATTRIBUTE_READONLY) != 0'i32:
  416. result = {fpUserExec, fpUserRead, fpGroupExec, fpGroupRead,
  417. fpOthersExec, fpOthersRead}
  418. else:
  419. result = {fpUserExec..fpOthersRead}
  420. proc setFilePermissions*(filename: string, permissions: set[FilePermission]) {.
  421. rtl, extern: "nos$1", tags: [WriteDirEffect].} =
  422. ## sets the file permissions for `filename`. `OSError` is raised in case of
  423. ## an error. On Windows, only the ``readonly`` flag is changed, depending on
  424. ## ``fpUserWrite``.
  425. when defined(posix):
  426. var p = 0'i32
  427. if fpUserRead in permissions: p = p or S_IRUSR
  428. if fpUserWrite in permissions: p = p or S_IWUSR
  429. if fpUserExec in permissions: p = p or S_IXUSR
  430. if fpGroupRead in permissions: p = p or S_IRGRP
  431. if fpGroupWrite in permissions: p = p or S_IWGRP
  432. if fpGroupExec in permissions: p = p or S_IXGRP
  433. if fpOthersRead in permissions: p = p or S_IROTH
  434. if fpOthersWrite in permissions: p = p or S_IWOTH
  435. if fpOthersExec in permissions: p = p or S_IXOTH
  436. if chmod(filename, p) != 0: raiseOSError(osLastError())
  437. else:
  438. when useWinUnicode:
  439. wrapUnary(res, getFileAttributesW, filename)
  440. else:
  441. var res = getFileAttributesA(filename)
  442. if res == -1'i32: raiseOSError(osLastError())
  443. if fpUserWrite in permissions:
  444. res = res and not FILE_ATTRIBUTE_READONLY
  445. else:
  446. res = res or FILE_ATTRIBUTE_READONLY
  447. when useWinUnicode:
  448. wrapBinary(res2, setFileAttributesW, filename, res)
  449. else:
  450. var res2 = setFileAttributesA(filename, res)
  451. if res2 == - 1'i32: raiseOSError(osLastError())
  452. proc copyFile*(source, dest: string) {.rtl, extern: "nos$1",
  453. tags: [ReadIOEffect, WriteIOEffect].} =
  454. ## Copies a file from `source` to `dest`.
  455. ##
  456. ## If this fails, `OSError` is raised. On the Windows platform this proc will
  457. ## copy the source file's attributes into dest. On other platforms you need
  458. ## to use `getFilePermissions() <#getFilePermissions>`_ and
  459. ## `setFilePermissions() <#setFilePermissions>`_ to copy them by hand (or use
  460. ## the convenience `copyFileWithPermissions() <#copyFileWithPermissions>`_
  461. ## proc), otherwise `dest` will inherit the default permissions of a newly
  462. ## created file for the user. If `dest` already exists, the file attributes
  463. ## will be preserved and the content overwritten.
  464. when defined(Windows):
  465. when useWinUnicode:
  466. let s = newWideCString(source)
  467. let d = newWideCString(dest)
  468. if copyFileW(s, d, 0'i32) == 0'i32: raiseOSError(osLastError())
  469. else:
  470. if copyFileA(source, dest, 0'i32) == 0'i32: raiseOSError(osLastError())
  471. else:
  472. # generic version of copyFile which works for any platform:
  473. const bufSize = 8000 # better for memory manager
  474. var d, s: File
  475. if not open(s, source): raiseOSError(osLastError())
  476. if not open(d, dest, fmWrite):
  477. close(s)
  478. raiseOSError(osLastError())
  479. var buf = alloc(bufSize)
  480. while true:
  481. var bytesread = readBuffer(s, buf, bufSize)
  482. if bytesread > 0:
  483. var byteswritten = writeBuffer(d, buf, bytesread)
  484. if bytesread != byteswritten:
  485. dealloc(buf)
  486. close(s)
  487. close(d)
  488. raiseOSError(osLastError())
  489. if bytesread != bufSize: break
  490. dealloc(buf)
  491. close(s)
  492. flushFile(d)
  493. close(d)
  494. when not declared(ENOENT) and not defined(Windows):
  495. when NoFakeVars:
  496. const ENOENT = cint(2) # 2 on most systems including Solaris
  497. else:
  498. var ENOENT {.importc, header: "<errno.h>".}: cint
  499. when defined(Windows):
  500. when useWinUnicode:
  501. template deleteFile(file: untyped): untyped = deleteFileW(file)
  502. template setFileAttributes(file, attrs: untyped): untyped =
  503. setFileAttributesW(file, attrs)
  504. else:
  505. template deleteFile(file: untyped): untyped = deleteFileA(file)
  506. template setFileAttributes(file, attrs: untyped): untyped =
  507. setFileAttributesA(file, attrs)
  508. proc tryRemoveFile*(file: string): bool {.rtl, extern: "nos$1", tags: [WriteDirEffect].} =
  509. ## Removes the `file`. If this fails, returns `false`. This does not fail
  510. ## if the file never existed in the first place.
  511. ## On Windows, ignores the read-only attribute.
  512. result = true
  513. when defined(Windows):
  514. when useWinUnicode:
  515. let f = newWideCString(file)
  516. else:
  517. let f = file
  518. if deleteFile(f) == 0:
  519. result = false
  520. let err = getLastError()
  521. if err == ERROR_FILE_NOT_FOUND or err == ERROR_PATH_NOT_FOUND:
  522. result = true
  523. elif err == ERROR_ACCESS_DENIED and
  524. setFileAttributes(f, FILE_ATTRIBUTE_NORMAL) != 0 and
  525. deleteFile(f) != 0:
  526. result = true
  527. else:
  528. if c_remove(file) != 0'i32 and errno != ENOENT:
  529. result = false
  530. proc removeFile*(file: string) {.rtl, extern: "nos$1", tags: [WriteDirEffect].} =
  531. ## Removes the `file`. If this fails, `OSError` is raised. This does not fail
  532. ## if the file never existed in the first place.
  533. ## On Windows, ignores the read-only attribute.
  534. if not tryRemoveFile(file):
  535. when defined(Windows):
  536. raiseOSError(osLastError())
  537. else:
  538. raiseOSError(osLastError(), $strerror(errno))
  539. proc tryMoveFSObject(source, dest: string): bool =
  540. ## Moves a file or directory from `source` to `dest`. Returns false in case
  541. ## of `EXDEV` error. In case of other errors `OSError` is raised. Returns
  542. ## true in case of success.
  543. when defined(Windows):
  544. when useWinUnicode:
  545. let s = newWideCString(source)
  546. let d = newWideCString(dest)
  547. if moveFileExW(s, d, MOVEFILE_COPY_ALLOWED) == 0'i32: raiseOSError(osLastError())
  548. else:
  549. if moveFileExA(source, dest, MOVEFILE_COPY_ALLOWED) == 0'i32: raiseOSError(osLastError())
  550. else:
  551. if c_rename(source, dest) != 0'i32:
  552. let err = osLastError()
  553. if err == EXDEV.OSErrorCode:
  554. return false
  555. else:
  556. raiseOSError(err, $strerror(errno))
  557. return true
  558. proc moveFile*(source, dest: string) {.rtl, extern: "nos$1",
  559. tags: [ReadIOEffect, WriteIOEffect].} =
  560. ## Moves a file from `source` to `dest`. If this fails, `OSError` is raised.
  561. if not tryMoveFSObject(source, dest):
  562. when not defined(windows):
  563. # Fallback to copy & del
  564. copyFile(source, dest)
  565. try:
  566. removeFile(source)
  567. except:
  568. discard tryRemoveFile(dest)
  569. raise
  570. proc execShellCmd*(command: string): int {.rtl, extern: "nos$1",
  571. tags: [ExecIOEffect].} =
  572. ## Executes a `shell command`:idx:.
  573. ##
  574. ## Command has the form 'program args' where args are the command
  575. ## line arguments given to program. The proc returns the error code
  576. ## of the shell when it has finished. The proc does not return until
  577. ## the process has finished. To execute a program without having a
  578. ## shell involved, use the `execProcess` proc of the `osproc`
  579. ## module.
  580. when defined(linux):
  581. result = c_system(command) shr 8
  582. else:
  583. result = c_system(command)
  584. # Templates for filtering directories and files
  585. when defined(windows):
  586. template isDir(f: WIN32_FIND_DATA): bool =
  587. (f.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) != 0'i32
  588. template isFile(f: WIN32_FIND_DATA): bool =
  589. not isDir(f)
  590. else:
  591. template isDir(f: string): bool =
  592. dirExists(f)
  593. template isFile(f: string): bool =
  594. fileExists(f)
  595. template defaultWalkFilter(item): bool =
  596. ## Walk filter used to return true on both
  597. ## files and directories
  598. true
  599. template walkCommon(pattern: string, filter) =
  600. ## Common code for getting the files and directories with the
  601. ## specified `pattern`
  602. when defined(windows):
  603. var
  604. f: WIN32_FIND_DATA
  605. res: int
  606. res = findFirstFile(pattern, f)
  607. if res != -1:
  608. defer: findClose(res)
  609. let dotPos = searchExtPos(pattern)
  610. while true:
  611. if not skipFindData(f) and filter(f):
  612. # Windows bug/gotcha: 't*.nim' matches 'tfoo.nims' -.- so we check
  613. # that the file extensions have the same length ...
  614. let ff = getFilename(f)
  615. let idx = ff.len - pattern.len + dotPos
  616. if dotPos < 0 or idx >= ff.len or ff[idx] == '.' or
  617. pattern[dotPos+1] == '*':
  618. yield splitFile(pattern).dir / extractFilename(ff)
  619. if findNextFile(res, f) == 0'i32: break
  620. else: # here we use glob
  621. var
  622. f: Glob
  623. res: int
  624. f.gl_offs = 0
  625. f.gl_pathc = 0
  626. f.gl_pathv = nil
  627. res = glob(pattern, 0, nil, addr(f))
  628. defer: globfree(addr(f))
  629. if res == 0:
  630. for i in 0.. f.gl_pathc - 1:
  631. assert(f.gl_pathv[i] != nil)
  632. let path = $f.gl_pathv[i]
  633. if filter(path):
  634. yield path
  635. iterator walkPattern*(pattern: string): string {.tags: [ReadDirEffect].} =
  636. ## Iterate over all the files and directories that match the `pattern`.
  637. ## On POSIX this uses the `glob`:idx: call.
  638. ##
  639. ## `pattern` is OS dependent, but at least the "\*.ext"
  640. ## notation is supported.
  641. walkCommon(pattern, defaultWalkFilter)
  642. iterator walkFiles*(pattern: string): string {.tags: [ReadDirEffect].} =
  643. ## Iterate over all the files that match the `pattern`. On POSIX this uses
  644. ## the `glob`:idx: call.
  645. ##
  646. ## `pattern` is OS dependent, but at least the "\*.ext"
  647. ## notation is supported.
  648. walkCommon(pattern, isFile)
  649. iterator walkDirs*(pattern: string): string {.tags: [ReadDirEffect].} =
  650. ## Iterate over all the directories that match the `pattern`.
  651. ## On POSIX this uses the `glob`:idx: call.
  652. ##
  653. ## `pattern` is OS dependent, but at least the "\*.ext"
  654. ## notation is supported.
  655. walkCommon(pattern, isDir)
  656. type
  657. PathComponent* = enum ## Enumeration specifying a path component.
  658. pcFile, ## path refers to a file
  659. pcLinkToFile, ## path refers to a symbolic link to a file
  660. pcDir, ## path refers to a directory
  661. pcLinkToDir ## path refers to a symbolic link to a directory
  662. {.deprecated: [TPathComponent: PathComponent].}
  663. when defined(posix):
  664. proc getSymlinkFileKind(path: string): PathComponent =
  665. # Helper function.
  666. var s: Stat
  667. assert(path != "")
  668. if stat(path, s) < 0'i32:
  669. raiseOSError(osLastError())
  670. if S_ISDIR(s.st_mode):
  671. result = pcLinkToDir
  672. else:
  673. result = pcLinkToFile
  674. proc staticWalkDir(dir: string; relative: bool): seq[
  675. tuple[kind: PathComponent, path: string]] =
  676. discard
  677. iterator walkDir*(dir: string; relative=false): tuple[kind: PathComponent, path: string] {.
  678. tags: [ReadDirEffect].} =
  679. ## walks over the directory `dir` and yields for each directory or file in
  680. ## `dir`. The component type and full path for each item is returned.
  681. ## Walking is not recursive. If ``relative`` is true the resulting path is
  682. ## shortened to be relative to ``dir``.
  683. ## Example: This directory structure::
  684. ## dirA / dirB / fileB1.txt
  685. ## / dirC
  686. ## / fileA1.txt
  687. ## / fileA2.txt
  688. ##
  689. ## and this code:
  690. ##
  691. ## .. code-block:: Nim
  692. ## for kind, path in walkDir("dirA"):
  693. ## echo(path)
  694. ##
  695. ## produces this output (but not necessarily in this order!)::
  696. ## dirA/dirB
  697. ## dirA/dirC
  698. ## dirA/fileA1.txt
  699. ## dirA/fileA2.txt
  700. when nimvm:
  701. for k, v in items(staticWalkDir(dir, relative)):
  702. yield (k, v)
  703. else:
  704. when defined(windows):
  705. var f: WIN32_FIND_DATA
  706. var h = findFirstFile(dir / "*", f)
  707. if h != -1:
  708. defer: findClose(h)
  709. while true:
  710. var k = pcFile
  711. if not skipFindData(f):
  712. if (f.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) != 0'i32:
  713. k = pcDir
  714. if (f.dwFileAttributes and FILE_ATTRIBUTE_REPARSE_POINT) != 0'i32:
  715. k = succ(k)
  716. let xx = if relative: extractFilename(getFilename(f))
  717. else: dir / extractFilename(getFilename(f))
  718. yield (k, xx)
  719. if findNextFile(h, f) == 0'i32: break
  720. else:
  721. var d = opendir(dir)
  722. if d != nil:
  723. defer: discard closedir(d)
  724. while true:
  725. var x = readdir(d)
  726. if x == nil: break
  727. var y = $x.d_name
  728. if y != "." and y != "..":
  729. var s: Stat
  730. if not relative:
  731. y = dir / y
  732. var k = pcFile
  733. when defined(linux) or defined(macosx) or defined(bsd) or defined(genode):
  734. if x.d_type != DT_UNKNOWN:
  735. if x.d_type == DT_DIR: k = pcDir
  736. if x.d_type == DT_LNK:
  737. if dirExists(y): k = pcLinkToDir
  738. else: k = pcLinkToFile
  739. yield (k, y)
  740. continue
  741. if lstat(y, s) < 0'i32: break
  742. if S_ISDIR(s.st_mode):
  743. k = pcDir
  744. elif S_ISLNK(s.st_mode):
  745. k = getSymlinkFileKind(y)
  746. yield (k, y)
  747. iterator walkDirRec*(dir: string, filter={pcFile, pcDir}): string {.
  748. tags: [ReadDirEffect].} =
  749. ## Recursively walks over the directory `dir` and yields for each file in `dir`.
  750. ## The full path for each file is returned. Directories are not returned.
  751. ## **Warning**:
  752. ## Modifying the directory structure while the iterator
  753. ## is traversing may result in undefined behavior!
  754. ##
  755. ## Walking is recursive. `filter` controls the behaviour of the iterator:
  756. ##
  757. ## --------------------- ---------------------------------------------
  758. ## filter meaning
  759. ## --------------------- ---------------------------------------------
  760. ## ``pcFile`` yield real files
  761. ## ``pcLinkToFile`` yield symbolic links to files
  762. ## ``pcDir`` follow real directories
  763. ## ``pcLinkToDir`` follow symbolic links to directories
  764. ## --------------------- ---------------------------------------------
  765. ##
  766. var stack = @[dir]
  767. while stack.len > 0:
  768. for k,p in walkDir(stack.pop()):
  769. if k in filter:
  770. case k
  771. of pcFile, pcLinkToFile: yield p
  772. of pcDir, pcLinkToDir: stack.add(p)
  773. proc rawRemoveDir(dir: string) =
  774. when defined(windows):
  775. when useWinUnicode:
  776. wrapUnary(res, removeDirectoryW, dir)
  777. else:
  778. var res = removeDirectoryA(dir)
  779. let lastError = osLastError()
  780. if res == 0'i32 and lastError.int32 != 3'i32 and
  781. lastError.int32 != 18'i32 and lastError.int32 != 2'i32:
  782. raiseOSError(lastError)
  783. else:
  784. if rmdir(dir) != 0'i32 and errno != ENOENT: raiseOSError(osLastError())
  785. proc removeDir*(dir: string) {.rtl, extern: "nos$1", tags: [
  786. WriteDirEffect, ReadDirEffect], benign.} =
  787. ## Removes the directory `dir` including all subdirectories and files
  788. ## in `dir` (recursively).
  789. ##
  790. ## If this fails, `OSError` is raised. This does not fail if the directory never
  791. ## existed in the first place.
  792. for kind, path in walkDir(dir):
  793. case kind
  794. of pcFile, pcLinkToFile, pcLinkToDir: removeFile(path)
  795. of pcDir: removeDir(path)
  796. rawRemoveDir(dir)
  797. proc rawCreateDir(dir: string): bool =
  798. # Try to create one directory (not the whole path).
  799. # returns `true` for success, `false` if the path has previously existed
  800. #
  801. # This is a thin wrapper over mkDir (or alternatives on other systems),
  802. # so in case of a pre-existing path we don't check that it is a directory.
  803. when defined(solaris):
  804. let res = mkdir(dir, 0o777)
  805. if res == 0'i32:
  806. result = true
  807. elif errno in {EEXIST, ENOSYS}:
  808. result = false
  809. else:
  810. raiseOSError(osLastError())
  811. elif defined(posix):
  812. let res = mkdir(dir, 0o777)
  813. if res == 0'i32:
  814. result = true
  815. elif errno == EEXIST:
  816. result = false
  817. else:
  818. echo res
  819. raiseOSError(osLastError())
  820. else:
  821. when useWinUnicode:
  822. wrapUnary(res, createDirectoryW, dir)
  823. else:
  824. let res = createDirectoryA(dir)
  825. if res != 0'i32:
  826. result = true
  827. elif getLastError() == 183'i32:
  828. result = false
  829. else:
  830. raiseOSError(osLastError())
  831. proc existsOrCreateDir*(dir: string): bool =
  832. ## Check if a `directory`:idx: `dir` exists, and create it otherwise.
  833. ##
  834. ## Does not create parent directories (fails if parent does not exist).
  835. ## Returns `true` if the directory already exists, and `false`
  836. ## otherwise.
  837. result = not rawCreateDir(dir)
  838. if result:
  839. # path already exists - need to check that it is indeed a directory
  840. if not existsDir(dir):
  841. raise newException(IOError, "Failed to create the directory")
  842. proc createDir*(dir: string) {.rtl, extern: "nos$1",
  843. tags: [WriteDirEffect, ReadDirEffect].} =
  844. ## Creates the `directory`:idx: `dir`.
  845. ##
  846. ## The directory may contain several subdirectories that do not exist yet.
  847. ## The full path is created. If this fails, `OSError` is raised. It does **not**
  848. ## fail if the directory already exists because for most usages this does not
  849. ## indicate an error.
  850. var omitNext = false
  851. when doslikeFileSystem:
  852. omitNext = isAbsolute(dir)
  853. for i in 1.. dir.len-1:
  854. if dir[i] in {DirSep, AltSep}:
  855. if omitNext:
  856. omitNext = false
  857. else:
  858. discard existsOrCreateDir(substr(dir, 0, i-1))
  859. # The loop does not create the dir itself if it doesn't end in separator
  860. if dir.len > 0 and not omitNext and
  861. dir[^1] notin {DirSep, AltSep}:
  862. discard existsOrCreateDir(dir)
  863. proc copyDir*(source, dest: string) {.rtl, extern: "nos$1",
  864. tags: [WriteIOEffect, ReadIOEffect], benign.} =
  865. ## Copies a directory from `source` to `dest`.
  866. ##
  867. ## If this fails, `OSError` is raised. On the Windows platform this proc will
  868. ## copy the attributes from `source` into `dest`. On other platforms created
  869. ## files and directories will inherit the default permissions of a newly
  870. ## created file/directory for the user. To preserve attributes recursively on
  871. ## these platforms use `copyDirWithPermissions() <#copyDirWithPermissions>`_.
  872. createDir(dest)
  873. for kind, path in walkDir(source):
  874. var noSource = path.substr(source.len()+1)
  875. case kind
  876. of pcFile:
  877. copyFile(path, dest / noSource)
  878. of pcDir:
  879. copyDir(path, dest / noSource)
  880. else: discard
  881. proc createSymlink*(src, dest: string) =
  882. ## Create a symbolic link at `dest` which points to the item specified
  883. ## by `src`. On most operating systems, will fail if a link already exists.
  884. ##
  885. ## **Warning**:
  886. ## Some OS's (such as Microsoft Windows) restrict the creation
  887. ## of symlinks to root users (administrators).
  888. when defined(Windows):
  889. # 2 is the SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE. This allows
  890. # anyone with developer mode on to create a link
  891. let flag = dirExists(src).int32 or 2
  892. when useWinUnicode:
  893. var wSrc = newWideCString(src)
  894. var wDst = newWideCString(dest)
  895. if createSymbolicLinkW(wDst, wSrc, flag) == 0 or getLastError() != 0:
  896. raiseOSError(osLastError())
  897. else:
  898. if createSymbolicLinkA(dest, src, flag) == 0 or getLastError() != 0:
  899. raiseOSError(osLastError())
  900. else:
  901. if symlink(src, dest) != 0:
  902. raiseOSError(osLastError())
  903. proc createHardlink*(src, dest: string) =
  904. ## Create a hard link at `dest` which points to the item specified
  905. ## by `src`.
  906. ##
  907. ## **Warning**: Some OS's restrict the creation of hard links to
  908. ## root users (administrators).
  909. when defined(Windows):
  910. when useWinUnicode:
  911. var wSrc = newWideCString(src)
  912. var wDst = newWideCString(dest)
  913. if createHardLinkW(wDst, wSrc, nil) == 0:
  914. raiseOSError(osLastError())
  915. else:
  916. if createHardLinkA(dest, src, nil) == 0:
  917. raiseOSError(osLastError())
  918. else:
  919. if link(src, dest) != 0:
  920. raiseOSError(osLastError())
  921. proc parseCmdLine*(c: string): seq[string] {.
  922. noSideEffect, rtl, extern: "nos$1".} =
  923. ## Splits a `command line`:idx: into several components;
  924. ## This proc is only occasionally useful, better use the `parseopt` module.
  925. ##
  926. ## On Windows, it uses the following parsing rules
  927. ## (see http://msdn.microsoft.com/en-us/library/17w5ykft.aspx ):
  928. ##
  929. ## * Arguments are delimited by white space, which is either a space or a tab.
  930. ## * The caret character (^) is not recognized as an escape character or
  931. ## delimiter. The character is handled completely by the command-line parser
  932. ## in the operating system before being passed to the argv array in the
  933. ## program.
  934. ## * A string surrounded by double quotation marks ("string") is interpreted
  935. ## as a single argument, regardless of white space contained within. A
  936. ## quoted string can be embedded in an argument.
  937. ## * A double quotation mark preceded by a backslash (\") is interpreted as a
  938. ## literal double quotation mark character (").
  939. ## * Backslashes are interpreted literally, unless they immediately precede
  940. ## a double quotation mark.
  941. ## * If an even number of backslashes is followed by a double quotation mark,
  942. ## one backslash is placed in the argv array for every pair of backslashes,
  943. ## and the double quotation mark is interpreted as a string delimiter.
  944. ## * If an odd number of backslashes is followed by a double quotation mark,
  945. ## one backslash is placed in the argv array for every pair of backslashes,
  946. ## and the double quotation mark is "escaped" by the remaining backslash,
  947. ## causing a literal double quotation mark (") to be placed in argv.
  948. ##
  949. ## On Posix systems, it uses the following parsing rules:
  950. ## Components are separated by whitespace unless the whitespace
  951. ## occurs within ``"`` or ``'`` quotes.
  952. result = @[]
  953. var i = 0
  954. var a = ""
  955. while true:
  956. setLen(a, 0)
  957. # eat all delimiting whitespace
  958. while c[i] == ' ' or c[i] == '\t' or c[i] == '\l' or c[i] == '\r' : inc(i)
  959. when defined(windows):
  960. # parse a single argument according to the above rules:
  961. if c[i] == '\0': break
  962. var inQuote = false
  963. while true:
  964. case c[i]
  965. of '\0': break
  966. of '\\':
  967. var j = i
  968. while c[j] == '\\': inc(j)
  969. if c[j] == '"':
  970. for k in 1..(j-i) div 2: a.add('\\')
  971. if (j-i) mod 2 == 0:
  972. i = j
  973. else:
  974. a.add('"')
  975. i = j+1
  976. else:
  977. a.add(c[i])
  978. inc(i)
  979. of '"':
  980. inc(i)
  981. if not inQuote: inQuote = true
  982. elif c[i] == '"':
  983. a.add(c[i])
  984. inc(i)
  985. else:
  986. inQuote = false
  987. break
  988. of ' ', '\t':
  989. if not inQuote: break
  990. a.add(c[i])
  991. inc(i)
  992. else:
  993. a.add(c[i])
  994. inc(i)
  995. else:
  996. case c[i]
  997. of '\'', '\"':
  998. var delim = c[i]
  999. inc(i) # skip ' or "
  1000. while c[i] != '\0' and c[i] != delim:
  1001. add a, c[i]
  1002. inc(i)
  1003. if c[i] != '\0': inc(i)
  1004. of '\0': break
  1005. else:
  1006. while c[i] > ' ':
  1007. add(a, c[i])
  1008. inc(i)
  1009. add(result, a)
  1010. proc copyFileWithPermissions*(source, dest: string,
  1011. ignorePermissionErrors = true) =
  1012. ## Copies a file from `source` to `dest` preserving file permissions.
  1013. ##
  1014. ## This is a wrapper proc around `copyFile() <#copyFile>`_,
  1015. ## `getFilePermissions() <#getFilePermissions>`_ and `setFilePermissions()
  1016. ## <#setFilePermissions>`_ on non Windows platform. On Windows this proc is
  1017. ## just a wrapper for `copyFile() <#copyFile>`_ since that proc already
  1018. ## copies attributes.
  1019. ##
  1020. ## On non Windows systems permissions are copied after the file itself has
  1021. ## been copied, which won't happen atomically and could lead to a race
  1022. ## condition. If `ignorePermissionErrors` is true, errors while
  1023. ## reading/setting file attributes will be ignored, otherwise will raise
  1024. ## `OSError`.
  1025. copyFile(source, dest)
  1026. when not defined(Windows):
  1027. try:
  1028. setFilePermissions(dest, getFilePermissions(source))
  1029. except:
  1030. if not ignorePermissionErrors:
  1031. raise
  1032. proc copyDirWithPermissions*(source, dest: string,
  1033. ignorePermissionErrors = true) {.rtl, extern: "nos$1",
  1034. tags: [WriteIOEffect, ReadIOEffect], benign.} =
  1035. ## Copies a directory from `source` to `dest` preserving file permissions.
  1036. ##
  1037. ## If this fails, `OSError` is raised. This is a wrapper proc around `copyDir()
  1038. ## <#copyDir>`_ and `copyFileWithPermissions() <#copyFileWithPermissions>`_
  1039. ## on non Windows platforms. On Windows this proc is just a wrapper for
  1040. ## `copyDir() <#copyDir>`_ since that proc already copies attributes.
  1041. ##
  1042. ## On non Windows systems permissions are copied after the file or directory
  1043. ## itself has been copied, which won't happen atomically and could lead to a
  1044. ## race condition. If `ignorePermissionErrors` is true, errors while
  1045. ## reading/setting file attributes will be ignored, otherwise will raise
  1046. ## `OSError`.
  1047. createDir(dest)
  1048. when not defined(Windows):
  1049. try:
  1050. setFilePermissions(dest, getFilePermissions(source))
  1051. except:
  1052. if not ignorePermissionErrors:
  1053. raise
  1054. for kind, path in walkDir(source):
  1055. var noSource = path.substr(source.len()+1)
  1056. case kind
  1057. of pcFile:
  1058. copyFileWithPermissions(path, dest / noSource, ignorePermissionErrors)
  1059. of pcDir:
  1060. copyDirWithPermissions(path, dest / noSource, ignorePermissionErrors)
  1061. else: discard
  1062. proc inclFilePermissions*(filename: string,
  1063. permissions: set[FilePermission]) {.
  1064. rtl, extern: "nos$1", tags: [ReadDirEffect, WriteDirEffect].} =
  1065. ## a convenience procedure for:
  1066. ##
  1067. ## .. code-block:: nim
  1068. ## setFilePermissions(filename, getFilePermissions(filename)+permissions)
  1069. setFilePermissions(filename, getFilePermissions(filename)+permissions)
  1070. proc exclFilePermissions*(filename: string,
  1071. permissions: set[FilePermission]) {.
  1072. rtl, extern: "nos$1", tags: [ReadDirEffect, WriteDirEffect].} =
  1073. ## a convenience procedure for:
  1074. ##
  1075. ## .. code-block:: nim
  1076. ## setFilePermissions(filename, getFilePermissions(filename)-permissions)
  1077. setFilePermissions(filename, getFilePermissions(filename)-permissions)
  1078. proc moveDir*(source, dest: string) {.tags: [ReadIOEffect, WriteIOEffect].} =
  1079. ## Moves a directory from `source` to `dest`. If this fails, `OSError` is raised.
  1080. if not tryMoveFSObject(source, dest):
  1081. when not defined(windows):
  1082. # Fallback to copy & del
  1083. copyDir(source, dest)
  1084. removeDir(source)
  1085. #include ospaths
  1086. proc expandSymlink*(symlinkPath: string): string =
  1087. ## Returns a string representing the path to which the symbolic link points.
  1088. ##
  1089. ## On Windows this is a noop, ``symlinkPath`` is simply returned.
  1090. when defined(windows):
  1091. result = symlinkPath
  1092. else:
  1093. result = newString(256)
  1094. var len = readlink(symlinkPath, result, 256)
  1095. if len < 0:
  1096. raiseOSError(osLastError())
  1097. if len > 256:
  1098. result = newString(len+1)
  1099. len = readlink(symlinkPath, result, len)
  1100. setLen(result, len)
  1101. when defined(nimdoc):
  1102. # Common forward declaration docstring block for parameter retrieval procs.
  1103. proc paramCount*(): int {.tags: [ReadIOEffect].} =
  1104. ## Returns the number of `command line arguments`:idx: given to the
  1105. ## application.
  1106. ##
  1107. ## If your binary was called without parameters this will return zero. You
  1108. ## can later query each individual paramater with `paramStr() <#paramStr>`_
  1109. ## or retrieve all of them in one go with `commandLineParams()
  1110. ## <#commandLineParams>`_.
  1111. ##
  1112. ## **Availability**: On Posix there is no portable way to get the command
  1113. ## line from a DLL and thus the proc isn't defined in this environment. You
  1114. ## can test for its availability with `declared() <system.html#declared>`_.
  1115. ## Example:
  1116. ##
  1117. ## .. code-block:: nim
  1118. ## when declared(paramCount):
  1119. ## # Use paramCount() here
  1120. ## else:
  1121. ## # Do something else!
  1122. proc paramStr*(i: int): TaintedString {.tags: [ReadIOEffect].} =
  1123. ## Returns the `i`-th `command line argument`:idx: given to the application.
  1124. ##
  1125. ## `i` should be in the range `1..paramCount()`, the `IndexError`
  1126. ## exception will be raised for invalid values. Instead of iterating over
  1127. ## `paramCount() <#paramCount>`_ with this proc you can call the
  1128. ## convenience `commandLineParams() <#commandLineParams>`_.
  1129. ##
  1130. ## It is possible to call ``paramStr(0)`` but this will return OS specific
  1131. ## contents (usually the name of the invoked executable). You should avoid
  1132. ## this and call `getAppFilename() <#getAppFilename>`_ instead.
  1133. ##
  1134. ## **Availability**: On Posix there is no portable way to get the command
  1135. ## line from a DLL and thus the proc isn't defined in this environment. You
  1136. ## can test for its availability with `declared() <system.html#declared>`_.
  1137. ## Example:
  1138. ##
  1139. ## .. code-block:: nim
  1140. ## when declared(paramStr):
  1141. ## # Use paramStr() here
  1142. ## else:
  1143. ## # Do something else!
  1144. elif defined(windows):
  1145. # Since we support GUI applications with Nim, we sometimes generate
  1146. # a WinMain entry proc. But a WinMain proc has no access to the parsed
  1147. # command line arguments. The way to get them differs. Thus we parse them
  1148. # ourselves. This has the additional benefit that the program's behaviour
  1149. # is always the same -- independent of the used C compiler.
  1150. var
  1151. ownArgv {.threadvar.}: seq[string]
  1152. proc paramCount*(): int {.rtl, extern: "nos$1", tags: [ReadIOEffect].} =
  1153. # Docstring in nimdoc block.
  1154. if isNil(ownArgv): ownArgv = parseCmdLine($getCommandLine())
  1155. result = ownArgv.len-1
  1156. proc paramStr*(i: int): TaintedString {.rtl, extern: "nos$1",
  1157. tags: [ReadIOEffect].} =
  1158. # Docstring in nimdoc block.
  1159. if isNil(ownArgv): ownArgv = parseCmdLine($getCommandLine())
  1160. if i < ownArgv.len and i >= 0: return TaintedString(ownArgv[i])
  1161. raise newException(IndexError, "invalid index")
  1162. elif not defined(createNimRtl) and
  1163. not(defined(posix) and appType == "lib") and
  1164. not defined(genode):
  1165. # On Posix, there is no portable way to get the command line from a DLL.
  1166. var
  1167. cmdCount {.importc: "cmdCount".}: cint
  1168. cmdLine {.importc: "cmdLine".}: cstringArray
  1169. proc paramStr*(i: int): TaintedString {.tags: [ReadIOEffect].} =
  1170. # Docstring in nimdoc block.
  1171. if i < cmdCount and i >= 0: return TaintedString($cmdLine[i])
  1172. raise newException(IndexError, "invalid index")
  1173. proc paramCount*(): int {.tags: [ReadIOEffect].} =
  1174. # Docstring in nimdoc block.
  1175. result = cmdCount-1
  1176. when declared(paramCount) or defined(nimdoc):
  1177. proc commandLineParams*(): seq[TaintedString] =
  1178. ## Convenience proc which returns the command line parameters.
  1179. ##
  1180. ## This returns **only** the parameters. If you want to get the application
  1181. ## executable filename, call `getAppFilename() <#getAppFilename>`_.
  1182. ##
  1183. ## **Availability**: On Posix there is no portable way to get the command
  1184. ## line from a DLL and thus the proc isn't defined in this environment. You
  1185. ## can test for its availability with `declared() <system.html#declared>`_.
  1186. ## Example:
  1187. ##
  1188. ## .. code-block:: nim
  1189. ## when declared(commandLineParams):
  1190. ## # Use commandLineParams() here
  1191. ## else:
  1192. ## # Do something else!
  1193. result = @[]
  1194. for i in 1..paramCount():
  1195. result.add(paramStr(i))
  1196. when defined(freebsd) or defined(dragonfly):
  1197. proc sysctl(name: ptr cint, namelen: cuint, oldp: pointer, oldplen: ptr csize,
  1198. newp: pointer, newplen: csize): cint
  1199. {.importc: "sysctl",header: """#include <sys/types.h>
  1200. #include <sys/sysctl.h>"""}
  1201. const
  1202. CTL_KERN = 1
  1203. KERN_PROC = 14
  1204. MAX_PATH = 1024
  1205. when defined(freebsd):
  1206. const KERN_PROC_PATHNAME = 12
  1207. else:
  1208. const KERN_PROC_PATHNAME = 9
  1209. proc getApplFreebsd(): string =
  1210. var pathLength = csize(MAX_PATH)
  1211. result = newString(pathLength)
  1212. var req = [CTL_KERN.cint, KERN_PROC.cint, KERN_PROC_PATHNAME.cint, -1.cint]
  1213. while true:
  1214. let res = sysctl(addr req[0], 4, cast[pointer](addr result[0]),
  1215. addr pathLength, nil, 0)
  1216. if res < 0:
  1217. let err = osLastError()
  1218. if err.int32 == ENOMEM:
  1219. result = newString(pathLength)
  1220. else:
  1221. result.setLen(0) # error!
  1222. break
  1223. else:
  1224. result.setLen(pathLength)
  1225. break
  1226. when defined(linux) or defined(solaris) or defined(bsd) or defined(aix):
  1227. proc getApplAux(procPath: string): string =
  1228. result = newString(256)
  1229. var len = readlink(procPath, result, 256)
  1230. if len > 256:
  1231. result = newString(len+1)
  1232. len = readlink(procPath, result, len)
  1233. setLen(result, len)
  1234. when not (defined(windows) or defined(macosx)):
  1235. proc getApplHeuristic(): string =
  1236. when declared(paramStr):
  1237. result = string(paramStr(0))
  1238. # POSIX guaranties that this contains the executable
  1239. # as it has been executed by the calling process
  1240. if len(result) > 0 and result[0] != DirSep: # not an absolute path?
  1241. # iterate over any path in the $PATH environment variable
  1242. for p in split(string(getEnv("PATH")), {PathSep}):
  1243. var x = joinPath(p, result)
  1244. if existsFile(x): return x
  1245. else:
  1246. result = ""
  1247. when defined(macosx):
  1248. type
  1249. cuint32* {.importc: "unsigned int", nodecl.} = int
  1250. ## This is the same as the type ``uint32_t`` in *C*.
  1251. # a really hacky solution: since we like to include 2 headers we have to
  1252. # define two procs which in reality are the same
  1253. proc getExecPath1(c: cstring, size: var cuint32) {.
  1254. importc: "_NSGetExecutablePath", header: "<sys/param.h>".}
  1255. proc getExecPath2(c: cstring, size: var cuint32): bool {.
  1256. importc: "_NSGetExecutablePath", header: "<mach-o/dyld.h>".}
  1257. proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect].} =
  1258. ## Returns the filename of the application's executable.
  1259. ##
  1260. ## This procedure will resolve symlinks.
  1261. # Linux: /proc/<pid>/exe
  1262. # Solaris:
  1263. # /proc/<pid>/object/a.out (filename only)
  1264. # /proc/<pid>/path/a.out (complete pathname)
  1265. when defined(windows):
  1266. var bufsize = int32(MAX_PATH)
  1267. when useWinUnicode:
  1268. var buf = newWideCString("", bufsize)
  1269. while true:
  1270. var L = getModuleFileNameW(0, buf, bufsize)
  1271. if L == 0'i32:
  1272. result = "" # error!
  1273. break
  1274. elif L > bufsize:
  1275. buf = newWideCString("", L)
  1276. bufsize = L
  1277. else:
  1278. result = buf$L
  1279. break
  1280. else:
  1281. result = newString(bufsize)
  1282. while true:
  1283. var L = getModuleFileNameA(0, result, bufsize)
  1284. if L == 0'i32:
  1285. result = "" # error!
  1286. break
  1287. elif L > bufsize:
  1288. result = newString(L)
  1289. bufsize = L
  1290. else:
  1291. setLen(result, L)
  1292. break
  1293. elif defined(macosx):
  1294. var size: cuint32
  1295. getExecPath1(nil, size)
  1296. result = newString(int(size))
  1297. if getExecPath2(result, size):
  1298. result = "" # error!
  1299. if result.len > 0:
  1300. result = result.expandFilename
  1301. else:
  1302. when defined(linux) or defined(aix) or defined(netbsd):
  1303. result = getApplAux("/proc/self/exe")
  1304. elif defined(solaris):
  1305. result = getApplAux("/proc/" & $getpid() & "/path/a.out")
  1306. elif defined(genode):
  1307. raiseOSError("POSIX command line not supported")
  1308. elif defined(freebsd) or defined(dragonfly):
  1309. result = getApplFreebsd()
  1310. # little heuristic that may work on other POSIX-like systems:
  1311. if result.len == 0:
  1312. result = getApplHeuristic()
  1313. proc getApplicationFilename*(): string {.rtl, extern: "nos$1", deprecated.} =
  1314. ## Returns the filename of the application's executable.
  1315. ## **Deprecated since version 0.8.12**: use ``getAppFilename``
  1316. ## instead.
  1317. result = getAppFilename()
  1318. proc getApplicationDir*(): string {.rtl, extern: "nos$1", deprecated.} =
  1319. ## Returns the directory of the application's executable.
  1320. ## **Deprecated since version 0.8.12**: use ``getAppDir``
  1321. ## instead.
  1322. result = splitFile(getAppFilename()).dir
  1323. proc getAppDir*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect].} =
  1324. ## Returns the directory of the application's executable.
  1325. result = splitFile(getAppFilename()).dir
  1326. proc sleep*(milsecs: int) {.rtl, extern: "nos$1", tags: [TimeEffect].} =
  1327. ## sleeps `milsecs` milliseconds.
  1328. when defined(windows):
  1329. winlean.sleep(int32(milsecs))
  1330. else:
  1331. var a, b: Timespec
  1332. a.tv_sec = Time(milsecs div 1000)
  1333. a.tv_nsec = (milsecs mod 1000) * 1000 * 1000
  1334. discard posix.nanosleep(a, b)
  1335. proc getFileSize*(file: string): BiggestInt {.rtl, extern: "nos$1",
  1336. tags: [ReadIOEffect].} =
  1337. ## returns the file size of `file` (in bytes). An ``OSError`` exception is
  1338. ## raised in case of an error.
  1339. when defined(windows):
  1340. var a: WIN32_FIND_DATA
  1341. var resA = findFirstFile(file, a)
  1342. if resA == -1: raiseOSError(osLastError())
  1343. result = rdFileSize(a)
  1344. findClose(resA)
  1345. else:
  1346. var f: File
  1347. if open(f, file):
  1348. result = getFileSize(f)
  1349. close(f)
  1350. else: raiseOSError(osLastError())
  1351. when defined(Windows):
  1352. type
  1353. DeviceId* = int32
  1354. FileId* = int64
  1355. else:
  1356. type
  1357. DeviceId* = Dev
  1358. FileId* = Ino
  1359. type
  1360. FileInfo* = object
  1361. ## Contains information associated with a file object.
  1362. id*: tuple[device: DeviceId, file: FileId] # Device and file id.
  1363. kind*: PathComponent # Kind of file object - directory, symlink, etc.
  1364. size*: BiggestInt # Size of file.
  1365. permissions*: set[FilePermission] # File permissions
  1366. linkCount*: BiggestInt # Number of hard links the file object has.
  1367. lastAccessTime*: Time # Time file was last accessed.
  1368. lastWriteTime*: Time # Time file was last modified/written to.
  1369. creationTime*: Time # Time file was created. Not supported on all systems!
  1370. template rawToFormalFileInfo(rawInfo, path, formalInfo): untyped =
  1371. ## Transforms the native file info structure into the one nim uses.
  1372. ## 'rawInfo' is either a 'TBY_HANDLE_FILE_INFORMATION' structure on Windows,
  1373. ## or a 'Stat' structure on posix
  1374. when defined(Windows):
  1375. template toTime(e: FILETIME): untyped {.gensym.} = winTimeToUnixTime(rdFileTime(e)) # local templates default to bind semantics
  1376. template merge(a, b): untyped = a or (b shl 32)
  1377. formalInfo.id.device = rawInfo.dwVolumeSerialNumber
  1378. formalInfo.id.file = merge(rawInfo.nFileIndexLow, rawInfo.nFileIndexHigh)
  1379. formalInfo.size = merge(rawInfo.nFileSizeLow, rawInfo.nFileSizeHigh)
  1380. formalInfo.linkCount = rawInfo.nNumberOfLinks
  1381. formalInfo.lastAccessTime = toTime(rawInfo.ftLastAccessTime)
  1382. formalInfo.lastWriteTime = toTime(rawInfo.ftLastWriteTime)
  1383. formalInfo.creationTime = toTime(rawInfo.ftCreationTime)
  1384. # Retrieve basic permissions
  1385. if (rawInfo.dwFileAttributes and FILE_ATTRIBUTE_READONLY) != 0'i32:
  1386. formalInfo.permissions = {fpUserExec, fpUserRead, fpGroupExec,
  1387. fpGroupRead, fpOthersExec, fpOthersRead}
  1388. else:
  1389. result.permissions = {fpUserExec..fpOthersRead}
  1390. # Retrieve basic file kind
  1391. result.kind = pcFile
  1392. if (rawInfo.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) != 0'i32:
  1393. formalInfo.kind = pcDir
  1394. if (rawInfo.dwFileAttributes and FILE_ATTRIBUTE_REPARSE_POINT) != 0'i32:
  1395. formalInfo.kind = succ(result.kind)
  1396. else:
  1397. template checkAndIncludeMode(rawMode, formalMode: untyped) =
  1398. if (rawInfo.st_mode and rawMode) != 0'i32:
  1399. formalInfo.permissions.incl(formalMode)
  1400. formalInfo.id = (rawInfo.st_dev, rawInfo.st_ino)
  1401. formalInfo.size = rawInfo.st_size
  1402. formalInfo.linkCount = rawInfo.st_Nlink.BiggestInt
  1403. formalInfo.lastAccessTime = rawInfo.st_atime
  1404. formalInfo.lastWriteTime = rawInfo.st_mtime
  1405. formalInfo.creationTime = rawInfo.st_ctime
  1406. result.permissions = {}
  1407. checkAndIncludeMode(S_IRUSR, fpUserRead)
  1408. checkAndIncludeMode(S_IWUSR, fpUserWrite)
  1409. checkAndIncludeMode(S_IXUSR, fpUserExec)
  1410. checkAndIncludeMode(S_IRGRP, fpGroupRead)
  1411. checkAndIncludeMode(S_IWGRP, fpGroupWrite)
  1412. checkAndIncludeMode(S_IXGRP, fpGroupExec)
  1413. checkAndIncludeMode(S_IROTH, fpOthersRead)
  1414. checkAndIncludeMode(S_IWOTH, fpOthersWrite)
  1415. checkAndIncludeMode(S_IXOTH, fpOthersExec)
  1416. formalInfo.kind = pcFile
  1417. if S_ISDIR(rawInfo.st_mode):
  1418. formalInfo.kind = pcDir
  1419. elif S_ISLNK(rawInfo.st_mode):
  1420. assert(path != "") # symlinks can't occur for file handles
  1421. formalInfo.kind = getSymlinkFileKind(path)
  1422. proc getFileInfo*(handle: FileHandle): FileInfo =
  1423. ## Retrieves file information for the file object represented by the given
  1424. ## handle.
  1425. ##
  1426. ## If the information cannot be retrieved, such as when the file handle
  1427. ## is invalid, an error will be thrown.
  1428. # Done: ID, Kind, Size, Permissions, Link Count
  1429. when defined(Windows):
  1430. var rawInfo: BY_HANDLE_FILE_INFORMATION
  1431. # We have to use the super special '_get_osfhandle' call (wrapped above)
  1432. # To transform the C file descripter to a native file handle.
  1433. var realHandle = get_osfhandle(handle)
  1434. if getFileInformationByHandle(realHandle, addr rawInfo) == 0:
  1435. raiseOSError(osLastError())
  1436. rawToFormalFileInfo(rawInfo, "", result)
  1437. else:
  1438. var rawInfo: Stat
  1439. if fstat(handle, rawInfo) < 0'i32:
  1440. raiseOSError(osLastError())
  1441. rawToFormalFileInfo(rawInfo, "", result)
  1442. proc getFileInfo*(file: File): FileInfo =
  1443. if file.isNil:
  1444. raise newException(IOError, "File is nil")
  1445. result = getFileInfo(file.getFileHandle())
  1446. proc getFileInfo*(path: string, followSymlink = true): FileInfo =
  1447. ## Retrieves file information for the file object pointed to by `path`.
  1448. ##
  1449. ## Due to intrinsic differences between operating systems, the information
  1450. ## contained by the returned `FileInfo` structure will be slightly different
  1451. ## across platforms, and in some cases, incomplete or inaccurate.
  1452. ##
  1453. ## When `followSymlink` is true, symlinks are followed and the information
  1454. ## retrieved is information related to the symlink's target. Otherwise,
  1455. ## information on the symlink itself is retrieved.
  1456. ##
  1457. ## If the information cannot be retrieved, such as when the path doesn't
  1458. ## exist, or when permission restrictions prevent the program from retrieving
  1459. ## file information, an error will be thrown.
  1460. when defined(Windows):
  1461. var
  1462. handle = openHandle(path, followSymlink)
  1463. rawInfo: BY_HANDLE_FILE_INFORMATION
  1464. if handle == INVALID_HANDLE_VALUE:
  1465. raiseOSError(osLastError())
  1466. if getFileInformationByHandle(handle, addr rawInfo) == 0:
  1467. raiseOSError(osLastError())
  1468. rawToFormalFileInfo(rawInfo, path, result)
  1469. discard closeHandle(handle)
  1470. else:
  1471. var rawInfo: Stat
  1472. if followSymlink:
  1473. if stat(path, rawInfo) < 0'i32:
  1474. raiseOSError(osLastError())
  1475. else:
  1476. if lstat(path, rawInfo) < 0'i32:
  1477. raiseOSError(osLastError())
  1478. rawToFormalFileInfo(rawInfo, path, result)
  1479. proc isHidden*(path: string): bool =
  1480. ## Determines whether a given path is hidden or not. Returns false if the
  1481. ## file doesn't exist. The given path must be accessible from the current
  1482. ## working directory of the program.
  1483. ##
  1484. ## On Windows, a file is hidden if the file's 'hidden' attribute is set.
  1485. ## On Unix-like systems, a file is hidden if it starts with a '.' (period)
  1486. ## and is not *just* '.' or '..' ' ."
  1487. when defined(Windows):
  1488. when useWinUnicode:
  1489. wrapUnary(attributes, getFileAttributesW, path)
  1490. else:
  1491. var attributes = getFileAttributesA(path)
  1492. if attributes != -1'i32:
  1493. result = (attributes and FILE_ATTRIBUTE_HIDDEN) != 0'i32
  1494. else:
  1495. if fileExists(path):
  1496. let
  1497. fileName = extractFilename(path)
  1498. nameLen = len(fileName)
  1499. if nameLen == 2:
  1500. result = (fileName[0] == '.') and (fileName[1] != '.')
  1501. elif nameLen > 2:
  1502. result = (fileName[0] == '.') and (fileName[3] != '.')
  1503. {.pop.}