Skip to content

Commit eef3a43

Browse files
bors[bot]jmmv
andcommitted
Merge #955
955: Add a fchownat(2) wrapper r=asomers a=jmmv Co-authored-by: Julio Merino <[email protected]> Co-authored-by: Julio Merino <[email protected]>
2 parents dc6b299 + 9bf4ab3 commit eef3a43

File tree

5 files changed

+120
-21
lines changed

5 files changed

+120
-21
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
2323
([#954](https://github.com/nix-rust/nix/pull/954))
2424
- Added a `truncate` wrapper.
2525
([#956](https://github.com/nix-rust/nix/pull/956))
26+
- Added a `fchownat` wrapper.
27+
([#955](https://github.com/nix-rust/nix/pull/955))
2628

2729
### Changed
2830
- Increased required Rust version to 1.22.1/

src/fcntl.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use {Error, Result, NixPath};
22
use errno::Errno;
33
use libc::{self, c_int, c_uint, c_char, size_t, ssize_t};
44
use sys::stat::Mode;
5+
use std::os::raw;
56
use std::os::unix::io::RawFd;
67
use std::ffi::OsStr;
78
use std::os::unix::ffi::OsStrExt;
@@ -182,6 +183,14 @@ pub fn readlinkat<'a, P: ?Sized + NixPath>(dirfd: RawFd, path: &P, buffer: &'a m
182183
wrap_readlink_result(buffer, res)
183184
}
184185

186+
/// Computes the raw fd consumed by a function of the form `*at`.
187+
pub(crate) fn at_rawfd(fd: Option<RawFd>) -> raw::c_int {
188+
match fd {
189+
None => libc::AT_FDCWD,
190+
Some(fd) => fd,
191+
}
192+
}
193+
185194
#[cfg(any(target_os = "android", target_os = "linux"))]
186195
libc_bitflags!(
187196
/// Additional flags for file sealing, which allows for limiting operations on a file.

src/sys/stat.rs

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@ pub use libc::stat as FileStat;
33

44
use {Result, NixPath};
55
use errno::Errno;
6-
use fcntl::AtFlags;
6+
use fcntl::{AtFlags, at_rawfd};
77
use libc;
88
use std::mem;
9-
use std::os::raw;
109
use std::os::unix::io::RawFd;
1110
use sys::time::{TimeSpec, TimeVal};
1211

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

138-
/// Computes the raw fd consumed by a function of the form `*at`.
139-
#[inline]
140-
fn actual_atfd(fd: Option<RawFd>) -> raw::c_int {
141-
match fd {
142-
None => libc::AT_FDCWD,
143-
Some(fd) => fd,
144-
}
145-
}
146-
147137
/// Flags for `fchmodat` function.
148138
#[derive(Clone, Copy, Debug)]
149139
pub enum FchmodatFlags {
@@ -180,7 +170,7 @@ pub fn fchmodat<P: ?Sized + NixPath>(
180170
};
181171
let res = path.with_nix_path(|cstr| unsafe {
182172
libc::fchmodat(
183-
actual_atfd(dirfd),
173+
at_rawfd(dirfd),
184174
cstr.as_ptr(),
185175
mode.bits() as mode_t,
186176
atflag.bits() as libc::c_int,
@@ -260,7 +250,7 @@ pub fn utimensat<P: ?Sized + NixPath>(
260250
let times: [libc::timespec; 2] = [*atime.as_ref(), *mtime.as_ref()];
261251
let res = path.with_nix_path(|cstr| unsafe {
262252
libc::utimensat(
263-
actual_atfd(dirfd),
253+
at_rawfd(dirfd),
264254
cstr.as_ptr(),
265255
&times[0],
266256
atflag.bits() as libc::c_int,

src/unistd.rs

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use errno::{self, Errno};
44
use {Error, Result, NixPath};
5-
use fcntl::{fcntl, FdFlag, OFlag};
5+
use fcntl::{AtFlags, at_rawfd, fcntl, FdFlag, OFlag};
66
use fcntl::FcntlArg::F_SETFD;
77
use libc::{self, c_char, c_void, c_int, c_long, c_uint, size_t, pid_t, off_t,
88
uid_t, gid_t, mode_t};
@@ -557,6 +557,16 @@ pub fn getcwd() -> Result<PathBuf> {
557557
}
558558
}
559559

560+
/// Computes the raw UID and GID values to pass to a `*chown` call.
561+
fn chown_raw_ids(owner: Option<Uid>, group: Option<Gid>) -> (libc::uid_t, libc::gid_t) {
562+
// According to the POSIX specification, -1 is used to indicate that owner and group
563+
// are not to be changed. Since uid_t and gid_t are unsigned types, we have to wrap
564+
// around to get -1.
565+
let uid = owner.map(Into::into).unwrap_or((0 as uid_t).wrapping_sub(1));
566+
let gid = group.map(Into::into).unwrap_or((0 as gid_t).wrapping_sub(1));
567+
(uid, gid)
568+
}
569+
560570
/// Change the ownership of the file at `path` to be owned by the specified
561571
/// `owner` (user) and `group` (see
562572
/// [chown(2)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/chown.html)).
@@ -567,17 +577,62 @@ pub fn getcwd() -> Result<PathBuf> {
567577
#[inline]
568578
pub fn chown<P: ?Sized + NixPath>(path: &P, owner: Option<Uid>, group: Option<Gid>) -> Result<()> {
569579
let res = try!(path.with_nix_path(|cstr| {
570-
// According to the POSIX specification, -1 is used to indicate that
571-
// owner and group, respectively, are not to be changed. Since uid_t and
572-
// gid_t are unsigned types, we use wrapping_sub to get '-1'.
573-
unsafe { libc::chown(cstr.as_ptr(),
574-
owner.map(Into::into).unwrap_or((0 as uid_t).wrapping_sub(1)),
575-
group.map(Into::into).unwrap_or((0 as gid_t).wrapping_sub(1))) }
580+
let (uid, gid) = chown_raw_ids(owner, group);
581+
unsafe { libc::chown(cstr.as_ptr(), uid, gid) }
576582
}));
577583

578584
Errno::result(res).map(drop)
579585
}
580586

587+
/// Flags for `fchownat` function.
588+
#[derive(Clone, Copy, Debug)]
589+
pub enum FchownatFlags {
590+
FollowSymlink,
591+
NoFollowSymlink,
592+
}
593+
594+
/// Change the ownership of the file at `path` to be owned by the specified
595+
/// `owner` (user) and `group`.
596+
///
597+
/// The owner/group for the provided path name will not be modified if `None` is
598+
/// provided for that argument. Ownership change will be attempted for the path
599+
/// only if `Some` owner/group is provided.
600+
///
601+
/// The file to be changed is determined relative to the directory associated
602+
/// with the file descriptor `dirfd` or the current working directory
603+
/// if `dirfd` is `None`.
604+
///
605+
/// If `flag` is `FchownatFlags::NoFollowSymlink` and `path` names a symbolic link,
606+
/// then the mode of the symbolic link is changed.
607+
///
608+
/// `fchownat(None, path, mode, FchownatFlags::NoFollowSymlink)` is identical to
609+
/// a call `libc::lchown(path, mode)`. That's why `lchmod` is unimplemented in
610+
/// the `nix` crate.
611+
///
612+
/// # References
613+
///
614+
/// [fchownat(2)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/fchownat.html).
615+
pub fn fchownat<P: ?Sized + NixPath>(
616+
dirfd: Option<RawFd>,
617+
path: &P,
618+
owner: Option<Uid>,
619+
group: Option<Gid>,
620+
flag: FchownatFlags,
621+
) -> Result<()> {
622+
let atflag =
623+
match flag {
624+
FchownatFlags::FollowSymlink => AtFlags::empty(),
625+
FchownatFlags::NoFollowSymlink => AtFlags::AT_SYMLINK_NOFOLLOW,
626+
};
627+
let res = path.with_nix_path(|cstr| unsafe {
628+
let (uid, gid) = chown_raw_ids(owner, group);
629+
libc::fchownat(at_rawfd(dirfd), cstr.as_ptr(), uid, gid,
630+
atflag.bits() as libc::c_int)
631+
})?;
632+
633+
Errno::result(res).map(|_| ())
634+
}
635+
581636
fn to_exec_array(args: &[CString]) -> Vec<*const c_char> {
582637
let mut args_p: Vec<*const c_char> = args.iter().map(|s| s.as_ptr()).collect();
583638
args_p.push(ptr::null());

test/test_unistd.rs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use nix::fcntl::{fcntl, FcntlArg, FdFlag, OFlag};
1+
use nix::fcntl::{fcntl, FcntlArg, FdFlag, open, OFlag};
22
use nix::unistd::*;
33
use nix::unistd::ForkResult::*;
44
use nix::sys::signal::{SaFlags, SigAction, SigHandler, SigSet, Signal, sigaction};
@@ -301,6 +301,49 @@ fn test_getcwd() {
301301
assert_eq!(getcwd().unwrap(), inner_tmp_dir.as_path());
302302
}
303303

304+
#[test]
305+
fn test_chown() {
306+
// Testing for anything other than our own UID/GID is hard.
307+
let uid = Some(getuid());
308+
let gid = Some(getgid());
309+
310+
let tempdir = tempfile::tempdir().unwrap();
311+
let path = tempdir.path().join("file");
312+
{
313+
File::create(&path).unwrap();
314+
}
315+
316+
chown(&path, uid, gid).unwrap();
317+
chown(&path, uid, None).unwrap();
318+
chown(&path, None, gid).unwrap();
319+
320+
fs::remove_file(&path).unwrap();
321+
chown(&path, uid, gid).unwrap_err();
322+
}
323+
324+
#[test]
325+
fn test_fchownat() {
326+
// Testing for anything other than our own UID/GID is hard.
327+
let uid = Some(getuid());
328+
let gid = Some(getgid());
329+
330+
let tempdir = tempfile::tempdir().unwrap();
331+
let path = tempdir.path().join("file");
332+
{
333+
File::create(&path).unwrap();
334+
}
335+
336+
let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap();
337+
338+
fchownat(Some(dirfd), "file", uid, gid, FchownatFlags::FollowSymlink).unwrap();
339+
340+
chdir(tempdir.path()).unwrap();
341+
fchownat(None, "file", uid, gid, FchownatFlags::FollowSymlink).unwrap();
342+
343+
fs::remove_file(&path).unwrap();
344+
fchownat(None, "file", uid, gid, FchownatFlags::FollowSymlink).unwrap_err();
345+
}
346+
304347
#[test]
305348
fn test_lseek() {
306349
const CONTENTS: &[u8] = b"abcdef123456";

0 commit comments

Comments
 (0)