mirror of https://github.com/mamba-org/mamba.git
1557 lines
47 KiB
C++
1557 lines
47 KiB
C++
// Copyright (c) 2019, QuantStack and Mamba Contributors
|
|
//
|
|
// Distributed under the terms of the BSD 3-Clause License.
|
|
//
|
|
// The full license is in the file LICENSE, distributed with this software.
|
|
|
|
|
|
#if defined(__APPLE__) || defined(__linux__)
|
|
#include <csignal>
|
|
|
|
#include <fcntl.h>
|
|
#include <pthread.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
#include <cassert>
|
|
|
|
#include <io.h>
|
|
|
|
extern "C"
|
|
{
|
|
#include <fcntl.h>
|
|
#include <io.h>
|
|
#include <process.h>
|
|
#include <sys/locking.h>
|
|
}
|
|
|
|
#endif
|
|
|
|
#include <cerrno>
|
|
#include <condition_variable>
|
|
#include <cstring>
|
|
#include <cwchar>
|
|
#include <iomanip>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <optional>
|
|
#include <unordered_map>
|
|
|
|
#include <openssl/evp.h>
|
|
|
|
#include "mamba/core/context.hpp"
|
|
#include "mamba/core/environment.hpp"
|
|
#include "mamba/core/execution.hpp"
|
|
#include "mamba/core/fsutil.hpp"
|
|
#include "mamba/core/invoke.hpp"
|
|
#include "mamba/core/output.hpp"
|
|
#include "mamba/core/shell_init.hpp"
|
|
#include "mamba/core/thread_utils.hpp"
|
|
#include "mamba/core/url.hpp"
|
|
#include "mamba/core/util.hpp"
|
|
#include "mamba/core/util_os.hpp"
|
|
#include "mamba/core/util_random.hpp"
|
|
#include "mamba/core/util_string.hpp"
|
|
#include "mamba/util/compare.hpp"
|
|
|
|
namespace mamba
|
|
{
|
|
bool is_package_file(const std::string_view& fn)
|
|
{
|
|
return ends_with(fn, ".tar.bz2") || ends_with(fn, ".conda");
|
|
}
|
|
|
|
// This function returns true even for broken symlinks
|
|
// E.g.
|
|
// ln -s abcdef emptylink
|
|
// fs::exists(emptylink) == false
|
|
// lexists(emptylink) == true
|
|
bool lexists(const fs::u8path& path, std::error_code& ec)
|
|
{
|
|
auto status = fs::symlink_status(path, ec).type();
|
|
if (status != fs::file_type::none)
|
|
{
|
|
ec.clear();
|
|
return status != fs::file_type::not_found || status == fs::file_type::symlink;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool lexists(const fs::u8path& path)
|
|
{
|
|
auto status = fs::symlink_status(path);
|
|
return status.type() != fs::file_type::not_found || status.type() == fs::file_type::symlink;
|
|
}
|
|
|
|
std::vector<fs::u8path> filter_dir(const fs::u8path& dir, const std::string& suffix)
|
|
{
|
|
std::vector<fs::u8path> result;
|
|
if (fs::exists(dir) && fs::is_directory(dir))
|
|
{
|
|
for (const auto& entry : fs::directory_iterator(dir))
|
|
{
|
|
if (suffix.size())
|
|
{
|
|
if (!entry.is_directory() && entry.path().extension() == suffix)
|
|
{
|
|
result.push_back(entry.path());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (entry.is_directory() == false)
|
|
{
|
|
result.push_back(entry.path());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// TODO expand variables, ~ and make absolute
|
|
bool paths_equal(const fs::u8path& lhs, const fs::u8path& rhs)
|
|
{
|
|
return lhs == rhs;
|
|
}
|
|
|
|
TemporaryDirectory::TemporaryDirectory()
|
|
{
|
|
bool success = false;
|
|
#ifndef _WIN32
|
|
std::string template_path = fs::temp_directory_path() / "mambadXXXXXX";
|
|
char* pth = mkdtemp(const_cast<char*>(template_path.c_str()));
|
|
success = (pth != nullptr);
|
|
template_path = pth;
|
|
#else
|
|
const std::string template_path = (fs::temp_directory_path() / "mambadXXXXXX").string();
|
|
// include \0 terminator
|
|
auto err [[maybe_unused]] = _mktemp_s(
|
|
const_cast<char*>(template_path.c_str()),
|
|
template_path.size() + 1
|
|
);
|
|
assert(err == 0);
|
|
success = fs::create_directory(template_path);
|
|
#endif
|
|
if (!success)
|
|
{
|
|
throw std::runtime_error("Could not create temporary directory!");
|
|
}
|
|
else
|
|
{
|
|
m_path = template_path;
|
|
}
|
|
}
|
|
|
|
TemporaryDirectory::~TemporaryDirectory()
|
|
{
|
|
if (!Context::instance().keep_temp_directories)
|
|
{
|
|
fs::remove_all(m_path);
|
|
}
|
|
}
|
|
|
|
const fs::u8path& TemporaryDirectory::path() const
|
|
{
|
|
return m_path;
|
|
}
|
|
|
|
TemporaryDirectory::operator fs::u8path()
|
|
{
|
|
return m_path;
|
|
}
|
|
|
|
TemporaryFile::TemporaryFile(
|
|
const std::string& prefix,
|
|
const std::string& suffix,
|
|
const std::optional<fs::u8path>& dir
|
|
)
|
|
{
|
|
static std::mutex file_creation_mutex;
|
|
|
|
bool success = false;
|
|
fs::u8path final_path;
|
|
fs::u8path temp_path = dir.value_or(fs::temp_directory_path());
|
|
|
|
std::lock_guard<std::mutex> file_creation_lock(file_creation_mutex);
|
|
|
|
do
|
|
{
|
|
std::string random_file_name = mamba::generate_random_alphanumeric_string(10);
|
|
final_path = temp_path / concat(prefix, random_file_name, suffix);
|
|
} while (fs::exists(final_path));
|
|
|
|
try
|
|
{
|
|
std::ofstream f = open_ofstream(final_path);
|
|
f.close();
|
|
success = true;
|
|
}
|
|
catch (...)
|
|
{
|
|
success = false;
|
|
}
|
|
|
|
if (!success)
|
|
{
|
|
throw std::runtime_error("Could not create temporary file!");
|
|
}
|
|
else
|
|
{
|
|
m_path = final_path;
|
|
}
|
|
}
|
|
|
|
TemporaryFile::~TemporaryFile()
|
|
{
|
|
if (!Context::instance().keep_temp_files)
|
|
{
|
|
fs::remove(m_path);
|
|
}
|
|
}
|
|
|
|
fs::u8path& TemporaryFile::path()
|
|
{
|
|
return m_path;
|
|
}
|
|
|
|
TemporaryFile::operator fs::u8path()
|
|
{
|
|
return m_path;
|
|
}
|
|
|
|
std::string read_contents(const fs::u8path& file_path, std::ios::openmode mode)
|
|
{
|
|
std::ifstream in(file_path.std_path(), std::ios::in | mode);
|
|
|
|
if (in)
|
|
{
|
|
std::string contents;
|
|
in.seekg(0, std::ios::end);
|
|
contents.resize(static_cast<std::size_t>(in.tellg()));
|
|
in.seekg(0, std::ios::beg);
|
|
in.read(&contents[0], static_cast<std::streamsize>(contents.size()));
|
|
in.close();
|
|
return (contents);
|
|
}
|
|
else
|
|
{
|
|
throw std::system_error(
|
|
errno,
|
|
std::system_category(),
|
|
"failed to open " + file_path.string()
|
|
);
|
|
}
|
|
}
|
|
|
|
std::vector<std::string> read_lines(const fs::u8path& file_path)
|
|
{
|
|
std::fstream file_stream(file_path.std_path(), std::ios_base::in | std::ios_base::binary);
|
|
if (file_stream.fail())
|
|
{
|
|
throw std::system_error(
|
|
errno,
|
|
std::system_category(),
|
|
"failed to open " + file_path.string()
|
|
);
|
|
}
|
|
|
|
std::vector<std::string> output;
|
|
std::string line;
|
|
while (std::getline(file_stream, line))
|
|
{
|
|
// Remove the trailing \r to accomodate Windows line endings.
|
|
if ((!line.empty()) && (line.back() == '\r'))
|
|
{
|
|
line.pop_back();
|
|
}
|
|
|
|
output.push_back(line);
|
|
}
|
|
file_stream.close();
|
|
|
|
return output;
|
|
}
|
|
|
|
void split_package_extension(const std::string& file, std::string& name, std::string& extension)
|
|
{
|
|
if (ends_with(file, ".conda"))
|
|
{
|
|
name = file.substr(0, file.size() - 6);
|
|
extension = ".conda";
|
|
}
|
|
else if (ends_with(file, ".tar.bz2"))
|
|
{
|
|
name = file.substr(0, file.size() - 8);
|
|
extension = ".tar.bz2";
|
|
}
|
|
else if (ends_with(file, ".json"))
|
|
{
|
|
name = file.substr(0, file.size() - 5);
|
|
extension = ".json";
|
|
}
|
|
else
|
|
{
|
|
name = file;
|
|
extension = "";
|
|
}
|
|
}
|
|
|
|
fs::u8path strip_package_extension(const std::string& file)
|
|
{
|
|
std::string name, extension;
|
|
split_package_extension(file, name, extension);
|
|
|
|
if (extension == "")
|
|
{
|
|
throw std::runtime_error("Cannot strip file extension from: " + file);
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
std::string quote_for_shell(const std::vector<std::string>& arguments, const std::string& shell)
|
|
{
|
|
if ((shell.empty() && on_win) || shell == "cmdexe")
|
|
{
|
|
// ported from CPython's list2cmdline to C++
|
|
//
|
|
// Translate a sequence of arguments into a command line
|
|
// string, using the same rules as the MS C runtime:
|
|
// 1) Arguments are delimited by white space, which is either a
|
|
// space or a tab.
|
|
// 2) A string surrounded by double quotation marks is
|
|
// interpreted as a single argument, regardless of white space
|
|
// contained within. A quoted string can be embedded in an
|
|
// argument.
|
|
// 3) A double quotation mark preceded by a backslash is
|
|
// interpreted as a literal double quotation mark.
|
|
// 4) Backslashes are interpreted literally, unless they
|
|
// immediately precede a double quotation mark.
|
|
// 5) If backslashes immediately precede a double quotation mark,
|
|
// every pair of backslashes is interpreted as a literal
|
|
// backslash. If the number of backslashes is odd, the last
|
|
// backslash escapes the next double quotation mark as
|
|
// described in rule 3.
|
|
// See
|
|
// http://msdn.microsoft.com/en-us/library/17w5ykft.aspx
|
|
// or search http://msdn.microsoft.com for
|
|
// "Parsing C++ Command-Line Arguments"
|
|
std::string result, bs_buf;
|
|
bool need_quote = false;
|
|
for (const auto& arg : arguments)
|
|
{
|
|
bs_buf.clear();
|
|
if (!result.empty())
|
|
{
|
|
// seperate arguments
|
|
result += " ";
|
|
}
|
|
|
|
need_quote = arg.find_first_of(" \t") != arg.npos || arg.empty();
|
|
if (need_quote)
|
|
{
|
|
result += "\"";
|
|
}
|
|
|
|
for (char c : arg)
|
|
{
|
|
if (c == '\\')
|
|
{
|
|
bs_buf += c;
|
|
}
|
|
else if (c == '"')
|
|
{
|
|
result += std::string(bs_buf.size() * 2, '\\');
|
|
bs_buf.clear();
|
|
result += "\\\"";
|
|
}
|
|
else
|
|
{
|
|
if (!bs_buf.empty())
|
|
{
|
|
result += bs_buf;
|
|
bs_buf.clear();
|
|
}
|
|
result += c;
|
|
}
|
|
}
|
|
if (!bs_buf.empty())
|
|
{
|
|
result += bs_buf;
|
|
}
|
|
if (need_quote)
|
|
{
|
|
result += bs_buf;
|
|
result += "\"";
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
else
|
|
{
|
|
// Identical to Python's shlex.quote.
|
|
auto quote_arg = [](const std::string& s)
|
|
{
|
|
if (s.size() == 0)
|
|
{
|
|
return std::string("''");
|
|
}
|
|
std::regex unsafe("[^\\w@%+=:,./-]");
|
|
if (std::regex_search(s, unsafe))
|
|
{
|
|
std::string s2 = s;
|
|
replace_all(s2, "'", "'\"'\"'");
|
|
return concat("'", s2, "'");
|
|
}
|
|
else
|
|
{
|
|
return s;
|
|
}
|
|
};
|
|
|
|
if (arguments.empty())
|
|
{
|
|
return "";
|
|
}
|
|
|
|
std::string argstring;
|
|
argstring += quote_arg(arguments[0]);
|
|
for (std::size_t i = 1; i < arguments.size(); ++i)
|
|
{
|
|
argstring += " ";
|
|
argstring += quote_arg(arguments[i]);
|
|
}
|
|
return argstring;
|
|
}
|
|
}
|
|
|
|
std::size_t clean_trash_files(const fs::u8path& prefix, bool deep_clean)
|
|
{
|
|
std::size_t deleted_files = 0;
|
|
std::size_t remainig_trash = 0;
|
|
std::error_code ec;
|
|
std::vector<fs::u8path> remaining_files;
|
|
auto trash_txt = prefix / "conda-meta" / "mamba_trash.txt";
|
|
if (!deep_clean && fs::exists(trash_txt))
|
|
{
|
|
auto all_files = read_lines(trash_txt);
|
|
for (auto& f : all_files)
|
|
{
|
|
fs::u8path full_path = prefix / f;
|
|
LOG_INFO << "Trash: removing " << full_path;
|
|
if (!fs::exists(full_path) || fs::remove(full_path, ec))
|
|
{
|
|
deleted_files += 1;
|
|
}
|
|
else
|
|
{
|
|
LOG_INFO << "Trash: could not remove " << full_path;
|
|
remainig_trash += 1;
|
|
// save relative path
|
|
remaining_files.push_back(f);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (deep_clean)
|
|
{
|
|
// recursive iterate over all files and delete `.mamba_trash` files
|
|
std::vector<fs::u8path> f_to_rm;
|
|
for (auto& p : fs::recursive_directory_iterator(prefix))
|
|
{
|
|
if (p.path().extension() == ".mamba_trash")
|
|
{
|
|
f_to_rm.push_back(p.path());
|
|
}
|
|
}
|
|
for (auto& p : f_to_rm)
|
|
{
|
|
LOG_INFO << "Trash: removing " << p;
|
|
if (fs::remove(p, ec))
|
|
{
|
|
deleted_files += 1;
|
|
}
|
|
else
|
|
{
|
|
remainig_trash += 1;
|
|
// save relative path
|
|
remaining_files.push_back(fs::relative(p, prefix));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (remaining_files.empty())
|
|
{
|
|
fs::remove(trash_txt, ec);
|
|
}
|
|
else
|
|
{
|
|
auto trash_out_file = open_ofstream(
|
|
trash_txt,
|
|
std::ios::out | std::ios::binary | std::ios::trunc
|
|
);
|
|
for (auto& rf : remaining_files)
|
|
{
|
|
trash_out_file << rf.string() << "\n";
|
|
}
|
|
}
|
|
|
|
LOG_INFO << "Cleaned " << deleted_files << " .mamba_trash files. " << remainig_trash
|
|
<< " remaining.";
|
|
return deleted_files;
|
|
}
|
|
|
|
std::size_t remove_or_rename(const fs::u8path& path)
|
|
{
|
|
std::error_code ec;
|
|
std::size_t result = 0;
|
|
if (!lexists(path, ec))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (fs::is_directory(path, ec))
|
|
{
|
|
result = fs::remove_all(path, ec);
|
|
}
|
|
else
|
|
{
|
|
result = fs::remove(path, ec);
|
|
}
|
|
|
|
if (ec)
|
|
{
|
|
int counter = 0;
|
|
|
|
// we should only attempt writing to the trash index file from one thread at a time
|
|
static std::mutex trash_mutex;
|
|
std::lock_guard<std::mutex> guard(trash_mutex);
|
|
|
|
while (ec)
|
|
{
|
|
LOG_INFO << "Caught a filesystem error for '" << path.string()
|
|
<< "':" << ec.message() << " (File in use?)";
|
|
fs::u8path trash_file = path;
|
|
std::size_t fcounter = 0;
|
|
|
|
trash_file.replace_extension(concat(trash_file.extension().string(), ".mamba_trash"));
|
|
while (lexists(trash_file))
|
|
{
|
|
trash_file = path;
|
|
trash_file.replace_extension(
|
|
concat(trash_file.extension().string(), std::to_string(fcounter), ".mamba_trash")
|
|
);
|
|
fcounter += 1;
|
|
if (fcounter > 100)
|
|
{
|
|
throw std::runtime_error("Too many existing trash files. Please force clean");
|
|
}
|
|
}
|
|
fs::rename(path, trash_file, ec);
|
|
if (!ec)
|
|
{
|
|
// The conda-meta directory is locked by transaction execute
|
|
auto trash_index = open_ofstream(
|
|
Context::instance().target_prefix / "conda-meta" / "mamba_trash.txt",
|
|
std::ios::app | std::ios::binary
|
|
);
|
|
|
|
// TODO add some unicode tests here?
|
|
trash_index << fs::relative(trash_file, Context::instance().target_prefix).string()
|
|
<< "\n";
|
|
return 1;
|
|
}
|
|
|
|
// this is some exponential back off
|
|
counter += 1;
|
|
LOG_ERROR << "Trying to remove " << path << ": " << ec.message()
|
|
<< " (file in use?). Sleeping for " << counter * 2 << "s";
|
|
if (counter > 3)
|
|
{
|
|
throw std::runtime_error(concat("Could not delete file ", path.string()));
|
|
}
|
|
std::this_thread::sleep_for(std::chrono::seconds(counter * 2));
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::string unindent(const char* p)
|
|
{
|
|
std::string result;
|
|
if (*p == '\n')
|
|
{
|
|
++p;
|
|
}
|
|
const char* p_leading = p;
|
|
while (std::isspace(*p) && *p != '\n')
|
|
{
|
|
++p;
|
|
}
|
|
std::size_t leading_len = static_cast<std::size_t>(p - p_leading);
|
|
while (*p)
|
|
{
|
|
result += *p;
|
|
if (*p++ == '\n')
|
|
{
|
|
for (std::size_t i = 0; i < leading_len; ++i)
|
|
{
|
|
if (p[i] != p_leading[i])
|
|
{
|
|
goto dont_skip_leading;
|
|
}
|
|
}
|
|
p += leading_len;
|
|
}
|
|
dont_skip_leading:;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::string prepend(const char* p, const char* start, const char* newline)
|
|
{
|
|
std::string result;
|
|
|
|
result += start;
|
|
while (*p)
|
|
{
|
|
result += *p;
|
|
if (*p++ == '\n')
|
|
{
|
|
result += newline;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::string prepend(const std::string& p, const char* start, const char* newline)
|
|
{
|
|
return prepend(p.c_str(), start, newline);
|
|
}
|
|
|
|
|
|
class LockFileOwner
|
|
{
|
|
public:
|
|
|
|
explicit LockFileOwner(const fs::u8path& file_path, const std::chrono::seconds timeout);
|
|
~LockFileOwner();
|
|
|
|
LockFileOwner(const LockFileOwner&) = delete;
|
|
LockFileOwner& operator=(const LockFileOwner&) = delete;
|
|
LockFileOwner(LockFileOwner&&) = delete;
|
|
LockFileOwner& operator=(LockFileOwner&&) = delete;
|
|
|
|
bool set_fd_lock(bool blocking) const;
|
|
bool lock_non_blocking();
|
|
bool lock_blocking();
|
|
bool lock(bool blocking) const;
|
|
|
|
void remove_lockfile() noexcept;
|
|
int close_fd();
|
|
bool unlock();
|
|
|
|
int fd() const
|
|
{
|
|
return m_fd;
|
|
}
|
|
|
|
fs::u8path path() const
|
|
{
|
|
return m_path;
|
|
}
|
|
|
|
fs::u8path lockfile_path() const
|
|
{
|
|
return m_lockfile_path;
|
|
}
|
|
|
|
private:
|
|
|
|
fs::u8path m_path;
|
|
fs::u8path m_lockfile_path;
|
|
std::chrono::seconds m_timeout;
|
|
int m_fd = -1;
|
|
bool m_locked;
|
|
bool m_lockfile_existed;
|
|
|
|
template <typename Func = no_op>
|
|
void throw_lock_error(std::string error_message, Func before_throw_task = no_op{}) const
|
|
{
|
|
auto complete_error_message = fmt::format(
|
|
"LockFile acquisition failed, aborting: {}",
|
|
std::move(error_message)
|
|
);
|
|
LOG_ERROR << error_message;
|
|
safe_invoke(before_throw_task)
|
|
.map_error([](const auto& error)
|
|
{ LOG_ERROR << "While handling LockFile failure: " << error.what(); });
|
|
throw mamba_error(complete_error_message, mamba_error_code::lockfile_failure);
|
|
}
|
|
};
|
|
|
|
LockFileOwner::LockFileOwner(const fs::u8path& path, const std::chrono::seconds timeout)
|
|
: m_path(path)
|
|
, m_timeout(timeout)
|
|
, m_locked(false)
|
|
{
|
|
std::error_code ec;
|
|
if (!fs::exists(path, ec))
|
|
{
|
|
throw_lock_error(fmt::format("Could not lock non-existing path '{}'", path.string()));
|
|
}
|
|
|
|
if (fs::is_directory(path))
|
|
{
|
|
LOG_DEBUG << "Locking directory '" << path.string() << "'";
|
|
m_lockfile_path = m_path / (m_path.filename().string() + ".lock");
|
|
}
|
|
else
|
|
{
|
|
LOG_DEBUG << "Locking file '" << path.string() << "'";
|
|
m_lockfile_path = m_path.string() + ".lock";
|
|
}
|
|
|
|
m_lockfile_existed = fs::exists(m_lockfile_path, ec);
|
|
#ifdef _WIN32
|
|
m_fd = _wopen(m_lockfile_path.wstring().c_str(), O_RDWR | O_CREAT, 0666);
|
|
#else
|
|
m_fd = open(m_lockfile_path.string().c_str(), O_RDWR | O_CREAT, 0666);
|
|
#endif
|
|
if (m_fd <= 0)
|
|
{
|
|
throw_lock_error(
|
|
fmt::format("Could not open lockfile '{}'", m_lockfile_path.string()),
|
|
[this] { unlock(); }
|
|
);
|
|
}
|
|
else
|
|
{
|
|
if ((m_locked = lock_non_blocking()) == false)
|
|
{
|
|
LOG_WARNING << "Cannot lock '" << m_path.string() << "'"
|
|
<< "\nWaiting for other mamba process to finish";
|
|
|
|
m_locked = lock_blocking();
|
|
}
|
|
|
|
if (m_locked)
|
|
{
|
|
LOG_TRACE << "Lockfile created at '" << m_lockfile_path.string() << "'";
|
|
LOG_DEBUG << "Successfully locked";
|
|
}
|
|
else
|
|
{
|
|
throw_lock_error(
|
|
fmt::format(
|
|
"LockFile can't be set at '{}'\n"
|
|
"This could be fixed by changing the locks' timeout or "
|
|
"cleaning your environment from previous runs",
|
|
m_path.string()
|
|
),
|
|
[this] { unlock(); }
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
LockFileOwner::~LockFileOwner()
|
|
{
|
|
LOG_DEBUG << "Unlocking '" << m_path.string() << "'";
|
|
unlock();
|
|
}
|
|
|
|
void LockFileOwner::remove_lockfile() noexcept
|
|
{
|
|
close_fd();
|
|
|
|
if (!m_lockfile_existed)
|
|
{
|
|
std::error_code ec;
|
|
LOG_TRACE << "Removing file '" << m_lockfile_path.string() << "'";
|
|
fs::remove(m_lockfile_path, ec);
|
|
|
|
if (ec)
|
|
{
|
|
LOG_ERROR << "Removing lock file '" << m_lockfile_path.string() << "' failed\n"
|
|
<< "You may need to remove it manually";
|
|
}
|
|
}
|
|
}
|
|
|
|
int LockFileOwner::close_fd()
|
|
{
|
|
int ret = 0;
|
|
if (m_fd > -1)
|
|
{
|
|
ret = close(m_fd);
|
|
m_fd = -1;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool LockFileOwner::unlock()
|
|
{
|
|
int ret = 0;
|
|
|
|
// POSIX systems automatically remove locks when closing any file
|
|
// descriptor related to the file
|
|
#ifdef _WIN32
|
|
LOG_TRACE << "Removing lock on '" << m_lockfile_path.string() << "'";
|
|
_lseek(m_fd, MAMBA_LOCK_POS, SEEK_SET);
|
|
ret = _locking(m_fd, LK_UNLCK, 1 /*lock_file_contents_length()*/);
|
|
#endif
|
|
remove_lockfile();
|
|
return ret == 0;
|
|
}
|
|
|
|
#ifndef _WIN32
|
|
int timedout_set_fd_lock(int fd, struct flock& lock, const std::chrono::seconds timeout)
|
|
{
|
|
int ret;
|
|
std::mutex m;
|
|
std::condition_variable cv;
|
|
|
|
thread t(
|
|
[&cv, &ret, &fd, &lock]()
|
|
{
|
|
ret = fcntl(fd, F_SETLKW, &lock);
|
|
cv.notify_one();
|
|
}
|
|
);
|
|
|
|
auto th = t.native_handle();
|
|
|
|
int err = 0;
|
|
set_signal_handler(
|
|
[&th, &cv, &ret, &err](sigset_t sigset) -> int
|
|
{
|
|
int signum = 0;
|
|
sigwait(&sigset, &signum);
|
|
pthread_cancel(th);
|
|
err = EINTR;
|
|
ret = -1;
|
|
cv.notify_one();
|
|
return signum;
|
|
}
|
|
);
|
|
|
|
MainExecutor::instance().take_ownership(t.extract());
|
|
|
|
{
|
|
std::unique_lock<std::mutex> l(m);
|
|
if (cv.wait_for(l, timeout) == std::cv_status::timeout)
|
|
{
|
|
pthread_cancel(th);
|
|
kill_receiver_thread();
|
|
err = EINTR;
|
|
ret = -1;
|
|
}
|
|
}
|
|
set_default_signal_handler();
|
|
errno = err;
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
bool LockFileOwner::set_fd_lock(bool blocking) const
|
|
{
|
|
int ret = 0;
|
|
#ifdef _WIN32
|
|
_lseek(m_fd, MAMBA_LOCK_POS, SEEK_SET);
|
|
|
|
if (blocking)
|
|
{
|
|
static constexpr auto default_timeout = std::chrono::seconds(30);
|
|
const auto timeout = m_timeout > std::chrono::seconds::zero() ? m_timeout
|
|
: default_timeout;
|
|
const auto begin_time = std::chrono::system_clock::now();
|
|
while ((std::chrono::system_clock::now() - begin_time) < timeout)
|
|
{
|
|
ret = _locking(m_fd, LK_NBLCK, 1 /*lock_file_contents_length()*/);
|
|
if (ret == 0)
|
|
{
|
|
break;
|
|
}
|
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
|
}
|
|
|
|
if (ret != 0)
|
|
{
|
|
errno = EINTR;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ret = _locking(m_fd, LK_NBLCK, 1 /*lock_file_contents_length()*/);
|
|
}
|
|
#else
|
|
struct flock lock;
|
|
lock.l_type = F_WRLCK;
|
|
lock.l_whence = SEEK_SET;
|
|
lock.l_start = MAMBA_LOCK_POS;
|
|
lock.l_len = 1;
|
|
|
|
if (blocking)
|
|
{
|
|
if (m_timeout.count())
|
|
{
|
|
ret = timedout_set_fd_lock(m_fd, lock, m_timeout);
|
|
}
|
|
else
|
|
{
|
|
ret = fcntl(m_fd, F_SETLKW, &lock);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ret = fcntl(m_fd, F_SETLK, &lock);
|
|
}
|
|
#endif
|
|
return ret == 0;
|
|
}
|
|
|
|
bool LockFileOwner::lock(bool blocking) const
|
|
{
|
|
if (!set_fd_lock(blocking))
|
|
{
|
|
LOG_ERROR << "Could not set lock (" << strerror(errno) << ")";
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool LockFileOwner::lock_blocking()
|
|
{
|
|
return lock(true);
|
|
}
|
|
|
|
namespace
|
|
{
|
|
|
|
void log_duplicate_lockfile_in_process(const fs::u8path& path)
|
|
{
|
|
LOG_DEBUG << "Path already locked by the same process: '" << fs::absolute(path).string()
|
|
<< "'";
|
|
}
|
|
|
|
bool is_lockfile_locked(const LockFileOwner& lockfile)
|
|
{
|
|
#ifdef _WIN32
|
|
return LockFile::is_locked(lockfile.lockfile_path());
|
|
#else
|
|
// Opening a new file descriptor on Unix would clear locks
|
|
return LockFile::is_locked(lockfile.fd());
|
|
#endif
|
|
}
|
|
|
|
class LockedFilesRegistry
|
|
{
|
|
public:
|
|
|
|
LockedFilesRegistry() = default;
|
|
LockedFilesRegistry(LockedFilesRegistry&&) = delete;
|
|
LockedFilesRegistry(const LockedFilesRegistry&) = delete;
|
|
LockedFilesRegistry& operator=(LockedFilesRegistry&&) = delete;
|
|
LockedFilesRegistry& operator=(const LockedFilesRegistry&) = delete;
|
|
|
|
|
|
tl::expected<std::shared_ptr<LockFileOwner>, mamba_error>
|
|
acquire_lock(const fs::u8path& file_path, const std::chrono::seconds timeout)
|
|
{
|
|
if (!Context::instance().use_lockfiles)
|
|
{
|
|
// No locking allowed, so do nothing.
|
|
return std::shared_ptr<LockFileOwner>{};
|
|
}
|
|
|
|
const auto absolute_file_path = fs::absolute(file_path);
|
|
std::scoped_lock lock{ mutex };
|
|
|
|
const auto it = locked_files.find(absolute_file_path);
|
|
if (it != locked_files.end())
|
|
{
|
|
if (auto lockedfile = it->second.lock())
|
|
{
|
|
log_duplicate_lockfile_in_process(absolute_file_path);
|
|
return lockedfile;
|
|
}
|
|
}
|
|
|
|
// At this point, we didn't find a lockfile alive, so we create one.
|
|
return safe_invoke(
|
|
[&]
|
|
{
|
|
auto lockedfile = std::make_shared<LockFileOwner>(absolute_file_path, timeout);
|
|
auto tracker = std::weak_ptr{ lockedfile };
|
|
locked_files.insert_or_assign(absolute_file_path, std::move(tracker));
|
|
fd_to_locked_path.insert_or_assign(lockedfile->fd(), absolute_file_path);
|
|
assert(is_lockfile_locked(*lockedfile));
|
|
return lockedfile;
|
|
}
|
|
);
|
|
}
|
|
|
|
// note: the resulting value will be obsolete before returning.
|
|
bool is_locked(const fs::u8path& file_path) const
|
|
{
|
|
const auto absolute_file_path = fs::absolute(file_path);
|
|
std::scoped_lock lock{ mutex };
|
|
auto it = locked_files.find(file_path);
|
|
if (it != locked_files.end())
|
|
{
|
|
return !it->second.expired();
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// note: the resulting value will be obsolete before returning.
|
|
bool is_locked(int fd) const
|
|
{
|
|
std::scoped_lock lock{ mutex };
|
|
const auto it = fd_to_locked_path.find(fd);
|
|
if (it != fd_to_locked_path.end())
|
|
{
|
|
return is_locked(it->second);
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private:
|
|
|
|
// TODO: replace by something like boost::multiindex or equivalent to avoid having to
|
|
// handle 2 hashmaps
|
|
std::unordered_map<fs::u8path, std::weak_ptr<LockFileOwner>> locked_files; // TODO:
|
|
// consider
|
|
// replacing
|
|
// by real
|
|
// concurrent
|
|
// set to
|
|
// avoid
|
|
// having to
|
|
// lock the
|
|
// whole
|
|
// container
|
|
|
|
std::unordered_map<int, fs::u8path> fd_to_locked_path; // this is a workaround the
|
|
// usage of file descriptors on
|
|
// linux instead of paths
|
|
mutable std::recursive_mutex mutex; // TODO: replace by synchronized_value once
|
|
// available
|
|
};
|
|
|
|
static LockedFilesRegistry files_locked_by_this_process;
|
|
}
|
|
|
|
bool LockFileOwner::lock_non_blocking()
|
|
{
|
|
if (files_locked_by_this_process.is_locked(m_lockfile_path))
|
|
{
|
|
log_duplicate_lockfile_in_process(m_lockfile_path);
|
|
return true;
|
|
}
|
|
return lock(false);
|
|
}
|
|
|
|
LockFile::~LockFile() = default;
|
|
LockFile::LockFile(LockFile&&) = default;
|
|
LockFile& LockFile::operator=(LockFile&&) = default;
|
|
|
|
LockFile::LockFile(const fs::u8path& path, const std::chrono::seconds& timeout)
|
|
: impl{ files_locked_by_this_process.acquire_lock(path, timeout) }
|
|
{
|
|
}
|
|
|
|
LockFile::LockFile(const fs::u8path& path)
|
|
: LockFile(path, std::chrono::seconds(Context::instance().lock_timeout))
|
|
{
|
|
}
|
|
|
|
int LockFile::fd() const
|
|
{
|
|
return impl.value()->fd();
|
|
}
|
|
|
|
fs::u8path LockFile::path() const
|
|
{
|
|
return impl.value()->path();
|
|
}
|
|
|
|
fs::u8path LockFile::lockfile_path() const
|
|
{
|
|
return impl.value()->lockfile_path();
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
bool LockFile::is_locked(const fs::u8path& path)
|
|
{
|
|
// Windows locks are isolated between file descriptor
|
|
// We can then test if locked by opening a new one
|
|
int fd = _wopen(path.wstring().c_str(), O_RDWR | O_CREAT, 0666);
|
|
if (fd == -1)
|
|
{
|
|
if (errno == EACCES)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// In other cases, something is wrong.
|
|
throw mamba_error{ fmt::format("failed to check if path is locked : '{}'", path.string()),
|
|
mamba_error_code::lockfile_failure };
|
|
}
|
|
_lseek(fd, MAMBA_LOCK_POS, SEEK_SET);
|
|
char buffer[1];
|
|
bool is_locked = _read(fd, buffer, 1) == -1;
|
|
_close(fd);
|
|
return is_locked;
|
|
}
|
|
#endif
|
|
|
|
#ifndef _WIN32
|
|
bool LockFile::is_locked(int fd)
|
|
{
|
|
// UNIX/POSIX record locks can't be checked from current process: opening
|
|
// then closing a new file descriptor would unset the locks
|
|
// 1. compare owner PID written in lockfile with current PID
|
|
// 2. call fcntl called with F_GETLK
|
|
// -> log an error if fcntl return a different owner PID vs lockfile content
|
|
|
|
// Warning:
|
|
// If called from the same process as the lockfile one and PID written in
|
|
// file is corrupted, the result is a false negative
|
|
|
|
// Note: don't use on Windows
|
|
// On Windows, if called from the same process, with the lockfile file descriptor
|
|
// and PID written in lockfile is corrupted, the result would be a false negative
|
|
|
|
// Here we replaced the pid check by tracking internally if we did or not lock
|
|
// the file.
|
|
if (files_locked_by_this_process.is_locked(fd))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
const auto this_process_pid = getpid();
|
|
|
|
struct flock lock;
|
|
lock.l_type = F_WRLCK;
|
|
lock.l_whence = SEEK_SET;
|
|
lock.l_start = MAMBA_LOCK_POS;
|
|
lock.l_len = 1;
|
|
auto result = fcntl(fd, F_GETLK, &lock);
|
|
|
|
if ((lock.l_type == F_UNLCK) && (this_process_pid != lock.l_pid))
|
|
{
|
|
LOG_ERROR << "LockFile file has wrong owner PID " << this_process_pid << ", actual is "
|
|
<< lock.l_pid;
|
|
}
|
|
|
|
return lock.l_type != F_UNLCK && result != -1;
|
|
}
|
|
#endif
|
|
|
|
|
|
std::string timestamp(const std::time_t& utc_time)
|
|
{
|
|
char buf[sizeof("2011-10-08T07:07:09Z")];
|
|
strftime(buf, sizeof(buf), "%FT%TZ", gmtime(&utc_time));
|
|
return buf;
|
|
}
|
|
|
|
std::time_t utc_time_now()
|
|
{
|
|
std::time_t now;
|
|
std::time(&now);
|
|
gmtime(&now);
|
|
return now;
|
|
}
|
|
|
|
std::string utc_timestamp_now()
|
|
{
|
|
return timestamp(utc_time_now());
|
|
}
|
|
|
|
std::time_t parse_utc_timestamp(const std::string& timestamp, int& error_code) noexcept
|
|
{
|
|
error_code = 0;
|
|
std::tm tt = {};
|
|
|
|
if (sscanf(
|
|
timestamp.data(),
|
|
"%04d-%02d-%02dT%02d:%02d:%02dZ",
|
|
&tt.tm_year,
|
|
&tt.tm_mon,
|
|
&tt.tm_mday,
|
|
&tt.tm_hour,
|
|
&tt.tm_min,
|
|
&tt.tm_sec
|
|
)
|
|
!= 6)
|
|
{
|
|
error_code = 1;
|
|
return -1;
|
|
}
|
|
|
|
tt.tm_mon -= 1;
|
|
tt.tm_year -= 1900;
|
|
tt.tm_isdst = -1;
|
|
return mktime(&tt);
|
|
}
|
|
|
|
std::time_t parse_utc_timestamp(const std::string& timestamp)
|
|
{
|
|
int errc = 0;
|
|
auto res = parse_utc_timestamp(timestamp, errc);
|
|
if (errc != 0)
|
|
{
|
|
LOG_ERROR << "Error , should be '2011-10-08T07:07:09Z' (ISO8601), but is: '"
|
|
<< timestamp << "'";
|
|
throw std::runtime_error("Timestamp format error. Aborting");
|
|
}
|
|
return res;
|
|
}
|
|
|
|
bool ensure_comspec_set()
|
|
{
|
|
std::string cmd_exe = env::get("COMSPEC").value_or("");
|
|
if (!ends_with(to_lower(cmd_exe), "cmd.exe"))
|
|
{
|
|
cmd_exe = (fs::u8path(env::get("SystemRoot").value_or("")) / "System32" / "cmd.exe").string();
|
|
if (!fs::is_regular_file(cmd_exe))
|
|
{
|
|
cmd_exe = (fs::u8path(env::get("windir").value_or("")) / "System32" / "cmd.exe").string();
|
|
}
|
|
if (!fs::is_regular_file(cmd_exe))
|
|
{
|
|
LOG_WARNING << "cmd.exe could not be found. Looked in SystemRoot and "
|
|
"windir env vars.";
|
|
}
|
|
else
|
|
{
|
|
env::set("COMSPEC", cmd_exe);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::ofstream open_ofstream(const fs::u8path& path, std::ios::openmode mode)
|
|
{
|
|
std::ofstream outfile(path.std_path(), mode);
|
|
|
|
if (!outfile.good())
|
|
{
|
|
LOG_ERROR << "Error opening for writing " << path << ": " << strerror(errno);
|
|
}
|
|
|
|
return outfile;
|
|
}
|
|
|
|
std::ifstream open_ifstream(const fs::u8path& path, std::ios::openmode mode)
|
|
{
|
|
std::ifstream infile(path.std_path(), mode);
|
|
if (!infile.good())
|
|
{
|
|
LOG_ERROR << "Error opening for reading " << path << ": " << strerror(errno);
|
|
}
|
|
|
|
return infile;
|
|
}
|
|
|
|
|
|
std::unique_ptr<TemporaryFile> wrap_call(
|
|
const fs::u8path& root_prefix,
|
|
const fs::u8path& prefix,
|
|
bool dev_mode,
|
|
bool debug_wrapper_scripts,
|
|
const std::vector<std::string>& arguments
|
|
)
|
|
{
|
|
// todo add abspath here
|
|
fs::u8path tmp_prefix = prefix / ".tmp";
|
|
|
|
#ifdef _WIN32
|
|
ensure_comspec_set();
|
|
std::string conda_bat;
|
|
|
|
// TODO
|
|
std::string CONDA_PACKAGE_ROOT = "";
|
|
|
|
std::string bat_name = Context::instance().is_micromamba ? "micromamba.bat" : "conda.bat";
|
|
|
|
if (dev_mode)
|
|
{
|
|
conda_bat = (fs::u8path(CONDA_PACKAGE_ROOT) / "shell" / "condabin" / "conda.bat").string();
|
|
}
|
|
else
|
|
{
|
|
conda_bat = env::get("CONDA_BAT")
|
|
.value_or((fs::absolute(root_prefix) / "condabin" / bat_name).string());
|
|
}
|
|
if (!fs::exists(conda_bat) && Context::instance().is_micromamba)
|
|
{
|
|
// this adds in the needed .bat files for activation
|
|
init_root_prefix_cmdexe(Context::instance().root_prefix);
|
|
}
|
|
|
|
auto tf = std::make_unique<TemporaryFile>("mamba_bat_", ".bat");
|
|
|
|
std::ofstream out = open_ofstream(tf->path());
|
|
|
|
std::string silencer = debug_wrapper_scripts ? "" : "@";
|
|
|
|
out << silencer << "ECHO OFF\n";
|
|
out << silencer << "SET PYTHONIOENCODING=utf-8\n";
|
|
out << silencer << "SET PYTHONUTF8=1\n";
|
|
out << silencer
|
|
<< "FOR /F \"tokens=2 delims=:.\" %%A in (\'chcp\') do for %%B in (%%A) "
|
|
"do set \"_CONDA_OLD_CHCP=%%B\"\n";
|
|
out << silencer << "chcp 65001 > NUL\n";
|
|
|
|
if (dev_mode)
|
|
{
|
|
// from conda.core.initialize import CONDA_PACKAGE_ROOT
|
|
out << silencer << "SET CONDA_DEV=1\n";
|
|
// In dev mode, conda is really:
|
|
// 'python -m conda'
|
|
// *with* PYTHONPATH set.
|
|
out << silencer << "SET PYTHONPATH=" << CONDA_PACKAGE_ROOT << "\n";
|
|
out << silencer << "SET CONDA_EXE="
|
|
<< "python.exe"
|
|
<< "\n"; // TODO this should be `sys.executable`
|
|
out << silencer << "SET _CE_M=-m\n";
|
|
out << silencer << "SET _CE_CONDA=conda\n";
|
|
}
|
|
|
|
if (debug_wrapper_scripts)
|
|
{
|
|
out << "echo *** environment before *** 1>&2\n";
|
|
out << "SET 1>&2\n";
|
|
}
|
|
|
|
out << silencer << "CALL \"" << conda_bat << "\" activate " << prefix << "\n";
|
|
out << silencer << "IF %ERRORLEVEL% NEQ 0 EXIT /b %ERRORLEVEL%\n";
|
|
|
|
if (debug_wrapper_scripts)
|
|
{
|
|
out << "echo *** environment after *** 1>&2\n";
|
|
out << "SET 1>&2\n";
|
|
}
|
|
#else
|
|
auto tf = std::make_unique<TemporaryFile>();
|
|
std::ofstream out = open_ofstream(tf->path());
|
|
std::stringstream hook_quoted;
|
|
|
|
std::string shebang, dev_arg;
|
|
|
|
if (!Context::instance().is_micromamba)
|
|
{
|
|
// During tests, we sometimes like to have a temp env with e.g. an old python
|
|
// in it and have it run tests against the very latest development sources.
|
|
// For that to work we need extra smarts here, we want it to be instead:
|
|
if (dev_mode)
|
|
{
|
|
shebang += std::string(root_prefix / "bin" / "python");
|
|
shebang += " -m conda";
|
|
|
|
dev_arg = "--dev";
|
|
}
|
|
else
|
|
{
|
|
if (std::getenv("CONDA_EXE"))
|
|
{
|
|
shebang = std::getenv("CONDA_EXE");
|
|
}
|
|
else
|
|
{
|
|
shebang = std::string(root_prefix / "bin" / "conda");
|
|
}
|
|
}
|
|
|
|
if (dev_mode)
|
|
{
|
|
// out << ">&2 export PYTHONPATH=" << CONDA_PACKAGE_ROOT << "\n";
|
|
}
|
|
|
|
hook_quoted << std::quoted(shebang, '\'') << " 'shell.posix' 'hook' " << dev_arg;
|
|
}
|
|
else
|
|
{
|
|
// Micromamba hook
|
|
out << "export MAMBA_EXE=" << std::quoted(get_self_exe_path().string(), '\'') << "\n";
|
|
hook_quoted << "$MAMBA_EXE 'shell' 'hook' '-s' 'bash' '-p' "
|
|
<< std::quoted(Context::instance().root_prefix.string(), '\'');
|
|
}
|
|
if (debug_wrapper_scripts)
|
|
{
|
|
out << "set -x\n";
|
|
out << ">&2 echo \"*** environment before ***\"\n"
|
|
<< ">&2 env\n"
|
|
<< ">&2 echo \"$(" << hook_quoted.str() << ")\"\n";
|
|
}
|
|
out << "eval \"$(" << hook_quoted.str() << ")\"\n";
|
|
|
|
if (!Context::instance().is_micromamba)
|
|
{
|
|
out << "conda activate " << dev_arg << " " << std::quoted(prefix.string()) << "\n";
|
|
}
|
|
else
|
|
{
|
|
out << "micromamba activate " << std::quoted(prefix.string()) << "\n";
|
|
}
|
|
|
|
|
|
if (debug_wrapper_scripts)
|
|
{
|
|
out << ">&2 echo \"*** environment after ***\"\n"
|
|
<< ">&2 env\n";
|
|
}
|
|
#endif
|
|
// write our command
|
|
out << "\n" << quote_for_shell(arguments);
|
|
return tf;
|
|
}
|
|
|
|
std::tuple<std::vector<std::string>, std::unique_ptr<TemporaryFile>>
|
|
prepare_wrapped_call(const fs::u8path& prefix, const std::vector<std::string>& cmd)
|
|
{
|
|
std::vector<std::string> command_args;
|
|
std::unique_ptr<TemporaryFile> script_file;
|
|
|
|
if (on_win)
|
|
{
|
|
ensure_comspec_set();
|
|
auto comspec = env::get("COMSPEC");
|
|
if (!comspec)
|
|
{
|
|
throw std::runtime_error(concat("Failed to run script: COMSPEC not set in env vars."));
|
|
}
|
|
|
|
script_file = wrap_call(
|
|
Context::instance().root_prefix,
|
|
prefix,
|
|
Context::instance().dev,
|
|
false,
|
|
cmd
|
|
);
|
|
|
|
command_args = { comspec.value(), "/D", "/C", script_file->path().string() };
|
|
}
|
|
else
|
|
{
|
|
// shell_path = 'sh' if 'bsd' in sys.platform else 'bash'
|
|
fs::u8path shell_path = env::which("bash");
|
|
if (shell_path.empty())
|
|
{
|
|
shell_path = env::which("sh");
|
|
}
|
|
if (shell_path.empty())
|
|
{
|
|
LOG_ERROR << "Failed to find a shell to run the script with.";
|
|
shell_path = "sh";
|
|
}
|
|
|
|
script_file = wrap_call(
|
|
Context::instance().root_prefix,
|
|
prefix,
|
|
Context::instance().dev,
|
|
false,
|
|
cmd
|
|
);
|
|
command_args.push_back(shell_path.string());
|
|
command_args.push_back(script_file->path().string());
|
|
}
|
|
return std::make_tuple(command_args, std::move(script_file));
|
|
}
|
|
|
|
bool is_yaml_file_name(std::string_view filename)
|
|
{
|
|
return ends_with(filename, ".yml") || ends_with(filename, ".yaml");
|
|
}
|
|
|
|
tl::expected<std::string, mamba_error> encode_base64(const std::string_view& input)
|
|
{
|
|
const auto pl = 4 * ((input.size() + 2) / 3);
|
|
std::vector<unsigned char> output(pl + 1);
|
|
const auto ol = EVP_EncodeBlock(
|
|
output.data(),
|
|
reinterpret_cast<const unsigned char*>(input.data()),
|
|
static_cast<int>(input.size())
|
|
);
|
|
|
|
if (util::cmp_not_equal(pl, ol))
|
|
{
|
|
return make_unexpected("Could not encode base64 string", mamba_error_code::openssl_failed);
|
|
}
|
|
|
|
return std::string(reinterpret_cast<const char*>(output.data()));
|
|
}
|
|
|
|
tl::expected<std::string, mamba_error> decode_base64(const std::string_view& input)
|
|
{
|
|
const auto pl = 3 * input.size() / 4;
|
|
|
|
std::vector<unsigned char> output(pl + 1);
|
|
const auto ol = EVP_DecodeBlock(
|
|
output.data(),
|
|
reinterpret_cast<const unsigned char*>(input.data()),
|
|
static_cast<int>(input.size())
|
|
);
|
|
if (util::cmp_not_equal(pl, ol))
|
|
{
|
|
return make_unexpected("Could not decode base64 string", mamba_error_code::openssl_failed);
|
|
}
|
|
|
|
return std::string(reinterpret_cast<const char*>(output.data()));
|
|
}
|
|
|
|
std::optional<std::string> proxy_match(const std::string& url)
|
|
{
|
|
/* This is a reimplementation of requests.utils.select_proxy(), of the python requests
|
|
library used by conda */
|
|
auto& proxies = Context::instance().proxy_servers;
|
|
if (proxies.empty())
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto handler = URLHandler(url);
|
|
auto scheme = handler.scheme();
|
|
auto host = handler.host();
|
|
std::vector<std::string> options;
|
|
|
|
if (host.empty())
|
|
{
|
|
options = {
|
|
scheme,
|
|
"all",
|
|
};
|
|
}
|
|
else
|
|
{
|
|
options = { scheme + "://" + host, scheme, "all://" + host, "all" };
|
|
}
|
|
|
|
for (auto& option : options)
|
|
{
|
|
auto proxy = proxies.find(option);
|
|
if (proxy != proxies.end())
|
|
{
|
|
return proxy->second;
|
|
}
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
} // namespace mamba
|