Skip to content

Commit aa74b5f

Browse files
committed
test: provide kqueue-based process_guard for FreeBSD & macOS
1 parent 058c820 commit aa74b5f

1 file changed

Lines changed: 146 additions & 24 deletions

File tree

test/tools_test.cpp

Lines changed: 146 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@
4545
#include <sys/types.h>
4646
#ifdef __APPLE__
4747
#include <sys/mount.h>
48+
#elif defined(__FreeBSD__)
49+
#include <sys/mount.h>
50+
#include <sys/param.h>
4851
#else
4952
#include <sys/vfs.h>
5053
#endif
@@ -163,7 +166,7 @@ bool skip_fuse_tests() {
163166
return dwarfs::getenv_is_enabled("DWARFS_SKIP_FUSE_TESTS");
164167
}
165168

166-
#if !(defined(_WIN32) || defined(__APPLE__))
169+
#ifndef _WIN32
167170
pid_t get_dwarfs_pid(fs::path const& path) {
168171
return dwarfs::to<pid_t>(dwarfs::getxattr(path, "user.dwarfs.driver.pid"));
169172
}
@@ -534,42 +537,142 @@ class subprocess {
534537
std::vector<std::string> cmdline_;
535538
};
536539

537-
#if !(defined(_WIN32) || defined(__APPLE__))
540+
#ifndef _WIN32
541+
namespace fs_guard_detail {
542+
543+
struct unique_fd {
544+
int fd{-1};
545+
unique_fd() = default;
546+
explicit unique_fd(int f)
547+
: fd{f} {}
548+
unique_fd(unique_fd const&) = delete;
549+
unique_fd& operator=(unique_fd const&) = delete;
550+
unique_fd(unique_fd&& o) noexcept
551+
: fd{o.fd} {
552+
o.fd = -1;
553+
}
554+
unique_fd& operator=(unique_fd&& o) noexcept {
555+
if (this != &o) {
556+
if (fd >= 0) {
557+
::close(fd);
558+
}
559+
fd = o.fd;
560+
o.fd = -1;
561+
}
562+
return *this;
563+
}
564+
~unique_fd() {
565+
if (fd >= 0) {
566+
::close(fd);
567+
}
568+
}
569+
int get() const { return fd; }
570+
int release() {
571+
int t = fd;
572+
fd = -1;
573+
return t;
574+
}
575+
explicit operator bool() const { return fd >= 0; }
576+
};
577+
578+
} // namespace fs_guard_detail
579+
538580
class process_guard {
539581
public:
540582
process_guard() = default;
541583

542584
explicit process_guard(pid_t pid)
543585
: pid_{pid} {
544-
auto proc_dir = fs::path("/proc") / dwarfs::to<std::string>(pid);
545-
proc_dir_fd_ = ::open(proc_dir.c_str(), O_DIRECTORY);
546-
547-
if (proc_dir_fd_ < 0) {
548-
throw std::runtime_error("could not open " + proc_dir.string());
586+
#if defined(__FreeBSD__) || defined(__APPLE__)
587+
kq_ = fs_guard_detail::unique_fd(::kqueue());
588+
if (!kq_) {
589+
throw std::system_error(errno, std::generic_category(), "kqueue");
590+
}
591+
592+
struct kevent kev{};
593+
EV_SET(&kev, static_cast<uintptr_t>(pid_), EVFILT_PROC, EV_ADD | EV_CLEAR,
594+
NOTE_EXIT, 0, nullptr);
595+
if (::kevent(kq_.get(), &kev, 1, nullptr, 0, nullptr) < 0) {
596+
// If the process is already gone, treat as non-existent.
597+
if (errno == ESRCH) {
598+
already_exited_ = true;
599+
} else {
600+
throw std::system_error(errno, std::generic_category(),
601+
"kevent(EV_ADD)");
602+
}
549603
}
550-
}
551-
552-
~process_guard() {
553-
if (proc_dir_fd_ >= 0) {
554-
::close(proc_dir_fd_);
604+
#else
605+
std::string proc_dir = "/proc/" + std::to_string(pid_);
606+
procfd_ = fs_guard_detail::unique_fd(
607+
::open(proc_dir.c_str(), O_DIRECTORY | O_CLOEXEC));
608+
if (!procfd_) {
609+
throw std::system_error(errno, std::generic_category(),
610+
"open(" + proc_dir + ")");
555611
}
612+
#endif
556613
}
557614

558615
bool check_exit(std::chrono::milliseconds timeout) {
559-
auto end = std::chrono::steady_clock::now() + timeout;
560-
while (::faccessat(proc_dir_fd_, "fd", F_OK, 0) == 0) {
561-
std::this_thread::sleep_for(std::chrono::milliseconds(1));
562-
if (std::chrono::steady_clock::now() >= end) {
616+
#if defined(__FreeBSD__) || defined(__APPLE__)
617+
if (already_exited_) {
618+
return true;
619+
}
620+
621+
struct kevent ev{};
622+
struct timespec ts{
623+
.tv_sec = static_cast<time_t>(timeout.count() / 1000),
624+
.tv_nsec = static_cast<long>((timeout.count() % 1000) * 1'000'000)};
625+
626+
int n = ::kevent(kq_.get(), nullptr, 0, &ev, 1, &ts);
627+
if (n < 0) {
628+
// If PID vanished between registration and wait, consider it exited.
629+
if (errno == ESRCH) {
630+
return true;
631+
}
632+
// Other error: be conservative and signal timeout+terminate behavior
633+
// below.
634+
n = 0;
635+
}
636+
637+
if (n == 0) {
638+
// timed out: gently ask it to stop
639+
::kill(pid_, SIGTERM);
640+
return false;
641+
}
642+
643+
// We got an event; NOTE_EXIT means it's gone.
644+
if (ev.filter == EVFILT_PROC && (ev.fflags & NOTE_EXIT)) {
645+
return true;
646+
}
647+
648+
// Unexpected event; treat as not exited yet.
649+
return false;
650+
#else
651+
// Poll for existence of a known child entry (like "fd") in /proc/<pid>.
652+
auto deadline = std::chrono::steady_clock::now() + timeout;
653+
for (;;) {
654+
int rc = ::faccessat(procfd_.get(), "fd", F_OK, 0);
655+
if (rc != 0) {
656+
// Directory no longer has 'fd' → process is gone.
657+
return true;
658+
}
659+
if (std::chrono::steady_clock::now() >= deadline) {
563660
::kill(pid_, SIGTERM);
564661
return false;
565662
}
663+
std::this_thread::sleep_for(std::chrono::milliseconds(1));
566664
}
567-
return true;
665+
#endif
568666
}
569667

570668
private:
571669
pid_t pid_{-1};
572-
int proc_dir_fd_{-1};
670+
#if defined(__FreeBSD__) || defined(__APPLE__)
671+
fs_guard_detail::unique_fd kq_;
672+
bool already_exited_{false};
673+
#else
674+
fs_guard_detail::unique_fd procfd_;
675+
#endif
573676
};
574677
#endif
575678

@@ -601,11 +704,7 @@ class driver_runner {
601704
options, std::forward<Args>(args)...)) {
602705
throw std::runtime_error("error running " + driver.string());
603706
}
604-
#ifdef __APPLE__
605-
wait_until_file_ready(mountpoint, std::chrono::seconds(5));
606-
#else
607707
dwarfs_guard_ = process_guard(get_dwarfs_pid(mountpoint));
608-
#endif
609708
#endif
610709
}
611710

@@ -622,7 +721,7 @@ class driver_runner {
622721
#endif
623722
std::forward<Args>(args)...);
624723
process_->run_background();
625-
#if !(defined(_WIN32) || defined(__APPLE__))
724+
#ifndef _WIN32
626725
dwarfs_guard_ = process_guard(process_->pid());
627726
#endif
628727
}
@@ -689,6 +788,16 @@ class driver_runner {
689788
return is_expected_exit_code;
690789
#ifndef _WIN32
691790
} else {
791+
#ifdef __FreeBSD__
792+
auto umount = find_umount();
793+
for (int i = 0; i < 5; ++i) {
794+
if (subprocess::check_run(umount, mountpoint_)) {
795+
break;
796+
}
797+
std::cerr << "retrying umount...\n";
798+
std::this_thread::sleep_for(std::chrono::milliseconds(200));
799+
}
800+
#else
692801
auto fusermount = find_fusermount();
693802
for (int i = 0; i < 5; ++i) {
694803
if (subprocess::check_run(fusermount, "-u", mountpoint_)) {
@@ -697,6 +806,7 @@ class driver_runner {
697806
std::cerr << "retrying fusermount...\n";
698807
std::this_thread::sleep_for(std::chrono::milliseconds(200));
699808
}
809+
#endif
700810
mountpoint_.clear();
701811
return dwarfs_guard_.check_exit(std::chrono::seconds(5));
702812
}
@@ -732,6 +842,15 @@ class driver_runner {
732842

733843
private:
734844
#if !(defined(_WIN32) || defined(__APPLE__))
845+
#ifdef __FreeBSD__
846+
static fs::path find_umount() {
847+
auto umount_bin = dwarfs::test::find_binary("umount");
848+
if (!umount_bin) {
849+
throw std::runtime_error("no umount binary found");
850+
}
851+
return *umount_bin;
852+
}
853+
#else
735854
static fs::path find_fusermount() {
736855
auto fusermount_bin = dwarfs::test::find_binary("fusermount");
737856
if (!fusermount_bin) {
@@ -742,6 +861,7 @@ class driver_runner {
742861
}
743862
return *fusermount_bin;
744863
}
864+
#endif
745865
#endif
746866

747867
static void setup_mountpoint(fs::path const& mp) {
@@ -755,7 +875,7 @@ class driver_runner {
755875

756876
fs::path mountpoint_;
757877
std::unique_ptr<subprocess> process_;
758-
#if !(defined(_WIN32) || defined(__APPLE__))
878+
#ifndef _WIN32
759879
process_guard dwarfs_guard_;
760880
#endif
761881
};
@@ -1047,6 +1167,8 @@ TEST_P(tools_test, end_to_end) {
10471167
EXPECT_TRUE(ec);
10481168
#ifdef __APPLE__
10491169
EXPECT_EQ(ec.value(), ENOATTR);
1170+
#elif defined(__FreeBSD__)
1171+
EXPECT_EQ(ec.value(), ERANGE); // FIXME: this is weird...
10501172
#else
10511173
EXPECT_EQ(ec.value(), ENODATA);
10521174
#endif

0 commit comments

Comments
 (0)