#include "eject.h" #include #include #if defined(__APPLE__) #include #include namespace bfebbx { namespace { struct EjectCtx { bool done = false; bool ok = false; }; void unmountCallback(DADiskRef, DADissenterRef dissenter, void* c) { auto* ctx = static_cast(c); ctx->ok = (dissenter == nullptr); ctx->done = true; CFRunLoopStop(CFRunLoopGetCurrent()); } } // namespace void ejectVolume(const MountEntry& vol) { DASessionRef session = DASessionCreate(kCFAllocatorDefault); if (!session) throw std::runtime_error("DASessionCreate failed"); DASessionScheduleWithRunLoop(session, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); CFURLRef url = CFURLCreateFromFileSystemRepresentation( kCFAllocatorDefault, reinterpret_cast(vol.mountpoint.c_str()), static_cast(vol.mountpoint.size()), true); DADiskRef disk = url ? DADiskCreateFromVolumePath(kCFAllocatorDefault, session, url) : nullptr; EjectCtx ctx; if (disk) { DADiskUnmount(disk, kDADiskUnmountOptionWhole, unmountCallback, &ctx); // Bound the wait: DiskArbitration's callback normally always fires, but a // daemon crash or invalidated disk could otherwise hang the process forever. const CFTimeInterval kUnmountTimeout = 15.0; // seconds, total while (!ctx.done) { CFRunLoopRunResult r = CFRunLoopRunInMode(kCFRunLoopDefaultMode, kUnmountTimeout, true); if (r == kCFRunLoopRunTimedOut || r == kCFRunLoopRunFinished) break; // kCFRunLoopRunStopped (callback called CFRunLoopStop) or // kCFRunLoopRunHandledSource: re-check ctx.done and continue. } CFRelease(disk); } if (url) CFRelease(url); DASessionUnscheduleFromRunLoop(session, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); CFRelease(session); if (!disk) throw std::runtime_error("could not resolve disk for " + vol.mountpoint); if (!ctx.done) throw std::runtime_error("unmount timed out for " + vol.mountpoint); if (!ctx.ok) throw std::runtime_error("unmount denied for " + vol.mountpoint); } } // namespace bfebbx #elif defined(__linux__) #include #include #include #include #include namespace bfebbx { namespace { // Run a command with no shell; returns the exit status (or -1 on spawn failure). int runNoShell(const char* const argv[]) { pid_t pid = fork(); if (pid < 0) return -1; if (pid == 0) { execvp(argv[0], const_cast(argv)); _exit(127); } int status = 0; while (waitpid(pid, &status, 0) < 0) { if (errno == EINTR) continue; return -1; } return WIFEXITED(status) ? WEXITSTATUS(status) : -1; } } // namespace void ejectVolume(const MountEntry& vol) { if (umount(vol.mountpoint.c_str()) == 0) return; if (errno == EPERM || errno == EACCES) { const char* argv[] = {"udisksctl", "unmount", "-b", vol.device.c_str(), nullptr}; if (runNoShell(argv) == 0) return; throw std::runtime_error("umount denied and udisksctl fallback failed for " + vol.mountpoint); } throw std::runtime_error("umount " + vol.mountpoint + ": " + std::strerror(errno)); } } // namespace bfebbx #endif