Skip to content

Commit 500036a

Browse files
bors[bot]jlb6740
andcommitted
Merge #1058
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]>
2 parents 96ae786 + 273fb63 commit 500036a

File tree

4 files changed

+94
-2
lines changed

4 files changed

+94
-2
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
1717
([#1089](https://github.com/nix-rust/nix/pull/1089))
1818
- Added `AF_VSOCK` to `AddressFamily`.
1919
([#1091](https://github.com/nix-rust/nix/pull/1091))
20+
- Add `unlinkat`
21+
([#1058](https://github.com/nix-rust/nix/pull/1058))
2022

2123
### Changed
2224
- Support for `ifaddrs` now present when building for Android.

src/fcntl.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ pub use self::posix_fadvise::*;
2323

2424
libc_bitflags!{
2525
pub struct AtFlags: c_int {
26+
AT_REMOVEDIR;
2627
AT_SYMLINK_NOFOLLOW;
2728
#[cfg(any(target_os = "android", target_os = "linux"))]
2829
AT_NO_AUTOMOUNT;

src/unistd.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1144,6 +1144,42 @@ pub fn unlink<P: ?Sized + NixPath>(path: &P) -> Result<()> {
11441144
Errno::result(res).map(drop)
11451145
}
11461146

1147+
/// Flags for `unlinkat` function.
1148+
#[derive(Clone, Copy, Debug)]
1149+
pub enum UnlinkatFlags {
1150+
RemoveDir,
1151+
NoRemoveDir,
1152+
}
1153+
1154+
/// Remove a directory entry
1155+
///
1156+
/// In the case of a relative path, the directory entry to be removed is determined relative to
1157+
/// the directory associated with the file descriptor `dirfd` or the current working directory
1158+
/// if `dirfd` is `None`. In the case of an absolute `path` `dirfd` is ignored. If `flag` is
1159+
/// `UnlinkatFlags::RemoveDir` then removal of the directory entry specified by `dirfd` and `path`
1160+
/// is performed.
1161+
///
1162+
/// # References
1163+
/// See also [unlinkat(2)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/unlinkat.html)
1164+
pub fn unlinkat<P: ?Sized + NixPath>(
1165+
dirfd: Option<RawFd>,
1166+
path: &P,
1167+
flag: UnlinkatFlags,
1168+
) -> Result<()> {
1169+
let atflag =
1170+
match flag {
1171+
UnlinkatFlags::RemoveDir => AtFlags::AT_REMOVEDIR,
1172+
UnlinkatFlags::NoRemoveDir => AtFlags::empty(),
1173+
};
1174+
let res = path.with_nix_path(|cstr| {
1175+
unsafe {
1176+
libc::unlinkat(at_rawfd(dirfd), cstr.as_ptr(), atflag.bits() as libc::c_int)
1177+
}
1178+
})?;
1179+
Errno::result(res).map(drop)
1180+
}
1181+
1182+
11471183
#[inline]
11481184
pub fn chroot<P: ?Sized + NixPath>(path: &P) -> Result<()> {
11491185
let res = path.with_nix_path(|cstr| {

test/test_unistd.rs

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
use nix::fcntl::{fcntl, FcntlArg, FdFlag, open, OFlag, readlink};
1+
use nix::fcntl::{self, fcntl, FcntlArg, FdFlag, open, OFlag, readlink};
22
use nix::unistd::*;
33
use nix::unistd::ForkResult::*;
44
use nix::sys::signal::{SaFlags, SigAction, SigHandler, SigSet, Signal, sigaction};
55
use nix::sys::wait::*;
66
use nix::sys::stat::{self, Mode, SFlag};
77
use nix::errno::Errno;
8+
use nix::Error;
89
use std::{env, iter};
910
use std::ffi::CString;
10-
use std::fs::{self, File};
11+
use std::fs::{self, DirBuilder, File};
1112
use std::io::Write;
1213
use std::os::unix::prelude::*;
1314
use tempfile::{self, tempfile};
@@ -599,6 +600,58 @@ fn test_symlinkat() {
599600
);
600601
}
601602

603+
604+
#[test]
605+
fn test_unlinkat_dir_noremovedir() {
606+
let tempdir = tempfile::tempdir().unwrap();
607+
let dirname = "foo_dir";
608+
let dirpath = tempdir.path().join(dirname);
609+
610+
// Create dir
611+
DirBuilder::new().recursive(true).create(&dirpath).unwrap();
612+
613+
// Get file descriptor for base directory
614+
let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
615+
616+
// Attempt unlink dir at relative path without proper flag
617+
let err_result = unlinkat(Some(dirfd), dirname, UnlinkatFlags::NoRemoveDir).unwrap_err();
618+
assert!(err_result == Error::Sys(Errno::EISDIR) || err_result == Error::Sys(Errno::EPERM));
619+
}
620+
621+
#[test]
622+
fn test_unlinkat_dir_removedir() {
623+
let tempdir = tempfile::tempdir().unwrap();
624+
let dirname = "foo_dir";
625+
let dirpath = tempdir.path().join(dirname);
626+
627+
// Create dir
628+
DirBuilder::new().recursive(true).create(&dirpath).unwrap();
629+
630+
// Get file descriptor for base directory
631+
let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
632+
633+
// Attempt unlink dir at relative path with proper flag
634+
unlinkat(Some(dirfd), dirname, UnlinkatFlags::RemoveDir).unwrap();
635+
assert!(!dirpath.exists());
636+
}
637+
638+
#[test]
639+
fn test_unlinkat_file() {
640+
let tempdir = tempfile::tempdir().unwrap();
641+
let filename = "foo.txt";
642+
let filepath = tempdir.path().join(filename);
643+
644+
// Create file
645+
File::create(&filepath).unwrap();
646+
647+
// Get file descriptor for base directory
648+
let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
649+
650+
// Attempt unlink file at relative path
651+
unlinkat(Some(dirfd), filename, UnlinkatFlags::NoRemoveDir).unwrap();
652+
assert!(!filepath.exists());
653+
}
654+
602655
#[test]
603656
fn test_access_not_existing() {
604657
let tempdir = tempfile::tempdir().unwrap();

0 commit comments

Comments
 (0)