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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
171 changes: 123 additions & 48 deletions test/test_fcntl.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
use nix::Error;
use nix::errno::*;
use nix::fcntl::{openat, open, OFlag, readlink, readlinkat, renameat};
use nix::fcntl::{open, openat, readlink, readlinkat, renameat, OFlag};
use nix::sys::stat::Mode;
use nix::unistd::{close, read};
use tempfile::{self, NamedTempFile};
use nix::Error;
use std::fs::File;
use std::io::prelude::*;
use std::os::unix::fs;
use tempfile::{self, NamedTempFile};

#[test]
fn test_openat() {
const CONTENTS: &[u8] = b"abcd";
let mut tmp = NamedTempFile::new().unwrap();
tmp.write_all(CONTENTS).unwrap();

let dirfd = open(tmp.path().parent().unwrap(),
OFlag::empty(),
Mode::empty()).unwrap();
let fd = openat(dirfd,
tmp.path().file_name().unwrap(),
OFlag::O_RDONLY,
Mode::empty()).unwrap();
let dirfd = open(tmp.path().parent().unwrap(), OFlag::empty(), Mode::empty()).unwrap();
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 you made some formatting changes unrelated to this PR. Could you please back those out?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, forgot to disable rustfmt while editing. It's fixed.

let fd = openat(
dirfd,
tmp.path().file_name().unwrap(),
OFlag::O_RDONLY,
Mode::empty(),
)
.unwrap();

let mut buf = [0u8; 1024];
assert_eq!(4, read(fd, &mut buf).unwrap());
Expand All @@ -39,8 +40,10 @@ fn test_renameat() {
let new_dir = tempfile::tempdir().unwrap();
let new_dirfd = open(new_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
renameat(Some(old_dirfd), "old", Some(new_dirfd), "new").unwrap();
assert_eq!(renameat(Some(old_dirfd), "old", Some(new_dirfd), "new").unwrap_err(),
Error::Sys(Errno::ENOENT));
assert_eq!(
renameat(Some(old_dirfd), "old", Some(new_dirfd), "new").unwrap_err(),
Error::Sys(Errno::ENOENT)
);
close(old_dirfd).unwrap();
close(new_dirfd).unwrap();
assert!(new_dir.path().join("new").exists());
Expand All @@ -53,25 +56,27 @@ fn test_readlink() {
let dst = tempdir.path().join("b");
println!("a: {:?}, b: {:?}", &src, &dst);
fs::symlink(&src.as_path(), &dst.as_path()).unwrap();
let dirfd = open(tempdir.path(),
OFlag::empty(),
Mode::empty()).unwrap();
let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap();
let expected_dir = src.to_str().unwrap();

assert_eq!(readlink(&dst).unwrap().to_str().unwrap(), expected_dir);
assert_eq!(readlinkat(dirfd, "b").unwrap().to_str().unwrap(), expected_dir);

assert_eq!(
readlinkat(dirfd, "b").unwrap().to_str().unwrap(),
expected_dir
);
}

#[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 @@ -123,8 +128,15 @@ mod linux_android {

let (rd, wr) = pipe().unwrap();
let mut offset: loff_t = 5;
let res = splice(tmp.as_raw_fd(), Some(&mut offset),
wr, None, 2, SpliceFFlags::empty()).unwrap();
let res = splice(
tmp.as_raw_fd(),
Some(&mut offset),
wr,
None,
2,
SpliceFFlags::empty(),
)
.unwrap();

assert_eq!(2, res);

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

#[test]
#[cfg(not(any(target_arch = "aarch64",
target_arch = "arm",
target_arch = "armv7",
target_arch = "i686",
Copy link
Member

Choose a reason for hiding this comment

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

Try "x86" instead of "i686". Also, please explain in a comment why the test is disabled on certain architectures.

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 added a comment and switched to 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",
target_os = "android",
target_os = "emscripten",
target_os = "fuchsia",
any(target_os = "wasi", target_env = "wasi"),
target_env = "uclibc",
target_env = "freebsd"))]
#[cfg(any(
target_os = "linux",
target_os = "android",
target_os = "emscripten",
target_os = "fuchsia",
any(target_os = "wasi", target_env = "wasi"),
target_env = "uclibc",
target_env = "freebsd"
))]
mod test_posix_fadvise {

use tempfile::NamedTempFile;
use std::os::unix::io::{RawFd, AsRawFd};
use nix::errno::Errno;
use nix::fcntl::*;
use nix::unistd::pipe;
use std::os::unix::io::{AsRawFd, RawFd};
use tempfile::NamedTempFile;

#[test]
fn test_success() {
Expand All @@ -226,25 +303,30 @@ mod test_posix_fadvise {
#[test]
fn test_errno() {
let (rd, _wr) = pipe().unwrap();
let errno = posix_fadvise(rd as RawFd, 0, 100, PosixFadviseAdvice::POSIX_FADV_WILLNEED)
.unwrap();
let errno =
posix_fadvise(rd as RawFd, 0, 100, PosixFadviseAdvice::POSIX_FADV_WILLNEED).unwrap();
assert_eq!(errno, Errno::ESPIPE as i32);
}
}

#[cfg(any(target_os = "linux",
target_os = "android",
target_os = "emscripten",
target_os = "fuchsia",
any(target_os = "wasi", target_env = "wasi"),
target_os = "freebsd"))]
#[cfg(any(
target_os = "linux",
target_os = "android",
target_os = "emscripten",
target_os = "fuchsia",
any(target_os = "wasi", target_env = "wasi"),
target_os = "freebsd"
))]
mod test_posix_fallocate {

use tempfile::NamedTempFile;
use std::{io::Read, os::unix::io::{RawFd, AsRawFd}};
use nix::errno::Errno;
use nix::fcntl::*;
use nix::unistd::pipe;
use std::{
io::Read,
os::unix::io::{AsRawFd, RawFd},
};
use tempfile::NamedTempFile;

#[test]
fn success() {
Expand Down Expand Up @@ -276,15 +358,8 @@ mod test_posix_fallocate {
let err = posix_fallocate(rd as RawFd, 0, 100).unwrap_err();
use nix::Error::Sys;
match err {
Sys(Errno::EINVAL)
| Sys(Errno::ENODEV)
| Sys(Errno::ESPIPE)
| Sys(Errno::EBADF) => (),
errno =>
panic!(
"unexpected errno {}",
errno,
),
Sys(Errno::EINVAL) | Sys(Errno::ENODEV) | Sys(Errno::ESPIPE) | Sys(Errno::EBADF) => (),
errno => panic!("unexpected errno {}", errno,),
}
}
}