diff --git a/src/safeposix/syscalls/fs_calls.rs b/src/safeposix/syscalls/fs_calls.rs index 02f07772c..806f1ef80 100644 --- a/src/safeposix/syscalls/fs_calls.rs +++ b/src/safeposix/syscalls/fs_calls.rs @@ -743,35 +743,101 @@ impl Cage { } } - //------------------------------------LINK SYSCALL------------------------------------ - + /// ## ------------------LINK SYSCALL------------------ + /// ### Description + /// + /// The `link_syscall()` creates a new link (directory entry) for the + /// existing file represented by oldpath and increments its link count + /// by one. Since, we are creating hard links between the files, both of + /// them must exist on the same file system. Both the old and the new + /// link share equal access and rights to the underlying object. + /// On successful completion, the timestamps for both the newly created file + /// and its parent are updated along with their linkcounts. + /// If it fails, no link is created and the link count of the file remains + /// unchanged. + /// + /// ### Function Arguments + /// + /// The `link_syscall()` receives two arguments: + /// * `oldpath` - This argument points to a pathname naming an existing + /// file. + /// * `newpath` - This argument points to a pathname naming the new + /// directory + /// entry and the link to be created. + /// + /// ### Returns + /// + /// Upon successful linking of the files, 0 is returned. + /// Otherwise, −1 is returned, no link is created, and errno is set to + /// indicate the error. + /// + /// ### Errors + /// + /// * `ENOENT` - The oldpath or newpath argument is a null pathname; + /// a component of either path prefix does not exist; or the file + /// named by oldpath does not exist. + /// * `EPERM` - The file named by oldpath is a directory; current + /// implementation probibits links to directories. + /// * `EEXIST` - The link named by newpath already exists + /// + /// ### Panics + /// + /// * If the parent inode does not exist in the inode table, causing + /// unwrap() to panic. + /// * If the parent inode is not of the type `directory`, causing code to + /// panic. + /// + /// For more detailed description of all the commands and return values, see + /// [link(2)](https://man7.org/linux/man-pages/man2/link.2.html) pub fn link_syscall(&self, oldpath: &str, newpath: &str) -> i32 { + // Return an error if the provided oldpath is empty if oldpath.len() == 0 { return syscall_error(Errno::ENOENT, "link", "given oldpath was null"); } + // Return an error if the provided newpath is empty if newpath.len() == 0 { return syscall_error(Errno::ENOENT, "link", "given newpath was null"); } + // Retrieve the absolute path from the root directory for both oldpath and + // newpath. The absolute path is then used to validate directory paths + // while navigating through subdirectories. let trueoldpath = normpath(convpath(oldpath), self); let truenewpath = normpath(convpath(newpath), self); + //for now we assume this is sane, but maybe this should be checked later let filename = truenewpath .file_name() .unwrap() .to_str() .unwrap() - .to_string(); //for now we assume this is sane, but maybe this should be checked later + .to_string(); + + // TODO BUG: Man-page contains a check for the directories in the path + // to have search/read permissions, which is not implemented in this syscall. + // Walk through the absolute path for the oldpath file which returns the inode + // number of file (if it exists). match metawalk(trueoldpath.as_path()) { - //If neither the file nor parent exists + // Case: If the directory component doesn't exist, return an error. None => syscall_error( Errno::ENOENT, "link", - "a directory component in pathname does not exist or is a dangling symbolic link", + "a directory component in pathname does not exist", + // Currently, we don't support the symbolic links ), + // Case: Get the inode number and increment the link count of the existing + // directory component i.e., (File, CharDev, and Socket). + // "Directory" type is not supported for this implementation. Some(inodenum) => { + // Get the mutable instance of the inode object from the FileMetaData table. let mut inodeobj = FS_METADATA.inodetable.get_mut(&inodenum).unwrap(); + // Match the inode object with the correct inode type and increment link count match *inodeobj { + // Directory type inode is not supported for linking, so return an error. + Inode::Dir(_) => { + return syscall_error(Errno::EPERM, "link", "oldpath is a directory") + } + Inode::File(ref mut normalfile_inode_obj) => { normalfile_inode_obj.linkcount += 1; //add link to // inode @@ -781,23 +847,34 @@ impl Cage { chardev_inode_obj.linkcount += 1; //add link to inode } + // The Sockets only have an inode if they are a unix type + // socket which has a corresponding inode. Regular sockets + // do not have inodes. Inode::Socket(ref mut socket_inode_obj) => { socket_inode_obj.linkcount += 1; //add link to inode } - - Inode::Dir(_) => { - return syscall_error(Errno::EPERM, "link", "oldpath is a directory") - } } + // the mutable reference to the inode has to be dropped because + //`log_metadata` will need to acquire an immutable reference to + // the same inode drop(inodeobj); + // Walk the newpath and once the parent directory inode is found, insert a + // reference of this oldpath inode in the inode table let retval = match metawalkandparent(truenewpath.as_path()) { + // If both the file and the parent doesn't exist, newpath can't be created (None, None) => { syscall_error(Errno::ENOENT, "link", "newpath cannot be created") } + // If the newpath exists, linking can't be perfomed and an error is returned. + (Some(_), ..) => syscall_error(Errno::EEXIST, "link", "newpath already exists"), + + // If the parent directory inode exists, make a reference of the oldpath inode + // in the parent directory to make a link between the two directory paths. (None, Some(pardirinode)) => { + // Get the mutable instance of the parent inode object let mut parentinodeobj = FS_METADATA.inodetable.get_mut(&pardirinode).unwrap(); //insert a reference to the inode in the parent directory @@ -805,23 +882,34 @@ impl Cage { parentdirinodeobj .filename_to_inode_dict .insert(filename, inodenum); + // Increment the link count of the parent inode as well because + // when a link is created, a new directory entry is added to + // the parent directory of the new link. parentdirinodeobj.linkcount += 1; + //drop the mutable instance of the parent inode object drop(parentinodeobj); log_metadata(&FS_METADATA, pardirinode); log_metadata(&FS_METADATA, inodenum); } else { + // If the parent inode is not of type "Directory", panic occurs. panic!("Parent directory was not a directory!"); } - 0 //link has succeeded + // If the linking is successful, 0 is returned. + 0 } - - (Some(_), ..) => syscall_error(Errno::EEXIST, "link", "newpath already exists"), }; + // If the linking fails, an error with a value < 0 is returned from above. + // The following cases lead to the failing of the linking of files: + // 1. When both the file and the parent doesn't exist, newpath can't be created + // 2. When the the parent inode is not of type "Directory". + // 3. When the newpath already exists. + // So, we revert the link count updates made to the oldpath inode. if retval != 0 { - //reduce the linkcount to its previous value if linking failed + // Fetch the inode object from the FileMetadata Table let mut inodeobj = FS_METADATA.inodetable.get_mut(&inodenum).unwrap(); + // Match the relevant inode object type and decrement link count match *inodeobj { Inode::File(ref mut normalfile_inode_obj) => { normalfile_inode_obj.linkcount -= 1; @@ -846,29 +934,87 @@ impl Cage { } } - //------------------------------------UNLINK SYSCALL------------------------------------ - + /// ## ------------------UNLINK SYSCALL------------------ + /// ### Description + /// + /// The `unlink_syscall()` removes a link to a file. It removes the link + /// named by the pathname pointed to by path and decrements the link + /// count of the file referenced by the link. + /// If that name was the last link to a file and no processes have the file + /// open, the file is deleted and the space it was using is made + /// available for reuse. If the name was the last link to a file but any + /// processes still have the file open, the file will remain in + /// existence until the last file descriptor referring to it is closed. + /// On successful completion, the timestamp for the parent directory is + /// updated along with its linkcounts. + /// + /// ### Function Arguments + /// + /// The `unlink_syscall()` receives one argument: + /// * `path` - This argument points to a pathname which needs to be unlinked + /// + /// ### Returns + /// + /// Upon successful unlinking of the file, 0 is returned. + /// Otherwise, −1 is returned, and errno is set to indicate the error. + /// + /// ### Errors + /// + /// * `ENOENT` - The path argument is a null pathname; + /// a component of path prefix does not exist; or the file + /// named by oldpath does not exist. + /// * `EISDIR` - When the unlinking is done on a directory + /// + /// ### Panics + /// + /// * If the parent inode does not exist in the inode table, causing + /// unwrap() to panic. + /// + /// For more detailed description of all the commands and return values, see + /// [unlink(2)](https://man7.org/linux/man-pages/man2/unlink.2.html) pub fn unlink_syscall(&self, path: &str) -> i32 { + // Return an error if the provided path is empty if path.len() == 0 { - return syscall_error(Errno::ENOENT, "unmknod", "given oldpath was null"); + return syscall_error(Errno::ENOENT, "unlink", "given path was null"); } + // Retrieve the absolute path from the root directory for the given path. + // The absolute path is then used to validate directory paths while navigating + // through subdirectories. let truepath = normpath(convpath(path), self); + // Walk through the absolute path which returns a tuple consisting of inode + // number of file (if it exists), and inode number of parent (if it exists) match metawalkandparent(truepath.as_path()) { - //If the file does not exist + // Return an error if the given file does not exist (None, ..) => syscall_error(Errno::ENOENT, "unlink", "path does not exist"), - //If the file exists but has no parent, it's the root directory + // If the file exists but has no parent, it's the root directory + // No unlinking is done on the root, and an error is returned (Some(_), None) => { syscall_error(Errno::EISDIR, "unlink", "cannot unlink root directory") } - //If both the file and the parent directory exists + // If both the file and the parent directory exists (Some(inodenum), Some(parentinodenum)) => { + // Get the mutable instance of the file from the Inode table let mut inodeobj = FS_METADATA.inodetable.get_mut(&inodenum).unwrap(); + // For the inode object, we update 4 parameters: + // reference count: refers to the active file descriptors pointing to the + // file. + // link count: refers to the number of hard links pointing to the file. + // linkcount is decremented by 1 for all inode types except "Dir" type. + // file object: indicates whether the inode being unlinked has an associated + // file object. This is relevant for managing the physical deletion of the file + // data from the filesystem. It is only "true" for "File" type inode. + // log: indicates whether the FileMetaData will be updated for the inode. let (currefcount, curlinkcount, has_fobj, log) = match *inodeobj { + Inode::Dir(_) => { + // Unlinking of a directory is not supported + return syscall_error(Errno::EISDIR, "unlink", "cannot unlink directory"); + } Inode::File(ref mut f) => { + // "File" type inode has an associated File Object, so is set to "True" f.linkcount -= 1; (f.refcount, f.linkcount, true, true) } @@ -878,34 +1024,52 @@ impl Cage { } Inode::Socket(ref mut f) => { f.linkcount -= 1; + // Sockets only exist as long as the cages using them are running. + // After these cages are closed, no changes to sockets' inodes + // need to be persisted, thus using log is unnecessary and is set to "false" (f.refcount, f.linkcount, false, false) } - Inode::Dir(_) => { - return syscall_error(Errno::EISDIR, "unlink", "cannot unlink directory"); - } }; //count current number of links and references drop(inodeobj); + // Once the link count for the file has been decremented, we need to remove the + // reference of file from the parent directory. If the removal is successful, + // 0 is returned, otherwise an error with value!=0 is returned by the function. let removal_result = Self::remove_from_parent_dir(parentinodenum, &truepath); if removal_result != 0 { return removal_result; } + // When the file's link count becomes 0 (no hard links present), + // we check for two scenarios: + // If the reference count is 0 (no open file descriptors) pointing to + // the file, then we remove the file from filesystem and free the space. + // If the reference count is > 0, then file contents are not removed + // from the system. if curlinkcount == 0 { + // Remove the file from the system when no references to the file + // exists. if currefcount == 0 { - //actually remove file and the handle to it + // remove the reference of the inode from the inodetable FS_METADATA.inodetable.remove(&inodenum); + // only "File" type inode has this flag set to "true", + // so, the file is removed from the FileSystem if has_fobj { + // FILEDATAPREFIX represents the common prefix of the name + // of the file which combined with the inode number represents + // a unique entity. It stores the data of the inode object. + // Since the file is of no use, we are removing its entry + // from the system. let sysfilename = format!("{}{}", FILEDATAPREFIX, inodenum); interface::removefile(sysfilename).unwrap(); } - } //we don't need a separate unlinked flag, we can just - // check that refcount is 0 + } } + // Remove any domain socket paths associated with the file NET_METADATA.domsock_paths.remove(&truepath); - // the log boolean will be false if we are workign on a domain socket + // the log boolean will be false if we are working on a domain socket if log { log_metadata(&FS_METADATA, parentinodenum); log_metadata(&FS_METADATA, inodenum); @@ -2997,14 +3161,47 @@ impl Cage { } } + /// ### Description + /// + /// The `remove_from_parent_dir()` is a helper function used by a couple + /// of syscalls to remove a file from its parent directory's inode. It + /// ensures that the parent directory has the appropriate permissions + /// before removing the file entry and updating the parent directory's + /// metadata. + /// + /// ### Arguments + /// + /// The `remove_from_parent_dir()` accepts two arguments: + /// * `parent_inodenum` - an inode number of the parent directory from which + /// the file is to be removed. + /// * `truepath` - the absolute path of the file to be removed, used to + /// identify + /// the filename within the parent directory. + /// + /// ### Returns + /// + /// Upon successful completion, zero is returned. In case of a failure, an + /// error is returned, and `errno` is set depending on the error, e.g., + /// EPERM. + /// + /// ### Errors + /// + /// Currently, the following error is supported: + /// * `EPERM` - the parent directory does not have write permission. + /// + /// ### Panics + /// + /// This function will panic if the `parent_inodenum` does not correspond to + /// a directory inode. pub fn remove_from_parent_dir( parent_inodenum: usize, truepath: &interface::RustPathBuf, ) -> i32 { + // Get the inode of the parent directory and ensure it is a directory if let Inode::Dir(ref mut parent_dir) = *(FS_METADATA.inodetable.get_mut(&parent_inodenum).unwrap()) { - // check if parent dir has write permission + // check if parent directory has write permissions if parent_dir.mode as u32 & (S_IWOTH | S_IWGRP | S_IWUSR) == 0 { return syscall_error( Errno::EPERM, @@ -3018,8 +3215,10 @@ impl Cage { .filename_to_inode_dict .remove(&truepath.file_name().unwrap().to_str().unwrap().to_string()) .unwrap(); - parent_dir.linkcount -= 1; // decrement linkcount of parent dir + // Decrement the link count of the parent directory + parent_dir.linkcount -= 1; } else { + // Panic if the parent inode is not a directory panic!("Non directory file was parent!"); } 0 diff --git a/src/tests/fs_tests.rs b/src/tests/fs_tests.rs index 8949a38ff..0e11fd944 100644 --- a/src/tests/fs_tests.rs +++ b/src/tests/fs_tests.rs @@ -805,44 +805,287 @@ pub mod fs_tests { } #[test] - pub fn ut_lind_fs_file_link_unlink() { - //acquiring a lock on TESTMUTEX prevents other tests from running concurrently, + pub fn ut_lind_fs_link_empty_path() { + // acquiring a lock on TESTMUTEX prevents other tests from running concurrently, + // and also performs clean env setup + let _thelock = setup::lock_and_init(); + + let cage = interface::cagetable_getref(1); + + // Case: When only oldpath is empty, expect an error ENOENT + let oldpath = ""; + let newpath = "/newpath"; + assert_eq!(cage.link_syscall(oldpath, newpath), -(Errno::ENOENT as i32)); + + // Case: When only newpath is empty, expect an error ENOENT + let oldpath = "/oldpath"; + let newpath = ""; + assert_eq!(cage.link_syscall(oldpath, newpath), -(Errno::ENOENT as i32)); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[test] + pub fn ut_lind_fs_link_nonexistent_oldpath() { + // acquiring a lock on TESTMUTEX prevents other tests from running concurrently, + // and also performs clean env setup + let _thelock = setup::lock_and_init(); + + let cage = interface::cagetable_getref(1); + + let oldpath = "/nonexistent"; + let newpath = "/newpath"; + + // Expect an error for non-existent oldpath + assert_eq!(cage.link_syscall(oldpath, newpath), -(Errno::ENOENT as i32)); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[test] + pub fn ut_lind_fs_link_existing_newpath() { + // acquiring a lock on TESTMUTEX prevents other tests from running concurrently, + // and also performs clean env setup + let _thelock = setup::lock_and_init(); + + let cage = interface::cagetable_getref(1); + + let oldpath = "/oldfile"; + let newpath = "/newfile"; + + // Create the oldfile + let _fd1 = cage.open_syscall(oldpath, O_CREAT | O_EXCL | O_WRONLY, S_IRWXA); + + // Create the newfile + let _fd2 = cage.open_syscall(newpath, O_CREAT | O_EXCL | O_WRONLY, S_IRWXA); + + // Expect an error since newpath already exists + assert_eq!(cage.link_syscall(oldpath, newpath), -(Errno::EEXIST as i32)); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[test] + pub fn ut_lind_fs_link_directory() { + // acquiring a lock on TESTMUTEX prevents other tests from running concurrently, // and also performs clean env setup let _thelock = setup::lock_and_init(); let cage = interface::cagetable_getref(1); - let path = "/fileLink"; - let path2 = "/fileLink2"; + let oldpath = "/olddir"; + let newpath = "/newpath"; + // Create the directory for the oldpath + assert_eq!(cage.mkdir_syscall(oldpath, S_IRWXA), 0); + + // Expect an error since linking directories is not allowed + assert_eq!(cage.link_syscall(oldpath, newpath), -(Errno::EPERM as i32)); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[test] + pub fn ut_lind_fs_unlink_empty_path() { + // acquiring a lock on TESTMUTEX prevents other tests from running concurrently, + // and also performs clean env setup + let _thelock = setup::lock_and_init(); + + let cage = interface::cagetable_getref(1); + + let path = ""; + // Expect an error for empty path + assert_eq!(cage.unlink_syscall(path), -(Errno::ENOENT as i32)); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[test] + pub fn ut_lind_fs_unlink_nonexistent_file() { + // acquiring a lock on TESTMUTEX prevents other tests from running concurrently, + // and also performs clean env setup + let _thelock = setup::lock_and_init(); + + let cage = interface::cagetable_getref(1); + + let path = "/nonexistent"; + // Expect an error for non-existent path + assert_eq!(cage.unlink_syscall(path), -(Errno::ENOENT as i32)); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[test] + pub fn ut_lind_fs_unlink_root_directory() { + // acquiring a lock on TESTMUTEX prevents other tests from running concurrently, + // and also performs clean env setup + let _thelock = setup::lock_and_init(); + + let cage = interface::cagetable_getref(1); + + let path = "/"; + // Expect an error for unlinking root directory + assert_eq!(cage.unlink_syscall(path), -(Errno::EISDIR as i32)); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[test] + pub fn ut_lind_fs_unlink_directory() { + // acquiring a lock on TESTMUTEX prevents other tests from running concurrently, + // and also performs clean env setup + let _thelock = setup::lock_and_init(); + + let cage = interface::cagetable_getref(1); + + let path = "/testdir"; + // Create the directory + assert_eq!(cage.mkdir_syscall(path, S_IRWXA), 0); + + // Expect an error for unlinking a directory + assert_eq!(cage.unlink_syscall(path), -(Errno::EISDIR as i32)); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[test] + pub fn ut_lind_fs_unlink_and_close_file() { + // acquiring a lock on TESTMUTEX prevents other tests from running concurrently, + // and also performs clean env setup + let _thelock = setup::lock_and_init(); + + let cage = interface::cagetable_getref(1); + let path = "/testfile"; + // Create a file let fd = cage.open_syscall(path, O_CREAT | O_EXCL | O_WRONLY, S_IRWXA); + let mut statdata = StatData::default(); + assert_eq!(cage.stat_syscall(path, &mut statdata), 0); + // Linkcount for the file should be 1 originally + assert_eq!(statdata.st_nlink, 1); + // Perform the unlinking of the file + assert_eq!(cage.unlink_syscall(path), 0); + // Once we close the file, all the existing references for it + // will get closed, so the file descriptor will get deleted + // from the system. + assert_eq!(cage.close_syscall(fd), 0); + // Inorder to verify if the file has been deleted, + // we will try to fetch its data but we must expect an error: + // (ENOENT) "Invalid File". + assert_eq!( + cage.stat_syscall(path, &mut statdata), + -(Errno::ENOENT as i32) + ); + // Verify if the file descriptor has been deleted + // (EBADF) "Invalid File Descriptor". + assert_eq!( + cage.fstat_syscall(fd, &mut statdata), + -(Errno::EBADF as i32) + ); + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[test] + pub fn ut_lind_fs_unlink_file() { + // acquiring a lock on TESTMUTEX prevents other tests from running concurrently, + // and also performs clean env setup + let _thelock = setup::lock_and_init(); + + let cage = interface::cagetable_getref(1); + let path = "/testfile"; + // Create a file + let fd = cage.open_syscall(path, O_CREAT | O_EXCL | O_WRONLY, S_IRWXA); + let mut statdata = StatData::default(); + assert_eq!(cage.stat_syscall(path, &mut statdata), 0); + // Linkcount for the file should be 1 originally + assert_eq!(statdata.st_nlink, 1); + // Perform the unlinking of the file + assert_eq!(cage.unlink_syscall(path), 0); + // Since we are not closing the file, the reference + // count should be > 0, and the fd should be valid. + // Verify if the file descriptor is still present + assert_eq!(cage.fstat_syscall(fd, &mut statdata), 0); + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[test] + pub fn ut_lind_fs_link_unlink_success() { + // acquiring a lock on TESTMUTEX prevents other tests from running concurrently, + // and also performs clean env setup + let _thelock = setup::lock_and_init(); + + let cage = interface::cagetable_getref(1); + + let oldpath = "/fileLink"; + let newpath = "/fileLink2"; + + // Create the oldpath file + let fd = cage.open_syscall(oldpath, O_CREAT | O_EXCL | O_WRONLY, S_IRWXA); assert_eq!(cage.lseek_syscall(fd, 0, SEEK_SET), 0); assert_eq!(cage.write_syscall(fd, str2cbuf("hi"), 2), 2); let mut statdata = StatData::default(); - - assert_eq!(cage.stat_syscall(path, &mut statdata), 0); + assert_eq!(cage.stat_syscall(oldpath, &mut statdata), 0); assert_eq!(statdata.st_size, 2); + + // Linkcount for the original file (oldpath) before linking should be 1 assert_eq!(statdata.st_nlink, 1); let mut statdata2 = StatData::default(); - //make sure that this has the same traits as the other file that we linked - // and make sure that the link count on the orig file has increased - assert_eq!(cage.link_syscall(path, path2), 0); - assert_eq!(cage.stat_syscall(path, &mut statdata), 0); - assert_eq!(cage.stat_syscall(path2, &mut statdata2), 0); + // Link the two files + assert_eq!(cage.link_syscall(oldpath, newpath), 0); + assert_eq!(cage.stat_syscall(oldpath, &mut statdata), 0); + assert_eq!(cage.stat_syscall(newpath, &mut statdata2), 0); + // make sure that this has the same traits as the other file that we linked assert!(statdata == statdata2); + // and make sure that the link count on the orig file has increased by 1 assert_eq!(statdata.st_nlink, 2); - //now we unlink - assert_eq!(cage.unlink_syscall(path), 0); - assert_eq!(cage.stat_syscall(path2, &mut statdata2), 0); + // Perform unlinking of the original file (oldpath) + assert_eq!(cage.unlink_syscall(oldpath), 0); + assert_eq!(cage.stat_syscall(newpath, &mut statdata2), 0); + // Since the file is unlinked, it's link count should be decreased by 1 assert_eq!(statdata2.st_nlink, 1); - //it shouldn't work to stat the orig since it is gone - assert_ne!(cage.stat_syscall(path, &mut statdata), 0); - assert_eq!(cage.unlink_syscall(path2), 0); + //it shouldn't work to stat the original since it is gone + assert_ne!(cage.stat_syscall(oldpath, &mut statdata), 0); + assert_eq!(cage.unlink_syscall(newpath), 0); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[test] + pub fn ut_lind_fs_link_invalid_path_permissions() { + // acquiring a lock on TESTMUTEX prevents other tests from running concurrently, + // and also performs clean env setup + let _thelock = setup::lock_and_init(); + + let cage = interface::cagetable_getref(1); + + let oldpath = "/testdir/olddir"; + let newpath = "/newpath"; + + // Create the directory for the oldpath with the parent not having read + // permission. Currently assigning "Write only" permissions + assert_eq!(cage.mkdir_syscall("/testdir", S_IWUSR), 0); + let fd = cage.open_syscall(oldpath, O_CREAT | O_EXCL | O_WRONLY, S_IWUSR); + assert_eq!(cage.lseek_syscall(fd, 0, SEEK_SET), 0); + + // Expect the linking to be successful, but this is a bug which must be fixed + // as the parent directory doesn't have read permissions due to which it should + // not be able to link the files. + assert_eq!(cage.link_syscall(oldpath, newpath), 0); assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); lindrustfinalize();