diff --git a/kmod/src/dir.c b/kmod/src/dir.c index 50a39290..5a2902f5 100644 --- a/kmod/src/dir.c +++ b/kmod/src/dir.c @@ -324,6 +324,10 @@ static int scoutfs_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, if (dentry->d_name.len > SCOUTFS_NAME_LEN) return -ENAMETOOLONG; + ret = scoutfs_dirty_inode_item(dir); + if (ret) + return ret; + inode = scoutfs_new_inode(sb, dir, mode, rdev); if (IS_ERR(inode)) return PTR_ERR(inode); @@ -408,6 +412,11 @@ static int scoutfs_unlink(struct inode *dir, struct dentry *dentry) if (S_ISDIR(inode->i_mode) && i_size_read(inode)) return -ENOTEMPTY; + ret = scoutfs_dirty_inode_item(dir) ?: + scoutfs_dirty_inode_item(inode); + if (ret) + return ret; + scoutfs_set_key(&key, scoutfs_ino(dir), SCOUTFS_DIRENT_KEY, di->hash); ret = scoutfs_read_item(sb, &key, &ref); diff --git a/kmod/src/inode.c b/kmod/src/inode.c index ba5eb8c4..a59cac5d 100644 --- a/kmod/src/inode.c +++ b/kmod/src/inode.c @@ -190,6 +190,41 @@ static void store_inode(struct scoutfs_inode *cinode, struct inode *inode) cinode->max_dirent_hash_nr = ci->max_dirent_hash_nr; } +/* + * Create a pinned dirty inode item so that we can later update the + * inode item without risking failure. We often wouldn't want to have + * to unwind inode modifcations (perhaps by shared vfs code!) if our + * item update failed. This is our chance to return errors for enospc + * for lack of space for new logged dirty inode items. + * + * This dirty inode item will be found by lookups in the interim so we + * have to update it now with the current inode contents. + * + * Callers don't delete these dirty items on errors. They're still + * valid and will be merged with the current item eventually. They can + * be found in the dirty block to avoid future dirtying (say repeated + * creations in a directory). + * + * The caller has to prevent sync between dirtying and updating the + * inodes. + */ +int scoutfs_dirty_inode_item(struct inode *inode) +{ + struct super_block *sb = inode->i_sb; + DECLARE_SCOUTFS_ITEM_REF(ref); + struct scoutfs_key key; + int ret; + + scoutfs_set_key(&key, scoutfs_ino(inode), SCOUTFS_INODE_KEY, 0); + + ret = scoutfs_dirty_item(sb, &key, sizeof(struct scoutfs_inode), &ref); + if (!ret) { + store_inode(ref.val, inode); + scoutfs_put_ref(&ref); + } + return ret; +} + /* * Every time we modify the inode in memory we copy it to its inode * item. This lets us write out blocks of items without having to track diff --git a/kmod/src/inode.h b/kmod/src/inode.h index 3da21640..650cd05b 100644 --- a/kmod/src/inode.h +++ b/kmod/src/inode.h @@ -23,6 +23,7 @@ struct inode *scoutfs_alloc_inode(struct super_block *sb); void scoutfs_destroy_inode(struct inode *inode); struct inode *scoutfs_iget(struct super_block *sb, u64 ino); +int scoutfs_dirty_inode_item(struct inode *inode); void scoutfs_update_inode_item(struct inode *inode); struct inode *scoutfs_new_inode(struct super_block *sb, struct inode *dir, umode_t mode, dev_t rdev); diff --git a/kmod/src/segment.c b/kmod/src/segment.c index 9ea41977..4e94760a 100644 --- a/kmod/src/segment.c +++ b/kmod/src/segment.c @@ -475,6 +475,49 @@ out: return ret; } +/* + * Ensure that there is a dirty item with the given key in the current + * dirty segment. + * + * The caller locks access to the item and prevents sync and made sure + * that there's enough free space in the segment for their dirty inodes. + * + * This is better than getting -EEXIST from create_item because that + * will leave the allocated item and val dangling in the block when it + * returns the error. + */ +int scoutfs_dirty_item(struct super_block *sb, struct scoutfs_key *key, + unsigned bytes, struct scoutfs_item_ref *ref) +{ + struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb); + struct scoutfs_item *item; + struct buffer_head *bh; + bool create = false; + int ret; + + mutex_lock(&sbi->dirty_mutex); + + if (sbi->dirty_blkno) { + ret = scoutfs_skip_lookup(sb, sbi->dirty_blkno, key, &bh, + &item); + if (ret == -ENOENT) + create = true; + else if (!ret) { + ret = populate_ref(sb, sbi->dirty_blkno, bh, item, + ref); + brelse(bh); + } + } else { + create = true; + } + mutex_unlock(&sbi->dirty_mutex); + + if (create) + ret = scoutfs_create_item(sb, key, bytes, ref); + + return ret; +} + /* * This is a really cheesy temporary delete method. It only works on items * that are stored in dirty blocks. The caller is responsible for dropping diff --git a/kmod/src/segment.h b/kmod/src/segment.h index 6b41579b..a990d422 100644 --- a/kmod/src/segment.h +++ b/kmod/src/segment.h @@ -22,6 +22,8 @@ int scoutfs_read_item(struct super_block *sb, struct scoutfs_key *key, struct scoutfs_item_ref *ref); int scoutfs_create_item(struct super_block *sb, struct scoutfs_key *key, unsigned bytes, struct scoutfs_item_ref *ref); +int scoutfs_dirty_item(struct super_block *sb, struct scoutfs_key *key, + unsigned bytes, struct scoutfs_item_ref *ref); int scoutfs_delete_item(struct super_block *sb, struct scoutfs_item_ref *ref); int scoutfs_next_item(struct super_block *sb, struct scoutfs_key *first, struct scoutfs_key *last, struct list_head *iter_list,