aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorElie Kheirallah <khei@google.com>2023-04-28 22:30:11 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2023-04-28 22:30:11 +0000
commitf11391f865539df4f20c657d5ec2aede7dadfe23 (patch)
treefe7bf0f76ec4187fddac28beafd69348da03ac13
parentf827f0ac19518ff016b1ba880e25f7c6153bfe0d (diff)
parentc443e836d5c56b83f7b2e87c0876bda29cb910c0 (diff)
downloadnamed-lock-f11391f865539df4f20c657d5ec2aede7dadfe23.tar.gz
Import named-lock am: 10099b967d am: a251f02d3c am: 2918246d30 am: 8c455a7a24 am: 37ff3bdb97 am: c443e836d5
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/named-lock/+/2566511 Change-Id: Ib5a5f26f82295c6153d3048f8a341a48b698175c Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r--CHANGELOG.md40
-rw-r--r--Cargo.toml64
-rw-r--r--Cargo.toml.orig31
-rw-r--r--LICENSE21
-rw-r--r--METADATA19
-rw-r--r--MODULE_LICENSE_MIT0
-rw-r--r--OWNERS5
-rw-r--r--README.md43
-rw-r--r--rustfmt.toml3
-rw-r--r--src/error.rs26
-rw-r--r--src/lib.rs294
-rw-r--r--src/unix.rs63
-rw-r--r--src/windows.rs73
13 files changed, 682 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..bb403ab
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,40 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [unreleased]
+
+## [0.3.0]
+
+### Changed
+
+- `NamedLock::create` now rejects names that contain `\0` character ([#5])
+- `NamedLock::create` now rejects empty names
+- Upgrade all dependencies
+
+## [0.2.0]
+
+### Added
+
+- Added `NamedLock::with_path` on UNIX ([#2], [#4])
+
+### Changed
+
+- `NamedLock::create` on UNIX respects `TMPDIR` environment variable ([#1], [#4])
+- `NamedLock::create` now rejects names that contain `/` or `\` characters ([#2], [#4])
+- `NamedLock::create` on Windows explicitly creates a global mutex
+- `Error::CreateFailed` now has the source of the error
+- Upgrade all dependencies
+
+
+[unreleased]: https://github.com/oblique/named-lock/compare/0.3.0...HEAD
+[0.2.0]: https://github.com/oblique/named-lock/compare/0.1.1...0.2.0
+[0.3.0]: https://github.com/oblique/named-lock/compare/0.2.0...0.3.0
+
+[#5]: https://github.com/oblique/named-lock/issues/5
+[#4]: https://github.com/oblique/named-lock/issues/4
+[#2]: https://github.com/oblique/named-lock/issues/2
+[#1]: https://github.com/oblique/named-lock/issues/1
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..e939ba4
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,64 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2018"
+name = "named-lock"
+version = "0.3.0"
+authors = ["oblique <psyberbits@gmail.com>"]
+description = "Cross-platform implementation of cross-process named locks"
+readme = "README.md"
+keywords = [
+ "process",
+ "inter-process",
+ "cross-process",
+ "flock",
+ "CreateMutexW",
+]
+categories = ["os"]
+license = "MIT"
+repository = "https://github.com/oblique/named-lock"
+
+[package.metadata.docs.rs]
+all-features = true
+rustdoc-args = [
+ "--cfg",
+ "docsrs",
+]
+
+[dependencies.once_cell]
+version = "1.14.0"
+
+[dependencies.parking_lot]
+version = "0.12.1"
+
+[dependencies.thiserror]
+version = "1.0.35"
+
+[dev-dependencies.uuid]
+version = "1.1.2"
+features = ["v4"]
+
+[target."cfg(unix)".dependencies.libc]
+version = "0.2.132"
+
+[target."cfg(windows)".dependencies.widestring]
+version = "1.0.2"
+
+[target."cfg(windows)".dependencies.winapi]
+version = "0.3.9"
+features = [
+ "handleapi",
+ "synchapi",
+ "winbase",
+ "winnt",
+ "winerror",
+]
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
new file mode 100644
index 0000000..3e75c37
--- /dev/null
+++ b/Cargo.toml.orig
@@ -0,0 +1,31 @@
+[package]
+name = "named-lock"
+version = "0.3.0"
+authors = ["oblique <psyberbits@gmail.com>"]
+edition = "2018"
+license = "MIT"
+readme = "README.md"
+
+description = "Cross-platform implementation of cross-process named locks"
+categories = ["os"]
+keywords = ["process", "inter-process", "cross-process", "flock", "CreateMutexW"]
+repository = "https://github.com/oblique/named-lock"
+
+[dependencies]
+thiserror = "1.0.35"
+once_cell = "1.14.0"
+parking_lot = "0.12.1"
+
+[target.'cfg(unix)'.dependencies]
+libc = "0.2.132"
+
+[target.'cfg(windows)'.dependencies]
+winapi = { version = "0.3.9", features = ["handleapi", "synchapi", "winbase", "winnt", "winerror"] }
+widestring = "1.0.2"
+
+[dev-dependencies]
+uuid = { version = "1.1.2", features = ["v4"] }
+
+[package.metadata.docs.rs]
+all-features = true
+rustdoc-args = ["--cfg", "docsrs"]
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..4f20363
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 oblique
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..4083bb6
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,19 @@
+name: "named-lock"
+description: "Cross-platform implementation of cross-process named locks"
+third_party {
+ url {
+ type: HOMEPAGE
+ value: "https://crates.io/crates/named-lock"
+ }
+ url {
+ type: ARCHIVE
+ value: "https://static.crates.io/crates/named-lock/named-lock-0.3.0.crate"
+ }
+ version: "0.3.0"
+ license_type: NOTICE
+ last_upgrade_date {
+ year: 2023
+ month: 4
+ day: 19
+ }
+}
diff --git a/MODULE_LICENSE_MIT b/MODULE_LICENSE_MIT
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_MIT
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..3abd431
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,5 @@
+include platform/prebuilts/rust:master:/OWNERS
+devinmoore@google.com
+fmayle@google.com
+khei@google.com
+smoreland@google.com
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..2d3dd2e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,43 @@
+# named-lock
+
+[![license][license badge]][license]
+[![crates.io][crate badge]][crate]
+[![docs][docs badge]][docs]
+
+This crate provides a simple and cross-platform implementation of named locks.
+You can use this to lock sections between processes.
+
+## Example
+
+```rust
+use named_lock::NamedLock;
+use named_lock::Result;
+
+fn main() -> Result<()> {
+ let lock = NamedLock::create("foobar")?;
+ let _guard = lock.lock()?;
+
+ // Do something...
+
+ Ok(())
+}
+```
+
+## Implementation
+
+On UNIX this is implemented by using files and [`flock`]. The path of the
+created lock file will be `$TMPDIR/<name>.lock`, or `/tmp/<name>.lock` if
+`TMPDIR` environment variable is not set.
+
+On Windows this is implemented by creating named mutex with [`CreateMutexW`].
+
+
+[license]: LICENSE
+[license badge]: https://img.shields.io/github/license/oblique/named-lock
+[crate]: https://crates.io/crates/named-lock
+[crate badge]: https://img.shields.io/crates/v/named-lock
+[docs]: https://docs.rs/named-lock
+[docs badge]: https://docs.rs/named-lock/badge.svg
+
+[`flock`]: https://linux.die.net/man/2/flock
+[`CreateMutexW`]: https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createmutexw
diff --git a/rustfmt.toml b/rustfmt.toml
new file mode 100644
index 0000000..6a7d0cd
--- /dev/null
+++ b/rustfmt.toml
@@ -0,0 +1,3 @@
+max_width = 80
+use_small_heuristics = "off"
+edition = "2018"
diff --git a/src/error.rs b/src/error.rs
new file mode 100644
index 0000000..7a1e2d4
--- /dev/null
+++ b/src/error.rs
@@ -0,0 +1,26 @@
+use thiserror::Error;
+
+/// Type alias to `Result<T, Error>`.
+pub type Result<T, E = Error> = std::result::Result<T, E>;
+
+/// Error type of this crate.
+#[derive(Debug, Error)]
+pub enum Error {
+ #[error("Invalid character in name")]
+ InvalidCharacter,
+
+ #[error("Name must not be empty")]
+ EmptyName,
+
+ #[error("Failed to create named lock: {0}")]
+ CreateFailed(#[source] std::io::Error),
+
+ #[error("Failed to lock named lock")]
+ LockFailed,
+
+ #[error("Failed to unlock named lock")]
+ UnlockFailed,
+
+ #[error("Named lock would block")]
+ WouldBlock,
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..15e5032
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,294 @@
+#![cfg_attr(docsrs, feature(doc_cfg))]
+
+//! This crate provides a simple and cross-platform implementation of named locks.
+//! You can use this to lock sections between processes.
+//!
+//! ## Example
+//!
+//! ```rust
+//! use named_lock::NamedLock;
+//! use named_lock::Result;
+//!
+//! fn main() -> Result<()> {
+//! let lock = NamedLock::create("foobar")?;
+//! let _guard = lock.lock()?;
+//!
+//! // Do something...
+//!
+//! Ok(())
+//! }
+//! ```
+
+use once_cell::sync::Lazy;
+use parking_lot::{Mutex, MutexGuard};
+use std::collections::HashMap;
+#[cfg(unix)]
+use std::path::{Path, PathBuf};
+use std::sync::{Arc, Weak};
+
+mod error;
+#[cfg(unix)]
+mod unix;
+#[cfg(windows)]
+mod windows;
+
+pub use crate::error::*;
+#[cfg(unix)]
+use crate::unix::RawNamedLock;
+#[cfg(windows)]
+use crate::windows::RawNamedLock;
+
+#[cfg(unix)]
+type NameType = PathBuf;
+#[cfg(windows)]
+type NameType = String;
+
+// We handle two edge cases:
+//
+// On UNIX systems, after locking a file descriptor you can lock it again
+// as many times you want. However OS does not keep a counter, so only one
+// unlock must be performed. To avoid re-locking, we guard it with real mutex.
+//
+// On Windows, after locking a `HANDLE` you can create another `HANDLE` for
+// the same named lock and the same process and Windows will allow you to
+// re-lock it. To avoid this, we ensure that one `HANDLE` exists in each
+// process for each name.
+static OPENED_RAW_LOCKS: Lazy<
+ Mutex<HashMap<NameType, Weak<Mutex<RawNamedLock>>>>,
+> = Lazy::new(|| Mutex::new(HashMap::new()));
+
+/// Cross-process lock that is identified by name.
+#[derive(Debug)]
+pub struct NamedLock {
+ raw: Arc<Mutex<RawNamedLock>>,
+}
+
+impl NamedLock {
+ /// Create/open a named lock.
+ ///
+ /// # UNIX
+ ///
+ /// This will create/open a file and use [`flock`] on it. The path of
+ /// the lock file will be `$TMPDIR/<name>.lock`, or `/tmp/<name>.lock`
+ /// if `TMPDIR` environment variable is not set.
+ ///
+ /// If you want to specify the exact path, then use [NamedLock::with_path].
+ ///
+ /// # Windows
+ ///
+ /// This will create/open a [global] mutex with [`CreateMutexW`].
+ ///
+ /// # Notes
+ ///
+ /// * `name` must not be empty, otherwise an error is returned.
+ /// * `name` must not contain `\0`, `/`, nor `\`, otherwise an error is returned.
+ ///
+ /// [`flock`]: https://linux.die.net/man/2/flock
+ /// [global]: https://docs.microsoft.com/en-us/windows/win32/termserv/kernel-object-namespaces
+ /// [`CreateMutexW`]: https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createmutexw
+ pub fn create(name: &str) -> Result<NamedLock> {
+ if name.is_empty() {
+ return Err(Error::EmptyName);
+ }
+
+ // On UNIX we want to restrict the user on `/tmp` directory,
+ // so we block the `/` character.
+ //
+ // On Windows `\` character is invalid.
+ //
+ // Both platforms expect null-terminated strings,
+ // so we block null-bytes.
+ if name.chars().any(|c| matches!(c, '\0' | '/' | '\\')) {
+ return Err(Error::InvalidCharacter);
+ }
+
+ // If `TMPDIR` environment variable is set then use it as the
+ // temporary directory, otherwise use `/tmp`.
+ #[cfg(unix)]
+ let name = std::env::var_os("TMPDIR")
+ .map(PathBuf::from)
+ .unwrap_or_else(|| PathBuf::from("/tmp"))
+ .join(format!("{}.lock", name));
+
+ #[cfg(windows)]
+ let name = format!("Global\\{}", name);
+
+ NamedLock::_create(name)
+ }
+
+ /// Create/open a named lock on specified path.
+ ///
+ /// # Notes
+ ///
+ /// * This function does not append `.lock` on the path.
+ /// * Parent directories must exist.
+ #[cfg(unix)]
+ #[cfg_attr(docsrs, doc(cfg(unix)))]
+ pub fn with_path<P>(path: P) -> Result<NamedLock>
+ where
+ P: AsRef<Path>,
+ {
+ NamedLock::_create(path.as_ref().to_owned())
+ }
+
+ fn _create(name: NameType) -> Result<NamedLock> {
+ let mut opened_locks = OPENED_RAW_LOCKS.lock();
+
+ let lock = match opened_locks.get(&name).and_then(|x| x.upgrade()) {
+ Some(lock) => lock,
+ None => {
+ let lock = Arc::new(Mutex::new(RawNamedLock::create(&name)?));
+ opened_locks.insert(name, Arc::downgrade(&lock));
+ lock
+ }
+ };
+
+ Ok(NamedLock {
+ raw: lock,
+ })
+ }
+
+ /// Try to lock named lock.
+ ///
+ /// If it is already locked, `Error::WouldBlock` will be returned.
+ pub fn try_lock(&self) -> Result<NamedLockGuard> {
+ let guard = self.raw.try_lock().ok_or(Error::WouldBlock)?;
+
+ guard.try_lock()?;
+
+ Ok(NamedLockGuard {
+ raw: guard,
+ })
+ }
+
+ /// Lock named lock.
+ pub fn lock(&self) -> Result<NamedLockGuard> {
+ let guard = self.raw.lock();
+
+ guard.lock()?;
+
+ Ok(NamedLockGuard {
+ raw: guard,
+ })
+ }
+}
+
+/// Scoped guard that unlocks NamedLock.
+#[derive(Debug)]
+pub struct NamedLockGuard<'r> {
+ raw: MutexGuard<'r, RawNamedLock>,
+}
+
+impl<'r> Drop for NamedLockGuard<'r> {
+ fn drop(&mut self) {
+ let _ = self.raw.unlock();
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::env;
+ use std::process::{Child, Command};
+ use std::thread::sleep;
+ use std::time::Duration;
+ use uuid::Uuid;
+
+ fn call_proc_num(num: u32, uuid: &str) -> Child {
+ let exe = env::current_exe().expect("no exe");
+ let mut cmd = Command::new(exe);
+
+ cmd.env("TEST_CROSS_PROCESS_LOCK_PROC_NUM", num.to_string())
+ .env("TEST_CROSS_PROCESS_LOCK_UUID", uuid)
+ .arg("tests::cross_process_lock")
+ .spawn()
+ .unwrap()
+ }
+
+ #[test]
+ fn cross_process_lock() -> Result<()> {
+ let proc_num = env::var("TEST_CROSS_PROCESS_LOCK_PROC_NUM")
+ .ok()
+ .and_then(|v| v.parse().ok())
+ .unwrap_or(0);
+ let uuid = env::var("TEST_CROSS_PROCESS_LOCK_UUID")
+ .unwrap_or_else(|_| Uuid::new_v4().as_hyphenated().to_string());
+
+ match proc_num {
+ 0 => {
+ let mut handle1 = call_proc_num(1, &uuid);
+ sleep(Duration::from_millis(100));
+
+ let mut handle2 = call_proc_num(2, &uuid);
+ sleep(Duration::from_millis(200));
+
+ let lock = NamedLock::create(&uuid)?;
+ assert!(matches!(lock.try_lock(), Err(Error::WouldBlock)));
+ lock.lock().expect("failed to lock");
+
+ assert!(handle2.wait().unwrap().success());
+ assert!(handle1.wait().unwrap().success());
+ }
+ 1 => {
+ let lock =
+ NamedLock::create(&uuid).expect("failed to create lock");
+
+ let _guard = lock.lock().expect("failed to lock");
+ assert!(matches!(lock.try_lock(), Err(Error::WouldBlock)));
+ sleep(Duration::from_millis(200));
+ }
+ 2 => {
+ let lock =
+ NamedLock::create(&uuid).expect("failed to create lock");
+
+ assert!(matches!(lock.try_lock(), Err(Error::WouldBlock)));
+ let _guard = lock.lock().expect("failed to lock");
+ sleep(Duration::from_millis(300));
+ }
+ _ => unreachable!(),
+ }
+
+ Ok(())
+ }
+
+ #[test]
+ fn edge_cases() -> Result<()> {
+ let uuid = Uuid::new_v4().as_hyphenated().to_string();
+ let lock1 = NamedLock::create(&uuid)?;
+ let lock2 = NamedLock::create(&uuid)?;
+
+ {
+ let _guard1 = lock1.try_lock()?;
+ assert!(matches!(lock1.try_lock(), Err(Error::WouldBlock)));
+ assert!(matches!(lock2.try_lock(), Err(Error::WouldBlock)));
+ }
+
+ {
+ let _guard2 = lock2.try_lock()?;
+ assert!(matches!(lock1.try_lock(), Err(Error::WouldBlock)));
+ assert!(matches!(lock2.try_lock(), Err(Error::WouldBlock)));
+ }
+
+ Ok(())
+ }
+
+ #[test]
+ fn invalid_names() {
+ assert!(matches!(NamedLock::create(""), Err(Error::EmptyName)));
+
+ assert!(matches!(
+ NamedLock::create("abc/"),
+ Err(Error::InvalidCharacter)
+ ));
+
+ assert!(matches!(
+ NamedLock::create("abc\\"),
+ Err(Error::InvalidCharacter)
+ ));
+
+ assert!(matches!(
+ NamedLock::create("abc\0"),
+ Err(Error::InvalidCharacter)
+ ));
+ }
+}
diff --git a/src/unix.rs b/src/unix.rs
new file mode 100644
index 0000000..58b90ce
--- /dev/null
+++ b/src/unix.rs
@@ -0,0 +1,63 @@
+use libc::{LOCK_EX, LOCK_NB, LOCK_UN};
+use std::fs::{File, OpenOptions};
+use std::io;
+use std::os::unix::io::{AsRawFd, RawFd};
+use std::path::Path;
+
+use crate::error::*;
+
+#[derive(Debug)]
+pub(crate) struct RawNamedLock {
+ lock_file: File,
+}
+
+impl RawNamedLock {
+ pub(crate) fn create(lock_path: &Path) -> Result<RawNamedLock> {
+ let lock_file = OpenOptions::new()
+ .write(true)
+ .create_new(true)
+ .open(&lock_path)
+ .or_else(|_| OpenOptions::new().write(true).open(&lock_path))
+ .map_err(Error::CreateFailed)?;
+
+ Ok(RawNamedLock {
+ lock_file,
+ })
+ }
+
+ pub(crate) fn try_lock(&self) -> Result<()> {
+ unsafe { flock(self.lock_file.as_raw_fd(), LOCK_EX | LOCK_NB) }
+ }
+
+ pub(crate) fn lock(&self) -> Result<()> {
+ unsafe { flock(self.lock_file.as_raw_fd(), LOCK_EX) }
+ }
+
+ pub(crate) fn unlock(&self) -> Result<()> {
+ unsafe { flock(self.lock_file.as_raw_fd(), LOCK_UN) }
+ }
+}
+
+unsafe fn flock(fd: RawFd, operation: i32) -> Result<()> {
+ loop {
+ let rc = libc::flock(fd, operation);
+
+ if rc < 0 {
+ let err = io::Error::last_os_error();
+
+ if err.kind() == io::ErrorKind::Interrupted {
+ continue;
+ } else if err.kind() == io::ErrorKind::WouldBlock {
+ return Err(Error::WouldBlock);
+ } else if (operation & LOCK_EX) == LOCK_EX {
+ return Err(Error::LockFailed);
+ } else if (operation & LOCK_UN) == LOCK_UN {
+ return Err(Error::UnlockFailed);
+ }
+ }
+
+ break;
+ }
+
+ Ok(())
+}
diff --git a/src/windows.rs b/src/windows.rs
new file mode 100644
index 0000000..8f65877
--- /dev/null
+++ b/src/windows.rs
@@ -0,0 +1,73 @@
+use std::io;
+use std::ptr;
+use widestring::WideCString;
+use winapi::shared::winerror::WAIT_TIMEOUT;
+use winapi::um::handleapi::CloseHandle;
+use winapi::um::synchapi::{CreateMutexW, ReleaseMutex, WaitForSingleObject};
+use winapi::um::winbase::{INFINITE, WAIT_ABANDONED, WAIT_OBJECT_0};
+use winapi::um::winnt::HANDLE;
+
+use crate::error::*;
+
+#[derive(Debug)]
+pub(crate) struct RawNamedLock {
+ handle: HANDLE,
+}
+
+unsafe impl Sync for RawNamedLock {}
+unsafe impl Send for RawNamedLock {}
+
+impl RawNamedLock {
+ pub(crate) fn create(name: &str) -> Result<RawNamedLock> {
+ let name = WideCString::from_str(name).unwrap();
+ let handle = unsafe { CreateMutexW(ptr::null_mut(), 0, name.as_ptr()) };
+
+ if handle.is_null() {
+ Err(Error::CreateFailed(io::Error::last_os_error()))
+ } else {
+ Ok(RawNamedLock {
+ handle,
+ })
+ }
+ }
+
+ pub(crate) fn try_lock(&self) -> Result<()> {
+ let rc = unsafe { WaitForSingleObject(self.handle, 0) };
+
+ if rc == WAIT_OBJECT_0 || rc == WAIT_ABANDONED {
+ Ok(())
+ } else if rc == WAIT_TIMEOUT {
+ Err(Error::WouldBlock)
+ } else {
+ Err(Error::LockFailed)
+ }
+ }
+
+ pub(crate) fn lock(&self) -> Result<()> {
+ let rc = unsafe { WaitForSingleObject(self.handle, INFINITE) };
+
+ if rc == WAIT_OBJECT_0 || rc == WAIT_ABANDONED {
+ Ok(())
+ } else {
+ Err(Error::LockFailed)
+ }
+ }
+
+ pub(crate) fn unlock(&self) -> Result<()> {
+ let rc = unsafe { ReleaseMutex(self.handle) };
+
+ if rc == 0 {
+ Err(Error::UnlockFailed)
+ } else {
+ Ok(())
+ }
+ }
+}
+
+impl Drop for RawNamedLock {
+ fn drop(&mut self) {
+ unsafe {
+ CloseHandle(self.handle);
+ }
+ }
+}