Skip to content

Commit

Permalink
primop: add readFileType, optimize readDir
Browse files Browse the repository at this point in the history
Allows checking directory entry type of a single file/directory.

This was added to optimize the use of `builtins.readDir` on some
filesystems and operating systems which cannot detect this information
using POSIX's `readdir`.

Previously `builtins.readDir` would eagerly use system calls to lookup
these filetypes using other interfaces; this change makes these
operations lazy in the attribute values for each file with application
of `builtins.readFileType`.
  • Loading branch information
aakropotkin committed Jan 22, 2023
1 parent 04de0dd commit 153ee46
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 8 deletions.
8 changes: 8 additions & 0 deletions doc/manual/src/release-notes/rl-next.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
# Release X.Y (202?-??-??)

* A new function `builtins.readFileType` is available. It is similar to
`builtins.readDir` but acts on a single file or directory.

* The `builtins.readDir` function has been optimized when encountering unknown
file types from POSIX's `readdir`. In such cases the type of each file is/was
discovered by making multiple syscalls. This change makes these operations
lazy such that these lookups will only be performed if the attribute is used.
This optimization effects a minority of filesystems and operating systems.
64 changes: 57 additions & 7 deletions src/libexpr/primops.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1646,23 +1646,73 @@ static RegisterPrimOp primop_hashFile({
.fun = prim_hashFile,
});


/* Stringize a directory entry enum. Used by `readFileType' and `readDir'. */
static const char * dirEntTypeToString(unsigned char dtType)
{
/* Enum DT_(DIR|LNK|REG|UNKNOWN) */
switch(dtType) {
case DT_REG: return "regular"; break;
case DT_DIR: return "directory"; break;
case DT_LNK: return "symlink"; break;
default: return "unknown"; break;
}
return "unknown"; /* Unreachable */
}


static void prim_readFileType(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
auto path = realisePath(state, pos, *args[0]);
/* Retrieve the directory entry type and stringize it. */
v.mkString(dirEntTypeToString(getFileType(path)));
}

static RegisterPrimOp primop_readFileType({
.name = "__readFileType",
.args = {"p"},
.doc = R"(
Determine the directory entry type of a filesystem node, being
one of "directory", "regular", "symlink", or "unknown".
)",
.fun = prim_readFileType,
});

/* Read a directory (without . or ..) */
static void prim_readDir(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
auto path = realisePath(state, pos, *args[0]);

// Retrieve directory entries for all nodes in a directory.
// This is similar to `getFileType` but is optimized to reduce system calls
// on many systems.
DirEntries entries = readDirectory(path);

auto attrs = state.buildBindings(entries.size());

// If we hit unknown directory entry types we may need to fallback to
// using `getFileType` on some systems.
// In order to reduce system calls we make each lookup lazy by using
// `builtins.readFileType` application.
Value * readFileType = nullptr;

for (auto & ent : entries) {
if (ent.type == DT_UNKNOWN)
ent.type = getFileType(path + "/" + ent.name);
attrs.alloc(ent.name).mkString(
ent.type == DT_REG ? "regular" :
ent.type == DT_DIR ? "directory" :
ent.type == DT_LNK ? "symlink" :
"unknown");
auto & attr = attrs.alloc(ent.name);
if (ent.type == DT_UNKNOWN) {
// Some filesystems or operating systems may not be able to return
// detailed node info quickly in this case we produce a thunk to
// query the file type lazily.
auto epath = state.allocValue();
Path path2 = path + "/" + ent.name;
epath->mkString(path2);
if (!readFileType)
readFileType = &state.getBuiltin("readFileType");
attr.mkApp(readFileType, epath);
} else {
// This branch of the conditional is much more likely.
// Here we just stringize the directory entry type.
attr.mkString(dirEntTypeToString(ent.type));
}
}

v.mkAttrs(attrs);
Expand Down
2 changes: 1 addition & 1 deletion tests/lang/eval-okay-readDir.exp
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{ bar = "regular"; foo = "directory"; }
{ bar = "regular"; foo = "directory"; ldir = "symlink"; linked = "symlink"; }
1 change: 1 addition & 0 deletions tests/lang/eval-okay-readFileType.exp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ bar = "regular"; foo = "directory"; ldir = "symlink"; linked = "symlink"; }
6 changes: 6 additions & 0 deletions tests/lang/eval-okay-readFileType.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
bar = builtins.readFileType ./readDir/bar;
foo = builtins.readFileType ./readDir/foo;
linked = builtins.readFileType ./readDir/linked;
ldir = builtins.readFileType ./readDir/ldir;
}
1 change: 1 addition & 0 deletions tests/lang/readDir/ldir
1 change: 1 addition & 0 deletions tests/lang/readDir/linked

0 comments on commit 153ee46

Please sign in to comment.