summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md2
-rw-r--r--src/fcntl.rs1
-rw-r--r--src/unistd.rs52
-rw-r--r--test/test_unistd.rs131
4 files changed, 186 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b839a5d3..00a72fdb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -23,6 +23,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Added `User::from_uid`, `User::from_name`, `User::from_gid` and
`Group::from_name`,
([#1139](https://github.com/nix-rust/nix/pull/1139))
+- Added `linkat`
+ ([#1101](https://github.com/nix-rust/nix/pull/1101))
### Changed
- `sys::socket::recvfrom` now returns
diff --git a/src/fcntl.rs b/src/fcntl.rs
index 7d745b06..1d66eb75 100644
--- a/src/fcntl.rs
+++ b/src/fcntl.rs
@@ -24,6 +24,7 @@ pub use self::posix_fadvise::*;
libc_bitflags!{
pub struct AtFlags: c_int {
AT_REMOVEDIR;
+ AT_SYMLINK_FOLLOW;
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 b38e463f..b7fe567b 100644
--- a/src/unistd.rs
+++ b/src/unistd.rs
@@ -1169,6 +1169,58 @@ pub fn isatty(fd: RawFd) -> Result<bool> {
}
}
+/// Flags for `linkat` function.
+#[derive(Clone, Copy, Debug)]
+pub enum LinkatFlags {
+ SymlinkFollow,
+ NoSymlinkFollow,
+}
+
+/// Link one file to another file
+///
+/// Creates a new link (directory entry) at `newpath` for the existing file at `oldpath`. In the
+/// case of a relative `oldpath`, the path is interpreted relative to the directory associated
+/// with file descriptor `olddirfd` instead of the current working directory and similiarly for
+/// `newpath` and file descriptor `newdirfd`. In case `flag` is LinkatFlags::SymlinkFollow and
+/// `oldpath` names a symoblic link, a new link for the target of the symbolic link is created.
+/// If either `olddirfd` or `newdirfd` is `None`, `AT_FDCWD` is used respectively where `oldpath`
+/// and/or `newpath` is then interpreted relative to the current working directory of the calling
+/// process. If either `oldpath` or `newpath` is absolute, then `dirfd` is ignored.
+///
+/// # References
+/// See also [linkat(2)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/linkat.html)
+pub fn linkat<P: ?Sized + NixPath>(
+ olddirfd: Option<RawFd>,
+ oldpath: &P,
+ newdirfd: Option<RawFd>,
+ newpath: &P,
+ flag: LinkatFlags,
+) -> Result<()> {
+
+ let atflag =
+ match flag {
+ LinkatFlags::SymlinkFollow => AtFlags::AT_SYMLINK_FOLLOW,
+ LinkatFlags::NoSymlinkFollow => AtFlags::empty(),
+ };
+
+ let res =
+ oldpath.with_nix_path(|oldcstr| {
+ newpath.with_nix_path(|newcstr| {
+ unsafe {
+ libc::linkat(
+ at_rawfd(olddirfd),
+ oldcstr.as_ptr(),
+ at_rawfd(newdirfd),
+ newcstr.as_ptr(),
+ atflag.bits() as libc::c_int
+ )
+ }
+ })
+ })??;
+ Errno::result(res).map(drop)
+}
+
+
/// Remove a directory entry
///
/// See also [unlink(2)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/unlink.html)
diff --git a/test/test_unistd.rs b/test/test_unistd.rs
index 94975049..e0b3ae7e 100644
--- a/test/test_unistd.rs
+++ b/test/test_unistd.rs
@@ -660,6 +660,137 @@ fn test_symlinkat() {
);
}
+#[test]
+fn test_linkat_file() {
+ let tempdir = tempfile::tempdir().unwrap();
+ let oldfilename = "foo.txt";
+ let oldfilepath = tempdir.path().join(oldfilename);
+
+ let newfilename = "bar.txt";
+ let newfilepath = tempdir.path().join(newfilename);
+
+ // Create file
+ File::create(&oldfilepath).unwrap();
+
+ // Get file descriptor for base directory
+ let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
+
+ // Attempt hard link file at relative path
+ linkat(Some(dirfd), oldfilename, Some(dirfd), newfilename, LinkatFlags::SymlinkFollow).unwrap();
+ assert!(newfilepath.exists());
+}
+
+#[test]
+fn test_linkat_olddirfd_none() {
+ let tempdir_oldfile = tempfile::tempdir().unwrap();
+ let oldfilename = "foo.txt";
+ let oldfilepath = tempdir_oldfile.path().join(oldfilename);
+
+ let tempdir_newfile = tempfile::tempdir().unwrap();
+ let newfilename = "bar.txt";
+ let newfilepath = tempdir_newfile.path().join(newfilename);
+
+ // Create file
+ File::create(&oldfilepath).unwrap();
+
+ // Get file descriptor for base directory of new file
+ let dirfd = fcntl::open(tempdir_newfile.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
+
+ // Attempt hard link file using curent working directory as relative path for old file path
+ chdir(tempdir_oldfile.path()).unwrap();
+ linkat(None, oldfilename, Some(dirfd), newfilename, LinkatFlags::SymlinkFollow).unwrap();
+ assert!(newfilepath.exists());
+}
+
+#[test]
+fn test_linkat_newdirfd_none() {
+ let tempdir_oldfile = tempfile::tempdir().unwrap();
+ let oldfilename = "foo.txt";
+ let oldfilepath = tempdir_oldfile.path().join(oldfilename);
+
+ let tempdir_newfile = tempfile::tempdir().unwrap();
+ let newfilename = "bar.txt";
+ let newfilepath = tempdir_newfile.path().join(newfilename);
+
+ // Create file
+ File::create(&oldfilepath).unwrap();
+
+ // Get file descriptor for base directory of old file
+ let dirfd = fcntl::open(tempdir_oldfile.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
+
+ // Attempt hard link file using current working directory as relative path for new file path
+ chdir(tempdir_newfile.path()).unwrap();
+ linkat(Some(dirfd), oldfilename, None, newfilename, LinkatFlags::SymlinkFollow).unwrap();
+ assert!(newfilepath.exists());
+}
+
+#[test]
+#[cfg(not(any(target_os = "ios", target_os = "macos")))]
+fn test_linkat_no_follow_symlink() {
+ let tempdir = tempfile::tempdir().unwrap();
+ let oldfilename = "foo.txt";
+ let oldfilepath = tempdir.path().join(oldfilename);
+
+ let symoldfilename = "symfoo.txt";
+ let symoldfilepath = tempdir.path().join(symoldfilename);
+
+ let newfilename = "nofollowsymbar.txt";
+ let newfilepath = tempdir.path().join(newfilename);
+
+ // Create file
+ File::create(&oldfilepath).unwrap();
+
+ // Create symlink to file
+ symlinkat(&oldfilepath, None, &symoldfilepath).unwrap();
+
+ // Get file descriptor for base directory
+ let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
+
+ // Attempt link symlink of file at relative path
+ linkat(Some(dirfd), symoldfilename, Some(dirfd), newfilename, LinkatFlags::NoSymlinkFollow).unwrap();
+
+ // Assert newfile is actually a symlink to oldfile.
+ assert_eq!(
+ readlink(&newfilepath)
+ .unwrap()
+ .to_str()
+ .unwrap(),
+ oldfilepath.to_str().unwrap()
+ );
+}
+
+#[test]
+fn test_linkat_follow_symlink() {
+ let tempdir = tempfile::tempdir().unwrap();
+ let oldfilename = "foo.txt";
+ let oldfilepath = tempdir.path().join(oldfilename);
+
+ let symoldfilename = "symfoo.txt";
+ let symoldfilepath = tempdir.path().join(symoldfilename);
+
+ let newfilename = "nofollowsymbar.txt";
+ let newfilepath = tempdir.path().join(newfilename);
+
+ // Create file
+ File::create(&oldfilepath).unwrap();
+
+ // Create symlink to file
+ symlinkat(&oldfilepath, None, &symoldfilepath).unwrap();
+
+ // Get file descriptor for base directory
+ let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
+
+ // Attempt link target of symlink of file at relative path
+ linkat(Some(dirfd), symoldfilename, Some(dirfd), newfilename, LinkatFlags::SymlinkFollow).unwrap();
+
+ let newfilestat = stat::stat(&newfilepath).unwrap();
+
+ // Check the file type of the new link
+ assert!((stat::SFlag::from_bits_truncate(newfilestat.st_mode) & SFlag::S_IFMT) == SFlag::S_IFREG);
+
+ // Check the number of hard links to the original file
+ assert_eq!(newfilestat.st_nlink, 2);
+}
#[test]
fn test_unlinkat_dir_noremovedir() {