diff --git a/src/libstore/meson.build b/src/libstore/meson.build index 2dc70a0b0c5..2deb42fdcea 100644 --- a/src/libstore/meson.build +++ b/src/libstore/meson.build @@ -151,6 +151,12 @@ endif configdata_priv.set('HAVE_SECCOMP', seccomp.found().to_int()) deps_private += seccomp +if host_machine.system() == 'freebsd' + # libjail is not in freebsd.libc, but there's no discovery mechanism + libjail = declare_dependency(link_args : [ '-ljail' ]) + deps_other += libjail +endif + nlohmann_json = dependency('nlohmann_json', version : '>= 3.9') deps_public += nlohmann_json diff --git a/src/libstore/package.nix b/src/libstore/package.nix index 968c4e3eb91..640e3d4ad5f 100644 --- a/src/libstore/package.nix +++ b/src/libstore/package.nix @@ -5,6 +5,7 @@ unixtools, darwin, + freebsd, nix-util, boost, @@ -16,6 +17,7 @@ sqlite, busybox-sandbox-shell ? null, + pkgsStatic, # Configuration Options @@ -65,6 +67,7 @@ mkMesonLibrary (finalAttrs: { sqlite ] ++ lib.optional stdenv.hostPlatform.isLinux libseccomp + ++ lib.optional stdenv.hostPlatform.isFreeBSD freebsd.libjail ++ lib.optional withAWS aws-crt-cpp; propagatedBuildInputs = [ @@ -79,6 +82,9 @@ mkMesonLibrary (finalAttrs: { ] ++ lib.optionals stdenv.hostPlatform.isLinux [ (lib.mesonOption "sandbox-shell" "${busybox-sandbox-shell}/bin/busybox") + ] + ++ lib.optionals stdenv.hostPlatform.isFreeBSD [ + (lib.mesonOption "sandbox-shell" "${pkgsStatic.bash}/bin/bash") ]; meta = { diff --git a/src/libstore/unix/build/chroot-derivation-builder.cc b/src/libstore/unix/build/chroot-derivation-builder.cc index 91866d1c060..fd54dcf963f 100644 --- a/src/libstore/unix/build/chroot-derivation-builder.cc +++ b/src/libstore/unix/build/chroot-derivation-builder.cc @@ -1,4 +1,4 @@ -#ifdef __linux__ +#if defined(__linux__) || defined(__FreeBSD__) namespace nix { @@ -52,6 +52,8 @@ struct ChrootDerivationBuilder : virtual DerivationBuilderImpl return buildUser->getGID(); } + virtual void extraChrootParentDirCleanup(const std::filesystem::path & chrootParentDir) {} + void prepareSandbox() override { /* Create a temporary directory in which we set up the chroot @@ -59,6 +61,7 @@ struct ChrootDerivationBuilder : virtual DerivationBuilderImpl so that the build outputs can be moved efficiently from the chroot to their final location. */ std::filesystem::path chrootParentDir = store.toRealPath(drvPath) + ".chroot"; + extraChrootParentDirCleanup(chrootParentDir); deletePath(chrootParentDir); /* Clean up the chroot directory automatically. */ diff --git a/src/libstore/unix/build/derivation-builder.cc b/src/libstore/unix/build/derivation-builder.cc index 8e5dc0a721d..704f13c6fa1 100644 --- a/src/libstore/unix/build/derivation-builder.cc +++ b/src/libstore/unix/build/derivation-builder.cc @@ -1276,13 +1276,22 @@ void DerivationBuilderImpl::runChild(RunChildArgs args) } } +#if defined(__FreeBSD__) + /* Close all other file descriptors. This must happen before + * enterChroot for FreeBSD. */ + unix::closeExtraFDs(); +#endif + enterChroot(); if (chdir(tmpDirInSandbox().c_str()) == -1) throw SysError("changing into %1%", PathFmt(tmpDir)); - /* Close all other file descriptors. */ +#if !defined(__FreeBSD__) + /* Close all other file descriptors. This must happen after + * enterChroot for Linux. */ unix::closeExtraFDs(); +#endif /* Disable core dumps by default. */ struct rlimit limit = {0, RLIM_INFINITY}; @@ -1969,6 +1978,7 @@ StorePath DerivationBuilderImpl::makeFallbackPath(const StorePath & path) // FIXME: do this properly #include "chroot-derivation-builder.cc" #include "linux-derivation-builder.cc" +#include "freebsd-derivation-builder.cc" #include "darwin-derivation-builder.cc" #include "external-derivation-builder.cc" @@ -2031,6 +2041,11 @@ std::unique_ptr makeDerivationBuilder( return std::make_unique(store, std::move(miscMethods), std::move(params)); return std::make_unique(store, std::move(miscMethods), std::move(params)); +#elif defined(__FreeBSD__) + if (useSandbox) + return std::make_unique(store, std::move(miscMethods), std::move(params)); + + return std::make_unique(store, std::move(miscMethods), std::move(params)); #else if (useSandbox) throw Error("sandboxing builds is not supported on this platform"); diff --git a/src/libstore/unix/build/freebsd-derivation-builder.cc b/src/libstore/unix/build/freebsd-derivation-builder.cc new file mode 100644 index 00000000000..ef19418b972 --- /dev/null +++ b/src/libstore/unix/build/freebsd-derivation-builder.cc @@ -0,0 +1,501 @@ +#ifdef __FreeBSD__ + +# include +# include + +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include + +# include "nix/util/freebsd-jail.hh" + +namespace nix { + +namespace { + +struct PasswordEntry +{ + std::string name; + uid_t uid; + gid_t gid; + std::string description; + std::filesystem::path home; + std::filesystem::path shell; +}; + +using OwnedDB = std::unique_ptr<::DB, decltype([](::DB * db) { + if (db != nullptr) { + (db->close)(db); + } + })>; + +// Database open flags from FreeBSD, in case they're necessary for compatibility +static const HASHINFO db_flags = { + .bsize = 4096, + .ffactor = 32, + .nelem = 256, + .cachesize = 2 * 1024 * 1024, + .hash = nullptr, + .lorder = BIG_ENDIAN, +}; + +// Password database version +// Version 4 has been current since 2003 +static const uint8_t dbVersion = 4; + +static void serializeString(std::vector & buf, std::string const & str) +{ + buf.reserve(buf.size() + str.size() + 1); + buf.insert(buf.end(), str.begin(), str.end()); + buf.push_back(0); +} + +static void serializeInt(std::vector & buf, uint32_t num) +{ + buf.reserve(buf.size() + sizeof(num)); + // Always big endian + buf.push_back((num >> 24) & 0xff); + buf.push_back((num >> 16) & 0xff); + buf.push_back((num >> 8) & 0xff); + buf.push_back((num >> 0) & 0xff); +} + +static std::vector byNameKey(std::string const & name) +{ + std::vector buf{_PW_VERSIONED(_PW_KEYBYNAME, dbVersion)}; + buf.reserve(1 + name.size()); + // We can't use serializeString since that's null terimated + buf.insert(buf.end(), name.begin(), name.end()); + + return buf; +} + +static std::vector byNumKey(uint32_t num) +{ + std::vector buf{_PW_VERSIONED(_PW_KEYBYNUM, dbVersion)}; + serializeInt(buf, num); + + return buf; +} + +static std::vector byUidKey(uid_t uid) +{ + std::vector buf{_PW_VERSIONED(_PW_KEYBYUID, dbVersion)}; + serializeInt(buf, uid); + + return buf; +} + +static void createPasswordFiles(std::filesystem::path & chrootRootDir, std::vector & users) +{ + OwnedDB db{dbopen((chrootRootDir / "etc/pwd.db").c_str(), O_CREAT | O_RDWR | O_EXCL, 0644, DB_HASH, &db_flags)}; + + if (db == nullptr) { + throw SysError("Could not create password database"); + } + + auto dbInsert = [&db](std::vector key_buf, std::vector & value_buf) { + DBT key = {key_buf.data(), key_buf.size()}; + DBT value = {value_buf.data(), value_buf.size()}; + + if ((db->put)(db.get(), &key, &value, R_NOOVERWRITE) == -1) { + throw SysError("Could not write to password database"); + } + }; + + // Annoyingly DBT doesn't have const pointers so we need this whole shuffle + std::string versionKeyStr(_PWD_VERSION_KEY); + std::vector versionKey(versionKeyStr.begin(), versionKeyStr.end()); + std::vector versionValue{dbVersion}; + dbInsert(versionKey, versionValue); + + for (size_t i = 0; i < users.size(); i++) { + auto user = users[i]; + + // flags for non-empty fields + uint32_t fields = _PWF_NAME | _PWF_PASSWD | _PWF_UID | _PWF_GID | _PWF_GECOS | _PWF_DIR | _PWF_SHELL; + + std::vector buf; + serializeString(buf, user.name); + // pw_password is always "*" in the insecure database + serializeString(buf, std::string("*")); + serializeInt(buf, user.uid); + serializeInt(buf, user.gid); + // pw_change = 0 means no requirement to change password + serializeInt(buf, 0); + // pw_class is empty since we don't make a class database + serializeString(buf, std::string("")); + serializeString(buf, user.description); + serializeString(buf, user.home); + serializeString(buf, user.shell); + // pw_expire = 0 means password does not expire + serializeInt(buf, 0); + serializeInt(buf, fields); + + dbInsert(byNameKey(user.name), buf); + // _PW_KEYBYNUM is 1-indexed + dbInsert(byNumKey(i + 1), buf); + dbInsert(byUidKey(user.uid), buf); + } + + // FreeBSD libc doesn't use /etc/passwd, but some software might + std::string passwdContent = ""; + for (auto user : users) { + passwdContent.append( + fmt("%s:*:%d:%d:%s:%s:%s\n", user.name, user.uid, user.gid, user.description, user.home, user.shell)); + } + + writeFile(chrootRootDir / "etc/passwd", passwdContent); + + // No need to make /etc/master.passwd or /etc/spwd.db, + // our build user wouldn't be able to read them anyway +} + +} // namespace + +struct FreeBSDDerivationBuilder : virtual DerivationBuilderImpl +{ + using DerivationBuilderImpl::DerivationBuilderImpl; +}; + +template +struct iovec iovFromStaticSizedString(const char (&array)[n]) +{ + return { + .iov_base = const_cast(static_cast(&array[0])), + .iov_len = n, + }; +} + +struct iovec iovFromDynamicSizeString(const std::string & s) +{ + return { + .iov_base = const_cast(static_cast(s.c_str())), + .iov_len = s.length() + 1, + }; +} + +struct ChrootFreeBSDDerivationBuilder : ChrootDerivationBuilder, FreeBSDDerivationBuilder +{ + /* Destructors happen in reverse order from declaration */ + AutoRemoveJail autoDelJail; + std::vector autoDelMounts; + + ChrootFreeBSDDerivationBuilder( + LocalStore & store, std::unique_ptr miscMethods, DerivationBuilderParams params) + : DerivationBuilderImpl{store, std::move(miscMethods), std::move(params)} + , ChrootDerivationBuilder{store, std::move(miscMethods), std::move(params)} + , FreeBSDDerivationBuilder{store, std::move(miscMethods), std::move(params)} + { + } + + void cleanupBuild(bool force) override + { + /* Unmount and free jail id, if in use */ + autoDelMounts.clear(); + autoDelJail.cancel(); + + ChrootDerivationBuilder::cleanupBuild(force); + } + + void extraChrootParentDirCleanup(const std::filesystem::path & chrootParentDir) override + { + int count; + struct statfs * mntbuf; + if ((count = getmntinfo(&mntbuf, MNT_WAIT)) < 0) { + throw SysError("Couldn't get mount info for chroot"); + } + + for (const struct statfs & st : std::span(mntbuf, count)) { + std::filesystem::path mounted = st.f_mntonname; + if (isInDir(mounted, chrootParentDir)) { + if (unmount(mounted.c_str(), 0) < 0) { + throw SysError("Failed to unmount path %1%", mounted); + } + } + } + } + + void prepareSandbox() override + { + ChrootDerivationBuilder::prepareSandbox(); + + std::vector users{ + { + .name = "root", + .uid = 0, + .gid = 0, + .description = "Nix build user", + .home = std::filesystem::path{settings.sandboxBuildDir.get()}, + .shell = std::filesystem::path{"/noshell"}, + }, + { + .name = "nixbld", + .uid = buildUser->getUID(), + .gid = sandboxGid(), + .description = "Nix build user", + .home = std::filesystem::path{settings.sandboxBuildDir.get()}, + .shell = std::filesystem::path{"/noshell"}, + }, + { + .name = "nobody", + .uid = 65534, + .gid = 65534, + .description = "Nobody", + .home = std::filesystem::path{"/"}, + .shell = std::filesystem::path{"/noshell"}, + }, + }; + + createPasswordFiles(chrootRootDir, users); + + // FreeBSD doesn't have a group database, just write a text file + writeFile( + chrootRootDir / "etc/group", + fmt("root:x:0:\n" + "nixbld:!:%1%:\n" + "nogroup:x:65534:\n", + sandboxGid())); + + // Linux waits until after entering the child to start mounting so it doesn't + // pollute the root mount namespace. + // FreeBSD doesn't have mount namespaces, so there's no reason to wait. + + auto devpath = chrootRootDir / "dev"; + mkdir(devpath.c_str(), 0555); + mkdir((chrootRootDir / "bin").c_str(), 0555); + char errmsg[255] = ""; + struct iovec iov[8] = { + iovFromStaticSizedString("fstype"), + iovFromStaticSizedString("devfs"), + iovFromStaticSizedString("fspath"), + iovFromDynamicSizeString(devpath), + iovFromStaticSizedString("ruleset"), + iovFromStaticSizedString("4"), + iovFromStaticSizedString("errmsg"), + iovFromStaticSizedString(errmsg), + }; + if (nmount(iov, 6, 0) < 0) { + throw SysError("Failed to mount jail /dev: %1%", errmsg); + } + autoDelMounts.push_back(AutoUnmount{devpath}); + + for (auto & i : pathsInChroot) { + char errmsg[255]; + errmsg[0] = 0; + + if (i.second.source == "/proc") { + continue; // backwards compatibility + } + auto path = chrootRootDir / i.first; + + struct stat stat_buf; + if (stat(i.second.source.c_str(), &stat_buf) < 0) { + throw SysError("stat"); + } + + // mount points must exist and be the right type + if (S_ISDIR(stat_buf.st_mode)) { + createDirs(path); + } else { + createDirs(path.parent_path()); + writeFile(path, ""); + } + + struct iovec iov[8] = { + iovFromStaticSizedString("fstype"), + iovFromStaticSizedString("nullfs"), + iovFromStaticSizedString("fspath"), + iovFromDynamicSizeString(path), + iovFromStaticSizedString("target"), + iovFromDynamicSizeString(i.second.source), + iovFromStaticSizedString("errmsg"), + iovFromStaticSizedString(errmsg), + }; + if (nmount(iov, 8, 0) < 0) { + throw SysError("Failed to mount nullfs for %1% - %2%", path, errmsg); + } + autoDelMounts.push_back(AutoUnmount{path}); + } + + /* Fixed-output derivations typically need to access the + network, so give them access to /etc/resolv.conf and so + on. */ + if (!derivationType.isSandboxed()) { + // Only use nss functions to resolve hosts and + // services. Don’t use it for anything else that may + // be configured for this system. This limits the + // potential impurities introduced in fixed-outputs. + writeFile(chrootRootDir / "etc/nsswitch.conf", "hosts: files dns\nservices: files\n"); + + /* N.B. it is realistic that these paths might not exist. It + happens when testing Nix building fixed-output derivations + within a pure derivation. */ + for (std::filesystem::path path : {"/etc/resolv.conf", "/etc/services", "/etc/hosts"}) { + if (pathExists(path)) { + // This means if your network config changes during a FOD build, + // the DNS in the sandbox will be wrong. However, this is pretty unlikely + // to actually be a problem, because FODs are generally pretty fast, + // and machines with often-changing network configurations probably + // want to run resolved or some other local resolver anyway. + // + // There's also just no simple way to do this correctly, you have to manually + // inotify watch the files for changes on the outside and update the sandbox + // while the build is running (or at least that's what Flatpak does). + // + // I also just generally feel icky about modifying sandbox state under a build, + // even though it really shouldn't be a big deal. -K900 + copyFile(path, chrootRootDir / path.relative_path(), /*andDelete=*/false, /*contents=*/true); + } + } + + if (settings.caFile != "" && pathExists(std::filesystem::path{settings.caFile.get()})) { + // For the same reasons as above, copy the CA certificates file too. + // It should be even less likely to change during the build than resolv.conf. + createDirs(chrootRootDir / "etc/ssl/certs"); + copyFile( + std::filesystem::path{settings.caFile.get()}, + chrootRootDir / "etc/ssl/certs/ca-certificates.crt", + /*andDelete=*/false, + /*contents=*/true); + } + } + } + + void startChild() override + { + RunChildArgs args{ +# if NIX_WITH_AWS_AUTH + .awsCredentials = preResolveAwsCredentials(), +# endif + }; + + if (derivationType.isSandboxed()) { + { + int jid = jail_setv( + JAIL_CREATE, + "persist", + "true", + "path", + chrootRootDir.c_str(), + "host.hostname", + "localhost", + // TODO: Make our own ruleset + "vnet", + "new", + NULL); + if (jid < 0) { + throw SysError("Failed to create jail (isolated network): %1%", jail_errmsg); + } + autoDelJail = {jid}; + } + + // Everything from here to the end of the block is setting up the network + AutoCloseFD fd(socket(PF_INET, SOCK_DGRAM, 0)); + if (!fd) + throw SysError("cannot open IP socket"); + + struct ifreq ifr; + strcpy(ifr.ifr_name, "lo0"); + ifr.ifr_flags = IFF_UP | IFF_LOOPBACK; + if (ioctl(fd.get(), SIOCSIFFLAGS, &ifr) == -1) + throw SysError("cannot set loopback interface flags"); + + AutoCloseFD netlink(socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE)); + + using IPv4 = std::array; + + struct + { + struct nlmsghdr nl_hdr; + struct ifaddrmsg addr_msg; + struct nlattr tl; + IPv4 addr; + } msg; + + // Many of the fields are deprecated or not useful to us, + // just zero them all here + memset(&msg, 0, sizeof(msg)); + + msg.nl_hdr.nlmsg_len = sizeof(msg); + msg.nl_hdr.nlmsg_type = NL_RTM_NEWADDR; + msg.nl_hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + + msg.addr_msg.ifa_family = AF_INET; + msg.addr_msg.ifa_prefixlen = 8; + msg.addr_msg.ifa_index = if_nametoindex("lo0"); + + msg.tl.nla_len = sizeof(struct nlattr) + 4; + msg.tl.nla_type = IFLA_ADDRESS; + msg.addr = IPv4{127, 0, 0, 1}; + + send(netlink.get(), (void *) &msg, sizeof(msg), 0); + + struct + { + struct nlmsghdr nl_hdr; + struct nlmsgerr err; + } response; + + size_t n = recv(netlink.get(), &response, sizeof(response), 0); + + if (n < sizeof(response) || response.nl_hdr.nlmsg_type != NLMSG_ERROR) { + throw SysError("Invalid repsonse when setting loopback interface address"); + } else if (response.err.error != 0) { + throw SysError(response.err.error, "Could not set loopback interface address"); + } + } else { + int jid = jail_setv( + JAIL_CREATE, + "persist", + "true", + "devfs_ruleset", + "4", + "path", + chrootRootDir.c_str(), + "host.hostname", + "localhost", + "ip4", + "inherit", + "ip6", + "inherit", + "allow.raw_sockets", + "true", + NULL); + if (jid < 0) { + throw SysError("Failed to create jail (networked): %1%", jail_errmsg); + } + autoDelJail = {jid}; + } + + pid = startProcess([&]() { + openSlave(); + runChild(args); + }); + } + + void enterChroot() override + { + if (jail_attach(autoDelJail) < 0) { + throw SysError("Failed to attach to jail"); + } + } + + void addDependency(const StorePath & path) + { + auto [source, target] = ChrootDerivationBuilder::addDependencyPrep(path); + throw UnimplementedError("'recursive-nix' is not supported on FreeBSD"); + } +}; + +} // namespace nix + +#endif diff --git a/src/libutil/file-system.cc b/src/libutil/file-system.cc index 0049d45343d..27113165f49 100644 --- a/src/libutil/file-system.cc +++ b/src/libutil/file-system.cc @@ -448,16 +448,21 @@ AutoDelete::AutoDelete(const std::filesystem::path & p, bool recursive) this->recursive = recursive; } +void AutoDelete::deletePath() +{ + if (del) { + if (recursive) + nix::deletePath(_path); + else + std::filesystem::remove(_path); + cancel(); + } +} + AutoDelete::~AutoDelete() { try { - if (del) { - if (recursive) - deletePath(_path); - else { - std::filesystem::remove(_path); - } - } + deletePath(); } catch (...) { ignoreExceptionInDestructor(); } @@ -483,7 +488,7 @@ AutoUnmount::AutoUnmount() { } -AutoUnmount::AutoUnmount(Path & p) +AutoUnmount::AutoUnmount(std::filesystem::path & p) : path(p) , del(true) { @@ -646,7 +651,7 @@ void setWriteTime(const std::filesystem::path & path, const struct stat & st) setWriteTime(path, st.st_atime, st.st_mtime, S_ISLNK(st.st_mode)); } -void copyFile(const std::filesystem::path & from, const std::filesystem::path & to, bool andDelete) +void copyFile(const std::filesystem::path & from, const std::filesystem::path & to, bool andDelete, bool contents) { auto fromStatus = std::filesystem::symlink_status(from); @@ -659,8 +664,14 @@ void copyFile(const std::filesystem::path & from, const std::filesystem::path & } if (std::filesystem::is_symlink(fromStatus) || std::filesystem::is_regular_file(fromStatus)) { - std::filesystem::copy( - from, to, std::filesystem::copy_options::copy_symlinks | std::filesystem::copy_options::overwrite_existing); + if (contents) { + std::filesystem::copy_file(from, to, std::filesystem::copy_options::overwrite_existing); + } else { + std::filesystem::copy( + from, + to, + std::filesystem::copy_options::copy_symlinks | std::filesystem::copy_options::overwrite_existing); + } } else if (std::filesystem::is_directory(fromStatus)) { std::filesystem::create_directory(to); for (auto & entry : DirectoryIterator(from)) { diff --git a/src/libutil/freebsd/freebsd-jail.cc b/src/libutil/freebsd/freebsd-jail.cc index 90fbe0cd62e..fd6e33a05dd 100644 --- a/src/libutil/freebsd/freebsd-jail.cc +++ b/src/libutil/freebsd/freebsd-jail.cc @@ -11,25 +11,27 @@ namespace nix { -AutoRemoveJail::AutoRemoveJail() - : del{false} -{ -} +AutoRemoveJail::AutoRemoveJail() = default; AutoRemoveJail::AutoRemoveJail(int jid) : jid(jid) - , del(true) { } +void AutoRemoveJail::remove() +{ + if (jid != INVALID_JAIL) { + if (jail_remove(jid) < 0) { + throw SysError("Failed to remove jail %1%", jid); + } + } + cancel(); +} + AutoRemoveJail::~AutoRemoveJail() { try { - if (del) { - if (jail_remove(jid) < 0) { - throw SysError("Failed to remove jail %1%", jid); - } - } + remove(); } catch (...) { ignoreExceptionInDestructor(); } @@ -37,12 +39,11 @@ AutoRemoveJail::~AutoRemoveJail() void AutoRemoveJail::cancel() { - del = false; + jid = INVALID_JAIL; } void AutoRemoveJail::reset(int j) { - del = true; jid = j; } diff --git a/src/libutil/freebsd/include/nix/util/freebsd-jail.hh b/src/libutil/freebsd/include/nix/util/freebsd-jail.hh index cfab3c2a644..8e031acc9be 100644 --- a/src/libutil/freebsd/include/nix/util/freebsd-jail.hh +++ b/src/libutil/freebsd/include/nix/util/freebsd-jail.hh @@ -7,16 +7,16 @@ namespace nix { class AutoRemoveJail { - int jid; - bool del; + static constexpr int INVALID_JAIL = -1; + int jid = INVALID_JAIL; public: + AutoRemoveJail(); AutoRemoveJail(int jid); AutoRemoveJail(const AutoRemoveJail &) = delete; AutoRemoveJail & operator=(const AutoRemoveJail &) = delete; AutoRemoveJail(AutoRemoveJail && other) noexcept : jid(other.jid) - , del(other.del) { other.cancel(); } @@ -24,13 +24,29 @@ public: AutoRemoveJail & operator=(AutoRemoveJail && other) noexcept { jid = other.jid; - del = other.del; other.cancel(); return *this; } - AutoRemoveJail(); + operator int() const + { + return jid; + } + ~AutoRemoveJail(); + + /** + * remove the jail that path points to, and cancel this + * `AutoRemoveJail`, so unmounting is not attempted a second time by the + * destructor. + * + * The destructor calls this ignoring any exception. + */ + void remove(); + + /** + * Cancel the jail removal. + */ void cancel(); void reset(int j); }; diff --git a/src/libutil/include/nix/util/file-system.hh b/src/libutil/include/nix/util/file-system.hh index fd3b7f32a27..cae142e78b8 100644 --- a/src/libutil/include/nix/util/file-system.hh +++ b/src/libutil/include/nix/util/file-system.hh @@ -294,8 +294,12 @@ void moveFile(const Path & src, const Path & dst); * `true`, then also remove `oldPath` (making this equivalent to `moveFile`, but * with the guaranty that the destination will be “fresh”, with no stale inode * or file descriptor pointing to it). + * + * If contents is set, always create a regular file, even if the source is a + * link. */ -void copyFile(const std::filesystem::path & from, const std::filesystem::path & to, bool andDelete); +void copyFile( + const std::filesystem::path & from, const std::filesystem::path & to, bool andDelete, bool contents = false); /** * Automatic cleanup of resources. @@ -322,6 +326,17 @@ public: AutoDelete & operator=(const AutoDelete &) = delete; ~AutoDelete(); + /** + * Delete the file the path points to, and cancel this `AutoDelete`, + * so deletion is not attempted a second time by the destructor. + * + * The destructor calls this ignoring any exception. + */ + void deletePath(); + + /** + * Cancel the pending deletion + */ void cancel(); void reset(const std::filesystem::path & p, bool recursive = true); @@ -493,11 +508,11 @@ private: #ifdef __FreeBSD__ class AutoUnmount { - Path path; + std::filesystem::path path; bool del; public: AutoUnmount(); - AutoUnmount(Path &); + AutoUnmount(std::filesystem::path &); AutoUnmount(const AutoUnmount &) = delete; AutoUnmount(AutoUnmount && other) noexcept diff --git a/tests/functional/restricted.sh b/tests/functional/restricted.sh index 2f65f15fe5d..cf7859989cd 100755 --- a/tests/functional/restricted.sh +++ b/tests/functional/restricted.sh @@ -6,7 +6,7 @@ clearStoreIfPossible nix-instantiate --restrict-eval --eval -E '1 + 2' (! nix-instantiate --eval --restrict-eval ./restricted.nix) -(! nix-instantiate --eval --restrict-eval <(echo '1 + 2')) +TMPFILE=$(mktemp); echo '1 + 2' >"$TMPFILE"; (! nix-instantiate --eval --restrict-eval "$TMPFILE"); rm "$TMPFILE" mkdir -p "$TEST_ROOT/nix" cp ./simple.nix "$TEST_ROOT/nix"