Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implment linkat #1101

Merged
merged 1 commit into from Nov 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
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
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
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.
jlb6740 marked this conversation as resolved.
Show resolved Hide resolved
/// 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
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