@@ -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]
665796fn test_unlinkat_dir_noremovedir ( ) {
0 commit comments