fslock-0.2.1/.cargo_vcs_info.json0000644000000001360000000000100123150ustar { "git": { "sha1": "ad9154f992b1bdedc53d2f07d2974ccbbb539a3e" }, "path_in_vcs": "" }fslock-0.2.1/.github/workflows/check.yml000064400000000000000000000072760072674642500163460ustar 00000000000000name: fslock CI - Check on: [push, pull_request] jobs: check-linux: name: Check Linux runs-on: ubuntu-latest env: RUST_BACKTRACE: 1 steps: - name: Checkout sources uses: actions/checkout@v2 - name: Install stable toolchain uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true - name: Run cargo check uses: actions-rs/cargo@v1 with: command: check - name: Run cargo check without default features uses: actions-rs/cargo@v1 with: command: check args: --no-default-features check-windows: name: Check Windows runs-on: windows-latest env: RUST_BACKTRACE: 1 steps: - name: Checkout sources uses: actions/checkout@v2 - name: Install stable toolchain uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true - name: Run cargo check uses: actions-rs/cargo@v1 with: command: check - name: Run cargo check without default features uses: actions-rs/cargo@v1 with: command: check args: --no-default-features check-macos: name: Check MacOS runs-on: macos-latest env: RUST_BACKTRACE: 1 steps: - name: Checkout sources uses: actions/checkout@v2 - name: Install stable toolchain uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true - name: Run cargo check uses: actions-rs/cargo@v1 with: command: check - name: Run cargo check without default features uses: actions-rs/cargo@v1 with: command: check args: --no-default-features test-linux: name: Test Suite on Linux runs-on: ubuntu-latest env: RUST_BACKTRACE: 1 steps: - name: Checkout sources uses: actions/checkout@v2 - name: Install stable toolchain uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true - name: Run cargo test uses: actions-rs/cargo@v1 with: command: test - name: Run cargo test without default features uses: actions-rs/cargo@v1 with: command: test args: --no-default-features test-windows: name: Test Suite on Windows runs-on: windows-latest env: RUST_BACKTRACE: 1 steps: - name: Checkout sources uses: actions/checkout@v2 - name: Install stable toolchain uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true - name: Run cargo test uses: actions-rs/cargo@v1 with: command: test - name: Run cargo test without default features uses: actions-rs/cargo@v1 with: command: test args: --no-default-features test-macos: name: Test Suite on MacOS runs-on: macos-latest env: RUST_BACKTRACE: 1 steps: - name: Checkout sources uses: actions/checkout@v2 - name: Install stable toolchain uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true - name: Run cargo test uses: actions-rs/cargo@v1 with: command: test - name: Run cargo test without default features uses: actions-rs/cargo@v1 with: command: test args: --no-default-features fslock-0.2.1/.github/workflows/pages.yml000064400000000000000000000013200072674642500163500ustar 00000000000000name: fslock CI - GH Pages on: push: branches: - master jobs: docs: name: Documentation runs-on: ubuntu-latest env: RUST_BACKTRACE: 1 steps: - name: Checkout sources uses: actions/checkout@v2 - name: Install stable toolchain uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true - name: Run cargo doc uses: actions-rs/cargo@v1 with: command: doc args: --all-features - name: Deploy Docs uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./target/doc fslock-0.2.1/.gitignore000064400000000000000000000000440072674642500131230ustar 00000000000000/target Cargo.lock testfiles/*.lock fslock-0.2.1/.rustfmt.toml000064400000000000000000000026050072674642500136170ustar 00000000000000disable_all_formatting = false indent_style = "Block" use_small_heuristics = "Max" binop_separator = "Front" combine_control_expr = true comment_width = 80 condense_wildcard_suffixes = true control_brace_style = "AlwaysSameLine" error_on_line_overflow = false error_on_unformatted = false fn_args_layout = "Tall" brace_style = "SameLineWhere" empty_item_single_line = true fn_single_line = false where_single_line = false force_explicit_abi = true format_strings = true format_macro_matchers = true format_macro_bodies = true hard_tabs = false imports_indent = "Block" imports_layout = "HorizontalVertical" imports_granularity = "Crate" match_block_trailing_comma = true max_width = 80 merge_derives = true force_multiline_blocks = false newline_style = "Unix" normalize_comments = false remove_nested_parens = true reorder_imports = true reorder_modules = false reorder_impl_items = false report_todo = "Never" report_fixme = "Never" skip_children = false space_after_colon = true space_before_colon = false struct_field_align_threshold = 0 spaces_around_ranges = true struct_lit_single_line = true tab_spaces = 4 trailing_comma = "Vertical" trailing_semicolon = true type_punctuation_density = "Wide" use_field_init_shorthand = true use_try_shorthand = true wrap_comments = true match_arm_blocks = true blank_lines_upper_bound = 1 blank_lines_lower_bound = 0 hide_parse_errors = false unstable_features = true fslock-0.2.1/CHANGELOG.md000064400000000000000000000012140072674642500127440ustar 00000000000000# 0.2.1 * Added `try_lock_with_pid` method. * Corrected bug that would not seek lock files in UNIX (when writing PIDs), and so it would fill the previous bytes with nul-bytes. * Corrected bug that would always truncate files on opening in Windows # 0.2.0 * Writing PID on locked file via `lock_with_pid()` method. * Unix and Windows locks are now always per-handle. * NOTE: version 0.2.x in UNIX is incompatible with 0.1.x due to this change. * Removed multilock feature as it became obsolete. # 0.1.8 * Compiling on Android b32 # 0.1.7 * Support for locks per handle/fd via multilock feature # 0.1.6 * Fixed #1: now fslock compiles on arm fslock-0.2.1/Cargo.lock0000644000000020610000000000100102670ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "fslock" version = "0.2.1" dependencies = [ "libc", "winapi", ] [[package]] name = "libc" version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7f823d141fe0a24df1e23b4af4e3c7ba9e5966ec514ea068c93024aa7deb765" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" fslock-0.2.1/Cargo.toml0000644000000023500000000000100103130ustar # 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 = "fslock" version = "0.2.1" description = "A library to use files as locks" readme = "README.md" keywords = ["file", "lock", "lockfile", "filelock", "lock-file"] categories = ["filesystem", "no-std", "concurrency"] license = "MIT" repository = "https://github.com/brunoczim/fslock" [features] default = ["std"] std = [] [target."cfg(unix)".dependencies.libc] version = "^0.2.66" default-features = false [target."cfg(windows)".dependencies.winapi] version = "^0.3.8" features = ["minwindef", "minwinbase", "winbase", "errhandlingapi", "winerror", "winnt", "synchapi", "handleapi", "fileapi", "processthreadsapi"] [badges.maintenance] status = "passively-maintained" [badges.travis-ci] branch = "master" repository = "https://github.com/brunoczim/fslock" fslock-0.2.1/Cargo.toml.orig000064400000000000000000000015110072674642500140220ustar 00000000000000[package] name = "fslock" version = "0.2.1" edition = "2018" description = "A library to use files as locks" repository = "https://github.com/brunoczim/fslock" readme = "README.md" keywords = ["file", "lock", "lockfile", "filelock", "lock-file"] categories = ["filesystem", "no-std", "concurrency"] license = "MIT" [badges] maintenance = { status = "passively-maintained" } [badges.travis-ci] repository = "https://github.com/brunoczim/fslock" branch = "master" [target.'cfg(unix)'.dependencies.libc] version = "^0.2.66" default-features = false [target.'cfg(windows)'.dependencies.winapi] version = "^0.3.8" features = [ "minwindef", "minwinbase", "winbase", "errhandlingapi", "winerror", "winnt", "synchapi", "handleapi", "fileapi", "processthreadsapi" ] [features] default = ["std"] std = [] fslock-0.2.1/LICENSE000064400000000000000000000020520072674642500121410ustar 00000000000000MIT License Copyright (c) 2019 brunoczim 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. fslock-0.2.1/README.md000064400000000000000000000013100072674642500124070ustar 00000000000000# fslock API to use files as a lock. Supports non-std crates by disabling feature `std`. # Types Currently, only one type is provided: [`LockFile`]. It does not destroy the file after closed and behaviour on locking different file handles owned by the same process is different between Unix and Windows, unless you activate the `multilock` feature, which enables the `open_excl` method that locks files per file descriptor/handle on all platforms. # Example ```rust use fslock::LockFile; fn main() -> Result<(), fslock::Error> { let mut file = LockFile::open("mylock")?; file.lock()?; do_stuff(); file.unlock()?; Ok(()) } ``` # Docs on Master https://brunoczim.github.io/fslock/fslock fslock-0.2.1/examples/locks_until_nl.rs000064400000000000000000000011100072674642500163310ustar 00000000000000#[cfg(feature = "std")] use fslock::LockFile; #[cfg(feature = "std")] use std::{env, io, io::Read, process}; #[cfg(feature = "std")] fn main() -> Result<(), fslock::Error> { let mut args = env::args(); args.next(); let path = match args.next() { Some(arg) if args.next().is_none() => arg, _ => { eprintln!("Expected one argument"); process::exit(1); }, }; let mut lockfile = LockFile::open(&path)?; lockfile.lock()?; io::stdin().read(&mut [0; 1])?; Ok(()) } #[cfg(not(feature = "std"))] fn main() {} fslock-0.2.1/examples/try_lock.rs000064400000000000000000000011550072674642500151510ustar 00000000000000#[cfg(feature = "std")] use fslock::LockFile; #[cfg(feature = "std")] use std::{env, process}; #[cfg(feature = "std")] fn main() -> Result<(), fslock::Error> { let mut args = env::args(); args.next(); let path = match args.next() { Some(arg) if args.next().is_none() => arg, _ => { eprintln!("Expected one argument"); process::exit(1); }, }; let mut lockfile = LockFile::open(&path)?; if lockfile.try_lock()? { println!("SUCCESS"); } else { println!("FAILURE"); } Ok(()) } #[cfg(not(feature = "std"))] fn main() {} fslock-0.2.1/examples/try_lock_with_pid.rs000064400000000000000000000016210072674642500170360ustar 00000000000000#[cfg(feature = "std")] use fslock::LockFile; #[cfg(feature = "std")] use std::{env, fs::read_to_string, process}; #[cfg(feature = "std")] fn main() -> Result<(), fslock::Error> { let mut args = env::args(); args.next(); let path = match args.next() { Some(arg) if args.next().is_none() => arg, _ => { eprintln!("Expected one argument"); process::exit(1); }, }; let mut lockfile = LockFile::open(&path)?; if lockfile.try_lock_with_pid()? { let content_a = read_to_string(&path)?; let content_b = read_to_string(&path)?; assert!(content_a.trim().len() > 0); assert!(content_a.trim().chars().all(|ch| ch.is_ascii_digit())); assert_eq!(content_a, content_b); println!("{}", content_a); } else { println!("FAILURE"); } Ok(()) } #[cfg(not(feature = "std"))] fn main() {} fslock-0.2.1/src/fmt.rs000064400000000000000000000047660072674642500130750ustar 00000000000000//! This module implements formatting functions for writing into lock files. use crate::sys; use core::{ fmt::{self, Write}, mem, }; /// I/O buffer size, chosen targeting possible PID's digits (I belive 11 would /// be enough tho). const BUF_SIZE: usize = 16; /// A fmt Writer that writes data into the given open file. #[derive(Debug, Clone, Copy)] pub struct Writer( /// The open file to which data will be written. pub sys::FileDesc, ); impl Writer { /// Writes formatting arguments into the file. pub fn write_fmt( &self, arguments: fmt::Arguments, ) -> Result<(), sys::Error> { let mut adapter = Adapter::new(self.0); let _ = adapter.write_fmt(arguments); adapter.finish() } } /// Fmt <-> IO adapter. /// /// Buffer is flushed on drop. #[derive(Debug)] struct Adapter { /// File being written to. desc: sys::FileDesc, /// Temporary buffer of bytes being written. buffer: [u8; BUF_SIZE], /// Cursor tracking where new bytes should be written at the buffer. cursor: usize, /// Partial result for writes. result: Result<(), sys::Error>, } impl Adapter { /// Creates a zeroed adapter from an open file. fn new(desc: sys::FileDesc) -> Self { Self { desc, buffer: [0; BUF_SIZE], cursor: 0, result: Ok(()) } } /// Flushes the buffer into the open file. fn flush(&mut self) -> Result<(), sys::Error> { sys::write(self.desc, &self.buffer[.. self.cursor])?; self.buffer = [0; BUF_SIZE]; self.cursor = 0; Ok(()) } /// Finishes the adapter, returning the I/O Result fn finish(mut self) -> Result<(), sys::Error> { mem::replace(&mut self.result, Ok(())) } } impl Write for Adapter { fn write_str(&mut self, data: &str) -> fmt::Result { let mut bytes = data.as_bytes(); while bytes.len() > 0 && self.result.is_ok() { let start = self.cursor; let size = (BUF_SIZE - self.cursor).min(bytes.len()); let end = start + size; self.buffer[start .. end].copy_from_slice(&bytes[.. size]); self.cursor = end; bytes = &bytes[size ..]; if bytes.len() > 0 { self.result = self.flush(); } } match self.result { Ok(_) => Ok(()), Err(_) => Err(fmt::Error), } } } impl Drop for Adapter { fn drop(&mut self) { let _ = self.flush(); let _ = sys::fsync(self.desc); } } fslock-0.2.1/src/lib.rs000064400000000000000000000265500072674642500130500ustar 00000000000000#![cfg_attr(not(feature = "std"), no_std)] //! API to use files as a lock. Supports non-std crates by disabling feature //! `std`. //! //! # Types //! Currently, only one type is provided: [`LockFile`]. It does not destroy the //! file after closed. Locks are per-handle and not by per-process in any //! platform. On Unix, however, under `fork` file descriptors might be //! duplicated sharing the same lock, but `fork` is usually `unsafe` in Rust. //! //! # Example //! ``` //! use fslock::LockFile; //! fn main() -> Result<(), fslock::Error> { //! //! let mut file = LockFile::open("testfiles/mylock.lock")?; //! file.lock()?; //! do_stuff(); //! file.unlock()?; //! //! Ok(()) //! } //! # fn do_stuff() { //! # // doing stuff here. //! # } //! ``` #[cfg(test)] mod test; #[cfg(unix)] mod unix; #[cfg(unix)] use crate::unix as sys; mod string; mod fmt; #[cfg(windows)] mod windows; #[cfg(windows)] use crate::windows as sys; pub use crate::{ string::{EitherOsStr, IntoOsString, ToOsStr}, sys::{Error, OsStr, OsString}, }; #[derive(Debug)] /// A handle to a file that is lockable. Does not delete the file. On both /// Unix and Windows, the lock is held by an individual handle, and not by the /// whole process. On Unix, however, under `fork` file descriptors might be /// duplicated sharing the same lock, but `fork` is usually `unsafe` in Rust. /// /// # Example /// ``` /// # fn main() -> Result<(), fslock::Error> { /// use fslock::LockFile; /// /// let mut file = LockFile::open("testfiles/mylock.lock")?; /// file.lock()?; /// do_stuff(); /// file.unlock()?; /// /// # Ok(()) /// # } /// # fn do_stuff() { /// # // doing stuff here. /// # } /// ``` pub struct LockFile { locked: bool, desc: sys::FileDesc, } impl LockFile { /// Opens a file for locking, with OS-dependent locking behavior. On Unix, /// if the path is nul-terminated (ends with 0), no extra allocation will be /// made. /// /// # Compatibility /// /// This crate used to behave differently in regards to Unix and Windows, /// when locks on Unix were per-process and not per-handle. However, the /// current version locks per-handle on any platform. On Unix, however, /// under `fork` file descriptors might be duplicated sharing the same lock, /// but `fork` is usually `unsafe` in Rust. /// /// # Panics /// Panics if the path contains a nul-byte in a place other than the end. /// /// # Example /// /// ``` /// # fn main() -> Result<(), fslock::Error> { /// use fslock::LockFile; /// /// let mut file = LockFile::open("testfiles/regular.lock")?; /// /// # Ok(()) /// # } /// ``` /// /// # Panicking Example /// /// ```should_panic /// # fn main() -> Result<(), fslock::Error> { /// use fslock::LockFile; /// /// let mut file = LockFile::open("my\0lock")?; /// /// # Ok(()) /// # } /// ``` pub fn open

(path: &P) -> Result where P: ToOsStr + ?Sized, { let path = path.to_os_str()?; let desc = sys::open(path.as_ref())?; Ok(Self { locked: false, desc }) } /// Locks this file. Blocks while it is not possible to lock (i.e. someone /// else already owns a lock). After locked, if no attempt to unlock is /// made, it will be automatically unlocked on the file handle drop. /// /// # Panics /// Panics if this handle already owns the file. /// /// # Example /// /// ``` /// # fn main() -> Result<(), fslock::Error> { /// use fslock::LockFile; /// /// let mut file = LockFile::open("testfiles/target.lock")?; /// file.lock()?; /// do_stuff(); /// file.unlock()?; /// /// # Ok(()) /// # } /// # fn do_stuff() { /// # // doing stuff here. /// # } /// ``` /// /// # Panicking Example /// /// ```should_panic /// # fn main() -> Result<(), fslock::Error> { /// use fslock::LockFile; /// /// let mut file = LockFile::open("testfiles/panicking.lock")?; /// file.lock()?; /// file.lock()?; /// /// # Ok(()) /// # } /// ``` pub fn lock(&mut self) -> Result<(), Error> { if self.locked { panic!("Cannot lock if already owning a lock"); } sys::lock(self.desc)?; self.locked = true; Ok(()) } /// Locks this file and writes this process's PID into the file, which will /// be erased on unlock. Like [`LockFile::lock`], blocks while it is not /// possible to lock. After locked, if no attempt to unlock is made, it will /// be automatically unlocked on the file handle drop. /// /// # Panics /// Panics if this handle already owns the file. /// /// # Example /// /// ``` /// # fn main() -> Result<(), fslock::Error> { /// use fslock::LockFile; /// # #[cfg(feature = "std")] /// use std::fs::read_to_string; /// /// let mut file = LockFile::open("testfiles/withpid.lock")?; /// file.lock_with_pid()?; /// # #[cfg(feature = "std")] /// # { /// do_stuff()?; /// # } /// file.unlock()?; /// /// # #[cfg(feature = "std")] /// fn do_stuff() -> Result<(), fslock::Error> { /// let mut content = read_to_string("testfiles/withpid.lock")?; /// assert!(content.trim().len() > 0); /// assert!(content.trim().chars().all(|ch| ch.is_ascii_digit())); /// Ok(()) /// } /// /// # Ok(()) /// # } /// ``` pub fn lock_with_pid(&mut self) -> Result<(), Error> { if let Err(error) = self.lock() { return Err(error); } let result = writeln!(fmt::Writer(self.desc), "{}", sys::pid()); if result.is_err() { let _ = self.unlock(); } result } /// Locks this file. Does NOT block if it is not possible to lock (i.e. /// someone else already owns a lock). After locked, if no attempt to /// unlock is made, it will be automatically unlocked on the file handle /// drop. /// /// # Panics /// Panics if this handle already owns the file. /// /// # Example /// /// ``` /// # fn main() -> Result<(), fslock::Error> { /// use fslock::LockFile; /// /// let mut file = LockFile::open("testfiles/attempt.lock")?; /// if file.try_lock()? { /// do_stuff(); /// file.unlock()?; /// } /// /// # Ok(()) /// # } /// # fn do_stuff() { /// # // doing stuff here. /// # } /// ``` /// /// # Panicking Example /// /// ```should_panic /// # fn main() -> Result<(), fslock::Error> { /// use fslock::LockFile; /// /// let mut file = LockFile::open("testfiles/attempt_panic.lock")?; /// file.lock()?; /// file.try_lock()?; /// /// # Ok(()) /// # } /// ``` pub fn try_lock(&mut self) -> Result { if self.locked { panic!("Cannot lock if already owning a lock"); } let lock_result = sys::try_lock(self.desc); if let Ok(true) = lock_result { self.locked = true; } lock_result } /// Locks this file and writes this process's PID into the file, which will /// be erased on unlock. Does NOT block if it is not possible to lock (i.e. /// someone else already owns a lock). After locked, if no attempt to /// unlock is made, it will be automatically unlocked on the file handle /// drop. /// /// # Panics /// Panics if this handle already owns the file. /// /// # Example /// /// ``` /// # #[cfg(feature = "std")] /// # use std::fs::read_to_string; /// # fn main() -> Result<(), fslock::Error> { /// use fslock::LockFile; /// /// let mut file = LockFile::open("testfiles/pid_attempt.lock")?; /// if file.try_lock_with_pid()? { /// # #[cfg(feature = "std")] /// # { /// do_stuff()?; /// # } /// file.unlock()?; /// } /// /// # Ok(()) /// # } /// # #[cfg(feature = "std")] /// fn do_stuff() -> Result<(), fslock::Error> { /// let mut content = read_to_string("testfiles/pid_attempt.lock")?; /// assert!(content.trim().len() > 0); /// assert!(content.trim().chars().all(|ch| ch.is_ascii_digit())); /// Ok(()) /// } /// ``` /// /// # Panicking Example /// /// ```should_panic /// # fn main() -> Result<(), fslock::Error> { /// use fslock::LockFile; /// /// let mut file = LockFile::open("testfiles/pid_attempt_panic.lock")?; /// file.lock_with_pid()?; /// file.try_lock_with_pid()?; /// /// # Ok(()) /// # } /// ``` pub fn try_lock_with_pid(&mut self) -> Result { match self.try_lock() { Ok(true) => (), Ok(false) => return Ok(false), Err(error) => return Err(error), } let result = sys::truncate(self.desc) .and_then(|_| writeln!(fmt::Writer(self.desc), "{}", sys::pid())); if result.is_err() { let _ = self.unlock(); } result.map(|_| true) } /// Returns whether this file handle owns the lock. /// /// # Example /// ``` /// use fslock::LockFile; /// # fn main() -> Result<(), fslock::Error> { /// /// let mut file = LockFile::open("testfiles/maybeowned.lock")?; /// do_stuff_with_lock(&mut file); /// if !file.owns_lock() { /// file.lock()?; /// do_stuff(); /// file.unlock()?; /// } /// /// # Ok(()) /// # } /// # fn do_stuff_with_lock(_lock: &mut LockFile) { /// # // doing stuff here. /// # } /// # fn do_stuff() { /// # // doing stuff here. /// # } /// ``` pub fn owns_lock(&self) -> bool { self.locked } /// Unlocks this file. This file handle must own the file lock. If not /// called manually, it is automatically called on `drop`. /// /// # Panics /// Panics if this handle does not own the file. /// /// # Example /// /// ``` /// # fn main() -> Result<(), fslock::Error> { /// use fslock::LockFile; /// /// let mut file = LockFile::open("testfiles/endinglock.lock")?; /// file.lock()?; /// do_stuff(); /// file.unlock()?; /// /// # Ok(()) /// # } /// # fn do_stuff() { /// # // doing stuff here. /// # } /// ``` /// /// # Panicking Example /// /// ```should_panic /// # fn main() -> Result<(), fslock::Error> { /// use fslock::LockFile; /// /// let mut file = LockFile::open("testfiles/endinglock.lock")?; /// file.unlock()?; /// /// # Ok(()) /// # } /// ``` pub fn unlock(&mut self) -> Result<(), Error> { if !self.locked { panic!("Attempted to unlock already locked lockfile"); } self.locked = false; sys::unlock(self.desc)?; sys::truncate(self.desc)?; Ok(()) } } impl Drop for LockFile { fn drop(&mut self) { if self.locked { let _ = self.unlock(); } sys::close(self.desc); } } // Safe because: // 1. We never actually access the contents of the pointer that represents the // Windows Handle. // // 2. We require a mutable reference to actually mutate the file // system. #[cfg(windows)] unsafe impl Send for LockFile {} #[cfg(windows)] unsafe impl Sync for LockFile {} fslock-0.2.1/src/string.rs000064400000000000000000000105540072674642500136050ustar 00000000000000//! This module implements common functionalities for OS's strings. use crate::sys::{Error, OsStr, OsString}; use core::{fmt, ops::Deref}; #[cfg(feature = "std")] use std::{ ffi, path::{Path, PathBuf}, }; impl Clone for OsString { fn clone(&self) -> Self { self.to_os_str() .and_then(|str| str.into_os_string()) .expect("Allocation error") } } impl fmt::Debug for OsString { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { write!(fmt, "{:?}", self.as_ref()) } } impl fmt::Display for OsString { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { write!(fmt, "{}", self.as_ref()) } } impl Deref for OsString { type Target = OsStr; fn deref(&self) -> &OsStr { self.as_ref() } } /// Either borrowed or owned allocation of an OS-native string. #[derive(Debug)] pub enum EitherOsStr<'str> { /// Borrowed allocation. Borrowed(&'str OsStr), /// Owned allocation. Owned(OsString), } impl<'str> AsRef for EitherOsStr<'str> { fn as_ref(&self) -> &OsStr { match self { Self::Borrowed(str) => str, Self::Owned(string) => string.as_ref(), } } } impl<'str> Deref for EitherOsStr<'str> { type Target = OsStr; fn deref(&self) -> &OsStr { self.as_ref() } } /// Conversion of anything into an owned OS-native string. If allocation fails, /// an error shall be returned. pub trait IntoOsString { /// Converts with possible allocation error. fn into_os_string(self) -> Result; } impl IntoOsString for OsString { fn into_os_string(self) -> Result { Ok(self) } } impl<'str> IntoOsString for EitherOsStr<'str> { fn into_os_string(self) -> Result { match self { Self::Borrowed(str) => str.into_os_string(), Self::Owned(string) => Ok(string), } } } #[cfg(feature = "std")] impl<'str> IntoOsString for &'str ffi::OsStr { fn into_os_string(self) -> Result { self.to_os_str()?.into_os_string() } } #[cfg(feature = "std")] impl IntoOsString for PathBuf { fn into_os_string(self) -> Result { (*self).into_os_string() } } #[cfg(feature = "std")] impl<'str> IntoOsString for &'str Path { fn into_os_string(self) -> Result { AsRef::::as_ref(self).to_os_str()?.into_os_string() } } #[cfg(feature = "std")] impl IntoOsString for ffi::OsString { fn into_os_string(self) -> Result { (*self).into_os_string() } } impl<'str> IntoOsString for &'str str { fn into_os_string(self) -> Result { self.to_os_str()?.into_os_string() } } #[cfg(feature = "std")] impl IntoOsString for String { fn into_os_string(self) -> Result { self.to_os_str()?.into_os_string() } } #[cfg(feature = "std")] impl ToOsStr for String { fn to_os_str(&self) -> Result { (**self).to_os_str() } } /// Conversion of anything to an either borrowed or owned OS-native string. If /// allocation fails, an error shall be returned. pub trait ToOsStr { /// Converts with possible allocation error. fn to_os_str(&self) -> Result; } impl<'str> ToOsStr for EitherOsStr<'str> { fn to_os_str(&self) -> Result { Ok(match self { EitherOsStr::Owned(string) => { EitherOsStr::Owned(string.to_os_str()?.into_os_string()?) }, EitherOsStr::Borrowed(str) => EitherOsStr::Borrowed(str), }) } } impl ToOsStr for OsStr { fn to_os_str(&self) -> Result { Ok(EitherOsStr::Borrowed(self)) } } impl ToOsStr for OsString { fn to_os_str(&self) -> Result { Ok(EitherOsStr::Borrowed(self.as_ref())) } } #[cfg(feature = "std")] impl ToOsStr for ffi::OsString { fn to_os_str(&self) -> Result { (**self).to_os_str() } } #[cfg(feature = "std")] impl ToOsStr for PathBuf { fn to_os_str(&self) -> Result { (**self).to_os_str() } } #[cfg(feature = "std")] impl ToOsStr for Path { fn to_os_str(&self) -> Result { AsRef::::as_ref(self).to_os_str() } } fslock-0.2.1/src/test.rs000064400000000000000000000112720072674642500132540ustar 00000000000000use crate::{Error, LockFile}; use core::str; #[cfg(feature = "std")] #[test] fn read_pid() -> Result<(), Error> { use std::fs::read_to_string; let path = "testfiles/read_pid.lock"; let mut file = LockFile::open(path)?; file.lock_with_pid()?; let content_a = read_to_string(path)?; let content_b = read_to_string(path)?; assert!(content_a.trim().len() > 0); assert!(content_a.trim().chars().all(|ch| ch.is_ascii_digit())); assert_eq!(content_a, content_b); Ok(()) } #[cfg(feature = "std")] #[test] fn try_read_pid() -> Result<(), Error> { use std::fs::read_to_string; let path = "testfiles/try_read_pid.lock"; let mut file = LockFile::open(path)?; assert!(file.try_lock_with_pid()?); let content_a = read_to_string(path)?; let content_b = read_to_string(path)?; assert!(content_a.trim().len() > 0); assert!(content_a.trim().chars().all(|ch| ch.is_ascii_digit())); assert_eq!(content_a, content_b); Ok(()) } #[cfg(feature = "std")] fn check_try_lock_example( lockpath: &str, expected: &[u8], ) -> Result<(), Error> { use std::process::{Command, Stdio}; let child = Command::new("cargo") .arg("run") .arg("-q") .arg("--example") .arg("try_lock") .arg("--") .arg(lockpath) .stdout(Stdio::piped()) .spawn()?; let output = child.wait_with_output()?; assert!(output.status.success()); assert_eq!(output.stderr, b""); assert_eq!(output.stdout, expected); Ok(()) } #[derive(Debug, Clone)] enum TryPidExpectedRes<'pid> { Success { pid_to_differ: &'pid str }, Failure, } #[cfg(feature = "std")] fn check_try_lock_with_pid_example( lockpath: &str, expected: TryPidExpectedRes, ) -> Result<(), Error> { use std::process::{Command, Stdio}; let child = Command::new("cargo") .arg("run") .arg("-q") .arg("--example") .arg("try_lock_with_pid") .arg("--") .arg(lockpath) .stdout(Stdio::piped()) .spawn()?; let output = child.wait_with_output()?; assert!(output.status.success()); assert_eq!(output.stderr, b""); match expected { TryPidExpectedRes::Success { pid_to_differ: pid } => { let output = str::from_utf8(&output.stdout).unwrap(); assert!(output.trim().len() > 0); assert!(output.trim().chars().all(|ch| ch.is_ascii_digit())); assert_ne!(output.trim(), pid); }, TryPidExpectedRes::Failure => assert_eq!(output.stdout, b"FAILURE\n"), } Ok(()) } #[cfg(feature = "std")] #[test] fn other_process() -> Result<(), Error> { let path = "testfiles/other_process.lock"; let mut file = LockFile::open(path)?; file.lock()?; check_try_lock_example(path, b"FAILURE\n")?; file.unlock()?; check_try_lock_example(path, b"SUCCESS\n")?; Ok(()) } #[cfg(feature = "std")] #[test] fn other_process_pid() -> Result<(), Error> { use std::fs::read_to_string; let path = "testfiles/other_process_pid.lock"; let mut file = LockFile::open(path)?; assert!(file.try_lock_with_pid()?); let content = read_to_string(path)?; assert!(content.trim().len() > 0); assert!(content.trim().chars().all(|ch| ch.is_ascii_digit())); check_try_lock_example(path, b"FAILURE\n")?; let content_again = read_to_string(path)?; assert!(content_again.trim().len() > 0); assert!(content_again.trim().chars().all(|ch| ch.is_ascii_digit())); file.unlock()?; check_try_lock_example(path, b"SUCCESS\n")?; let child_content = read_to_string(path)?; assert!(child_content.trim().len() == 0); assert!(file.try_lock_with_pid()?); let content_again = read_to_string(path)?; assert_eq!(content_again, content); check_try_lock_with_pid_example(path, TryPidExpectedRes::Failure)?; let content_again = read_to_string(path)?; assert!(content_again.trim().len() > 0); assert!(content_again.trim().chars().all(|ch| ch.is_ascii_digit())); file.unlock()?; check_try_lock_with_pid_example( path, TryPidExpectedRes::Success { pid_to_differ: &content }, )?; let child_content = read_to_string(path)?; assert!(child_content.trim().len() == 0); Ok(()) } #[cfg(feature = "std")] #[test] fn other_process_but_curr_reads() -> Result<(), Error> { use std::fs::read_to_string; let path = "testfiles/other_process_but_curr_reads.lock"; let mut file = LockFile::open(path)?; file.lock()?; check_try_lock_example(path, b"FAILURE\n")?; let mut _content = read_to_string(path)?; check_try_lock_example(path, b"FAILURE\n")?; file.unlock()?; check_try_lock_example(path, b"SUCCESS\n")?; Ok(()) } fslock-0.2.1/src/unix.rs000064400000000000000000000227260072674642500132660ustar 00000000000000use crate::{EitherOsStr, IntoOsString, ToOsStr}; use core::{fmt, mem::transmute, ptr::NonNull, slice, str}; #[cfg(feature = "std")] use std::{ffi, os::unix::ffi::OsStrExt}; #[cfg(not(feature = "std"))] extern "C" { /// Yeah, I had to copy this from std #[cfg(not(target_os = "dragonfly"))] #[cfg_attr( any( target_os = "linux", target_os = "emscripten", target_os = "fuchsia", target_os = "l4re" ), link_name = "__errno_location" )] #[cfg_attr( any( target_os = "netbsd", target_os = "openbsd", target_os = "android", target_os = "redox", target_env = "newlib" ), link_name = "__errno" )] #[cfg_attr(target_os = "solaris", link_name = "___errno")] #[cfg_attr( any(target_os = "macos", target_os = "ios", target_os = "freebsd"), link_name = "__error" )] #[cfg_attr(target_os = "haiku", link_name = "_errnop")] fn errno_location() -> *mut libc::c_int; } #[cfg(not(feature = "std"))] fn errno() -> libc::c_int { unsafe { *errno_location() } } #[cfg(feature = "std")] fn errno() -> libc::c_int { Error::last_os_error().raw_os_error().unwrap_or(0) as libc::c_int } /// A type representing file descriptor on Unix. pub type FileDesc = libc::c_int; /// A type representing Process ID on Unix. pub type Pid = libc::pid_t; #[cfg(feature = "std")] /// An IO error. pub type Error = std::io::Error; #[cfg(not(feature = "std"))] #[derive(Debug)] /// An IO error. Without std, you can only get a message or an OS error code. pub struct Error { code: i32, } #[cfg(not(feature = "std"))] impl Error { /// Creates an error from a raw OS error code. pub fn from_raw_os_error(code: i32) -> Self { Self { code } } /// Creates an error from the last OS error code. pub fn last_os_error() -> Error { Self::from_raw_os_error(errno() as i32) } /// Raw OS error code. Returns option for compatibility with std. pub fn raw_os_error(&self) -> Option { Some(self.code) } } #[cfg(not(feature = "std"))] impl fmt::Display for Error { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { let msg_ptr = unsafe { libc::strerror(self.code as libc::c_int) }; let len = unsafe { libc::strlen(msg_ptr) }; let slice = unsafe { slice::from_raw_parts(msg_ptr, len) }; write!(fmt, "{}", unsafe { OsStr::from_slice(slice) })?; Ok(()) } } /// Owned allocation of an OS-native string. pub struct OsString { alloc: NonNull, /// Length without the nul-byte. len: usize, } impl Drop for OsString { fn drop(&mut self) { let ptr = self.alloc.as_ptr() as *mut libc::c_void; unsafe { libc::free(ptr) } } } impl AsRef for OsString { fn as_ref(&self) -> &OsStr { unsafe { OsStr::from_slice(slice::from_raw_parts( self.alloc.as_ptr(), self.len, )) } } } /// Borrowed allocation of an OS-native string. #[repr(transparent)] pub struct OsStr { bytes: [libc::c_char], } impl OsStr { /// Unsafe cause sequence needs to end with 0. unsafe fn from_slice(slice: &[libc::c_char]) -> &Self { transmute(slice) } } impl fmt::Debug for OsStr { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { let mut first = false; write!(fmt, "[")?; for &signed in &self.bytes { let byte = signed as u8; if first { first = false; } else { write!(fmt, ", ")?; } if byte.is_ascii() { write!(fmt, "{:?}", char::from(byte))?; } else { write!(fmt, "'\\x{:x}'", byte)?; } } write!(fmt, "]")?; Ok(()) } } impl fmt::Display for OsStr { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { let ptr = self.bytes.as_ptr(); let len = self.bytes.len(); let slice = unsafe { slice::from_raw_parts(ptr as _, len) }; let mut sub = slice; while sub.len() > 0 { match str::from_utf8(sub) { Ok(string) => { write!(fmt, "{}", string)?; sub = &[]; }, Err(err) => { let string = str::from_utf8(&sub[.. err.valid_up_to()]) .expect("Inconsistent utf8 error"); write!(fmt, "{}�", string,)?; sub = &sub[err.valid_up_to() + 1 ..]; }, } } Ok(()) } } impl<'str> IntoOsString for &'str OsStr { fn into_os_string(self) -> Result { let len = self.bytes.len(); let alloc = unsafe { libc::malloc(len + 1) }; let alloc = match NonNull::new(alloc as *mut libc::c_char) { Some(alloc) => alloc, None => { return Err(Error::last_os_error()); }, }; unsafe { libc::memcpy( alloc.as_ptr() as *mut libc::c_void, self.bytes.as_ptr() as *const libc::c_void, len + 1, ); } Ok(OsString { alloc, len }) } } impl ToOsStr for str { fn to_os_str(&self) -> Result { make_os_str(self.as_bytes()) } } #[cfg(feature = "std")] impl ToOsStr for ffi::OsStr { fn to_os_str(&self) -> Result { make_os_str(self.as_bytes()) } } /// Path must not contain a nul-byte in the middle, but a nul-byte in the end /// (and only in the end) is allowed, which in this case no extra allocation /// will be made. Otherwise, an extra allocation is made. fn make_os_str(slice: &[u8]) -> Result { if let Some((&last, init)) = slice.split_last() { if init.contains(&0) { panic!("Path to file cannot contain nul-byte in the middle"); } if last == 0 { let str = unsafe { OsStr::from_slice(transmute(slice)) }; return Ok(EitherOsStr::Borrowed(str)); } } let alloc = unsafe { libc::malloc(slice.len() + 1) }; let alloc = match NonNull::new(alloc as *mut libc::c_char) { Some(alloc) => alloc, None => { return Err(Error::last_os_error()); }, }; unsafe { libc::memcpy( alloc.as_ptr() as *mut libc::c_void, slice.as_ptr() as *const libc::c_void, slice.len(), ); *alloc.as_ptr().add(slice.len()) = 0; } Ok(EitherOsStr::Owned(OsString { alloc, len: slice.len() })) } /// Returns the ID of the current process. pub fn pid() -> Pid { unsafe { libc::getpid() } } /// Opens a file with only purpose of locking it. Creates it if it does not /// exist. Path must not contain a nul-byte in the middle, but a nul-byte in the /// end (and only in the end) is allowed, which in this case no extra allocation /// will be made. Otherwise, an extra allocation is made. pub fn open(path: &OsStr) -> Result { let fd = unsafe { libc::open( path.bytes.as_ptr(), libc::O_RDWR | libc::O_CLOEXEC | libc::O_CREAT, (libc::S_IRUSR | libc::S_IWUSR | libc::S_IRGRP | libc::S_IROTH) as libc::c_int, ) }; if fd >= 0 { Ok(fd) } else { Err(Error::last_os_error()) } } /// Writes data into the given open file. pub fn write(fd: FileDesc, mut bytes: &[u8]) -> Result<(), Error> { while bytes.len() > 0 { let written = unsafe { libc::write(fd, bytes.as_ptr() as *const libc::c_void, bytes.len()) }; if written < 0 && errno() != libc::EAGAIN { return Err(Error::last_os_error()); } bytes = &bytes[written as usize ..]; } Ok(()) } pub fn fsync(fd: FileDesc) -> Result<(), Error> { let result = unsafe { libc::fsync(fd) }; if result >= 0 { Ok(()) } else { Err(Error::last_os_error()) } } /// Truncates the file referenced by the given file descriptor and seeks it to /// the start. pub fn truncate(fd: FileDesc) -> Result<(), Error> { let res = unsafe { libc::lseek(fd, 0, libc::SEEK_SET) }; if res < 0 { return Err(Error::last_os_error()); } let res = unsafe { libc::ftruncate(fd, 0) }; if res < 0 { Err(Error::last_os_error()) } else { Ok(()) } } /// Tries to lock a file and blocks until it is possible to lock. pub fn lock(fd: FileDesc) -> Result<(), Error> { let res = unsafe { libc::flock(fd, libc::LOCK_EX) }; if res >= 0 { Ok(()) } else { Err(Error::last_os_error()) } } /// Tries to lock a file but returns as soon as possible if already locked. pub fn try_lock(fd: FileDesc) -> Result { let res = unsafe { libc::flock(fd, libc::LOCK_EX | libc::LOCK_NB) }; if res >= 0 { Ok(true) } else { let err = errno(); if err == libc::EWOULDBLOCK || err == libc::EINTR { Ok(false) } else { Err(Error::from_raw_os_error(err as i32)) } } } /// Unlocks the file. pub fn unlock(fd: FileDesc) -> Result<(), Error> { let res = unsafe { libc::flock(fd, libc::LOCK_UN) }; if res >= 0 { Ok(()) } else { Err(Error::last_os_error()) } } /// Closes the file. pub fn close(fd: FileDesc) { unsafe { libc::close(fd) }; } fslock-0.2.1/src/windows.rs000064400000000000000000000327070072674642500137750ustar 00000000000000#[cfg(not(feature = "std"))] use winapi::um::{ winbase::{ FormatMessageW, FORMAT_MESSAGE_ALLOCATE_BUFFER, FORMAT_MESSAGE_FROM_SYSTEM, FORMAT_MESSAGE_IGNORE_INSERTS, }, winnt::{LANG_USER_DEFAULT, LPWSTR}, }; #[cfg(feature = "std")] use std::{ffi, os::windows::ffi::OsStrExt}; use crate::{EitherOsStr, IntoOsString, ToOsStr}; use core::{ convert::TryFrom, fmt, mem::{transmute, MaybeUninit}, ptr::{self, NonNull}, slice, }; use winapi::{ shared::{ minwindef::{DWORD, FALSE, LPCVOID, LPVOID, TRUE}, winerror::{ERROR_INVALID_DATA, ERROR_LOCK_VIOLATION}, }, um::{ errhandlingapi::GetLastError, fileapi::{ CreateFileW, FlushFileBuffers, LockFileEx, SetEndOfFile, SetFilePointer, UnlockFileEx, WriteFile, INVALID_SET_FILE_POINTER, OPEN_ALWAYS, }, handleapi::{CloseHandle, INVALID_HANDLE_VALUE}, minwinbase::{ OVERLAPPED_u, LMEM_FIXED, LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY, LPOVERLAPPED, LPSECURITY_ATTRIBUTES, OVERLAPPED, SECURITY_ATTRIBUTES, }, processthreadsapi::GetCurrentProcessId, synchapi::{CreateEventW, WaitForSingleObject}, winbase::{LocalAlloc, LocalFree, FILE_BEGIN, WAIT_FAILED}, winnt::{ RtlCopyMemory, FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, GENERIC_WRITE, HANDLE, WCHAR, }, }, }; /// A type representing file descriptor on Unix. pub type FileDesc = HANDLE; /// A type representing Process ID on Windows. pub type Pid = DWORD; #[cfg(feature = "std")] /// An IO error. pub type Error = std::io::Error; #[cfg(not(feature = "std"))] #[derive(Debug)] /// An IO error. Without std, you can only get a message or an OS error code. pub struct Error { code: i32, } #[cfg(not(feature = "std"))] impl Error { /// Creates an error from a raw OS error code. pub fn from_raw_os_error(code: i32) -> Self { Self { code } } /// Creates an error from the last OS error code. pub fn last_os_error() -> Error { Self::from_raw_os_error(unsafe { GetLastError() } as i32) } /// Raw OS error code. Returns option for compatibility with std. pub fn raw_os_error(&self) -> Option { Some(self.code) } } #[cfg(not(feature = "std"))] impl fmt::Display for Error { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { let mut buf: LPWSTR = ptr::null_mut(); let res = unsafe { FormatMessageW( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, ptr::null_mut(), self.code as DWORD, LANG_USER_DEFAULT as DWORD, &mut buf as *mut LPWSTR as LPWSTR, 0, ptr::null_mut(), ) }; if res == 0 { write!(fmt, "error getting error message")?; } else { { let slice = unsafe { slice::from_raw_parts(buf as *const WCHAR, res as usize) }; let str = unsafe { OsStr::from_slice(slice) }; write!(fmt, "{}", str)?; } unsafe { LocalFree(buf as LPVOID); } } Ok(()) } } /// Owned allocation of an OS-native string. pub struct OsString { alloc: NonNull, /// Length without the nul-byte. len: usize, } impl Drop for OsString { fn drop(&mut self) { let ptr = self.alloc.as_ptr() as LPVOID; unsafe { LocalFree(ptr); } } } impl AsRef for OsString { fn as_ref(&self) -> &OsStr { unsafe { OsStr::from_slice(slice::from_raw_parts( self.alloc.as_ptr(), self.len, )) } } } /// Borrowed allocation of an OS-native string. pub struct OsStr { chars: [WCHAR], } impl OsStr { /// Unsafe cause sequence needs to end with 0. unsafe fn from_slice(slice: &[WCHAR]) -> &Self { transmute(slice) } fn chars(&self) -> Chars { Chars { inner: self.chars.iter() } } } impl fmt::Debug for OsStr { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { let mut first = false; write!(fmt, "[")?; for ch in self.chars() { if first { first = false; } else { write!(fmt, ", ")?; } write!(fmt, "{:?}", ch)?; } write!(fmt, "]")?; Ok(()) } } impl fmt::Display for OsStr { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { for ch in self.chars() { write!(fmt, "{}", ch)?; } Ok(()) } } impl<'str> IntoOsString for &'str OsStr { fn into_os_string(self) -> Result { let len = self.chars.len(); let alloc = unsafe { LocalAlloc(LMEM_FIXED, len * 2 + 2) }; let alloc = match NonNull::new(alloc as *mut WCHAR) { Some(alloc) => alloc, None => { return Err(Error::last_os_error()); }, }; unsafe { RtlCopyMemory( alloc.as_ptr() as LPVOID, self.chars.as_ptr() as _, len * 2 + 2, ); } Ok(OsString { alloc, len }) } } impl ToOsStr for str { fn to_os_str(&self) -> Result { let res = unsafe { make_os_string(|| self.encode_utf16()) }; res.map(EitherOsStr::Owned) } } #[cfg(feature = "std")] impl ToOsStr for ffi::OsStr { fn to_os_str(&self) -> Result { let res = unsafe { make_os_string(|| self.encode_wide()) }; res.map(EitherOsStr::Owned) } } /// Unsafe because the returned iterator must be exactly the same. unsafe fn make_os_string(mut make_iter: F) -> Result where F: FnMut() -> I, I: Iterator, { let mut len = 0; let mut prev_zero = false; for ch in make_iter() { if prev_zero { Err(Error::from_raw_os_error(ERROR_INVALID_DATA as i32))?; } if ch == 0 { prev_zero = true; } else { len += 1; } } let alloc = LocalAlloc(LMEM_FIXED, len * 2 + 2); let alloc = match NonNull::new(alloc as *mut WCHAR) { Some(alloc) => alloc, None => { return Err(Error::last_os_error()); }, }; let mut iter = make_iter(); for i in 0 .. len { let ch = iter.next().expect("Inconsistent .encode_utf16()"); *alloc.as_ptr().add(i) = ch; } *alloc.as_ptr().add(len) = 0; Ok(OsString { alloc, len }) } #[derive(Debug)] struct Chars<'str> { inner: slice::Iter<'str, WCHAR>, } impl<'str> Iterator for Chars<'str> { type Item = char; fn next(&mut self) -> Option { let curr = *self.inner.next()?; if curr <= 0xD7FF || curr >= 0xE000 { let ch = char::try_from(curr as u32) .expect("Inconsistent char implementation"); Some(ch) } else { let next = *self.inner.next()?; let high = curr as u32 - 0xD800; let low = next as u32 - 0xDC00; let ch = char::try_from((high << 10 | low) + 0x10000) .expect("Inconsistent char implementation"); Some(ch) } } } /// Helper to auto-drop a HANDLE. #[derive(Debug)] struct DropHandle { /// HANDLE being dropped. handle: HANDLE, } impl Drop for DropHandle { fn drop(&mut self) { unsafe { CloseHandle(self.handle); } } } /// Creates an event to be used by this implementation. fn make_event() -> Result { let mut security = make_security_attributes(); let res = unsafe { CreateEventW( &mut security as LPSECURITY_ATTRIBUTES, FALSE, FALSE, ptr::null_mut(), ) }; if res != INVALID_HANDLE_VALUE { Ok(res) } else { Err(Error::last_os_error()) } } /// Creates security attributes to be used with this implementation. fn make_security_attributes() -> SECURITY_ATTRIBUTES { SECURITY_ATTRIBUTES { nLength: 0, lpSecurityDescriptor: ptr::null_mut(), bInheritHandle: FALSE, } } /// Creates an overlapped struct to be used with this implementation. fn make_overlapped() -> Result { Ok(OVERLAPPED { Internal: 0, InternalHigh: 0, u: { let mut uninit = MaybeUninit::::uninit(); unsafe { let mut refer = (&mut *uninit.as_mut_ptr()).s_mut(); refer.Offset = DWORD::max_value() - 1; refer.OffsetHigh = DWORD::max_value() - 1; uninit.assume_init() } }, hEvent: make_event()?, }) } /// Returns the ID of the current process. pub fn pid() -> Pid { unsafe { GetCurrentProcessId() } } /// Opens a file with only purpose of locking it. Creates it if it does not /// exist. Path must not contain a nul-byte in the middle, but a nul-byte in the /// end (and only in the end) is allowed, which in this case no extra allocation /// will be made. Otherwise, an extra allocation is made. pub fn open(path: &OsStr) -> Result { let mut security = make_security_attributes(); let handle = unsafe { CreateFileW( path.chars.as_ptr(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, &mut security as LPSECURITY_ATTRIBUTES, OPEN_ALWAYS, 0, ptr::null_mut(), ) }; if handle != INVALID_HANDLE_VALUE { Ok(handle) } else { Err(Error::last_os_error()) } } /// Writes data into the given open file. pub fn write(handle: FileDesc, bytes: &[u8]) -> Result<(), Error> { let result = unsafe { WriteFile( handle, bytes.as_ptr() as LPCVOID, bytes.len() as DWORD, ptr::null_mut(), ptr::null_mut(), ) }; if result == 0 { Err(Error::last_os_error()) } else { Ok(()) } } pub fn fsync(handle: FileDesc) -> Result<(), Error> { let result = unsafe { FlushFileBuffers(handle) }; if result == 0 { Err(Error::last_os_error()) } else { Ok(()) } } /// Truncates the file referenced by the given HANDLE and seeks it to the start. pub fn truncate(handle: FileDesc) -> Result<(), Error> { let res = unsafe { SetFilePointer(handle, 0, ptr::null_mut(), FILE_BEGIN) }; if res == INVALID_SET_FILE_POINTER { return Err(Error::last_os_error()); } let res = unsafe { SetEndOfFile(handle) }; if res == 0 { Err(Error::last_os_error()) } else { Ok(()) } } /// Tries to lock a file and blocks until it is possible to lock. pub fn lock(handle: FileDesc) -> Result<(), Error> { let mut overlapped = make_overlapped()?; let drop_handle = DropHandle { handle: overlapped.hEvent }; let res = unsafe { LockFileEx( handle, LOCKFILE_EXCLUSIVE_LOCK, 0, 1, 1, &mut overlapped as LPOVERLAPPED, ) }; let ret = if res == TRUE { let res = unsafe { WaitForSingleObject(overlapped.hEvent, 0) }; if res != WAIT_FAILED { Ok(()) } else { Err(Error::last_os_error()) } } else { Err(Error::last_os_error()) }; drop(drop_handle); ret } /// Tries to lock a file but returns as soon as possible if already locked. pub fn try_lock(handle: FileDesc) -> Result { let mut overlapped = make_overlapped()?; let drop_handle = DropHandle { handle: overlapped.hEvent }; let res = unsafe { LockFileEx( handle, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY, 0, 1, 1, &mut overlapped as LPOVERLAPPED, ) }; let ret = if res == TRUE { let res = unsafe { WaitForSingleObject(overlapped.hEvent, 0) }; if res != WAIT_FAILED { Ok(true) } else { Err(Error::last_os_error()) } } else { let err = unsafe { GetLastError() }; if err == ERROR_LOCK_VIOLATION { Ok(false) } else { Err(Error::from_raw_os_error(err as i32)) } }; drop(drop_handle); ret } /// Unlocks the file. pub fn unlock(handle: FileDesc) -> Result<(), Error> { let mut overlapped = make_overlapped()?; let drop_handle = DropHandle { handle: overlapped.hEvent }; let res = unsafe { UnlockFileEx(handle, 0, 1, 1, &mut overlapped as LPOVERLAPPED) }; let ret = if res == TRUE { let res = unsafe { WaitForSingleObject(overlapped.hEvent, 0) }; if res != WAIT_FAILED { Ok(()) } else { Err(Error::last_os_error()) } } else { Err(Error::last_os_error()) }; drop(drop_handle); ret } /// Closes the file. pub fn close(handle: FileDesc) { unsafe { CloseHandle(handle); } } fslock-0.2.1/testfiles/.gitignore000064400000000000000000000000000072674642500151150ustar 00000000000000