aboutsummaryrefslogtreecommitdiff
path: root/src/libutil/util.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/libutil/util.cc')
-rw-r--r--src/libutil/util.cc200
1 files changed, 131 insertions, 69 deletions
diff --git a/src/libutil/util.cc b/src/libutil/util.cc
index cd359cfee..c075a14b4 100644
--- a/src/libutil/util.cc
+++ b/src/libutil/util.cc
@@ -71,13 +71,11 @@ void clearEnv()
unsetenv(name.first.c_str());
}
-void replaceEnv(std::map<std::string, std::string> newEnv)
+void replaceEnv(const std::map<std::string, std::string> & newEnv)
{
clearEnv();
- for (auto newEnvVar : newEnv)
- {
+ for (auto & newEnvVar : newEnv)
setenv(newEnvVar.first.c_str(), newEnvVar.second.c_str(), 1);
- }
}
@@ -110,13 +108,13 @@ Path canonPath(PathView path, bool resolveSymlinks)
{
assert(path != "");
- string s;
+ std::string s;
s.reserve(256);
if (path[0] != '/')
throw Error("not an absolute path: '%1%'", path);
- string temp;
+ std::string temp;
/* Count the number of times we follow a symlink and stop at some
arbitrary (but high) limit to prevent infinite loops. */
@@ -142,7 +140,7 @@ Path canonPath(PathView path, bool resolveSymlinks)
/* Normal component; copy it. */
else {
s += '/';
- if (const auto slash = path.find('/'); slash == string::npos) {
+ if (const auto slash = path.find('/'); slash == std::string::npos) {
s += path;
path = {};
} else {
@@ -175,7 +173,7 @@ Path canonPath(PathView path, bool resolveSymlinks)
Path dirOf(const PathView path)
{
Path::size_type pos = path.rfind('/');
- if (pos == string::npos)
+ if (pos == std::string::npos)
return ".";
return pos == 0 ? "/" : Path(path, 0, pos);
}
@@ -191,7 +189,7 @@ std::string_view baseNameOf(std::string_view path)
last -= 1;
auto pos = path.rfind('/', last);
- if (pos == string::npos)
+ if (pos == std::string::npos)
pos = 0;
else
pos += 1;
@@ -249,7 +247,7 @@ Path readLink(const Path & path)
else
throw SysError("reading symbolic link '%1%'", path);
else if (rlSize < bufSize)
- return string(buf.data(), rlSize);
+ return std::string(buf.data(), rlSize);
}
}
@@ -269,7 +267,7 @@ DirEntries readDirectory(DIR *dir, const Path & path)
struct dirent * dirent;
while (errno = 0, dirent = readdir(dir)) { /* sic */
checkInterrupt();
- string name = dirent->d_name;
+ std::string name = dirent->d_name;
if (name == "." || name == "..") continue;
entries.emplace_back(name, dirent->d_ino,
#ifdef HAVE_STRUCT_DIRENT_D_TYPE
@@ -303,7 +301,7 @@ unsigned char getFileType(const Path & path)
}
-string readFile(int fd)
+std::string readFile(int fd)
{
struct stat st;
if (fstat(fd, &st) == -1)
@@ -313,7 +311,7 @@ string readFile(int fd)
}
-string readFile(const Path & path)
+std::string readFile(const Path & path)
{
AutoCloseFD fd = open(path.c_str(), O_RDONLY | O_CLOEXEC);
if (!fd)
@@ -366,9 +364,9 @@ void writeFile(const Path & path, Source & source, mode_t mode)
}
}
-string readLine(int fd)
+std::string readLine(int fd)
{
- string s;
+ std::string s;
while (1) {
checkInterrupt();
char ch;
@@ -387,7 +385,7 @@ string readLine(int fd)
}
-void writeLine(int fd, string s)
+void writeLine(int fd, std::string s)
{
s += '\n';
writeFull(fd, s);
@@ -398,7 +396,7 @@ static void _deletePath(int parentfd, const Path & path, uint64_t & bytesFreed)
{
checkInterrupt();
- string name(baseNameOf(path));
+ std::string name(baseNameOf(path));
struct stat st;
if (fstatat(parentfd, name.c_str(), &st, AT_SYMLINK_NOFOLLOW) == -1) {
@@ -406,8 +404,29 @@ static void _deletePath(int parentfd, const Path & path, uint64_t & bytesFreed)
throw SysError("getting status of '%1%'", path);
}
- if (!S_ISDIR(st.st_mode) && st.st_nlink == 1)
- bytesFreed += st.st_size;
+ if (!S_ISDIR(st.st_mode)) {
+ /* We are about to delete a file. Will it likely free space? */
+
+ switch (st.st_nlink) {
+ /* Yes: last link. */
+ case 1:
+ bytesFreed += st.st_size;
+ break;
+ /* Maybe: yes, if 'auto-optimise-store' or manual optimisation
+ was performed. Instead of checking for real let's assume
+ it's an optimised file and space will be freed.
+
+ In worst case we will double count on freed space for files
+ with exactly two hardlinks for unoptimised packages.
+ */
+ case 2:
+ bytesFreed += st.st_size;
+ break;
+ /* No: 3+ links. */
+ default:
+ break;
+ }
+ }
if (S_ISDIR(st.st_mode)) {
/* Make the directory accessible. */
@@ -566,8 +585,8 @@ Path getConfigDir()
std::vector<Path> getConfigDirs()
{
Path configHome = getConfigDir();
- string configDirs = getEnv("XDG_CONFIG_DIRS").value_or("/etc/xdg");
- std::vector<Path> result = tokenizeString<std::vector<string>>(configDirs, ":");
+ auto configDirs = getEnv("XDG_CONFIG_DIRS").value_or("/etc/xdg");
+ std::vector<Path> result = tokenizeString<std::vector<std::string>>(configDirs, ":");
result.insert(result.begin(), configHome);
return result;
}
@@ -670,7 +689,7 @@ void writeFull(int fd, std::string_view s, bool allowInterrupts)
}
-string drainFD(int fd, bool block, const size_t reserveSize)
+std::string drainFD(int fd, bool block, const size_t reserveSize)
{
// the parser needs two extra bytes to append terminating characters, other users will
// not care very much about the extra memory.
@@ -682,7 +701,14 @@ string drainFD(int fd, bool block, const size_t reserveSize)
void drainFD(int fd, Sink & sink, bool block)
{
- int saved;
+ // silence GCC maybe-uninitialized warning in finally
+ int saved = 0;
+
+ if (!block) {
+ saved = fcntl(fd, F_GETFL);
+ if (fcntl(fd, F_SETFL, saved | O_NONBLOCK) == -1)
+ throw SysError("making file descriptor non-blocking");
+ }
Finally finally([&]() {
if (!block) {
@@ -691,12 +717,6 @@ void drainFD(int fd, Sink & sink, bool block)
}
});
- if (!block) {
- saved = fcntl(fd, F_GETFL);
- if (fcntl(fd, F_SETFL, saved | O_NONBLOCK) == -1)
- throw SysError("making file descriptor non-blocking");
- }
-
std::vector<unsigned char> buf(64 * 1024);
while (1) {
checkInterrupt();
@@ -719,7 +739,7 @@ void drainFD(int fd, Sink & sink, bool block)
AutoDelete::AutoDelete() : del{false} {}
-AutoDelete::AutoDelete(const string & p, bool recursive) : path(p)
+AutoDelete::AutoDelete(const std::string & p, bool recursive) : path(p)
{
del = true;
this->recursive = recursive;
@@ -1036,7 +1056,7 @@ std::vector<char *> stringsToCharPtrs(const Strings & ss)
return res;
}
-string runProgram(Path program, bool searchPath, const Strings & args,
+std::string runProgram(Path program, bool searchPath, const Strings & args,
const std::optional<std::string> & input)
{
auto res = runProgram(RunOptions {.program = program, .searchPath = searchPath, .args = args, .input = input});
@@ -1174,7 +1194,7 @@ void runProgram2(const RunOptions & options)
}
-void closeMostFDs(const set<int> & exceptions)
+void closeMostFDs(const std::set<int> & exceptions)
{
#if __linux__
try {
@@ -1238,11 +1258,11 @@ void _interrupted()
template<class C> C tokenizeString(std::string_view s, std::string_view separators)
{
C result;
- string::size_type pos = s.find_first_not_of(separators, 0);
- while (pos != string::npos) {
- string::size_type end = s.find_first_of(separators, pos + 1);
- if (end == string::npos) end = s.size();
- result.insert(result.end(), string(s, pos, end - pos));
+ auto pos = s.find_first_not_of(separators, 0);
+ while (pos != std::string_view::npos) {
+ auto end = s.find_first_of(separators, pos + 1);
+ if (end == std::string_view::npos) end = s.size();
+ result.insert(result.end(), std::string(s, pos, end - pos));
pos = s.find_first_not_of(separators, end);
}
return result;
@@ -1250,29 +1270,30 @@ template<class C> C tokenizeString(std::string_view s, std::string_view separato
template Strings tokenizeString(std::string_view s, std::string_view separators);
template StringSet tokenizeString(std::string_view s, std::string_view separators);
-template vector<string> tokenizeString(std::string_view s, std::string_view separators);
+template std::vector<std::string> tokenizeString(std::string_view s, std::string_view separators);
-string chomp(std::string_view s)
+std::string chomp(std::string_view s)
{
size_t i = s.find_last_not_of(" \n\r\t");
- return i == string::npos ? "" : string(s, 0, i + 1);
+ return i == std::string_view::npos ? "" : std::string(s, 0, i + 1);
}
-string trim(const string & s, const string & whitespace)
+std::string trim(std::string_view s, std::string_view whitespace)
{
auto i = s.find_first_not_of(whitespace);
- if (i == string::npos) return "";
+ if (i == s.npos) return "";
auto j = s.find_last_not_of(whitespace);
- return string(s, i, j == string::npos ? j : j - i + 1);
+ return std::string(s, i, j == s.npos ? j : j - i + 1);
}
-string replaceStrings(std::string_view s,
- const std::string & from, const std::string & to)
+std::string replaceStrings(
+ std::string res,
+ std::string_view from,
+ std::string_view to)
{
- string res(s);
if (from.empty()) return res;
size_t pos = 0;
while ((pos = res.find(from, pos)) != std::string::npos) {
@@ -1283,20 +1304,19 @@ string replaceStrings(std::string_view s,
}
-std::string rewriteStrings(const std::string & _s, const StringMap & rewrites)
+std::string rewriteStrings(std::string s, const StringMap & rewrites)
{
- auto s = _s;
for (auto & i : rewrites) {
if (i.first == i.second) continue;
size_t j = 0;
- while ((j = s.find(i.first, j)) != string::npos)
+ while ((j = s.find(i.first, j)) != std::string::npos)
s.replace(j, i.first.size(), i.second);
}
return s;
}
-string statusToString(int status)
+std::string statusToString(int status)
{
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
if (WIFEXITED(status))
@@ -1358,11 +1378,15 @@ std::string shellEscape(const std::string_view s)
void ignoreException()
{
+ /* Make sure no exceptions leave this function.
+ printError() also throws when remote is closed. */
try {
- throw;
- } catch (std::exception & e) {
- printError("error (ignored): %1%", e.what());
- }
+ try {
+ throw;
+ } catch (std::exception & e) {
+ printError("error (ignored): %1%", e.what());
+ }
+ } catch (...) { }
}
bool shouldANSI()
@@ -1408,7 +1432,7 @@ std::string filterANSIEscapes(const std::string & s, bool filterAll, unsigned in
}
}
- else if (*i == '\r')
+ else if (*i == '\r' || *i == '\a')
// do nothing for now
i++;
@@ -1444,9 +1468,10 @@ std::string filterANSIEscapes(const std::string & s, bool filterAll, unsigned in
constexpr char base64Chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
-string base64Encode(std::string_view s)
+std::string base64Encode(std::string_view s)
{
- string res;
+ std::string res;
+ res.reserve((s.size() + 2) / 3 * 4);
int data = 0, nbits = 0;
for (char c : s) {
@@ -1465,7 +1490,7 @@ string base64Encode(std::string_view s)
}
-string base64Decode(std::string_view s)
+std::string base64Decode(std::string_view s)
{
constexpr char npos = -1;
constexpr std::array<char, 256> base64DecodeChars = [&]() {
@@ -1477,7 +1502,10 @@ string base64Decode(std::string_view s)
return result;
}();
- string res;
+ std::string res;
+ // Some sequences are missing the padding consisting of up to two '='.
+ // vvv
+ res.reserve((s.size() + 2) / 4 * 3);
unsigned int d = 0, bits = 0;
for (char c : s) {
@@ -1561,7 +1589,22 @@ std::pair<unsigned short, unsigned short> getWindowSize()
}
-static Sync<std::list<std::function<void()>>> _interruptCallbacks;
+/* We keep track of interrupt callbacks using integer tokens, so we can iterate
+ safely without having to lock the data structure while executing arbitrary
+ functions.
+ */
+struct InterruptCallbacks {
+ typedef int64_t Token;
+
+ /* We use unique tokens so that we can't accidentally delete the wrong
+ handler because of an erroneous double delete. */
+ Token nextToken = 0;
+
+ /* Used as a list, see InterruptCallbacks comment. */
+ std::map<Token, std::function<void()>> callbacks;
+};
+
+static Sync<InterruptCallbacks> _interruptCallbacks;
static void signalHandlerThread(sigset_t set)
{
@@ -1583,8 +1626,19 @@ void triggerInterrupt()
_isInterrupted = true;
{
- auto interruptCallbacks(_interruptCallbacks.lock());
- for (auto & callback : *interruptCallbacks) {
+ InterruptCallbacks::Token i = 0;
+ while (true) {
+ std::function<void()> callback;
+ {
+ auto interruptCallbacks(_interruptCallbacks.lock());
+ auto lb = interruptCallbacks->callbacks.lower_bound(i);
+ if (lb == interruptCallbacks->callbacks.end())
+ break;
+
+ callback = lb->second;
+ i = lb->first + 1;
+ }
+
try {
callback();
} catch (...) {
@@ -1638,7 +1692,9 @@ void setStackSize(size_t stackSize)
#endif
}
+#if __linux__
static AutoCloseFD fdSavedMountNamespace;
+#endif
void saveMountNamespace()
{
@@ -1657,8 +1713,13 @@ void restoreMountNamespace()
{
#if __linux__
try {
+ auto savedCwd = absPath(".");
+
if (fdSavedMountNamespace && setns(fdSavedMountNamespace.get(), CLONE_NEWNS) == -1)
throw SysError("restoring parent mount namespace");
+ if (chdir(savedCwd.c_str()) == -1) {
+ throw SysError("restoring cwd");
+ }
} catch (Error & e) {
debug(e.msg());
}
@@ -1694,21 +1755,22 @@ void restoreProcessContext(bool restoreMounts)
/* RAII helper to automatically deregister a callback. */
struct InterruptCallbackImpl : InterruptCallback
{
- std::list<std::function<void()>>::iterator it;
+ InterruptCallbacks::Token token;
~InterruptCallbackImpl() override
{
- _interruptCallbacks.lock()->erase(it);
+ auto interruptCallbacks(_interruptCallbacks.lock());
+ interruptCallbacks->callbacks.erase(token);
}
};
std::unique_ptr<InterruptCallback> createInterruptCallback(std::function<void()> callback)
{
auto interruptCallbacks(_interruptCallbacks.lock());
- interruptCallbacks->push_back(callback);
+ auto token = interruptCallbacks->nextToken++;
+ interruptCallbacks->callbacks.emplace(token, callback);
auto res = std::make_unique<InterruptCallbackImpl>();
- res->it = interruptCallbacks->end();
- res->it--;
+ res->token = token;
return std::unique_ptr<InterruptCallback>(res.release());
}
@@ -1804,7 +1866,7 @@ void connect(int fd, const std::string & path)
}
-string showBytes(uint64_t bytes)
+std::string showBytes(uint64_t bytes)
{
return fmt("%.2f MiB", bytes / (1024.0 * 1024.0));
}
@@ -1815,7 +1877,7 @@ void commonChildInit(Pipe & logPipe)
{
logger = makeSimpleLogger();
- const static string pathNullDevice = "/dev/null";
+ const static std::string pathNullDevice = "/dev/null";
restoreProcessContext(false);
/* Put the child in a separate session (and thus a separate