fslock-0.2.1/.cargo_vcs_info.json 0000644 00000000136 00000000001 0012315 0 ustar {
"git": {
"sha1": "ad9154f992b1bdedc53d2f07d2974ccbbb539a3e"
},
"path_in_vcs": ""
} fslock-0.2.1/.github/workflows/check.yml 0000644 0000000 0000000 00000007276 00726746425 0016346 0 ustar 0000000 0000000 name: 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.yml 0000644 0000000 0000000 00000001320 00726746425 0016350 0 ustar 0000000 0000000 name: 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/.gitignore 0000644 0000000 0000000 00000000044 00726746425 0013123 0 ustar 0000000 0000000 /target
Cargo.lock
testfiles/*.lock
fslock-0.2.1/.rustfmt.toml 0000644 0000000 0000000 00000002605 00726746425 0013617 0 ustar 0000000 0000000 disable_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.md 0000644 0000000 0000000 00000001214 00726746425 0012744 0 ustar 0000000 0000000 # 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.lock 0000644 00000002061 00000000001 0010267 0 ustar # 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.toml 0000644 00000002350 00000000001 0010313 0 ustar # 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.orig 0000644 0000000 0000000 00000001511 00726746425 0014022 0 ustar 0000000 0000000 [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/LICENSE 0000644 0000000 0000000 00000002052 00726746425 0012141 0 ustar 0000000 0000000 MIT 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.md 0000644 0000000 0000000 00000001310 00726746425 0012407 0 ustar 0000000 0000000 # 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.rs 0000644 0000000 0000000 00000001110 00726746425 0016331 0 ustar 0000000 0000000 #[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.rs 0000644 0000000 0000000 00000001155 00726746425 0015151 0 ustar 0000000 0000000 #[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.rs 0000644 0000000 0000000 00000001621 00726746425 0017036 0 ustar 0000000 0000000 #[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.rs 0000644 0000000 0000000 00000004766 00726746425 0013075 0 ustar 0000000 0000000 //! 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.rs 0000644 0000000 0000000 00000026550 00726746425 0013050 0 ustar 0000000 0000000 #![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.rs 0000644 0000000 0000000 00000010554 00726746425 0013605 0 ustar 0000000 0000000 //! 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.rs 0000644 0000000 0000000 00000011272 00726746425 0013254 0 ustar 0000000 0000000 use 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.rs 0000644 0000000 0000000 00000022726 00726746425 0013266 0 ustar 0000000 0000000 use 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.rs 0000644 0000000 0000000 00000032707 00726746425 0013775 0 ustar 0000000 0000000 #[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/.gitignore 0000644 0000000 0000000 00000000000 00726746425 0015115 0 ustar 0000000 0000000