├── .github └── workflows │ ├── check.yml │ └── pages.yml ├── .gitignore ├── .rustfmt.toml ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── locks_until_nl.rs ├── try_lock.rs └── try_lock_with_pid.rs ├── src ├── fmt.rs ├── lib.rs ├── string.rs ├── test.rs ├── unix.rs └── windows.rs └── testfiles └── .gitignore /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: fslock CI - Check 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | check-linux: 6 | name: Check Linux 7 | runs-on: ubuntu-latest 8 | env: 9 | RUST_BACKTRACE: 1 10 | steps: 11 | - name: Checkout sources 12 | uses: actions/checkout@v2 13 | 14 | - name: Install stable toolchain 15 | uses: actions-rs/toolchain@v1 16 | with: 17 | profile: minimal 18 | toolchain: stable 19 | override: true 20 | 21 | - name: Run cargo check 22 | uses: actions-rs/cargo@v1 23 | with: 24 | command: check 25 | 26 | - name: Run cargo check without default features 27 | uses: actions-rs/cargo@v1 28 | with: 29 | command: check 30 | args: --no-default-features 31 | 32 | check-windows: 33 | name: Check Windows 34 | runs-on: windows-latest 35 | env: 36 | RUST_BACKTRACE: 1 37 | steps: 38 | - name: Checkout sources 39 | uses: actions/checkout@v2 40 | 41 | - name: Install stable toolchain 42 | uses: actions-rs/toolchain@v1 43 | with: 44 | profile: minimal 45 | toolchain: stable 46 | override: true 47 | 48 | - name: Run cargo check 49 | uses: actions-rs/cargo@v1 50 | with: 51 | command: check 52 | 53 | - name: Run cargo check without default features 54 | uses: actions-rs/cargo@v1 55 | with: 56 | command: check 57 | args: --no-default-features 58 | 59 | check-macos: 60 | name: Check MacOS 61 | runs-on: macos-latest 62 | env: 63 | RUST_BACKTRACE: 1 64 | steps: 65 | - name: Checkout sources 66 | uses: actions/checkout@v2 67 | 68 | - name: Install stable toolchain 69 | uses: actions-rs/toolchain@v1 70 | with: 71 | profile: minimal 72 | toolchain: stable 73 | override: true 74 | 75 | - name: Run cargo check 76 | uses: actions-rs/cargo@v1 77 | with: 78 | command: check 79 | 80 | - name: Run cargo check without default features 81 | uses: actions-rs/cargo@v1 82 | with: 83 | command: check 84 | args: --no-default-features 85 | 86 | test-linux: 87 | name: Test Suite on Linux 88 | runs-on: ubuntu-latest 89 | env: 90 | RUST_BACKTRACE: 1 91 | steps: 92 | - name: Checkout sources 93 | uses: actions/checkout@v2 94 | 95 | - name: Install stable toolchain 96 | uses: actions-rs/toolchain@v1 97 | with: 98 | profile: minimal 99 | toolchain: stable 100 | override: true 101 | 102 | - name: Run cargo test 103 | uses: actions-rs/cargo@v1 104 | with: 105 | command: test 106 | 107 | - name: Run cargo test without default features 108 | uses: actions-rs/cargo@v1 109 | with: 110 | command: test 111 | args: --no-default-features 112 | 113 | test-windows: 114 | name: Test Suite on Windows 115 | runs-on: windows-latest 116 | env: 117 | RUST_BACKTRACE: 1 118 | steps: 119 | - name: Checkout sources 120 | uses: actions/checkout@v2 121 | 122 | - name: Install stable toolchain 123 | uses: actions-rs/toolchain@v1 124 | with: 125 | profile: minimal 126 | toolchain: stable 127 | override: true 128 | 129 | - name: Run cargo test 130 | uses: actions-rs/cargo@v1 131 | with: 132 | command: test 133 | 134 | - name: Run cargo test without default features 135 | uses: actions-rs/cargo@v1 136 | with: 137 | command: test 138 | args: --no-default-features 139 | 140 | test-macos: 141 | name: Test Suite on MacOS 142 | runs-on: macos-latest 143 | env: 144 | RUST_BACKTRACE: 1 145 | steps: 146 | - name: Checkout sources 147 | uses: actions/checkout@v2 148 | 149 | - name: Install stable toolchain 150 | uses: actions-rs/toolchain@v1 151 | with: 152 | profile: minimal 153 | toolchain: stable 154 | override: true 155 | 156 | - name: Run cargo test 157 | uses: actions-rs/cargo@v1 158 | with: 159 | command: test 160 | 161 | - name: Run cargo test without default features 162 | uses: actions-rs/cargo@v1 163 | with: 164 | command: test 165 | args: --no-default-features 166 | -------------------------------------------------------------------------------- /.github/workflows/pages.yml: -------------------------------------------------------------------------------- 1 | name: fslock CI - GH Pages 2 | on: 3 | push: 4 | branches: 5 | - master 6 | 7 | jobs: 8 | docs: 9 | name: Documentation 10 | runs-on: ubuntu-latest 11 | env: 12 | RUST_BACKTRACE: 1 13 | steps: 14 | - name: Checkout sources 15 | uses: actions/checkout@v2 16 | 17 | - name: Install stable toolchain 18 | uses: actions-rs/toolchain@v1 19 | with: 20 | profile: minimal 21 | toolchain: stable 22 | override: true 23 | 24 | - name: Run cargo doc 25 | uses: actions-rs/cargo@v1 26 | with: 27 | command: doc 28 | args: --all-features 29 | 30 | - name: Deploy Docs 31 | uses: peaceiris/actions-gh-pages@v3 32 | with: 33 | github_token: ${{ secrets.GITHUB_TOKEN }} 34 | publish_dir: ./target/doc 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | testfiles/*.lock 4 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | disable_all_formatting = false 2 | 3 | indent_style = "Block" 4 | use_small_heuristics = "Max" 5 | binop_separator = "Front" 6 | combine_control_expr = true 7 | comment_width = 80 8 | condense_wildcard_suffixes = true 9 | control_brace_style = "AlwaysSameLine" 10 | error_on_line_overflow = false 11 | error_on_unformatted = false 12 | fn_args_layout = "Tall" 13 | brace_style = "SameLineWhere" 14 | empty_item_single_line = true 15 | fn_single_line = false 16 | where_single_line = false 17 | force_explicit_abi = true 18 | format_strings = true 19 | format_macro_matchers = true 20 | format_macro_bodies = true 21 | hard_tabs = false 22 | imports_indent = "Block" 23 | imports_layout = "HorizontalVertical" 24 | imports_granularity = "Crate" 25 | match_block_trailing_comma = true 26 | max_width = 80 27 | merge_derives = true 28 | force_multiline_blocks = false 29 | newline_style = "Unix" 30 | normalize_comments = false 31 | remove_nested_parens = true 32 | reorder_imports = true 33 | reorder_modules = false 34 | reorder_impl_items = false 35 | report_todo = "Never" 36 | report_fixme = "Never" 37 | skip_children = false 38 | space_after_colon = true 39 | space_before_colon = false 40 | struct_field_align_threshold = 0 41 | spaces_around_ranges = true 42 | struct_lit_single_line = true 43 | tab_spaces = 4 44 | trailing_comma = "Vertical" 45 | trailing_semicolon = true 46 | type_punctuation_density = "Wide" 47 | use_field_init_shorthand = true 48 | use_try_shorthand = true 49 | wrap_comments = true 50 | match_arm_blocks = true 51 | blank_lines_upper_bound = 1 52 | blank_lines_lower_bound = 0 53 | hide_parse_errors = false 54 | unstable_features = true 55 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.2.1 2 | * Added `try_lock_with_pid` method. 3 | * Corrected bug that would not seek lock files in UNIX (when writing PIDs), and 4 | so it would fill the previous bytes with nul-bytes. 5 | * Corrected bug that would always truncate files on opening in Windows 6 | 7 | # 0.2.0 8 | * Writing PID on locked file via `lock_with_pid()` method. 9 | * Unix and Windows locks are now always per-handle. 10 | * NOTE: version 0.2.x in UNIX is incompatible with 0.1.x due to this change. 11 | * Removed multilock feature as it became obsolete. 12 | 13 | # 0.1.8 14 | * Compiling on Android b32 15 | 16 | # 0.1.7 17 | * Support for locks per handle/fd via multilock feature 18 | 19 | # 0.1.6 20 | * Fixed #1: now fslock compiles on arm 21 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fslock" 3 | version = "0.2.1" 4 | authors = ["brunoczim "] 5 | edition = "2018" 6 | description = "A library to use files as locks" 7 | repository = "https://github.com/brunoczim/fslock" 8 | readme = "README.md" 9 | keywords = ["file", "lock", "lockfile", "filelock", "lock-file"] 10 | categories = ["filesystem", "no-std", "concurrency"] 11 | license = "MIT" 12 | 13 | [badges] 14 | maintenance = { status = "passively-maintained" } 15 | 16 | [badges.travis-ci] 17 | repository = "https://github.com/brunoczim/fslock" 18 | branch = "master" 19 | 20 | [target.'cfg(unix)'.dependencies.libc] 21 | version = "^0.2.66" 22 | default-features = false 23 | 24 | [target.'cfg(windows)'.dependencies.winapi] 25 | version = "^0.3.8" 26 | features = [ 27 | "minwindef", 28 | "minwinbase", 29 | "winbase", 30 | "errhandlingapi", 31 | "winerror", 32 | "winnt", 33 | "synchapi", 34 | "handleapi", 35 | "fileapi", 36 | "processthreadsapi" 37 | ] 38 | 39 | [features] 40 | default = ["std"] 41 | std = [] 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 brunoczim 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fslock 2 | 3 | API to use files as a lock. Supports non-std crates by disabling feature 4 | `std`. 5 | 6 | # Types 7 | Currently, only one type is provided: [`LockFile`]. It does not destroy the 8 | file after closed and behaviour on locking different file handles owned by 9 | the same process is different between Unix and Windows, unless you activate the 10 | `multilock` feature, which enables the `open_excl` method that locks files per 11 | file descriptor/handle on all platforms. 12 | 13 | # Example 14 | ```rust 15 | use fslock::LockFile; 16 | fn main() -> Result<(), fslock::Error> { 17 | 18 | let mut file = LockFile::open("mylock")?; 19 | file.lock()?; 20 | do_stuff(); 21 | file.unlock()?; 22 | 23 | Ok(()) 24 | } 25 | ``` 26 | 27 | # Docs on Master 28 | 29 | https://brunoczim.github.io/fslock/fslock 30 | -------------------------------------------------------------------------------- /examples/locks_until_nl.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "std")] 2 | use fslock::LockFile; 3 | #[cfg(feature = "std")] 4 | use std::{env, io, io::Read, process}; 5 | 6 | #[cfg(feature = "std")] 7 | fn main() -> Result<(), fslock::Error> { 8 | let mut args = env::args(); 9 | args.next(); 10 | 11 | let path = match args.next() { 12 | Some(arg) if args.next().is_none() => arg, 13 | _ => { 14 | eprintln!("Expected one argument"); 15 | process::exit(1); 16 | }, 17 | }; 18 | let mut lockfile = LockFile::open(&path)?; 19 | lockfile.lock()?; 20 | io::stdin().read(&mut [0; 1])?; 21 | 22 | Ok(()) 23 | } 24 | 25 | #[cfg(not(feature = "std"))] 26 | fn main() {} 27 | -------------------------------------------------------------------------------- /examples/try_lock.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "std")] 2 | use fslock::LockFile; 3 | #[cfg(feature = "std")] 4 | use std::{env, process}; 5 | 6 | #[cfg(feature = "std")] 7 | fn main() -> Result<(), fslock::Error> { 8 | let mut args = env::args(); 9 | args.next(); 10 | 11 | let path = match args.next() { 12 | Some(arg) if args.next().is_none() => arg, 13 | _ => { 14 | eprintln!("Expected one argument"); 15 | process::exit(1); 16 | }, 17 | }; 18 | 19 | let mut lockfile = LockFile::open(&path)?; 20 | 21 | if lockfile.try_lock()? { 22 | println!("SUCCESS"); 23 | } else { 24 | println!("FAILURE"); 25 | } 26 | 27 | Ok(()) 28 | } 29 | 30 | #[cfg(not(feature = "std"))] 31 | fn main() {} 32 | -------------------------------------------------------------------------------- /examples/try_lock_with_pid.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "std")] 2 | use fslock::LockFile; 3 | #[cfg(feature = "std")] 4 | use std::{env, fs::read_to_string, process}; 5 | 6 | #[cfg(feature = "std")] 7 | fn main() -> Result<(), fslock::Error> { 8 | let mut args = env::args(); 9 | args.next(); 10 | 11 | let path = match args.next() { 12 | Some(arg) if args.next().is_none() => arg, 13 | _ => { 14 | eprintln!("Expected one argument"); 15 | process::exit(1); 16 | }, 17 | }; 18 | 19 | let mut lockfile = LockFile::open(&path)?; 20 | 21 | if lockfile.try_lock_with_pid()? { 22 | let content_a = read_to_string(&path)?; 23 | let content_b = read_to_string(&path)?; 24 | assert!(content_a.trim().len() > 0); 25 | assert!(content_a.trim().chars().all(|ch| ch.is_ascii_digit())); 26 | assert_eq!(content_a, content_b); 27 | 28 | println!("{}", content_a); 29 | } else { 30 | println!("FAILURE"); 31 | } 32 | 33 | Ok(()) 34 | } 35 | 36 | #[cfg(not(feature = "std"))] 37 | fn main() {} 38 | -------------------------------------------------------------------------------- /src/fmt.rs: -------------------------------------------------------------------------------- 1 | //! This module implements formatting functions for writing into lock files. 2 | 3 | use crate::sys; 4 | use core::{ 5 | fmt::{self, Write}, 6 | mem, 7 | }; 8 | 9 | /// I/O buffer size, chosen targeting possible PID's digits (I belive 11 would 10 | /// be enough tho). 11 | const BUF_SIZE: usize = 16; 12 | 13 | /// A fmt Writer that writes data into the given open file. 14 | #[derive(Debug, Clone, Copy)] 15 | pub struct Writer( 16 | /// The open file to which data will be written. 17 | pub sys::FileDesc, 18 | ); 19 | 20 | impl Writer { 21 | /// Writes formatting arguments into the file. 22 | pub fn write_fmt( 23 | &self, 24 | arguments: fmt::Arguments, 25 | ) -> Result<(), sys::Error> { 26 | let mut adapter = Adapter::new(self.0); 27 | let _ = adapter.write_fmt(arguments); 28 | adapter.finish() 29 | } 30 | } 31 | 32 | /// Fmt <-> IO adapter. 33 | /// 34 | /// Buffer is flushed on drop. 35 | #[derive(Debug)] 36 | struct Adapter { 37 | /// File being written to. 38 | desc: sys::FileDesc, 39 | /// Temporary buffer of bytes being written. 40 | buffer: [u8; BUF_SIZE], 41 | /// Cursor tracking where new bytes should be written at the buffer. 42 | cursor: usize, 43 | /// Partial result for writes. 44 | result: Result<(), sys::Error>, 45 | } 46 | 47 | impl Adapter { 48 | /// Creates a zeroed adapter from an open file. 49 | fn new(desc: sys::FileDesc) -> Self { 50 | Self { desc, buffer: [0; BUF_SIZE], cursor: 0, result: Ok(()) } 51 | } 52 | 53 | /// Flushes the buffer into the open file. 54 | fn flush(&mut self) -> Result<(), sys::Error> { 55 | sys::write(self.desc, &self.buffer[.. self.cursor])?; 56 | self.buffer = [0; BUF_SIZE]; 57 | self.cursor = 0; 58 | Ok(()) 59 | } 60 | 61 | /// Finishes the adapter, returning the I/O Result 62 | fn finish(mut self) -> Result<(), sys::Error> { 63 | mem::replace(&mut self.result, Ok(())) 64 | } 65 | } 66 | 67 | impl Write for Adapter { 68 | fn write_str(&mut self, data: &str) -> fmt::Result { 69 | let mut bytes = data.as_bytes(); 70 | 71 | while bytes.len() > 0 && self.result.is_ok() { 72 | let start = self.cursor; 73 | let size = (BUF_SIZE - self.cursor).min(bytes.len()); 74 | let end = start + size; 75 | 76 | self.buffer[start .. end].copy_from_slice(&bytes[.. size]); 77 | self.cursor = end; 78 | bytes = &bytes[size ..]; 79 | 80 | if bytes.len() > 0 { 81 | self.result = self.flush(); 82 | } 83 | } 84 | 85 | match self.result { 86 | Ok(_) => Ok(()), 87 | Err(_) => Err(fmt::Error), 88 | } 89 | } 90 | } 91 | 92 | impl Drop for Adapter { 93 | fn drop(&mut self) { 94 | let _ = self.flush(); 95 | let _ = sys::fsync(self.desc); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std)] 2 | 3 | //! **WARNING**: v0.1.x is incompatible with v0.2.x onwards. 4 | //! 5 | //! API to use files as a lock. Supports non-std crates by disabling feature 6 | //! `std`. 7 | //! 8 | //! # Types 9 | //! Currently, only one type is provided: [`LockFile`]. It does not destroy the 10 | //! file after closed. Locks are per-handle and not by per-process in any 11 | //! platform. On Unix, however, under `fork` file descriptors might be 12 | //! duplicated sharing the same lock, but `fork` is usually `unsafe` in Rust. 13 | //! 14 | //! # Example 15 | //! ``` 16 | //! use fslock::LockFile; 17 | //! fn main() -> Result<(), fslock::Error> { 18 | //! 19 | //! let mut file = LockFile::open("testfiles/mylock.lock")?; 20 | //! file.lock()?; 21 | //! do_stuff(); 22 | //! file.unlock()?; 23 | //! 24 | //! Ok(()) 25 | //! } 26 | //! # fn do_stuff() { 27 | //! # // doing stuff here. 28 | //! # } 29 | //! ``` 30 | 31 | #[cfg(test)] 32 | mod test; 33 | 34 | #[cfg(unix)] 35 | mod unix; 36 | #[cfg(unix)] 37 | use crate::unix as sys; 38 | 39 | mod string; 40 | mod fmt; 41 | 42 | #[cfg(windows)] 43 | mod windows; 44 | #[cfg(windows)] 45 | use crate::windows as sys; 46 | 47 | pub use crate::{ 48 | string::{EitherOsStr, IntoOsString, ToOsStr}, 49 | sys::{Error, OsStr, OsString}, 50 | }; 51 | 52 | #[derive(Debug)] 53 | /// A handle to a file that is lockable. Does not delete the file. On both 54 | /// Unix and Windows, the lock is held by an individual handle, and not by the 55 | /// whole process. On Unix, however, under `fork` file descriptors might be 56 | /// duplicated sharing the same lock, but `fork` is usually `unsafe` in Rust. 57 | /// 58 | /// # Example 59 | /// ``` 60 | /// # fn main() -> Result<(), fslock::Error> { 61 | /// use fslock::LockFile; 62 | /// 63 | /// let mut file = LockFile::open("testfiles/mylock.lock")?; 64 | /// file.lock()?; 65 | /// do_stuff(); 66 | /// file.unlock()?; 67 | /// 68 | /// # Ok(()) 69 | /// # } 70 | /// # fn do_stuff() { 71 | /// # // doing stuff here. 72 | /// # } 73 | /// ``` 74 | pub struct LockFile { 75 | locked: bool, 76 | desc: sys::FileDesc, 77 | } 78 | 79 | impl LockFile { 80 | /// Opens a file for locking, with OS-dependent locking behavior. On Unix, 81 | /// if the path is nul-terminated (ends with 0), no extra allocation will be 82 | /// made. 83 | /// 84 | /// # Compatibility 85 | /// 86 | /// This crate used to behave differently in regards to Unix and Windows, 87 | /// when locks on Unix were per-process and not per-handle. However, the 88 | /// current version locks per-handle on any platform. On Unix, however, 89 | /// under `fork` file descriptors might be duplicated sharing the same lock, 90 | /// but `fork` is usually `unsafe` in Rust. 91 | /// 92 | /// # Panics 93 | /// Panics if the path contains a nul-byte in a place other than the end. 94 | /// 95 | /// # Example 96 | /// 97 | /// ``` 98 | /// # fn main() -> Result<(), fslock::Error> { 99 | /// use fslock::LockFile; 100 | /// 101 | /// let mut file = LockFile::open("testfiles/regular.lock")?; 102 | /// 103 | /// # Ok(()) 104 | /// # } 105 | /// ``` 106 | /// 107 | /// # Panicking Example 108 | /// 109 | /// ```should_panic 110 | /// # fn main() -> Result<(), fslock::Error> { 111 | /// use fslock::LockFile; 112 | /// 113 | /// let mut file = LockFile::open("my\0lock")?; 114 | /// 115 | /// # Ok(()) 116 | /// # } 117 | /// ``` 118 | pub fn open

(path: &P) -> Result 119 | where 120 | P: ToOsStr + ?Sized, 121 | { 122 | let path = path.to_os_str()?; 123 | let desc = sys::open(path.as_ref())?; 124 | Ok(Self { locked: false, desc }) 125 | } 126 | 127 | /// Locks this file. Blocks while it is not possible to lock (i.e. someone 128 | /// else already owns a lock). After locked, if no attempt to unlock is 129 | /// made, it will be automatically unlocked on the file handle drop. 130 | /// 131 | /// # Panics 132 | /// Panics if this handle already owns the file. 133 | /// 134 | /// # Example 135 | /// 136 | /// ``` 137 | /// # fn main() -> Result<(), fslock::Error> { 138 | /// use fslock::LockFile; 139 | /// 140 | /// let mut file = LockFile::open("testfiles/target.lock")?; 141 | /// file.lock()?; 142 | /// do_stuff(); 143 | /// file.unlock()?; 144 | /// 145 | /// # Ok(()) 146 | /// # } 147 | /// # fn do_stuff() { 148 | /// # // doing stuff here. 149 | /// # } 150 | /// ``` 151 | /// 152 | /// # Panicking Example 153 | /// 154 | /// ```should_panic 155 | /// # fn main() -> Result<(), fslock::Error> { 156 | /// use fslock::LockFile; 157 | /// 158 | /// let mut file = LockFile::open("testfiles/panicking.lock")?; 159 | /// file.lock()?; 160 | /// file.lock()?; 161 | /// 162 | /// # Ok(()) 163 | /// # } 164 | /// ``` 165 | pub fn lock(&mut self) -> Result<(), Error> { 166 | if self.locked { 167 | panic!("Cannot lock if already owning a lock"); 168 | } 169 | sys::lock(self.desc)?; 170 | self.locked = true; 171 | Ok(()) 172 | } 173 | 174 | /// Locks this file and writes this process's PID into the file, which will 175 | /// be erased on unlock. Like [`LockFile::lock`], blocks while it is not 176 | /// possible to lock. After locked, if no attempt to unlock is made, it will 177 | /// be automatically unlocked on the file handle drop. 178 | /// 179 | /// # Panics 180 | /// Panics if this handle already owns the file. 181 | /// 182 | /// # Example 183 | /// 184 | /// ``` 185 | /// # fn main() -> Result<(), fslock::Error> { 186 | /// use fslock::LockFile; 187 | /// # #[cfg(feature = "std")] 188 | /// use std::fs::read_to_string; 189 | /// 190 | /// let mut file = LockFile::open("testfiles/withpid.lock")?; 191 | /// file.lock_with_pid()?; 192 | /// # #[cfg(feature = "std")] 193 | /// # { 194 | /// do_stuff()?; 195 | /// # } 196 | /// file.unlock()?; 197 | /// 198 | /// # #[cfg(feature = "std")] 199 | /// fn do_stuff() -> Result<(), fslock::Error> { 200 | /// let mut content = read_to_string("testfiles/withpid.lock")?; 201 | /// assert!(content.trim().len() > 0); 202 | /// assert!(content.trim().chars().all(|ch| ch.is_ascii_digit())); 203 | /// Ok(()) 204 | /// } 205 | /// 206 | /// # Ok(()) 207 | /// # } 208 | /// ``` 209 | pub fn lock_with_pid(&mut self) -> Result<(), Error> { 210 | if let Err(error) = self.lock() { 211 | return Err(error); 212 | } 213 | 214 | let result = writeln!(fmt::Writer(self.desc), "{}", sys::pid()); 215 | if result.is_err() { 216 | let _ = self.unlock(); 217 | } 218 | result 219 | } 220 | 221 | /// Locks this file. Does NOT block if it is not possible to lock (i.e. 222 | /// someone else already owns a lock). After locked, if no attempt to 223 | /// unlock is made, it will be automatically unlocked on the file handle 224 | /// drop. 225 | /// 226 | /// # Panics 227 | /// Panics if this handle already owns the file. 228 | /// 229 | /// # Example 230 | /// 231 | /// ``` 232 | /// # fn main() -> Result<(), fslock::Error> { 233 | /// use fslock::LockFile; 234 | /// 235 | /// let mut file = LockFile::open("testfiles/attempt.lock")?; 236 | /// if file.try_lock()? { 237 | /// do_stuff(); 238 | /// file.unlock()?; 239 | /// } 240 | /// 241 | /// # Ok(()) 242 | /// # } 243 | /// # fn do_stuff() { 244 | /// # // doing stuff here. 245 | /// # } 246 | /// ``` 247 | /// 248 | /// # Panicking Example 249 | /// 250 | /// ```should_panic 251 | /// # fn main() -> Result<(), fslock::Error> { 252 | /// use fslock::LockFile; 253 | /// 254 | /// let mut file = LockFile::open("testfiles/attempt_panic.lock")?; 255 | /// file.lock()?; 256 | /// file.try_lock()?; 257 | /// 258 | /// # Ok(()) 259 | /// # } 260 | /// ``` 261 | pub fn try_lock(&mut self) -> Result { 262 | if self.locked { 263 | panic!("Cannot lock if already owning a lock"); 264 | } 265 | let lock_result = sys::try_lock(self.desc); 266 | if let Ok(true) = lock_result { 267 | self.locked = true; 268 | } 269 | lock_result 270 | } 271 | 272 | /// Locks this file and writes this process's PID into the file, which will 273 | /// be erased on unlock. Does NOT block if it is not possible to lock (i.e. 274 | /// someone else already owns a lock). After locked, if no attempt to 275 | /// unlock is made, it will be automatically unlocked on the file handle 276 | /// drop. 277 | /// 278 | /// # Panics 279 | /// Panics if this handle already owns the file. 280 | /// 281 | /// # Example 282 | /// 283 | /// ``` 284 | /// # #[cfg(feature = "std")] 285 | /// # use std::fs::read_to_string; 286 | /// # fn main() -> Result<(), fslock::Error> { 287 | /// use fslock::LockFile; 288 | /// 289 | /// let mut file = LockFile::open("testfiles/pid_attempt.lock")?; 290 | /// if file.try_lock_with_pid()? { 291 | /// # #[cfg(feature = "std")] 292 | /// # { 293 | /// do_stuff()?; 294 | /// # } 295 | /// file.unlock()?; 296 | /// } 297 | /// 298 | /// # Ok(()) 299 | /// # } 300 | /// # #[cfg(feature = "std")] 301 | /// fn do_stuff() -> Result<(), fslock::Error> { 302 | /// let mut content = read_to_string("testfiles/pid_attempt.lock")?; 303 | /// assert!(content.trim().len() > 0); 304 | /// assert!(content.trim().chars().all(|ch| ch.is_ascii_digit())); 305 | /// Ok(()) 306 | /// } 307 | /// ``` 308 | /// 309 | /// # Panicking Example 310 | /// 311 | /// ```should_panic 312 | /// # fn main() -> Result<(), fslock::Error> { 313 | /// use fslock::LockFile; 314 | /// 315 | /// let mut file = LockFile::open("testfiles/pid_attempt_panic.lock")?; 316 | /// file.lock_with_pid()?; 317 | /// file.try_lock_with_pid()?; 318 | /// 319 | /// # Ok(()) 320 | /// # } 321 | /// ``` 322 | pub fn try_lock_with_pid(&mut self) -> Result { 323 | match self.try_lock() { 324 | Ok(true) => (), 325 | Ok(false) => return Ok(false), 326 | Err(error) => return Err(error), 327 | } 328 | 329 | let result = sys::truncate(self.desc) 330 | .and_then(|_| writeln!(fmt::Writer(self.desc), "{}", sys::pid())); 331 | if result.is_err() { 332 | let _ = self.unlock(); 333 | } 334 | result.map(|_| true) 335 | } 336 | 337 | /// Returns whether this file handle owns the lock. 338 | /// 339 | /// # Example 340 | /// ``` 341 | /// use fslock::LockFile; 342 | /// # fn main() -> Result<(), fslock::Error> { 343 | /// 344 | /// let mut file = LockFile::open("testfiles/maybeowned.lock")?; 345 | /// do_stuff_with_lock(&mut file); 346 | /// if !file.owns_lock() { 347 | /// file.lock()?; 348 | /// do_stuff(); 349 | /// file.unlock()?; 350 | /// } 351 | /// 352 | /// # Ok(()) 353 | /// # } 354 | /// # fn do_stuff_with_lock(_lock: &mut LockFile) { 355 | /// # // doing stuff here. 356 | /// # } 357 | /// # fn do_stuff() { 358 | /// # // doing stuff here. 359 | /// # } 360 | /// ``` 361 | pub fn owns_lock(&self) -> bool { 362 | self.locked 363 | } 364 | 365 | /// Unlocks this file. This file handle must own the file lock. If not 366 | /// called manually, it is automatically called on `drop`. 367 | /// 368 | /// # Panics 369 | /// Panics if this handle does not own the file. 370 | /// 371 | /// # Example 372 | /// 373 | /// ``` 374 | /// # fn main() -> Result<(), fslock::Error> { 375 | /// use fslock::LockFile; 376 | /// 377 | /// let mut file = LockFile::open("testfiles/endinglock.lock")?; 378 | /// file.lock()?; 379 | /// do_stuff(); 380 | /// file.unlock()?; 381 | /// 382 | /// # Ok(()) 383 | /// # } 384 | /// # fn do_stuff() { 385 | /// # // doing stuff here. 386 | /// # } 387 | /// ``` 388 | /// 389 | /// # Panicking Example 390 | /// 391 | /// ```should_panic 392 | /// # fn main() -> Result<(), fslock::Error> { 393 | /// use fslock::LockFile; 394 | /// 395 | /// let mut file = LockFile::open("testfiles/endinglock.lock")?; 396 | /// file.unlock()?; 397 | /// 398 | /// # Ok(()) 399 | /// # } 400 | /// ``` 401 | pub fn unlock(&mut self) -> Result<(), Error> { 402 | if !self.locked { 403 | panic!("Attempted to unlock already unlocked lockfile"); 404 | } 405 | self.locked = false; 406 | sys::unlock(self.desc)?; 407 | sys::truncate(self.desc)?; 408 | Ok(()) 409 | } 410 | } 411 | 412 | impl Drop for LockFile { 413 | fn drop(&mut self) { 414 | if self.locked { 415 | let _ = self.unlock(); 416 | } 417 | sys::close(self.desc); 418 | } 419 | } 420 | 421 | // Safe because: 422 | // 1. We never actually access the contents of the pointer that represents the 423 | // Windows Handle. 424 | // 425 | // 2. We require a mutable reference to actually mutate the file 426 | // system. 427 | 428 | #[cfg(windows)] 429 | unsafe impl Send for LockFile {} 430 | 431 | #[cfg(windows)] 432 | unsafe impl Sync for LockFile {} 433 | -------------------------------------------------------------------------------- /src/string.rs: -------------------------------------------------------------------------------- 1 | //! This module implements common functionalities for OS's strings. 2 | 3 | use crate::sys::{Error, OsStr, OsString}; 4 | use core::{fmt, ops::Deref}; 5 | 6 | #[cfg(feature = "std")] 7 | use std::{ 8 | ffi, 9 | path::{Path, PathBuf}, 10 | }; 11 | 12 | impl Clone for OsString { 13 | fn clone(&self) -> Self { 14 | self.to_os_str() 15 | .and_then(|str| str.into_os_string()) 16 | .expect("Allocation error") 17 | } 18 | } 19 | 20 | impl fmt::Debug for OsString { 21 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 22 | write!(fmt, "{:?}", self.as_ref()) 23 | } 24 | } 25 | 26 | impl fmt::Display for OsString { 27 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 28 | write!(fmt, "{}", self.as_ref()) 29 | } 30 | } 31 | 32 | impl Deref for OsString { 33 | type Target = OsStr; 34 | 35 | fn deref(&self) -> &OsStr { 36 | self.as_ref() 37 | } 38 | } 39 | 40 | /// Either borrowed or owned allocation of an OS-native string. 41 | #[derive(Debug)] 42 | pub enum EitherOsStr<'str> { 43 | /// Borrowed allocation. 44 | Borrowed(&'str OsStr), 45 | /// Owned allocation. 46 | Owned(OsString), 47 | } 48 | 49 | impl<'str> AsRef for EitherOsStr<'str> { 50 | fn as_ref(&self) -> &OsStr { 51 | match self { 52 | Self::Borrowed(str) => str, 53 | Self::Owned(string) => string.as_ref(), 54 | } 55 | } 56 | } 57 | 58 | impl<'str> Deref for EitherOsStr<'str> { 59 | type Target = OsStr; 60 | 61 | fn deref(&self) -> &OsStr { 62 | self.as_ref() 63 | } 64 | } 65 | 66 | /// Conversion of anything into an owned OS-native string. If allocation fails, 67 | /// an error shall be returned. 68 | pub trait IntoOsString { 69 | /// Converts with possible allocation error. 70 | fn into_os_string(self) -> Result; 71 | } 72 | 73 | impl IntoOsString for OsString { 74 | fn into_os_string(self) -> Result { 75 | Ok(self) 76 | } 77 | } 78 | 79 | impl<'str> IntoOsString for EitherOsStr<'str> { 80 | fn into_os_string(self) -> Result { 81 | match self { 82 | Self::Borrowed(str) => str.into_os_string(), 83 | Self::Owned(string) => Ok(string), 84 | } 85 | } 86 | } 87 | 88 | #[cfg(feature = "std")] 89 | impl<'str> IntoOsString for &'str ffi::OsStr { 90 | fn into_os_string(self) -> Result { 91 | self.to_os_str()?.into_os_string() 92 | } 93 | } 94 | 95 | #[cfg(feature = "std")] 96 | impl IntoOsString for PathBuf { 97 | fn into_os_string(self) -> Result { 98 | (*self).into_os_string() 99 | } 100 | } 101 | 102 | #[cfg(feature = "std")] 103 | impl<'str> IntoOsString for &'str Path { 104 | fn into_os_string(self) -> Result { 105 | AsRef::::as_ref(self).to_os_str()?.into_os_string() 106 | } 107 | } 108 | 109 | #[cfg(feature = "std")] 110 | impl IntoOsString for ffi::OsString { 111 | fn into_os_string(self) -> Result { 112 | (*self).into_os_string() 113 | } 114 | } 115 | 116 | impl<'str> IntoOsString for &'str str { 117 | fn into_os_string(self) -> Result { 118 | self.to_os_str()?.into_os_string() 119 | } 120 | } 121 | 122 | #[cfg(feature = "std")] 123 | impl IntoOsString for String { 124 | fn into_os_string(self) -> Result { 125 | self.to_os_str()?.into_os_string() 126 | } 127 | } 128 | 129 | #[cfg(feature = "std")] 130 | impl ToOsStr for String { 131 | fn to_os_str(&self) -> Result { 132 | (**self).to_os_str() 133 | } 134 | } 135 | 136 | /// Conversion of anything to an either borrowed or owned OS-native string. If 137 | /// allocation fails, an error shall be returned. 138 | pub trait ToOsStr { 139 | /// Converts with possible allocation error. 140 | fn to_os_str(&self) -> Result; 141 | } 142 | 143 | impl<'str> ToOsStr for EitherOsStr<'str> { 144 | fn to_os_str(&self) -> Result { 145 | Ok(match self { 146 | EitherOsStr::Owned(string) => { 147 | EitherOsStr::Owned(string.to_os_str()?.into_os_string()?) 148 | }, 149 | EitherOsStr::Borrowed(str) => EitherOsStr::Borrowed(str), 150 | }) 151 | } 152 | } 153 | 154 | impl ToOsStr for OsStr { 155 | fn to_os_str(&self) -> Result { 156 | Ok(EitherOsStr::Borrowed(self)) 157 | } 158 | } 159 | 160 | impl ToOsStr for OsString { 161 | fn to_os_str(&self) -> Result { 162 | Ok(EitherOsStr::Borrowed(self.as_ref())) 163 | } 164 | } 165 | 166 | #[cfg(feature = "std")] 167 | impl ToOsStr for ffi::OsString { 168 | fn to_os_str(&self) -> Result { 169 | (**self).to_os_str() 170 | } 171 | } 172 | 173 | #[cfg(feature = "std")] 174 | impl ToOsStr for PathBuf { 175 | fn to_os_str(&self) -> Result { 176 | (**self).to_os_str() 177 | } 178 | } 179 | 180 | #[cfg(feature = "std")] 181 | impl ToOsStr for Path { 182 | fn to_os_str(&self) -> Result { 183 | AsRef::::as_ref(self).to_os_str() 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/test.rs: -------------------------------------------------------------------------------- 1 | use crate::{Error, LockFile}; 2 | use core::str; 3 | 4 | #[cfg(feature = "std")] 5 | #[test] 6 | fn read_pid() -> Result<(), Error> { 7 | use std::fs::read_to_string; 8 | 9 | let path = "testfiles/read_pid.lock"; 10 | let mut file = LockFile::open(path)?; 11 | file.lock_with_pid()?; 12 | 13 | let content_a = read_to_string(path)?; 14 | let content_b = read_to_string(path)?; 15 | 16 | assert!(content_a.trim().len() > 0); 17 | assert!(content_a.trim().chars().all(|ch| ch.is_ascii_digit())); 18 | 19 | assert_eq!(content_a, content_b); 20 | Ok(()) 21 | } 22 | 23 | #[cfg(feature = "std")] 24 | #[test] 25 | fn try_read_pid() -> Result<(), Error> { 26 | use std::fs::read_to_string; 27 | 28 | let path = "testfiles/try_read_pid.lock"; 29 | let mut file = LockFile::open(path)?; 30 | assert!(file.try_lock_with_pid()?); 31 | 32 | let content_a = read_to_string(path)?; 33 | let content_b = read_to_string(path)?; 34 | 35 | assert!(content_a.trim().len() > 0); 36 | assert!(content_a.trim().chars().all(|ch| ch.is_ascii_digit())); 37 | 38 | assert_eq!(content_a, content_b); 39 | Ok(()) 40 | } 41 | 42 | #[cfg(feature = "std")] 43 | fn check_try_lock_example( 44 | lockpath: &str, 45 | expected: &[u8], 46 | ) -> Result<(), Error> { 47 | use std::process::{Command, Stdio}; 48 | 49 | let child = Command::new("cargo") 50 | .arg("run") 51 | .arg("-q") 52 | .arg("--example") 53 | .arg("try_lock") 54 | .arg("--") 55 | .arg(lockpath) 56 | .stdout(Stdio::piped()) 57 | .spawn()?; 58 | let output = child.wait_with_output()?; 59 | 60 | assert!(output.status.success()); 61 | assert_eq!(output.stderr, b""); 62 | assert_eq!(output.stdout, expected); 63 | 64 | Ok(()) 65 | } 66 | 67 | #[derive(Debug, Clone)] 68 | enum TryPidExpectedRes<'pid> { 69 | Success { pid_to_differ: &'pid str }, 70 | Failure, 71 | } 72 | 73 | #[cfg(feature = "std")] 74 | fn check_try_lock_with_pid_example( 75 | lockpath: &str, 76 | expected: TryPidExpectedRes, 77 | ) -> Result<(), Error> { 78 | use std::process::{Command, Stdio}; 79 | 80 | let child = Command::new("cargo") 81 | .arg("run") 82 | .arg("-q") 83 | .arg("--example") 84 | .arg("try_lock_with_pid") 85 | .arg("--") 86 | .arg(lockpath) 87 | .stdout(Stdio::piped()) 88 | .spawn()?; 89 | let output = child.wait_with_output()?; 90 | 91 | assert!(output.status.success()); 92 | assert_eq!(output.stderr, b""); 93 | match expected { 94 | TryPidExpectedRes::Success { pid_to_differ: pid } => { 95 | let output = str::from_utf8(&output.stdout).unwrap(); 96 | assert!(output.trim().len() > 0); 97 | assert!(output.trim().chars().all(|ch| ch.is_ascii_digit())); 98 | assert_ne!(output.trim(), pid); 99 | }, 100 | 101 | TryPidExpectedRes::Failure => assert_eq!(output.stdout, b"FAILURE\n"), 102 | } 103 | 104 | Ok(()) 105 | } 106 | 107 | #[cfg(feature = "std")] 108 | #[test] 109 | fn other_process() -> Result<(), Error> { 110 | let path = "testfiles/other_process.lock"; 111 | let mut file = LockFile::open(path)?; 112 | file.lock()?; 113 | check_try_lock_example(path, b"FAILURE\n")?; 114 | file.unlock()?; 115 | check_try_lock_example(path, b"SUCCESS\n")?; 116 | Ok(()) 117 | } 118 | 119 | #[cfg(feature = "std")] 120 | #[test] 121 | fn other_process_pid() -> Result<(), Error> { 122 | use std::fs::read_to_string; 123 | 124 | let path = "testfiles/other_process_pid.lock"; 125 | let mut file = LockFile::open(path)?; 126 | assert!(file.try_lock_with_pid()?); 127 | 128 | let content = read_to_string(path)?; 129 | assert!(content.trim().len() > 0); 130 | assert!(content.trim().chars().all(|ch| ch.is_ascii_digit())); 131 | 132 | check_try_lock_example(path, b"FAILURE\n")?; 133 | let content_again = read_to_string(path)?; 134 | assert!(content_again.trim().len() > 0); 135 | assert!(content_again.trim().chars().all(|ch| ch.is_ascii_digit())); 136 | file.unlock()?; 137 | check_try_lock_example(path, b"SUCCESS\n")?; 138 | 139 | let child_content = read_to_string(path)?; 140 | assert!(child_content.trim().len() == 0); 141 | 142 | assert!(file.try_lock_with_pid()?); 143 | 144 | let content_again = read_to_string(path)?; 145 | assert_eq!(content_again, content); 146 | 147 | check_try_lock_with_pid_example(path, TryPidExpectedRes::Failure)?; 148 | let content_again = read_to_string(path)?; 149 | assert!(content_again.trim().len() > 0); 150 | assert!(content_again.trim().chars().all(|ch| ch.is_ascii_digit())); 151 | file.unlock()?; 152 | check_try_lock_with_pid_example( 153 | path, 154 | TryPidExpectedRes::Success { pid_to_differ: &content }, 155 | )?; 156 | 157 | let child_content = read_to_string(path)?; 158 | assert!(child_content.trim().len() == 0); 159 | 160 | Ok(()) 161 | } 162 | 163 | #[cfg(feature = "std")] 164 | #[test] 165 | fn other_process_but_curr_reads() -> Result<(), Error> { 166 | use std::fs::read_to_string; 167 | 168 | let path = "testfiles/other_process_but_curr_reads.lock"; 169 | let mut file = LockFile::open(path)?; 170 | file.lock()?; 171 | 172 | check_try_lock_example(path, b"FAILURE\n")?; 173 | let mut _content = read_to_string(path)?; 174 | check_try_lock_example(path, b"FAILURE\n")?; 175 | 176 | file.unlock()?; 177 | check_try_lock_example(path, b"SUCCESS\n")?; 178 | Ok(()) 179 | } 180 | -------------------------------------------------------------------------------- /src/unix.rs: -------------------------------------------------------------------------------- 1 | use crate::{EitherOsStr, IntoOsString, ToOsStr}; 2 | use core::{fmt, mem::transmute, ptr::NonNull, slice, str}; 3 | 4 | #[cfg(feature = "std")] 5 | use std::{ffi, os::unix::ffi::OsStrExt}; 6 | 7 | #[cfg(not(feature = "std"))] 8 | extern "C" { 9 | /// Yeah, I had to copy this from std 10 | #[cfg(not(target_os = "dragonfly"))] 11 | #[cfg_attr( 12 | any( 13 | target_os = "linux", 14 | target_os = "emscripten", 15 | target_os = "fuchsia", 16 | target_os = "l4re" 17 | ), 18 | link_name = "__errno_location" 19 | )] 20 | #[cfg_attr( 21 | any( 22 | target_os = "netbsd", 23 | target_os = "openbsd", 24 | target_os = "android", 25 | target_os = "redox", 26 | target_env = "newlib" 27 | ), 28 | link_name = "__errno" 29 | )] 30 | #[cfg_attr(target_os = "solaris", link_name = "___errno")] 31 | #[cfg_attr( 32 | any(target_os = "macos", target_os = "ios", target_os = "freebsd"), 33 | link_name = "__error" 34 | )] 35 | #[cfg_attr(target_os = "haiku", link_name = "_errnop")] 36 | fn errno_location() -> *mut libc::c_int; 37 | } 38 | 39 | #[cfg(not(feature = "std"))] 40 | fn errno() -> libc::c_int { 41 | unsafe { *errno_location() } 42 | } 43 | 44 | #[cfg(feature = "std")] 45 | fn errno() -> libc::c_int { 46 | Error::last_os_error().raw_os_error().unwrap_or(0) as libc::c_int 47 | } 48 | 49 | /// A type representing file descriptor on Unix. 50 | pub type FileDesc = libc::c_int; 51 | 52 | /// A type representing Process ID on Unix. 53 | pub type Pid = libc::pid_t; 54 | 55 | #[cfg(feature = "std")] 56 | /// An IO error. 57 | pub type Error = std::io::Error; 58 | 59 | #[cfg(not(feature = "std"))] 60 | #[derive(Debug)] 61 | /// An IO error. Without std, you can only get a message or an OS error code. 62 | pub struct Error { 63 | code: i32, 64 | } 65 | 66 | #[cfg(not(feature = "std"))] 67 | impl Error { 68 | /// Creates an error from a raw OS error code. 69 | pub fn from_raw_os_error(code: i32) -> Self { 70 | Self { code } 71 | } 72 | 73 | /// Creates an error from the last OS error code. 74 | pub fn last_os_error() -> Error { 75 | Self::from_raw_os_error(errno() as i32) 76 | } 77 | 78 | /// Raw OS error code. Returns option for compatibility with std. 79 | pub fn raw_os_error(&self) -> Option { 80 | Some(self.code) 81 | } 82 | } 83 | 84 | #[cfg(not(feature = "std"))] 85 | impl fmt::Display for Error { 86 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 87 | let msg_ptr = unsafe { libc::strerror(self.code as libc::c_int) }; 88 | let len = unsafe { libc::strlen(msg_ptr) }; 89 | let slice = unsafe { slice::from_raw_parts(msg_ptr, len) }; 90 | write!(fmt, "{}", unsafe { OsStr::from_slice(slice) })?; 91 | Ok(()) 92 | } 93 | } 94 | 95 | /// Owned allocation of an OS-native string. 96 | pub struct OsString { 97 | alloc: NonNull, 98 | /// Length without the nul-byte. 99 | len: usize, 100 | } 101 | 102 | unsafe impl Send for OsString {} 103 | 104 | impl Drop for OsString { 105 | fn drop(&mut self) { 106 | let ptr = self.alloc.as_ptr() as *mut libc::c_void; 107 | unsafe { libc::free(ptr) } 108 | } 109 | } 110 | 111 | impl AsRef for OsString { 112 | fn as_ref(&self) -> &OsStr { 113 | unsafe { 114 | OsStr::from_slice(slice::from_raw_parts( 115 | self.alloc.as_ptr(), 116 | self.len, 117 | )) 118 | } 119 | } 120 | } 121 | 122 | /// Borrowed allocation of an OS-native string. 123 | #[repr(transparent)] 124 | pub struct OsStr { 125 | bytes: [libc::c_char], 126 | } 127 | 128 | impl OsStr { 129 | /// Unsafe cause sequence needs to end with 0. 130 | unsafe fn from_slice(slice: &[libc::c_char]) -> &Self { 131 | transmute(slice) 132 | } 133 | } 134 | 135 | impl fmt::Debug for OsStr { 136 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 137 | let mut first = false; 138 | write!(fmt, "[")?; 139 | 140 | for &signed in &self.bytes { 141 | let byte = signed as u8; 142 | if first { 143 | first = false; 144 | } else { 145 | write!(fmt, ", ")?; 146 | } 147 | if byte.is_ascii() { 148 | write!(fmt, "{:?}", char::from(byte))?; 149 | } else { 150 | write!(fmt, "'\\x{:x}'", byte)?; 151 | } 152 | } 153 | 154 | write!(fmt, "]")?; 155 | Ok(()) 156 | } 157 | } 158 | 159 | impl fmt::Display for OsStr { 160 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 161 | let ptr = self.bytes.as_ptr(); 162 | let len = self.bytes.len(); 163 | let slice = unsafe { slice::from_raw_parts(ptr as _, len) }; 164 | 165 | let mut sub = slice; 166 | 167 | while sub.len() > 0 { 168 | match str::from_utf8(sub) { 169 | Ok(string) => { 170 | write!(fmt, "{}", string)?; 171 | sub = &[]; 172 | }, 173 | Err(err) => { 174 | let string = str::from_utf8(&sub[.. err.valid_up_to()]) 175 | .expect("Inconsistent utf8 error"); 176 | write!(fmt, "{}�", string,)?; 177 | 178 | sub = &sub[err.valid_up_to() + 1 ..]; 179 | }, 180 | } 181 | } 182 | 183 | Ok(()) 184 | } 185 | } 186 | 187 | impl<'str> IntoOsString for &'str OsStr { 188 | fn into_os_string(self) -> Result { 189 | let len = self.bytes.len(); 190 | let alloc = unsafe { libc::malloc(len + 1) }; 191 | let alloc = match NonNull::new(alloc as *mut libc::c_char) { 192 | Some(alloc) => alloc, 193 | None => { 194 | return Err(Error::last_os_error()); 195 | }, 196 | }; 197 | unsafe { 198 | libc::memcpy( 199 | alloc.as_ptr() as *mut libc::c_void, 200 | self.bytes.as_ptr() as *const libc::c_void, 201 | len + 1, 202 | ); 203 | } 204 | 205 | Ok(OsString { alloc, len }) 206 | } 207 | } 208 | 209 | impl ToOsStr for str { 210 | fn to_os_str(&self) -> Result { 211 | make_os_str(self.as_bytes()) 212 | } 213 | } 214 | 215 | #[cfg(feature = "std")] 216 | impl ToOsStr for ffi::OsStr { 217 | fn to_os_str(&self) -> Result { 218 | make_os_str(self.as_bytes()) 219 | } 220 | } 221 | 222 | /// Path must not contain a nul-byte in the middle, but a nul-byte in the end 223 | /// (and only in the end) is allowed, which in this case no extra allocation 224 | /// will be made. Otherwise, an extra allocation is made. 225 | fn make_os_str(slice: &[u8]) -> Result { 226 | if let Some((&last, init)) = slice.split_last() { 227 | if init.contains(&0) { 228 | panic!("Path to file cannot contain nul-byte in the middle"); 229 | } 230 | if last == 0 { 231 | let str = unsafe { OsStr::from_slice(transmute(slice)) }; 232 | return Ok(EitherOsStr::Borrowed(str)); 233 | } 234 | } 235 | 236 | let alloc = unsafe { libc::malloc(slice.len() + 1) }; 237 | let alloc = match NonNull::new(alloc as *mut libc::c_char) { 238 | Some(alloc) => alloc, 239 | None => { 240 | return Err(Error::last_os_error()); 241 | }, 242 | }; 243 | unsafe { 244 | libc::memcpy( 245 | alloc.as_ptr() as *mut libc::c_void, 246 | slice.as_ptr() as *const libc::c_void, 247 | slice.len(), 248 | ); 249 | *alloc.as_ptr().add(slice.len()) = 0; 250 | } 251 | 252 | Ok(EitherOsStr::Owned(OsString { alloc, len: slice.len() })) 253 | } 254 | 255 | /// Returns the ID of the current process. 256 | pub fn pid() -> Pid { 257 | unsafe { libc::getpid() } 258 | } 259 | 260 | /// Opens a file with only purpose of locking it. Creates it if it does not 261 | /// exist. Path must not contain a nul-byte in the middle, but a nul-byte in the 262 | /// end (and only in the end) is allowed, which in this case no extra allocation 263 | /// will be made. Otherwise, an extra allocation is made. 264 | pub fn open(path: &OsStr) -> Result { 265 | let fd = unsafe { 266 | libc::open( 267 | path.bytes.as_ptr(), 268 | libc::O_RDWR | libc::O_CLOEXEC | libc::O_CREAT, 269 | (libc::S_IRUSR | libc::S_IWUSR | libc::S_IRGRP | libc::S_IROTH) 270 | as libc::c_int, 271 | ) 272 | }; 273 | 274 | if fd >= 0 { 275 | Ok(fd) 276 | } else { 277 | Err(Error::last_os_error()) 278 | } 279 | } 280 | 281 | /// Writes data into the given open file. 282 | pub fn write(fd: FileDesc, mut bytes: &[u8]) -> Result<(), Error> { 283 | while bytes.len() > 0 { 284 | let written = unsafe { 285 | libc::write(fd, bytes.as_ptr() as *const libc::c_void, bytes.len()) 286 | }; 287 | if written < 0 && errno() != libc::EAGAIN { 288 | return Err(Error::last_os_error()); 289 | } 290 | bytes = &bytes[written as usize ..]; 291 | } 292 | 293 | Ok(()) 294 | } 295 | 296 | pub fn fsync(fd: FileDesc) -> Result<(), Error> { 297 | let result = unsafe { libc::fsync(fd) }; 298 | 299 | if result >= 0 { 300 | Ok(()) 301 | } else { 302 | Err(Error::last_os_error()) 303 | } 304 | } 305 | 306 | /// Truncates the file referenced by the given file descriptor and seeks it to 307 | /// the start. 308 | pub fn truncate(fd: FileDesc) -> Result<(), Error> { 309 | let res = unsafe { libc::lseek(fd, 0, libc::SEEK_SET) }; 310 | if res < 0 { 311 | return Err(Error::last_os_error()); 312 | } 313 | 314 | let res = unsafe { libc::ftruncate(fd, 0) }; 315 | if res < 0 { 316 | Err(Error::last_os_error()) 317 | } else { 318 | Ok(()) 319 | } 320 | } 321 | 322 | /// Tries to lock a file and blocks until it is possible to lock. 323 | pub fn lock(fd: FileDesc) -> Result<(), Error> { 324 | let res = unsafe { libc::flock(fd, libc::LOCK_EX) }; 325 | if res >= 0 { 326 | Ok(()) 327 | } else { 328 | Err(Error::last_os_error()) 329 | } 330 | } 331 | 332 | /// Tries to lock a file but returns as soon as possible if already locked. 333 | pub fn try_lock(fd: FileDesc) -> Result { 334 | let res = unsafe { libc::flock(fd, libc::LOCK_EX | libc::LOCK_NB) }; 335 | if res >= 0 { 336 | Ok(true) 337 | } else { 338 | let err = errno(); 339 | if err == libc::EWOULDBLOCK || err == libc::EINTR { 340 | Ok(false) 341 | } else { 342 | Err(Error::from_raw_os_error(err as i32)) 343 | } 344 | } 345 | } 346 | 347 | /// Unlocks the file. 348 | pub fn unlock(fd: FileDesc) -> Result<(), Error> { 349 | let res = unsafe { libc::flock(fd, libc::LOCK_UN) }; 350 | if res >= 0 { 351 | Ok(()) 352 | } else { 353 | Err(Error::last_os_error()) 354 | } 355 | } 356 | 357 | /// Closes the file. 358 | pub fn close(fd: FileDesc) { 359 | unsafe { libc::close(fd) }; 360 | } 361 | -------------------------------------------------------------------------------- /src/windows.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "std"))] 2 | use winapi::um::{ 3 | winbase::{ 4 | FormatMessageW, 5 | FORMAT_MESSAGE_ALLOCATE_BUFFER, 6 | FORMAT_MESSAGE_FROM_SYSTEM, 7 | FORMAT_MESSAGE_IGNORE_INSERTS, 8 | }, 9 | winnt::{LANG_USER_DEFAULT, LPWSTR}, 10 | }; 11 | 12 | #[cfg(feature = "std")] 13 | use std::{ffi, os::windows::ffi::OsStrExt}; 14 | 15 | use crate::{EitherOsStr, IntoOsString, ToOsStr}; 16 | use core::{ 17 | convert::TryFrom, 18 | fmt, 19 | mem::{transmute, MaybeUninit}, 20 | ptr::{self, NonNull}, 21 | slice, 22 | }; 23 | use winapi::{ 24 | shared::{ 25 | minwindef::{DWORD, FALSE, LPCVOID, LPVOID, TRUE}, 26 | winerror::{ERROR_INVALID_DATA, ERROR_LOCK_VIOLATION}, 27 | }, 28 | um::{ 29 | errhandlingapi::GetLastError, 30 | fileapi::{ 31 | CreateFileW, 32 | FlushFileBuffers, 33 | LockFileEx, 34 | SetEndOfFile, 35 | SetFilePointer, 36 | UnlockFileEx, 37 | WriteFile, 38 | INVALID_SET_FILE_POINTER, 39 | OPEN_ALWAYS, 40 | }, 41 | handleapi::{CloseHandle, INVALID_HANDLE_VALUE}, 42 | minwinbase::{ 43 | OVERLAPPED_u, 44 | LMEM_FIXED, 45 | LOCKFILE_EXCLUSIVE_LOCK, 46 | LOCKFILE_FAIL_IMMEDIATELY, 47 | LPOVERLAPPED, 48 | LPSECURITY_ATTRIBUTES, 49 | OVERLAPPED, 50 | SECURITY_ATTRIBUTES, 51 | }, 52 | processthreadsapi::GetCurrentProcessId, 53 | synchapi::{CreateEventW, WaitForSingleObject}, 54 | winbase::{LocalAlloc, LocalFree, FILE_BEGIN, WAIT_FAILED}, 55 | winnt::{ 56 | RtlCopyMemory, 57 | FILE_SHARE_DELETE, 58 | FILE_SHARE_READ, 59 | FILE_SHARE_WRITE, 60 | GENERIC_WRITE, 61 | HANDLE, 62 | WCHAR, 63 | }, 64 | }, 65 | }; 66 | 67 | /// A type representing file descriptor on Unix. 68 | pub type FileDesc = HANDLE; 69 | 70 | /// A type representing Process ID on Windows. 71 | pub type Pid = DWORD; 72 | 73 | #[cfg(feature = "std")] 74 | /// An IO error. 75 | pub type Error = std::io::Error; 76 | 77 | #[cfg(not(feature = "std"))] 78 | #[derive(Debug)] 79 | /// An IO error. Without std, you can only get a message or an OS error code. 80 | pub struct Error { 81 | code: i32, 82 | } 83 | 84 | #[cfg(not(feature = "std"))] 85 | impl Error { 86 | /// Creates an error from a raw OS error code. 87 | pub fn from_raw_os_error(code: i32) -> Self { 88 | Self { code } 89 | } 90 | 91 | /// Creates an error from the last OS error code. 92 | pub fn last_os_error() -> Error { 93 | Self::from_raw_os_error(unsafe { GetLastError() } as i32) 94 | } 95 | 96 | /// Raw OS error code. Returns option for compatibility with std. 97 | pub fn raw_os_error(&self) -> Option { 98 | Some(self.code) 99 | } 100 | } 101 | 102 | #[cfg(not(feature = "std"))] 103 | impl fmt::Display for Error { 104 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 105 | let mut buf: LPWSTR = ptr::null_mut(); 106 | let res = unsafe { 107 | FormatMessageW( 108 | FORMAT_MESSAGE_ALLOCATE_BUFFER 109 | | FORMAT_MESSAGE_FROM_SYSTEM 110 | | FORMAT_MESSAGE_IGNORE_INSERTS, 111 | ptr::null_mut(), 112 | self.code as DWORD, 113 | LANG_USER_DEFAULT as DWORD, 114 | &mut buf as *mut LPWSTR as LPWSTR, 115 | 0, 116 | ptr::null_mut(), 117 | ) 118 | }; 119 | 120 | if res == 0 { 121 | write!(fmt, "error getting error message")?; 122 | } else { 123 | { 124 | let slice = unsafe { 125 | slice::from_raw_parts(buf as *const WCHAR, res as usize) 126 | }; 127 | let str = unsafe { OsStr::from_slice(slice) }; 128 | write!(fmt, "{}", str)?; 129 | } 130 | unsafe { 131 | LocalFree(buf as LPVOID); 132 | } 133 | } 134 | 135 | Ok(()) 136 | } 137 | } 138 | 139 | /// Owned allocation of an OS-native string. 140 | pub struct OsString { 141 | alloc: NonNull, 142 | /// Length without the nul-byte. 143 | len: usize, 144 | } 145 | 146 | unsafe impl Send for OsString {} 147 | 148 | impl Drop for OsString { 149 | fn drop(&mut self) { 150 | let ptr = self.alloc.as_ptr() as LPVOID; 151 | unsafe { 152 | LocalFree(ptr); 153 | } 154 | } 155 | } 156 | 157 | impl AsRef for OsString { 158 | fn as_ref(&self) -> &OsStr { 159 | unsafe { 160 | OsStr::from_slice(slice::from_raw_parts( 161 | self.alloc.as_ptr(), 162 | self.len, 163 | )) 164 | } 165 | } 166 | } 167 | 168 | /// Borrowed allocation of an OS-native string. 169 | pub struct OsStr { 170 | chars: [WCHAR], 171 | } 172 | 173 | impl OsStr { 174 | /// Unsafe cause sequence needs to end with 0. 175 | unsafe fn from_slice(slice: &[WCHAR]) -> &Self { 176 | transmute(slice) 177 | } 178 | 179 | fn chars(&self) -> Chars { 180 | Chars { inner: self.chars.iter() } 181 | } 182 | } 183 | 184 | impl fmt::Debug for OsStr { 185 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 186 | let mut first = false; 187 | write!(fmt, "[")?; 188 | 189 | for ch in self.chars() { 190 | if first { 191 | first = false; 192 | } else { 193 | write!(fmt, ", ")?; 194 | } 195 | write!(fmt, "{:?}", ch)?; 196 | } 197 | 198 | write!(fmt, "]")?; 199 | Ok(()) 200 | } 201 | } 202 | 203 | impl fmt::Display for OsStr { 204 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 205 | for ch in self.chars() { 206 | write!(fmt, "{}", ch)?; 207 | } 208 | 209 | Ok(()) 210 | } 211 | } 212 | 213 | impl<'str> IntoOsString for &'str OsStr { 214 | fn into_os_string(self) -> Result { 215 | let len = self.chars.len(); 216 | let alloc = unsafe { LocalAlloc(LMEM_FIXED, len * 2 + 2) }; 217 | let alloc = match NonNull::new(alloc as *mut WCHAR) { 218 | Some(alloc) => alloc, 219 | None => { 220 | return Err(Error::last_os_error()); 221 | }, 222 | }; 223 | unsafe { 224 | RtlCopyMemory( 225 | alloc.as_ptr() as LPVOID, 226 | self.chars.as_ptr() as _, 227 | len * 2 + 2, 228 | ); 229 | } 230 | 231 | Ok(OsString { alloc, len }) 232 | } 233 | } 234 | 235 | impl ToOsStr for str { 236 | fn to_os_str(&self) -> Result { 237 | let res = unsafe { make_os_string(|| self.encode_utf16()) }; 238 | res.map(EitherOsStr::Owned) 239 | } 240 | } 241 | 242 | #[cfg(feature = "std")] 243 | impl ToOsStr for ffi::OsStr { 244 | fn to_os_str(&self) -> Result { 245 | let res = unsafe { make_os_string(|| self.encode_wide()) }; 246 | res.map(EitherOsStr::Owned) 247 | } 248 | } 249 | 250 | /// Unsafe because the returned iterator must be exactly the same. 251 | unsafe fn make_os_string(mut make_iter: F) -> Result 252 | where 253 | F: FnMut() -> I, 254 | I: Iterator, 255 | { 256 | let mut len = 0; 257 | let mut prev_zero = false; 258 | for ch in make_iter() { 259 | if prev_zero { 260 | Err(Error::from_raw_os_error(ERROR_INVALID_DATA as i32))?; 261 | } 262 | if ch == 0 { 263 | prev_zero = true; 264 | } else { 265 | len += 1; 266 | } 267 | } 268 | 269 | let alloc = LocalAlloc(LMEM_FIXED, len * 2 + 2); 270 | let alloc = match NonNull::new(alloc as *mut WCHAR) { 271 | Some(alloc) => alloc, 272 | None => { 273 | return Err(Error::last_os_error()); 274 | }, 275 | }; 276 | 277 | let mut iter = make_iter(); 278 | for i in 0 .. len { 279 | let ch = iter.next().expect("Inconsistent .encode_utf16()"); 280 | *alloc.as_ptr().add(i) = ch; 281 | } 282 | *alloc.as_ptr().add(len) = 0; 283 | Ok(OsString { alloc, len }) 284 | } 285 | 286 | #[derive(Debug)] 287 | struct Chars<'str> { 288 | inner: slice::Iter<'str, WCHAR>, 289 | } 290 | 291 | impl<'str> Iterator for Chars<'str> { 292 | type Item = char; 293 | 294 | fn next(&mut self) -> Option { 295 | let curr = *self.inner.next()?; 296 | if curr <= 0xD7FF || curr >= 0xE000 { 297 | let ch = char::try_from(curr as u32) 298 | .expect("Inconsistent char implementation"); 299 | Some(ch) 300 | } else { 301 | let next = *self.inner.next()?; 302 | let high = curr as u32 - 0xD800; 303 | let low = next as u32 - 0xDC00; 304 | let ch = char::try_from((high << 10 | low) + 0x10000) 305 | .expect("Inconsistent char implementation"); 306 | Some(ch) 307 | } 308 | } 309 | } 310 | 311 | /// Helper to auto-drop a HANDLE. 312 | #[derive(Debug)] 313 | struct DropHandle { 314 | /// HANDLE being dropped. 315 | handle: HANDLE, 316 | } 317 | 318 | impl Drop for DropHandle { 319 | fn drop(&mut self) { 320 | unsafe { 321 | CloseHandle(self.handle); 322 | } 323 | } 324 | } 325 | 326 | /// Creates an event to be used by this implementation. 327 | fn make_event() -> Result { 328 | let mut security = make_security_attributes(); 329 | let res = unsafe { 330 | CreateEventW( 331 | &mut security as LPSECURITY_ATTRIBUTES, 332 | FALSE, 333 | FALSE, 334 | ptr::null_mut(), 335 | ) 336 | }; 337 | 338 | if res != INVALID_HANDLE_VALUE { 339 | Ok(res) 340 | } else { 341 | Err(Error::last_os_error()) 342 | } 343 | } 344 | 345 | /// Creates security attributes to be used with this implementation. 346 | fn make_security_attributes() -> SECURITY_ATTRIBUTES { 347 | SECURITY_ATTRIBUTES { 348 | nLength: 0, 349 | lpSecurityDescriptor: ptr::null_mut(), 350 | bInheritHandle: FALSE, 351 | } 352 | } 353 | 354 | /// Creates an overlapped struct to be used with this implementation. 355 | fn make_overlapped() -> Result { 356 | Ok(OVERLAPPED { 357 | Internal: 0, 358 | InternalHigh: 0, 359 | u: { 360 | let mut uninit = MaybeUninit::::uninit(); 361 | unsafe { 362 | let mut refer = (&mut *uninit.as_mut_ptr()).s_mut(); 363 | refer.Offset = DWORD::max_value() - 1; 364 | refer.OffsetHigh = DWORD::max_value() - 1; 365 | uninit.assume_init() 366 | } 367 | }, 368 | hEvent: make_event()?, 369 | }) 370 | } 371 | 372 | /// Returns the ID of the current process. 373 | pub fn pid() -> Pid { 374 | unsafe { GetCurrentProcessId() } 375 | } 376 | 377 | /// Opens a file with only purpose of locking it. Creates it if it does not 378 | /// exist. Path must not contain a nul-byte in the middle, but a nul-byte in the 379 | /// end (and only in the end) is allowed, which in this case no extra allocation 380 | /// will be made. Otherwise, an extra allocation is made. 381 | pub fn open(path: &OsStr) -> Result { 382 | let mut security = make_security_attributes(); 383 | let handle = unsafe { 384 | CreateFileW( 385 | path.chars.as_ptr(), 386 | GENERIC_WRITE, 387 | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 388 | &mut security as LPSECURITY_ATTRIBUTES, 389 | OPEN_ALWAYS, 390 | 0, 391 | ptr::null_mut(), 392 | ) 393 | }; 394 | 395 | if handle != INVALID_HANDLE_VALUE { 396 | Ok(handle) 397 | } else { 398 | Err(Error::last_os_error()) 399 | } 400 | } 401 | 402 | /// Writes data into the given open file. 403 | pub fn write(handle: FileDesc, bytes: &[u8]) -> Result<(), Error> { 404 | let result = unsafe { 405 | WriteFile( 406 | handle, 407 | bytes.as_ptr() as LPCVOID, 408 | bytes.len() as DWORD, 409 | ptr::null_mut(), 410 | ptr::null_mut(), 411 | ) 412 | }; 413 | if result == 0 { 414 | Err(Error::last_os_error()) 415 | } else { 416 | Ok(()) 417 | } 418 | } 419 | 420 | pub fn fsync(handle: FileDesc) -> Result<(), Error> { 421 | let result = unsafe { FlushFileBuffers(handle) }; 422 | if result == 0 { 423 | Err(Error::last_os_error()) 424 | } else { 425 | Ok(()) 426 | } 427 | } 428 | 429 | /// Truncates the file referenced by the given HANDLE and seeks it to the start. 430 | pub fn truncate(handle: FileDesc) -> Result<(), Error> { 431 | let res = unsafe { SetFilePointer(handle, 0, ptr::null_mut(), FILE_BEGIN) }; 432 | if res == INVALID_SET_FILE_POINTER { 433 | return Err(Error::last_os_error()); 434 | } 435 | 436 | let res = unsafe { SetEndOfFile(handle) }; 437 | if res == 0 { 438 | Err(Error::last_os_error()) 439 | } else { 440 | Ok(()) 441 | } 442 | } 443 | 444 | /// Tries to lock a file and blocks until it is possible to lock. 445 | pub fn lock(handle: FileDesc) -> Result<(), Error> { 446 | let mut overlapped = make_overlapped()?; 447 | let drop_handle = DropHandle { handle: overlapped.hEvent }; 448 | let res = unsafe { 449 | LockFileEx( 450 | handle, 451 | LOCKFILE_EXCLUSIVE_LOCK, 452 | 0, 453 | 1, 454 | 1, 455 | &mut overlapped as LPOVERLAPPED, 456 | ) 457 | }; 458 | 459 | let ret = if res == TRUE { 460 | let res = unsafe { WaitForSingleObject(overlapped.hEvent, 0) }; 461 | if res != WAIT_FAILED { 462 | Ok(()) 463 | } else { 464 | Err(Error::last_os_error()) 465 | } 466 | } else { 467 | Err(Error::last_os_error()) 468 | }; 469 | 470 | drop(drop_handle); 471 | ret 472 | } 473 | 474 | /// Tries to lock a file but returns as soon as possible if already locked. 475 | pub fn try_lock(handle: FileDesc) -> Result { 476 | let mut overlapped = make_overlapped()?; 477 | let drop_handle = DropHandle { handle: overlapped.hEvent }; 478 | let res = unsafe { 479 | LockFileEx( 480 | handle, 481 | LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY, 482 | 0, 483 | 1, 484 | 1, 485 | &mut overlapped as LPOVERLAPPED, 486 | ) 487 | }; 488 | 489 | let ret = if res == TRUE { 490 | let res = unsafe { WaitForSingleObject(overlapped.hEvent, 0) }; 491 | if res != WAIT_FAILED { 492 | Ok(true) 493 | } else { 494 | Err(Error::last_os_error()) 495 | } 496 | } else { 497 | let err = unsafe { GetLastError() }; 498 | if err == ERROR_LOCK_VIOLATION { 499 | Ok(false) 500 | } else { 501 | Err(Error::from_raw_os_error(err as i32)) 502 | } 503 | }; 504 | 505 | drop(drop_handle); 506 | ret 507 | } 508 | 509 | /// Unlocks the file. 510 | pub fn unlock(handle: FileDesc) -> Result<(), Error> { 511 | let mut overlapped = make_overlapped()?; 512 | let drop_handle = DropHandle { handle: overlapped.hEvent }; 513 | let res = unsafe { 514 | UnlockFileEx(handle, 0, 1, 1, &mut overlapped as LPOVERLAPPED) 515 | }; 516 | 517 | let ret = if res == TRUE { 518 | let res = unsafe { WaitForSingleObject(overlapped.hEvent, 0) }; 519 | if res != WAIT_FAILED { 520 | Ok(()) 521 | } else { 522 | Err(Error::last_os_error()) 523 | } 524 | } else { 525 | Err(Error::last_os_error()) 526 | }; 527 | 528 | drop(drop_handle); 529 | ret 530 | } 531 | 532 | /// Closes the file. 533 | pub fn close(handle: FileDesc) { 534 | unsafe { 535 | CloseHandle(handle); 536 | } 537 | } 538 | -------------------------------------------------------------------------------- /testfiles/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brunoczim/fslock/cceb79f02c73cee0def73e1be0696fc26b395863/testfiles/.gitignore --------------------------------------------------------------------------------