osdirs.nim 19 KB

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