#include "util.hh" #include "fmt.hh" #include #include #include #include #include namespace nix { void initLibUtil() { // Check that exception handling works. Exception handling has been observed // not to work on darwin when the linker flags aren't quite right. // In this case we don't want to expose the user to some unrelated uncaught // exception, but rather tell them exactly that exception handling is // broken. // When exception handling fails, the message tends to be printed by the // C++ runtime, followed by an abort. // For example on macOS we might see an error such as // libc++abi: terminating with uncaught exception of type nix::SysError: error: C++ exception handling is broken. This would appear to be a problem with the way Nix was compiled and/or linked and/or loaded. bool caught = false; try { throwExceptionSelfCheck(); } catch (const nix::Error & _e) { caught = true; } // This is not actually the main point of this check, but let's make sure anyway: assert(caught); } ////////////////////////////////////////////////////////////////////// std::vector stringsToCharPtrs(const Strings & ss) { std::vector res; for (auto & s : ss) res.push_back((char *) s.c_str()); res.push_back(0); return res; } ////////////////////////////////////////////////////////////////////// template C tokenizeString(std::string_view s, std::string_view separators) { C result; 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; } template Strings tokenizeString(std::string_view s, std::string_view separators); template StringSet tokenizeString(std::string_view s, std::string_view separators); template std::vector tokenizeString(std::string_view s, std::string_view separators); std::string chomp(std::string_view s) { size_t i = s.find_last_not_of(" \n\r\t"); return i == std::string_view::npos ? "" : std::string(s, 0, i + 1); } std::string trim(std::string_view s, std::string_view whitespace) { auto i = s.find_first_not_of(whitespace); if (i == s.npos) return ""; auto j = s.find_last_not_of(whitespace); return std::string(s, i, j == s.npos ? j : j - i + 1); } std::string replaceStrings( std::string res, std::string_view from, std::string_view to) { if (from.empty()) return res; size_t pos = 0; while ((pos = res.find(from, pos)) != std::string::npos) { res.replace(pos, from.size(), to); pos += to.size(); } return res; } std::string rewriteStrings(std::string s, const StringMap & rewrites) { for (auto & i : rewrites) { if (i.first == i.second) continue; size_t j = 0; while ((j = s.find(i.first, j)) != std::string::npos) s.replace(j, i.first.size(), i.second); } return s; } bool hasPrefix(std::string_view s, std::string_view prefix) { return s.compare(0, prefix.size(), prefix) == 0; } bool hasSuffix(std::string_view s, std::string_view suffix) { return s.size() >= suffix.size() && s.substr(s.size() - suffix.size()) == suffix; } std::string toLower(const std::string & s) { std::string r(s); for (auto & c : r) c = std::tolower(c); return r; } std::string shellEscape(const std::string_view s) { std::string r; r.reserve(s.size() + 2); r += "'"; for (auto & i : s) if (i == '\'') r += "'\\''"; else r += i; r += '\''; return r; } /* Recreate the effect of the perl shellwords function, breaking up a * string into arguments like a shell word, including escapes */ std::vector shellwords2(const std::string & s) { std::regex whitespace("^(\\s+).*"); auto begin = s.cbegin(); std::vector res; std::string cur; enum state { sBegin, sQuote }; state st = sBegin; auto it = begin; for (; it != s.cend(); ++it) { if (st == sBegin) { std::smatch match; if (regex_search(it, s.cend(), match, whitespace)) { cur.append(begin, it); res.push_back(cur); cur.clear(); it = match[1].second; begin = it; } } switch (*it) { case '"': cur.append(begin, it); begin = it + 1; st = st == sBegin ? sQuote : sBegin; break; case '\\': /* perl shellwords mostly just treats the next char as part of the string with no special processing */ cur.append(begin, it); begin = ++it; break; } } cur.append(begin, it); if (!cur.empty()) res.push_back(cur); return res; } void ignoreException(Verbosity lvl) { /* Make sure no exceptions leave this function. printError() also throws when remote is closed. */ try { try { throw; } catch (std::exception & e) { printMsg(lvl, "error (ignored): %1%", e.what()); } } catch (...) { } } constexpr char base64Chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; std::string base64Encode(std::string_view s) { std::string res; res.reserve((s.size() + 2) / 3 * 4); int data = 0, nbits = 0; for (char c : s) { data = data << 8 | (unsigned char) c; nbits += 8; while (nbits >= 6) { nbits -= 6; res.push_back(base64Chars[data >> nbits & 0x3f]); } } if (nbits) res.push_back(base64Chars[data << (6 - nbits) & 0x3f]); while (res.size() % 4) res.push_back('='); return res; } std::string base64Decode(std::string_view s) { constexpr char npos = -1; constexpr std::array base64DecodeChars = [&]() { std::array result{}; for (auto& c : result) c = npos; for (int i = 0; i < 64; i++) result[base64Chars[i]] = i; return result; }(); 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) { if (c == '=') break; if (c == '\n') continue; char digit = base64DecodeChars[(unsigned char) c]; if (digit == npos) throw Error("invalid character in Base64 string: '%c'", c); bits += 6; d = d << 6 | digit; if (bits >= 8) { res.push_back(d >> (bits - 8) & 0xff); bits -= 8; } } return res; } std::string stripIndentation(std::string_view s) { size_t minIndent = 10000; size_t curIndent = 0; bool atStartOfLine = true; for (auto & c : s) { if (atStartOfLine && c == ' ') curIndent++; else if (c == '\n') { if (atStartOfLine) minIndent = std::max(minIndent, curIndent); curIndent = 0; atStartOfLine = true; } else { if (atStartOfLine) { minIndent = std::min(minIndent, curIndent); atStartOfLine = false; } } } std::string res; size_t pos = 0; while (pos < s.size()) { auto eol = s.find('\n', pos); if (eol == s.npos) eol = s.size(); if (eol - pos > minIndent) res.append(s.substr(pos + minIndent, eol - pos - minIndent)); res.push_back('\n'); pos = eol + 1; } return res; } std::pair getLine(std::string_view s) { auto newline = s.find('\n'); if (newline == s.npos) { return {s, ""}; } else { auto line = s.substr(0, newline); if (!line.empty() && line[line.size() - 1] == '\r') line = line.substr(0, line.size() - 1); return {line, s.substr(newline + 1)}; } } std::string showBytes(uint64_t bytes) { return fmt("%.2f MiB", bytes / (1024.0 * 1024.0)); } }