Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
267 changes: 0 additions & 267 deletions elisp/private/tools/system.cc
Original file line number Diff line number Diff line change
Expand Up @@ -837,273 +837,6 @@ absl::Status CopyFile(const FileName& from, const FileName& to) {
return absl::OkStatus();
}

#if !defined _WIN32 && !defined __APPLE__
# define RULES_ELISP_COPY_TREE_POSIX
#endif

#ifdef _WIN32
static absl::Status CopyTreeWindows(const FileName& from, const FileName& to) {
if (!::CreateDirectoryExW(from.pointer(), to.pointer(), nullptr)) {
return WindowsStatus("CreateDirectoryExW", from, to, nullptr);
}
const std::wstring pattern = from.string() + L"\\*";
WIN32_FIND_DATAW data;
const HANDLE handle = ::FindFirstFileW(Pointer(pattern), &data);
if (handle == INVALID_HANDLE_VALUE) {
return ::GetLastError() == ERROR_FILE_NOT_FOUND
? absl::OkStatus()
: WindowsStatus("FindFirstFileW", pattern);
}
const absl::Cleanup cleanup = [handle] {
if (!::FindClose(handle)) LOG(ERROR) << WindowsStatus("FindClose");
};
do {
const std::wstring_view name = data.cFileName;
if (name == L"." || name == L"..") continue;
const absl::StatusOr<FileName> from_entry = from.Child(name);
if (!from_entry.ok()) return from_entry.status();
const absl::StatusOr<FileName> to_entry = to.Child(name);
if (!to_entry.ok()) return to_entry.status();
if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
const absl::Status status = CopyTreeWindows(*from_entry, *to_entry);
if (!status.ok()) return status;
} else {
constexpr DWORD flags = COPY_FILE_FAIL_IF_EXISTS | COPY_FILE_COPY_SYMLINK;
if (!::CopyFileExW(from_entry->pointer(), to_entry->pointer(), nullptr,
nullptr, nullptr, flags)) {
return WindowsStatus("CopyFileExW", *from_entry, *to_entry, nullptr,
nullptr, nullptr, absl::Hex(flags));
}
}
} while (::FindNextFileW(handle, &data));
if (::GetLastError() != ERROR_NO_MORE_FILES) {
return WindowsStatus("FindNextFileW");
}
return absl::OkStatus();
}
#endif

#ifdef RULES_ELISP_COPY_TREE_POSIX
static absl::Status CopyFilePosix(const int from_parent, const int to_parent,
const FileName& name,
const struct stat& from_stat) {
CHECK_NE(name.string(), ".");
CHECK_NE(name.string(), "..");
CHECK_EQ(name.string().find('/'), name.string().npos)
<< "invalid name " << name;
CHECK(S_ISREG(from_stat.st_mode)) << "file " << name << " is not regular";

constexpr int from_flags = O_RDONLY | O_CLOEXEC | O_NOCTTY | O_NOFOLLOW;
const int from_fd = openat(from_parent, name.pointer(), from_flags);
if (from_fd < 0) {
return ErrnoStatus("openat", from_parent, name, absl::Hex(from_flags));
}
const absl::Cleanup close_from = [from_fd] {
if (close(from_fd) != 0) LOG(ERROR) << ErrnoStatus("close", from_fd);
};

const mode_t mode = from_stat.st_mode;
constexpr int to_flags =
O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC | O_NOCTTY | O_NOFOLLOW;
const int to_fd = openat(to_parent, name.pointer(), to_flags, mode);
if (to_fd < 0) {
return ErrnoStatus("openat", to_parent, name, to_flags, Oct(mode));
}
const absl::Cleanup close_to = [to_fd] {
if (close(to_fd) != 0) LOG(ERROR) << ErrnoStatus("close", to_fd);
};

while (true) {
constexpr std::size_t buffer_size = 0x1000;
char buffer[buffer_size];
const ssize_t result = read(from_fd, buffer, buffer_size);
if (result < 0) {
return ErrnoStatus("read", from_fd, kEllipsis, absl::Hex(buffer_size));
}
if (result == 0) break;
std::string_view remaining(
buffer, CastNumber<std::string_view::size_type>(result).value());
while (!remaining.empty()) {
const ssize_t written = write(to_fd, remaining.data(), remaining.size());
if (written < 0) {
return ErrnoStatus("write", to_fd, kEllipsis, remaining.size());
}
if (written == 0) {
// Prevent infinite loop.
return absl::DataLossError(absl::StrFormat(
"Cannot write %d bytes to %v", remaining.size(), name));
}
remaining.remove_prefix(
CastNumber<std::string_view::size_type>(written).value());
}
}

if (fsync(to_fd) != 0) return ErrnoStatus("fsync", to_fd);

const struct timespec times[2] = {{from_stat.st_atime, 0},
{from_stat.st_mtime, 0}};
if (futimens(to_fd, times) != 0) return ErrnoStatus("futimens", to_fd);

return absl::OkStatus();
}

