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
167170pid_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+
538580class 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