From f3dab48a9221ede9f5409662eacca4b53de34171 Mon Sep 17 00:00:00 2001 From: Julio Merino Date: Wed, 17 Oct 2018 06:35:45 -0400 Subject: [PATCH 1/2] Add a fchownat(2) wrapper --- CHANGELOG.md | 2 ++ src/fcntl.rs | 9 +++++++ src/sys/stat.rs | 16 +++--------- src/unistd.rs | 69 ++++++++++++++++++++++++++++++++++++++++++++----- 4 files changed, 76 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e16278a2c..3b1543f4f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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/ diff --git a/src/fcntl.rs b/src/fcntl.rs index 58de3b3119..5942506be4 100644 --- a/src/fcntl.rs +++ b/src/fcntl.rs @@ -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; @@ -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) -> 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. diff --git a/src/sys/stat.rs b/src/sys/stat.rs index d0f0f8d9d8..a0b3aafcb9 100644 --- a/src/sys/stat.rs +++ b/src/sys/stat.rs @@ -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}; @@ -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) -> raw::c_int { - match fd { - None => libc::AT_FDCWD, - Some(fd) => fd, - } -} - /// Flags for `fchmodat` function. #[derive(Clone, Copy, Debug)] pub enum FchmodatFlags { @@ -180,7 +170,7 @@ pub fn fchmodat( }; 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, @@ -260,7 +250,7 @@ pub fn utimensat( 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(), ×[0], atflag.bits() as libc::c_int, diff --git a/src/unistd.rs b/src/unistd.rs index 7088260448..d5192977fd 100644 --- a/src/unistd.rs +++ b/src/unistd.rs @@ -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}; @@ -557,6 +557,16 @@ pub fn getcwd() -> Result { } } +/// Computes the raw UID and GID values to pass to a `*chown` call. +fn chown_raw_ids(owner: Option, group: Option) -> (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)). @@ -567,17 +577,62 @@ pub fn getcwd() -> Result { #[inline] pub fn chown(path: &P, owner: Option, group: Option) -> 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( + dirfd: Option, + path: &P, + owner: Option, + group: Option, + 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()); From 9bf4ab32764be9b05fd74d67b21c6433bed7e488 Mon Sep 17 00:00:00 2001 From: Julio Merino Date: Wed, 17 Oct 2018 12:03:08 -0400 Subject: [PATCH 2/2] Add trivial tests for chown and fchownat These are mostly to ensure that all the platforms we care about in our CI can reference these primitives. --- test/test_unistd.rs | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/test/test_unistd.rs b/test/test_unistd.rs index 981ab53deb..e5f9d448dd 100644 --- a/test/test_unistd.rs +++ b/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}; @@ -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";