static absl::Status CopyTreePosix(const int from_parent,
const FileName& from_name,
std::optional<struct stat> from_stat,
const int to_parent,
const FileName& to_name) {
CHECK_NE(from_name.string(), ".");
CHECK_NE(from_name.string(), "..");
CHECK(from_parent == AT_FDCWD ||
from_name.string().find('/') == from_name.string().npos)
<< "invalid source name " << from_name;

CHECK_NE(to_name.string(), ".");
CHECK_NE(to_name.string(), "..");
CHECK(to_parent == AT_FDCWD ||
to_name.string().find('/') == to_name.string().npos)
<< "invalid target name " << to_name;

const int from_flags =
O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC | O_NOCTTY;
const int from_fd = openat(from_parent, from_name.pointer(), from_flags);
if (from_fd < 0) {
return ErrnoStatus("openat", from_parent, from_name, absl::Hex(from_flags));
}
bool owns_from_fd = true;
const absl::Cleanup close_from_fd = [from_fd, &owns_from_fd] {
if (owns_from_fd) {
if (close(from_fd) != 0) LOG(ERROR) << ErrnoStatus("close", from_fd);
}
};
if (!from_stat.has_value()) {
struct stat buffer;
if (fstat(from_fd, &buffer) != 0) return ErrnoStatus("fstat", from_fd);
if (!S_ISDIR(buffer.st_mode)) {
return absl::FailedPreconditionError(
absl::StrFormat("Source %v is not a directory", from_name));
}
from_stat = buffer;
}
CHECK(from_stat.has_value());

DIR* const absl_nullable dir = fdopendir(from_fd);
if (dir == nullptr) return ErrnoStatus("fdopendir", from_fd);
owns_from_fd = false;
const absl::Cleanup close_dir = [dir, &owns_from_fd] {
CHECK(!owns_from_fd);
if (closedir(dir) != 0) LOG(ERROR) << ErrnoStatus("closedir");
};

const mode_t mode = from_stat->st_mode | S_IWUSR;
const int result = mkdirat(to_parent, to_name.pointer(), mode);
if (result != 0) return ErrnoStatus("mkdirat", to_parent, to_name, Oct(mode));

constexpr int to_flags =
O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC | O_NOCTTY;
const int to_fd = openat(to_parent, to_name.pointer(), to_flags);
if (to_fd < 0) {
return ErrnoStatus("openat", to_parent, to_name, absl::Hex(to_flags));
}
const absl::Cleanup close_to = [to_fd] {
if (close(to_fd) != 0) LOG(ERROR) << ErrnoStatus("close");
};

while (true) {
errno = 0;
const struct dirent* const absl_nullable entry = readdir(dir);
if (entry == nullptr) {
if (errno != 0) return ErrnoStatus("readdir");
break;
}
const std::string_view name = entry->d_name;
if (name == "." || name == "..") continue;
const absl::StatusOr<FileName> file = FileName::FromString(name);
if (!file.ok()) return file.status();

struct stat buffer;
constexpr int stat_flags = AT_SYMLINK_NOFOLLOW;
if (fstatat(from_fd, entry->d_name, &buffer, stat_flags) != 0) {
return ErrnoStatus("fstatat", from_fd, name, kEllipsis,
absl::Hex(stat_flags));
}
const mode_t mode = buffer.st_mode;
if (S_ISDIR(mode)) {
const absl::Status status =
CopyTreePosix(from_fd, *file, buffer, to_fd, *file);
if (!status.ok()) return status;
} else if (S_ISREG(mode)) {
const absl::Status status = CopyFilePosix(from_fd, to_fd, *file, buffer);
if (!status.ok()) return status;
} else {
return absl::FailedPreconditionError(absl::StrFormat(
"File %s with mode 0%03o cannot be copied", name, mode));
}
}

const struct timespec times[2] = {{from_stat->st_atime, 0},
{from_stat->st_mtime, 0}};
if (futimens(to_fd, times) != 0) return ErrnoStatus("futimens", to_fd);

return absl::OkStatus();
}
#endif

