summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--lib/pure/os.nim11
-rw-r--r--lib/std/dirs.nim32
-rw-r--r--lib/std/private/oscommon.nim13
-rw-r--r--lib/std/private/osdirs.nim46
-rw-r--r--tests/stdlib/tgetfileinfo.nim29
-rw-r--r--tests/stdlib/tos.nim19
-rw-r--r--tools/nimgrep.nim2
7 files changed, 107 insertions, 45 deletions
diff --git a/lib/pure/os.nim b/lib/pure/os.nim
index a1df1c24e..bdcd93fd3 100644
--- a/lib/pure/os.nim
+++ b/lib/pure/os.nim
@@ -1095,6 +1095,8 @@ type
     creationTime*: times.Time         ## Time file was created. Not supported on all systems!
     blockSize*: int                   ## Preferred I/O block size for this object.
                                       ## In some filesystems, this may vary from file to file.
+    isRegular*: bool                  ## Is file regular? (on Unix some "files"
+                                      ## can be non-regular like FIFOs, devices)
 
 template rawToFormalFileInfo(rawInfo, path, formalInfo): untyped =
   ## Transforms the native file info structure into the one nim uses.
@@ -1155,14 +1157,14 @@ template rawToFormalFileInfo(rawInfo, path, formalInfo): untyped =
     checkAndIncludeMode(S_IWOTH, fpOthersWrite)
     checkAndIncludeMode(S_IXOTH, fpOthersExec)
 
-    formalInfo.kind =
+    (formalInfo.kind, formalInfo.isRegular) =
       if S_ISDIR(rawInfo.st_mode):
-        pcDir
+        (pcDir, true)
       elif S_ISLNK(rawInfo.st_mode):
         assert(path != "") # symlinks can't occur for file handles
         getSymlinkFileKind(path)
       else:
-        pcFile
+        (pcFile, S_ISREG(rawInfo.st_mode))
 
 when defined(js):
   when not declared(FileHandle):
@@ -1215,7 +1217,8 @@ proc getFileInfo*(path: string, followSymlink = true): FileInfo {.noWeirdTarget.
   ##
   ## When `followSymlink` is true (default), symlinks are followed and the
   ## information retrieved is information related to the symlink's target.
-  ## Otherwise, information on the symlink itself is retrieved.
+  ## Otherwise, information on the symlink itself is retrieved (however,
+  ## field `isRegular` is still determined from the target on Unix).
   ##
   ## If the information cannot be retrieved, such as when the path doesn't
   ## exist, or when permission restrictions prevent the program from retrieving
diff --git a/lib/std/dirs.nim b/lib/std/dirs.nim
index e89bfc668..304075a6f 100644
--- a/lib/std/dirs.nim
+++ b/lib/std/dirs.nim
@@ -121,30 +121,33 @@ iterator walkDirs*(pattern: Path): Path {.tags: [ReadDirEffect].} =
   for p in walkDirs(pattern.string):
     yield Path(p)
 
-iterator walkDir*(dir: Path; relative = false, checkDir = false):
+iterator walkDir*(dir: Path; relative = false, checkDir = false,
+                 onlyRegular = false):
     tuple[kind: PathComponent, path: Path] {.tags: [ReadDirEffect].} =
   ## Walks over the directory `dir` and yields for each directory or file in
   ## `dir`. The component type and full path for each item are returned.
   ##
-  ## Walking is not recursive. If ``relative`` is true (default: false)
-  ## the resulting path is shortened to be relative to ``dir``.
-  ##
-  ## If `checkDir` is true, `OSError` is raised when `dir`
-  ## doesn't exist.
-  for (k, p) in walkDir(dir.string, relative, checkDir):
+  ## Walking is not recursive.
+  ## * If `relative` is true (default: false)
+  ##   the resulting path is shortened to be relative to ``dir``,
+  ##   otherwise the full path is returned.
+  ## * If `checkDir` is true, `OSError` is raised when `dir`
+  ##   doesn't exist.
+  ## * If `onlyRegular` is true, then (besides all directories) only *regular*
+  ##   files (**without** special "file" objects like FIFOs, device files,
+  ##   etc) will be yielded on Unix.
+  for (k, p) in walkDir(dir.string, relative, checkDir, onlyRegular):
     yield (k, Path(p))
 
 iterator walkDirRec*(dir: Path,
                      yieldFilter = {pcFile}, followFilter = {pcDir},
-                     relative = false, checkDir = false): Path {.tags: [ReadDirEffect].} =
+                     relative = false, checkDir = false, onlyRegular = false):
+                    Path {.tags: [ReadDirEffect].} =
   ## Recursively walks over the directory `dir` and yields for each file
   ## or directory in `dir`.
   ##
