Skip to content

Commit

Permalink
Implment linkat
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jlb6740 committed Jul 31, 2019
1 parent 38de042 commit 6b00bf6
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -21,6 +21,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
([#1058](https://github.com/nix-rust/nix/pull/1058))
- Add `renameat`.
([#1097](https://github.com/nix-rust/nix/pull/1097))
- Add `linkat`
([#1101](https://github.com/nix-rust/nix/pull/1101))

### Changed
- Support for `ifaddrs` now present when building for Android.
Expand Down
1 change: 1 addition & 0 deletions src/fcntl.rs
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
51 changes: 51 additions & 0 deletions src/unistd.rs
Expand Up @@ -1132,6 +1132,57 @@ 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.
///
/// # 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
89 changes: 89 additions & 0 deletions test/test_unistd.rs
Expand Up @@ -601,6 +601,95 @@ 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());
}

#[cfg(not(any(target_os = "ios", target_os = "macos")))]
#[test]
fn test_linkat_no_follow_symlink() {
let mut buf = [0; 1024];
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, &mut buf)
.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() {
let tempdir = tempfile::tempdir().unwrap();
Expand Down

0 comments on commit 6b00bf6

Please sign in to comment.