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

Add a fchownat(2) wrapper #955

Merged
merged 2 commits into from Oct 20, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -23,6 +23,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
([#954](https://github.com/nix-rust/nix/pull/954))
- Added a `truncate` wrapper.
([#956](https://github.com/nix-rust/nix/pull/956))
- Added a `fchownat` wrapper.
([#955](https://github.com/nix-rust/nix/pull/955))

### Changed
- Increased required Rust version to 1.22.1/
Expand Down
9 changes: 9 additions & 0 deletions src/fcntl.rs
Expand Up @@ -2,6 +2,7 @@ use {Error, Result, NixPath};
use errno::Errno;
use libc::{self, c_int, c_uint, c_char, size_t, ssize_t};
use sys::stat::Mode;
use std::os::raw;
use std::os::unix::io::RawFd;
use std::ffi::OsStr;
use std::os::unix::ffi::OsStrExt;
Expand Down Expand Up @@ -182,6 +183,14 @@ pub fn readlinkat<'a, P: ?Sized + NixPath>(dirfd: RawFd, path: &P, buffer: &'a m
wrap_readlink_result(buffer, res)
}

/// Computes the raw fd consumed by a function of the form `*at`.
pub(crate) fn at_rawfd(fd: Option<RawFd>) -> raw::c_int {
match fd {
None => libc::AT_FDCWD,
Some(fd) => fd,
}
}

#[cfg(any(target_os = "android", target_os = "linux"))]
libc_bitflags!(
/// Additional flags for file sealing, which allows for limiting operations on a file.
Expand Down
16 changes: 3 additions & 13 deletions src/sys/stat.rs
Expand Up @@ -3,10 +3,9 @@ pub use libc::stat as FileStat;

use {Result, NixPath};
use errno::Errno;
use fcntl::AtFlags;
use fcntl::{AtFlags, at_rawfd};
use libc;
use std::mem;
use std::os::raw;
use std::os::unix::io::RawFd;
use sys::time::{TimeSpec, TimeVal};

Expand Down Expand Up @@ -135,15 +134,6 @@ pub fn fchmod(fd: RawFd, mode: Mode) -> Result<()> {
Errno::result(res).map(|_| ())
}

/// Computes the raw fd consumed by a function of the form `*at`.
#[inline]
fn actual_atfd(fd: Option<RawFd>) -> raw::c_int {
match fd {
None => libc::AT_FDCWD,
Some(fd) => fd,
}
}

/// Flags for `fchmodat` function.
#[derive(Clone, Copy, Debug)]
pub enum FchmodatFlags {
Expand Down Expand Up @@ -180,7 +170,7 @@ pub fn fchmodat<P: ?Sized + NixPath>(
};
let res = path.with_nix_path(|cstr| unsafe {
libc::fchmodat(
actual_atfd(dirfd),
at_rawfd(dirfd),
cstr.as_ptr(),
mode.bits() as mode_t,
atflag.bits() as libc::c_int,
Expand Down Expand Up @@ -260,7 +250,7 @@ pub fn utimensat<P: ?Sized + NixPath>(
let times: [libc::timespec; 2] = [*atime.as_ref(), *mtime.as_ref()];
let res = path.with_nix_path(|cstr| unsafe {
libc::utimensat(
actual_atfd(dirfd),
at_rawfd(dirfd),
cstr.as_ptr(),
&times[0],
atflag.bits() as libc::c_int,
Expand Down
69 changes: 62 additions & 7 deletions src/unistd.rs
Expand Up @@ -2,7 +2,7 @@

use errno::{self, Errno};
use {Error, Result, NixPath};
use fcntl::{fcntl, FdFlag, OFlag};
use fcntl::{AtFlags, at_rawfd, fcntl, FdFlag, OFlag};
use fcntl::FcntlArg::F_SETFD;
use libc::{self, c_char, c_void, c_int, c_long, c_uint, size_t, pid_t, off_t,
uid_t, gid_t, mode_t};
Expand Down Expand Up @@ -557,6 +557,16 @@ pub fn getcwd() -> Result<PathBuf> {
}
}

/// Computes the raw UID and GID values to pass to a `*chown` call.
fn chown_raw_ids(owner: Option<Uid>, group: Option<Gid>) -> (libc::uid_t, libc::gid_t) {
// According to the POSIX specification, -1 is used to indicate that owner and group
// are not to be changed. Since uid_t and gid_t are unsigned types, we have to wrap
// around to get -1.
let uid = owner.map(Into::into).unwrap_or((0 as uid_t).wrapping_sub(1));
let gid = group.map(Into::into).unwrap_or((0 as gid_t).wrapping_sub(1));
(uid, gid)
}

/// Change the ownership of the file at `path` to be owned by the specified
/// `owner` (user) and `group` (see
/// [chown(2)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/chown.html)).
Expand All @@ -567,17 +577,62 @@ pub fn getcwd() -> Result<PathBuf> {
#[inline]
pub fn chown<P: ?Sized + NixPath>(path: &P, owner: Option<Uid>, group: Option<Gid>) -> Result<()> {
let res = try!(path.with_nix_path(|cstr| {
// According to the POSIX specification, -1 is used to indicate that
// owner and group, respectively, are not to be changed. Since uid_t and
// gid_t are unsigned types, we use wrapping_sub to get '-1'.
unsafe { libc::chown(cstr.as_ptr(),
owner.map(Into::into).unwrap_or((0 as uid_t).wrapping_sub(1)),
group.map(Into::into).unwrap_or((0 as gid_t).wrapping_sub(1))) }
let (uid, gid) = chown_raw_ids(owner, group);
unsafe { libc::chown(cstr.as_ptr(), uid, gid) }
}));

Errno::result(res).map(drop)
}

/// Flags for `fchownat` function.
#[derive(Clone, Copy, Debug)]
pub enum FchownatFlags {
FollowSymlink,
NoFollowSymlink,
}

/// Change the ownership of the file at `path` to be owned by the specified
/// `owner` (user) and `group`.
///
/// The owner/group for the provided path name will not be modified if `None` is
/// provided for that argument. Ownership change will be attempted for the path
/// only if `Some` owner/group is provided.
///
/// The file to be changed is determined relative to the directory associated
/// with the file descriptor `dirfd` or the current working directory
/// if `dirfd` is `None`.
///
/// If `flag` is `FchownatFlags::NoFollowSymlink` and `path` names a symbolic link,
/// then the mode of the symbolic link is changed.
///
/// `fchownat(None, path, mode, FchownatFlags::NoFollowSymlink)` is identical to
/// a call `libc::lchown(path, mode)`. That's why `lchmod` is unimplemented in
/// the `nix` crate.
///
/// # References
///
/// [fchownat(2)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/fchownat.html).
pub fn fchownat<P: ?Sized + NixPath>(
dirfd: Option<RawFd>,
path: &P,
owner: Option<Uid>,
group: Option<Gid>,
flag: FchownatFlags,
) -> Result<()> {
let atflag =
match flag {
FchownatFlags::FollowSymlink => AtFlags::empty(),
FchownatFlags::NoFollowSymlink => AtFlags::AT_SYMLINK_NOFOLLOW,
};
let res = path.with_nix_path(|cstr| unsafe {
let (uid, gid) = chown_raw_ids(owner, group);
libc::fchownat(at_rawfd(dirfd), cstr.as_ptr(), uid, gid,
atflag.bits() as libc::c_int)
})?;

Errno::result(res).map(|_| ())
}

fn to_exec_array(args: &[CString]) -> Vec<*const c_char> {
let mut args_p: Vec<*const c_char> = args.iter().map(|s| s.as_ptr()).collect();
args_p.push(ptr::null());
Expand Down
45 changes: 44 additions & 1 deletion test/test_unistd.rs
@@ -1,4 +1,4 @@
use nix::fcntl::{fcntl, FcntlArg, FdFlag, OFlag};
use nix::fcntl::{fcntl, FcntlArg, FdFlag, open, OFlag};
use nix::unistd::*;
use nix::unistd::ForkResult::*;
use nix::sys::signal::{SaFlags, SigAction, SigHandler, SigSet, Signal, sigaction};
Expand Down Expand Up @@ -301,6 +301,49 @@ fn test_getcwd() {
assert_eq!(getcwd().unwrap(), inner_tmp_dir.as_path());
}

#[test]
fn test_chown() {
// Testing for anything other than our own UID/GID is hard.
let uid = Some(getuid());
let gid = Some(getgid());

let tempdir = tempfile::tempdir().unwrap();
let path = tempdir.path().join("file");
{
File::create(&path).unwrap();
}

chown(&path, uid, gid).unwrap();
chown(&path, uid, None).unwrap();
chown(&path, None, gid).unwrap();

fs::remove_file(&path).unwrap();
chown(&path, uid, gid).unwrap_err();
}

#[test]
fn test_fchownat() {
// Testing for anything other than our own UID/GID is hard.
let uid = Some(getuid());
let gid = Some(getgid());

let tempdir = tempfile::tempdir().unwrap();
let path = tempdir.path().join("file");
{
File::create(&path).unwrap();
}

let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap();

fchownat(Some(dirfd), "file", uid, gid, FchownatFlags::FollowSymlink).unwrap();

chdir(tempdir.path()).unwrap();
fchownat(None, "file", uid, gid, FchownatFlags::FollowSymlink).unwrap();

fs::remove_file(&path).unwrap();
fchownat(None, "file", uid, gid, FchownatFlags::FollowSymlink).unwrap_err();
}

#[test]
fn test_lseek() {
const CONTENTS: &[u8] = b"abcdef123456";
Expand Down