Skip to content

Commit

Permalink
Merge #1101
Browse files Browse the repository at this point in the history
1101: Implment linkat r=asomers a=jlb6740

This adds the linkat function which is part of POSIX:
http://pubs.opengroup.org/onlinepubs/9699919799/functions/linkat.html
and widely implmented on Unix-Family platforms.

Co-authored-by: Johnnie Birch <45402135+jlb6740@users.noreply.github.com>
  • Loading branch information
bors[bot] and jlb6740 authored Nov 16, 2019
2 parents 30b04c5 + 848825b commit 5cb526d
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/fcntl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
52 changes: 52 additions & 0 deletions src/unistd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
131 changes: 131 additions & 0 deletions test/test_unistd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down

0 comments on commit 5cb526d

Please sign in to comment.