summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md2
-rw-r--r--src/fcntl.rs1
-rw-r--r--src/unistd.rs36
-rw-r--r--test/test_unistd.rs57
4 files changed, 94 insertions, 2 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cf30fd98..dcb2c599 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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.
diff --git a/src/fcntl.rs b/src/fcntl.rs
index 2201873a..d99c2c1a 100644
--- a/src/fcntl.rs
+++ b/src/fcntl.rs
@@ -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;
diff --git a/src/unistd.rs b/src/unistd.rs
index 96d8ace7..f422f091 100644
--- a/src/unistd.rs
+++ b/src/unistd.rs
@@ -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| {
diff --git a/test/test_unistd.rs b/test/test_unistd.rs
index b2061458..46196dec 100644
--- a/test/test_unistd.rs
+++ b/test/test_unistd.rs
@@ -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};
@@ -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();