diff --git a/modules/lib/fs.nix b/modules/lib/fs.nix index eeeda4b4..e21596ef 100644 --- a/modules/lib/fs.nix +++ b/modules/lib/fs.nix @@ -10,6 +10,10 @@ in rec { wantedText = text: wantedSymlink { inherit text; }; # Type: derefSymlinkOrNul :: config.sane.fs.type -> str -> (str|null) + # N.B.: the returned path, even though absolute, might not be "canonical". + # for example, if the symlink is relative, to "../Pictures", this function + # could return "/home/colin/Music/../Pictures". + # consider feeding the output into `sane-lib.path.realpath` to correct this. derefSymlinkOrNull = fs: logical: let symlinkedPrefixes = lib.filter (p: ((fs."${p}" or {}).symlink or null) != null) @@ -20,7 +24,6 @@ in rec { firstSymlinkDestAbs = if lib.hasPrefix "/" firstSymlinkDest then firstSymlinkDest else - # TODO: path canonicalization (for i.e. relative symlinks) sane-path.join [ firstSymlinkParent firstSymlinkDest ]; in if symlinkedPrefixes != [] then diff --git a/modules/lib/path.nix b/modules/lib/path.nix index 868c0324..e71bc4d0 100644 --- a/modules/lib/path.nix +++ b/modules/lib/path.nix @@ -20,6 +20,22 @@ let path = rec { # always starting with '/', never trailing with '/' unless it's the empty (root) path norm = str: path.join (path.split str); + # removes ".." and "." components from a path component list, by evaluating them logically. + # Type realpathArray: [ str ] -> [ str ] + realpathArray = arr: lib.foldl' + (acc: next: + if next == ".." then lib.init acc + else if next == "." then acc + else acc ++ [ next ] + ) + [] + arr + ; + # removes ".." and "." from a path, by evaluating them logically. + # has the effect of also normalizing the path, in the process. + # Type realpath: str -> str + realpath = str: path.join (path.realpathArray (path.split str)); + # return the parent directory. doesn't care about leading/trailing slashes. # the parent of "/" is "/". parent = str: path.norm (builtins.dirOf (path.norm str)); diff --git a/modules/programs/default.nix b/modules/programs/default.nix index 95bc4e19..710e5b5e 100644 --- a/modules/programs/default.nix +++ b/modules/programs/default.nix @@ -46,11 +46,12 @@ let # removeStorePaths: [ str ] -> [ str ], but remove store paths, because nix evals aren't allowed to contain any (for purity reasons?) removeStorePaths = paths: lib.filter (p: !(lib.hasPrefix "/nix/store" p)) paths; + makeCanonical = paths: builtins.map path-lib.realpath paths; # derefSymlinks: [ str ] -> [ str ]: for each path which is a symlink (or a child of a symlink'd dir), dereference one layer of symlink. else, drop it from the list. derefSymlinks' = paths: builtins.map (fs-lib.derefSymlinkOrNull config.sane.fs) paths; derefSymlinks = paths: lib.filter (p: p != null) (derefSymlinks' paths); # expandSymlinksOnce: [ str ] -> [ str ], returning all the original paths plus dereferencing any symlinks and adding their targets to this list. - expandSymlinksOnce = paths: lib.unique (paths ++ removeStorePaths (derefSymlinks paths)); + expandSymlinksOnce = paths: lib.unique (paths ++ removeStorePaths (makeCanonical (derefSymlinks paths))); expandSymlinks = paths: lib.converge expandSymlinksOnce paths; vpn = lib.findSingle (v: v.default) null null (builtins.attrValues config.sane.vpn);