Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
cberner committed Sep 23, 2024
1 parent 4f1be92 commit 75e5e04
Show file tree
Hide file tree
Showing 8 changed files with 347 additions and 0 deletions.
130 changes: 130 additions & 0 deletions library/std/src/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,136 @@ impl File {
self.inner.datasync()
}

/// Acquires an exclusive lock on the file. Blocks until the lock can be acquired.
///
/// Note, this method is meant to interact with [`lock_shared`], [`try_lock`],
/// [`try_lock_shared`], and [`unlock`]. Its interactions with other methods, such as [`read`]
/// and [`write`] are platform specific, and it may or may not cause non-lockholders to block.
///
/// The exact behavior when attempting to lock a file handle which is already locked is
/// unspecified and platform dependent. It may deadlock, it may convert the lock from shared to
/// exclusive or vice versa, but the requested lock will be held if the function returns.
///
/// # Examples
///
/// ```no_run
/// #![feature(file_lock)]
/// use std::fs::File;
///
/// fn main() -> std::io::Result<()> {
/// let f = File::open("foo.txt")?;
/// f.lock()?;
/// Ok(())
/// }
/// ```
#[unstable(feature = "file_lock", issue = "none")]
pub fn lock(&self) -> io::Result<()> {
self.inner.lock()
}

/// Acquires a shared lock on the file. Blocks until the lock can be acquired.
///
/// Note, this method is meant to interact with [`lock`], [`try_lock`], [`try_lock_shared`],
/// and [`unlock`]. Its interactions with other methods, such as [`read`] and [`write`] are
/// platform specific, and it may or may not cause non-lockholders to block
///
/// The exact behavior when attempting to lock a file handle which is already locked is
/// unspecified and platform dependent. It may deadlock, it may convert the lock from shared to
/// exclusive or vice versa, but the requested lock will be held if the function returns.
///
/// # Examples
///
/// ```no_run
/// #![feature(file_lock)]
/// use std::fs::File;
///
/// fn main() -> std::io::Result<()> {
/// let f = File::open("foo.txt")?;
/// f.lock_shared()?;
/// Ok(())
/// }
/// ```
#[unstable(feature = "file_lock", issue = "none")]
pub fn lock_shared(&self) -> io::Result<()> {
self.inner.lock_shared()
}

/// If file is not locked, acquires an exclusive lock on the file.
/// Returns `Ok(false)` if the file is locked.
///
/// Note, this method is meant to interact with [`lock`], [`lock_shared`], [`try_lock_shared`],
/// and [`unlock`]. Its interactions with other methods, such as [`read`] and [`write`] are
/// platform specific, and it may or may not cause non-lockholders to block
///
/// The exact behavior when attempting to lock a file handle which is already locked is
/// unspecified and platform dependent. It may deadlock, it may convert the lock from shared to
/// exclusive or vice versa, but the requested lock will be held if the function returns.
///
/// # Examples
///
/// ```no_run
/// #![feature(file_lock)]
/// use std::fs::File;
///
/// fn main() -> std::io::Result<()> {
/// let f = File::open("foo.txt")?;
/// f.try_lock()?;
/// Ok(())
/// }
/// ```
#[unstable(feature = "file_lock", issue = "none")]
pub fn try_lock(&self) -> io::Result<bool> {
self.inner.try_lock()
}

/// If file is not exclusively locked, acquires a shared lock on the file.
/// Returns `Ok(false)` if the file is exclusively locked.
/// Note, this method is meant to interact with [`lock`], [`lock_shared`], [`try_lock`],
/// and [`unlock`]. Its interactions with other methods, such as [`read`] and [`write`] are
/// platform specific, and it may or may not cause non-lockholders to block
///
/// The exact behavior when attempting to lock a file handle which is already locked is
/// unspecified and platform dependent. It may deadlock, it may convert the lock from shared to
/// exclusive or vice versa, but the requested lock will be held if the function returns.
///
/// # Examples
///
/// ```no_run
/// #![feature(file_lock)]
/// use std::fs::File;
///
/// fn main() -> std::io::Result<()> {
/// let f = File::open("foo.txt")?;
/// f.try_lock_shared()?;
/// Ok(())
/// }
/// ```
#[unstable(feature = "file_lock", issue = "none")]
pub fn try_lock_shared(&self) -> io::Result<bool> {
self.inner.try_lock_shared()
}

/// Releases all locks on the file.
///
/// # Examples
///
/// ```no_run
/// #![feature(file_lock)]
/// use std::fs::File;
///
/// fn main() -> std::io::Result<()> {
/// let f = File::open("foo.txt")?;
/// f.lock()?;
/// f.unlock()?;
/// Ok(())
/// }
/// ```
#[unstable(feature = "file_lock", issue = "none")]
pub fn unlock(&self) -> io::Result<()> {
self.inner.unlock()
}

/// Truncates or extends the underlying file, updating the size of
/// this file to become `size`.
///
Expand Down
80 changes: 80 additions & 0 deletions library/std/src/fs/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,86 @@ fn file_test_io_seek_and_write() {
assert!(read_str == final_msg);
}

#[test]
fn file_lock_multiple_shared() {
let tmpdir = tmpdir();
let filename = &tmpdir.join("file_lock_multiple_shared_test.txt");
let f1 = check!(File::create(filename));
let f2 = check!(File::open(filename));

// Check that we can acquire concurrent shared locks
check!(f1.lock_shared());
check!(f2.lock_shared());
check!(f1.unlock());
check!(f2.unlock());
assert!(check!(f1.try_lock_shared()));
assert!(check!(f2.try_lock_shared()));
}

#[test]
fn file_lock_blocking() {
let tmpdir = tmpdir();
let filename = &tmpdir.join("file_lock_blocking_test.txt");
let f1 = check!(File::create(filename));
let f2 = check!(File::open(filename));

// Check that shared locks block exclusive locks
check!(f1.lock_shared());
assert!(!check!(f2.try_lock()));
check!(f1.unlock());

// Check that exclusive locks block shared locks
check!(f1.lock());
assert!(!check!(f2.try_lock_shared()));
}

#[test]
fn file_lock_drop() {
let tmpdir = tmpdir();
let filename = &tmpdir.join("file_lock_dup_test.txt");
let f1 = check!(File::create(filename));
let f2 = check!(File::open(filename));

// Check that locks are released when the File is dropped
check!(f1.lock_shared());
assert!(!check!(f2.try_lock()));
drop(f1);
assert!(check!(f2.try_lock()));
}

#[test]
fn file_lock_dup() {
let tmpdir = tmpdir();
let filename = &tmpdir.join("file_lock_dup_test.txt");
let f1 = check!(File::create(filename));
let f2 = check!(File::open(filename));

// Check that locks are not dropped if the File has been cloned
check!(f1.lock_shared());
assert!(!check!(f2.try_lock()));
let cloned = check!(f1.try_clone());
drop(f1);
assert!(!check!(f2.try_lock()));
drop(cloned)
}

#[test]
#[cfg(windows)]
fn file_lock_double_unlock() {
let tmpdir = tmpdir();
let filename = &tmpdir.join("file_lock_double_unlock_test.txt");
let f1 = check!(File::create(filename));
let f2 = check!(File::open(filename));

// On Windows a file handle may acquire both a shared and exclusive lock.
// Check that both are released by unlock()
check!(f1.lock());
check!(f1.lock_shared());
assert!(!check!(f2.try_lock()));
check!(f1.unlock());
assert!(check!(f2.try_lock()));
}

