Skip to content

Commit 848825b

Browse files
committed
Implment linkat
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. Add back trailing whitespace removed on previous force push
1 parent 30b04c5 commit 848825b

File tree

4 files changed

+186
-0
lines changed

4 files changed

+186
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
2323
- Added `User::from_uid`, `User::from_name`, `User::from_gid` and
2424
`Group::from_name`,
2525
([#1139](https://github.com/nix-rust/nix/pull/1139))
26+
- Added `linkat`
27+
([#1101](https://github.com/nix-rust/nix/pull/1101))
2628

2729
### Changed
2830
- `sys::socket::recvfrom` now returns

src/fcntl.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pub use self::posix_fadvise::*;
2424
libc_bitflags!{
2525
pub struct AtFlags: c_int {
2626
AT_REMOVEDIR;
27+
AT_SYMLINK_FOLLOW;
2728
AT_SYMLINK_NOFOLLOW;
2829
#[cfg(any(target_os = "android", target_os = "linux"))]
2930
AT_NO_AUTOMOUNT;

src/unistd.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1169,6 +1169,58 @@ pub fn isatty(fd: RawFd) -> Result<bool> {
11691169
}
11701170
}
11711171

1172+
/// Flags for `linkat` function.
1173+
#[derive(Clone, Copy, Debug)]
1174+
pub enum LinkatFlags {
1175+
SymlinkFollow,
1176+
NoSymlinkFollow,
1177+
}
1178+
1179+
/// Link one file to another file
1180+
///
1181+
/// Creates a new link (directory entry) at `newpath` for the existing file at `oldpath`. In the
1182+
/// case of a relative `oldpath`, the path is interpreted relative to the directory associated
1183+
/// with file descriptor `olddirfd` instead of the current working directory and similiarly for
1184+
/// `newpath` and file descriptor `newdirfd`. In case `flag` is LinkatFlags::SymlinkFollow and
1185+
/// `oldpath` names a symoblic link, a new link for the target of the symbolic link is created.
1186+
/// If either `olddirfd` or `newdirfd` is `None`, `AT_FDCWD` is used respectively where `oldpath`
1187+
/// and/or `newpath` is then interpreted relative to the current working directory of the calling
1188+
/// process. If either `oldpath` or `newpath` is absolute, then `dirfd` is ignored.
1189+
///
1190+
/// # References
1191+
/// See also [linkat(2)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/linkat.html)
1192+
pub fn linkat<P: ?Sized + NixPath>(
1193+
olddirfd: Option<RawFd>,
1194+
oldpath: &P,
1195+
newdirfd: Option<RawFd>,
1196+
newpath: &P,
1197+
flag: LinkatFlags,
1198+
) -> Result<()> {
1199+
1200+
let atflag =
1201+
match flag {
1202+
LinkatFlags::SymlinkFollow => AtFlags::AT_SYMLINK_FOLLOW,
1203+
LinkatFlags::NoSymlinkFollow => AtFlags::empty(),
1204+
};
1205+
1206+
let res =
1207+
oldpath.with_nix_path(|oldcstr| {
1208+
newpath.with_nix_path(|newcstr| {
1209+
unsafe {
1210+
libc::linkat(
1211+
at_rawfd(olddirfd),
1212+
oldcstr.as_ptr(),
1213+
at_rawfd(newdirfd),
1214+
newcstr.as_ptr(),
1215+
atflag.bits() as libc::c_int
1216+
)
1217+
}
1218+
})
1219+
})??;
1220+
Errno::result(res).map(drop)
1221+
}
1222+
1223+
11721224
/// Remove a directory entry
11731225
///
11741226
/// See also [unlink(2)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/unlink.html)

test/test_unistd.rs

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,137 @@ fn test_symlinkat() {
660660
);
661661
}
662662

663+
#[test]
664+
fn test_linkat_file() {
665+
let tempdir = tempfile::tempdir().unwrap();
666+
let oldfilename = "foo.txt";
667+
let oldfilepath = tempdir.path().join(oldfilename);
668+
669+
let newfilename = "bar.txt";
670+
let newfilepath = tempdir.path().join(newfilename);
671+
672+
// Create file
673+
File::create(&oldfilepath).unwrap();
674+
675+
// Get file descriptor for base directory
676+
let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
677+
678+
// Attempt hard link file at relative path
679+
linkat(Some(dirfd), oldfilename, Some(dirfd), newfilename, LinkatFlags::SymlinkFollow).unwrap();
680+
assert!(newfilepath.exists());
681+
}
682+
683+
#[test]
684+
fn test_linkat_olddirfd_none() {
685+
let tempdir_oldfile = tempfile::tempdir().unwrap();
686+
let oldfilename = "foo.txt";
687+
let oldfilepath = tempdir_oldfile.path().join(oldfilename);
688+
689+
let tempdir_newfile = tempfile::tempdir().unwrap();
690+
let newfilename = "bar.txt";
691+
let newfilepath = tempdir_newfile.path().join(newfilename);
692+
693+
// Create file
694+
File::create(&oldfilepath).unwrap();
695+
696+
// Get file descriptor for base directory of new file
697+
let dirfd = fcntl::open(tempdir_newfile.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
698+
699+
// Attempt hard link file using curent working directory as relative path for old file path
700+
chdir(tempdir_oldfile.path()).unwrap();
701+
linkat(None, oldfilename, Some(dirfd), newfilename, LinkatFlags::SymlinkFollow).unwrap();
702+
assert!(newfilepath.exists());
703+
}
704+
705+
#[test]
706+
fn test_linkat_newdirfd_none() {
707+
let tempdir_oldfile = tempfile::tempdir().unwrap();
708+
let oldfilename = "foo.txt";
709+
let oldfilepath = tempdir_oldfile.path().join(oldfilename);
710+
711+
let tempdir_newfile = tempfile::tempdir().unwrap();
712+
let newfilename = "bar.txt";
713+
let newfilepath = tempdir_newfile.path().join(newfilename);
714+
715+
// Create file
716+
File::create(&oldfilepath).unwrap();
717+
718+
// Get file descriptor for base directory of old file
719+
let dirfd = fcntl::open(tempdir_oldfile.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
720+
721+
// Attempt hard link file using current working directory as relative path for new file path
722+
chdir(tempdir_newfile.path()).unwrap();
723+
linkat(Some(dirfd), oldfilename, None, newfilename, LinkatFlags::SymlinkFollow).unwrap();
724+
assert!(newfilepath.exists());
725+
}
726+
727+
#[test]
728+
#[cfg(not(any(target_os = "ios", target_os = "macos")))]
729+
fn test_linkat_no_follow_symlink() {
730+
let tempdir = tempfile::tempdir().unwrap();
731+
let oldfilename = "foo.txt";
732+
let oldfilepath = tempdir.path().join(oldfilename);
733+
734+
let symoldfilename = "symfoo.txt";
735+
let symoldfilepath = tempdir.path().join(symoldfilename);
736+
737+
let newfilename = "nofollowsymbar.txt";
738+
let newfilepath = tempdir.path().join(newfilename);
739+
740+
// Create file
741+
File::create(&oldfilepath).unwrap();
742+
743+
// Create symlink to file
744+
symlinkat(&oldfilepath, None, &symoldfilepath).unwrap();
745+
746+
// Get file descriptor for base directory
747+
let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
748+
749+
// Attempt link symlink of file at relative path
750+
linkat(Some(dirfd), symoldfilename, Some(dirfd), newfilename, LinkatFlags::NoSymlinkFollow).unwrap();
751+
752+
// Assert newfile is actually a symlink to oldfile.
753+
assert_eq!(
754+
readlink(&newfilepath)
755+
.unwrap()
756+
.to_str()
757+
.unwrap(),
758+
oldfilepath.to_str().unwrap()
759+
);
760+
}
761+
762+
#[test]
763+
fn test_linkat_follow_symlink() {
764+
let tempdir = tempfile::tempdir().unwrap();
765+
let oldfilename = "foo.txt";
766+
let oldfilepath = tempdir.path().join(oldfilename);
767+
768+
let symoldfilename = "symfoo.txt";
769+
let symoldfilepath = tempdir.path().join(symoldfilename);
770+
771+
let newfilename = "nofollowsymbar.txt";
772+
let newfilepath = tempdir.path().join(newfilename);
773+
774+
// Create file
775+
File::create(&oldfilepath).unwrap();
776+
777+
// Create symlink to file
778+
symlinkat(&oldfilepath, None, &symoldfilepath).unwrap();
779+
780+
// Get file descriptor for base directory
781+
let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
782+
783+
// Attempt link target of symlink of file at relative path
784+
linkat(Some(dirfd), symoldfilename, Some(dirfd), newfilename, LinkatFlags::SymlinkFollow).unwrap();
785+
786+
let newfilestat = stat::stat(&newfilepath).unwrap();
787+
788+
// Check the file type of the new link
789+
assert!((stat::SFlag::from_bits_truncate(newfilestat.st_mode) & SFlag::S_IFMT) == SFlag::S_IFREG);
790+
791+
// Check the number of hard links to the original file
792+
assert_eq!(newfilestat.st_nlink, 2);
793+
}
663794

664795
#[test]
665796
fn test_unlinkat_dir_noremovedir() {

0 commit comments

Comments
 (0)