#pragma once #include namespace fgc { // Refuses to let two copies of the program run at once. Two instances would // fight over the motor's serial port, the camera, and the exposure store, so // the second launch has to fail loudly rather than half-work. // // The mechanism is an advisory flock(2) on a lockfile holding the owner's PID. // The lock lives in the binary (not the launcher script) so every route in - // scripts/fgc, systemd, or a bare ./fire_gimbal_control over ssh - is guarded. // // There is deliberately no stale-lock cleanup: the kernel drops an flock when // the holding process dies, including a SIGKILL or a power cut, so a lockfile // left behind with a dead PID in it never blocks the next launch. The PID // written inside is purely for the "already running as PID N" message. // // auto lock = SingleInstance::acquire(SingleInstance::defaultLockPath()); // if (!lock) { std::cerr << lock.error() << "\n"; return 2; } // ... lock stays in scope for the lifetime of the run ... class SingleInstance { public: // Default lockfile: /run/user//fire_gimbal_control.lock when that // directory exists, else /tmp/fire_gimbal_control-.lock. Derived from // the uid rather than $XDG_RUNTIME_DIR so it is identical whether the // program was launched from an interactive shell, a non-interactive // `ssh host cmd`, or a tmux pane - see the note in the .cpp. static std::string defaultLockPath(); // Try to take the lock. Never blocks. On failure the returned object is // falsey and error() says why (including the holder's PID when we could // read it). Always check before use. static SingleInstance acquire(const std::string& path); // A default-constructed instance holds nothing; useful as a placeholder // when locking is disabled (--no-lock). SingleInstance() = default; ~SingleInstance(); SingleInstance(SingleInstance&& other) noexcept; SingleInstance& operator=(SingleInstance&& other) noexcept; SingleInstance(const SingleInstance&) = delete; SingleInstance& operator=(const SingleInstance&) = delete; bool held() const { return fd_ >= 0; } explicit operator bool() const { return held(); } // Human-readable reason the acquire failed; empty when held(). const std::string& error() const { return error_; } // PID of the process holding the lock, or 0 if unknown. Only meaningful // after a failed acquire. int holderPid() const { return holder_pid_; } const std::string& path() const { return path_; } // Release early (also done by the destructor). Safe to call twice. void release(); private: int fd_ = -1; int holder_pid_ = 0; std::string path_; std::string error_; }; } // namespace fgc