-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Put
canonicaliseTimestampAndPermissions
in its own header/file
It is not inherently tied to `LocalStore`, it could probably even go in `libnixutil`. Functions not attached to `LocalStore` should not be declared in `local-store.hh`. I am moving it to facilitate experimenting for #9344. If canonicalisation should be done client-side in client-side builds, there wouldn't be a `LocalStore` at all so having to include that header to get this freestanding function is cumbersome and wrong. Perhaps canonicalisation should still be done server-side for security reasons --- I don't mean to make that judgement call now --- but even if so, this freestanding function still isn't connected to `LocalStore` so while less urgent it is still better to move out of this header.
- Loading branch information
1 parent
c4a74d6
commit ea23fca
Showing
7 changed files
with
217 additions
and
192 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
#include <sys/xattr.h> | ||
|
||
#include "local-fs-canonicalise.hh" | ||
#include "file-system.hh" | ||
#include "signals.hh" | ||
#include "util.hh" | ||
#include "globals.hh" | ||
#include "store-api.hh" | ||
|
||
namespace nix { | ||
|
||
const time_t mtimeStore = 1; /* 1 second into the epoch */ | ||
|
||
static void canonicaliseTimestampAndPermissions(const Path & path, const struct stat & st) | ||
{ | ||
if (!S_ISLNK(st.st_mode)) { | ||
|
||
/* Mask out all type related bits. */ | ||
mode_t mode = st.st_mode & ~S_IFMT; | ||
|
||
if (mode != 0444 && mode != 0555) { | ||
mode = (st.st_mode & S_IFMT) | ||
| 0444 | ||
| (st.st_mode & S_IXUSR ? 0111 : 0); | ||
if (chmod(path.c_str(), mode) == -1) | ||
throw SysError("changing mode of '%1%' to %2$o", path, mode); | ||
} | ||
|
||
} | ||
|
||
if (st.st_mtime != mtimeStore) { | ||
struct timeval times[2]; | ||
times[0].tv_sec = st.st_atime; | ||
times[0].tv_usec = 0; | ||
times[1].tv_sec = mtimeStore; | ||
times[1].tv_usec = 0; | ||
#if HAVE_LUTIMES | ||
if (lutimes(path.c_str(), times) == -1) | ||
if (errno != ENOSYS || | ||
(!S_ISLNK(st.st_mode) && utimes(path.c_str(), times) == -1)) | ||
#else | ||
if (!S_ISLNK(st.st_mode) && utimes(path.c_str(), times) == -1) | ||
#endif | ||
throw SysError("changing modification time of '%1%'", path); | ||
} | ||
} | ||
|
||
|
||
void canonicaliseTimestampAndPermissions(const Path & path) | ||
{ | ||
canonicaliseTimestampAndPermissions(path, lstat(path)); | ||
} | ||
|
||
|
||
static void canonicalisePathMetaData_( | ||
const Path & path, | ||
std::optional<std::pair<uid_t, uid_t>> uidRange, | ||
InodesSeen & inodesSeen) | ||
{ | ||
checkInterrupt(); | ||
|
||
#if __APPLE__ | ||
/* Remove flags, in particular UF_IMMUTABLE which would prevent | ||
the file from being garbage-collected. FIXME: Use | ||
setattrlist() to remove other attributes as well. */ | ||
if (lchflags(path.c_str(), 0)) { | ||
if (errno != ENOTSUP) | ||
throw SysError("clearing flags of path '%1%'", path); | ||
} | ||
#endif | ||
|
||
auto st = lstat(path); | ||
|
||
/* Really make sure that the path is of a supported type. */ | ||
if (!(S_ISREG(st.st_mode) || S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode))) | ||
throw Error("file '%1%' has an unsupported type", path); | ||
|
||
#if __linux__ | ||
/* Remove extended attributes / ACLs. */ | ||
ssize_t eaSize = llistxattr(path.c_str(), nullptr, 0); | ||
|
||
if (eaSize < 0) { | ||
if (errno != ENOTSUP && errno != ENODATA) | ||
throw SysError("querying extended attributes of '%s'", path); | ||
} else if (eaSize > 0) { | ||
std::vector<char> eaBuf(eaSize); | ||
|
||
if ((eaSize = llistxattr(path.c_str(), eaBuf.data(), eaBuf.size())) < 0) | ||
throw SysError("querying extended attributes of '%s'", path); | ||
|
||
for (auto & eaName: tokenizeString<Strings>(std::string(eaBuf.data(), eaSize), std::string("\000", 1))) { | ||
if (settings.ignoredAcls.get().count(eaName)) continue; | ||
if (lremovexattr(path.c_str(), eaName.c_str()) == -1) | ||
throw SysError("removing extended attribute '%s' from '%s'", eaName, path); | ||
} | ||
} | ||
#endif | ||
|
||
/* Fail if the file is not owned by the build user. This prevents | ||
us from messing up the ownership/permissions of files | ||
hard-linked into the output (e.g. "ln /etc/shadow $out/foo"). | ||
However, ignore files that we chown'ed ourselves previously to | ||
ensure that we don't fail on hard links within the same build | ||
(i.e. "touch $out/foo; ln $out/foo $out/bar"). */ | ||
if (uidRange && (st.st_uid < uidRange->first || st.st_uid > uidRange->second)) { | ||
if (S_ISDIR(st.st_mode) || !inodesSeen.count(Inode(st.st_dev, st.st_ino))) | ||
throw BuildError("invalid ownership on file '%1%'", path); | ||
mode_t mode = st.st_mode & ~S_IFMT; | ||
assert(S_ISLNK(st.st_mode) || (st.st_uid == geteuid() && (mode == 0444 || mode == 0555) && st.st_mtime == mtimeStore)); | ||
return; | ||
} | ||
|
||
inodesSeen.insert(Inode(st.st_dev, st.st_ino)); | ||
|
||
canonicaliseTimestampAndPermissions(path, st); | ||
|
||
/* Change ownership to the current uid. If it's a symlink, use | ||
lchown if available, otherwise don't bother. Wrong ownership | ||
of a symlink doesn't matter, since the owning user can't change | ||
the symlink and can't delete it because the directory is not | ||
writable. The only exception is top-level paths in the Nix | ||
store (since that directory is group-writable for the Nix build | ||
users group); we check for this case below. */ | ||
if (st.st_uid != geteuid()) { | ||
#if HAVE_LCHOWN | ||
if (lchown(path.c_str(), geteuid(), getegid()) == -1) | ||
#else | ||
if (!S_ISLNK(st.st_mode) && | ||
chown(path.c_str(), geteuid(), getegid()) == -1) | ||
#endif | ||
throw SysError("changing owner of '%1%' to %2%", | ||
path, geteuid()); | ||
} | ||
|
||
if (S_ISDIR(st.st_mode)) { | ||
DirEntries entries = readDirectory(path); | ||
for (auto & i : entries) | ||
canonicalisePathMetaData_(path + "/" + i.name, uidRange, inodesSeen); | ||
} | ||
} | ||
|
||
|
||
void canonicalisePathMetaData( | ||
const Path & path, | ||
std::optional<std::pair<uid_t, uid_t>> uidRange, | ||
InodesSeen & inodesSeen) | ||
{ | ||
canonicalisePathMetaData_(path, uidRange, inodesSeen); | ||
|
||
/* On platforms that don't have lchown(), the top-level path can't | ||
be a symlink, since we can't change its ownership. */ | ||
auto st = lstat(path); | ||
|
||
if (st.st_uid != geteuid()) { | ||
assert(S_ISLNK(st.st_mode)); | ||
throw Error("wrong ownership of top-level store path '%1%'", path); | ||
} | ||
} | ||
|
||
|
||
void canonicalisePathMetaData(const Path & path, | ||
std::optional<std::pair<uid_t, uid_t>> uidRange) | ||
{ | ||
InodesSeen inodesSeen; | ||
canonicalisePathMetaData(path, uidRange, inodesSeen); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
#pragma once | ||
///@file | ||
|
||
#include <sys/stat.h> | ||
#include <sys/time.h> | ||
|
||
#include "types.hh" | ||
#include "error.hh" | ||
|
||
namespace nix { | ||
|
||
typedef std::pair<dev_t, ino_t> Inode; | ||
typedef std::set<Inode> InodesSeen; | ||
|
||
|
||
/** | ||
* "Fix", or canonicalise, the meta-data of the files in a store path | ||
* after it has been built. In particular: | ||
* | ||
* - the last modification date on each file is set to 1 (i.e., | ||
* 00:00:01 1/1/1970 UTC) | ||
* | ||
* - the permissions are set of 444 or 555 (i.e., read-only with or | ||
* without execute permission; setuid bits etc. are cleared) | ||
* | ||
* - the owner and group are set to the Nix user and group, if we're | ||
* running as root. | ||
* | ||
* If uidRange is not empty, this function will throw an error if it | ||
* encounters files owned by a user outside of the closed interval | ||
* [uidRange->first, uidRange->second]. | ||
*/ | ||
void canonicalisePathMetaData( | ||
const Path & path, | ||
std::optional<std::pair<uid_t, uid_t>> uidRange, | ||
InodesSeen & inodesSeen); | ||
void canonicalisePathMetaData( | ||
const Path & path, | ||
std::optional<std::pair<uid_t, uid_t>> uidRange); | ||
|
||
void canonicaliseTimestampAndPermissions(const Path & path); | ||
|
||
MakeError(PathInUse, Error); | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.