-  ## If ``relative`` is true (default: false) the resulting path is
-  ## shortened to be relative to ``dir``, otherwise the full path is returned.
-  ##
-  ## If `checkDir` is true, `OSError` is raised when `dir`
-  ## doesn't exist.
+  ## Options `relative`, `checkdir`, `onlyRegular` are explained in
+  ## [walkDir iterator] description.
   ##
   ## .. warning:: Modifying the directory structure while the iterator
   ##   is traversing may result in undefined behavior!
@@ -173,5 +176,6 @@ iterator walkDirRec*(dir: Path,
   ## * `walkFiles iterator`_
   ## * `walkDirs iterator`_
   ## * `walkDir iterator`_
-  for p in walkDirRec(dir.string, yieldFilter, followFilter, relative, checkDir):
+  for p in walkDirRec(dir.string, yieldFilter, followFilter, relative,
+                      checkDir, onlyRegular):
     yield Path(p)
diff --git a/lib/std/private/oscommon.nim b/lib/std/private/oscommon.nim
index dd66d137e..dbed1ba96 100644
--- a/lib/std/private/oscommon.nim
+++ b/lib/std/private/oscommon.nim
@@ -77,14 +77,17 @@ type
 
 
 when defined(posix) and not weirdTarget:
-  proc getSymlinkFileKind*(path: string): PathComponent =
+  proc getSymlinkFileKind*(path: string):
+      tuple[pc: PathComponent, isRegular: bool] =
     # Helper function.
     var s: Stat
     assert(path != "")
-    if stat(path, s) == 0'i32 and S_ISDIR(s.st_mode):
-      result = pcLinkToDir
-    else:
-      result = pcLinkToFile
+    result = (pcLinkToFile, true)
+    if stat(path, s) == 0'i32:
+      if S_ISDIR(s.st_mode):
+        result = (pcLinkToDir, true)
+      elif not S_ISREG(s.st_mode):
+        result = (pcLinkToFile, false)
 
 proc tryMoveFSObject*(source, dest: string, isDir: bool): bool {.noWeirdTarget.} =
   ## Moves a file (or directory if `isDir` is true) from `source` to `dest`.
diff --git a/lib/std/private/osdirs.nim b/lib/std/private/osdirs.nim
index 486b1445b..2f98257e2 100644
--- a/lib/std/private/osdirs.nim
+++ b/lib/std/private/osdirs.nim
@@ -154,16 +154,21 @@ proc staticWalkDir(dir: string; relative: bool): seq[
                   tuple[kind: PathComponent, path: string]] =
   discard
 
-iterator walkDir*(dir: string; relative = false, checkDir = false):
+iterator walkDir*(dir: string; relative = false, checkDir = false,
+                  onlyRegular = false):
   tuple[kind: PathComponent, path: string] {.tags: [ReadDirEffect].} =
   ## Walks over the directory `dir` and yields for each directory or file in
   ## `dir`. The component type and full path for each item are returned.
   ##
-  ## Walking is not recursive. If ``relative`` is true (default: false)
-  ## the resulting path is shortened to be relative to ``dir``.
-  ##
-  ## If `checkDir` is true, `OSError` is raised when `dir`
-  ## doesn't exist.
+  ## Walking is not recursive.
+  ## * If `relative` is true (default: false)
+  ##   the resulting path is shortened to be relative to ``dir``,
+  ##   otherwise the full path is returned.
+  ## * If `checkDir` is true, `OSError` is raised when `dir`
+  ##   doesn't exist.
+  ## * If `onlyRegular` is true, then (besides all directories) only *regular*
+  ##   files (**without** special "file" objects like FIFOs, device files,
+  ##   etc) will be yielded on Unix.
   ##
   ## **Example:**
   ##
@@ -234,24 +239,30 @@ iterator walkDir*(dir: string; relative = false, checkDir = false):
               y = path
             var k = pcFile
 
+            template resolveSymlink() =
+              var isRegular: bool
+              (k, isRegular) = getSymlinkFileKind(path)
+              if onlyRegular and not isRegular: continue
+
             template kSetGeneric() =  # pure Posix component `k` resolution
               if lstat(path.cstring, s) < 0'i32: continue  # don't yield
               elif S_ISDIR(s.st_mode):
                 k = pcDir
               elif S_ISLNK(s.st_mode):
-                k = getSymlinkFileKind(path)
+                resolveSymlink()
+              elif onlyRegular and not S_ISREG(s.st_mode): continue
 
             when defined(linux) or defined(macosx) or
                  defined(bsd) or defined(genode) or defined(nintendoswitch):
               case x.d_type
               of DT_DIR: k = pcDir
               of DT_LNK:
-                if dirExists(path): k = pcLinkToDir
-                else: k = pcLinkToFile
+                resolveSymlink()
               of DT_UNKNOWN:
                 kSetGeneric()
-              else: # e.g. DT_REG etc
-                discard # leave it as pcFile
+              else: # DT_REG or special "files" like FIFOs
+                if onlyRegular and x.d_type != DT_REG: continue
+                else: discard # leave it as pcFile
             else:  # assuming that field `d_type` is not present
               kSetGeneric()
 
@@ -259,15 +270,13 @@ iterator walkDir*(dir: string; relative = false, checkDir = false):
 
 iterator walkDirRec*(dir: string,
                      yieldFilter = {pcFile}, followFilter = {pcDir},
-                     relative = false, checkDir = false): string {.tags: [ReadDirEffect].} =
+                     relative = false, checkDir = false, onlyRegular = false):
+                    string {.tags: [ReadDirEffect].} =
   ## Recursively walks over the directory `dir` and yields for each file
   ## or directory in `dir`.
   ##
