osdirs.nim 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565
  1. ## .. importdoc:: osfiles.nim, appdirs.nim, paths.nim
  2. include system/inclrtl
  3. import std/oserrors
  4. import ospaths2, osfiles
  5. import oscommon
  6. import std/staticos
  7. when supportedSystem:
  8. export dirExists, PathComponent
  9. when defined(nimPreviewSlimSystem):
  10. import std/[syncio, assertions, widestrs]
  11. when weirdTarget:
  12. discard
  13. elif defined(windows):
  14. import std/[winlean, times]
  15. elif defined(posix):
  16. import std/[posix, times]
  17. when weirdTarget:
  18. {.pragma: noWeirdTarget, error: "this proc is not available on the NimScript/js target".}
  19. else:
  20. {.pragma: noWeirdTarget.}
  21. when defined(nimscript):
  22. # for procs already defined in scriptconfig.nim
  23. template noNimJs(body): untyped = discard
  24. elif defined(js):
  25. {.pragma: noNimJs, error: "this proc is not available on the js target".}
  26. else:
  27. {.pragma: noNimJs.}
  28. # Templates for filtering directories and files
  29. when defined(windows) and not weirdTarget:
  30. template isDir(f: WIN32_FIND_DATA): bool =
  31. (f.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) != 0'i32
  32. template isFile(f: WIN32_FIND_DATA): bool =
  33. not isDir(f)
  34. else:
  35. template isDir(f: string): bool {.dirty.} =
  36. dirExists(f)
  37. template isFile(f: string): bool {.dirty.} =
  38. fileExists(f)
  39. template defaultWalkFilter(item): bool =
  40. ## Walk filter used to return true on both
  41. ## files and directories
  42. true
  43. template walkCommon(pattern: string, filter) =
  44. ## Common code for getting the files and directories with the
  45. ## specified `pattern`
  46. when defined(windows):
  47. var
  48. f: WIN32_FIND_DATA
  49. res: int
  50. res = findFirstFile(pattern, f)
  51. if res != -1:
  52. defer: findClose(res)
  53. let dotPos = searchExtPos(pattern)
  54. while true:
  55. if not skipFindData(f) and filter(f):
  56. # Windows bug/gotcha: 't*.nim' matches 'tfoo.nims' -.- so we check
  57. # that the file extensions have the same length ...
  58. let ff = getFilename(f)
  59. let idx = ff.len - pattern.len + dotPos
  60. if dotPos < 0 or idx >= ff.len or (idx >= 0 and ff[idx] == '.') or
  61. (dotPos >= 0 and dotPos+1 < pattern.len and pattern[dotPos+1] == '*'):
  62. yield splitFile(pattern).dir / extractFilename(ff)
  63. if findNextFile(res, f) == 0'i32:
  64. let errCode = getLastError()
  65. if errCode == ERROR_NO_MORE_FILES: break
  66. else: raiseOSError(errCode.OSErrorCode)
  67. else: # here we use glob
  68. var
  69. f: Glob
  70. res: int
  71. f.gl_offs = 0
  72. f.gl_pathc = 0
  73. f.gl_pathv = nil
  74. res = glob(pattern, 0, nil, addr(f))
  75. defer: globfree(addr(f))
  76. if res == 0:
  77. for i in 0.. f.gl_pathc - 1:
  78. assert(f.gl_pathv[i] != nil)
  79. let path = $f.gl_pathv[i]
  80. if filter(path):
  81. yield path
  82. iterator walkPattern*(pattern: string): string {.tags: [ReadDirEffect], noWeirdTarget.} =
  83. ## Iterate over all the files and directories that match the `pattern`.
  84. ##
  85. ## On POSIX this uses the `glob`:idx: call.
  86. ## `pattern` is OS dependent, but at least the `"*.ext"`
  87. ## notation is supported.
  88. ##
  89. ## See also:
  90. ## * `walkFiles iterator`_
  91. ## * `walkDirs iterator`_
  92. ## * `walkDir iterator`_
  93. ## * `walkDirRec iterator`_
  94. runnableExamples:
  95. import std/os
  96. import std/sequtils
  97. let paths = toSeq(walkPattern("lib/pure/*")) # works on Windows too
  98. assert "lib/pure/concurrency".unixToNativePath in paths
  99. assert "lib/pure/os.nim".unixToNativePath in paths
  100. walkCommon(pattern, defaultWalkFilter)
  101. iterator walkFiles*(pattern: string): string {.tags: [ReadDirEffect], noWeirdTarget.} =
  102. ## Iterate over all the files that match the `pattern`.
  103. ##
  104. ## On POSIX this uses the `glob`:idx: call.
  105. ## `pattern` is OS dependent, but at least the `"*.ext"`
  106. ## notation is supported.
  107. ##
  108. ## See also:
  109. ## * `walkPattern iterator`_
  110. ## * `walkDirs iterator`_
  111. ## * `walkDir iterator`_
  112. ## * `walkDirRec iterator`_
  113. runnableExamples:
  114. import std/os
  115. import std/sequtils
  116. assert "lib/pure/os.nim".unixToNativePath in toSeq(walkFiles("lib/pure/*.nim")) # works on Windows too
  117. walkCommon(pattern, isFile)
  118. iterator walkDirs*(pattern: string): string {.tags: [ReadDirEffect], noWeirdTarget.} =
  119. ## Iterate over all the directories that match the `pattern`.
  120. ##
  121. ## On POSIX this uses the `glob`:idx: call.
  122. ## `pattern` is OS dependent, but at least the `"*.ext"`
  123. ## notation is supported.
  124. ##
  125. ## See also:
  126. ## * `walkPattern iterator`_
  127. ## * `walkFiles iterator`_
  128. ## * `walkDir iterator`_
  129. ## * `walkDirRec iterator`_
  130. runnableExamples:
  131. import std/os
  132. import std/sequtils
  133. let paths = toSeq(walkDirs("lib/pure/*")) # works on Windows too
  134. assert "lib/pure/concurrency".unixToNativePath in paths
  135. walkCommon(pattern, isDir)
  136. iterator walkDir*(dir: string; relative = false, checkDir = false,
  137. skipSpecial = false):
  138. tuple[kind: PathComponent, path: string] {.tags: [ReadDirEffect].} =
  139. ## Walks over the directory `dir` and yields for each directory or file in
  140. ## `dir`. The component type and full path for each item are returned.
  141. ##
  142. ## Walking is not recursive.
  143. ## * If `relative` is true (default: false)
  144. ## the resulting path is shortened to be relative to ``dir``,
  145. ## otherwise the full path is returned.
  146. ## * If `checkDir` is true, `OSError` is raised when `dir`
  147. ## doesn't exist.
  148. ## * If `skipSpecial` is true, then (besides all directories) only *regular*
  149. ## files (**without** special "file" objects like FIFOs, device files,
  150. ## etc) will be yielded on Unix.
  151. ##
  152. ## **Example:**
  153. ##
  154. ## This directory structure:
  155. ##
  156. ## dirA / dirB / fileB1.txt
  157. ## / dirC
  158. ## / fileA1.txt
  159. ## / fileA2.txt
  160. ##
  161. ## and this code:
  162. runnableExamples("-r:off"):
  163. import std/[strutils, sugar]
  164. # note: order is not guaranteed
  165. # this also works at compile time
  166. assert collect(for k in walkDir("dirA"): k.path).join(" ") ==
  167. "dirA/dirB dirA/dirC dirA/fileA2.txt dirA/fileA1.txt"
  168. ## See also:
  169. ## * `walkPattern iterator`_
  170. ## * `walkFiles iterator`_
  171. ## * `walkDirs iterator`_
  172. ## * `walkDirRec iterator`_
  173. when nimvm:
  174. for k, v in items(staticWalkDir(dir, relative)):
  175. yield (k, v)
  176. else:
  177. when weirdTarget:
  178. for k, v in items(staticWalkDir(dir, relative)):
  179. yield (k, v)
  180. elif defined(windows):
  181. var f: WIN32_FIND_DATA
  182. var h = findFirstFile(dir / "*", f)
  183. if h == -1:
  184. if checkDir:
  185. raiseOSError(osLastError(), dir)
  186. else:
  187. defer: findClose(h)
  188. while true:
  189. var k = pcFile
  190. if not skipFindData(f):
  191. if (f.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) != 0'i32:
  192. k = pcDir
  193. if (f.dwFileAttributes and FILE_ATTRIBUTE_REPARSE_POINT) != 0'i32:
  194. k = succ(k)
  195. let xx = if relative: extractFilename(getFilename(f))
  196. else: dir / extractFilename(getFilename(f))
  197. yield (k, xx)
  198. if findNextFile(h, f) == 0'i32:
  199. let errCode = getLastError()
  200. if errCode == ERROR_NO_MORE_FILES: break
  201. else: raiseOSError(errCode.OSErrorCode)
  202. else:
  203. var d = opendir(dir)
  204. if d == nil:
  205. if checkDir:
  206. raiseOSError(osLastError(), dir)
  207. else:
  208. defer: discard closedir(d)
  209. while true:
  210. var x = readdir(d)
  211. if x == nil: break
  212. var y = $cast[cstring](addr x.d_name)
  213. if y != "." and y != "..":
  214. var s: Stat
  215. let path = dir / y
  216. if not relative:
  217. y = path
  218. var k = pcFile
  219. template resolveSymlink() =
  220. var isSpecial: bool
  221. (k, isSpecial) = getSymlinkFileKind(path)
  222. if skipSpecial and isSpecial: continue
  223. template kSetGeneric() = # pure Posix component `k` resolution
  224. if lstat(path.cstring, s) < 0'i32: continue # don't yield
  225. elif S_ISDIR(s.st_mode):
  226. k = pcDir
  227. elif S_ISLNK(s.st_mode):
  228. resolveSymlink()
  229. elif skipSpecial and not S_ISREG(s.st_mode): continue
  230. when defined(linux) or defined(macosx) or
  231. defined(bsd) or defined(genode) or defined(nintendoswitch):
  232. case x.d_type
  233. of DT_DIR: k = pcDir
  234. of DT_LNK:
  235. resolveSymlink()
  236. of DT_UNKNOWN:
  237. kSetGeneric()
  238. else: # DT_REG or special "files" like FIFOs
  239. if skipSpecial and x.d_type != DT_REG: continue
  240. else: discard # leave it as pcFile
  241. else: # assuming that field `d_type` is not present
  242. kSetGeneric()
  243. yield (k, y)
  244. iterator walkDirRec*(dir: string,
  245. yieldFilter = {pcFile}, followFilter = {pcDir},
  246. relative = false, checkDir = false, skipSpecial = false):
  247. string {.tags: [ReadDirEffect].} =
  248. ## Recursively walks over the directory `dir` and yields for each file
  249. ## or directory in `dir`.
  250. ##
  251. ## Options `relative`, `checkdir`, `skipSpecial` are explained in
  252. ## [walkDir iterator] description.
  253. ##
  254. ## .. warning:: Modifying the directory structure while the iterator
  255. ## is traversing may result in undefined behavior!
  256. ##
  257. ## Walking is recursive. `followFilter` controls the behaviour of the iterator:
  258. ##
  259. ## ===================== =============================================
  260. ## yieldFilter meaning
  261. ## ===================== =============================================
  262. ## ``pcFile`` yield real files (default)
  263. ## ``pcLinkToFile`` yield symbolic links to files
  264. ## ``pcDir`` yield real directories
  265. ## ``pcLinkToDir`` yield symbolic links to directories
  266. ## ===================== =============================================
  267. ##
  268. ## ===================== =============================================
  269. ## followFilter meaning
  270. ## ===================== =============================================
  271. ## ``pcDir`` follow real directories (default)
  272. ## ``pcLinkToDir`` follow symbolic links to directories
  273. ## ===================== =============================================
  274. ##
  275. ##
  276. ## See also:
  277. ## * `walkPattern iterator`_
  278. ## * `walkFiles iterator`_
  279. ## * `walkDirs iterator`_
  280. ## * `walkDir iterator`_
  281. var stack = @[""]
  282. var checkDir = checkDir
  283. while stack.len > 0:
  284. let d = stack.pop()
  285. for k, p in walkDir(dir / d, relative = true, checkDir = checkDir,
  286. skipSpecial = skipSpecial):
  287. let rel = d / p
  288. if k in {pcDir, pcLinkToDir} and k in followFilter:
  289. stack.add rel
  290. if k in yieldFilter:
  291. yield if relative: rel else: dir / rel
  292. checkDir = false
  293. # We only check top-level dir, otherwise if a subdir is invalid (eg. wrong
  294. # permissions), it'll abort iteration and there would be no way to
  295. # continue iteration.
  296. # Future work can provide a way to customize this and do error reporting.
  297. proc rawRemoveDir(dir: string) {.noWeirdTarget.} =
  298. when defined(windows):
  299. wrapUnary(res, removeDirectoryW, dir)
  300. let lastError = osLastError()
  301. if res == 0'i32 and lastError.int32 != 3'i32 and
  302. lastError.int32 != 18'i32 and lastError.int32 != 2'i32:
  303. raiseOSError(lastError, dir)
  304. else:
  305. if rmdir(dir) != 0'i32 and errno != ENOENT: raiseOSError(osLastError(), dir)
  306. proc removeDir*(dir: string, checkDir = false) {.rtl, extern: "nos$1", tags: [
  307. WriteDirEffect, ReadDirEffect], benign, noWeirdTarget.} =
  308. ## Removes the directory `dir` including all subdirectories and files
  309. ## in `dir` (recursively).
  310. ##
  311. ## If this fails, `OSError` is raised. This does not fail if the directory never
  312. ## existed in the first place, unless `checkDir` = true.
  313. ##
  314. ## See also:
  315. ## * `tryRemoveFile proc`_
  316. ## * `removeFile proc`_
  317. ## * `existsOrCreateDir proc`_
  318. ## * `createDir proc`_
  319. ## * `copyDir proc`_
  320. ## * `copyDirWithPermissions proc`_
  321. ## * `moveDir proc`_
  322. for kind, path in walkDir(dir, checkDir = checkDir):
  323. case kind
  324. of pcFile, pcLinkToFile, pcLinkToDir: removeFile(path)
  325. of pcDir: removeDir(path, true)
  326. # for subdirectories there is no benefit in `checkDir = false`
  327. # (unless perhaps for edge case of concurrent processes also deleting
  328. # the same files)
  329. rawRemoveDir(dir)
  330. proc rawCreateDir(dir: string): bool {.noWeirdTarget.} =
  331. # Try to create one directory (not the whole path).
  332. # returns `true` for success, `false` if the path has previously existed
  333. #
  334. # This is a thin wrapper over mkDir (or alternatives on other systems),
  335. # so in case of a pre-existing path we don't check that it is a directory.
  336. when defined(solaris):
  337. let res = mkdir(dir, 0o777)
  338. if res == 0'i32:
  339. result = true
  340. elif errno in {EEXIST, ENOSYS}:
  341. result = false
  342. else:
  343. raiseOSError(osLastError(), dir)
  344. elif defined(haiku):
  345. let res = mkdir(dir, 0o777)
  346. if res == 0'i32:
  347. result = true
  348. elif errno == EEXIST or errno == EROFS:
  349. result = false
  350. else:
  351. raiseOSError(osLastError(), dir)
  352. elif defined(posix):
  353. let res = mkdir(dir, 0o777)
  354. if res == 0'i32:
  355. result = true
  356. elif errno == EEXIST:
  357. result = false
  358. else:
  359. #echo res
  360. raiseOSError(osLastError(), dir)
  361. else:
  362. wrapUnary(res, createDirectoryW, dir)
  363. if res != 0'i32:
  364. result = true
  365. elif getLastError() == 183'i32:
  366. result = false
  367. else:
  368. raiseOSError(osLastError(), dir)
  369. proc existsOrCreateDir*(dir: string): bool {.rtl, extern: "nos$1",
  370. tags: [WriteDirEffect, ReadDirEffect], noWeirdTarget.} =
  371. ## Checks if a `directory`:idx: `dir` exists, and creates it otherwise.
  372. ##
  373. ## Does not create parent directories (raises `OSError` if parent directories do not exist).
  374. ## Returns `true` if the directory already exists, and `false` otherwise.
  375. ##
  376. ## See also:
  377. ## * `removeDir proc`_
  378. ## * `createDir proc`_
  379. ## * `copyDir proc`_
  380. ## * `copyDirWithPermissions proc`_
  381. ## * `moveDir proc`_
  382. result = not rawCreateDir(dir)
  383. if result:
  384. # path already exists - need to check that it is indeed a directory
  385. if not dirExists(dir):
  386. raise newException(IOError, "Failed to create '" & dir & "'")
  387. proc createDir*(dir: string) {.rtl, extern: "nos$1",
  388. tags: [WriteDirEffect, ReadDirEffect], noWeirdTarget.} =
  389. ## Creates the `directory`:idx: `dir`.
  390. ##
  391. ## The directory may contain several subdirectories that do not exist yet.
  392. ## The full path is created. If this fails, `OSError` is raised.
  393. ##
  394. ## It does **not** fail if the directory already exists because for
  395. ## most usages this does not indicate an error.
  396. ##
  397. ## See also:
  398. ## * `removeDir proc`_
  399. ## * `existsOrCreateDir proc`_
  400. ## * `copyDir proc`_
  401. ## * `copyDirWithPermissions proc`_
  402. ## * `moveDir proc`_
  403. if dir == "":
  404. return
  405. var omitNext = isAbsolute(dir)
  406. for p in parentDirs(dir, fromRoot=true):
  407. if omitNext:
  408. omitNext = false
  409. else:
  410. discard existsOrCreateDir(p)
  411. proc copyDir*(source, dest: string, skipSpecial = false) {.rtl, extern: "nos$1",
  412. tags: [ReadDirEffect, WriteIOEffect, ReadIOEffect], benign, noWeirdTarget.} =
  413. ## Copies a directory from `source` to `dest`.
  414. ##
  415. ## On non-Windows OSes, symlinks are copied as symlinks. On Windows, symlinks
  416. ## are skipped.
  417. ##
  418. ## If `skipSpecial` is true, then (besides all directories) only *regular*
  419. ## files (**without** special "file" objects like FIFOs, device files,
  420. ## etc) will be copied on Unix.
  421. ##
  422. ## If this fails, `OSError` is raised.
  423. ##
  424. ## On the Windows platform this proc will copy the attributes from
  425. ## `source` into `dest`.
  426. ##
  427. ## On other platforms created files and directories will inherit the
  428. ## default permissions of a newly created file/directory for the user.
  429. ## Use `copyDirWithPermissions proc`_
  430. ## to preserve attributes recursively on these platforms.
  431. ##
  432. ## See also:
  433. ## * `copyDirWithPermissions proc`_
  434. ## * `copyFile proc`_
  435. ## * `copyFileWithPermissions proc`_
  436. ## * `removeDir proc`_
  437. ## * `existsOrCreateDir proc`_
  438. ## * `createDir proc`_
  439. ## * `moveDir proc`_
  440. createDir(dest)
  441. for kind, path in walkDir(source, skipSpecial = skipSpecial):
  442. var noSource = splitPath(path).tail
  443. if kind == pcDir:
  444. copyDir(path, dest / noSource, skipSpecial = skipSpecial)
  445. else:
  446. copyFile(path, dest / noSource, {cfSymlinkAsIs})
  447. proc copyDirWithPermissions*(source, dest: string,
  448. ignorePermissionErrors = true,
  449. skipSpecial = false)
  450. {.rtl, extern: "nos$1", tags: [ReadDirEffect, WriteIOEffect, ReadIOEffect],
  451. benign, noWeirdTarget.} =
  452. ## Copies a directory from `source` to `dest` preserving file permissions.
  453. ##
  454. ## On non-Windows OSes, symlinks are copied as symlinks. On Windows, symlinks
  455. ## are skipped.
  456. ##
  457. ## If `skipSpecial` is true, then (besides all directories) only *regular*
  458. ## files (**without** special "file" objects like FIFOs, device files,
  459. ## etc) will be copied on Unix.
  460. ##
  461. ## If this fails, `OSError` is raised. This is a wrapper proc around
  462. ## `copyDir`_ and `copyFileWithPermissions`_ procs
  463. ## on non-Windows platforms.
  464. ##
  465. ## On Windows this proc is just a wrapper for `copyDir proc`_ since
  466. ## that proc already copies attributes.
  467. ##
  468. ## On non-Windows systems permissions are copied after the file or directory
  469. ## itself has been copied, which won't happen atomically and could lead to a
  470. ## race condition. If `ignorePermissionErrors` is true (default), errors while
  471. ## reading/setting file attributes will be ignored, otherwise will raise
  472. ## `OSError`.
  473. ##
  474. ## See also:
  475. ## * `copyDir proc`_
  476. ## * `copyFile proc`_
  477. ## * `copyFileWithPermissions proc`_
  478. ## * `removeDir proc`_
  479. ## * `moveDir proc`_
  480. ## * `existsOrCreateDir proc`_
  481. ## * `createDir proc`_
  482. createDir(dest)
  483. when not defined(windows):
  484. try:
  485. setFilePermissions(dest, getFilePermissions(source), followSymlinks =
  486. false)
  487. except:
  488. if not ignorePermissionErrors:
  489. raise
  490. for kind, path in walkDir(source, skipSpecial = skipSpecial):
  491. var noSource = splitPath(path).tail
  492. if kind == pcDir:
  493. copyDirWithPermissions(path, dest / noSource, ignorePermissionErrors, skipSpecial = skipSpecial)
  494. else:
  495. copyFileWithPermissions(path, dest / noSource, ignorePermissionErrors, {cfSymlinkAsIs})
  496. proc moveDir*(source, dest: string) {.tags: [ReadIOEffect, WriteIOEffect], noWeirdTarget.} =
  497. ## Moves a directory from `source` to `dest`.
  498. ##
  499. ## Symlinks are not followed: if `source` contains symlinks, they themself are
  500. ## moved, not their target.
  501. ##
  502. ## If this fails, `OSError` is raised.
  503. ##
  504. ## See also:
  505. ## * `moveFile proc`_
  506. ## * `copyDir proc`_
  507. ## * `copyDirWithPermissions proc`_
  508. ## * `removeDir proc`_
  509. ## * `existsOrCreateDir proc`_
  510. ## * `createDir proc`_
  511. if not tryMoveFSObject(source, dest, isDir = true):
  512. # Fallback to copy & del
  513. copyDir(source, dest)
  514. removeDir(source)
  515. proc setCurrentDir*(newDir: string) {.inline, tags: [], noWeirdTarget.} =
  516. ## Sets the `current working directory`:idx:; `OSError`
  517. ## is raised if `newDir` cannot been set.
  518. ##
  519. ## See also:
  520. ## * `getHomeDir proc`_
  521. ## * `getConfigDir proc`_
  522. ## * `getTempDir proc`_
  523. ## * `getCurrentDir proc`_
  524. when defined(windows):
  525. if setCurrentDirectoryW(newWideCString(newDir)) == 0'i32:
  526. raiseOSError(osLastError(), newDir)
  527. else:
  528. if chdir(newDir) != 0'i32: raiseOSError(osLastError(), newDir)