mirror of
https://github.com/NixOS/nix
synced 2025-07-05 16:31:47 +02:00
Merge remote-tracking branch 'upstream/master' into support-hardlinks-in-tarballs
This commit is contained in:
commit
86420753ec
583 changed files with 11313 additions and 16547 deletions
1
src/libutil/.version
Symbolic link
1
src/libutil/.version
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../.version
|
|
@ -6,7 +6,7 @@
|
|||
#include <strings.h> // for strcasecmp
|
||||
|
||||
#include "archive.hh"
|
||||
#include "config.hh"
|
||||
#include "config-global.hh"
|
||||
#include "posix-source-accessor.hh"
|
||||
#include "source-path.hh"
|
||||
#include "file-system.hh"
|
||||
|
@ -165,7 +165,7 @@ struct CaseInsensitiveCompare
|
|||
};
|
||||
|
||||
|
||||
static void parse(FileSystemObjectSink & sink, Source & source, const Path & path)
|
||||
static void parse(FileSystemObjectSink & sink, Source & source, const CanonPath & path)
|
||||
{
|
||||
std::string s;
|
||||
|
||||
|
@ -246,7 +246,7 @@ static void parse(FileSystemObjectSink & sink, Source & source, const Path & pat
|
|||
}
|
||||
} else if (s == "node") {
|
||||
if (name.empty()) throw badArchive("entry name missing");
|
||||
parse(sink, source, path + "/" + name);
|
||||
parse(sink, source, path / name);
|
||||
} else
|
||||
throw badArchive("unknown field " + s);
|
||||
}
|
||||
|
@ -290,11 +290,11 @@ void parseDump(FileSystemObjectSink & sink, Source & source)
|
|||
}
|
||||
if (version != narVersionMagic1)
|
||||
throw badArchive("input doesn't look like a Nix archive");
|
||||
parse(sink, source, "");
|
||||
parse(sink, source, CanonPath::root);
|
||||
}
|
||||
|
||||
|
||||
void restorePath(const Path & path, Source & source)
|
||||
void restorePath(const std::filesystem::path & path, Source & source)
|
||||
{
|
||||
RestoreSink sink;
|
||||
sink.dstPath = path;
|
||||
|
|
|
@ -75,7 +75,7 @@ void dumpString(std::string_view s, Sink & sink);
|
|||
|
||||
void parseDump(FileSystemObjectSink & sink, Source & source);
|
||||
|
||||
void restorePath(const Path & path, Source & source);
|
||||
void restorePath(const std::filesystem::path & path, Source & source);
|
||||
|
||||
/**
|
||||
* Read a NAR from 'source' and write it to 'sink'.
|
||||
|
|
1
src/libutil/build-utils-meson
Symbolic link
1
src/libutil/build-utils-meson
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../build-utils-meson
|
67
src/libutil/config-global.cc
Normal file
67
src/libutil/config-global.cc
Normal file
|
@ -0,0 +1,67 @@
|
|||
#include "config-global.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
bool GlobalConfig::set(const std::string & name, const std::string & value)
|
||||
{
|
||||
for (auto & config : *configRegistrations)
|
||||
if (config->set(name, value))
|
||||
return true;
|
||||
|
||||
unknownSettings.emplace(name, value);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void GlobalConfig::getSettings(std::map<std::string, SettingInfo> & res, bool overriddenOnly)
|
||||
{
|
||||
for (auto & config : *configRegistrations)
|
||||
config->getSettings(res, overriddenOnly);
|
||||
}
|
||||
|
||||
void GlobalConfig::resetOverridden()
|
||||
{
|
||||
for (auto & config : *configRegistrations)
|
||||
config->resetOverridden();
|
||||
}
|
||||
|
||||
nlohmann::json GlobalConfig::toJSON()
|
||||
{
|
||||
auto res = nlohmann::json::object();
|
||||
for (const auto & config : *configRegistrations)
|
||||
res.update(config->toJSON());
|
||||
return res;
|
||||
}
|
||||
|
||||
std::string GlobalConfig::toKeyValue()
|
||||
{
|
||||
std::string res;
|
||||
std::map<std::string, Config::SettingInfo> settings;
|
||||
globalConfig.getSettings(settings);
|
||||
for (const auto & s : settings)
|
||||
res += fmt("%s = %s\n", s.first, s.second.value);
|
||||
return res;
|
||||
}
|
||||
|
||||
void GlobalConfig::convertToArgs(Args & args, const std::string & category)
|
||||
{
|
||||
for (auto & config : *configRegistrations)
|
||||
config->convertToArgs(args, category);
|
||||
}
|
||||
|
||||
GlobalConfig globalConfig;
|
||||
|
||||
GlobalConfig::ConfigRegistrations * GlobalConfig::configRegistrations;
|
||||
|
||||
GlobalConfig::Register::Register(Config * config)
|
||||
{
|
||||
if (!configRegistrations)
|
||||
configRegistrations = new ConfigRegistrations;
|
||||
configRegistrations->emplace_back(config);
|
||||
}
|
||||
|
||||
ExperimentalFeatureSettings experimentalFeatureSettings;
|
||||
|
||||
static GlobalConfig::Register rSettings(&experimentalFeatureSettings);
|
||||
|
||||
}
|
33
src/libutil/config-global.hh
Normal file
33
src/libutil/config-global.hh
Normal file
|
@ -0,0 +1,33 @@
|
|||
#pragma once
|
||||
///@file
|
||||
|
||||
#include "config.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
struct GlobalConfig : public AbstractConfig
|
||||
{
|
||||
typedef std::vector<Config *> ConfigRegistrations;
|
||||
static ConfigRegistrations * configRegistrations;
|
||||
|
||||
bool set(const std::string & name, const std::string & value) override;
|
||||
|
||||
void getSettings(std::map<std::string, SettingInfo> & res, bool overriddenOnly = false) override;
|
||||
|
||||
void resetOverridden() override;
|
||||
|
||||
nlohmann::json toJSON() override;
|
||||
|
||||
std::string toKeyValue() override;
|
||||
|
||||
void convertToArgs(Args & args, const std::string & category) override;
|
||||
|
||||
struct Register
|
||||
{
|
||||
Register(Config * config);
|
||||
};
|
||||
};
|
||||
|
||||
extern GlobalConfig globalConfig;
|
||||
|
||||
}
|
|
@ -91,7 +91,14 @@ void Config::getSettings(std::map<std::string, SettingInfo> & res, bool overridd
|
|||
}
|
||||
|
||||
|
||||
static void applyConfigInner(const std::string & contents, const std::string & path, std::vector<std::pair<std::string, std::string>> & parsedContents) {
|
||||
/**
|
||||
* Parse configuration in `contents`, and also the configuration files included from there, with their location specified relative to `path`.
|
||||
*
|
||||
* `contents` and `path` represent the file that is being parsed.
|
||||
* The result is only an intermediate list of key-value pairs of strings.
|
||||
* More parsing according to the settings-specific semantics is being done by `loadConfFile` in `libstore/globals.cc`.
|
||||
*/
|
||||
static void parseConfigFiles(const std::string & contents, const std::string & path, std::vector<std::pair<std::string, std::string>> & parsedContents) {
|
||||
unsigned int pos = 0;
|
||||
|
||||
while (pos < contents.size()) {
|
||||
|
@ -125,7 +132,7 @@ static void applyConfigInner(const std::string & contents, const std::string & p
|
|||
if (pathExists(p)) {
|
||||
try {
|
||||
std::string includedContents = readFile(p);
|
||||
applyConfigInner(includedContents, p, parsedContents);
|
||||
parseConfigFiles(includedContents, p, parsedContents);
|
||||
} catch (SystemError &) {
|
||||
// TODO: Do we actually want to ignore this? Or is it better to fail?
|
||||
}
|
||||
|
@ -153,7 +160,7 @@ static void applyConfigInner(const std::string & contents, const std::string & p
|
|||
void AbstractConfig::applyConfig(const std::string & contents, const std::string & path) {
|
||||
std::vector<std::pair<std::string, std::string>> parsedContents;
|
||||
|
||||
applyConfigInner(contents, path, parsedContents);
|
||||
parseConfigFiles(contents, path, parsedContents);
|
||||
|
||||
// First apply experimental-feature related settings
|
||||
for (const auto & [name, value] : parsedContents)
|
||||
|
@ -443,67 +450,6 @@ void OptionalPathSetting::operator =(const std::optional<Path> & v)
|
|||
this->assign(v);
|
||||
}
|
||||
|
||||
bool GlobalConfig::set(const std::string & name, const std::string & value)
|
||||
{
|
||||
for (auto & config : *configRegistrations)
|
||||
if (config->set(name, value)) return true;
|
||||
|
||||
unknownSettings.emplace(name, value);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void GlobalConfig::getSettings(std::map<std::string, SettingInfo> & res, bool overriddenOnly)
|
||||
{
|
||||
for (auto & config : *configRegistrations)
|
||||
config->getSettings(res, overriddenOnly);
|
||||
}
|
||||
|
||||
void GlobalConfig::resetOverridden()
|
||||
{
|
||||
for (auto & config : *configRegistrations)
|
||||
config->resetOverridden();
|
||||
}
|
||||
|
||||
nlohmann::json GlobalConfig::toJSON()
|
||||
{
|
||||
auto res = nlohmann::json::object();
|
||||
for (const auto & config : *configRegistrations)
|
||||
res.update(config->toJSON());
|
||||
return res;
|
||||
}
|
||||
|
||||
std::string GlobalConfig::toKeyValue()
|
||||
{
|
||||
std::string res;
|
||||
std::map<std::string, Config::SettingInfo> settings;
|
||||
globalConfig.getSettings(settings);
|
||||
for (const auto & s : settings)
|
||||
res += fmt("%s = %s\n", s.first, s.second.value);
|
||||
return res;
|
||||
}
|
||||
|
||||
void GlobalConfig::convertToArgs(Args & args, const std::string & category)
|
||||
{
|
||||
for (auto & config : *configRegistrations)
|
||||
config->convertToArgs(args, category);
|
||||
}
|
||||
|
||||
GlobalConfig globalConfig;
|
||||
|
||||
GlobalConfig::ConfigRegistrations * GlobalConfig::configRegistrations;
|
||||
|
||||
GlobalConfig::Register::Register(Config * config)
|
||||
{
|
||||
if (!configRegistrations)
|
||||
configRegistrations = new ConfigRegistrations;
|
||||
configRegistrations->emplace_back(config);
|
||||
}
|
||||
|
||||
ExperimentalFeatureSettings experimentalFeatureSettings;
|
||||
|
||||
static GlobalConfig::Register rSettings(&experimentalFeatureSettings);
|
||||
|
||||
bool ExperimentalFeatureSettings::isEnabled(const ExperimentalFeature & feature) const
|
||||
{
|
||||
auto & f = experimentalFeatures.get();
|
||||
|
|
|
@ -375,31 +375,6 @@ public:
|
|||
void operator =(const std::optional<Path> & v);
|
||||
};
|
||||
|
||||
struct GlobalConfig : public AbstractConfig
|
||||
{
|
||||
typedef std::vector<Config*> ConfigRegistrations;
|
||||
static ConfigRegistrations * configRegistrations;
|
||||
|
||||
bool set(const std::string & name, const std::string & value) override;
|
||||
|
||||
void getSettings(std::map<std::string, SettingInfo> & res, bool overriddenOnly = false) override;
|
||||
|
||||
void resetOverridden() override;
|
||||
|
||||
nlohmann::json toJSON() override;
|
||||
|
||||
std::string toKeyValue() override;
|
||||
|
||||
void convertToArgs(Args & args, const std::string & category) override;
|
||||
|
||||
struct Register
|
||||
{
|
||||
Register(Config * config);
|
||||
};
|
||||
};
|
||||
|
||||
extern GlobalConfig globalConfig;
|
||||
|
||||
|
||||
struct ExperimentalFeatureSettings : Config {
|
||||
|
||||
|
|
|
@ -155,6 +155,7 @@ public:
|
|||
: err(e)
|
||||
{ }
|
||||
|
||||
/** The error message without "error: " prefixed to it. */
|
||||
std::string message() {
|
||||
return err.msg.str();
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ constexpr std::array<ExperimentalFeatureDetails, numXpFeatures> xpFeatureDetails
|
|||
an impure derivation cannot also be
|
||||
[content-addressed](#xp-feature-ca-derivations).
|
||||
|
||||
This is a more explicit alternative to using [`builtins.currentTime`](@docroot@/language/builtin-constants.md#builtins-currentTime).
|
||||
This is a more explicit alternative to using [`builtins.currentTime`](@docroot@/language/builtins.md#builtins-currentTime).
|
||||
)",
|
||||
.trackingUrl = "https://github.com/NixOS/nix/milestone/42",
|
||||
},
|
||||
|
|
|
@ -10,7 +10,7 @@ static std::optional<FileSerialisationMethod> parseFileSerialisationMethodOpt(st
|
|||
if (input == "flat") {
|
||||
return FileSerialisationMethod::Flat;
|
||||
} else if (input == "nar") {
|
||||
return FileSerialisationMethod::Recursive;
|
||||
return FileSerialisationMethod::NixArchive;
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ std::string_view renderFileSerialisationMethod(FileSerialisationMethod method)
|
|||
switch (method) {
|
||||
case FileSerialisationMethod::Flat:
|
||||
return "flat";
|
||||
case FileSerialisationMethod::Recursive:
|
||||
case FileSerialisationMethod::NixArchive:
|
||||
return "nar";
|
||||
default:
|
||||
assert(false);
|
||||
|
@ -57,7 +57,7 @@ std::string_view renderFileIngestionMethod(FileIngestionMethod method)
|
|||
{
|
||||
switch (method) {
|
||||
case FileIngestionMethod::Flat:
|
||||
case FileIngestionMethod::Recursive:
|
||||
case FileIngestionMethod::NixArchive:
|
||||
return renderFileSerialisationMethod(
|
||||
static_cast<FileSerialisationMethod>(method));
|
||||
case FileIngestionMethod::Git:
|
||||
|
@ -78,7 +78,7 @@ void dumpPath(
|
|||
case FileSerialisationMethod::Flat:
|
||||
path.readFile(sink);
|
||||
break;
|
||||
case FileSerialisationMethod::Recursive:
|
||||
case FileSerialisationMethod::NixArchive:
|
||||
path.dumpPath(sink, filter);
|
||||
break;
|
||||
}
|
||||
|
@ -94,7 +94,7 @@ void restorePath(
|
|||
case FileSerialisationMethod::Flat:
|
||||
writeFile(path, source);
|
||||
break;
|
||||
case FileSerialisationMethod::Recursive:
|
||||
case FileSerialisationMethod::NixArchive:
|
||||
restorePath(path, source);
|
||||
break;
|
||||
}
|
||||
|
@ -119,7 +119,7 @@ std::pair<Hash, std::optional<uint64_t>> hashPath(
|
|||
{
|
||||
switch (method) {
|
||||
case FileIngestionMethod::Flat:
|
||||
case FileIngestionMethod::Recursive: {
|
||||
case FileIngestionMethod::NixArchive: {
|
||||
auto res = hashPath(path, (FileSerialisationMethod) method, ht, filter);
|
||||
return {res.first, {res.second}};
|
||||
}
|
||||
|
|
|
@ -35,14 +35,14 @@ enum struct FileSerialisationMethod : uint8_t {
|
|||
* See `file-system-object/content-address.md#serial-nix-archive` in
|
||||
* the manual.
|
||||
*/
|
||||
Recursive,
|
||||
NixArchive,
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse a `FileSerialisationMethod` by name. Choice of:
|
||||
*
|
||||
* - `flat`: `FileSerialisationMethod::Flat`
|
||||
* - `nar`: `FileSerialisationMethod::Recursive`
|
||||
* - `nar`: `FileSerialisationMethod::NixArchive`
|
||||
*
|
||||
* Opposite of `renderFileSerialisationMethod`.
|
||||
*/
|
||||
|
@ -107,16 +107,18 @@ enum struct FileIngestionMethod : uint8_t {
|
|||
Flat,
|
||||
|
||||
/**
|
||||
* Hash `FileSerialisationMethod::Recursive` serialisation.
|
||||
* Hash `FileSerialisationMethod::NixArchive` serialisation.
|
||||
*
|
||||
* See `file-system-object/content-address.md#serial-flat` in the
|
||||
* manual.
|
||||
*/
|
||||
Recursive,
|
||||
NixArchive,
|
||||
|
||||
/**
|
||||
* Git hashing.
|
||||
*
|
||||
* Part of `ExperimentalFeature::GitHashing`.
|
||||
*
|
||||
* See `file-system-object/content-address.md#serial-git` in the
|
||||
* manual.
|
||||
*/
|
||||
|
@ -127,7 +129,7 @@ enum struct FileIngestionMethod : uint8_t {
|
|||
* Parse a `FileIngestionMethod` by name. Choice of:
|
||||
*
|
||||
* - `flat`: `FileIngestionMethod::Flat`
|
||||
* - `nar`: `FileIngestionMethod::Recursive`
|
||||
* - `nar`: `FileIngestionMethod::NixArchive`
|
||||
* - `git`: `FileIngestionMethod::Git`
|
||||
*
|
||||
* Opposite of `renderFileIngestionMethod`.
|
||||
|
|
|
@ -140,6 +140,7 @@ public:
|
|||
};
|
||||
|
||||
#ifndef _WIN32 // Not needed on Windows, where we don't fork
|
||||
namespace unix {
|
||||
|
||||
/**
|
||||
* Close all file descriptors except those listed in the given set.
|
||||
|
@ -152,13 +153,16 @@ void closeMostFDs(const std::set<Descriptor> & exceptions);
|
|||
*/
|
||||
void closeOnExec(Descriptor fd);
|
||||
|
||||
} // namespace unix
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
# if _WIN32_WINNT >= 0x0600
|
||||
#if defined(_WIN32) && _WIN32_WINNT >= 0x0600
|
||||
namespace windows {
|
||||
|
||||
Path handleToPath(Descriptor handle);
|
||||
std::wstring handleToFileName(Descriptor handle);
|
||||
# endif
|
||||
|
||||
} // namespace windows
|
||||
#endif
|
||||
|
||||
MakeError(EndOfFile, Error);
|
||||
|
|
|
@ -412,31 +412,19 @@ void deletePath(const fs::path & path)
|
|||
deletePath(path, dummy);
|
||||
}
|
||||
|
||||
|
||||
Paths createDirs(const Path & path)
|
||||
void createDir(const Path & path, mode_t mode)
|
||||
{
|
||||
Paths created;
|
||||
if (path == "/") return created;
|
||||
if (mkdir(path.c_str(), mode) == -1)
|
||||
throw SysError("creating directory '%1%'", path);
|
||||
}
|
||||
|
||||
struct stat st;
|
||||
if (STAT(path.c_str(), &st) == -1) {
|
||||
created = createDirs(dirOf(path));
|
||||
if (mkdir(path.c_str()
|
||||
#ifndef _WIN32 // TODO abstract mkdir perms for Windows
|
||||
, 0777
|
||||
#endif
|
||||
) == -1 && errno != EEXIST)
|
||||
throw SysError("creating directory '%1%'", path);
|
||||
st = STAT(path);
|
||||
created.push_back(path);
|
||||
void createDirs(const Path & path)
|
||||
{
|
||||
try {
|
||||
fs::create_directories(path);
|
||||
} catch (fs::filesystem_error & e) {
|
||||
throw SysError("creating directory '%1%'", path);
|
||||
}
|
||||
|
||||
if (S_ISLNK(st.st_mode) && stat(path.c_str(), &st) == -1)
|
||||
throw SysError("statting symlink '%1%'", path);
|
||||
|
||||
if (!S_ISDIR(st.st_mode)) throw Error("'%1%' is not a directory", path);
|
||||
|
||||
return created;
|
||||
}
|
||||
|
||||
|
||||
|
@ -546,7 +534,7 @@ std::pair<AutoCloseFD, Path> createTempFile(const Path & prefix)
|
|||
if (!fd)
|
||||
throw SysError("creating temporary file '%s'", tmpl);
|
||||
#ifndef _WIN32
|
||||
closeOnExec(fd.get());
|
||||
unix::closeOnExec(fd.get());
|
||||
#endif
|
||||
return {std::move(fd), tmpl};
|
||||
}
|
||||
|
@ -574,29 +562,69 @@ void replaceSymlink(const Path & target, const Path & link)
|
|||
}
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
static void setWriteTime(const fs::path & p, const struct stat & st)
|
||||
void setWriteTime(
|
||||
const std::filesystem::path & path,
|
||||
time_t accessedTime,
|
||||
time_t modificationTime,
|
||||
std::optional<bool> optIsSymlink)
|
||||
{
|
||||
struct timeval times[2];
|
||||
times[0] = {
|
||||
.tv_sec = st.st_atime,
|
||||
.tv_usec = 0,
|
||||
#ifndef _WIN32
|
||||
struct timeval times[2] = {
|
||||
{
|
||||
.tv_sec = accessedTime,
|
||||
.tv_usec = 0,
|
||||
},
|
||||
{
|
||||
.tv_sec = modificationTime,
|
||||
.tv_usec = 0,
|
||||
},
|
||||
};
|
||||
times[1] = {
|
||||
.tv_sec = st.st_mtime,
|
||||
.tv_usec = 0,
|
||||
};
|
||||
if (lutimes(p.c_str(), times) != 0)
|
||||
throw SysError("changing modification time of '%s'", p);
|
||||
}
|
||||
#endif
|
||||
|
||||
auto nonSymlink = [&]{
|
||||
bool isSymlink = optIsSymlink
|
||||
? *optIsSymlink
|
||||
: fs::is_symlink(path);
|
||||
|
||||
if (!isSymlink) {
|
||||
#ifdef _WIN32
|
||||
// FIXME use `fs::last_write_time`.
|
||||
//
|
||||
// Would be nice to use std::filesystem unconditionally, but
|
||||
// doesn't support access time just modification time.
|
||||
//
|
||||
// System clock vs File clock issues also make that annoying.
|
||||
warn("Changing file times is not yet implemented on Windows, path is '%s'", path);
|
||||
#else
|
||||
if (utimes(path.c_str(), times) == -1) {
|
||||
|
||||
throw SysError("changing modification time of '%s' (not a symlink)", path);
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
throw Error("Cannot modification time of symlink '%s'", path);
|
||||
}
|
||||
};
|
||||
|
||||
#if HAVE_LUTIMES
|
||||
if (lutimes(path.c_str(), times) == -1) {
|
||||
if (errno == ENOSYS)
|
||||
nonSymlink();
|
||||
else
|
||||
throw SysError("changing modification time of '%s'", path);
|
||||
}
|
||||
#else
|
||||
nonSymlink();
|
||||
#endif
|
||||
}
|
||||
|
||||
void setWriteTime(const fs::path & path, const struct stat & st)
|
||||
{
|
||||
setWriteTime(path, st.st_atime, st.st_mtime, S_ISLNK(st.st_mode));
|
||||
}
|
||||
|
||||
void copyFile(const fs::path & from, const fs::path & to, bool andDelete)
|
||||
{
|
||||
#ifndef _WIN32
|
||||
// TODO: Rewrite the `is_*` to use `symlink_status()`
|
||||
auto statOfFrom = lstat(from.c_str());
|
||||
#endif
|
||||
auto fromStatus = fs::symlink_status(from);
|
||||
|
||||
// Mark the directory as writable so that we can delete its children
|
||||
|
@ -616,9 +644,7 @@ void copyFile(const fs::path & from, const fs::path & to, bool andDelete)
|
|||
throw Error("file '%s' has an unsupported type", from);
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
setWriteTime(to, statOfFrom);
|
||||
#endif
|
||||
setWriteTime(to, lstat(from.string().c_str()));
|
||||
if (andDelete) {
|
||||
if (!fs::is_symlink(fromStatus))
|
||||
fs::permissions(from, fs::perms::owner_write, fs::perm_options::add | fs::perm_options::nofollow);
|
||||
|
|
|
@ -148,15 +148,43 @@ void deletePath(const std::filesystem::path & path);
|
|||
void deletePath(const std::filesystem::path & path, uint64_t & bytesFreed);
|
||||
|
||||
/**
|
||||
* Create a directory and all its parents, if necessary. Returns the
|
||||
* list of created directories, in order of creation.
|
||||
* Create a directory and all its parents, if necessary.
|
||||
*/
|
||||
Paths createDirs(const Path & path);
|
||||
inline Paths createDirs(PathView path)
|
||||
void createDirs(const Path & path);
|
||||
inline void createDirs(PathView path)
|
||||
{
|
||||
return createDirs(Path(path));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a single directory.
|
||||
*/
|
||||
void createDir(const Path & path, mode_t mode = 0755);
|
||||
|
||||
/**
|
||||
* Set the access and modification times of the given path, not
|
||||
* following symlinks.
|
||||
*
|
||||
* @param accessTime Specified in seconds.
|
||||
*
|
||||
* @param modificationTime Specified in seconds.
|
||||
*
|
||||
* @param isSymlink Whether the file in question is a symlink. Used for
|
||||
* fallback code where we don't have `lutimes` or similar. if
|
||||
* `std::optional` is passed, the information will be recomputed if it
|
||||
* is needed. Race conditions are possible so be careful!
|
||||
*/
|
||||
void setWriteTime(
|
||||
const std::filesystem::path & path,
|
||||
time_t accessedTime,
|
||||
time_t modificationTime,
|
||||
std::optional<bool> isSymlink = std::nullopt);
|
||||
|
||||
/**
|
||||
* Convenience wrapper that takes all arguments from the `struct stat`.
|
||||
*/
|
||||
void setWriteTime(const std::filesystem::path & path, const struct stat & st);
|
||||
|
||||
/**
|
||||
* Create a symlink.
|
||||
*/
|
||||
|
|
|
@ -111,6 +111,8 @@ std::ostream & operator<<(std::ostream & out, const Magenta<T> & y)
|
|||
/**
|
||||
* Values wrapped in this class are printed without coloring.
|
||||
*
|
||||
* Specifically, the color is reset to normal before printing the value.
|
||||
*
|
||||
* By default, arguments to `HintFmt` are printed in magenta (see `Magenta`).
|
||||
*/
|
||||
template <class T>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#include <fcntl.h>
|
||||
|
||||
#include "error.hh"
|
||||
#include "config.hh"
|
||||
#include "config-global.hh"
|
||||
#include "fs-sink.hh"
|
||||
|
||||
#if _WIN32
|
||||
|
@ -14,7 +14,7 @@ namespace nix {
|
|||
|
||||
void copyRecursive(
|
||||
SourceAccessor & accessor, const CanonPath & from,
|
||||
FileSystemObjectSink & sink, const Path & to)
|
||||
FileSystemObjectSink & sink, const CanonPath & to)
|
||||
{
|
||||
auto stat = accessor.lstat(from);
|
||||
|
||||
|
@ -43,7 +43,7 @@ void copyRecursive(
|
|||
for (auto & [name, _] : accessor.readDirectory(from)) {
|
||||
copyRecursive(
|
||||
accessor, from / name,
|
||||
sink, to + "/" + name);
|
||||
sink, to / name);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
@ -69,17 +69,9 @@ static RestoreSinkSettings restoreSinkSettings;
|
|||
static GlobalConfig::Register r1(&restoreSinkSettings);
|
||||
|
||||
|
||||
void RestoreSink::createDirectory(const Path & path)
|
||||
void RestoreSink::createDirectory(const CanonPath & path)
|
||||
{
|
||||
Path p = dstPath + path;
|
||||
if (
|
||||
#ifndef _WIN32 // TODO abstract mkdir perms for Windows
|
||||
mkdir(p.c_str(), 0777) == -1
|
||||
#else
|
||||
!CreateDirectoryW(pathNG(p).c_str(), NULL)
|
||||
#endif
|
||||
)
|
||||
throw NativeSysError("creating directory '%1%'", p);
|
||||
std::filesystem::create_directory(dstPath / path.rel());
|
||||
};
|
||||
|
||||
struct RestoreRegularFile : CreateRegularFileSink {
|
||||
|
@ -90,13 +82,22 @@ struct RestoreRegularFile : CreateRegularFileSink {
|
|||
void preallocateContents(uint64_t size) override;
|
||||
};
|
||||
|
||||
void RestoreSink::createRegularFile(const Path & path, std::function<void(CreateRegularFileSink &)> func)
|
||||
static std::filesystem::path append(const std::filesystem::path & src, const CanonPath & path)
|
||||
{
|
||||
Path p = dstPath + path;
|
||||
auto dst = src;
|
||||
if (!path.rel().empty())
|
||||
dst /= path.rel();
|
||||
return dst;
|
||||
}
|
||||
|
||||
void RestoreSink::createRegularFile(const CanonPath & path, std::function<void(CreateRegularFileSink &)> func)
|
||||
{
|
||||
auto p = append(dstPath, path);
|
||||
|
||||
RestoreRegularFile crf;
|
||||
crf.fd =
|
||||
#ifdef _WIN32
|
||||
CreateFileW(pathNG(path).c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)
|
||||
CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)
|
||||
#else
|
||||
open(p.c_str(), O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0666)
|
||||
#endif
|
||||
|
@ -141,14 +142,14 @@ void RestoreRegularFile::operator () (std::string_view data)
|
|||
writeFull(fd.get(), data);
|
||||
}
|
||||
|
||||
void RestoreSink::createSymlink(const Path & path, const std::string & target)
|
||||
void RestoreSink::createSymlink(const CanonPath & path, const std::string & target)
|
||||
{
|
||||
Path p = dstPath + path;
|
||||
auto p = append(dstPath, path);
|
||||
nix::createSymlink(target, p);
|
||||
}
|
||||
|
||||
|
||||
void RegularFileSink::createRegularFile(const Path & path, std::function<void(CreateRegularFileSink &)> func)
|
||||
void RegularFileSink::createRegularFile(const CanonPath & path, std::function<void(CreateRegularFileSink &)> func)
|
||||
{
|
||||
struct CRF : CreateRegularFileSink {
|
||||
RegularFileSink & back;
|
||||
|
@ -163,7 +164,7 @@ void RegularFileSink::createRegularFile(const Path & path, std::function<void(Cr
|
|||
}
|
||||
|
||||
|
||||
void NullFileSystemObjectSink::createRegularFile(const Path & path, std::function<void(CreateRegularFileSink &)> func)
|
||||
void NullFileSystemObjectSink::createRegularFile(const CanonPath & path, std::function<void(CreateRegularFileSink &)> func)
|
||||
{
|
||||
struct : CreateRegularFileSink {
|
||||
void operator () (std::string_view data) override {}
|
||||
|
|
|
@ -28,17 +28,17 @@ struct FileSystemObjectSink
|
|||
{
|
||||
virtual ~FileSystemObjectSink() = default;
|
||||
|
||||
virtual void createDirectory(const Path & path) = 0;
|
||||
virtual void createDirectory(const CanonPath & path) = 0;
|
||||
|
||||
/**
|
||||
* This function in general is no re-entrant. Only one file can be
|
||||
* written at a time.
|
||||
*/
|
||||
virtual void createRegularFile(
|
||||
const Path & path,
|
||||
const CanonPath & path,
|
||||
std::function<void(CreateRegularFileSink &)>) = 0;
|
||||
|
||||
virtual void createSymlink(const Path & path, const std::string & target) = 0;
|
||||
virtual void createSymlink(const CanonPath & path, const std::string & target) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -59,17 +59,17 @@ struct ExtendedFileSystemObjectSink : virtual FileSystemObjectSink
|
|||
*/
|
||||
void copyRecursive(
|
||||
SourceAccessor & accessor, const CanonPath & sourcePath,
|
||||
FileSystemObjectSink & sink, const Path & destPath);
|
||||
FileSystemObjectSink & sink, const CanonPath & destPath);
|
||||
|
||||
/**
|
||||
* Ignore everything and do nothing
|
||||
*/
|
||||
struct NullFileSystemObjectSink : FileSystemObjectSink
|
||||
{
|
||||
void createDirectory(const Path & path) override { }
|
||||
void createSymlink(const Path & path, const std::string & target) override { }
|
||||
void createDirectory(const CanonPath & path) override { }
|
||||
void createSymlink(const CanonPath & path, const std::string & target) override { }
|
||||
void createRegularFile(
|
||||
const Path & path,
|
||||
const CanonPath & path,
|
||||
std::function<void(CreateRegularFileSink &)>) override;
|
||||
};
|
||||
|
||||
|
@ -78,15 +78,15 @@ struct NullFileSystemObjectSink : FileSystemObjectSink
|
|||
*/
|
||||
struct RestoreSink : FileSystemObjectSink
|
||||
{
|
||||
Path dstPath;
|
||||
std::filesystem::path dstPath;
|
||||
|
||||
void createDirectory(const Path & path) override;
|
||||
void createDirectory(const CanonPath & path) override;
|
||||
|
||||
void createRegularFile(
|
||||
const Path & path,
|
||||
const CanonPath & path,
|
||||
std::function<void(CreateRegularFileSink &)>) override;
|
||||
|
||||
void createSymlink(const Path & path, const std::string & target) override;
|
||||
void createSymlink(const CanonPath & path, const std::string & target) override;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -101,18 +101,18 @@ struct RegularFileSink : FileSystemObjectSink
|
|||
|
||||
RegularFileSink(Sink & sink) : sink(sink) { }
|
||||
|
||||
void createDirectory(const Path & path) override
|
||||
void createDirectory(const CanonPath & path) override
|
||||
{
|
||||
regular = false;
|
||||
}
|
||||
|
||||
void createSymlink(const Path & path, const std::string & target) override
|
||||
void createSymlink(const CanonPath & path, const std::string & target) override
|
||||
{
|
||||
regular = false;
|
||||
}
|
||||
|
||||
void createRegularFile(
|
||||
const Path & path,
|
||||
const CanonPath & path,
|
||||
std::function<void(CreateRegularFileSink &)>) override;
|
||||
};
|
||||
|
||||
|
|
|
@ -53,7 +53,7 @@ static std::string getString(Source & source, int n)
|
|||
|
||||
void parseBlob(
|
||||
FileSystemObjectSink & sink,
|
||||
const Path & sinkPath,
|
||||
const CanonPath & sinkPath,
|
||||
Source & source,
|
||||
BlobMode blobMode,
|
||||
const ExperimentalFeatureSettings & xpSettings)
|
||||
|
@ -116,7 +116,7 @@ void parseBlob(
|
|||
|
||||
void parseTree(
|
||||
FileSystemObjectSink & sink,
|
||||
const Path & sinkPath,
|
||||
const CanonPath & sinkPath,
|
||||
Source & source,
|
||||
std::function<SinkHook> hook,
|
||||
const ExperimentalFeatureSettings & xpSettings)
|
||||
|
@ -147,7 +147,7 @@ void parseTree(
|
|||
Hash hash(HashAlgorithm::SHA1);
|
||||
std::copy(hashs.begin(), hashs.end(), hash.hash);
|
||||
|
||||
hook(name, TreeEntry {
|
||||
hook(CanonPath{name}, TreeEntry {
|
||||
.mode = mode,
|
||||
.hash = hash,
|
||||
});
|
||||
|
@ -171,7 +171,7 @@ ObjectType parseObjectType(
|
|||
|
||||
void parse(
|
||||
FileSystemObjectSink & sink,
|
||||
const Path & sinkPath,
|
||||
const CanonPath & sinkPath,
|
||||
Source & source,
|
||||
BlobMode rootModeIfBlob,
|
||||
std::function<SinkHook> hook,
|
||||
|
@ -208,7 +208,7 @@ std::optional<Mode> convertMode(SourceAccessor::Type type)
|
|||
|
||||
void restore(FileSystemObjectSink & sink, Source & source, std::function<RestoreHook> hook)
|
||||
{
|
||||
parse(sink, "", source, BlobMode::Regular, [&](Path name, TreeEntry entry) {
|
||||
parse(sink, CanonPath::root, source, BlobMode::Regular, [&](CanonPath name, TreeEntry entry) {
|
||||
auto [accessor, from] = hook(entry.hash);
|
||||
auto stat = accessor->lstat(from);
|
||||
auto gotOpt = convertMode(stat.type);
|
||||
|
|
|
@ -64,7 +64,7 @@ using Tree = std::map<std::string, TreeEntry>;
|
|||
* Implementations may seek to memoize resources (bandwidth, storage,
|
||||
* etc.) for the same Git hash.
|
||||
*/
|
||||
using SinkHook = void(const Path & name, TreeEntry entry);
|
||||
using SinkHook = void(const CanonPath & name, TreeEntry entry);
|
||||
|
||||
/**
|
||||
* Parse the "blob " or "tree " prefix.
|
||||
|
@ -89,13 +89,13 @@ enum struct BlobMode : RawMode
|
|||
};
|
||||
|
||||
void parseBlob(
|
||||
FileSystemObjectSink & sink, const Path & sinkPath,
|
||||
FileSystemObjectSink & sink, const CanonPath & sinkPath,
|
||||
Source & source,
|
||||
BlobMode blobMode,
|
||||
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
|
||||
|
||||
void parseTree(
|
||||
FileSystemObjectSink & sink, const Path & sinkPath,
|
||||
FileSystemObjectSink & sink, const CanonPath & sinkPath,
|
||||
Source & source,
|
||||
std::function<SinkHook> hook,
|
||||
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
|
||||
|
@ -108,7 +108,7 @@ void parseTree(
|
|||
* a blob, this is ignored.
|
||||
*/
|
||||
void parse(
|
||||
FileSystemObjectSink & sink, const Path & sinkPath,
|
||||
FileSystemObjectSink & sink, const CanonPath & sinkPath,
|
||||
Source & source,
|
||||
BlobMode rootModeIfBlob,
|
||||
std::function<SinkHook> hook,
|
||||
|
|
|
@ -52,11 +52,11 @@ bool Hash::operator == (const Hash & h2) const
|
|||
|
||||
std::strong_ordering Hash::operator <=> (const Hash & h) const
|
||||
{
|
||||
if (auto cmp = algo <=> h.algo; cmp != 0) return cmp;
|
||||
if (auto cmp = hashSize <=> h.hashSize; cmp != 0) return cmp;
|
||||
for (unsigned int i = 0; i < hashSize; i++) {
|
||||
if (auto cmp = hash[i] <=> h.hash[i]; cmp != 0) return cmp;
|
||||
}
|
||||
if (auto cmp = algo <=> h.algo; cmp != 0) return cmp;
|
||||
return std::strong_ordering::equivalent;
|
||||
}
|
||||
|
||||
|
|
11
src/libutil/linux/meson.build
Normal file
11
src/libutil/linux/meson.build
Normal file
|
@ -0,0 +1,11 @@
|
|||
sources += files(
|
||||
'cgroup.cc',
|
||||
'namespaces.cc',
|
||||
)
|
||||
|
||||
include_dirs += include_directories('.')
|
||||
|
||||
headers += files(
|
||||
'cgroup.hh',
|
||||
'namespaces.hh',
|
||||
)
|
|
@ -3,7 +3,7 @@
|
|||
#include "environment-variables.hh"
|
||||
#include "terminal.hh"
|
||||
#include "util.hh"
|
||||
#include "config.hh"
|
||||
#include "config-global.hh"
|
||||
#include "source-path.hh"
|
||||
#include "position.hh"
|
||||
|
||||
|
|
|
@ -124,9 +124,9 @@ SourcePath MemorySourceAccessor::addFile(CanonPath path, std::string && contents
|
|||
|
||||
using File = MemorySourceAccessor::File;
|
||||
|
||||
void MemorySink::createDirectory(const Path & path)
|
||||
void MemorySink::createDirectory(const CanonPath & path)
|
||||
{
|
||||
auto * f = dst.open(CanonPath{path}, File { File::Directory { } });
|
||||
auto * f = dst.open(path, File { File::Directory { } });
|
||||
if (!f)
|
||||
throw Error("file '%s' cannot be made because some parent file is not a directory", path);
|
||||
|
||||
|
@ -146,9 +146,9 @@ struct CreateMemoryRegularFile : CreateRegularFileSink {
|
|||
void preallocateContents(uint64_t size) override;
|
||||
};
|
||||
|
||||
void MemorySink::createRegularFile(const Path & path, std::function<void(CreateRegularFileSink &)> func)
|
||||
void MemorySink::createRegularFile(const CanonPath & path, std::function<void(CreateRegularFileSink &)> func)
|
||||
{
|
||||
auto * f = dst.open(CanonPath{path}, File { File::Regular {} });
|
||||
auto * f = dst.open(path, File { File::Regular {} });
|
||||
if (!f)
|
||||
throw Error("file '%s' cannot be made because some parent file is not a directory", path);
|
||||
if (auto * rp = std::get_if<File::Regular>(&f->raw)) {
|
||||
|
@ -173,9 +173,9 @@ void CreateMemoryRegularFile::operator () (std::string_view data)
|
|||
regularFile.contents += data;
|
||||
}
|
||||
|
||||
void MemorySink::createSymlink(const Path & path, const std::string & target)
|
||||
void MemorySink::createSymlink(const CanonPath & path, const std::string & target)
|
||||
{
|
||||
auto * f = dst.open(CanonPath{path}, File { File::Symlink { } });
|
||||
auto * f = dst.open(path, File { File::Symlink { } });
|
||||
if (!f)
|
||||
throw Error("file '%s' cannot be made because some parent file is not a directory", path);
|
||||
if (auto * s = std::get_if<File::Symlink>(&f->raw))
|
||||
|
|
|
@ -81,13 +81,13 @@ struct MemorySink : FileSystemObjectSink
|
|||
|
||||
MemorySink(MemorySourceAccessor & dst) : dst(dst) { }
|
||||
|
||||
void createDirectory(const Path & path) override;
|
||||
void createDirectory(const CanonPath & path) override;
|
||||
|
||||
void createRegularFile(
|
||||
const Path & path,
|
||||
const CanonPath & path,
|
||||
std::function<void(CreateRegularFileSink &)>) override;
|
||||
|
||||
void createSymlink(const Path & path, const std::string & target) override;
|
||||
void createSymlink(const CanonPath & path, const std::string & target) override;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
265
src/libutil/meson.build
Normal file
265
src/libutil/meson.build
Normal file
|
@ -0,0 +1,265 @@
|
|||
project('nix-util', 'cpp',
|
||||
version : files('.version'),
|
||||
default_options : [
|
||||
'cpp_std=c++2a',
|
||||
# TODO(Qyriad): increase the warning level
|
||||
'warning_level=1',
|
||||
'debug=true',
|
||||
'optimization=2',
|
||||
'errorlogs=true', # Please print logs for tests that fail
|
||||
],
|
||||
meson_version : '>= 1.1',
|
||||
license : 'LGPL-2.1-or-later',
|
||||
)
|
||||
|
||||
cxx = meson.get_compiler('cpp')
|
||||
|
||||
subdir('build-utils-meson/deps-lists')
|
||||
|
||||
configdata = configuration_data()
|
||||
|
||||
deps_private_maybe_subproject = [
|
||||
]
|
||||
deps_public_maybe_subproject = [
|
||||
]
|
||||
subdir('build-utils-meson/subprojects')
|
||||
|
||||
# Check for each of these functions, and create a define like `#define
|
||||
# HAVE_LUTIMES 1`. The `#define` is unconditional, 0 for not found and 1
|
||||
# for found. One therefore uses it with `#if` not `#ifdef`.
|
||||
check_funcs = [
|
||||
# Optionally used for changing the mtime of symlinks.
|
||||
'lutimes',
|
||||
# Optionally used for creating pipes on Unix
|
||||
'pipe2',
|
||||
# Optionally used to preallocate files to be large enough before
|
||||
# writing to them.
|
||||
'posix_fallocate',
|
||||
# Optionally used to get more information about processes failing due
|
||||
# to a signal on Unix.
|
||||
'strsignal',
|
||||
# Optionally used to try to close more file descriptors (e.g. before
|
||||
# forking) on Unix.
|
||||
'sysconf',
|
||||
]
|
||||
foreach funcspec : check_funcs
|
||||
define_name = 'HAVE_' + funcspec.underscorify().to_upper()
|
||||
define_value = cxx.has_function(funcspec).to_int()
|
||||
configdata.set(define_name, define_value)
|
||||
endforeach
|
||||
|
||||
subdir('build-utils-meson/threads')
|
||||
|
||||
if host_machine.system() == 'windows'
|
||||
socket = cxx.find_library('ws2_32')
|
||||
deps_other += socket
|
||||
elif host_machine.system() == 'sunos'
|
||||
socket = cxx.find_library('socket')
|
||||
network_service_library = cxx.find_library('nsl')
|
||||
deps_other += [socket, network_service_library]
|
||||
endif
|
||||
|
||||
boost = dependency(
|
||||
'boost',
|
||||
modules : ['context', 'coroutine'],
|
||||
)
|
||||
# boost is a public dependency, but not a pkg-config dependency unfortunately, so we
|
||||
# put in `deps_other`.
|
||||
deps_other += boost
|
||||
|
||||
openssl = dependency(
|
||||
'libcrypto',
|
||||
'openssl',
|
||||
version : '>= 1.1.1',
|
||||
)
|
||||
deps_private += openssl
|
||||
|
||||
libarchive = dependency('libarchive', version : '>= 3.1.2')
|
||||
deps_public += libarchive
|
||||
if get_option('default_library') == 'static'
|
||||
# Workaround until https://github.com/libarchive/libarchive/issues/1446 is fixed
|
||||
add_project_arguments('-lz', language : 'cpp')
|
||||
endif
|
||||
|
||||
sodium = dependency('libsodium', 'sodium')
|
||||
deps_private += sodium
|
||||
|
||||
brotli = [
|
||||
dependency('libbrotlicommon'),
|
||||
dependency('libbrotlidec'),
|
||||
dependency('libbrotlienc'),
|
||||
]
|
||||
deps_private += brotli
|
||||
|
||||
cpuid_required = get_option('cpuid')
|
||||
if host_machine.cpu_family() != 'x86_64' and cpuid_required.enabled()
|
||||
warning('Force-enabling seccomp on non-x86_64 does not make sense')
|
||||
endif
|
||||
cpuid = dependency('libcpuid', 'cpuid', required : cpuid_required)
|
||||
configdata.set('HAVE_LIBCPUID', cpuid.found().to_int())
|
||||
deps_private += cpuid
|
||||
|
||||
nlohmann_json = dependency('nlohmann_json', version : '>= 3.9')
|
||||
deps_public += nlohmann_json
|
||||
|
||||
config_h = configure_file(
|
||||
configuration : configdata,
|
||||
output : 'config-util.hh',
|
||||
)
|
||||
|
||||
add_project_arguments(
|
||||
# TODO(Qyriad): Yes this is how the autoconf+Make system did it.
|
||||
# It would be nice for our headers to be idempotent instead.
|
||||
'-include', 'config-util.hh',
|
||||
language : 'cpp',
|
||||
)
|
||||
|
||||
subdir('build-utils-meson/diagnostics')
|
||||
|
||||
sources = files(
|
||||
'archive.cc',
|
||||
'args.cc',
|
||||
'canon-path.cc',
|
||||
'compression.cc',
|
||||
'compute-levels.cc',
|
||||
'config.cc',
|
||||
'config-global.cc',
|
||||
'current-process.cc',
|
||||
'english.cc',
|
||||
'environment-variables.cc',
|
||||
'error.cc',
|
||||
'exit.cc',
|
||||
'experimental-features.cc',
|
||||
'file-content-address.cc',
|
||||
'file-descriptor.cc',
|
||||
'file-system.cc',
|
||||
'fs-sink.cc',
|
||||
'git.cc',
|
||||
'hash.cc',
|
||||
'hilite.cc',
|
||||
'json-utils.cc',
|
||||
'logging.cc',
|
||||
'memory-source-accessor.cc',
|
||||
'position.cc',
|
||||
'posix-source-accessor.cc',
|
||||
'references.cc',
|
||||
'serialise.cc',
|
||||
'signature/local-keys.cc',
|
||||
'signature/signer.cc',
|
||||
'source-accessor.cc',
|
||||
'source-path.cc',
|
||||
'suggestions.cc',
|
||||
'tarfile.cc',
|
||||
'terminal.cc',
|
||||
'thread-pool.cc',
|
||||
'unix-domain-socket.cc',
|
||||
'url.cc',
|
||||
'users.cc',
|
||||
'util.cc',
|
||||
'xml-writer.cc',
|
||||
)
|
||||
|
||||
include_dirs = [include_directories('.')]
|
||||
|
||||
headers = [config_h] + files(
|
||||
'abstract-setting-to-json.hh',
|
||||
'ansicolor.hh',
|
||||
'archive.hh',
|
||||
'args.hh',
|
||||
'args/root.hh',
|
||||
'callback.hh',
|
||||
'canon-path.hh',
|
||||
'chunked-vector.hh',
|
||||
'closure.hh',
|
||||
'comparator.hh',
|
||||
'compression.hh',
|
||||
'compute-levels.hh',
|
||||
'config-global.hh',
|
||||
'config-impl.hh',
|
||||
'config.hh',
|
||||
'current-process.hh',
|
||||
'english.hh',
|
||||
'environment-variables.hh',
|
||||
'error.hh',
|
||||
'exit.hh',
|
||||
'experimental-features.hh',
|
||||
'file-content-address.hh',
|
||||
'file-descriptor.hh',
|
||||
'file-path-impl.hh',
|
||||
'file-path.hh',
|
||||
'file-system.hh',
|
||||
'finally.hh',
|
||||
'fmt.hh',
|
||||
'fs-sink.hh',
|
||||
'git.hh',
|
||||
'hash.hh',
|
||||
'hilite.hh',
|
||||
'json-impls.hh',
|
||||
'json-utils.hh',
|
||||
'logging.hh',
|
||||
'lru-cache.hh',
|
||||
'memory-source-accessor.hh',
|
||||
'muxable-pipe.hh',
|
||||
'pool.hh',
|
||||
'position.hh',
|
||||
'posix-source-accessor.hh',
|
||||
'processes.hh',
|
||||
'ref.hh',
|
||||
'references.hh',
|
||||
'regex-combinators.hh',
|
||||
'repair-flag.hh',
|
||||
'serialise.hh',
|
||||
'signals.hh',
|
||||
'signature/local-keys.hh',
|
||||
'signature/signer.hh',
|
||||
'source-accessor.hh',
|
||||
'source-path.hh',
|
||||
'split.hh',
|
||||
'suggestions.hh',
|
||||
'sync.hh',
|
||||
'tarfile.hh',
|
||||
'terminal.hh',
|
||||
'thread-pool.hh',
|
||||
'topo-sort.hh',
|
||||
'types.hh',
|
||||
'unix-domain-socket.hh',
|
||||
'url-parts.hh',
|
||||
'url.hh',
|
||||
'users.hh',
|
||||
'util.hh',
|
||||
'variant-wrapper.hh',
|
||||
'xml-writer.hh',
|
||||
)
|
||||
|
||||
if host_machine.system() == 'linux'
|
||||
subdir('linux')
|
||||
endif
|
||||
|
||||
if host_machine.system() == 'windows'
|
||||
subdir('windows')
|
||||
else
|
||||
subdir('unix')
|
||||
endif
|
||||
|
||||
subdir('build-utils-meson/export-all-symbols')
|
||||
|
||||
this_library = library(
|
||||
'nixutil',
|
||||
sources,
|
||||
dependencies : deps_public + deps_private + deps_other,
|
||||
include_directories : include_dirs,
|
||||
link_args: linker_export_flags,
|
||||
prelink : true, # For C++ static initializers
|
||||
install : true,
|
||||
)
|
||||
|
||||
install_headers(headers, subdir : 'nix', preserve_path : true)
|
||||
|
||||
libraries_private = []
|
||||
if host_machine.system() == 'windows'
|
||||
# `libraries_private` cannot contain ad-hoc dependencies (from
|
||||
# `find_library), so we need to do this manually
|
||||
libraries_private += ['-lws2_32']
|
||||
endif
|
||||
|
||||
subdir('build-utils-meson/export')
|
5
src/libutil/meson.options
Normal file
5
src/libutil/meson.options
Normal file
|
@ -0,0 +1,5 @@
|
|||
# vim: filetype=meson
|
||||
|
||||
option('cpuid', type : 'feature',
|
||||
description : 'determine microarchitecture levels with libcpuid (only relevant on x86_64)',
|
||||
)
|
103
src/libutil/package.nix
Normal file
103
src/libutil/package.nix
Normal file
|
@ -0,0 +1,103 @@
|
|||
{ lib
|
||||
, stdenv
|
||||
, mkMesonDerivation
|
||||
, releaseTools
|
||||
|
||||
, meson
|
||||
, ninja
|
||||
, pkg-config
|
||||
|
||||
, boost
|
||||
, brotli
|
||||
, libarchive
|
||||
, libcpuid
|
||||
, libsodium
|
||||
, nlohmann_json
|
||||
, openssl
|
||||
|
||||
# Configuration Options
|
||||
|
||||
, version
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (lib) fileset;
|
||||
in
|
||||
|
||||
mkMesonDerivation (finalAttrs: {
|
||||
pname = "nix-util";
|
||||
inherit version;
|
||||
|
||||
workDir = ./.;
|
||||
fileset = fileset.unions [
|
||||
../../build-utils-meson
|
||||
./build-utils-meson
|
||||
../../.version
|
||||
./.version
|
||||
./meson.build
|
||||
./meson.options
|
||||
./linux/meson.build
|
||||
./unix/meson.build
|
||||
./windows/meson.build
|
||||
(fileset.fileFilter (file: file.hasExt "cc") ./.)
|
||||
(fileset.fileFilter (file: file.hasExt "hh") ./.)
|
||||
];
|
||||
|
||||
outputs = [ "out" "dev" ];
|
||||
|
||||
nativeBuildInputs = [
|
||||
meson
|
||||
ninja
|
||||
pkg-config
|
||||
];
|
||||
|
||||
buildInputs = [
|
||||
brotli
|
||||
libsodium
|
||||
openssl
|
||||
] ++ lib.optional stdenv.hostPlatform.isx86_64 libcpuid
|
||||
;
|
||||
|
||||
propagatedBuildInputs = [
|
||||
boost
|
||||
libarchive
|
||||
nlohmann_json
|
||||
];
|
||||
|
||||
preConfigure =
|
||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
||||
# Do the meson utils, without modification.
|
||||
#
|
||||
# TODO: change release process to add `pre` in `.version`, remove it
|
||||
# before tagging, and restore after.
|
||||
''
|
||||
chmod u+w ./.version
|
||||
echo ${version} > ../../.version
|
||||
'';
|
||||
|
||||
mesonFlags = [
|
||||
(lib.mesonEnable "cpuid" stdenv.hostPlatform.isx86_64)
|
||||
];
|
||||
|
||||
env = {
|
||||
# Needed for Meson to find Boost.
|
||||
# https://github.com/NixOS/nixpkgs/issues/86131.
|
||||
BOOST_INCLUDEDIR = "${lib.getDev boost}/include";
|
||||
BOOST_LIBRARYDIR = "${lib.getLib boost}/lib";
|
||||
} // lib.optionalAttrs (stdenv.isLinux && !(stdenv.hostPlatform.isStatic && stdenv.system == "aarch64-linux")) {
|
||||
LDFLAGS = "-fuse-ld=gold";
|
||||
};
|
||||
|
||||
enableParallelBuilding = true;
|
||||
|
||||
separateDebugInfo = !stdenv.hostPlatform.isStatic;
|
||||
|
||||
strictDeps = true;
|
||||
|
||||
hardeningDisable = lib.optional stdenv.hostPlatform.isStatic "pie";
|
||||
|
||||
meta = {
|
||||
platforms = lib.platforms.unix ++ lib.platforms.windows;
|
||||
};
|
||||
|
||||
})
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "types.hh"
|
||||
#include "error.hh"
|
||||
#include "file-descriptor.hh"
|
||||
#include "logging.hh"
|
||||
#include "ansicolor.hh"
|
||||
|
||||
|
@ -23,26 +24,36 @@ namespace nix {
|
|||
struct Sink;
|
||||
struct Source;
|
||||
|
||||
#ifndef _WIN32
|
||||
class Pid
|
||||
{
|
||||
#ifndef _WIN32
|
||||
pid_t pid = -1;
|
||||
bool separatePG = false;
|
||||
int killSignal = SIGKILL;
|
||||
#else
|
||||
AutoCloseFD pid = INVALID_DESCRIPTOR;
|
||||
#endif
|
||||
public:
|
||||
Pid();
|
||||
#ifndef _WIN32
|
||||
Pid(pid_t pid);
|
||||
~Pid();
|
||||
void operator =(pid_t pid);
|
||||
operator pid_t();
|
||||
#else
|
||||
Pid(AutoCloseFD pid);
|
||||
void operator =(AutoCloseFD pid);
|
||||
#endif
|
||||
~Pid();
|
||||
int kill();
|
||||
int wait();
|
||||
|
||||
// TODO: Implement for Windows
|
||||
#ifndef _WIN32
|
||||
void setSeparatePG(bool separatePG);
|
||||
void setKillSignal(int signal);
|
||||
pid_t release();
|
||||
};
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
#ifndef _WIN32
|
||||
|
|
|
@ -67,6 +67,17 @@ int getArchiveFilterCodeByName(const std::string & method)
|
|||
return code;
|
||||
}
|
||||
|
||||
static void enableSupportedFormats(struct archive * archive)
|
||||
{
|
||||
archive_read_support_format_tar(archive);
|
||||
archive_read_support_format_zip(archive);
|
||||
|
||||
/* Enable support for empty files so we don't throw an exception
|
||||
for empty HTTP 304 "Not modified" responses. See
|
||||
downloadTarball(). */
|
||||
archive_read_support_format_empty(archive);
|
||||
}
|
||||
|
||||
TarArchive::TarArchive(Source & source, bool raw, std::optional<std::string> compression_method)
|
||||
: archive{archive_read_new()}
|
||||
, source{&source}
|
||||
|
@ -78,9 +89,9 @@ TarArchive::TarArchive(Source & source, bool raw, std::optional<std::string> com
|
|||
archive_read_support_filter_by_code(archive, getArchiveFilterCodeByName(*compression_method));
|
||||
}
|
||||
|
||||
if (!raw) {
|
||||
archive_read_support_format_all(archive);
|
||||
} else {
|
||||
if (!raw)
|
||||
enableSupportedFormats(archive);
|
||||
else {
|
||||
archive_read_support_format_raw(archive);
|
||||
archive_read_support_format_empty(archive);
|
||||
}
|
||||
|
@ -96,7 +107,7 @@ TarArchive::TarArchive(const Path & path)
|
|||
, buffer(defaultBufferSize)
|
||||
{
|
||||
archive_read_support_filter_all(archive);
|
||||
archive_read_support_format_all(archive);
|
||||
enableSupportedFormats(archive);
|
||||
archive_read_set_option(archive, NULL, "mac-ext", NULL);
|
||||
check(archive_read_open_filename(archive, path.c_str(), 16384), "failed to open archive: %s");
|
||||
}
|
||||
|
@ -176,6 +187,7 @@ time_t unpackTarfileToSink(TarArchive & archive, ExtendedFileSystemObjectSink &
|
|||
auto path = archive_entry_pathname(entry);
|
||||
if (!path)
|
||||
throw Error("cannot get archive member name: %s", archive_error_string(archive.archive));
|
||||
auto cpath = CanonPath{path};
|
||||
if (r == ARCHIVE_WARN)
|
||||
warn(archive_error_string(archive.archive));
|
||||
else
|
||||
|
@ -191,11 +203,11 @@ time_t unpackTarfileToSink(TarArchive & archive, ExtendedFileSystemObjectSink &
|
|||
switch (auto type = archive_entry_filetype(entry)) {
|
||||
|
||||
case AE_IFDIR:
|
||||
parseSink.createDirectory(path);
|
||||
parseSink.createDirectory(cpath);
|
||||
break;
|
||||
|
||||
case AE_IFREG: {
|
||||
parseSink.createRegularFile(path, [&](auto & crf) {
|
||||
parseSink.createRegularFile(cpath, [&](auto & crf) {
|
||||
if (archive_entry_mode(entry) & S_IXUSR)
|
||||
crf.isExecutable();
|
||||
|
||||
|
@ -219,7 +231,7 @@ time_t unpackTarfileToSink(TarArchive & archive, ExtendedFileSystemObjectSink &
|
|||
case AE_IFLNK: {
|
||||
auto target = archive_entry_symlink(entry);
|
||||
|
||||
parseSink.createSymlink(path, target);
|
||||
parseSink.createSymlink(cpath, target);
|
||||
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ AutoCloseFD createUnixDomainSocket()
|
|||
if (!fdSocket)
|
||||
throw SysError("cannot create Unix domain socket");
|
||||
#ifndef _WIN32
|
||||
closeOnExec(fdSocket.get());
|
||||
unix::closeOnExec(fdSocket.get());
|
||||
#endif
|
||||
return fdSocket;
|
||||
}
|
||||
|
|
|
@ -110,8 +110,8 @@ void Pipe::create()
|
|||
if (pipe2(fds, O_CLOEXEC) != 0) throw SysError("creating pipe");
|
||||
#else
|
||||
if (pipe(fds) != 0) throw SysError("creating pipe");
|
||||
closeOnExec(fds[0]);
|
||||
closeOnExec(fds[1]);
|
||||
unix::closeOnExec(fds[0]);
|
||||
unix::closeOnExec(fds[1]);
|
||||
#endif
|
||||
readSide = fds[0];
|
||||
writeSide = fds[1];
|
||||
|
@ -120,7 +120,7 @@ void Pipe::create()
|
|||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
void closeMostFDs(const std::set<int> & exceptions)
|
||||
void unix::closeMostFDs(const std::set<int> & exceptions)
|
||||
{
|
||||
#if __linux__
|
||||
try {
|
||||
|
@ -139,14 +139,16 @@ void closeMostFDs(const std::set<int> & exceptions)
|
|||
#endif
|
||||
|
||||
int maxFD = 0;
|
||||
#if HAVE_SYSCONF
|
||||
maxFD = sysconf(_SC_OPEN_MAX);
|
||||
#endif
|
||||
for (int fd = 0; fd < maxFD; ++fd)
|
||||
if (!exceptions.count(fd))
|
||||
close(fd); /* ignore result */
|
||||
}
|
||||
|
||||
|
||||
void closeOnExec(int fd)
|
||||
void unix::closeOnExec(int fd)
|
||||
{
|
||||
int prev;
|
||||
if ((prev = fcntl(fd, F_GETFD, 0)) == -1 ||
|
||||
|
|
17
src/libutil/unix/meson.build
Normal file
17
src/libutil/unix/meson.build
Normal file
|
@ -0,0 +1,17 @@
|
|||
sources += files(
|
||||
'environment-variables.cc',
|
||||
'file-descriptor.cc',
|
||||
'file-path.cc',
|
||||
'file-system.cc',
|
||||
'muxable-pipe.cc',
|
||||
'processes.cc',
|
||||
'signals.cc',
|
||||
'users.cc',
|
||||
)
|
||||
|
||||
include_dirs += include_directories('.')
|
||||
|
||||
headers += files(
|
||||
'monitor-fd.hh',
|
||||
'signals-impl.hh',
|
||||
)
|
|
@ -122,7 +122,7 @@ void Pipe::create()
|
|||
|
||||
#if _WIN32_WINNT >= 0x0600
|
||||
|
||||
std::wstring handleToFileName(HANDLE handle) {
|
||||
std::wstring windows::handleToFileName(HANDLE handle) {
|
||||
std::vector<wchar_t> buf(0x100);
|
||||
DWORD dw = GetFinalPathNameByHandleW(handle, buf.data(), buf.size(), FILE_NAME_OPENED);
|
||||
if (dw == 0) {
|
||||
|
@ -141,7 +141,7 @@ std::wstring handleToFileName(HANDLE handle) {
|
|||
}
|
||||
|
||||
|
||||
Path handleToPath(HANDLE handle) {
|
||||
Path windows::handleToPath(HANDLE handle) {
|
||||
return os_string_to_string(handleToFileName(handle));
|
||||
}
|
||||
|
||||
|
|
19
src/libutil/windows/meson.build
Normal file
19
src/libutil/windows/meson.build
Normal file
|
@ -0,0 +1,19 @@
|
|||
sources += files(
|
||||
'environment-variables.cc',
|
||||
'file-descriptor.cc',
|
||||
'file-path.cc',
|
||||
'file-system.cc',
|
||||
'muxable-pipe.cc',
|
||||
'processes.cc',
|
||||
'users.cc',
|
||||
'windows-async-pipe.cc',
|
||||
'windows-error.cc',
|
||||
)
|
||||
|
||||
include_dirs += include_directories('.')
|
||||
|
||||
headers += files(
|
||||
'signals-impl.hh',
|
||||
'windows-async-pipe.hh',
|
||||
'windows-error.hh',
|
||||
)
|
|
@ -1,9 +1,15 @@
|
|||
#include "current-process.hh"
|
||||
#include "environment-variables.hh"
|
||||
#include "error.hh"
|
||||
#include "file-descriptor.hh"
|
||||
#include "file-path.hh"
|
||||
#include "signals.hh"
|
||||
#include "processes.hh"
|
||||
#include "finally.hh"
|
||||
#include "serialise.hh"
|
||||
#include "file-system.hh"
|
||||
#include "util.hh"
|
||||
#include "windows-error.hh"
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
|
@ -16,25 +22,347 @@
|
|||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
namespace nix {
|
||||
|
||||
std::string runProgram(Path program, bool lookupPath, const Strings & args,
|
||||
const std::optional<std::string> & input, bool isInteractive)
|
||||
using namespace nix::windows;
|
||||
|
||||
Pid::Pid() {}
|
||||
|
||||
Pid::Pid(AutoCloseFD pid)
|
||||
: pid(std::move(pid))
|
||||
{
|
||||
throw UnimplementedError("Cannot shell out to git on Windows yet");
|
||||
}
|
||||
|
||||
Pid::~Pid()
|
||||
{
|
||||
if (pid.get() != INVALID_DESCRIPTOR)
|
||||
kill();
|
||||
}
|
||||
|
||||
void Pid::operator=(AutoCloseFD pid)
|
||||
{
|
||||
if (this->pid.get() != INVALID_DESCRIPTOR && this->pid.get() != pid.get())
|
||||
kill();
|
||||
this->pid = std::move(pid);
|
||||
}
|
||||
|
||||
// TODO: Implement (not needed for process spawning yet)
|
||||
int Pid::kill()
|
||||
{
|
||||
assert(pid.get() != INVALID_DESCRIPTOR);
|
||||
|
||||
debug("killing process %1%", pid.get());
|
||||
|
||||
throw UnimplementedError("Pid::kill unimplemented");
|
||||
}
|
||||
|
||||
int Pid::wait()
|
||||
{
|
||||
// https://github.com/nix-windows/nix/blob/windows-meson/src/libutil/util.cc#L1938
|
||||
assert(pid.get() != INVALID_DESCRIPTOR);
|
||||
DWORD status = WaitForSingleObject(pid.get(), INFINITE);
|
||||
if (status != WAIT_OBJECT_0) {
|
||||
debug("WaitForSingleObject returned %1%", status);
|
||||
}
|
||||
|
||||
DWORD exitCode = 0;
|
||||
if (GetExitCodeProcess(pid.get(), &exitCode) == FALSE) {
|
||||
debug("GetExitCodeProcess failed on pid %1%", pid.get());
|
||||
}
|
||||
|
||||
pid.close();
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
// TODO: Merge this with Unix's runProgram since it's identical logic.
|
||||
std::string runProgram(
|
||||
Path program, bool lookupPath, const Strings & args, const std::optional<std::string> & input, bool isInteractive)
|
||||
{
|
||||
auto res = runProgram(RunOptions{
|
||||
.program = program, .lookupPath = lookupPath, .args = args, .input = input, .isInteractive = isInteractive});
|
||||
|
||||
if (!statusOk(res.first))
|
||||
throw ExecError(res.first, "program '%1%' %2%", program, statusToString(res.first));
|
||||
|
||||
return res.second;
|
||||
}
|
||||
|
||||
std::optional<Path> getProgramInterpreter(const Path & program)
|
||||
{
|
||||
// These extensions are automatically handled by Windows and don't require an interpreter.
|
||||
static constexpr const char * exts[] = {".exe", ".cmd", ".bat"};
|
||||
for (const auto ext : exts) {
|
||||
if (hasSuffix(program, ext)) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
// TODO: Open file and read the shebang
|
||||
throw UnimplementedError("getProgramInterpreter unimplemented");
|
||||
}
|
||||
|
||||
// TODO: Not sure if this is needed in the unix version but it might be useful as a member func
|
||||
void setFDInheritable(AutoCloseFD & fd, bool inherit)
|
||||
{
|
||||
if (fd.get() != INVALID_DESCRIPTOR) {
|
||||
if (!SetHandleInformation(fd.get(), HANDLE_FLAG_INHERIT, inherit ? HANDLE_FLAG_INHERIT : 0)) {
|
||||
throw WinError("Couldn't disable inheriting of handle");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AutoCloseFD nullFD()
|
||||
{
|
||||
// Create null handle to discard reads / writes
|
||||
// https://stackoverflow.com/a/25609668
|
||||
// https://github.com/nix-windows/nix/blob/windows-meson/src/libutil/util.cc#L2228
|
||||
AutoCloseFD nul = CreateFileW(
|
||||
L"NUL",
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
// We don't care who reads / writes / deletes this file since it's NUL anyways
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
NULL,
|
||||
OPEN_EXISTING,
|
||||
0,
|
||||
NULL);
|
||||
if (!nul.get()) {
|
||||
throw WinError("Couldn't open NUL device");
|
||||
}
|
||||
// Let this handle be inheritable by child processes
|
||||
setFDInheritable(nul, true);
|
||||
return nul;
|
||||
}
|
||||
|
||||
// Adapted from
|
||||
// https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
|
||||
std::string windowsEscape(const std::string & str, bool cmd)
|
||||
{
|
||||
// TODO: This doesn't handle cmd.exe escaping.
|
||||
if (cmd) {
|
||||
throw UnimplementedError("cmd.exe escaping is not implemented");
|
||||
}
|
||||
|
||||
if (str.find_first_of(" \t\n\v\"") == str.npos && !str.empty()) {
|
||||
// No need to escape this one, the nonempty contents don't have a special character
|
||||
return str;
|
||||
}
|
||||
std::string buffer;
|
||||
// Add the opening quote
|
||||
buffer += '"';
|
||||
for (auto iter = str.begin();; ++iter) {
|
||||
size_t backslashes = 0;
|
||||
while (iter != str.end() && *iter == '\\') {
|
||||
++iter;
|
||||
++backslashes;
|
||||
}
|
||||
|
||||
// We only escape backslashes if:
|
||||
// - They come immediately before the closing quote
|
||||
// - They come immediately before a quote in the middle of the string
|
||||
// Both of these cases break the escaping if not handled. Otherwise backslashes are fine as-is
|
||||
if (iter == str.end()) {
|
||||
// Need to escape each backslash
|
||||
buffer.append(backslashes * 2, '\\');
|
||||
// Exit since we've reached the end of the string
|
||||
break;
|
||||
} else if (*iter == '"') {
|
||||
// Need to escape each backslash and the intermediate quote character
|
||||
buffer.append(backslashes * 2, '\\');
|
||||
buffer += "\\\"";
|
||||
} else {
|
||||
// Don't escape the backslashes since they won't break the delimiter
|
||||
buffer.append(backslashes, '\\');
|
||||
buffer += *iter;
|
||||
}
|
||||
}
|
||||
// Add the closing quote
|
||||
return buffer + '"';
|
||||
}
|
||||
|
||||
Pid spawnProcess(const Path & realProgram, const RunOptions & options, Pipe & out, Pipe & in)
|
||||
{
|
||||
// Setup pipes.
|
||||
if (options.standardOut) {
|
||||
// Don't inherit the read end of the output pipe
|
||||
setFDInheritable(out.readSide, false);
|
||||
} else {
|
||||
out.writeSide = nullFD();
|
||||
}
|
||||
if (options.standardIn) {
|
||||
// Don't inherit the write end of the input pipe
|
||||
setFDInheritable(in.writeSide, false);
|
||||
} else {
|
||||
in.readSide = nullFD();
|
||||
}
|
||||
|
||||
STARTUPINFOW startInfo = {0};
|
||||
startInfo.cb = sizeof(startInfo);
|
||||
startInfo.dwFlags = STARTF_USESTDHANDLES;
|
||||
startInfo.hStdInput = in.readSide.get();
|
||||
startInfo.hStdOutput = out.writeSide.get();
|
||||
startInfo.hStdError = out.writeSide.get();
|
||||
|
||||
std::string envline;
|
||||
// Retain the current processes' environment variables.
|
||||
for (const auto & envVar : getEnv()) {
|
||||
envline += (envVar.first + '=' + envVar.second + '\0');
|
||||
}
|
||||
// Also add new ones specified in options.
|
||||
if (options.environment) {
|
||||
for (const auto & envVar : *options.environment) {
|
||||
envline += (envVar.first + '=' + envVar.second + '\0');
|
||||
}
|
||||
}
|
||||
|
||||
std::string cmdline = windowsEscape(realProgram, false);
|
||||
for (const auto & arg : options.args) {
|
||||
// TODO: This isn't the right way to escape windows command
|
||||
// See https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw
|
||||
cmdline += ' ' + windowsEscape(arg, false);
|
||||
}
|
||||
|
||||
PROCESS_INFORMATION procInfo = {0};
|
||||
if (CreateProcessW(
|
||||
// EXE path is provided in the cmdline
|
||||
NULL,
|
||||
string_to_os_string(cmdline).data(),
|
||||
NULL,
|
||||
NULL,
|
||||
TRUE,
|
||||
CREATE_UNICODE_ENVIRONMENT | CREATE_SUSPENDED,
|
||||
string_to_os_string(envline).data(),
|
||||
options.chdir.has_value() ? string_to_os_string(*options.chdir).data() : NULL,
|
||||
&startInfo,
|
||||
&procInfo)
|
||||
== 0) {
|
||||
throw WinError("CreateProcessW failed (%1%)", cmdline);
|
||||
}
|
||||
|
||||
// Convert these to use RAII
|
||||
AutoCloseFD process = procInfo.hProcess;
|
||||
AutoCloseFD thread = procInfo.hThread;
|
||||
|
||||
// Add current process and child to job object so child terminates when parent terminates
|
||||
// TODO: This spawns one job per child process. We can probably keep this as a global, and
|
||||
// add children a single job so we don't use so many jobs at once.
|
||||
Descriptor job = CreateJobObjectW(NULL, NULL);
|
||||
if (job == NULL) {
|
||||
TerminateProcess(procInfo.hProcess, 0);
|
||||
throw WinError("Couldn't create job object for child process");
|
||||
}
|
||||
if (AssignProcessToJobObject(job, procInfo.hProcess) == FALSE) {
|
||||
TerminateProcess(procInfo.hProcess, 0);
|
||||
throw WinError("Couldn't assign child process to job object");
|
||||
}
|
||||
if (ResumeThread(procInfo.hThread) == (DWORD) -1) {
|
||||
TerminateProcess(procInfo.hProcess, 0);
|
||||
throw WinError("Couldn't resume child process thread");
|
||||
}
|
||||
|
||||
return process;
|
||||
}
|
||||
|
||||
// TODO: Merge this with Unix's runProgram since it's identical logic.
|
||||
// Output = error code + "standard out" output stream
|
||||
std::pair<int, std::string> runProgram(RunOptions && options)
|
||||
{
|
||||
throw UnimplementedError("Cannot shell out to git on Windows yet");
|
||||
}
|
||||
StringSink sink;
|
||||
options.standardOut = &sink;
|
||||
|
||||
int status = 0;
|
||||
|
||||
try {
|
||||
runProgram2(options);
|
||||
} catch (ExecError & e) {
|
||||
status = e.status;
|
||||
}
|
||||
|
||||
return {status, std::move(sink.s)};
|
||||
}
|
||||
|
||||
void runProgram2(const RunOptions & options)
|
||||
{
|
||||
throw UnimplementedError("Cannot shell out to git on Windows yet");
|
||||
checkInterrupt();
|
||||
|
||||
assert(!(options.standardIn && options.input));
|
||||
|
||||
std::unique_ptr<Source> source_;
|
||||
Source * source = options.standardIn;
|
||||
|
||||
if (options.input) {
|
||||
source_ = std::make_unique<StringSource>(*options.input);
|
||||
source = source_.get();
|
||||
}
|
||||
|
||||
/* Create a pipe. */
|
||||
Pipe out, in;
|
||||
// TODO: I copied this from unix but this is handled again in spawnProcess, so might be weird to split it up like
|
||||
// this
|
||||
if (options.standardOut)
|
||||
out.create();
|
||||
if (source)
|
||||
in.create();
|
||||
|
||||
Path realProgram = options.program;
|
||||
// TODO: Implement shebang / program interpreter lookup on Windows
|
||||
auto interpreter = getProgramInterpreter(realProgram);
|
||||
|
||||
std::optional<Finally<std::function<void()>>> resumeLoggerDefer;
|
||||
if (options.isInteractive) {
|
||||
logger->pause();
|
||||
resumeLoggerDefer.emplace([]() { logger->resume(); });
|
||||
}
|
||||
|
||||
Pid pid = spawnProcess(interpreter.has_value() ? *interpreter : realProgram, options, out, in);
|
||||
|
||||
// TODO: This is identical to unix, deduplicate?
|
||||
out.writeSide.close();
|
||||
|
||||
std::thread writerThread;
|
||||
|
||||
std::promise<void> promise;
|
||||
|
||||
Finally doJoin([&] {
|
||||
if (writerThread.joinable())
|
||||
writerThread.join();
|
||||
});
|
||||
|
||||
if (source) {
|
||||
in.readSide.close();
|
||||
writerThread = std::thread([&] {
|
||||
try {
|
||||
std::vector<char> buf(8 * 1024);
|
||||
while (true) {
|
||||
size_t n;
|
||||
try {
|
||||
n = source->read(buf.data(), buf.size());
|
||||
} catch (EndOfFile &) {
|
||||
break;
|
||||
}
|
||||
writeFull(in.writeSide.get(), {buf.data(), n});
|
||||
}
|
||||
promise.set_value();
|
||||
} catch (...) {
|
||||
promise.set_exception(std::current_exception());
|
||||
}
|
||||
in.writeSide.close();
|
||||
});
|
||||
}
|
||||
|
||||
if (options.standardOut)
|
||||
drainFD(out.readSide.get(), *options.standardOut);
|
||||
|
||||
/* Wait for the child to finish. */
|
||||
int status = pid.wait();
|
||||
|
||||
/* Wait for the writer thread to finish. */
|
||||
if (source)
|
||||
promise.get_future().get();
|
||||
|
||||
if (status)
|
||||
throw ExecError(status, "program '%1%' %2%", options.program, statusToString(status));
|
||||
}
|
||||
|
||||
std::string statusToString(int status)
|
||||
|
@ -45,10 +373,8 @@ std::string statusToString(int status)
|
|||
return "succeeded";
|
||||
}
|
||||
|
||||
|
||||
bool statusOk(int status)
|
||||
{
|
||||
return status == 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue