2022-03-17 16:28:08 +02:00
|
|
|
|
#include <sys/time.h>
|
2022-03-17 17:13:29 +02:00
|
|
|
|
#include <filesystem>
|
2022-11-18 10:37:11 +02:00
|
|
|
|
#include <atomic>
|
2022-03-17 16:28:08 +02:00
|
|
|
|
|
2022-04-13 15:48:31 +03:00
|
|
|
|
#include "finally.hh"
|
2022-03-17 16:28:08 +02:00
|
|
|
|
#include "util.hh"
|
|
|
|
|
#include "types.hh"
|
|
|
|
|
|
2022-03-17 17:13:29 +02:00
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
|
|
2022-03-17 16:28:08 +02:00
|
|
|
|
namespace nix {
|
|
|
|
|
|
2022-04-13 15:48:31 +03:00
|
|
|
|
static Path tempName(Path tmpRoot, const Path & prefix, bool includePid,
|
2022-11-18 10:37:11 +02:00
|
|
|
|
std::atomic<unsigned int> & counter)
|
2022-04-13 15:48:31 +03:00
|
|
|
|
{
|
|
|
|
|
tmpRoot = canonPath(tmpRoot.empty() ? getEnv("TMPDIR").value_or("/tmp") : tmpRoot, true);
|
|
|
|
|
if (includePid)
|
|
|
|
|
return (format("%1%/%2%-%3%-%4%") % tmpRoot % prefix % getpid() % counter++).str();
|
|
|
|
|
else
|
|
|
|
|
return (format("%1%/%2%-%3%") % tmpRoot % prefix % counter++).str();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Path createTempDir(const Path & tmpRoot, const Path & prefix,
|
|
|
|
|
bool includePid, bool useGlobalCounter, mode_t mode)
|
|
|
|
|
{
|
2022-11-18 10:37:11 +02:00
|
|
|
|
static std::atomic<unsigned int> globalCounter = 0;
|
|
|
|
|
std::atomic<unsigned int> localCounter = 0;
|
|
|
|
|
auto & counter(useGlobalCounter ? globalCounter : localCounter);
|
2022-04-13 15:48:31 +03:00
|
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
|
checkInterrupt();
|
|
|
|
|
Path tmpDir = tempName(tmpRoot, prefix, includePid, counter);
|
|
|
|
|
if (mkdir(tmpDir.c_str(), mode) == 0) {
|
|
|
|
|
#if __FreeBSD__
|
|
|
|
|
/* Explicitly set the group of the directory. This is to
|
|
|
|
|
work around around problems caused by BSD's group
|
|
|
|
|
ownership semantics (directories inherit the group of
|
|
|
|
|
the parent). For instance, the group of /tmp on
|
|
|
|
|
FreeBSD is "wheel", so all directories created in /tmp
|
|
|
|
|
will be owned by "wheel"; but if the user is not in
|
|
|
|
|
"wheel", then "tar" will fail to unpack archives that
|
|
|
|
|
have the setgid bit set on directories. */
|
|
|
|
|
if (chown(tmpDir.c_str(), (uid_t) -1, getegid()) != 0)
|
|
|
|
|
throw SysError("setting group of directory '%1%'", tmpDir);
|
|
|
|
|
#endif
|
|
|
|
|
return tmpDir;
|
|
|
|
|
}
|
|
|
|
|
if (errno != EEXIST)
|
|
|
|
|
throw SysError("creating directory '%1%'", tmpDir);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
std::pair<AutoCloseFD, Path> createTempFile(const Path & prefix)
|
|
|
|
|
{
|
|
|
|
|
Path tmpl(getEnv("TMPDIR").value_or("/tmp") + "/" + prefix + ".XXXXXX");
|
|
|
|
|
// Strictly speaking, this is UB, but who cares...
|
|
|
|
|
// FIXME: use O_TMPFILE.
|
|
|
|
|
AutoCloseFD fd(mkstemp((char *) tmpl.c_str()));
|
|
|
|
|
if (!fd)
|
|
|
|
|
throw SysError("creating temporary file '%s'", tmpl);
|
|
|
|
|
closeOnExec(fd.get());
|
|
|
|
|
return {std::move(fd), tmpl};
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-17 16:28:08 +02:00
|
|
|
|
void createSymlink(const Path & target, const Path & link,
|
|
|
|
|
std::optional<time_t> mtime)
|
|
|
|
|
{
|
|
|
|
|
if (symlink(target.c_str(), link.c_str()))
|
|
|
|
|
throw SysError("creating symlink from '%1%' to '%2%'", link, target);
|
|
|
|
|
if (mtime) {
|
|
|
|
|
struct timeval times[2];
|
|
|
|
|
times[0].tv_sec = *mtime;
|
|
|
|
|
times[0].tv_usec = 0;
|
|
|
|
|
times[1].tv_sec = *mtime;
|
|
|
|
|
times[1].tv_usec = 0;
|
|
|
|
|
if (lutimes(link.c_str(), times))
|
|
|
|
|
throw SysError("setting time of symlink '%s'", link);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void replaceSymlink(const Path & target, const Path & link,
|
|
|
|
|
std::optional<time_t> mtime)
|
|
|
|
|
{
|
|
|
|
|
for (unsigned int n = 0; true; n++) {
|
|
|
|
|
Path tmp = canonPath(fmt("%s/.%d_%s", dirOf(link), n, baseNameOf(link)));
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
createSymlink(target, tmp, mtime);
|
|
|
|
|
} catch (SysError & e) {
|
|
|
|
|
if (e.errNo == EEXIST) continue;
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-13 15:10:36 +03:00
|
|
|
|
renameFile(tmp, link);
|
2022-03-17 16:28:08 +02:00
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-18 15:25:56 +02:00
|
|
|
|
void setWriteTime(const fs::path & p, const struct stat & st)
|
|
|
|
|
{
|
|
|
|
|
struct timeval times[2];
|
|
|
|
|
times[0] = {
|
|
|
|
|
.tv_sec = st.st_atime,
|
|
|
|
|
.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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void copy(const fs::directory_entry & from, const fs::path & to, bool andDelete)
|
|
|
|
|
{
|
|
|
|
|
// TODO: Rewrite the `is_*` to use `symlink_status()`
|
|
|
|
|
auto statOfFrom = lstat(from.path().c_str());
|
|
|
|
|
auto fromStatus = from.symlink_status();
|
|
|
|
|
|
|
|
|
|
// Mark the directory as writable so that we can delete its children
|
|
|
|
|
if (andDelete && fs::is_directory(fromStatus)) {
|
|
|
|
|
fs::permissions(from.path(), fs::perms::owner_write, fs::perm_options::add | fs::perm_options::nofollow);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (fs::is_symlink(fromStatus) || fs::is_regular_file(fromStatus)) {
|
|
|
|
|
fs::copy(from.path(), to, fs::copy_options::copy_symlinks | fs::copy_options::overwrite_existing);
|
|
|
|
|
} else if (fs::is_directory(fromStatus)) {
|
|
|
|
|
fs::create_directory(to);
|
|
|
|
|
for (auto & entry : fs::directory_iterator(from.path())) {
|
|
|
|
|
copy(entry, to / entry.path().filename(), andDelete);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
throw Error("file '%s' has an unsupported type", from.path());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setWriteTime(to, statOfFrom);
|
|
|
|
|
if (andDelete) {
|
|
|
|
|
if (!fs::is_symlink(fromStatus))
|
|
|
|
|
fs::permissions(from.path(), fs::perms::owner_write, fs::perm_options::add | fs::perm_options::nofollow);
|
|
|
|
|
fs::remove(from.path());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-13 15:10:36 +03:00
|
|
|
|
void renameFile(const Path & oldName, const Path & newName)
|
2022-03-17 16:28:46 +02:00
|
|
|
|
{
|
2022-04-13 15:19:42 +03:00
|
|
|
|
fs::rename(oldName, newName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void moveFile(const Path & oldName, const Path & newName)
|
|
|
|
|
{
|
2022-03-17 17:13:29 +02:00
|
|
|
|
try {
|
2022-04-13 15:19:42 +03:00
|
|
|
|
renameFile(oldName, newName);
|
2022-03-17 17:13:29 +02:00
|
|
|
|
} catch (fs::filesystem_error & e) {
|
2022-04-13 15:19:42 +03:00
|
|
|
|
auto oldPath = fs::path(oldName);
|
|
|
|
|
auto newPath = fs::path(newName);
|
2022-04-13 15:48:31 +03:00
|
|
|
|
// For the move to be as atomic as possible, copy to a temporary
|
|
|
|
|
// directory
|
|
|
|
|
fs::path temp = createTempDir(newPath.parent_path(), "rename-tmp");
|
|
|
|
|
Finally removeTemp = [&]() { fs::remove(temp); };
|
|
|
|
|
auto tempCopyTarget = temp / "copy-target";
|
2022-03-17 17:13:29 +02:00
|
|
|
|
if (e.code().value() == EXDEV) {
|
2022-03-18 15:25:56 +02:00
|
|
|
|
fs::remove(newPath);
|
2022-04-13 15:19:42 +03:00
|
|
|
|
warn("Can’t rename %s as %s, copying instead", oldName, newName);
|
2022-04-13 15:48:31 +03:00
|
|
|
|
copy(fs::directory_entry(oldPath), tempCopyTarget, true);
|
|
|
|
|
renameFile(tempCopyTarget, newPath);
|
2022-03-17 17:13:29 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2022-03-17 16:28:46 +02:00
|
|
|
|
}
|
|
|
|
|
|
2022-03-17 16:28:08 +02:00
|
|
|
|
}
|