Skip to content

Commit

Permalink
Merge #955
Browse files Browse the repository at this point in the history
955: Add a fchownat(2) wrapper r=asomers a=jmmv



Co-authored-by: Julio Merino <julio@meroh.net>
Co-authored-by: Julio Merino <jmmv@google.com>
  • Loading branch information
3 people committed Oct 20, 2018
2 parents dc6b299 + 9bf4ab3 commit eef3a43
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 21 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit eef3a43

Please sign in to comment.