Skip to content

Commit

Permalink
Merge #1058
Browse files Browse the repository at this point in the history
1058: Implement unlinkat r=asomers a=jlb6740

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.

Co-authored-by: Johnnie Birch <[email protected]>
  • Loading branch information
bors[bot] and jlb6740 committed Jul 16, 2019
2 parents 96ae786 + 273fb63 commit 500036a
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 2 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
([#1089](https://github.com/nix-rust/nix/pull/1089))
- Added `AF_VSOCK` to `AddressFamily`.
([#1091](https://github.com/nix-rust/nix/pull/1091))
- Add `unlinkat`
([#1058](https://github.com/nix-rust/nix/pull/1058))

### Changed
- Support for `ifaddrs` now present when building for Android.
Expand Down
1 change: 1 addition & 0 deletions src/fcntl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub use self::posix_fadvise::*;

libc_bitflags!{
pub struct AtFlags: c_int {
AT_REMOVEDIR;
AT_SYMLINK_NOFOLLOW;
#[cfg(any(target_os = "android", target_os = "linux"))]
AT_NO_AUTOMOUNT;
Expand Down
36 changes: 36 additions & 0 deletions src/unistd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1144,6 +1144,42 @@ pub fn unlink<P: ?Sized + NixPath>(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` or the current working directory
/// if `dirfd` is `None`. In the case of an absolute `path` `dirfd` is ignored. If `flag` is
/// `UnlinkatFlags::RemoveDir` then removal of 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<P: ?Sized + NixPath>(
dirfd: Option<RawFd>,
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<P: ?Sized + NixPath>(path: &P) -> Result<()> {
let res = path.with_nix_path(|cstr| {
Expand Down
57 changes: 55 additions & 2 deletions test/test_unistd.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
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};
use nix::sys::wait::*;
use nix::sys::stat::{self, Mode, SFlag};
use nix::errno::Errno;
use nix::Error;
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 tempfile::{self, tempfile};
Expand Down Expand Up @@ -599,6 +600,58 @@ fn test_symlinkat() {
);
}


#[test]
fn test_unlinkat_dir_noremovedir() {
let tempdir = tempfile::tempdir().unwrap();
let dirname = "foo_dir";
let dirpath = tempdir.path().join(dirname);

// 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
let err_result = unlinkat(Some(dirfd), dirname, UnlinkatFlags::NoRemoveDir).unwrap_err();
assert!(err_result == Error::Sys(Errno::EISDIR) || err_result == Error::Sys(Errno::EPERM));
}

#[test]
fn test_unlinkat_dir_removedir() {
let tempdir = tempfile::tempdir().unwrap();
let dirname = "foo_dir";
let dirpath = tempdir.path().join(dirname);

// 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 with proper flag
unlinkat(Some(dirfd), dirname, UnlinkatFlags::RemoveDir).unwrap();
assert!(!dirpath.exists());
}

#[test]
fn test_unlinkat_file() {
let tempdir = tempfile::tempdir().unwrap();
let filename = "foo.txt";
let filepath = tempdir.path().join(filename);

// Create file
File::create(&filepath).unwrap();

// Get file descriptor for base directory
let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();

// 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();
Expand Down

0 comments on commit 500036a

Please sign in to comment.