-  ## If ``relative`` is true (default: false) the resulting path is
-  ## shortened to be relative to ``dir``, otherwise the full path is returned.
-  ##
-  ## If `checkDir` is true, `OSError` is raised when `dir`
-  ## doesn't exist.
+  ## Options `relative`, `checkdir`, `onlyRegular` are explained in
+  ## [walkDir iterator] description.
   ##
   ## .. warning:: Modifying the directory structure while the iterator
   ##   is traversing may result in undefined behavior!
@@ -301,7 +310,8 @@ iterator walkDirRec*(dir: string,
   var checkDir = checkDir
   while stack.len > 0:
     let d = stack.pop()
-    for k, p in walkDir(dir / d, relative = true, checkDir = checkDir):
+    for k, p in walkDir(dir / d, relative = true, checkDir = checkDir,
+                        onlyRegular = onlyRegular):
       let rel = d / p
       if k in {pcDir, pcLinkToDir} and k in followFilter:
         stack.add rel
diff --git a/tests/stdlib/tgetfileinfo.nim b/tests/stdlib/tgetfileinfo.nim
index 0f21622d0..f08f59d87 100644
--- a/tests/stdlib/tgetfileinfo.nim
+++ b/tests/stdlib/tgetfileinfo.nim
@@ -4,7 +4,7 @@ discard """
 """
 
 import os, strutils
-import std/syncio
+import std/[syncio, assertions]
 # Cases
 #  1 - String : Existing File : Symlink true
 #  2 - String : Existing File : Symlink false
@@ -127,10 +127,37 @@ proc testGetFileInfo =
       echo pcLinkToDir
       echo pcLinkToFile
 
+    doAssert dirInfo.isRegular == true
+    doAssert fileInfo.isRegular == true
+    when defined(posix):
+      doAssert linkDirInfo.isRegular == true
+      doAssert linkFileInfo.isRegular == true
+
     removeDir(dirPath)
     removeFile(filePath)
     when defined(posix):
       removeFile(linkDirPath)
       removeFile(linkFilePath)
 
+  # Test that `isRegular` is set correctly
+  block:
+    when defined(posix):
+      let
+        tmp = getTempDir()
+        fifoPath     = tmp / "test-fifo"
+        linkFifoPath = tmp / "test-link-fifo"
+
+      doAssert execShellCmd("mkfifo " & fifoPath) == 0
+      createSymlink(fifoPath, linkFifoPath)
+
+      let
+        fifoInfo = getFileInfo(fifoPath)
+        linkFifoInfo = getFileInfo(linkFifoPath)
+
+      doAssert fifoInfo.isRegular == false
+      doAssert linkFifoInfo.isRegular == false
+
+      removeFile(fifoPath)
+      removeFile(linkFifoPath)
+
 testGetFileInfo()
diff --git a/tests/stdlib/tos.nim b/tests/stdlib/tos.nim
index ce0371c53..2ade042ae 100644
--- a/tests/stdlib/tos.nim
+++ b/tests/stdlib/tos.nim
@@ -344,6 +344,8 @@ block walkDirRec:
 
   removeDir("walkdir_test")
 
+import std/sequtils
+
 block: # walkDir
   doAssertRaises(OSError):
     for a in walkDir("nonexistent", checkDir = true): discard
@@ -358,6 +360,21 @@ block: # walkDir
         doAssert k == pcLinkToDir
       removeDir("walkdir_test")
 
+  when defined(posix):
+    block walkDirRegular:
+      createDir("walkdir_test")
+      doAssert execShellCmd("mkfifo walkdir_test/fifo") == 0
+      createSymlink("fifo", "walkdir_test/fifo_link")
+      let withSpecialFiles = toSeq(walkDir("walkdir_test", relative = true))
+      doAssert (withSpecialFiles.len == 2 and
+                (pcFile, "fifo") in withSpecialFiles and
+                (pcLinkToFile, "fifo_link") in withSpecialFiles)
+      # now Unix special files are excluded from walkdir output:
+      let onlyRegularFiles = toSeq(walkDir("walkdir_test", relative = true,
+                                           onlyRegular = true))
+      doAssert onlyRegularFiles.len == 0
+      removeDir("walkdir_test")
+
 block normalizedPath:
   doAssert normalizedPath("") == ""
   block relative:
@@ -708,8 +725,6 @@ block: # isAdmin
   # In Azure on POSIX tests run as a normal user
   if isAzure and defined(posix): doAssert not isAdmin()
 
-import std/sequtils
-
 when doslikeFileSystem:
   import std/private/ntpath
 
diff --git a/tools/nimgrep.nim b/tools/nimgrep.nim
index e847528d1..3d2f04c47 100644
--- a/tools/nimgrep.nim
+++ b/tools/nimgrep.nim
@@ -989,7 +989,7 @@ iterator walkDirBasic(dir: string, walkOptC: WalkOptComp[Pattern]): string
     let rightDirForFiles = d.isRightDirectory(walkOptC)
     var files = newSeq[string]()
     var dirs = newSeq[string]()
-    for kind, path in walkDir(d):
+    for kind, path in walkDir(d, onlyRegular = true):
       case kind
       of pcFile:
         if path.hasRightPath(walkOptC) and rightDirForFiles: