Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement open file descriptor locks in fcntl #1195

Merged
merged 14 commits into from Apr 8, 2020
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -5,6 +5,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased] - ReleaseDate
### Added
- Added support for `F_OFD_*` `fcntl` commands on Linux and Android.
(#[1195](https://github.com/nix-rust/nix/pull/1195))
- Added `env::clearenv()`: calls `libc::clearenv` on platforms
where it's available, and clears the environment of all variables
via `std::env::vars` and `std::env::remove_var` on others.
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Expand Up @@ -16,7 +16,7 @@ exclude = [
]

[dependencies]
libc = { version = "0.2.60", features = [ "extra_traits" ] }
libc = { version = "0.2.68", features = [ "extra_traits" ] }
bitflags = "1.1"
cfg-if = "0.1.10"
void = "1.0.2"
Expand Down
8 changes: 6 additions & 2 deletions src/fcntl.rs
Expand Up @@ -286,6 +286,12 @@ pub fn fcntl(fd: RawFd, arg: FcntlArg) -> Result<c_int> {
F_SETLKW(flock) => libc::fcntl(fd, libc::F_SETLKW, flock),
F_GETLK(flock) => libc::fcntl(fd, libc::F_GETLK, flock),
#[cfg(any(target_os = "android", target_os = "linux"))]
F_OFD_SETLK(flock) => libc::fcntl(fd, libc::F_OFD_SETLK, flock),
#[cfg(any(target_os = "android", target_os = "linux"))]
F_OFD_SETLKW(flock) => libc::fcntl(fd, libc::F_OFD_SETLKW, flock),
#[cfg(any(target_os = "android", target_os = "linux"))]
F_OFD_GETLK(flock) => libc::fcntl(fd, libc::F_OFD_GETLK, flock),
#[cfg(any(target_os = "android", target_os = "linux"))]
F_ADD_SEALS(flag) => libc::fcntl(fd, libc::F_ADD_SEALS, flag.bits()),
#[cfg(any(target_os = "android", target_os = "linux"))]
F_GET_SEALS => libc::fcntl(fd, libc::F_GET_SEALS),
Expand All @@ -295,8 +301,6 @@ pub fn fcntl(fd: RawFd, arg: FcntlArg) -> Result<c_int> {
F_GETPIPE_SZ => libc::fcntl(fd, libc::F_GETPIPE_SZ),
#[cfg(any(target_os = "linux", target_os = "android"))]
F_SETPIPE_SZ(size) => libc::fcntl(fd, libc::F_SETPIPE_SZ, size),
#[cfg(any(target_os = "linux", target_os = "android"))]
_ => unimplemented!()
}
};

Expand Down
70 changes: 69 additions & 1 deletion test/test_fcntl.rs
Expand Up @@ -65,13 +65,15 @@ fn test_readlink() {

#[cfg(any(target_os = "linux", target_os = "android"))]
mod linux_android {
use std::fs::File;
use std::io::prelude::*;
use std::io::SeekFrom;
use std::io::{BufRead, BufReader, SeekFrom};
use std::os::unix::prelude::*;

use libc::loff_t;

use nix::fcntl::*;
use nix::sys::stat::fstat;
use nix::sys::uio::IoVec;
use nix::unistd::{close, pipe, read, write};

Expand Down Expand Up @@ -197,6 +199,72 @@ mod linux_android {
let mut buf = [0u8; 200];
assert_eq!(100, read(fd, &mut buf).unwrap());
}

// This test is disabled for the target architectures below
// due to OFD locks not being available in the kernel/libc
// versions used in the CI environment.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably related to the fact that CI for these platforms runs under QEMU.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've amended the comment.

#[test]
#[cfg(not(any(target_arch = "aarch64",
target_arch = "arm",
target_arch = "armv7",
target_arch = "x86",
target_arch = "mips",
target_arch = "mips64",
target_arch = "mips64el",
target_arch = "powerpc64",
target_arch = "powerpc64le")))]
fn test_ofd_locks() {
let tmp = NamedTempFile::new().unwrap();

let fd = tmp.as_raw_fd();
let inode = fstat(fd).expect("fstat failed").st_ino as usize;

let mut flock = libc::flock {
l_type: libc::F_WRLCK as libc::c_short,
l_whence: libc::SEEK_SET as libc::c_short,
l_start: 0,
l_len: 0,
l_pid: 0,
};
fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("write lock failed");
assert_eq!(
Some(("OFDLCK".to_string(), "WRITE".to_string())),
lock_info(inode)
);

flock.l_type = libc::F_UNLCK as libc::c_short;
fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("write unlock failed");
assert_eq!(None, lock_info(inode));

flock.l_type = libc::F_RDLCK as libc::c_short;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this test is doing two separate things: setting and clearing a write lock, and setting and clearing a read lock. Please split it up into two separate tests. That will make it easier to debug.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("read lock failed");
assert_eq!(
Some(("OFDLCK".to_string(), "READ".to_string())),
lock_info(inode)
);

flock.l_type = libc::F_UNLCK as libc::c_short;
fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("read unlock failed");
assert_eq!(None, lock_info(inode));
}

fn lock_info(inode: usize) -> Option<(String, String)> {
let file = File::open("/proc/locks").expect("open /proc/locks failed");
let buf = BufReader::new(file);

for line in buf.lines() {
let line = line.unwrap();
let parts: Vec<_> = line.split_whitespace().collect();
let lock_type = parts[1];
let lock_access = parts[3];
let ino_parts: Vec<_> = parts[5].split(':').collect();
let ino: usize = ino_parts[2].parse().unwrap();
if ino == inode {
return Some((lock_type.to_string(), lock_access.to_string()));
}
}
None
}
}

#[cfg(any(target_os = "linux",
Expand Down