Skip to content

Commit b1c7fa1

Browse files
committed
fix: handle broken symlinks during mount destination creation
When a destination file doesn't exist, the previous implementation couldn't distinguish between creation failure due to existing files vs other reasons. If both opening and creating fail, the destination is definitely a broken symlink. - Add recursive symlink resolution with depth limit (32) in create_destination_file() - Use O_NOFOLLOW to detect symlink during file creation - When creation fails with ELOOP, read symlink target and recursively create it - Replace filesystem functions with internal utils for consistent error handling - Add proper broken symlink detection and resolution logic This ensures mount destinations work correctly even when they point to broken symlinks, by creating the missing target files in the symlink chain. Signed-off-by: ComixHe <ComixHe1895@outlook.com>
1 parent 08c47ff commit b1c7fa1

File tree

7 files changed

+109
-27
lines changed

7 files changed

+109
-27
lines changed

src/linyaps_box/container.cpp

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
#endif
4949

5050
constexpr auto propagations_flag = (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE);
51+
constexpr auto max_symlink_depth{ 32 };
5152

5253
namespace linyaps_box {
5354

@@ -399,29 +400,48 @@ void do_remount(const remount_t &mount)
399400
}
400401
}
401402

402-
[[nodiscard]] linyaps_box::utils::file_descriptor create_destination_file(
403+
[[nodiscard]] linyaps_box::utils::file_descriptor create_destination_directory(
403404
const linyaps_box::utils::file_descriptor &root, const std::filesystem::path &destination)
404405
{
405-
LINYAPS_BOX_DEBUG() << "Creating file " << destination.string() << " under "
406+
LINYAPS_BOX_DEBUG() << "Creating directory " << destination.string() << " under "
406407
<< linyaps_box::utils::inspect_path(root.get());
407-
const auto &parent = linyaps_box::utils::mkdir(root, destination.parent_path());
408-
return linyaps_box::utils::touch(parent, destination.filename());
408+
return linyaps_box::utils::mkdir(root, destination);
409409
}
410410

411-
[[nodiscard]] linyaps_box::utils::file_descriptor create_destination_directory(
412-
const linyaps_box::utils::file_descriptor &root, const std::filesystem::path &destination)
411+
[[nodiscard]] linyaps_box::utils::file_descriptor
412+
create_destination_file(const linyaps_box::utils::file_descriptor &root,
413+
const std::filesystem::path &destination,
414+
int max_depth)
413415
{
414-
LINYAPS_BOX_DEBUG() << "Creating directory " << destination.string() << " under "
416+
if (max_depth < 0) {
417+
throw std::system_error(ELOOP, std::system_category(), "failed to create file");
418+
}
419+
420+
LINYAPS_BOX_DEBUG() << "Creating file " << destination.string() << " under "
415421
<< linyaps_box::utils::inspect_path(root.get());
416-
return linyaps_box::utils::mkdir(root, destination);
422+
const auto &parent = create_destination_directory(root, destination.parent_path());
423+
424+
try {
425+
auto ret = linyaps_box::utils::touch(parent,
426+
destination.filename(),
427+
O_CLOEXEC | O_CREAT | O_WRONLY | O_NOFOLLOW);
428+
return ret;
429+
} catch (std::system_error &e) {
430+
if (e.code() != std::errc::too_many_symbolic_link_levels) {
431+
throw;
432+
}
433+
434+
auto target = linyaps_box::utils::readlinkat(parent, destination.filename());
435+
return create_destination_file(root, target, max_depth - 1);
436+
}
417437
}
418438

419439
[[nodiscard]] linyaps_box::utils::file_descriptor
420440
create_destination_symlink(const linyaps_box::utils::file_descriptor &root,
421441
const std::filesystem::path &source,
422442
std::filesystem::path destination)
423443
{
424-
auto ret = std::filesystem::read_symlink(source);
444+
auto ret = linyaps_box::utils::readlink(source);
425445
auto parent = linyaps_box::utils::mkdir(root, destination.parent_path());
426446

427447
LINYAPS_BOX_DEBUG() << "Creating symlink " << destination.string() << " under "
@@ -447,13 +467,8 @@ create_destination_symlink(const linyaps_box::utils::file_descriptor &root,
447467
+ " already exists and is not a symlink");
448468
}
449469

450-
std::array<char, PATH_MAX + 1> buf{};
451-
auto to = ::readlinkat(root.get(), destination.c_str(), buf.data(), buf.size());
452-
if (to == -1) {
453-
throw std::system_error(errno, std::system_category(), "readlinkat");
454-
}
455-
456-
if (std::string_view{ buf.data(), static_cast<size_t>(to) } == ret) {
470+
auto target = linyaps_box::utils::readlinkat(root, destination);
471+
if (target == ret) {
457472
return linyaps_box::utils::open_at(root, destination, O_PATH | O_NOFOLLOW | O_CLOEXEC);
458473
}
459474

@@ -469,11 +484,9 @@ ensure_mount_destination(bool isDir,
469484
const linyaps_box::config::mount_t &mount)
470485
try {
471486
assert(mount.destination.has_value());
472-
auto open_flag = O_PATH;
473-
if ((mount.flags & LINGYAPS_MS_NOSYMFOLLOW) != 0) {
474-
open_flag |= O_NOFOLLOW;
475-
}
476-
487+
auto open_flag = O_PATH | O_CLOEXEC;
488+
LINYAPS_BOX_DEBUG() << "Opening " << (isDir ? "directory " : "file ")
489+
<< mount.destination.value() << " under " << root.current_path();
477490
return linyaps_box::utils::open_at(root, mount.destination.value(), open_flag);
478491
} catch (const std::system_error &e) {
479492
if (e.code().value() != ENOENT) {
@@ -492,7 +505,7 @@ try {
492505
return create_destination_directory(root, path);
493506
}
494507

495-
return create_destination_file(root, path);
508+
return create_destination_file(root, path, max_symlink_depth);
496509
}
497510

498511
void do_propagation_mount(const linyaps_box::utils::file_descriptor &destination,

src/linyaps_box/utils/file_describer.cpp

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,23 @@ linyaps_box::utils::file_descriptor_closed_exception::file_descriptor_closed_exc
1818
linyaps_box::utils::file_descriptor_closed_exception::~file_descriptor_closed_exception() noexcept =
1919
default;
2020

21+
linyaps_box::utils::file_descriptor_invalid_exception::file_descriptor_invalid_exception(
22+
const std::string &message)
23+
: std::runtime_error(message)
24+
{
25+
}
26+
27+
linyaps_box::utils::file_descriptor_invalid_exception::
28+
~file_descriptor_invalid_exception() noexcept = default;
29+
2130
linyaps_box::utils::file_descriptor::file_descriptor(int fd)
2231
: fd_(fd)
2332
{
2433
}
2534

2635
linyaps_box::utils::file_descriptor::~file_descriptor()
2736
{
28-
if (fd_ == -1) {
37+
if (fd_ < 0) {
2938
return;
3039
}
3140

@@ -58,6 +67,10 @@ auto linyaps_box::utils::file_descriptor::duplicate() const -> linyaps_box::util
5867
throw file_descriptor_closed_exception();
5968
}
6069

70+
if (fd_ == AT_FDCWD) {
71+
throw file_descriptor_invalid_exception("cannot duplicate AT_FDCWD");
72+
}
73+
6174
auto ret = dup(fd_);
6275
if (ret < 0) {
6376
throw std::system_error(errno, std::generic_category(), "fcntl");
@@ -134,3 +147,8 @@ auto linyaps_box::utils::file_descriptor::current_path() const noexcept -> std::
134147

135148
return path;
136149
}
150+
151+
auto linyaps_box::utils::file_descriptor::cwd() -> file_descriptor
152+
{
153+
return file_descriptor{ AT_FDCWD };
154+
}

src/linyaps_box/utils/file_describer.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,19 @@ class file_descriptor_closed_exception : public std::runtime_error
2323
~file_descriptor_closed_exception() noexcept override;
2424
};
2525

26+
class file_descriptor_invalid_exception : public std::runtime_error
27+
{
28+
public:
29+
explicit file_descriptor_invalid_exception(const std::string &message);
30+
file_descriptor_invalid_exception(const file_descriptor_invalid_exception &) = default;
31+
file_descriptor_invalid_exception(file_descriptor_invalid_exception &&) noexcept = default;
32+
auto operator=(const file_descriptor_invalid_exception &)
33+
-> file_descriptor_invalid_exception & = default;
34+
auto operator=(file_descriptor_invalid_exception &&) noexcept
35+
-> file_descriptor_invalid_exception & = default;
36+
~file_descriptor_invalid_exception() noexcept override;
37+
};
38+
2639
class file_descriptor
2740
{
2841
public:
@@ -51,6 +64,8 @@ class file_descriptor
5164

5265
[[nodiscard]] auto current_path() const noexcept -> std::filesystem::path;
5366

67+
static auto cwd() -> file_descriptor;
68+
5469
private:
5570
int fd_{ -1 };
5671
};

src/linyaps_box/utils/symlink.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66

77
#include "linyaps_box/utils/log.h"
88

9+
#include <linux/limits.h>
10+
11+
#include <array>
12+
913
void linyaps_box::utils::symlink(const std::filesystem::path &target,
1014
const std::filesystem::path &link_path)
1115
{
@@ -30,3 +34,26 @@ void linyaps_box::utils::symlink_at(const std::filesystem::path &target,
3034
throw std::system_error(errno, std::system_category(), "symlinkat");
3135
}
3236
}
37+
38+
std::filesystem::path linyaps_box::utils::readlink(const std::filesystem::path &path)
39+
{
40+
std::error_code ec;
41+
auto ret = std::filesystem::read_symlink(path, ec);
42+
if (ec) {
43+
throw std::system_error{ ec.value(), std::system_category(), ec.message() };
44+
}
45+
46+
return ret;
47+
}
48+
49+
std::filesystem::path linyaps_box::utils::readlinkat(const file_descriptor &dirfd,
50+
const std::filesystem::path &path)
51+
{
52+
std::array<char, PATH_MAX + 1> buf{};
53+
auto ret = ::readlinkat(dirfd.get(), path.c_str(), buf.data(), PATH_MAX + 1);
54+
if (ret == -1) {
55+
throw std::system_error(errno, std::system_category(), "readlinkat");
56+
}
57+
58+
return std::filesystem::path{ buf.data() };
59+
}

src/linyaps_box/utils/symlink.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,8 @@ void symlink_at(const std::filesystem::path &target,
1616
const file_descriptor &dirfd,
1717
const std::filesystem::path &link_path);
1818

19+
std::filesystem::path readlink(const std::filesystem::path &path);
20+
21+
std::filesystem::path readlinkat(const file_descriptor &dirfd, const std::filesystem::path &path);
22+
1923
} // namespace linyaps_box::utils

src/linyaps_box/utils/touch.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@
99

1010
#include <fcntl.h>
1111

12-
auto linyaps_box::utils::touch(const file_descriptor &root, const std::filesystem::path &path)
13-
-> linyaps_box::utils::file_descriptor
12+
auto linyaps_box::utils::touch(const file_descriptor &root,
13+
const std::filesystem::path &path,
14+
int flag,
15+
mode_t mode) -> linyaps_box::utils::file_descriptor
1416
{
1517
LINYAPS_BOX_DEBUG() << "touch " << path << " at " << inspect_fd(root.get());
16-
const auto fd = ::openat(root.get(), path.c_str(), O_CREAT | O_WRONLY, 0666);
18+
const auto fd = ::openat(root.get(), path.c_str(), flag, mode);
1719
if (fd == -1) {
1820
throw std::system_error(errno,
1921
std::system_category(),

src/linyaps_box/utils/touch.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88

99
namespace linyaps_box::utils {
1010

11-
auto touch(const file_descriptor &root, const std::filesystem::path &path) -> file_descriptor;
11+
auto touch(const file_descriptor &root,
12+
const std::filesystem::path &path,
13+
int flag,
14+
mode_t mode = 0700) -> file_descriptor;
1215

1316
} // namespace linyaps_box::utils

0 commit comments

Comments
 (0)