Skip to main content

single_instance/
single_instance.rs

1//! A rust library for single instance application.
2//!
3//! single-instance provides a single API to check if there are any other running instance.
4//!
5//! ## Detail
6//! On linux init will bind abstract unix domain socket with given name . On macos, init will create or open a file which path is given `&str`,
7//! then call `flock` to apply an advisory lock on the open file.
8//!
9//! ### Examples
10//! ```rust
11//! extern crate single_instance;
12//!
13//! use std::thread;
14//! use single_instance::SingleInstance;
15//!
16//! fn main() {
17//!     let instance = SingleInstance::new("whatever").unwrap();
18//!     assert!(instance.is_single());
19//! }
20//! ```
21
22#[derive(thiserror::Error, Debug)]
23pub enum Error {
24    #[cfg(any(target_os = "linux", target_os = "android"))]
25    #[error("new abstract addr error")]
26    Nix(#[from] nix::Error),
27
28    #[cfg(target_os = "macos")]
29    #[error("file open or create error")]
30    Io(#[from] std::io::Error),
31}
32
33// #[cfg(target_os = "macos")]
34// extern crate libc;
35// #[cfg(any(target_os = "linux", target_os ="android"))]
36// extern crate nix;
37// extern crate thiserror;
38// #[cfg(target_os = "windows")]
39// extern crate widestring;
40// #[cfg(target_os = "windows")]
41// extern crate winapi;
42
43pub use self::inner::*;
44
45#[cfg(any(target_os = "linux", target_os = "android"))]
46mod inner {
47    use std::os::unix::prelude::RawFd;
48
49    use nix::{
50        sys::socket::{self, UnixAddr},
51        unistd,
52    };
53
54    use super::Error;
55
56    /// A struct representing one running instance.
57    pub struct SingleInstance {
58        maybe_sock: Option<RawFd>,
59    }
60
61    impl SingleInstance {
62        /// Returns a new SingleInstance object.
63        pub fn new(name: &str) -> Result<Self, Error> {
64            let addr = UnixAddr::new_abstract(name.as_bytes())?;
65            let sock = socket::socket(
66                socket::AddressFamily::Unix,
67                socket::SockType::Stream,
68                // If we fork and exec, then make sure the child process doesn't
69                // hang on to this file descriptor.
70                socket::SockFlag::SOCK_CLOEXEC,
71                None,
72            )?;
73
74            let maybe_sock = match socket::bind(sock, &socket::SockAddr::Unix(addr)) {
75                Ok(()) => Some(sock),
76                Err(nix::errno::Errno::EADDRINUSE) => None,
77                Err(e) => return Err(e.into()),
78            };
79
80            Ok(Self {
81                maybe_sock,
82            })
83        }
84
85        /// Returns whether this instance is single.
86        pub fn is_single(&self) -> bool {
87            self.maybe_sock.is_some()
88        }
89    }
90
91    impl Drop for SingleInstance {
92        fn drop(&mut self) {
93            if let Some(sock) = self.maybe_sock {
94                // Intentionally discard any close errors.
95                let _ = unistd::close(sock);
96            }
97        }
98    }
99}
100
101#[cfg(target_os = "macos")]
102mod inner {
103    use std::{fs::File, os::unix::io::AsRawFd, path::Path};
104
105    use libc::{__error, EWOULDBLOCK, LOCK_EX, LOCK_NB, flock};
106
107    use super::Error;
108
109    /// A struct representing one running instance.
110    pub struct SingleInstance {
111        _file: File,
112        is_single: bool,
113    }
114
115    impl SingleInstance {
116        /// Returns a new SingleInstance object.
117        pub fn new(name: &str) -> Result<Self, Error> {
118            let path = Path::new(name);
119            let file = if path.exists() {
120                File::open(path)?
121            } else {
122                File::create(path)?
123            };
124            unsafe {
125                let rc = flock(file.as_raw_fd(), LOCK_EX | LOCK_NB);
126                let is_single = rc == 0 || EWOULDBLOCK != *__error();
127                Ok(Self {
128                    _file: file,
129                    is_single,
130                })
131            }
132        }
133
134        /// Returns whether this instance is single.
135        pub fn is_single(&self) -> bool {
136            self.is_single
137        }
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144    static UNIQ_ID: &'static str = "aa2d0258-ffe9-11e7-ba89-0ed5f89f718b";
145    #[test]
146    fn test_single_instance() {
147        {
148            let instance_a = SingleInstance::new(UNIQ_ID).unwrap();
149            assert!(instance_a.is_single());
150            let instance_b = SingleInstance::new(UNIQ_ID).unwrap();
151            assert!(!instance_b.is_single());
152        }
153        let instance_c = SingleInstance::new(UNIQ_ID).unwrap();
154        assert!(instance_c.is_single());
155    }
156}