diff --git a/lix/legacy/build-remote.cc b/lix/legacy/build-remote.cc index bab2670cb..780e7c990 100644 --- a/lix/legacy/build-remote.cc +++ b/lix/legacy/build-remote.cc @@ -1,5 +1,6 @@ #include "lix/libstore/path.hh" #include "lix/libutil/async.hh" +#include "lix/libutil/c-calls.hh" #include "lix/libutil/error.hh" #include "lix/libutil/file-descriptor.hh" #include "lix/libutil/logging.hh" @@ -255,7 +256,7 @@ try { bool canBuildLocally = amWilling && couldBuildLocally; /* Error ignored here, will be caught later */ - mkdir(currentLoad.c_str(), 0777); + (void) sys::mkdir(currentLoad, 0777); while (true) { bestSlotLock.reset(); diff --git a/lix/legacy/nix-build.cc b/lix/legacy/nix-build.cc index ca28cd54a..ac091b783 100644 --- a/lix/legacy/nix-build.cc +++ b/lix/legacy/nix-build.cc @@ -9,6 +9,7 @@ #include "lix/libstore/store-api.hh" #include "lix/libstore/local-fs-store.hh" #include "lix/libstore/globals.hh" +#include "lix/libutil/c-calls.hh" #include "lix/libutil/current-process.hh" #include "lix/libstore/derivations.hh" #include "lix/libmain/shared.hh" @@ -225,8 +226,9 @@ static void main_nix_build(AsyncIoRoot & aio, std::string programName, Strings a left = {"default.nix"}; } - if (runEnv) - setenv("IN_NIX_SHELL", pure ? "pure" : "impure", 1); + if (runEnv) { + (void) sys::setenv("IN_NIX_SHELL", pure ? "pure" : "impure", 1); + } DrvInfos drvs; @@ -540,15 +542,13 @@ static void main_nix_build(AsyncIoRoot & aio, std::string programName, Strings a environ = envPtrs.data(); - auto argPtrs = stringsToCharPtrs(args); - restoreProcessContext(); logger->pause(); printMsg(lvlChatty, "running shell: %s", concatMapStringsSep(" ", args, shellEscape)); - execvp(shell->c_str(), argPtrs.data()); + sys::execvp(*shell, args); throw SysError("executing shell '%s'", *shell); } diff --git a/lix/legacy/nix-channel.cc b/lix/legacy/nix-channel.cc index 26055d4c0..84b16654b 100644 --- a/lix/legacy/nix-channel.cc +++ b/lix/legacy/nix-channel.cc @@ -8,6 +8,7 @@ #include "lix/libexpr/eval-settings.hh" // for defexpr #include "lix/libstore/temporary-dir.hh" #include "lix/libutil/async.hh" +#include "lix/libutil/c-calls.hh" #include "lix/libutil/regex.hh" #include "lix/libutil/result.hh" #include "lix/libutil/users.hh" @@ -44,7 +45,7 @@ static void readChannels() // Writes the list of channels. static void writeChannels() { - auto channelsFD = AutoCloseFD{open(channelsList.c_str(), O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC, 0644)}; + auto channelsFD = sys::open(channelsList, O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC, 0644); if (!channelsFD) throw SysError("opening '%1%' for writing", channelsList); for (const auto & channel : channels) @@ -174,11 +175,12 @@ static void update(AsyncIoRoot & aio, const StringSet & channelNames) // Make the channels appear in nix-env. struct stat st; - if (lstat(nixDefExpr.c_str(), &st) == 0) { + if (sys::lstat(nixDefExpr, &st) == 0) { if (S_ISLNK(st.st_mode)) // old-skool ~/.nix-defexpr - if (unlink(nixDefExpr.c_str()) == -1) + if (sys::unlink(nixDefExpr) == -1) { throw SysError("unlinking %1%", nixDefExpr); + } } else if (errno != ENOENT) { throw SysError("getting status of %1%", nixDefExpr); } diff --git a/lix/legacy/nix-collect-garbage.cc b/lix/legacy/nix-collect-garbage.cc index 863b90c74..5dc45df09 100644 --- a/lix/legacy/nix-collect-garbage.cc +++ b/lix/legacy/nix-collect-garbage.cc @@ -1,3 +1,4 @@ +#include "lix/libutil/c-calls.hh" #include "lix/libutil/file-system.hh" #include "lix/libstore/store-api.hh" #include "lix/libstore/store-cast.hh" @@ -24,9 +25,11 @@ bool dryRun = false; static void removeOldGenerations(std::string dir, NeverAsync = {}) { - if (access(dir.c_str(), R_OK) != 0) return; + if (sys::access(dir, R_OK) != 0) { + return; + } - bool canWrite = access(dir.c_str(), W_OK) == 0; + bool canWrite = sys::access(dir, W_OK) == 0; for (auto & i : readDirectory(dir)) { checkInterrupt(); diff --git a/lix/libcmd/command.cc b/lix/libcmd/command.cc index 4c9626d8c..812f8b446 100644 --- a/lix/libcmd/command.cc +++ b/lix/libcmd/command.cc @@ -5,6 +5,8 @@ #include "lix/libstore/profiles.hh" #include "lix/libcmd/repl.hh" #include "lix/libutil/async.hh" +#include "lix/libutil/c-calls.hh" +#include "lix/libutil/error.hh" extern char * * environ __attribute__((weak)); @@ -303,8 +305,10 @@ void MixEnvironment::setEnviron() { throw UsageError("--unset does not make sense with --ignore-environment"); for (const auto & var : keep) { - auto val = getenv(var.c_str()); - if (val) stringsEnv.emplace_back(fmt("%s=%s", var.c_str(), val)); + auto val = sys::getenv(var); + if (val) { + stringsEnv.emplace_back(fmt("%s=%s", var, val)); + } } vectorEnv = stringsToCharPtrs(stringsEnv); @@ -314,7 +318,7 @@ void MixEnvironment::setEnviron() { throw UsageError("--keep does not make sense without --ignore-environment"); for (const auto & var : unset) - unsetenv(var.c_str()); + (void) sys::unsetenv(var); } } diff --git a/lix/libcmd/repl-interacter.cc b/lix/libcmd/repl-interacter.cc index 8cf847082..caaf1682f 100644 --- a/lix/libcmd/repl-interacter.cc +++ b/lix/libcmd/repl-interacter.cc @@ -1,3 +1,4 @@ +#include "lix/libutil/c-calls.hh" #include "lix/libutil/error.hh" #include "lix/libutil/file-system.hh" #include "lix/libutil/logging.hh" @@ -110,7 +111,7 @@ static el_status_t doCompletion() { if (possible.size() == 1) { const auto completion = *possible.cbegin(); if (completion.size() > s.size()) { - rl_insert_text(completion.c_str() + s.size()); + rl_insert_text(requireCString(completion.substr(s.size()))); return redisplay(); } @@ -134,7 +135,7 @@ static el_status_t doCompletion() { } if (len > 0) { auto commonPrefix = possible.begin()->substr(start, len); - rl_insert_text(commonPrefix.c_str()); + rl_insert_text(requireCString(commonPrefix)); el_ring_bell(); return redisplay(); } @@ -154,7 +155,7 @@ ReadlineLikeInteracter::Guard ReadlineLikeInteracter::init(detail::ReplCompleter logWarning(e.info()); } el_hist_size = 1000; - read_history(historyFile.c_str()); + read_history(requireCString(historyFile)); auto oldRepl = curRepl; curRepl = repl; Guard restoreRepl([oldRepl] { curRepl = oldRepl; }); @@ -202,7 +203,7 @@ bool ReadlineLikeInteracter::getLine(std::string & input, ReplPromptType promptT }; setupSignals(); - char * s = readline(promptForType(promptType)); + char * s = readline(promptForType(promptType)); // NOLINT(lix-unsafe-c-calls) Finally doFree([&]() { free(s); }); restoreSignals(); @@ -223,7 +224,7 @@ bool ReadlineLikeInteracter::getLine(std::string & input, ReplPromptType promptT void ReadlineLikeInteracter::writeHistory() { - int ret = write_history(historyFile.c_str()); + int ret = write_history(requireCString(historyFile)); int writeHistErr = errno; if (ret == 0) { diff --git a/lix/libcmd/repl.cc b/lix/libcmd/repl.cc index 3cff251b3..b737e5521 100644 --- a/lix/libcmd/repl.cc +++ b/lix/libcmd/repl.cc @@ -56,6 +56,7 @@ using NdString = std::unique_ptr; */ NdString lambdaDocsForPos(SourcePath const path, nix::Pos const &pos) { std::string const file = path.to_string(); + // NOLINTNEXTLINE(lix-unsafe-c-calls): paths are safe return NdString{lixdoc_get_function_docs(file.c_str(), pos.line, pos.column), &lixdoc_free_string}; } diff --git a/lix/libexpr/eval.cc b/lix/libexpr/eval.cc index c577c6bd0..ed02a1e01 100644 --- a/lix/libexpr/eval.cc +++ b/lix/libexpr/eval.cc @@ -2603,6 +2603,7 @@ bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_v return v1.str() == v2.str(); case nPath: + // NOLINTNEXTLINE(lix-unsafe-c-calls) return strcmp(v1.string().content, v2.string().content) == 0; case nNull: diff --git a/lix/libexpr/primops.cc b/lix/libexpr/primops.cc index 7dd27f5d3..53eaecaf6 100644 --- a/lix/libexpr/primops.cc +++ b/lix/libexpr/primops.cc @@ -16,6 +16,7 @@ #include "lix/libexpr/value-to-xml.hh" #include "lix/libexpr/primops.hh" #include "lix/libfetchers/fetch-to-store.hh" +#include "lix/libutil/c-calls.hh" #include "lix/libutil/regex.hh" #include "lix/libutil/types.hh" #include "value.hh" @@ -273,12 +274,12 @@ void prim_importNative(EvalState & state, Value * * args, Value & v) std::string sym(state.forceStringNoCtx(*args[1], noPos, "while evaluating the second argument passed to builtins.importNative")); - void *handle = dlopen(path.canonical().c_str(), RTLD_LAZY | RTLD_LOCAL); + void * handle = dlopen(requireCString(path.canonical().abs()), RTLD_LAZY | RTLD_LOCAL); if (!handle) state.ctx.errors.make("could not open '%1%': %2%", path, dlerror()).debugThrow(); dlerror(); - ValueInitializer func = reinterpret_cast(dlsym(handle, sym.c_str())); + ValueInitializer func = reinterpret_cast(dlsym(handle, requireCString(sym))); if(!func) { char *message = dlerror(); if (message) @@ -484,6 +485,7 @@ struct CompareValues : NeverAsync case nString: return v1.str() < v2.str(); case nPath: + // NOLINTNEXTLINE(lix-unsafe-c-calls) return strcmp(v1.string().content, v2.string().content) < 0; case nList: // Lexicographic comparison diff --git a/lix/libfetchers/git.cc b/lix/libfetchers/git.cc index 9e6d5a078..0c694187b 100644 --- a/lix/libfetchers/git.cc +++ b/lix/libfetchers/git.cc @@ -1,6 +1,7 @@ #include "lix/libutil/archive.hh" #include "lix/libutil/async-io.hh" #include "lix/libutil/async.hh" +#include "lix/libutil/c-calls.hh" #include "lix/libutil/error.hh" #include "lix/libfetchers/fetchers.hh" #include "lix/libfetchers/cache.hh" @@ -51,7 +52,7 @@ bool touchCacheFile(const Path & path, time_t touch_time) times[1].tv_sec = touch_time; times[1].tv_usec = 0; - return lutimes(path.c_str(), times) == 0; + return sys::lutimes(path, times) == 0; } Path getCachePath(std::string_view key) @@ -128,7 +129,7 @@ try { time_t now = time(0); struct stat st; std::optional cachedRef; - if (stat(headRefFile.c_str(), &st) == 0) { + if (sys::stat(headRefFile, &st) == 0) { cachedRef = TRY_AWAIT(readHead(cacheDir)); if (cachedRef != std::nullopt && *cachedRef != gitInitialBranch && @@ -737,8 +738,7 @@ struct GitInputScheme : InputScheme /* If the local ref is older than ‘tarball-ttl’ seconds, do a git fetch to update the local ref to the remote ref. */ struct stat st; - return stat(path.c_str(), &st) == 0 && - isCacheFileWithinTtl(now, st); + return sys::stat(path, &st) == 0 && isCacheFileWithinTtl(now, st); }; if (auto result = resolveRefToCachePath( input, diff --git a/lix/libmain/shared.cc b/lix/libmain/shared.cc index 86996059b..0f844c4b1 100644 --- a/lix/libmain/shared.cc +++ b/lix/libmain/shared.cc @@ -3,6 +3,7 @@ #include "lix/libmain/shared.hh" #include "lix/libstore/store-api.hh" #include "lix/libstore/gc-store.hh" +#include "lix/libutil/c-calls.hh" #include "lix/libutil/result.hh" #include "lix/libutil/signals.hh" #include "lix/libmain/loggers.hh" @@ -307,8 +308,8 @@ void printVersion(const std::string & programName) void showManPage(const std::string & name) { restoreProcessContext(); - setenv("MANPATH", settings.nixManDir.c_str(), 1); - execlp("man", "man", name.c_str(), nullptr); + (void) sys::setenv("MANPATH", settings.nixManDir, 1); + execlp("man", "man", requireCString(name).asCStr(), nullptr); throw SysError("command 'man %1%' failed", name.c_str()); } diff --git a/lix/libstore/build/child.cc b/lix/libstore/build/child.cc index 631a1aa6f..d0b11add0 100644 --- a/lix/libstore/build/child.cc +++ b/lix/libstore/build/child.cc @@ -1,3 +1,4 @@ +#include "lix/libutil/c-calls.hh" #include "lix/libutil/current-process.hh" #include "lix/libutil/logging.hh" @@ -22,12 +23,13 @@ void commonExecveingChildInit() throw SysError("cannot dup stderr into stdout"); /* Reroute stdin to /dev/null. */ - int fdDevNull = open(pathNullDevice.c_str(), O_RDWR); - if (fdDevNull == -1) + auto fdDevNull = sys::open(pathNullDevice, O_RDWR); + if (!fdDevNull) { throw SysError("cannot open '%1%'", pathNullDevice); - if (dup2(fdDevNull, STDIN_FILENO) == -1) + } + if (dup2(fdDevNull.get(), STDIN_FILENO) == -1) { throw SysError("cannot dup null device into stdin"); - close(fdDevNull); + } } } diff --git a/lix/libstore/build/derivation-goal.cc b/lix/libstore/build/derivation-goal.cc index 11bcd208f..10f1d566e 100644 --- a/lix/libstore/build/derivation-goal.cc +++ b/lix/libstore/build/derivation-goal.cc @@ -1,6 +1,7 @@ #include "lix/libstore/build/derivation-goal.hh" #include "lix/libutil/async-io.hh" #include "lix/libutil/async.hh" +#include "lix/libutil/c-calls.hh" #include "lix/libutil/file-descriptor.hh" #include "lix/libutil/file-system.hh" #include "lix/libstore/build/hook-instance.hh" @@ -1182,7 +1183,7 @@ Path DerivationGoal::openLogFile() Path logFileName = fmt("%s/%s%s", dir, baseName.substr(2), settings.compressLog ? ".bz2" : ""); - fdLogFile = AutoCloseFD{open(logFileName.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0666)}; + fdLogFile = sys::open(logFileName, O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0666); if (!fdLogFile) throw SysError("creating log file '%1%'", logFileName); logFileSink = std::make_shared(fdLogFile.get()); diff --git a/lix/libstore/build/hook-instance.cc b/lix/libstore/build/hook-instance.cc index 325a84591..1c9478642 100644 --- a/lix/libstore/build/hook-instance.cc +++ b/lix/libstore/build/hook-instance.cc @@ -1,4 +1,5 @@ #include "lix/libstore/build/child.hh" +#include "lix/libutil/c-calls.hh" #include "lix/libutil/error.hh" #include "lix/libutil/file-system.hh" #include "lix/libstore/globals.hh" @@ -56,7 +57,7 @@ try { throw SysError("dupping to-hook read side"); } - execv(buildHook.c_str(), stringsToCharPtrs(args).data()); + sys::execv(buildHook, args); throw SysError("executing '%s'", buildHook); }); diff --git a/lix/libstore/build/local-derivation-goal.cc b/lix/libstore/build/local-derivation-goal.cc index 58988b563..c330b12b1 100644 --- a/lix/libstore/build/local-derivation-goal.cc +++ b/lix/libstore/build/local-derivation-goal.cc @@ -12,6 +12,7 @@ #include "lix/libstore/path-references.hh" #include "lix/libutil/archive.hh" #include "lix/libstore/daemon.hh" +#include "lix/libutil/c-calls.hh" #include "lix/libutil/fmt.hh" #include "lix/libutil/regex.hh" #include "lix/libutil/file-descriptor.hh" @@ -370,13 +371,14 @@ bool LocalDerivationGoal::cleanupDecideWhetherDiskFull() auto & localStore = getLocalStore(); uint64_t required = 8ULL * 1024 * 1024; // FIXME: make configurable struct statvfs st; - if (statvfs(localStore.config().realStoreDir.get().c_str(), &st) == 0 && - (uint64_t) st.f_bavail * st.f_bsize < required) - diskFull = true; - if (statvfs(tmpDirRoot.c_str(), &st) == 0 && (uint64_t) st.f_bavail * st.f_bsize < required) + if (sys::statvfs(localStore.config().realStoreDir, &st) == 0 + && (uint64_t) st.f_bavail * st.f_bsize < required) { diskFull = true; } + if (sys::statvfs(tmpDirRoot, &st) == 0 && (uint64_t) st.f_bavail * st.f_bsize < required) { + diskFull = true; + } } #endif @@ -491,7 +493,7 @@ try { } /* The TOCTOU between the previous mkdir call and this open call is unavoidable due to * POSIX semantics.*/ - tmpDirRootFd = AutoCloseFD{open(tmpDirRoot.c_str(), O_RDONLY | O_NOFOLLOW | O_DIRECTORY)}; + tmpDirRootFd = sys::open(tmpDirRoot, O_RDONLY | O_NOFOLLOW | O_DIRECTORY); if (!tmpDirRootFd) { throw SysError("failed to open the build temporary directory descriptor '%1%'", tmpDirRoot); } @@ -785,11 +787,13 @@ try { std::string slaveName = ptsname(builderOutPTY.get()); if (buildUser) { - if (chmod(slaveName.c_str(), 0600)) + if (sys::chmod(slaveName, 0600)) { throw SysError("changing mode of pseudoterminal slave"); + } - if (chown(slaveName.c_str(), buildUser->getUID(), 0)) + if (sys::chown(slaveName, buildUser->getUID(), 0)) { throw SysError("changing owner of pseudoterminal slave"); + } } #if __APPLE__ else { @@ -802,9 +806,8 @@ try { throw SysError("unlocking pseudoterminal"); /* Open the slave side of the pseudoterminal and use it as stderr. */ - auto openSlave = [&]() - { - AutoCloseFD builderOut{open(slaveName.c_str(), O_RDWR | O_NOCTTY)}; + auto openSlave = [&]() { + AutoCloseFD builderOut{sys::open(slaveName, O_RDWR | O_NOCTTY)}; if (!builderOut) throw SysError("opening pseudoterminal slave"); @@ -896,7 +899,12 @@ void LocalDerivationGoal::initTmpDir() { std::string fn = ".attr-" + hash.to_string(Base::Base32, false); Path p = tmpDir + "/" + fn; /* TODO(jade): we should have BorrowedFD instead of OwnedFD. */ - AutoCloseFD passAsFileFd{openat(tmpDirFd.get(), fn.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC | O_EXCL | O_NOFOLLOW, 0666)}; + AutoCloseFD passAsFileFd{sys::openat( + tmpDirFd.get(), + fn, + O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC | O_EXCL | O_NOFOLLOW, + 0666 + )}; if (!passAsFileFd) { throw SysError("opening `passAsFile` file in the sandbox '%1%'", p); } @@ -1083,8 +1091,9 @@ try { void LocalDerivationGoal::chownToBuilder(const Path & path) { if (!buildUser) return; - if (chown(path.c_str(), buildUser->getUID(), buildUser->getGID()) == -1) + if (sys::chown(path, buildUser->getUID(), buildUser->getGID()) == -1) { throw SysError("cannot change ownership of '%1%'", path); + } } void LocalDerivationGoal::chownToBuilder(const AutoCloseFD & fd) @@ -1173,8 +1182,9 @@ void LocalDerivationGoal::runChild() /* Bind-mount chroot directory to itself, to treat it as a different filesystem from /, as needed for pivot_root. */ - if (mount(chrootRootDir.c_str(), chrootRootDir.c_str(), 0, MS_BIND, 0) == -1) + if (sys::mount(chrootRootDir, chrootRootDir, "", MS_BIND, 0) == -1) { throw SysError("unable to bind mount '%1%'", chrootRootDir); + } /* Bind-mount the sandbox's Nix store onto itself so that we can mark it as a "shared" subtree, allowing bind @@ -1186,11 +1196,13 @@ void LocalDerivationGoal::runChild() to fail with EINVAL. Don't know why. */ Path chrootStoreDir = chrootRootDir + worker.store.config().storeDir; - if (mount(chrootStoreDir.c_str(), chrootStoreDir.c_str(), 0, MS_BIND, 0) == -1) + if (sys::mount(chrootStoreDir, chrootStoreDir, "", MS_BIND, 0) == -1) { throw SysError("unable to bind mount the Nix store", chrootStoreDir); + } - if (mount(0, chrootStoreDir.c_str(), 0, MS_SHARED, 0) == -1) + if (sys::mount("", chrootStoreDir, "", MS_SHARED, 0) == -1) { throw SysError("unable to make '%s' shared", chrootStoreDir); + } /* Set up a nearly empty /dev, unless the user asked to bind-mount the host /dev. */ @@ -1295,21 +1307,31 @@ void LocalDerivationGoal::runChild() /* Bind a new instance of procfs on /proc. */ createDirs(chrootRootDir + "/proc"); - if (mount("none", (chrootRootDir + "/proc").c_str(), "proc", 0, 0) == -1) + if (sys::mount("none", chrootRootDir + "/proc", "proc", 0, 0) == -1) { throw SysError("mounting /proc"); + } /* Mount sysfs on /sys. */ if (buildUser && buildUser->getUIDCount() != 1) { createDirs(chrootRootDir + "/sys"); - if (mount("none", (chrootRootDir + "/sys").c_str(), "sysfs", 0, 0) == -1) + if (sys::mount("none", chrootRootDir + "/sys", "sysfs", 0, 0) == -1) { throw SysError("mounting /sys"); + } } /* Mount a new tmpfs on /dev/shm to ensure that whatever the builder puts in /dev/shm is cleaned up automatically. */ - if (pathExists("/dev/shm") && mount("none", (chrootRootDir + "/dev/shm").c_str(), "tmpfs", 0, - fmt("size=%s", settings.sandboxShmSize).c_str()) == -1) + if (pathExists("/dev/shm") + && sys::mount( + "none", + chrootRootDir + "/dev/shm", + "tmpfs", + 0, + fmt("size=%s", settings.sandboxShmSize).c_str() + ) == -1) + { throw SysError("mounting /dev/shm"); + } /* Mount a new devpts on /dev/pts. Note that this requires the kernel to be compiled with @@ -1319,7 +1341,10 @@ void LocalDerivationGoal::runChild() !pathExists(chrootRootDir + "/dev/ptmx") && !pathsInChroot.count("/dev/pts")) { - if (mount("none", (chrootRootDir + "/dev/pts").c_str(), "devpts", 0, "newinstance,mode=0620") == 0) + if (sys::mount( + "none", (chrootRootDir + "/dev/pts"), "devpts", 0, "newinstance,mode=0620" + ) + == 0) { createSymlink("/dev/pts/ptmx", chrootRootDir + "/dev/ptmx"); @@ -1364,8 +1389,9 @@ void LocalDerivationGoal::runChild() throw SysError("unsharing cgroup namespace"); /* Do the chroot(). */ - if (chdir(chrootRootDir.c_str()) == -1) + if (sys::chdir(chrootRootDir) == -1) { throw SysError("cannot change directory to '%1%'", chrootRootDir); + } if (mkdir("real-root", 0) == -1) throw SysError("cannot create real-root directory"); @@ -1424,8 +1450,9 @@ void LocalDerivationGoal::runChild() } #endif - if (chdir(tmpDirInSandbox.c_str()) == -1) + if (sys::chdir(tmpDirInSandbox) == -1) { throw SysError("changing into '%1%'", tmpDir); + } /* Close all other file descriptors. */ closeExtraFDs(); @@ -1658,7 +1685,7 @@ void LocalDerivationGoal::runChild() void LocalDerivationGoal::execBuilder(std::string builder, Strings args, Strings envStrs) { - execve(builder.c_str(), stringsToCharPtrs(args).data(), stringsToCharPtrs(envStrs).data()); + sys::execve(builder, args, envStrs); } @@ -1738,7 +1765,7 @@ try { continue; } - auto optSt = maybeLstat(actualPath.c_str()); + auto optSt = maybeLstat(actualPath); if (!optSt) throw BuildError( "builder for '%s' failed to produce output path for output '%s' at '%s'", @@ -2216,9 +2243,11 @@ try { msg << HintFmt("derivation '%s' may not be deterministic: outputs differ", drvPath.to_string()); for (auto [oldPath, newPath]: nondeterministic) { if (newPath) { - msg << HintFmt("\n output differs: output '%s' differs from '%s'", oldPath.c_str(), *newPath); + msg << HintFmt( + "\n output differs: output '%s' differs from '%s'", oldPath, *newPath + ); } else { - msg << HintFmt("\n output '%s' differs", oldPath.c_str()); + msg << HintFmt("\n output '%s' differs", oldPath); } } throw NotDeterministic(msg.str()); @@ -2569,36 +2598,39 @@ try { static void makeVisible(int parentFd, const char * entry, uid_t user, gid_t group) { struct stat st; + // NOLINTNEXTLINE(lix-unsafe-c-calls): entry is a dentry name if (fstatat(parentFd, entry, &st, AT_SYMLINK_NOFOLLOW)) { throw SysError("fstat(%s)", guessOrInventPathFromFD(parentFd)); } if (S_ISDIR(st.st_mode)) { - int dirfd = openat(parentFd, entry, O_RDONLY | O_DIRECTORY | O_NOFOLLOW); - if (dirfd < 0) { + auto dirfd = sys::openat(parentFd, entry, O_RDONLY | O_DIRECTORY | O_NOFOLLOW); + if (!dirfd) { throw SysError("openat(%s/%s)", guessOrInventPathFromFD(parentFd), entry); } - AutoCloseDir dir(fdopendir(dirfd)); + AutoCloseDir dir(fdopendir(dirfd.get())); if (!dir) { - close(dirfd); throw SysError("fdopendir(%s/%s)", guessOrInventPathFromFD(parentFd), entry); } + dirfd.release(); struct dirent * dirent; while (errno = 0, dirent = readdir(dir.get())) { if (strcmp(dirent->d_name, ".") == 0 || strcmp(dirent->d_name, "..") == 0) { continue; } - makeVisible(dirfd, dirent->d_name, user, group); + makeVisible(::dirfd(dir.get()), dirent->d_name, user, group); } } // ignore permissions errors for symlinks. linux can't chmod them. // clear special permission bits while we're here, just to be safe - if (fchmodat(parentFd, entry, st.st_mode & 0777, AT_SYMLINK_NOFOLLOW) && !S_ISLNK(st.st_mode)) { + if (sys::fchmodat(parentFd, entry, st.st_mode & 0777, AT_SYMLINK_NOFOLLOW) + && !S_ISLNK(st.st_mode)) + { throw SysError("fchmod(%s)", guessOrInventPathFromFD(parentFd)); } if (user != uid_t(-1) && group != gid_t(-1) - && fchownat(parentFd, entry, user, group, AT_SYMLINK_NOFOLLOW)) + && sys::fchownat(parentFd, entry, user, group, AT_SYMLINK_NOFOLLOW)) { throw SysError("fchown(%s)", guessOrInventPathFromFD(parentFd)); } @@ -2621,7 +2653,7 @@ void LocalDerivationGoal::finalizeTmpDir(bool force, bool duringDestruction) } catch (SysError & e) { printError("error making '%s' accessible: %s", tmpDir, e.what()); } - chmod(tmpDirRoot.c_str(), 0755); + (void) sys::chmod(tmpDirRoot, 0755); } else if (duringDestruction) deletePathUninterruptible(tmpDirRoot); diff --git a/lix/libstore/builtins/buildenv.cc b/lix/libstore/builtins/buildenv.cc index 75e1fe69a..8a07bcb38 100644 --- a/lix/libstore/builtins/buildenv.cc +++ b/lix/libstore/builtins/buildenv.cc @@ -1,4 +1,5 @@ #include "lix/libstore/builtins/buildenv.hh" +#include "lix/libutil/c-calls.hh" #include "lix/libutil/strings.hh" #include @@ -40,8 +41,9 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir, struct stat srcSt; try { - if (stat(srcFile.c_str(), &srcSt) == -1) + if (sys::stat(srcFile, &srcSt) == -1) { throw SysError("getting status of '%1%'", srcFile); + } } catch (SysError & e) { if (e.errNo == ENOENT || e.errNo == ENOTDIR) { printTaggedWarning("skipping dangling symlink '%s'", dstFile); @@ -66,7 +68,7 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir, continue; else if (S_ISDIR(srcSt.st_mode)) { - auto dstStOpt = maybeLstat(dstFile.c_str()); + auto dstStOpt = maybeLstat(dstFile); if (dstStOpt) { auto & dstSt = *dstStOpt; if (S_ISDIR(dstSt.st_mode)) { @@ -76,10 +78,12 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir, auto target = canonPath(dstFile, true); if (!S_ISDIR(lstat(target).st_mode)) throw Error("collision between '%1%' and non-directory '%2%'", srcFile, target); - if (unlink(dstFile.c_str()) == -1) + if (sys::unlink(dstFile) == -1) { throw SysError("unlinking '%1%'", dstFile); - if (mkdir(dstFile.c_str(), 0755) == -1) + } + if (sys::mkdir(dstFile, 0755) == -1) { throw SysError("creating directory '%1%'", dstFile); + } createLinks(state, target, dstFile, state.priorities[dstFile]); createLinks(state, srcFile, dstFile, priority); continue; @@ -88,7 +92,7 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir, } else { - auto dstStOpt = maybeLstat(dstFile.c_str()); + auto dstStOpt = maybeLstat(dstFile); if (dstStOpt) { auto & dstSt = *dstStOpt; if (S_ISLNK(dstSt.st_mode)) { @@ -101,8 +105,9 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir, ); if (prevPriority < priority) continue; - if (unlink(dstFile.c_str()) == -1) + if (sys::unlink(dstFile) == -1) { throw SysError("unlinking '%1%'", dstFile); + } } else if (S_ISDIR(dstSt.st_mode)) throw Error("collision between non-directory '%1%' and directory '%2%'", srcFile, dstFile); } diff --git a/lix/libstore/builtins/fetchurl.cc b/lix/libstore/builtins/fetchurl.cc index 401073d1a..8642713d7 100644 --- a/lix/libstore/builtins/fetchurl.cc +++ b/lix/libstore/builtins/fetchurl.cc @@ -3,6 +3,7 @@ #include "lix/libstore/store-api.hh" #include "lix/libutil/archive.hh" #include "lix/libutil/async.hh" +#include "lix/libutil/c-calls.hh" #include "lix/libutil/compression.hh" #include "lix/libutil/strings.hh" @@ -51,8 +52,9 @@ void builtinFetchurl(const BasicDerivation & drv, const std::string & netrcData, auto executable = drv.env.find("executable"); if (executable != drv.env.end() && executable->second == "1") { - if (chmod(storePath.c_str(), 0755) == -1) + if (sys::chmod(storePath, 0755) == -1) { throw SysError("making '%1%' executable", storePath); + } } }; diff --git a/lix/libstore/filetransfer.cc b/lix/libstore/filetransfer.cc index 788ff32d8..4d5bd4f5a 100644 --- a/lix/libstore/filetransfer.cc +++ b/lix/libstore/filetransfer.cc @@ -1,6 +1,7 @@ #include "lix/libstore/filetransfer.hh" #include "lix/libutil/async-io.hh" #include "lix/libutil/async.hh" +#include "lix/libutil/c-calls.hh" #include "lix/libutil/error.hh" #include "lix/libutil/namespaces.hh" #include "lix/libstore/globals.hh" @@ -163,7 +164,7 @@ struct curlFileTransfer : public FileTransfer } for (auto it = options.headers.begin(); it != options.headers.end(); ++it){ if (auto next = curl_slist_append( - requestHeaders.get(), fmt("%s: %s", it->first, it->second).c_str() + requestHeaders.get(), requireCString(fmt("%s: %s", it->first, it->second)) ); next != nullptr) { @@ -788,7 +789,7 @@ struct curlFileTransfer : public FileTransfer throw std::bad_alloc(); } KJ_DEFER(curl_url_cleanup(url)); - curl_url_set(url, CURLUPART_URL, uri.c_str(), 0); + curl_url_set(url, CURLUPART_URL, requireCString(uri), 0); char * path = nullptr; curl_url_get(url, CURLUPART_PATH, &path, 0); auto decoded = kj::decodeUriComponent(kj::arrayPtr(path, path + strlen(path))); @@ -796,7 +797,7 @@ struct curlFileTransfer : public FileTransfer Path fsPath(decoded.cStr(), decoded.size()); FileTransferResult metadata{.effectiveUri = std::string("file://") + path}; struct stat st; - AutoCloseFD fd(open(fsPath.c_str(), O_RDONLY)); + AutoCloseFD fd(sys::open(fsPath, O_RDONLY)); if (!fd || fstat(fd.get(), &st) != 0) { throw FileTransferError( NotFound, std::nullopt, "%s: file not found (%s)", fsPath, strerror(errno) diff --git a/lix/libstore/gc.cc b/lix/libstore/gc.cc index 659750eff..0decc377f 100644 --- a/lix/libstore/gc.cc +++ b/lix/libstore/gc.cc @@ -2,6 +2,7 @@ #include "lix/libstore/local-store.hh" #include "lix/libstore/pathlocks.hh" #include "lix/libutil/async.hh" +#include "lix/libutil/c-calls.hh" #include "lix/libutil/error.hh" #include "lix/libutil/file-descriptor.hh" #include "lix/libutil/processes.hh" @@ -42,7 +43,7 @@ static void makeSymlink(const Path & link, const Path & target) /* Create the new symlink. */ Path tempLink = makeTempPath(link); - unlink(tempLink.c_str()); // just in case; ignore errors + (void) sys::unlink(tempLink); // just in case; ignore errors createSymlink(target, tempLink); /* Atomically replace the old one. */ @@ -98,21 +99,21 @@ void LocalStore::createTempRootsFile() /* Create the temporary roots file for this process. */ while (true) { auto tmp = makeTempPath(fnTempRoots, ".tmp"); - AutoCloseFD fd{open(tmp.c_str(), O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, 0600)}; + AutoCloseFD fd{sys::open(tmp, O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, 0600)}; if (!fd && errno != EEXIST) { throw SysError("opening lock file '%1%'", tmp); } // if we can't lock it then GC must've found and deleted it, so we try again. // if we *can* lock it GC may have still deleted it, and rename will tell us. if (!tryLockFile(fd.get(), ltWrite)) { - unlink(tmp.c_str()); // just to be sure it's gone + (void) sys::unlink(tmp); // just to be sure it's gone continue; } else if (auto fdTempRoots(_fdTempRoots.lock()); *fdTempRoots) { - if (unlink(tmp.c_str()) == -1) { + if (sys::unlink(tmp) == -1) { throw SysError("deleting lock file '%1%'", tmp); } break; - } else if (rename(tmp.c_str(), fnTempRoots.c_str()) == -1) { + } else if (sys::rename(tmp, fnTempRoots) == -1) { if (errno != ENOENT) { throw SysError("moving lock file '%1%'", tmp); } @@ -224,7 +225,7 @@ void LocalStore::findTempRoots(Roots & tempRoots, bool censor) pid_t pid = std::stoi(i.name); debug("reading temporary root file '%1%'", path); - AutoCloseFD fd(open(path.c_str(), O_CLOEXEC | O_RDWR, 0666)); + AutoCloseFD fd(sys::open(path, O_CLOEXEC | O_RDWR, 0666)); if (!fd) { /* It's okay if the file has disappeared. */ if (errno == ENOENT) continue; @@ -236,7 +237,7 @@ void LocalStore::findTempRoots(Roots & tempRoots, bool censor) we don't care about its temporary roots. */ if (tryLockFile(fd.get(), ltWrite)) { printInfo("removing stale temporary roots file '%1%'", path); - unlink(path.c_str()); + (void) sys::unlink(path); writeFull(fd.get(), "d"); continue; } @@ -296,7 +297,7 @@ try { if (!pathExists(target)) { if (isInDir(path, config().stateDir + "/" + gcRootsDir + "/auto")) { printInfo("removing stale link from '%1%' to '%2%'", path, target); - unlink(path.c_str()); + (void) sys::unlink(path); } } else { struct stat st2 = lstat(target); @@ -673,7 +674,7 @@ try { by another process. We need to be sure that we can acquire an exclusive lock before deleting them. */ if (baseName.find("tmp-", 0) == 0) { - AutoCloseFD tmpDirFd{open(realPath.c_str(), O_RDONLY | O_DIRECTORY)}; + AutoCloseFD tmpDirFd{sys::open(realPath, O_RDONLY | O_DIRECTORY)}; if (tmpDirFd.get() == -1 || !tryLockFile(tmpDirFd.get(), ltWrite)) { debug("skipping locked tempdir '%s'", realPath); return; @@ -847,7 +848,7 @@ try { printInfo("determining live/dead paths..."); try { - AutoCloseDir dir(opendir(config().realStoreDir.get().c_str())); + AutoCloseDir dir(sys::opendir(config().realStoreDir)); if (!dir) throw SysError("opening directory '%1%'", config().realStoreDir); /* Read the store and delete all paths that are invalid or @@ -891,7 +892,7 @@ try { if (options.action == GCOptions::gcDeleteDead || deleteSpecific) { printInfo("deleting unused links..."); - AutoCloseDir dir(opendir(linksDir.c_str())); + AutoCloseDir dir(sys::opendir(linksDir)); if (!dir) throw SysError("opening directory '%1%'", linksDir); int64_t actualSize = 0, unsharedSize = 0; @@ -913,16 +914,18 @@ try { printMsg(lvlTalkative, "deleting unused link '%1%'", path); - if (unlink(path.c_str()) == -1) + if (sys::unlink(path) == -1) { throw SysError("deleting '%1%'", path); + } /* Do not accound for deleted file here. Rely on deletePath() accounting. */ } struct stat st; - if (stat(linksDir.c_str(), &st) == -1) + if (sys::stat(linksDir, &st) == -1) { throw SysError("statting '%1%'", linksDir); + } int64_t overhead = st.st_blocks * 512ULL; printInfo("note: currently hard linking saves %.2f MiB", @@ -962,8 +965,9 @@ try { return std::stoll(readFile(*fakeFreeSpaceFile)); struct statvfs st; - if (statvfs(config().realStoreDir.get().c_str(), &st)) + if (sys::statvfs(config().realStoreDir, &st)) { throw SysError("getting filesystem info about '%s'", config().realStoreDir); + } return (uint64_t) st.f_bavail * st.f_frsize; }; diff --git a/lix/libstore/globals.cc b/lix/libstore/globals.cc index 72ad0316d..e1a4dd9a9 100644 --- a/lix/libstore/globals.cc +++ b/lix/libstore/globals.cc @@ -9,6 +9,7 @@ #include "lix/libutil/compute-levels.hh" #include "lix/libutil/current-process.hh" #include "lix/libutil/json.hh" +#include "lix/libutil/c-calls.hh" #include #include @@ -403,8 +404,7 @@ void initPlugins() for (const auto & file : pluginFiles) { /* handle is purposefully leaked as there may be state in the DSO needed by the action of the plugin. */ - void *handle = - dlopen(file.c_str(), RTLD_LAZY | RTLD_LOCAL); + void * handle = dlopen(requireCString(file), RTLD_LAZY | RTLD_LOCAL); if (!handle) { printTaggedWarning( "could not dynamically open plugin file '%s', skipping it: %s", file, dlerror() diff --git a/lix/libstore/local-fs-store.cc b/lix/libstore/local-fs-store.cc index 038a0044d..6f4de280d 100644 --- a/lix/libstore/local-fs-store.cc +++ b/lix/libstore/local-fs-store.cc @@ -3,6 +3,7 @@ #include "lix/libstore/store-api.hh" #include "lix/libstore/local-fs-store.hh" #include "lix/libutil/async-io.hh" +#include "lix/libutil/c-calls.hh" #include "lix/libutil/compression.hh" #include "lix/libutil/file-system.hh" #include "lix/libutil/result.hh" @@ -27,7 +28,7 @@ try { auto realPath = TRY_AWAIT(toRealPath(path)); struct stat st; - if (lstat(realPath.c_str(), &st)) { + if (sys::lstat(realPath, &st)) { if (errno == ENOENT || errno == ENOTDIR) { co_return {Type::tMissing, 0, false}; } diff --git a/lix/libstore/local-store.cc b/lix/libstore/local-store.cc index 1e786f46b..e3a6c300d 100644 --- a/lix/libstore/local-store.cc +++ b/lix/libstore/local-store.cc @@ -8,6 +8,7 @@ #include "lix/libstore/nar-info.hh" #include "lix/libutil/async-io.hh" #include "lix/libutil/async.hh" +#include "lix/libutil/c-calls.hh" #include "lix/libutil/references.hh" #include "lix/libutil/result.hh" #include "lix/libutil/serialise.hh" @@ -125,8 +126,9 @@ LocalStore::LocalStore(LocalStoreConfig config) for (auto & perUserDir : {profilesDir + "/per-user", gcRootsDir + "/per-user"}) { createDirs(perUserDir); if (!config_.readOnly) { - if (chmod(perUserDir.c_str(), 0755) == -1) + if (sys::chmod(perUserDir, 0755) == -1) { throw SysError("could not set permissions on '%s' to 755", perUserDir); + } } } @@ -135,19 +137,22 @@ LocalStore::LocalStore(LocalStoreConfig config) if (getuid() == 0 && settings.buildUsersGroup != "") { mode_t perm = 01775; - struct group * gr = getgrnam(settings.buildUsersGroup.get().c_str()); + struct group * gr = sys::getgrnam(settings.buildUsersGroup); if (!gr) printError("warning: the group '%1%' specified in 'build-users-group' does not exist", settings.buildUsersGroup); else { struct stat st; - if (stat(config_.realStoreDir.get().c_str(), &st)) + if (sys::stat(config_.realStoreDir, &st)) { throw SysError("getting attributes of path '%1%'", config_.realStoreDir); + } if (st.st_uid != 0 || st.st_gid != gr->gr_gid || (st.st_mode & ~S_IFMT) != perm) { - if (chown(config_.realStoreDir.get().c_str(), 0, gr->gr_gid) == -1) + if (sys::chown(config_.realStoreDir, 0, gr->gr_gid) == -1) { throw SysError("changing ownership of path '%1%'", config_.realStoreDir); - if (chmod(config_.realStoreDir.get().c_str(), perm) == -1) + } + if (sys::chmod(config_.realStoreDir, perm) == -1) { throw SysError("changing permissions on path '%1%'", config_.realStoreDir); + } } } } @@ -173,10 +178,8 @@ LocalStore::LocalStore(LocalStoreConfig config) before doing a garbage collection. */ try { struct stat st; - if (stat(reservedSpacePath.c_str(), &st) == -1 || - st.st_size != settings.reservedSize) - { - AutoCloseFD fd{open(reservedSpacePath.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, 0600)}; + if (sys::stat(reservedSpacePath, &st) == -1 || st.st_size != settings.reservedSize) { + AutoCloseFD fd{sys::open(reservedSpacePath, O_WRONLY | O_CREAT | O_CLOEXEC, 0600)}; int res = -1; #if HAVE_POSIX_FALLOCATE res = posix_fallocate(fd.get(), 0, settings.reservedSize); @@ -193,7 +196,7 @@ LocalStore::LocalStore(LocalStoreConfig config) schema upgrade is in progress. */ if (!config_.readOnly) { Path globalLockPath = dbDir + "/big-lock"; - globalLock = openLockFile(globalLockPath.c_str(), true); + globalLock = openLockFile(globalLockPath, true); } if (!config_.readOnly && !tryLockFile(globalLock.get(), ltRead)) { @@ -328,7 +331,7 @@ LocalStore::LocalStore(std::string scheme, std::string path, LocalStoreConfig co AutoCloseFD LocalStore::openGCLock() { Path fnGCLock = config_.stateDir + "/gc.lock"; - AutoCloseFD fdGCLock{open(fnGCLock.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0600)}; + AutoCloseFD fdGCLock{sys::open(fnGCLock, O_RDWR | O_CREAT | O_CLOEXEC, 0600)}; if (!fdGCLock) throw SysError("opening global GC lock '%1%'", fnGCLock); return fdGCLock; @@ -354,7 +357,7 @@ LocalStore::~LocalStore() auto fdTempRoots(_fdTempRoots.lock()); if (*fdTempRoots) { fdTempRoots->reset(); - unlink(fnTempRoots.c_str()); + (void) sys::unlink(fnTempRoots); } } catch (...) { ignoreExceptionInDestructor(); @@ -377,8 +380,9 @@ void LocalStore::openDB(DBState & state, bool create) throw Error("cannot create database while in read-only mode"); } - if (access(dbDir.c_str(), R_OK | (config_.readOnly ? 0 : W_OK))) + if (sys::access(dbDir, R_OK | (config_.readOnly ? 0 : W_OK))) { throw SysError("Nix database directory '%1%' is not writable", dbDir); + } /* Open the Nix database. */ std::string dbPath = dbDir + "/db.sqlite"; @@ -445,12 +449,14 @@ void LocalStore::makeStoreWritable() if (getuid() != 0) return; /* Check if /nix/store is on a read-only mount. */ struct statvfs stat; - if (statvfs(config_.realStoreDir.get().c_str(), &stat) != 0) + if (sys::statvfs(config_.realStoreDir, &stat) != 0) { throw SysError("getting info about the Nix store mount point"); + } if (stat.f_flag & ST_RDONLY) { - if (mount(0, config_.realStoreDir.get().c_str(), "none", MS_REMOUNT | MS_BIND, 0) == -1) + if (sys::mount("", config_.realStoreDir, "none", MS_REMOUNT | MS_BIND, 0) == -1) { throw SysError("remounting %1% writable", config_.realStoreDir); + } } #endif } @@ -470,8 +476,9 @@ static void canonicaliseTimestampAndPermissions(const Path & path, const struct mode = (st.st_mode & S_IFMT) | 0444 | (st.st_mode & S_IXUSR ? 0111 : 0); - if (chmod(path.c_str(), mode) == -1) + if (sys::chmod(path, mode) == -1) { throw SysError("changing mode of '%1%' to %2$o", path, mode); + } } } @@ -483,13 +490,13 @@ static void canonicaliseTimestampAndPermissions(const Path & path, const struct 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)) + if (sys::lutimes(path, times) == -1) { + if (errno != ENOSYS || (!S_ISLNK(st.st_mode) && sys::utimes(path, times) == -1)) #else if (!S_ISLNK(st.st_mode) && utimes(path.c_str(), times) == -1) #endif - throw SysError("changing modification time of '%1%'", path); + throw SysError("changing modification time of '%1%'", path); + } } } @@ -539,7 +546,7 @@ static void canonicalisePathMetaData_( #if __linux__ /* Remove extended attributes / ACLs. */ - ssize_t eaSize = llistxattr(path.c_str(), nullptr, 0); + ssize_t eaSize = sys::llistxattr(path, nullptr, 0); if (eaSize < 0) { if (errno != ENOTSUP && errno != ENODATA) @@ -547,21 +554,23 @@ static void canonicalisePathMetaData_( } else if (eaSize > 0) { std::vector eaBuf(eaSize); - if ((eaSize = llistxattr(path.c_str(), eaBuf.data(), eaBuf.size())) < 0) + if ((eaSize = sys::llistxattr(path, eaBuf.data(), eaBuf.size())) < 0) { throw SysError("querying extended attributes of '%s'", path); + } bool resetMode = false; if ((S_ISREG(st.st_mode) || S_ISDIR(st.st_mode)) && !(st.st_mode & S_IWUSR)) { resetMode = true; - chmod(path.c_str(), st.st_mode | S_IWUSR); + (void) sys::chmod(path, st.st_mode | S_IWUSR); } for (auto & eaName: tokenizeString(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) + if (sys::lremovexattr(path, eaName) == -1) { throw SysError("removing extended attribute '%s' from '%s'", eaName, path); + } } if (resetMode) { - chmod(path.c_str(), st.st_mode); + (void) sys::chmod(path, st.st_mode); resetMode = false; } } @@ -580,7 +589,7 @@ static void canonicalisePathMetaData_( users group); we check for this case below. */ if (st.st_uid != geteuid()) { #if HAVE_LCHOWN - if (lchown(path.c_str(), geteuid(), getegid()) == -1) + if (sys::lchown(path, geteuid(), getegid()) == -1) #else if (!S_ISLNK(st.st_mode) && chown(path.c_str(), geteuid(), getegid()) == -1) @@ -1407,7 +1416,7 @@ std::pair LocalStore::createTempDirInStore() the GC between createTempDir() and when we acquire a lock on it. We'll repeat until 'tmpDir' exists and we've locked it. */ tmpDirFn = createTempDir(config_.realStoreDir, "tmp"); - tmpDirFd = AutoCloseFD{open(tmpDirFn.c_str(), O_RDONLY | O_DIRECTORY)}; + tmpDirFd = sys::open(tmpDirFn, O_RDONLY | O_DIRECTORY); if (tmpDirFd.get() < 0) { continue; } @@ -1501,10 +1510,11 @@ try { printError("link '%s' was modified! expected hash '%s', got '%s'", linkPath, link.name, hash); if (repair) { - if (unlink(linkPath.c_str()) == 0) + if (sys::unlink(linkPath) == 0) { printInfo("removed link '%s'", linkPath); - else + } else { throw SysError("removing corrupt link '%s'", linkPath); + } } else { errors = true; } diff --git a/lix/libstore/lock.cc b/lix/libstore/lock.cc index b8a623cbb..5301fd25a 100644 --- a/lix/libstore/lock.cc +++ b/lix/libstore/lock.cc @@ -1,4 +1,5 @@ #include "lix/libstore/lock.hh" +#include "lix/libutil/c-calls.hh" #include "lix/libutil/logging.hh" #include "lix/libutil/file-system.hh" #include "lix/libstore/globals.hh" @@ -11,14 +12,14 @@ namespace nix { #if __linux__ -static std::vector get_group_list(const char *username, gid_t group_id) +static std::vector get_group_list(const std::string & username, gid_t group_id) { std::vector gids; gids.resize(32); // Initial guess auto getgroupl_failed {[&] { int ngroups = gids.size(); - int err = getgrouplist(username, group_id, gids.data(), &ngroups); + int err = getgrouplist(requireCString(username), group_id, gids.data(), &ngroups); gids.resize(ngroups); return err == -1; }}; @@ -53,7 +54,7 @@ struct SimpleUserLock : UserLock createDirs(settings.nixStateDir + "/userpool"); /* Get the members of the build-users-group. */ - struct group * gr = getgrnam(settings.buildUsersGroup.get().c_str()); + struct group * gr = sys::getgrnam(settings.buildUsersGroup); if (!gr) throw Error("the group '%s' specified in 'build-users-group' does not exist", settings.buildUsersGroup); @@ -72,7 +73,7 @@ struct SimpleUserLock : UserLock for (auto & i : users) { debug("trying user '%s'", i); - struct passwd * pw = getpwnam(i.c_str()); + auto pw = sys::getpwnam(i); if (!pw) { #ifdef __APPLE__ #define APPLE_HINT "\n\nhint: this may be caused by an update to macOS Sequoia breaking existing Lix installations.\n" \ @@ -86,7 +87,7 @@ struct SimpleUserLock : UserLock auto fnUserLock = fmt("%s/userpool/%s", settings.nixStateDir,pw->pw_uid); - AutoCloseFD fd{open(fnUserLock.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0600)}; + AutoCloseFD fd{sys::open(fnUserLock, O_RDWR | O_CREAT | O_CLOEXEC, 0600)}; if (!fd) throw SysError("opening user lock '%s'", fnUserLock); @@ -158,7 +159,7 @@ struct AutoUserLock : UserLock auto fnUserLock = fmt("%s/userpool2/slot-%d", settings.nixStateDir, i); - AutoCloseFD fd{open(fnUserLock.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0600)}; + AutoCloseFD fd{sys::open(fnUserLock, O_RDWR | O_CREAT | O_CLOEXEC, 0600)}; if (!fd) throw SysError("opening user lock '%s'", fnUserLock); @@ -176,7 +177,7 @@ struct AutoUserLock : UserLock if (useUserNamespace) lock->firstGid = firstUid; else { - struct group * gr = getgrnam(settings.buildUsersGroup.get().c_str()); + struct group * gr = sys::getgrnam(settings.buildUsersGroup); if (!gr) throw Error("the group '%s' specified in 'build-users-group' does not exist", settings.buildUsersGroup); lock->firstGid = gr->gr_gid; diff --git a/lix/libstore/optimise-store.cc b/lix/libstore/optimise-store.cc index 1544bac4f..ee3ee2819 100644 --- a/lix/libstore/optimise-store.cc +++ b/lix/libstore/optimise-store.cc @@ -1,6 +1,7 @@ #include "lix/libstore/local-store.hh" #include "lix/libstore/globals.hh" #include "lix/libutil/async.hh" +#include "lix/libutil/c-calls.hh" #include "lix/libutil/result.hh" #include "lix/libutil/signals.hh" #include "lix/libutil/strings.hh" @@ -21,8 +22,9 @@ namespace nix { static void makeWritable(const Path & path) { auto st = lstat(path); - if (chmod(path.c_str(), st.st_mode | S_IWUSR) == -1) + if (sys::chmod(path, st.st_mode | S_IWUSR) == -1) { throw SysError("changing writability of '%1%'", path); + } } @@ -47,7 +49,7 @@ LocalStore::InodeHash LocalStore::loadInodeHash() debug("loading hash inodes in memory"); InodeHash inodeHash; - AutoCloseDir dir(opendir(linksDir.c_str())); + AutoCloseDir dir(sys::opendir(linksDir)); if (!dir) throw SysError("opening directory '%1%'", linksDir); struct dirent * dirent; @@ -68,7 +70,7 @@ Strings LocalStore::readDirectoryIgnoringInodes(const Path & path, const InodeHa { Strings names; - AutoCloseDir dir(opendir(path.c_str())); + AutoCloseDir dir(sys::opendir(path)); if (!dir) throw SysError("opening directory '%1%'", path); struct dirent * dirent; @@ -167,15 +169,16 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats, "There may be more corrupted paths." "\nYou should run `nix-store --verify --check-contents --repair` to fix them all" ); - if (unlink(linkPath.c_str()) == -1 && errno != ENOENT) + if (sys::unlink(linkPath) == -1 && errno != ENOENT) { throw SysError("cannot unlink '%1%'", linkPath); + } stLinkOpt.reset(); } } if (!stLinkOpt) { /* Nope, create a hard link in the links directory. */ - if (link(path.c_str(), linkPath.c_str()) == 0) { + if (sys::link(path, linkPath) == 0) { inodeHash.insert(st.st_ino); return; } @@ -221,9 +224,9 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats, MakeReadOnly makeReadOnly(mustToggle ? dirOfPath : ""); Path tempLink = makeTempPath(config().realStoreDir, "/.tmp-link"); - unlink(tempLink.c_str()); // just in case; ignore errors + (void) sys::unlink(tempLink); // just in case; ignore errors - if (link(linkPath.c_str(), tempLink.c_str()) == -1) { + if (sys::link(linkPath, tempLink) == -1) { if (errno == EMLINK) { /* Too many links to the same file (>= 32000 on most file systems). This is likely to happen with empty files. @@ -239,8 +242,9 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats, try { renameFile(tempLink, path); } catch (SysError & e) { - if (unlink(tempLink.c_str()) == -1) + if (sys::unlink(tempLink) == -1) { printError("unable to unlink '%1%': %2%", tempLink, strerror(errno)); + } if (errno == EMLINK) { /* Some filesystems generate too many links on the rename, rather than on the original link. (Probably it diff --git a/lix/libstore/pathlocks.cc b/lix/libstore/pathlocks.cc index befe0c26e..beb503ca7 100644 --- a/lix/libstore/pathlocks.cc +++ b/lix/libstore/pathlocks.cc @@ -1,5 +1,6 @@ #include "lix/libstore/pathlocks.hh" #include "lix/libutil/async.hh" +#include "lix/libutil/c-calls.hh" #include "lix/libutil/file-descriptor.hh" #include "lix/libutil/logging.hh" #include "lix/libutil/signals.hh" @@ -20,7 +21,7 @@ namespace nix { AutoCloseFD openLockFile(const Path & path, bool create) { - AutoCloseFD fd{open(path.c_str(), O_CLOEXEC | O_RDWR | (create ? O_CREAT : 0), 0600)}; + AutoCloseFD fd{sys::open(path, O_CLOEXEC | O_RDWR | (create ? O_CREAT : 0), 0600)}; if (!fd && (create || errno != ENOENT)) throw SysError("opening lock file '%1%'", path); @@ -235,7 +236,7 @@ void PathLock::unlock() // this file it will figure out that the file is stale once it calls stat() // and inspects the link count. if unlink fails we merely leave around some // stale lock file paths that can be reused or cleaned up by other threads. - unlink(path.c_str()); + (void) sys::unlink(path); // clobber file contents for compatibility wither other nix implementations writeFull(fd.get(), "d"); diff --git a/lix/libstore/platform/linux.cc b/lix/libstore/platform/linux.cc index 5b29558c7..b94b7e327 100644 --- a/lix/libstore/platform/linux.cc +++ b/lix/libstore/platform/linux.cc @@ -1,4 +1,5 @@ #include "lix/libstore/build/worker.hh" +#include "lix/libutil/c-calls.hh" #include "lix/libutil/cgroup.hh" #include "lix/libutil/file-descriptor.hh" #include "lix/libutil/file-system.hh" @@ -46,7 +47,7 @@ static void readProcLink(const std::string & file, UncheckedRoots & roots) { constexpr auto bufsiz = PATH_MAX; char buf[bufsiz]; - auto res = readlink(file.c_str(), buf, bufsiz); + auto res = sys::readlink(file, buf, bufsiz); if (res == -1) { if (errno == ENOENT || errno == EACCES || errno == ESRCH) { return; @@ -96,7 +97,7 @@ try { readProcLink(fmt("/proc/%s/cwd", ent->d_name), unchecked); auto fdStr = fmt("/proc/%s/fd", ent->d_name); - auto fdDir = AutoCloseDir(opendir(fdStr.c_str())); + auto fdDir = sys::opendir(fdStr); if (!fdDir) { if (errno == ENOENT || errno == EACCES) { continue; @@ -787,11 +788,19 @@ void LinuxLocalDerivationGoal::prepareSandbox() printMsg(lvlChatty, "setting up chroot environment in '%1%'", chrootRootDir); // FIXME: make this 0700 - if (mkdir(chrootRootDir.c_str(), buildUser && buildUser->getUIDCount() != 1 ? 0755 : 0750) == -1) + if (sys::mkdir(chrootRootDir, buildUser && buildUser->getUIDCount() != 1 ? 0755 : 0750) == -1) { throw SysError("cannot create '%1%'", chrootRootDir); + } - if (buildUser && chown(chrootRootDir.c_str(), buildUser->getUIDCount() != 1 ? buildUser->getUID() : 0, buildUser->getGID()) == -1) + if (buildUser + && sys::chown( + chrootRootDir, + buildUser->getUIDCount() != 1 ? buildUser->getUID() : 0, + buildUser->getGID() + ) == -1) + { throw SysError("cannot change ownership of '%1%'", chrootRootDir); + } /* Create a writable /tmp in the chroot. Many builders need this. (Of course they should really respect $TMPDIR @@ -827,8 +836,9 @@ void LinuxLocalDerivationGoal::prepareSandbox() createDirs(chrootStoreDir); chmodPath(chrootStoreDir, 01775); - if (buildUser && chown(chrootStoreDir.c_str(), 0, buildUser->getGID()) == -1) + if (buildUser && sys::chown(chrootStoreDir, 0, buildUser->getGID()) == -1) { throw SysError("cannot change ownership of '%1%'", chrootStoreDir); + } for (auto & i : inputPaths) { auto p = worker.store.printStorePath(i); @@ -1077,7 +1087,7 @@ Pid LinuxLocalDerivationGoal::startChild(std::function openSlave) // clang-format on }; - AutoCloseFD netns(open(fmt("/proc/%i/ns/net", pid.get()).c_str(), O_RDONLY | O_CLOEXEC)); + AutoCloseFD netns(sys::open(fmt("/proc/%i/ns/net", pid.get()), O_RDONLY | O_CLOEXEC)); if (!netns) { throw SysError("failed to open netns"); } @@ -1085,7 +1095,7 @@ Pid LinuxLocalDerivationGoal::startChild(std::function openSlave) AutoCloseFD userns; if (usingUserNamespace) { userns = - AutoCloseFD(open(fmt("/proc/%i/ns/user", pid.get()).c_str(), O_RDONLY | O_CLOEXEC)); + AutoCloseFD(sys::open(fmt("/proc/%i/ns/user", pid.get()), O_RDONLY | O_CLOEXEC)); if (!userns) { throw SysError("failed to open userns"); } diff --git a/lix/libstore/profiles.cc b/lix/libstore/profiles.cc index aa511fd86..296040c05 100644 --- a/lix/libstore/profiles.cc +++ b/lix/libstore/profiles.cc @@ -1,6 +1,7 @@ #include "lix/libstore/profiles.hh" #include "lix/libstore/local-fs-store.hh" #include "lix/libutil/async.hh" +#include "lix/libutil/c-calls.hh" #include "lix/libutil/types.hh" #include "lix/libutil/users.hh" #include "lix/libutil/strings.hh" @@ -114,8 +115,9 @@ try { static void removeFile(const Path & path) { - if (remove(path.c_str()) == -1) + if (sys::remove(path) == -1) { throw SysError("cannot unlink '%1%'", path); + } } diff --git a/lix/libstore/remote-fs-accessor.cc b/lix/libstore/remote-fs-accessor.cc index b00df732e..83c79e0a7 100644 --- a/lix/libstore/remote-fs-accessor.cc +++ b/lix/libstore/remote-fs-accessor.cc @@ -1,5 +1,6 @@ #include "lix/libstore/remote-fs-accessor.hh" #include "lix/libstore/nar-accessor.hh" +#include "lix/libutil/c-calls.hh" #include "lix/libutil/json.hh" #include @@ -72,10 +73,9 @@ try { try { listing = nix::readFile(makeCacheFile(storePath.hashPart(), "ls")); - auto narAccessor = makeLazyNarAccessor(listing, - [cacheFile](uint64_t offset, uint64_t length) { - - AutoCloseFD fd{open(cacheFile.c_str(), O_RDONLY | O_CLOEXEC)}; + auto narAccessor = + makeLazyNarAccessor(listing, [cacheFile](uint64_t offset, uint64_t length) { + AutoCloseFD fd{sys::open(cacheFile, O_RDONLY | O_CLOEXEC)}; if (!fd) throw SysError("opening NAR cache file '%s'", cacheFile); diff --git a/lix/libstore/sqlite.cc b/lix/libstore/sqlite.cc index f90757d1f..5a9800b1e 100644 --- a/lix/libstore/sqlite.cc +++ b/lix/libstore/sqlite.cc @@ -1,4 +1,5 @@ #include "lix/libutil/async.hh" +#include "lix/libutil/c-calls.hh" #include "lix/libutil/charptr-cast.hh" #include "lix/libstore/sqlite.hh" #include "lix/libstore/globals.hh" @@ -71,7 +72,8 @@ SQLite::SQLite(const Path & path, SQLiteOpenMode mode) if (mode == SQLiteOpenMode::Normal) flags |= SQLITE_OPEN_CREATE; auto uri = "file:" + percentEncode(path) + "?immutable=" + (immutable ? "1" : "0"); sqlite3 * db; - int ret = sqlite3_open_v2(uri.c_str(), &db, SQLITE_OPEN_URI | flags, vfs); + // NOLINTNEXTLINE(lix-unsafe-c-calls): vfs is safe + int ret = sqlite3_open_v2(requireCString(uri), &db, SQLITE_OPEN_URI | flags, vfs); if (ret != SQLITE_OK) { const char * err = sqlite3_errstr(ret); throw Error("cannot open SQLite database '%s': %s", path, err); @@ -108,8 +110,9 @@ void SQLite::isCache() void SQLite::exec(const std::string & stmt, NeverAsync) { retrySQLite([&]() { - if (sqlite3_exec(db.get(), stmt.c_str(), 0, 0, 0) != SQLITE_OK) + if (sqlite3_exec(db.get(), requireCString(stmt), 0, 0, 0) != SQLITE_OK) { SQLiteError::throw_(db.get(), "executing SQLite statement '%s'", stmt); + } }); } @@ -145,8 +148,9 @@ SQLiteStmt::SQLiteStmt(sqlite3 * db, const std::string & sql) { checkInterrupt(); sqlite3_stmt * stmt; - if (sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0) != SQLITE_OK) + if (sqlite3_prepare_v2(db, requireCString(sql), -1, &stmt, 0) != SQLITE_OK) { SQLiteError::throw_(db, "creating statement '%s'", sql); + } this->stmt = {stmt, {this}}; this->db = db; this->sql = sql; @@ -179,6 +183,7 @@ SQLiteStmt::Use::~Use() SQLiteStmt::Use & SQLiteStmt::Use::operator () (std::string_view value, bool notNull) { if (notNull) { + // NOLINTNEXTLINE(lix-unsafe-c-calls): nul bytes are allowed, hence the length arg if (sqlite3_bind_text( stmt.stmt.get(), curArg++, value.data(), value.length(), SQLITE_TRANSIENT ) @@ -272,6 +277,7 @@ SQLiteTxn::SQLiteTxn(sqlite3 * db, SQLiteTxnType type) sql = "begin exclusive;"; break; } + // NOLINTNEXTLINE(lix-unsafe-c-calls): only immediate strings here if (sqlite3_exec(db, sql, 0, 0, 0) != SQLITE_OK) SQLiteError::throw_(db, "starting transaction"); this->db.reset(db); diff --git a/lix/libstore/ssh.cc b/lix/libstore/ssh.cc index c314d4211..15afdc339 100644 --- a/lix/libstore/ssh.cc +++ b/lix/libstore/ssh.cc @@ -1,3 +1,4 @@ +#include "lix/libutil/c-calls.hh" #include "lix/libutil/current-process.hh" #include "lix/libutil/environment-variables.hh" #include "lix/libstore/ssh.hh" @@ -92,7 +93,7 @@ std::unique_ptr SSH::startCommand(const std::string & command) throw SysError("duping over stderr"); } - execvp(args.begin()->c_str(), stringsToCharPtrs(args).data()); + sys::execvp(*args.begin(), args); // could not exec ssh/bash throw SysError("unable to execute '%s'", args.front()); diff --git a/lix/libstore/store-api.cc b/lix/libstore/store-api.cc index 8607a4945..52bea0d33 100644 --- a/lix/libstore/store-api.cc +++ b/lix/libstore/store-api.cc @@ -7,6 +7,7 @@ #include "lix/libutil/async-io.hh" #include "lix/libutil/async.hh" #include "lix/libutil/box_ptr.hh" +#include "lix/libutil/c-calls.hh" #include "lix/libutil/hash.hh" #include "lix/libutil/json.hh" #include "lix/libutil/logging.hh" @@ -1366,7 +1367,7 @@ openFromNonUri(const std::string & uri, const StoreConfig::Params & params, Allo auto stateDir = getOr(params, "state", settings.nixStateDir); if (allowDaemon == AllowDaemon::Allow && pathExists(settings.nixDaemonSocketFile)) { return make_ref(params); - } else if (access(stateDir.c_str(), R_OK | W_OK) == 0) { + } else if (sys::access(stateDir, R_OK | W_OK) == 0) { return LocalStore::makeLocalStore(params); } #if __linux__ diff --git a/lix/libstore/temporary-dir.cc b/lix/libstore/temporary-dir.cc index 685d5aee7..43047c9e1 100644 --- a/lix/libstore/temporary-dir.cc +++ b/lix/libstore/temporary-dir.cc @@ -1,5 +1,6 @@ #include "lix/libstore/temporary-dir.hh" +#include "lix/libutil/c-calls.hh" #include "lix/libutil/file-system.hh" #include "lix/libstore/globals.hh" @@ -15,7 +16,7 @@ std::pair createTempFile(const Path & prefix) { Path tmpl(defaultTempDir() + "/" + prefix + ".XXXXXX"); // FIXME: use O_TMPFILE. - AutoCloseFD fd(mkstemp(tmpl.data())); + AutoCloseFD fd(sys::mkstemp(tmpl)); if (!fd) throw SysError("creating temporary file '%s'", tmpl); closeOnExec(fd.get()); diff --git a/lix/libutil/archive.cc b/lix/libutil/archive.cc index cf9b5295c..2a04e1797 100644 --- a/lix/libutil/archive.cc +++ b/lix/libutil/archive.cc @@ -13,6 +13,7 @@ #include #include "lix/libutil/archive.hh" +#include "c-calls.hh" #include "lix/libutil/async-io.hh" #include "lix/libutil/box_ptr.hh" #include "lix/libutil/config.hh" @@ -40,7 +41,7 @@ PathFilter defaultPathFilter = [](const Path &) { return true; }; static WireFormatGenerator dumpContents(Path path, off_t size) { - AutoCloseFD fd{open(path.c_str(), O_RDONLY | O_CLOEXEC)}; + AutoCloseFD fd{sys::open(path, O_RDONLY | O_CLOEXEC)}; if (!fd) throw SysError("opening file '%1%'", path); std::vector buf(65536); @@ -316,6 +317,7 @@ struct CaseInsensitiveCompare { bool operator() (const std::string & a, const std::string & b) const { + // NOLINTNEXTLINE(lix-unsafe-c-calls): valid pathnames never contain nuls return strcasecmp(a.c_str(), b.c_str()) < 0; } }; @@ -1032,8 +1034,9 @@ public: { auto name = maybeCaseHackFilename(name_); Path p = dstPath + name; - if (mkdir(p.c_str(), 0777) == -1) + if (sys::mkdir(p, 0777) == -1) { throw SysError("creating directory '%1%'", p); + } return make_box_ptr(p + "/", useCaseHack); }; @@ -1041,7 +1044,7 @@ public: { auto name = maybeCaseHackFilename(name_); Path p = dstPath + name; - AutoCloseFD fd = AutoCloseFD{open(p.c_str(), O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0666)}; + AutoCloseFD fd = sys::open(p, O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0666); if (!fd) throw SysError("creating file '%1%'", p); return make_box_ptr(std::move(fd), size, executable); diff --git a/lix/libutil/args.cc b/lix/libutil/args.cc index 5560d3bc5..f85975f41 100644 --- a/lix/libutil/args.cc +++ b/lix/libutil/args.cc @@ -1,4 +1,5 @@ #include "lix/libutil/args.hh" +#include "c-calls.hh" #include "lix/libutil/args/root.hh" #include "lix/libutil/hash.hh" #include "lix/libutil/strings.hh" @@ -366,6 +367,7 @@ static void _completePath(AddCompletions & completions, std::string_view prefix, flags |= GLOB_ONLYDIR; #endif // using expandTilde here instead of GLOB_TILDE(_CHECK) so that ~ expands to /home/user/ + // NOLINTNEXTLINE(lix-unsafe-c-calls) if (glob((expandTilde(prefix) + "*").c_str(), flags, nullptr, &globbuf) == 0) { for (size_t i = 0; i < globbuf.gl_pathc; ++i) { if (onlyDirs) { @@ -603,7 +605,7 @@ void ExternalCommand::run() { "running external command: %s", concatMapStringsSep(" ", externalArgv, shellEscape) ); - execv(absoluteBinaryPath.c_str(), stringsToCharPtrs(externalArgv).data()); + sys::execv(absoluteBinaryPath, externalArgv); throw SysError(errno, "failed to execute external command '%1%'", absoluteBinaryPath); } diff --git a/lix/libutil/c-calls.cc b/lix/libutil/c-calls.cc new file mode 100644 index 000000000..bfc851849 --- /dev/null +++ b/lix/libutil/c-calls.cc @@ -0,0 +1,280 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "c-calls.hh" +#include "error.hh" +#include "strings.hh" + +namespace nix { +CString requireCString(const std::string & s) +{ + if (s.contains('\0')) { + std::string p{s}; + for (auto pos = p.find('\0'); pos != p.npos; pos = p.find('\0')) { + p.replace(pos, 1, "␀"); + } + throw Error( + "string %s that contains NUL bytes was used in a place that doesn't allow this", p + ); + } + return CString{s}; +} +} + +namespace nix::sys { + +AutoCloseFD open(const std::string & path, int flags) +{ + return AutoCloseFD{::open(requireCString(path), flags)}; +} + +AutoCloseFD open(const std::string & path, int flags, mode_t mode) +{ + return AutoCloseFD{::open(requireCString(path), flags, mode)}; +} + +AutoCloseFD openat(int dir, const std::string & path, int flags) +{ + return AutoCloseFD{::openat(dir, requireCString(path), flags)}; +} + +AutoCloseFD openat(int dir, const std::string & path, int flags, mode_t mode) +{ + return AutoCloseFD{::openat(dir, requireCString(path), flags, mode)}; +} + +AutoCloseDir opendir(const std::string & path) +{ + return AutoCloseDir{::opendir(requireCString(path))}; +} + +int mkdir(const std::string & path, mode_t mode) +{ + return ::mkdir(requireCString(path), mode); +} + +int lstat(const std::string & path, struct ::stat * st) +{ + return ::lstat(requireCString(path), st); +} + +int stat(const std::string & path, struct ::stat * st) +{ + return ::stat(requireCString(path), st); +} + +int unlink(const std::string & path) +{ + return ::unlink(requireCString(path)); +} + +int access(const std::string & path, int mode) +{ + return ::access(requireCString(path), mode); +} + +int chmod(const std::string & path, mode_t mode) +{ + return ::chmod(requireCString(path), mode); +} + +int chown(const std::string & path, uid_t uid, gid_t gid) +{ + return ::chown(requireCString(path), uid, gid); +} + +int fchownat(int dir, const std::string & path, uid_t uid, gid_t gid, int flags) +{ + return ::fchownat(dir, requireCString(path), uid, gid, flags); +} + +int lchown(const std::string & path, uid_t uid, gid_t gid) +{ + return ::lchown(requireCString(path), uid, gid); +} + +int rename(const std::string & oldPath, const std::string & newPath) +{ + return ::rename(requireCString(oldPath), requireCString(newPath)); +} + +int utimes(const std::string & path, const struct ::timeval times[2]) +{ + return ::utimes(requireCString(path), times); +} + +int lutimes(const std::string & path, const struct ::timeval times[2]) +{ + return ::lutimes(requireCString(path), times); +} + +int link(const std::string & oldPath, const std::string & newPath) +{ + return ::link(requireCString(oldPath), requireCString(newPath)); +} + +int symlink(const std::string & target, const std::string & linkpath) +{ + return ::symlink(requireCString(target), requireCString(linkpath)); +} + +int unlinkat(int dir, const std::string & path, int flags) +{ + return ::unlinkat(dir, requireCString(path), flags); +} + +int remove(const std::string & path) +{ + return ::remove(requireCString(path)); +} + +int rmdir(const std::string & path) +{ + return ::rmdir(requireCString(path)); +} + +int fstatat(int dir, const std::string & path, struct ::stat * st, int flags) +{ + return ::fstatat(dir, requireCString(path), st, flags); +} + +int fchmodat(int dir, const std::string & path, mode_t mode, int flags) +{ + return ::fchmodat(dir, requireCString(path), mode, flags); +} + +int statvfs(const std::string & path, struct ::statvfs * st) +{ + return ::statvfs(requireCString(path), st); +} + +#if __linux__ +int mount( + const std::string & source, + const std::string & target, + const std::string & filesystemtype, + unsigned long mountflags, + const void * data +) +{ + return ::mount( + requireCString(source), + requireCString(target), + requireCString(filesystemtype), + mountflags, + data + ); +} + +ssize_t llistxattr(const std::string & path, char * list, size_t size) +{ + return ::llistxattr(requireCString(path), list, size); +} + +ssize_t lremovexattr(const std::string & path, const std::string & name) +{ + return ::lremovexattr(requireCString(path), requireCString(name)); +} + +ssize_t getxattr(const std::string & path, const std::string & name, void * value, size_t size) +{ + return ::getxattr(requireCString(path), requireCString(name), value, size); +} +#endif + +int chdir(const std::string & path) +{ + return ::chdir(requireCString(path)); +} + +int chroot(const std::string & path) +{ + return ::chroot(requireCString(path)); +} + +AutoCloseFD mkstemp(std::string & path) +{ + return AutoCloseFD{::mkstemp(path.data())}; +} + +ssize_t readlink(const std::string & path, char * buf, size_t bufsiz) +{ + return ::readlink(requireCString(path), buf, bufsiz); +} + +int execv(const std::string & path, const std::list & argv) +{ + return ::execv(requireCString(path), stringsToCharPtrs(argv).data()); +} + +int execvp(const std::string & path, const std::list & argv) +{ + return ::execvp(requireCString(path), stringsToCharPtrs(argv).data()); +} + +int execve( + const std::string & path, + const std::list & argv, + const std::list & envp +) +{ + // NOLINTNEXTLINE(lix-unsafe-c-calls) + return ::execve( + requireCString(path), stringsToCharPtrs(argv).data(), stringsToCharPtrs(envp).data() + ); +} + +char * getenv(const std::string & name) +{ + return ::getenv(requireCString(name)); +} + +int setenv(const std::string & name, const std::string & value, int overwrite) +{ + return ::setenv(requireCString(name), requireCString(value), overwrite); +} + +int unsetenv(const std::string & name) +{ + return ::unsetenv(requireCString(name)); +} + +struct group * getgrnam(const std::string & name) +{ + return ::getgrnam(requireCString(name)); +} + +std::optional getpwnam(const std::string & name) +{ + std::vector buf(1024); + auto cName = requireCString(name); + struct passwd pw, *result; + + while (true) { + const auto err = getpwnam_r(cName, &pw, buf.data(), buf.size(), &result); + if (err == ERANGE) { + buf.resize(buf.size() * 2); + continue; + } else if (err != 0) { + throw SysError(err, "getpwnam"); + } + if (!result) { + return std::nullopt; + } + return {{ + .pw_name = result->pw_name, + .pw_passwd = result->pw_passwd, + .pw_uid = result->pw_uid, + .pw_gid = result->pw_gid, + .pw_gecos = result->pw_gecos, + .pw_dir = result->pw_dir, + .pw_shell = result->pw_shell, + }}; + } +} +} diff --git a/lix/libutil/c-calls.hh b/lix/libutil/c-calls.hh new file mode 100644 index 000000000..8e9bccdfb --- /dev/null +++ b/lix/libutil/c-calls.hh @@ -0,0 +1,168 @@ +#pragma once +/** + * @file + * + * NUL-safe wrappers for C functions. + */ + +#include "file-descriptor.hh" +#include "file-system.hh" +#include +#include + +namespace nix { + +class CString +{ + friend CString requireCString(const std::string & s); + + const std::string * s; + + explicit CString(const std::string & s) : s(&s) {} + +public: + operator const char *() const + { + return s->c_str(); + } + + const char * asCStr() const + { + return *this; + } +}; + +CString requireCString(const std::string & s); +} + +namespace nix::sys { + +AutoCloseFD open(const std::string & path, int flags); +AutoCloseFD open(const std::string & path, int flags, mode_t mode); + +AutoCloseFD openat(int dir, const std::string & path, int flags); +AutoCloseFD openat(int dir, const std::string & path, int flags, mode_t mode); + +AutoCloseDir opendir(const std::string & path); + +[[nodiscard]] +int mkdir(const std::string & path, mode_t mode); + +[[nodiscard]] +int lstat(const std::string & path, struct ::stat * st); + +[[nodiscard]] +int stat(const std::string & path, struct ::stat * st); + +[[nodiscard]] +int unlink(const std::string & path); + +[[nodiscard]] +int access(const std::string & path, int mode); + +[[nodiscard]] +int chmod(const std::string & path, mode_t mode); + +[[nodiscard]] +int chown(const std::string & path, uid_t uid, gid_t gid); + +[[nodiscard]] +int fchownat(int dir, const std::string & path, uid_t uid, gid_t gid, int flags); + +[[nodiscard]] +int lchown(const std::string & path, uid_t uid, gid_t gid); + +[[nodiscard]] +int rename(const std::string & oldPath, const std::string & newPath); + +[[nodiscard]] +int utimes(const std::string & path, const struct ::timeval times[2]); +[[nodiscard]] +int lutimes(const std::string & path, const struct ::timeval times[2]); + +[[nodiscard]] +int link(const std::string & oldPath, const std::string & newPath); + +[[nodiscard]] +int symlink(const std::string & target, const std::string & linkpath); + +[[nodiscard]] +int unlinkat(int dir, const std::string & path, int flags); + +[[nodiscard]] +int remove(const std::string & path); + +[[nodiscard]] +int rmdir(const std::string & path); + +[[nodiscard]] +int fstatat(int dir, const std::string & path, struct ::stat * st, int flags); + +[[nodiscard]] +int fchmodat(int dir, const std::string & path, mode_t mode, int flags); + +[[nodiscard]] +int statvfs(const std::string & path, struct ::statvfs * st); + +#if __linux__ +[[nodiscard]] +int mount( + const std::string & source, + const std::string & target, + const std::string & filesystemtype, + unsigned long mountflags, + const void * data +); + +[[nodiscard]] +ssize_t llistxattr(const std::string & path, char * list, size_t size); + +[[nodiscard]] +ssize_t lremovexattr(const std::string & path, const std::string & name); + +[[nodiscard]] +ssize_t getxattr(const std::string & path, const std::string & name, void * value, size_t size); +#endif + +[[nodiscard]] +int chdir(const std::string & path); + +[[nodiscard]] +int chroot(const std::string & path); + +AutoCloseFD mkstemp(std::string & path); + +[[nodiscard]] +ssize_t readlink(const std::string & path, char * buf, size_t bufsiz); + +int execv(const std::string & path, const std::list & argv); +int execvp(const std::string & path, const std::list & argv); +int execve( + const std::string & path, + const std::list & argv, + const std::list & envp +); + +char * getenv(const std::string & name); + +[[nodiscard]] +int setenv(const std::string & name, const std::string & value, int overwrite); + +[[nodiscard]] +int unsetenv(const std::string & name); + +struct group * getgrnam(const std::string & name); + +struct Passwd +{ + std::string pw_name; + std::string pw_passwd; + uid_t pw_uid; + gid_t pw_gid; + std::string pw_gecos; + std::string pw_dir; + std::string pw_shell; +}; + +std::optional getpwnam(const std::string & name); +} diff --git a/lix/libutil/cgroup.cc b/lix/libutil/cgroup.cc index 4f34940ce..543dc112e 100644 --- a/lix/libutil/cgroup.cc +++ b/lix/libutil/cgroup.cc @@ -1,3 +1,4 @@ +#include "c-calls.hh" #include "error.hh" #include "file-descriptor.hh" #include "logging.hh" @@ -26,7 +27,7 @@ static bool isCgroupDelegated(const Path & path) { char delegate_xattr; - if (getxattr(path.c_str(), "user.delegate", &delegate_xattr, sizeof(delegate_xattr)) >= 1) { + if (sys::getxattr(path, "user.delegate", &delegate_xattr, sizeof(delegate_xattr)) >= 1) { if (delegate_xattr != '1') { throw Error("Unexpected `user.delegate` xattr: '%c'", delegate_xattr); } @@ -130,7 +131,7 @@ destroyCgroup(const std::string & name, const std::filesystem::path & aliveCgrou // has been excised. until then a timeout on group death will have to do. { auto eventsFile = aliveCgroup / "cgroup.events"; - AutoCloseFD events(open(eventsFile.c_str(), O_RDONLY)); + AutoCloseFD events(sys::open(eventsFile, O_RDONLY)); if (!events) { throw SysError("failed to open %s", eventsFile); } @@ -169,7 +170,7 @@ destroyCgroup(const std::string & name, const std::filesystem::path & aliveCgrou Result stats = readStatistics(aliveCgroup); - if (rmdir(aliveCgroup.c_str()) == -1) { + if (sys::rmdir(aliveCgroup) == -1) { throw SysError("deleting cgroup '%s' at '%s'", name, aliveCgroup); } @@ -316,13 +317,13 @@ AutoDestroyCgroup::AutoDestroyCgroup( { auto path = std::get(cgroup_); - if (mkdir(path.c_str(), 0755) == -1) { + if (sys::mkdir(path, 0755) == -1) { throw SysError( "cannot create the top-level directory at '%s' for cgroup '%s'", path, name_ ); } - if (chown(path.c_str(), uid, gid) == -1) { + if (sys::chown(path, uid, gid) == -1) { throw SysError( "cannot delegate the top-level directory '%s' from cgroup '%s' to user uid=%d,gid=%d", path, @@ -332,9 +333,9 @@ AutoDestroyCgroup::AutoDestroyCgroup( ); } - AutoCloseFD cgroupFd{open(path.c_str(), O_PATH | O_NOFOLLOW)}; + AutoCloseFD cgroupFd{sys::open(path, O_PATH | O_NOFOLLOW)}; for (auto node : {"procs", "threads", "subtree_control"}) { - if (fchownat(cgroupFd.get(), fmt("cgroup.%s", node).c_str(), uid, gid, 0) == -1) { + if (sys::fchownat(cgroupFd.get(), fmt("cgroup.%s", node), uid, gid, 0) == -1) { throw SysError( "cannot delegate '%s' from cgroup '%s' to user uid=%d,gid=%d", node, name_, uid, gid ); diff --git a/lix/libutil/compression.cc b/lix/libutil/compression.cc index 614ee982c..eea659c10 100644 --- a/lix/libutil/compression.cc +++ b/lix/libutil/compression.cc @@ -4,6 +4,7 @@ #include "error.hh" #include "file-descriptor.hh" #include "io-buffer.hh" +#include "lix/libutil/c-calls.hh" #include "lix/libutil/charptr-cast.hh" #include "lix/libutil/compression.hh" #include "lix/libutil/tarfile.hh" @@ -85,14 +86,21 @@ struct ArchiveCompressionSink : CompressionSink ArchiveCompressionSink(Sink & nextSink, std::string format, bool parallel, int level = COMPRESSION_LEVEL_DEFAULT) : nextSink(nextSink) { + auto cFormat = requireCString(format); archive = archive_write_new(); if (!archive) throw Error("failed to initialize libarchive"); - check(archive_write_add_filter_by_name(archive, format.c_str()), "couldn't initialize compression (%s)"); + check( + archive_write_add_filter_by_name(archive, cFormat), + "couldn't initialize compression (%s)" + ); check(archive_write_set_format_raw(archive)); - if (parallel) - check(archive_write_set_filter_option(archive, format.c_str(), "threads", "0")); + if (parallel) { + check(archive_write_set_filter_option(archive, cFormat, "threads", "0")); + } if (level != COMPRESSION_LEVEL_DEFAULT) - check(archive_write_set_filter_option(archive, format.c_str(), "compression-level", std::to_string(level).c_str())); + check(archive_write_set_filter_option( + archive, cFormat, "compression-level", requireCString(std::to_string(level)) + )); // disable internal buffering check(archive_write_set_bytes_per_block(archive, 0)); // disable output padding diff --git a/lix/libutil/environment-variables.cc b/lix/libutil/environment-variables.cc index a6999d616..c9223521b 100644 --- a/lix/libutil/environment-variables.cc +++ b/lix/libutil/environment-variables.cc @@ -1,3 +1,4 @@ +#include "c-calls.hh" #include #include #include @@ -11,7 +12,7 @@ namespace nix { std::optional getEnv(const std::string & key) { - char * value = getenv(key.c_str()); + char * value = sys::getenv(key); if (!value) return {}; return std::string(value); } @@ -40,14 +41,14 @@ std::map getEnv() void clearEnv() { for (auto & name : getEnv()) - unsetenv(name.first.c_str()); + (void) sys::unsetenv(name.first); } void replaceEnv(const std::map & newEnv) { clearEnv(); - for (auto & newEnvVar : newEnv) - setenv(newEnvVar.first.c_str(), newEnvVar.second.c_str(), 1); + for (auto & newEnvVar : newEnv) { + (void) sys::setenv(newEnvVar.first, newEnvVar.second, 1); + } } - } diff --git a/lix/libutil/file-descriptor.cc b/lix/libutil/file-descriptor.cc index 296837be9..0c8f1d9b3 100644 --- a/lix/libutil/file-descriptor.cc +++ b/lix/libutil/file-descriptor.cc @@ -184,7 +184,7 @@ std::string guessOrInventPathFromFD(int fd) * But we can read /proc/ */ #if __linux__ try { - return readLink(fmt("/proc/self/fd/%1%", fd).c_str()); + return readLink(fmt("/proc/self/fd/%1%", fd)); } catch (...) { } #elif defined (HAVE_F_GETPATH) && HAVE_F_GETPATH diff --git a/lix/libutil/file-system.cc b/lix/libutil/file-system.cc index 6f0580062..ebb46549e 100644 --- a/lix/libutil/file-system.cc +++ b/lix/libutil/file-system.cc @@ -1,9 +1,12 @@ +#include #include #include #include #include #include +#include +#include "c-calls.hh" #include "lix/libutil/environment-variables.hh" #include "lix/libutil/file-descriptor.hh" #include "lix/libutil/file-system.hh" @@ -106,7 +109,7 @@ Path canonPath(PathView path, bool resolveSymlinks) Path realPath(Path const & path) { // With nullptr, realpath() malloc's and returns a new c-string. - char * resolved = realpath(path.c_str(), nullptr); + char * resolved = realpath(requireCString(path), nullptr); int saved = errno; if (resolved == nullptr) { throw SysError(saved, "cannot get realpath for '%s'", path); @@ -138,8 +141,9 @@ Path tildePath(Path const & path, const std::optional & home) void chmodPath(const Path & path, mode_t mode) { - if (chmod(path.c_str(), mode) == -1) + if (sys::chmod(path, mode) == -1) { throw SysError("setting permissions on '%s'", path); + } } Path dirOf(const PathView path) @@ -199,8 +203,9 @@ bool isDirOrInDir(std::string_view path, std::string_view dir) struct stat stat(const Path & path) { struct stat st; - if (stat(path.c_str(), &st)) + if (sys::stat(path, &st)) { throw SysError("getting status of '%1%'", path); + } return st; } @@ -208,16 +213,16 @@ struct stat stat(const Path & path) struct stat lstat(const Path & path) { struct stat st; - if (lstat(path.c_str(), &st)) + if (sys::lstat(path, &st)) { throw SysError("getting status of '%1%'", path); + } return st; } std::optional maybeStat(const Path & path) { std::optional st{std::in_place}; - if (stat(path.c_str(), &*st)) - { + if (sys::stat(path, &*st)) { if (errno == ENOENT || errno == ENOTDIR) st.reset(); else @@ -229,8 +234,7 @@ std::optional maybeStat(const Path & path) std::optional maybeLstat(const Path & path) { std::optional st{std::in_place}; - if (lstat(path.c_str(), &*st)) - { + if (sys::lstat(path, &*st)) { if (errno == ENOENT || errno == ENOTDIR) st.reset(); else @@ -271,7 +275,7 @@ Path readLink(const Path & path) std::vector buf; for (ssize_t bufSize = PATH_MAX/4; true; bufSize += bufSize/2) { buf.resize(bufSize); - ssize_t rlSize = readlink(path.c_str(), buf.data(), bufSize); + ssize_t rlSize = sys::readlink(path, buf.data(), bufSize); if (rlSize == -1) if (errno == EINVAL) throw Error("'%1%' is not a symlink", path); @@ -317,7 +321,7 @@ static DirEntries readDirectory(DIR *dir, const Path & path, bool interruptible) static DirEntries readDirectory(const Path & path, bool interruptible) { - AutoCloseDir dir(opendir(path.c_str())); + AutoCloseDir dir(sys::opendir(path)); if (!dir) throw SysError("opening directory '%1%'", path); return readDirectory(dir.get(), path, interruptible); @@ -341,7 +345,7 @@ unsigned char getFileType(const Path & path) std::string readFile(const Path & path) { - AutoCloseFD fd{open(path.c_str(), O_RDONLY | O_CLOEXEC)}; + AutoCloseFD fd{sys::open(path, O_RDONLY | O_CLOEXEC)}; if (!fd) throw SysError("opening file '%1%'", path); return readFile(fd.get()); @@ -350,7 +354,7 @@ std::string readFile(const Path & path) Generator readFileSource(const Path & path) { - AutoCloseFD fd{open(path.c_str(), O_RDONLY | O_CLOEXEC)}; + AutoCloseFD fd{sys::open(path, O_RDONLY | O_CLOEXEC)}; if (!fd) throw SysError("opening file '%s'", path); return [](AutoCloseFD fd) -> Generator { @@ -360,7 +364,7 @@ Generator readFileSource(const Path & path) void writeFile(const Path & path, std::string_view s, mode_t mode, bool allowInterrupts) { - AutoCloseFD fd{open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, mode)}; + AutoCloseFD fd{sys::open(path, O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, mode)}; if (!fd) throw SysError("opening file '%1%'", path); @@ -389,7 +393,7 @@ void writeFileUninterruptible(const Path & path, std::string_view s, mode_t mode void writeFileAndSync(const Path & path, std::string_view s, mode_t mode) { { - AutoCloseFD fd{open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, mode)}; + AutoCloseFD fd{sys::open(path, O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, mode)}; if (!fd) { throw SysError("opening file '%1%'", path); } @@ -405,7 +409,7 @@ void writeFileAndSync(const Path & path, std::string_view s, mode_t mode) static AutoCloseFD openForWrite(const Path & path, mode_t mode) { - AutoCloseFD fd{open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, mode)}; + AutoCloseFD fd{sys::open(path, O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, mode)}; if (!fd) throw SysError("opening file '%1%'", path); return fd; @@ -467,7 +471,7 @@ try { void syncParent(const Path & path) { - AutoCloseFD fd{open(dirOf(path).c_str(), O_RDONLY, 0)}; + AutoCloseFD fd{sys::open(dirOf(path), O_RDONLY, 0)}; if (!fd) throw SysError("opening file '%1%'", path); fd.fsync(); @@ -493,7 +497,7 @@ static void _deletePath(int parentfd, const std::string & name, uint64_t & bytes */ struct stat st; - if (fstatat(parentfd, name.c_str(), &st, AT_SYMLINK_NOFOLLOW) == -1) { + if (sys::fstatat(parentfd, name, &st, AT_SYMLINK_NOFOLLOW) == -1) { if (errno == ENOENT) return; throw SysError("getting status of '%1%' in directory '%2%'", name, guessOrInventPathFromFD(parentfd)); } @@ -526,23 +530,29 @@ static void _deletePath(int parentfd, const std::string & name, uint64_t & bytes /* Make the directory accessible. */ const auto PERM_MASK = S_IRUSR | S_IWUSR | S_IXUSR; if ((st.st_mode & PERM_MASK) != PERM_MASK) { - if (fchmodat(parentfd, name.c_str(), st.st_mode | PERM_MASK, 0) == -1) { + if (sys::fchmodat(parentfd, name, st.st_mode | PERM_MASK, 0) == -1) { throw SysError("chmod '%1%' in directory '%2%'", name, guessOrInventPathFromFD(parentfd)); } } - int fd = openat(parentfd, name.c_str(), O_RDONLY | O_DIRECTORY | O_NOFOLLOW); - if (fd == -1) - throw SysError("opening directory '%1%' in directory '%2%'", name, guessOrInventPathFromFD(parentfd)); - AutoCloseDir dir(fdopendir(fd)); + auto fd = sys::openat(parentfd, name, O_RDONLY | O_DIRECTORY | O_NOFOLLOW); + if (!fd) { + throw SysError( + "opening directory '%1%' in directory '%2%'", + name, + guessOrInventPathFromFD(parentfd) + ); + } + AutoCloseDir dir(fdopendir(fd.get())); if (!dir) throw SysError("opening directory '%1%' in directory '%2%'", name, guessOrInventPathFromFD(parentfd)); + fd.release(); for (auto & i : readDirectory(dir.get(), name, interruptible)) _deletePath(dirfd(dir.get()), i.name, bytesFreed, interruptible); } int flags = S_ISDIR(st.st_mode) ? AT_REMOVEDIR : 0; - if (unlinkat(parentfd, name.c_str(), flags) == -1) { + if (sys::unlinkat(parentfd, name, flags) == -1) { if (errno == ENOENT) return; throw SysError("cannot unlink '%1%' in directory '%2%'", name, guessOrInventPathFromFD(parentfd)); } @@ -554,7 +564,7 @@ static void _deletePath(const Path & path, uint64_t & bytesFreed, bool interrupt if (dir == "") dir = "/"; - AutoCloseFD dirfd{open(dir.c_str(), O_RDONLY)}; + AutoCloseFD dirfd{sys::open(dir, O_RDONLY)}; if (!dirfd) { if (errno == ENOENT) return; throw SysError("opening directory '%1%'", path); @@ -590,16 +600,18 @@ Paths createDirs(const Path & path) if (path == "/") return created; struct stat st; - if (lstat(path.c_str(), &st) == -1) { + if (sys::lstat(path, &st) == -1) { created = createDirs(dirOf(path)); - if (mkdir(path.c_str(), 0777) == -1 && errno != EEXIST) + if (sys::mkdir(path, 0777) == -1 && errno != EEXIST) { throw SysError("creating directory '%1%'", path); + } st = lstat(path); created.push_back(path); } - if (S_ISLNK(st.st_mode) && stat(path.c_str(), &st) == -1) + if (S_ISLNK(st.st_mode) && sys::stat(path, &st) == -1) { throw SysError("statting symlink '%1%'", path); + } if (!S_ISDIR(st.st_mode)) throw Error("'%1%' is not a directory", path); @@ -624,8 +636,9 @@ AutoDelete::~AutoDelete() if (recursive) deletePath(path); else { - if (remove(path.c_str()) == -1) + if (sys::remove(path) == -1) { throw SysError("cannot unlink '%1%'", path); + } } } } catch (...) { @@ -666,7 +679,7 @@ Path createTempSubdir(const Path & parent, const Path & prefix, while (1) { checkInterrupt(); Path tmpDir = tempName(parent, prefix, includePid, counter); - if (mkdir(tmpDir.c_str(), mode) == 0) { + if (sys::mkdir(tmpDir, mode) == 0) { #if __FreeBSD__ /* Explicitly set the group of the directory. This is to work around around problems caused by BSD's group @@ -695,8 +708,9 @@ Path makeTempPath(const Path & root, const Path & suffix) void createSymlink(const Path & target, const Path & link) { - if (symlink(target.c_str(), link.c_str())) + if (sys::symlink(target, link)) { throw SysError("creating symlink from '%1%' to '%2%'", link, target); + } } void replaceSymlink(const Path & target, const Path & link) @@ -728,14 +742,15 @@ void setWriteTime(const fs::path & p, const struct stat & st) .tv_sec = st.st_mtime, .tv_usec = 0, }; - if (lutimes(p.c_str(), times) != 0) + if (sys::lutimes(p, times) != 0) { throw SysError("changing modification time of '%s'", p); + } } void copy(const fs::directory_entry & from, const fs::path & to, CopyFileFlags flags) { // TODO: Rewrite the `is_*` to use `symlink_status()` - auto statOfFrom = lstat(from.path().c_str()); + auto statOfFrom = lstat(from.path()); auto fromStatus = from.symlink_status(); // Mark the directory as writable so that we can delete its children diff --git a/lix/libutil/logging.cc b/lix/libutil/logging.cc index a2248395c..7305700c8 100644 --- a/lix/libutil/logging.cc +++ b/lix/libutil/logging.cc @@ -1,3 +1,4 @@ +#include "c-calls.hh" #include "lix/libutil/environment-variables.hh" #include "lix/libutil/file-descriptor.hh" #include "lix/libutil/logging.hh" @@ -366,7 +367,7 @@ void logFatal(std::string const & s) { writeLogsToStderr(s + "\n"); // std::string for guaranteed null termination - syslog(LOG_CRIT, "%s", s.c_str()); + syslog(LOG_CRIT, "%s", requireCString(s).asCStr()); } } diff --git a/lix/libutil/meson.build b/lix/libutil/meson.build index f3b2332d5..8ca4eded3 100644 --- a/lix/libutil/meson.build +++ b/lix/libutil/meson.build @@ -3,6 +3,7 @@ libutil_sources = files( 'archive.cc', 'args.cc', 'async-io.cc', + 'c-calls.cc', 'canon-path.cc', 'cgroup.cc', 'compression.cc', @@ -66,6 +67,7 @@ libutil_headers = files( 'async.hh', 'backed-string-view.hh', 'box_ptr.hh', + 'c-calls.hh', 'canon-path.hh', 'cgroup.hh', 'charptr-cast.hh', diff --git a/lix/libutil/mount.cc b/lix/libutil/mount.cc index 830c2c538..151df835f 100644 --- a/lix/libutil/mount.cc +++ b/lix/libutil/mount.cc @@ -1,4 +1,5 @@ #include "lix/libutil/mount.hh" +#include "c-calls.hh" #include "lix/libutil/error.hh" #include "lix/libutil/file-system.hh" #include "lix/libutil/logging.hh" @@ -12,8 +13,9 @@ void bindPath(const Path & source, const Path & target, bool optional, CopyFileF debug("bind mounting '%1%' to '%2%'", source, target); auto bindMount = [&]() { - if (mount(source.c_str(), target.c_str(), "", MS_BIND | MS_REC, 0) == -1) + if (sys::mount(source, target, "", MS_BIND | MS_REC, 0) == -1) { throw SysError("bind mount from '%1%' to '%2%' failed", source, target); + } }; auto maybeSt = maybeLstat(source); diff --git a/lix/libutil/namespaces.cc b/lix/libutil/namespaces.cc index 97ec9029a..24def2736 100644 --- a/lix/libutil/namespaces.cc +++ b/lix/libutil/namespaces.cc @@ -1,3 +1,4 @@ +#include "c-calls.hh" #include "lix/libutil/file-descriptor.hh" #include "lix/libutil/file-system.hh" #include "lix/libutil/logging.hh" @@ -49,8 +50,9 @@ void restoreMountNamespace() throw SysError("chroot into saved root"); } - if (chdir(savedCwd.c_str()) == -1) + if (sys::chdir(savedCwd) == -1) { throw SysError("restoring cwd"); + } } catch (Error & e) { debug("%1%", Uncolored(e.msg())); } diff --git a/lix/libutil/processes.cc b/lix/libutil/processes.cc index 17483d951..6a833d3ad 100644 --- a/lix/libutil/processes.cc +++ b/lix/libutil/processes.cc @@ -1,4 +1,5 @@ #include "async-io.hh" +#include "c-calls.hh" #include "lix/libutil/current-process.hh" #include "lix/libutil/environment-variables.hh" #include "lix/libutil/finally.hh" @@ -353,8 +354,9 @@ RunningProgram runProgram2(const RunOptions & options) } } - if (options.chdir && chdir((*options.chdir).c_str()) == -1) + if (options.chdir && sys::chdir(*options.chdir) == -1) { throw SysError("chdir failed"); + } #if __linux__ if (!options.caps.empty() && prctl(PR_SET_KEEPCAPS, 1) < 0) { @@ -414,12 +416,12 @@ RunningProgram runProgram2(const RunOptions & options) restoreProcessContext(); - if (options.searchPath) - execvp(options.program.c_str(), stringsToCharPtrs(args_).data()); + if (options.searchPath) { + sys::execvp(options.program, args_); // This allows you to refer to a program with a pathname relative // to the PATH variable. - else - execv(options.program.c_str(), stringsToCharPtrs(args_).data()); + } else + sys::execv(options.program, args_); throw SysError("executing '%1%'", options.program); }, processOptions)}; diff --git a/lix/libutil/strings.cc b/lix/libutil/strings.cc index d34448303..aca971a03 100644 --- a/lix/libutil/strings.cc +++ b/lix/libutil/strings.cc @@ -1,3 +1,4 @@ +#include "lix/libutil/c-calls.hh" #include "lix/libutil/strings.hh" #include "lix/libutil/references.hh" #include @@ -9,7 +10,9 @@ std::vector stringsToCharPtrs(const Strings & ss) { std::vector res; // This is const cast since this exists for OS APIs that want char * - for (auto & s : ss) res.push_back(const_cast(s.data())); + for (auto & s : ss) { + res.push_back(const_cast(requireCString(s).asCStr())); + } res.push_back(0); return res; } diff --git a/lix/libutil/tarfile.cc b/lix/libutil/tarfile.cc index c5d368834..a9fcca682 100644 --- a/lix/libutil/tarfile.cc +++ b/lix/libutil/tarfile.cc @@ -4,6 +4,7 @@ #include "async-io.hh" #include "file-descriptor.hh" +#include "lix/libutil/c-calls.hh" #include "lix/libutil/charptr-cast.hh" #include "lix/libutil/file-system.hh" #include "lix/libutil/logging.hh" @@ -28,6 +29,7 @@ static ssize_t callback_read(struct archive * archive, void * _self, const void } catch (EndOfFile &) { return 0; } catch (std::exception & err) { // NOLINT(lix-foreign-exceptions) + // NOLINTNEXTLINE(lix-unsafe-c-calls): what() is a c string archive_set_error(archive, EIO, "Source threw exception: %s", err.what()); return -1; } @@ -71,7 +73,10 @@ TarArchive::TarArchive(const Path & path) archive_read_support_filter_all(archive); archive_read_support_format_all(archive); archive_read_set_option(archive, nullptr, "mac-ext", nullptr); - check(archive_read_open_filename(archive, path.c_str(), 16384), "failed to open archive: %s"); + check( + archive_read_open_filename(archive, requireCString(path), 16384), + "failed to open archive: %s" + ); } void TarArchive::close() @@ -86,6 +91,8 @@ TarArchive::~TarArchive() static void extract_archive(TarArchive & archive, const Path & destDir) { + requireCString(destDir); + int flags = ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_SECURE_SYMLINKS | ARCHIVE_EXTRACT_SECURE_NODOTDOT; @@ -102,6 +109,7 @@ static void extract_archive(TarArchive & archive, const Path & destDir) else archive.check(r); + // NOLINTNEXTLINE(lix-unsafe-c-calls): destDir is checked, name is a c string archive_entry_copy_pathname(entry, (destDir + "/" + name).c_str()); @@ -112,6 +120,7 @@ static void extract_archive(TarArchive & archive, const Path & destDir) // Patch hardlink path const char *original_hardlink = archive_entry_hardlink(entry); if (original_hardlink) { + // NOLINTNEXTLINE(lix-unsafe-c-calls): destDir is checked, name is a c string archive_entry_copy_hardlink(entry, (destDir + "/" + original_hardlink).c_str()); } diff --git a/lix/libutil/thread-name.cc b/lix/libutil/thread-name.cc index 85a3dcb61..4d43674c7 100644 --- a/lix/libutil/thread-name.cc +++ b/lix/libutil/thread-name.cc @@ -9,10 +9,13 @@ void setCurrentThreadName(const char * name) { // https://stackoverflow.com/questions/2369738/how-to-set-the-name-of-a-thread-in-linux-pthreads/7989973 #if defined(__linux__) + // NOLINTNEXTLINE(lix-unsafe-c-calls) pthread_setname_np(pthread_self(), name); #elif defined(__APPLE__) + // NOLINTNEXTLINE(lix-unsafe-c-calls) pthread_setname_np(name); #elif defined(__FreeBSD__) || defined(__OpenBSD__) + // NOLINTNEXTLINE(lix-unsafe-c-calls) pthread_set_name_np(pthread_self(), name); #endif } diff --git a/lix/libutil/unix-domain-socket.cc b/lix/libutil/unix-domain-socket.cc index 36131a4e4..5f1b38b91 100644 --- a/lix/libutil/unix-domain-socket.cc +++ b/lix/libutil/unix-domain-socket.cc @@ -1,3 +1,4 @@ +#include "c-calls.hh" #include "lix/libutil/file-system.hh" #include "lix/libutil/processes.hh" #include "lix/libutil/unix-domain-socket.hh" @@ -29,7 +30,7 @@ AutoCloseFD createUnixDomainSocket(const Path & path, mode_t mode) bind(fdSocket.get(), path); - chmodPath(path.c_str(), mode); + chmodPath(path, mode); if (listen(fdSocket.get(), 100) == -1) throw SysError("cannot listen on socket '%1%'", path); @@ -69,8 +70,9 @@ static void bindConnectProcHelper( try { pipe.readSide.close(); Path dir = dirOf(path); - if (chdir(dir.c_str()) == -1) + if (sys::chdir(dir) == -1) { throw SysError("chdir to '%s' failed", dir); + } std::string base(baseNameOf(path)); if (base.size() + 1 >= sizeof(addr.sun_path)) throw Error("socket path '%s' is too long", base); @@ -102,7 +104,7 @@ static void bindConnectProcHelper( void bind(int fd, const std::string & path) { - unlink(path.c_str()); + (void) sys::unlink(path); bindConnectProcHelper("bind", ::bind, fd, path); } diff --git a/lix/libutil/users.cc b/lix/libutil/users.cc index af4a9d1ce..bfb674210 100644 --- a/lix/libutil/users.cc +++ b/lix/libutil/users.cc @@ -1,3 +1,4 @@ +#include "c-calls.hh" #include "lix/libutil/environment-variables.hh" #include "lix/libutil/file-system.hh" #include "lix/libutil/logging.hh" @@ -38,7 +39,7 @@ Path getHome() if (homeDir) { // Only use `$HOME` if it exists and is owned by the current user. struct stat st; - int result = stat(homeDir->c_str(), &st); + int result = sys::stat(*homeDir, &st); if (result != 0) { if (errno != ENOENT) { printTaggedWarning( diff --git a/lix/nix/daemon.cc b/lix/nix/daemon.cc index 65db2a8f9..82d120c99 100644 --- a/lix/nix/daemon.cc +++ b/lix/nix/daemon.cc @@ -16,6 +16,7 @@ #include "lix/libutil/result.hh" #include "lix/libutil/serialise.hh" #include "lix/libutil/archive.hh" +#include "lix/libutil/c-calls.hh" #include "lix/libstore/globals.hh" #include "lix/libstore/derivations.hh" #include "lix/libutil/finally.hh" @@ -153,8 +154,11 @@ static bool matchUser(const std::string & user, const std::string & group, const for (auto & i : users) if (i.substr(0, 1) == "@") { - if (group == i.substr(1)) return true; - struct group * gr = getgrnam(i.c_str() + 1); + auto rest = i.substr(1); + if (group == rest) { + return true; + } + struct group * gr = sys::getgrnam(rest); if (!gr) continue; if (matchUser(user, *gr)) return true; } diff --git a/lix/nix/develop.cc b/lix/nix/develop.cc index e670284fd..eb81529b8 100644 --- a/lix/nix/develop.cc +++ b/lix/nix/develop.cc @@ -8,6 +8,7 @@ #include "lix/libstore/derivations.hh" #include "lix/libstore/parsed-derivations.hh" #include "lix/libutil/async.hh" +#include "lix/libutil/c-calls.hh" #include "lix/libutil/json.hh" #include "run.hh" #include "lix/libstore/temporary-dir.hh" @@ -618,7 +619,7 @@ struct CmdDevelop : Common, MixEnvironment setEnviron(); // prevent garbage collection until shell exits - setenv("NIX_GCROOT", gcroot.c_str(), 1); + (void) sys::setenv("NIX_GCROOT", gcroot, 1); Path shell = "bash"; @@ -662,7 +663,7 @@ struct CmdDevelop : Common, MixEnvironment // Override SHELL with the one chosen for this environment. // This is to make sure the system shell doesn't leak into the build environment. - setenv("SHELL", shell.c_str(), 1); + (void) sys::setenv("SHELL", shell, 1); // If running a phase or single command, don't want an interactive shell running after // Ctrl-C, so don't pass --rcfile @@ -676,7 +677,7 @@ struct CmdDevelop : Common, MixEnvironment if (installableFlake) { auto sourcePath = installableFlake->getLockedFlake(*state)->flake.resolvedRef.input.getSourcePath(); if (sourcePath) { - if (chdir(sourcePath->c_str()) == -1) { + if (sys::chdir(*sourcePath) == -1) { throw SysError("chdir to '%s' failed", *sourcePath); } } diff --git a/lix/nix/edit.cc b/lix/nix/edit.cc index cab9b6e38..8eb7fd9ae 100644 --- a/lix/nix/edit.cc +++ b/lix/nix/edit.cc @@ -3,6 +3,7 @@ #include "lix/libexpr/eval.hh" #include "lix/libexpr/attr-path.hh" #include "lix/libcmd/editor-for.hh" +#include "lix/libutil/c-calls.hh" #include "lix/libutil/current-process.hh" #include "edit.hh" @@ -51,7 +52,7 @@ struct CmdEdit : InstallableCommand printMsg(lvlChatty, "running editor: %s", concatMapStringsSep(" ", args, shellEscape)); - execvp(args.front().c_str(), stringsToCharPtrs(args).data()); + sys::execvp(args.front(), args); std::string command; for (const auto &arg : args) command += " '" + arg + "'"; diff --git a/lix/nix/prefetch.cc b/lix/nix/prefetch.cc index bee701652..40a37a40d 100644 --- a/lix/nix/prefetch.cc +++ b/lix/nix/prefetch.cc @@ -5,6 +5,7 @@ #include "lix/libmain/shared.hh" #include "lix/libstore/store-api.hh" #include "lix/libstore/filetransfer.hh" +#include "lix/libutil/c-calls.hh" #include "lix/libutil/tarfile.hh" #include "lix/libexpr/attr-path.hh" #include "lix/libexpr/eval-inline.hh" // IWYU pragma: keep @@ -99,7 +100,7 @@ std::tuple prefetchFile( if (executable) mode = 0700; - AutoCloseFD fd{open(tmpFile.c_str(), O_WRONLY | O_CREAT | O_EXCL, mode)}; + AutoCloseFD fd{sys::open(tmpFile, O_WRONLY | O_CREAT | O_EXCL, mode)}; if (!fd) throw SysError("creating temporary file '%s'", tmpFile); FdSink sink(fd.get()); diff --git a/lix/nix/run.cc b/lix/nix/run.cc index 6ba99a9aa..83c67eec0 100644 --- a/lix/nix/run.cc +++ b/lix/nix/run.cc @@ -6,6 +6,7 @@ #include "lix/libstore/store-api.hh" #include "lix/libstore/derivations.hh" #include "lix/libstore/local-store.hh" +#include "lix/libutil/c-calls.hh" #include "lix/libutil/finally.hh" #include "lix/libstore/fs-accessor.hh" #include "lix/libexpr/eval.hh" @@ -59,7 +60,7 @@ void runProgramInStore(ref store, }; for (auto & arg : args) helperArgs.push_back(arg); - execv(getSelfExe().value_or("nix").c_str(), stringsToCharPtrs(helperArgs).data()); + sys::execv(getSelfExe().value_or("nix"), helperArgs); throw SysError("could not execute chroot helper"); } @@ -68,9 +69,9 @@ void runProgramInStore(ref store, setPersonality(*system); if (useSearchPath == UseSearchPath::Use) - execvp(program.c_str(), stringsToCharPtrs(args).data()); + sys::execvp(program, args); else - execv(program.c_str(), stringsToCharPtrs(args).data()); + sys::execv(program, args); throw SysError("unable to execute '%s'", program); } @@ -147,8 +148,8 @@ struct CmdShell : InstallablesCommand, MixEnvironment auto unixPath = tokenizeString(getEnv("PATH").value_or(""), ":"); unixPath.insert(unixPath.begin(), pathAdditions.begin(), pathAdditions.end()); auto unixPathString = concatStringsSep(":", unixPath); - setenv("PATH", unixPathString.c_str(), 1); - setenv("IN_NIX_SHELL", ignoreEnvironment ? "pure" : "impure", 1); + (void) sys::setenv("PATH", unixPathString, 1); + (void) sys::setenv("IN_NIX_SHELL", ignoreEnvironment ? "pure" : "impure", 1); Strings args; for (auto & arg : command) args.push_back(arg); @@ -262,8 +263,9 @@ void chrootHelper(int argc, char * * argv) createDirs(tmpDir + storeDir); - if (mount(realStoreDir.c_str(), (tmpDir + storeDir).c_str(), "", MS_BIND, 0) == -1) + if (sys::mount(realStoreDir, (tmpDir + storeDir), "", MS_BIND, 0) == -1) { throw SysError("mounting '%s' on '%s'", realStoreDir, storeDir); + } for (auto entry : readDirectory("/")) { auto src = "/" + entry.name; @@ -271,10 +273,12 @@ void chrootHelper(int argc, char * * argv) if (pathExists(dst)) continue; auto st = lstat(src); if (S_ISDIR(st.st_mode)) { - if (mkdir(dst.c_str(), 0700) == -1) + if (sys::mkdir(dst, 0700) == -1) { throw SysError("creating directory '%s'", dst); - if (mount(src.c_str(), dst.c_str(), "", MS_BIND | MS_REC, 0) == -1) + } + if (sys::mount(src, dst, "", MS_BIND | MS_REC, 0) == -1) { throw SysError("mounting '%s' on '%s'", src, dst); + } } else if (S_ISLNK(st.st_mode)) createSymlink(readLink(src), dst); } @@ -283,14 +287,16 @@ void chrootHelper(int argc, char * * argv) if (!cwd) throw SysError("getting current directory"); Finally freeCwd([&]() { free(cwd); }); - if (chroot(tmpDir.c_str()) == -1) + if (sys::chroot(tmpDir) == -1) { throw SysError("chrooting into '%s'", tmpDir); + } - if (chdir(cwd) == -1) + if (sys::chdir(cwd) == -1) { throw SysError("chdir to '%s' in chroot", cwd); - } else - if (mount(realStoreDir.c_str(), storeDir.c_str(), "", MS_BIND, 0) == -1) - throw SysError("mounting '%s' on '%s'", realStoreDir, storeDir); + } + } else if (sys::mount(realStoreDir, storeDir, "", MS_BIND, 0) == -1) { + throw SysError("mounting '%s' on '%s'", realStoreDir, storeDir); + } writeFile("/proc/self/setgroups", "deny"); writeFile("/proc/self/uid_map", fmt("%d %d %d", uid, uid, 1)); @@ -299,7 +305,7 @@ void chrootHelper(int argc, char * * argv) if (system != "") setPersonality(system); - execvp(cmd.c_str(), stringsToCharPtrs(args).data()); + sys::execvp(cmd, args); throw SysError("unable to exec '%s'", cmd); diff --git a/subprojects/lix-clang-tidy/LixClangTidyChecks.cc b/subprojects/lix-clang-tidy/LixClangTidyChecks.cc index a68beef83..3ab205e9f 100644 --- a/subprojects/lix-clang-tidy/LixClangTidyChecks.cc +++ b/subprojects/lix-clang-tidy/LixClangTidyChecks.cc @@ -1,10 +1,11 @@ -#include -#include +#include "CharPtrCast.hh" #include "DisallowedDecls.hh" #include "ForeignExceptions.hh" #include "HasPrefixSuffix.hh" -#include "CharPtrCast.hh" #include "NeverAsync.hh" +#include "UnsafeCCalls.hh" +#include +#include namespace nix::clang_tidy { using namespace clang; @@ -18,6 +19,7 @@ class NixClangTidyChecks : public ClangTidyModule { CheckFactories.registerCheck("lix-never-async"); CheckFactories.registerCheck("lix-disallowed-decls"); CheckFactories.registerCheck("lix-foreign-exceptions"); + CheckFactories.registerCheck("lix-unsafe-c-calls"); } }; diff --git a/subprojects/lix-clang-tidy/UnsafeCCalls.cc b/subprojects/lix-clang-tidy/UnsafeCCalls.cc new file mode 100644 index 000000000..e5b38be75 --- /dev/null +++ b/subprojects/lix-clang-tidy/UnsafeCCalls.cc @@ -0,0 +1,41 @@ +#include "UnsafeCCalls.hh" +#include +#include +#include +#include +#include + +namespace nix::clang_tidy { +using namespace clang::ast_matchers; +using namespace clang; + +void UnsafeCCalls::registerMatchers(ast_matchers::MatchFinder *Finder) { + auto cStringType = pointerType(pointee(isAnyCharacter(), isConstQualified())); + + Finder->addMatcher( + traverse( + clang::TK_IgnoreUnlessSpelledInSource, + callExpr(callee(functionDecl( + hasAnyParameter(hasType(cStringType)), + unless(anyOf(hasName("strlen"), hasName("strdup"), + hasName("strcpy"))), + unless(hasAncestor(namespaceDecl())))), + hasAnyArgument(allOf( + hasType(asString("const char *")), + unless(callExpr(callee(cxxMethodDecl( + hasName("asCStr"), hasParent(cxxRecordDecl(hasName( + "nix::CString"))))))))))) + .bind("call"), + this); +} + +void UnsafeCCalls::check( + const ast_matchers::MatchFinder::MatchResult &Result) { + const auto ReinterpretCastExpr = Result.Nodes.getNodeAs("call"); + auto Diag = + diag(ReinterpretCastExpr->getExprLoc(), + "potentially unsafe call to C function (maybe use a sys::* wrapper instead)"); + Diag << ReinterpretCastExpr->getSourceRange(); +} + +} // namespace nix::clang_tidy diff --git a/subprojects/lix-clang-tidy/UnsafeCCalls.hh b/subprojects/lix-clang-tidy/UnsafeCCalls.hh new file mode 100644 index 000000000..f99810583 --- /dev/null +++ b/subprojects/lix-clang-tidy/UnsafeCCalls.hh @@ -0,0 +1,22 @@ +#pragma once +///@file + +#include +#include +#include +#include + +namespace nix::clang_tidy { + +using namespace clang; +using namespace clang::tidy; + +class UnsafeCCalls : public ClangTidyCheck { +public: + UnsafeCCalls(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; +} // namespace nix::clang_tidy diff --git a/subprojects/lix-clang-tidy/meson.build b/subprojects/lix-clang-tidy/meson.build index 03c878191..091b7aed4 100644 --- a/subprojects/lix-clang-tidy/meson.build +++ b/subprojects/lix-clang-tidy/meson.build @@ -12,6 +12,7 @@ sources = files( 'HasPrefixSuffix.cc', 'LixClangTidyChecks.cc', 'NeverAsync.cc', + 'UnsafeCCalls.cc', ) lix_clang_tidy = shared_module('lix-clang-tidy', sources, diff --git a/tests/functional/repl_characterization/test-session.cc b/tests/functional/repl_characterization/test-session.cc index 72a5fbe3a..1871020b4 100644 --- a/tests/functional/repl_characterization/test-session.cc +++ b/tests/functional/repl_characterization/test-session.cc @@ -3,7 +3,9 @@ #include #include "test-session.hh" +#include "lix/libutil/c-calls.hh" #include "lix/libutil/escape-char.hh" +#include "lix/libutil/file-system.hh" #include "lix/libutil/processes.hh" #include "lix/libutil/strings.hh" @@ -34,7 +36,7 @@ RunningProcess RunningProcess::start(std::string executable, Strings args) if (dup2(STDOUT_FILENO, STDERR_FILENO) == -1) { throw SysError("dupping stderr"); } - execv(executable.c_str(), stringsToCharPtrs(args).data()); + sys::execv(executable, args); throw SysError("exec did not happen"); }); diff --git a/tests/unit/libutil/tests.cc b/tests/unit/libutil/tests.cc index c9ab0e8af..885a4cec7 100644 --- a/tests/unit/libutil/tests.cc +++ b/tests/unit/libutil/tests.cc @@ -1,3 +1,4 @@ +#include "lix/libutil/c-calls.hh" #include "lix/libutil/file-system.hh" #include "lix/libutil/processes.hh" #include "lix/libutil/strings.hh" @@ -232,14 +233,14 @@ namespace nix { Path filePath = getUnitTestDataPath("guess-or-invent/test.txt"); createDirs(dirOf(filePath)); writeFile(filePath, "some text"); - AutoCloseFD file{open(filePath.c_str(), O_RDONLY, 0666)}; + AutoCloseFD file{sys::open(filePath, O_RDONLY, 0666)}; testGuessOrInventPathPrePostDeletion(file, filePath); } TEST(guessOrInventPath, directories) { Path dirPath = getUnitTestDataPath("guess-or-invent/test-dir"); createDirs(dirPath); - AutoCloseFD directory{open(dirPath.c_str(), O_DIRECTORY, 0666)}; + AutoCloseFD directory{sys::open(dirPath, O_DIRECTORY, 0666)}; testGuessOrInventPathPrePostDeletion(directory, dirPath); } @@ -249,15 +250,15 @@ namespace nix { Path targetPath = getUnitTestDataPath("guess-or-invent/nowhere"); createDirs(dirOf(symlinkPath)); createSymlink(targetPath, symlinkPath); - AutoCloseFD symlink{open(symlinkPath.c_str(), O_PATH | O_NOFOLLOW, 0666)}; + AutoCloseFD symlink{sys::open(symlinkPath, O_PATH | O_NOFOLLOW, 0666)}; testGuessOrInventPathPrePostDeletion(symlink, symlinkPath); } TEST(guessOrInventPath, fifos) { Path fifoPath = getUnitTestDataPath("guess-or-invent/fifo"); createDirs(dirOf(fifoPath)); - ASSERT_TRUE(mkfifo(fifoPath.c_str(), 0666) == 0); - AutoCloseFD fifo{open(fifoPath.c_str(), O_PATH | O_NOFOLLOW, 0666)}; + ASSERT_TRUE(mkfifo(fifoPath.c_str(), 0666) == 0); // NOLINT(lix-unsafe-c-calls) + AutoCloseFD fifo{sys::open(fifoPath, O_PATH | O_NOFOLLOW, 0666)}; testGuessOrInventPathPrePostDeletion(fifo, fifoPath); } #endif