From 890de051f778cd4279769abe5da2bb66bd0494da Mon Sep 17 00:00:00 2001 From: Johnnie Birch <45402135+jlb6740@users.noreply.github.com> Date: Wed, 15 May 2019 22:18:21 -0700 Subject: [PATCH] Implement unlinkat This adds the unlinkat function, which is part of POSIX: http://pubs.opengroup.org/onlinepubs/9699919799/functions/unlinkat.html and widely implmented on Unix-family platforms. --- CHANGELOG.md | 2 ++ src/fcntl.rs | 1 + src/unistd.rs | 34 ++++++++++++++++++++++++++++++++++ test/test_unistd.rs | 38 ++++++++++++++++++++++++++++++++++++-- 4 files changed, 73 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa741580f8..6d3dc9c37b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). ([#1045](https://github.com/nix-rust/nix/pull/1045)) - Add `forkpty` ([#1042](https://github.com/nix-rust/nix/pull/1042)) +- Add `unlinkat` + ([#TBD](https://github.com/nix-rust/nix/pull/TBD)) ### Changed - `PollFd` event flags renamed to `PollFlags` ([#1024](https://github.com/nix-rust/nix/pull/1024/)) diff --git a/src/fcntl.rs b/src/fcntl.rs index a763c10f9a..7acd099332 100644 --- a/src/fcntl.rs +++ b/src/fcntl.rs @@ -12,6 +12,7 @@ use sys::uio::IoVec; // For vmsplice libc_bitflags!{ pub struct AtFlags: c_int { + AT_REMOVEDIR; AT_SYMLINK_NOFOLLOW; #[cfg(any(target_os = "android", target_os = "linux"))] AT_NO_AUTOMOUNT; diff --git a/src/unistd.rs b/src/unistd.rs index 96d8ace78c..908e9b94bb 100644 --- a/src/unistd.rs +++ b/src/unistd.rs @@ -1144,6 +1144,40 @@ pub fn unlink(path: &P) -> Result<()> { Errno::result(res).map(drop) } +/// Flags for `unlinkat` function. +#[derive(Clone, Copy, Debug)] +pub enum UnlinkatFlags { + RemoveDir, + NoRemoveDir, +} + +/// Remove a directory entry +/// +/// In the case of a relative path, the directory entry to be removed is determined relative to +/// the directory associated with the file descriptor dirfd. If flag is UnlinkatFlags::RemoveDir +/// then remove the directory entry specified by dirfd and path is performed. +/// +/// # References +/// See also [unlinkat(2)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/unlinkat.html) +pub fn unlinkat( + dirfd: Option, + path: &P, + flag: UnlinkatFlags, +) -> Result<()> { + let atflag = + match flag { + UnlinkatFlags::RemoveDir => AtFlags::AT_REMOVEDIR, + UnlinkatFlags::NoRemoveDir => AtFlags::empty(), + }; + let res = path.with_nix_path(|cstr| { + unsafe { + libc::unlinkat(at_rawfd(dirfd), cstr.as_ptr(), atflag.bits() as libc::c_int) + } + })?; + Errno::result(res).map(drop) +} + + #[inline] pub fn chroot(path: &P) -> Result<()> { let res = path.with_nix_path(|cstr| { diff --git a/test/test_unistd.rs b/test/test_unistd.rs index 3f13883c7e..39a9b7d403 100644 --- a/test/test_unistd.rs +++ b/test/test_unistd.rs @@ -1,4 +1,4 @@ -use nix::fcntl::{fcntl, FcntlArg, FdFlag, open, OFlag, readlink}; +use nix::fcntl::{self, fcntl, FcntlArg, FdFlag, open, OFlag, readlink}; use nix::unistd::*; use nix::unistd::ForkResult::*; use nix::sys::signal::{SaFlags, SigAction, SigHandler, SigSet, Signal, sigaction}; @@ -7,9 +7,10 @@ use nix::sys::stat::{self, Mode, SFlag}; use nix::errno::Errno; use std::{env, iter}; use std::ffi::CString; -use std::fs::{self, File}; +use std::fs::{self, DirBuilder, File}; use std::io::Write; use std::os::unix::prelude::*; +use std::panic; use tempfile::{self, tempfile}; use libc::{self, _exit, off_t}; @@ -576,6 +577,39 @@ fn test_symlinkat() { ); } + +#[test] +fn test_unlinkat() { + let tempdir = tempfile::tempdir().unwrap(); + let filename = "foo.txt"; + let dirname = "foo_dir"; + let filepath = tempdir.path().join(filename); + let dirpath = tempdir.path().join(dirname); + + // Create file + File::create(&filepath).unwrap(); + + // Create dir + DirBuilder::new().recursive(true).create(&dirpath).unwrap(); + + // Get file descriptor for base directory + let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap(); + + // Attempt unlink dir at relative path without proper flag + panic::set_hook(Box::new(|_info| {})); + let result = std::panic::catch_unwind(|| unlinkat(Some(dirfd), dirname, UnlinkatFlags::NoRemoveDir).unwrap()); + assert!(result.is_err()); + assert!(dirpath.exists()); + + // Attempt unlink dir at relative path with proper flag + unlinkat(Some(dirfd), dirname, UnlinkatFlags::RemoveDir).unwrap(); + assert!(!dirpath.exists()); + + // Attempt unlink file at relative path + unlinkat(Some(dirfd), filename, UnlinkatFlags::NoRemoveDir).unwrap(); + assert!(!filepath.exists()); + } + #[test] fn test_access_not_existing() { let tempdir = tempfile::tempdir().unwrap();