libstore: ignore broken symlinks in ssl-cert-file default

Also tweak `pathAccessible` to ignore other relevant errors too. It was
documented as ignoring permission errors but it was only ignoring
`EPERM`, which comes from the darwin sandbox, and not ignoring `EACCESS`
which is the real permission error. I figured it also makes sense to
ignore `ELOOP`.

Fixes: #560
Change-Id: Ibb849b68d07386eb80afb52b57f7d12b3a48a202
This commit is contained in:
Lily Ballard 2024-10-29 23:13:13 -07:00
parent 11950a0a79
commit 684f93e783
4 changed files with 49 additions and 8 deletions

View file

@ -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`.

View file

@ -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 "";
}

View file

@ -214,6 +214,19 @@ struct stat lstat(const Path & path)
return st;
}
std::optional<struct stat> maybeStat(const Path & path)
{
std::optional<struct stat> 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<struct stat> maybeLstat(const Path & path)
{
std::optional<struct stat> st{std::in_place};
@ -232,16 +245,25 @@ 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;
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;
}
}
}
Path readLink(const Path & path)

View file

@ -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<struct stat> 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