diff --git a/doc/manual/rl-next/ssl-cert-symlink.md b/doc/manual/rl-next/ssl-cert-symlink.md new file mode 100644 index 000000000..63bff61e1 --- /dev/null +++ b/doc/manual/rl-next/ssl-cert-symlink.md @@ -0,0 +1,12 @@ +--- +synopsis: Ignore broken `/etc/ssl/certs/ca-certificates.crt` symlink +issues: [fj#560] +cls: [2144] +category: Fixes +credits: lilyball +--- + +[`ssl-cert-file`](@docroot@/command-ref/conf-file.md#conf-ssl-cert-file) now checks its default +value for a broken symlink before using it. This fixes a problem on macOS where uninstalling +nix-darwin may leave behind a broken symlink at `/etc/ssl/certs/ca-certificates.crt` that was +stopping Lix from using the cert at `/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt`. diff --git a/src/libstore/globals.cc b/src/libstore/globals.cc index 9377ac936..cf72cfcbc 100644 --- a/src/libstore/globals.cc +++ b/src/libstore/globals.cc @@ -263,7 +263,7 @@ bool Settings::isWSL1() Path Settings::getDefaultSSLCertFile() { for (auto & fn : {"/etc/ssl/certs/ca-certificates.crt", "/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt"}) - if (pathAccessible(fn)) return fn; + if (pathAccessible(fn, true)) return fn; return ""; } diff --git a/src/libutil/file-system.cc b/src/libutil/file-system.cc index c4ffb1d0c..cc87375f8 100644 --- a/src/libutil/file-system.cc +++ b/src/libutil/file-system.cc @@ -214,6 +214,19 @@ struct stat lstat(const Path & path) return st; } +std::optional maybeStat(const Path & path) +{ + std::optional st{std::in_place}; + if (stat(path.c_str(), &*st)) + { + if (errno == ENOENT || errno == ENOTDIR) + st.reset(); + else + throw SysError("getting status of '%s'", path); + } + return st; +} + std::optional maybeLstat(const Path & path) { std::optional st{std::in_place}; @@ -232,14 +245,23 @@ bool pathExists(const Path & path) return maybeLstat(path).has_value(); } -bool pathAccessible(const Path & path) +bool pathAccessible(const Path & path, bool resolveSymlinks) { try { - return pathExists(path); + return resolveSymlinks ? maybeStat(path).has_value() : pathExists(path); } catch (SysError & e) { - // swallow EPERM - if (e.errNo == EPERM) return false; - throw; + switch (e.errNo) { + case EPERM: + // operation not permitted error, can occur in darwin sandbox + case EACCES: + // permission error + case ELOOP: + // path component is a looping symlink + // this seems like a reasonable condition to handle as well + return false; + default: + throw; + } } } diff --git a/src/libutil/file-system.hh b/src/libutil/file-system.hh index 2547c63b5..08ec54632 100644 --- a/src/libutil/file-system.hh +++ b/src/libutil/file-system.hh @@ -123,6 +123,12 @@ bool isDirOrInDir(std::string_view path, std::string_view dir); struct stat stat(const Path & path); struct stat lstat(const Path & path); +/** + * `stat` the given path if it exists. + * @return std::nullopt if the path doesn't exist, or an optional containing the result of `stat` otherwise + */ +std::optional maybeStat(const Path & path); + /** * `lstat` the given path if it exists. * @return std::nullopt if the path doesn't exist, or an optional containing the result of `lstat` otherwise @@ -137,10 +143,11 @@ bool pathExists(const Path & path); /** * A version of pathExists that returns false on a permission error. * Useful for inferring default paths across directories that might not - * be readable. + * be readable. Optionally resolves symlinks to determine if the real + * path exists. * @return true iff the given path can be accessed and exists */ -bool pathAccessible(const Path & path); +bool pathAccessible(const Path & path, bool resolveSymlinks = false); /** * Read the contents (target) of a symbolic link. The result is not