# HG changeset patch # User Paul Boddie # Date 1644966157 -3600 # Node ID dfd7946310c2f04be7d70f627742da182a3f897f # Parent 04941be379a4add20a3bbf7b57729c935be90b58 Added separate functions for testing empty directories and updating parent directory reference counts. diff -r 04941be379a4 -r dfd7946310c2 libe2access/include/e2access/image.h --- a/libe2access/include/e2access/image.h Tue Feb 15 22:01:34 2022 +0100 +++ b/libe2access/include/e2access/image.h Wed Feb 16 00:02:37 2022 +0100 @@ -70,6 +70,10 @@ errcode_t image_remove_by_path(ext2_filsys fs, const char *path); +errcode_t image_remove_directory_test(ext2_filsys fs, ext2_ino_t ino); + +errcode_t image_remove_parent_decrement(ext2_filsys fs, ext2_ino_t ino); + errcode_t image_rename(ext2_filsys fs, ext2_ino_t source, ext2_ino_t source_parent, const char *source_basename, ext2_ino_t target_parent, const char *target_basename); diff -r 04941be379a4 -r dfd7946310c2 libe2access/lib/src/image.c --- a/libe2access/lib/src/image.c Tue Feb 15 22:01:34 2022 +0100 +++ b/libe2access/lib/src/image.c Wed Feb 16 00:02:37 2022 +0100 @@ -60,13 +60,53 @@ return 0; } -/* Update the parent entry of a renamed directory. */ +/* Common parent entry state. */ struct _image_parent_entry_state { ext2_ino_t ino; + int nonempty; }; +/* Get entry details for a directory to be removed. */ + +static int _image_get_parent_entry(struct ext2_dir_entry *dir_entry, + int offset, int blocksize, char *buf, + void *priv_data) +{ + struct _image_parent_entry_state *state = (struct _image_parent_entry_state *) priv_data; + + (void) offset; (void) blocksize; (void) buf; + + if (!strcmp(dir_entry->name, "..")) + state->ino = dir_entry->inode; + else if (strcmp(dir_entry->name, ".")) + state->nonempty = 1; + + return 0; +} + +/* Test for objects in a directory. */ + +static int _image_test_directory(struct ext2_dir_entry *dir_entry, + int offset, int blocksize, char *buf, + void *priv_data) +{ + struct _image_parent_entry_state *state = (struct _image_parent_entry_state *) priv_data; + + (void) offset; (void) blocksize; (void) buf; + + if (strcmp(dir_entry->name, ".") && strcmp(dir_entry->name, "..")) + { + state->nonempty = 1; + return DIRENT_ABORT; + } + + return 0; +} + +/* Update the parent entry of a renamed directory. */ + static int _image_update_parent_entry(struct ext2_dir_entry *dir_entry, int offset, int blocksize, char *buf, void *priv_data) @@ -75,7 +115,7 @@ (void) offset; (void) blocksize; (void) buf; - if (strcmp(dir_entry->name, "..") == 0) + if (!strcmp(dir_entry->name, "..")) { dir_entry->inode = state->ino; return DIRENT_CHANGED | DIRENT_ABORT; @@ -363,13 +403,22 @@ errcode_t image_remove_by_inode(ext2_filsys fs, ext2_ino_t ino) { struct ext2_inode_large inode; - errcode_t err = ext2fs_read_inode_full(fs, ino, (struct ext2_inode *) &inode, - sizeof(inode)); + errcode_t retval; + + if (_image_isdir(fs, ino)) + { + retval = image_remove_directory_test(fs, ino); + if (retval) + return retval; + } + + retval = ext2fs_read_inode_full(fs, ino, (struct ext2_inode *) &inode, + sizeof(inode)); /* Handle invalid inodes, ignore unreferenced inodes. */ - if (err) - return err; + if (retval) + return retval; if (!inode.i_links_count) return 0; @@ -381,21 +430,28 @@ if (!inode.i_links_count) { - err = ext2fs_free_ext_attr(fs, ino, &inode); + retval = image_remove_parent_decrement(fs, ino); + + if (retval) + return retval; - if (!err) + /* NOTE: Update deletion time. */ + + retval = ext2fs_free_ext_attr(fs, ino, &inode); + + if (!retval) { /* Deallocate blocks, if appropriate. ~0ULL as the end represents truncation. */ if (ext2fs_inode_has_valid_blocks2(fs, (struct ext2_inode *) &inode)) { - err = ext2fs_punch(fs, ino, (struct ext2_inode *) &inode, NULL, + retval = ext2fs_punch(fs, ino, (struct ext2_inode *) &inode, NULL, 0, ~0ULL); /* Update allocation statistics. */ - if (!err) + if (!retval) ext2fs_inode_alloc_stats2(fs, ino, -1, LINUX_S_ISDIR(inode.i_mode)); } @@ -420,6 +476,67 @@ return image_remove_by_inode(fs, ino); } +/* Test for an empty directory. */ + +errcode_t image_remove_directory_test(ext2_filsys fs, ext2_ino_t ino) +{ + /* Initialise the directory listing processing state. */ + + struct _image_parent_entry_state state = {.nonempty = 0}; + errcode_t retval = ext2fs_dir_iterate(fs, ino, 0, NULL, + _image_test_directory, &state); + + if (retval) + return retval; + + /* NOTE: Need a proper error here. */ + + return state.nonempty; +} + +/* Find any parent reference and decrement the parent reference count. */ + +errcode_t image_remove_parent_decrement(ext2_filsys fs, ext2_ino_t ino) +{ + struct ext2_inode_large inode; + + /* Initialise the directory listing processing state. */ + + struct _image_parent_entry_state state = {.ino = 0}; + + /* A directory needs to be inspected for files and for the .. entry + to be queried to obtain the parent directory. */ + + errcode_t retval = ext2fs_dir_iterate(fs, ino, 0, NULL, + _image_get_parent_entry, &state); + + if (retval) + return retval; + + /* Reduce the reference count on the parent directory. */ + + if (state.ino) + { + retval = ext2fs_read_inode_full(fs, state.ino, + (struct ext2_inode *) &inode, + sizeof(inode)); + + if (retval) + return retval; + + if (inode.i_links_count) + inode.i_links_count--; + + /* NOTE: Update modification time. */ + + retval = ext2fs_write_inode_full(fs, state.ino, + (struct ext2_inode *) &inode, + sizeof(inode)); + } + + return retval; +} + /* Rename a file. */ errcode_t image_rename(ext2_filsys fs, ext2_ino_t source, @@ -428,7 +545,11 @@ { errcode_t retval; struct ext2_inode source_inode, source_parent_inode, target_parent_inode; - struct _image_parent_entry_state state = {target_parent}; + + /* Initialise the directory listing processing state to refer to the target + parent. */ + + struct _image_parent_entry_state state = {.ino = target_parent}; /* NOTE: Should check for space. */