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::io::{AsRawFd, OwnedFd};
48
49    use nix::sys::socket::{self, UnixAddr};
50
51    use super::Error;
52
53    /// A struct representing one running instance.
54    pub struct SingleInstance {
55        maybe_sock: Option<OwnedFd>,
56    }
57
58    impl SingleInstance {
59        /// Returns a new SingleInstance object.
60        pub fn new(name: &str) -> Result<Self, Error> {
61            let addr = UnixAddr::new_abstract(name.as_bytes())?;
62            let sock = socket::socket(
63                socket::AddressFamily::Unix,
64                socket::SockType::Stream,
65                // If we fork and exec, then make sure the child process doesn't
66                // hang on to this file descriptor.
67                socket::SockFlag::SOCK_CLOEXEC,
68                None,
69            )?;
70
71            let maybe_sock = match socket::bind(sock.as_raw_fd(), &addr) {
72                Ok(()) => Some(sock),
73                Err(nix::errno::Errno::EADDRINUSE) => None,
74                Err(e) => return Err(e.into()),
75            };
76
77            Ok(Self {
78                maybe_sock,
79            })
80        }
81
82        /// Returns whether this instance is single.
83        pub fn is_single(&self) -> bool {
84            self.maybe_sock.is_some()
85        }
86    }
87
88    impl Drop for SingleInstance {
89        fn drop(&mut self) {
90            // OwnedFd closes the file descriptor automatically on drop.
91            if let Some(sock) = self.maybe_sock.take() {
92                drop(sock);
93            }
94        }
95    }
96}
97
98#[cfg(target_os = "macos")]
99mod inner {
100    use std::{fs::File, os::unix::io::AsRawFd, path::Path};
101
102    use libc::{__error, EWOULDBLOCK, LOCK_EX, LOCK_NB, flock};
103
104    use super::Error;
105
106    /// A struct representing one running instance.
107    pub struct SingleInstance {
108        _file: File,
109        is_single: bool,
110    }
111
112    impl SingleInstance {
113        /// Returns a new SingleInstance object.
114        pub fn new(name: &str) -> Result<Self, Error> {
115            let path = Path::new(name);
116            let file = if path.exists() {
117                File::open(path)?
118            } else {
119                File::create(path)?
120            };
121            unsafe {
122                let rc = flock(file.as_raw_fd(), LOCK_EX | LOCK_NB);
123                let is_single = rc == 0 || EWOULDBLOCK != *__error();
124                Ok(Self {
125                    _file: file,
126                    is_single,
127                })
128            }
129        }
130
131        /// Returns whether this instance is single.
132        pub fn is_single(&self) -> bool {
133            self.is_single
134        }
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141    static UNIQ_ID: &'static str = "aa2d0258-ffe9-11e7-ba89-0ed5f89f718b";
142    #[test]
143    fn test_single_instance() {
144        {
145            let instance_a = SingleInstance::new(UNIQ_ID).unwrap();
146            assert!(instance_a.is_single());
147            let instance_b = SingleInstance::new(UNIQ_ID).unwrap();
148            assert!(!instance_b.is_single());
149        }
150        let instance_c = SingleInstance::new(UNIQ_ID).unwrap();
151        assert!(instance_c.is_single());
152    }
153}