absl::Status CopyTree(const FileName& from, const FileName& to) {
#if defined _WIN32
const absl::StatusOr<FileName> from_abs = from.MakeAbsolute();
if (!from_abs.ok()) return from_abs.status();
if (from_abs->string().length() < 4) {
return absl::InvalidArgumentError(
absl::StrFormat("Cannot copy drive %v", *from_abs));
}
const absl::StatusOr<FileName> to_abs = to.MakeAbsolute();
if (!to_abs.ok()) return to_abs.status();
if (to_abs->string().length() < 4) {
return absl::AlreadyExistsError(
absl::StrFormat("Cannot copy over drive %v", *from_abs));
}
return CopyTreeWindows(*from_abs, *to_abs);
#elif defined __APPLE__
std::string from_str = from.string();
// The behavior of copyfile changes if the source directory name ends in a
// slash, see the man page copyfile(3). We need to remove trailing slashes to
// get the expected behavior.
while (from_str.back() == '/') from_str.pop_back();
if (from_str.empty()) {
// If the source directory name is empty, it has consisted only of slashes.
return absl::InvalidArgumentError("Cannot copy from root directory");
}
// The behavior of copyfile changes if the destination directory already
// exists, see the man page copyfile(3).
struct stat buffer;
if (lstat(to.pointer(), &buffer) == 0) {
return absl::AlreadyExistsError(
absl::StrFormat("Destination directory %v already exists", to));
}
if (errno != ENOENT) return ErrnoStatus("lstat", to);
constexpr copyfile_flags_t flags = COPYFILE_ALL | COPYFILE_RECURSIVE |
COPYFILE_EXCL | COPYFILE_NOFOLLOW |
COPYFILE_CLONE | COPYFILE_DATA_SPARSE;
if (copyfile(Pointer(from_str), to.pointer(), nullptr, flags) != 0) {
return ErrnoStatus("copyfile", from_str, to, nullptr, absl::Hex(flags));
}
return absl::OkStatus();
#elif defined RULES_ELISP_COPY_TREE_POSIX
if (from.string().find_first_not_of('/') == from.string().npos) {
return absl::InvalidArgumentError(
absl::StrFormat("Cannot copy from root directory %v", from));
}
if (to.string().find_first_not_of('/') == to.string().npos) {
return absl::AlreadyExistsError(
absl::StrFormat("Cannot overwrite root directory %v", to));
}
return CopyTreePosix(AT_FDCWD, from, std::nullopt, AT_FDCWD, to);
#else
# error CopyTree not implemented on this system
#endif
}

namespace {

class EnvironmentBlock final {
Expand Down
1 change: 0 additions & 1 deletion elisp/private/tools/system.h
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,6 @@ absl::Status RemoveTree(const FileName& directory);
absl::StatusOr<std::vector<FileName>> ListDirectory(const FileName& dir);
absl::Status Rename(const FileName& from, const FileName& to);
absl::Status CopyFile(const FileName& from, const FileName& to);
absl::Status CopyTree(const FileName& from, const FileName& to);

class Environment final {
private:
Expand Down
Loading