#[test]
fn file_test_io_seek_shakedown() {
// 01234567890123
Expand Down
20 changes: 20 additions & 0 deletions library/std/src/sys/pal/hermit/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,26 @@ impl File {
self.fsync()
}

pub fn lock(&self) -> io::Result<()> {
unsupported()
}

pub fn lock_shared(&self) -> io::Result<()> {
unsupported()
}

pub fn try_lock(&self) -> io::Result<bool> {
unsupported()
}

pub fn try_lock_shared(&self) -> io::Result<bool> {
unsupported()
}

pub fn unlock(&self) -> io::Result<()> {
unsupported()
}

pub fn truncate(&self, _size: u64) -> io::Result<()> {
Err(Error::from_raw_os_error(22))
}
Expand Down
20 changes: 20 additions & 0 deletions library/std/src/sys/pal/solid/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,26 @@ impl File {
self.flush()
}

pub fn lock(&self) -> io::Result<()> {
unsupported()
}

pub fn lock_shared(&self) -> io::Result<()> {
unsupported()
}

pub fn try_lock(&self) -> io::Result<bool> {
unsupported()
}

pub fn try_lock_shared(&self) -> io::Result<bool> {
unsupported()
}

pub fn unlock(&self) -> io::Result<()> {
unsupported()
}

pub fn truncate(&self, _size: u64) -> io::Result<()> {
unsupported()
}
Expand Down
37 changes: 37 additions & 0 deletions library/std/src/sys/pal/unix/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1244,6 +1244,43 @@ impl File {
}
}

pub fn lock(&self) -> io::Result<()> {
cvt(unsafe { libc::flock(self.as_raw_fd(), libc::LOCK_EX) })?;
return Ok(());
}

pub fn lock_shared(&self) -> io::Result<()> {
cvt(unsafe { libc::flock(self.as_raw_fd(), libc::LOCK_SH) })?;
return Ok(());
}

pub fn try_lock(&self) -> io::Result<bool> {
let result = cvt(unsafe { libc::flock(self.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB) });
if let Err(ref err) = result {
if err.kind() == io::ErrorKind::WouldBlock {
return Ok(false);
}
}
result?;
return Ok(true);
}

pub fn try_lock_shared(&self) -> io::Result<bool> {
let result = cvt(unsafe { libc::flock(self.as_raw_fd(), libc::LOCK_SH | libc::LOCK_NB) });
if let Err(ref err) = result {
if err.kind() == io::ErrorKind::WouldBlock {
return Ok(false);
}
}
result?;
return Ok(true);
}

pub fn unlock(&self) -> io::Result<()> {
cvt(unsafe { libc::flock(self.as_raw_fd(), libc::LOCK_UN) })?;
return Ok(());
}

pub fn truncate(&self, size: u64) -> io::Result<()> {
let size: off64_t =
size.try_into().map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
Expand Down
20 changes: 20 additions & 0 deletions library/std/src/sys/pal/unsupported/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,26 @@ impl File {
self.0
}

pub fn lock(&self) -> io::Result<()> {
self.0
}

pub fn lock_shared(&self) -> io::Result<()> {
self.0
}

pub fn try_lock(&self) -> io::Result<bool> {
self.0
}

pub fn try_lock_shared(&self) -> io::Result<bool> {
self.0
}

pub fn unlock(&self) -> io::Result<()> {
self.0
}

pub fn truncate(&self, _size: u64) -> io::Result<()> {
self.0
}
Expand Down
20 changes: 20 additions & 0 deletions library/std/src/sys/pal/wasi/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,26 @@ impl File {
self.fd.datasync()
}

pub fn lock(&self) -> io::Result<()> {
unsupported()
}

pub fn lock_shared(&self) -> io::Result<()> {
unsupported()
}

pub fn try_lock(&self) -> io::Result<bool> {
unsupported()
}

pub fn try_lock_shared(&self) -> io::Result<bool> {
unsupported()
}

pub fn unlock(&self) -> io::Result<()> {
unsupported()
}

pub fn truncate(&self, size: u64) -> io::Result<()> {
self.fd.filestat_set_size(size)
}
Expand Down
Loading

0 comments on commit 75e5e04

Please sign in to comment.