@@ -2607,9 +2607,15 @@ impl Cage {
26072607 None => { }
26082608 }
26092609 if dir_inode_obj. linkcount == 2 && dir_inode_obj. refcount == 0 {
2610- //removing the file from the metadata
2611- FS_METADATA . inodetable . remove ( & inodenum) ;
2610+ //The reference to the inode has to be dropped to avoid
2611+ //deadlocking because the remove() method will need to
2612+ //acquire a reference to the same inode from the
2613+ //filesystem's inodetable.
2614+ //The inodetable represents a Rust DashMap that deadlocks
2615+ //when trying to get a reference to its entry while holding any sort
2616+ //of reference into it.
26122617 drop ( inodeobj) ;
2618+ FS_METADATA . inodetable . remove ( & inodenum) ;
26132619 log_metadata ( & FS_METADATA , inodenum) ;
26142620 }
26152621 }
@@ -3382,64 +3388,169 @@ impl Cage {
33823388 0
33833389 }
33843390
3385- //------------------RMDIR SYSCALL------------------
3391+ /// ### Description
3392+ ///
3393+ /// The `rmdir_syscall()` deletes a directory whose name is given by `path`.
3394+ /// The directory shall be removed only if it is an empty directory.
3395+ ///
3396+ /// ### Arguments
3397+ ///
3398+ /// The `rmdir_syscall()` accepts one argument:
3399+ /// * `path` - the path to the directory that shall be removed. It can be
3400+ /// either
3401+ /// relative or absolute (symlinks are not supported).
3402+ ///
3403+ /// ### Returns
3404+ ///
3405+ /// Upon successful completion, 0 is returned.
3406+ /// In case of a failure, an error is returned, and `errno` is set depending
3407+ /// on the error, e.g. EACCES, ENOENT, etc.
3408+ ///
3409+ /// ### Errors
3410+ ///
3411+ /// * `ENOENT` - `path` is an empty string or names a nonexistent directory
3412+ /// * `EBUSY` - `path` names a root directory that cannot be removed
3413+ /// * `ENOEMPTY` - `path` names a non-empty directory,
3414+ /// * `EPERM` - the directory to be removed or its parent directory
3415+ /// does not allow write permission
3416+ /// * `ENOTDIR` - `path` is not a directory
3417+ /// Other errors, like `EACCES`, `EINVAL`, etc. are not supported.
3418+ ///
3419+ /// ### Panics
3420+ /// A panic occurs when the directory to be removed does not have `S_IFDIR"`
3421+ /// (directory file type flag) set or when parent inode is not a directory.
3422+ ///
3423+ /// To learn more about the syscall, error values, etc.,
3424+ /// see [rmdir(3)](https://linux.die.net/man/3/rmdir)
33863425
33873426 pub fn rmdir_syscall ( & self , path : & str ) -> i32 {
33883427 if path. len ( ) == 0 {
3389- return syscall_error ( Errno :: ENOENT , "rmdir" , "Given path is null " ) ;
3428+ return syscall_error ( Errno :: ENOENT , "rmdir" , "Given path is an empty string " ) ;
33903429 }
3430+ //Convert the provided pathname into an absolute path without `.` or `..`
3431+ //components.
33913432 let truepath = normpath ( convpath ( path) , self ) ;
33923433
3393- // try to get inodenum of input path and its parent
3434+ //Perfrom a walk down the file tree starting from the root directory to
3435+ //obtain an inode number of the file whose pathname was specified and
3436+ //its parent directory's inode.
33943437 match metawalkandparent ( truepath. as_path ( ) ) {
33953438 ( None , ..) => syscall_error ( Errno :: ENOENT , "rmdir" , "Path does not exist" ) ,
3396- ( Some ( _) , None ) => {
3397- // path exists but parent does not => path is root dir
3398- syscall_error ( Errno :: EBUSY , "rmdir" , "Cannot remove root directory" )
3399- }
3439+ //The specified directory exists, but its parent does not,
3440+ //which means it is a root directory that cannot be removed
3441+ ( Some ( _) , None ) => syscall_error ( Errno :: EBUSY , "rmdir" , "Cannot remove root directory" ) ,
34003442 ( Some ( inodenum) , Some ( parent_inodenum) ) => {
3443+ //If the parent directory of the directory that shall be removed
3444+ //doesn't allow write permission, the removal cannot be performed
3445+ if let Inode :: Dir ( ref mut parent_dir) =
3446+ * ( FS_METADATA . inodetable . get_mut ( & parent_inodenum) . unwrap ( ) )
3447+ {
3448+ // check if parent directory has write permissions
3449+ if parent_dir. mode as u32 & ( S_IWOTH | S_IWGRP | S_IWUSR ) == 0 {
3450+ return syscall_error (
3451+ Errno :: EPERM ,
3452+ "rmdir" ,
3453+ "Parent directory does not have write permission" ,
3454+ ) ;
3455+ }
3456+ }
3457+ //Getting a mutable reference to an inode struct that corresponds to
3458+ //the directory that shall be removed
34013459 let mut inodeobj = FS_METADATA . inodetable . get_mut ( & inodenum) . unwrap ( ) ;
34023460
34033461 match & mut * inodeobj {
3404- // make sure inode matches a directory
3462+ //A sanity check to make sure the inode matches a directory
34053463 Inode :: Dir ( ref mut dir_obj) => {
3464+ //When a new empty directory is created, its linkcount
3465+ //is set to 3. Thus, any empty directory should have a
3466+ //linkcount of 3. Otherwise, it is a non-empty directory
3467+ //that cannot be removed
34063468 if dir_obj. linkcount > 3 {
34073469 return syscall_error (
34083470 Errno :: ENOTEMPTY ,
34093471 "rmdir" ,
34103472 "Directory is not empty" ,
34113473 ) ;
34123474 }
3475+ //A sanity check to make sure that the correct directory
3476+ //file type flag is set
34133477 if !is_dir ( dir_obj. mode ) {
34143478 panic ! ( "This directory does not have its mode set to S_IFDIR" ) ;
34153479 }
34163480
3417- // check if dir has write permission
3481+ //The directory to be removed should allow write permission
34183482 if dir_obj. mode as u32 & ( S_IWOTH | S_IWGRP | S_IWUSR ) == 0 {
34193483 return syscall_error (
34203484 Errno :: EPERM ,
34213485 "rmdir" ,
3422- "Directory does not have write permission" ,
3486+ "Directory does not allow write permission" ,
34233487 ) ;
34243488 }
34253489
3490+ //The directory cannot be removed if one or more processes
3491+ //have the directory open, which corresponds to a non-zero
3492+ //reference count
34263493 let remove_inode = dir_obj. refcount == 0 ;
3494+ //Any new empty directory has a linkcount of 3.
3495+ //If the refcount of the directory is 0, it means
3496+ //that there are no open file descriptors for that directory,
3497+ //and it can be safely removed both from the filesystem
3498+ //and the parent directory's inode table.
3499+ //However, if there exists an open file descriptor for that
3500+ //directory, it cannot be removed from the filesystem because
3501+ //otherwise, the process that has the directory open
3502+ //will end up calling `close_syscall()` on a nonexistent directory.
3503+ //At the same time, after `rmdir_syscall()` is called on the directory,
3504+ //no new files can be created inside it, even if the directory is open
3505+ //by some process. To prevent creating new files inside the directory,
3506+ //we delete its entry from the parent directory's inode table.
3507+ //This way, in case there is an open file descriptor for the directory
3508+ //to be removed, by deleting the directory's entry from its parent
3509+ //directory's inode table and keeping the directory's entry in the
3510+ //filesystem table, we both disallow the creation of any files inside
3511+ //that directory and prevent the process that has the directory
3512+ //open from closing a nonexistent directory.
3513+ //Setting the directory's linkcount as 2 works as a flag for
3514+ //the `close_syscall()` to mark the directory that needs to be
3515+ //removed from the filesystem when its last open file descriptor
3516+ //is closed because it could not be removed at the time of calling
3517+ //`rmdir_syscall()` because of some open file descriptor.
34273518 if remove_inode {
34283519 dir_obj. linkcount = 2 ;
3429- } // linkcount for an empty directory after rmdir must be 2
3520+ }
3521+
3522+ //The mutable reference to the inode has to be dropped because
3523+ //remove_from_parent_dir() method will need to acquire an immutable
3524+ //reference to the parent directory's inode from the filesystem's
3525+ //inodetable. The inodetable represents a Rust DashMap that deadlocks
3526+ //when trying to get a reference to its entry while holding any sort
3527+ //of reference into it.
34303528 drop ( inodeobj) ;
34313529
3530+ //`remove_from_parent_dir()` helper function returns 0 if an
3531+ //entry corresponding to the specified directory was
3532+ //successfully removed from the filename-inode dictionary
3533+ //of its parent.
3534+ //If the parent directory does not allow write permission,
3535+ //`EPERM` is returned.
3536+ //As a sanity check, if the parent inode specifies a
3537+ //non-directory type, the funciton panics
34323538 let removal_result =
34333539 Self :: remove_from_parent_dir ( parent_inodenum, & truepath) ;
34343540 if removal_result != 0 {
34353541 return removal_result;
34363542 }
34373543
3438- // remove entry of corresponding inodenum from inodetable
3544+ //Remove entry of corresponding inodenum from the filesystem
3545+ //inodetable
34393546 if remove_inode {
34403547 FS_METADATA . inodetable . remove ( & inodenum) . unwrap ( ) ;
34413548 }
3442-
3549+ //Log is used to store all the changes made to the filesystem. After
3550+ //the cage is closed, all the collected changes are serialized and
3551+ //the state of the underlying filesystem is persisted. This allows us
3552+ //to avoid serializing and persisting filesystem state after every
3553+ //`rmdir_syscall()`.
34433554 log_metadata ( & FS_METADATA , parent_inodenum) ;
34443555 log_metadata ( & FS_METADATA , inodenum) ;
34453556 0 // success
0 commit comments