From 71898cdb8cbb2e415d4a87466c521cee5574b9cc Mon Sep 17 00:00:00 2001 From: jason50123 Date: Thu, 20 Jun 2024 10:26:08 +0800 Subject: [PATCH 1/3] Implement external journal device initialization During fill super, call simplefs_parse_options to find the external device path and set it as the journal device. Additionally, add the `make journal` parameter in the Makefile to create an external journal device. --- Makefile | 13 ++- simplefs.h | 5 + super.c | 263 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 278 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index cff6d56..8d10f24 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,9 @@ all: $(MKFS) IMAGE ?= test.img IMAGESIZE ?= 200 +JOURNAL ?= journal.img +JOURNALSIZE ?= 8 + # To test max files(40920) in directory, the image size should be at least 159.85 MiB # 40920 * 4096(block size) ~= 159.85 MiB @@ -20,12 +23,18 @@ $(IMAGE): $(MKFS) dd if=/dev/zero of=${IMAGE} bs=1M count=${IMAGESIZE} ./$< $(IMAGE) +journal: $(JOURNAL) + +$(JOURNAL): + dd if=/dev/zero of=$(JOURNAL) bs=1M count=$(JOURNALSIZE) + mke2fs -b 4096 -O journal_dev $(JOURNAL) + check: all script/test.sh $(IMAGE) $(IMAGESIZE) $(MKFS) clean: make -C $(KDIR) M=$(PWD) clean rm -f *~ $(PWD)/*.ur-safe - rm -f $(MKFS) $(IMAGE) + rm -f $(MKFS) $(IMAGE) $(JOURNAL) -.PHONY: all clean +.PHONY: all clean journal diff --git a/simplefs.h b/simplefs.h index 8e7c543..11105f3 100644 --- a/simplefs.h +++ b/simplefs.h @@ -37,6 +37,9 @@ * | blocks | rest of the blocks * +---------------+ */ +#ifdef __KERNEL__ +#include +#endif struct simplefs_inode { uint32_t i_mode; /* File mode */ @@ -69,6 +72,8 @@ struct simplefs_sb_info { uint32_t nr_free_blocks; /* Number of free blocks */ #ifdef __KERNEL__ + journal_t *journal; + struct bdev_handle *s_journal_bdev_handle; unsigned long *ifree_bitmap; /* In-memory free inodes bitmap */ unsigned long *bfree_bitmap; /* In-memory free blocks bitmap */ #endif diff --git a/super.c b/super.c index e966ec9..1c03443 100644 --- a/super.c +++ b/super.c @@ -7,6 +7,11 @@ #include #include +#include +#include +#include +#include + #include "simplefs.h" struct dentry *simplefs_mount(struct file_system_type *fs_type, @@ -196,6 +201,251 @@ static int simplefs_statfs(struct dentry *dentry, struct kstatfs *stat) return 0; } +/* journal relation code */ + + +static struct bdev_handle *simplefs_get_journal_blkdev( + struct super_block *sb, + dev_t jdev, + unsigned long long *j_start, + unsigned long long *j_len) +{ + struct buffer_head *bh; + struct bdev_handle *bdev_handle; + struct block_device *bdev; + int hblock, blocksize; + unsigned long long sb_block; + unsigned long offset; + int errno; + + bdev_handle = bdev_open_by_dev( + jdev, BLK_OPEN_READ | BLK_OPEN_WRITE | BLK_OPEN_RESTRICT_WRITES, sb, + &fs_holder_ops); + + if (IS_ERR(bdev_handle)) { + printk(KERN_ERR + "failed to open journal device unknown-block(%u,%u) %ld\n", + MAJOR(jdev), MINOR(jdev), PTR_ERR(bdev_handle)); + return bdev_handle; + } + + bdev = bdev_handle->bdev; + blocksize = sb->s_blocksize; + hblock = bdev_logical_block_size(bdev); + + if (blocksize < hblock) { + pr_err("blocksize too small for journal device\n"); + errno = -EINVAL; + goto out_bdev; + } + + sb_block = SIMPLEFS_BLOCK_SIZE / blocksize; + offset = SIMPLEFS_BLOCK_SIZE % blocksize; + set_blocksize(bdev, blocksize); + bh = __bread(bdev, sb_block, blocksize); + + if (!bh) { + pr_err("couldn't read superblock of external journal\n"); + errno = -EINVAL; + goto out_bdev; + } + // sbi = (struct simplefs_sb_info *) (bh->b_data + offset); + + *j_start = sb_block; + + /* + *Fix me: This number is calculated by dividing the external device capacity + *(8MB) by the external device block size (4096). Currently, I don't know + *how to obtain the capacity size of the external device. + */ + + *j_len = 2048; + brelse(bh); + + return bdev_handle; + +out_bdev: + bdev_release(bdev_handle); + return ERR_PTR(errno); +} + + +#if SIMPLEFS_AT_LEAST(6, 8, 0) +static journal_t *simplefs_get_dev_journal(struct super_block *sb, + dev_t journal_dev) +{ + journal_t *journal; + unsigned long long j_start; + unsigned long long j_len; + struct bdev_handle *bdev_handle; + int errno = 0; + + pr_info("simplefs_get_dev_journal: getting journal for device %u:%u\n", + MAJOR(journal_dev), MINOR(journal_dev)); + + struct simplefs_sb_info *sbi = SIMPLEFS_SB(sb); + + bdev_handle = + simplefs_get_journal_blkdev(sb, journal_dev, &j_start, &j_len); + if (IS_ERR(bdev_handle)) { + pr_err( + "simplefs_get_dev_journal: failed to get journal block device, " + "error %ld\n", + PTR_ERR(bdev_handle)); + return ERR_CAST(bdev_handle); + } + + pr_info( + "simplefs_get_dev_journal: journal block device obtained, start=%llu, " + "len=%llu\n", + j_start, j_len); + + journal = jbd2_journal_init_dev(bdev_handle->bdev, sb->s_bdev, j_start, + j_len, sb->s_blocksize); + if (IS_ERR(journal)) { + pr_err( + "simplefs_get_dev_journal: failed to initialize journal, error " + "%ld\n", + PTR_ERR(journal)); + errno = PTR_ERR(journal); + goto out_bdev; + } + + journal->j_private = sb; + sbi->s_journal_bdev_handle = bdev_handle; + + pr_info("simplefs_get_dev_journal: journal initialized successfully\n"); + + return journal; + +out_bdev: + bdev_release(bdev_handle); + return ERR_PTR(errno); +} + +#endif + +static int simplefs_load_journal(struct super_block *sb, + unsigned long journal_devnum) +{ + journal_t *journal; + struct simplefs_sb_info *sbi = SIMPLEFS_SB(sb); + dev_t journal_dev; + int err = 0; + pr_info("simplefs_load_journal: loading journal from device number %lu\n", + journal_devnum); + journal_dev = new_decode_dev(journal_devnum); + + journal = simplefs_get_dev_journal(sb, journal_dev); + if (IS_ERR(journal)) { + pr_err("Failed to get journal from device, error %ld\n", + PTR_ERR(journal)); + return PTR_ERR(journal); + } + pr_info("simplefs_load_journal: journal obtained successfully\n"); + + err = jbd2_journal_load(journal); + if (err) { + pr_err("error loading journal, error %d\n", err); + goto err_out; + } + + sbi->journal = journal; + pr_info("simplefs_load_journal: journal loaded successfully\n"); + + return 0; + +err_out: + jbd2_journal_destroy(journal); + return err; +} + +// we use SIMPLEFS_OPT_JOURNAL_PATH case to load external journal device now +/**/ +#define SIMPLEFS_OPT_JOURNAL_DEV 1 +#define SIMPLEFS_OPT_JOURNAL_PATH 2 +static const match_table_t tokens = { + {SIMPLEFS_OPT_JOURNAL_DEV, "journal_dev=%u"}, + {SIMPLEFS_OPT_JOURNAL_PATH, "journal_path=%s"}, +}; +static int simplefs_parse_options(struct super_block *sb, char *options) +{ + substring_t args[MAX_OPT_ARGS]; + int token, ret = 0, arg; + char *p; + + pr_info("simplefs_parse_options: parsing options '%s'\n", options); + + while ((p = strsep(&options, ",")) != NULL) { + if (!*p) + continue; + + args[0].to = args[0].from = NULL; + token = match_token(p, tokens, args); + + pr_info("simplefs_parse_options: token '%s' parsed as %d\n", p, token); + + switch (token) { + case SIMPLEFS_OPT_JOURNAL_DEV: + if (args->from && match_int(args, &arg)) { + pr_err("simplefs_parse_options: match_int failed\n"); + return 1; + } + printk(KERN_INFO "Loading journal devnum: %i\n", arg); + if ((ret = simplefs_load_journal(sb, arg))) { + pr_err( + "simplefs_parse_options: simplefs_load_journal failed with " + "%d\n", + ret); + return ret; + } + break; + + case SIMPLEFS_OPT_JOURNAL_PATH: { + char *journal_path; + struct inode *journal_inode; + struct path path; + + pr_info("simplefs_parse_options: parsing journal path\n"); + + journal_path = match_strdup(&args[0]); + if (!journal_path) { + pr_err("simplefs_parse_options: match_strdup failed\n"); + return -ENOMEM; + } + ret = kern_path(journal_path, LOOKUP_FOLLOW, &path); + if (ret) { + pr_err( + "simplefs_parse_options: kern_path failed with error %d\n", + ret); + kfree(journal_path); + return ret; + } + + journal_inode = path.dentry->d_inode; + + path_put(&path); + kfree(journal_path); + + if (S_ISBLK(journal_inode->i_mode)) { + unsigned long journal_devnum = + new_encode_dev(journal_inode->i_rdev); + if ((ret = simplefs_load_journal(sb, journal_devnum))) { + pr_err( + "simplefs_parse_options: simplefs_load_journal failed " + "with %d\n", + ret); + return ret; + } + } + break; + } + } + } + + pr_info("simplefs_parse_options: options parsed successfully\n"); + return 0; +} static struct super_operations simplefs_super_ops = { .put_super = simplefs_put_super, .alloc_inode = simplefs_alloc_inode, @@ -319,6 +569,17 @@ int simplefs_fill_super(struct super_block *sb, void *data, int silent) goto iput; } + ret = simplefs_parse_options(sb, data); + if (ret) { + pr_err( + "simplefs_fill_super: simplefs_parse_options failed with error " + "%d\n", + ret); + goto release; + } + + pr_info("simplefs_fill_super: successfully loaded superblock\n"); + return 0; iput: @@ -331,6 +592,6 @@ int simplefs_fill_super(struct super_block *sb, void *data, int silent) kfree(sbi); release: brelse(bh); - + pr_err("simplefs_fill_super: failed to load superblock\n"); return ret; } From e2af92942fe3f401ad03f196d7b9e962f193ff8c Mon Sep 17 00:00:00 2001 From: jason50123 Date: Thu, 20 Jun 2024 10:54:01 +0800 Subject: [PATCH 2/3] Add journal to write function In the current design of simplefs, we can only access the extent metadata and perform the 'journal' action on it. To write inode and other metadata, we might need to emulate `ext4_dirty_inode`. This method primarily uses the content of struct ext4_iloc to find the correct location of the inode on disk and records it in the journal. However, in our current situation, simply using `mark_inode_dirty` to handle dirty inodes does not allow us to obtain the inode's location, thus preventing us from recording it in the journal. But currently, the idea is that we might create a new buffer head, use `jbd2_journal_get_create_access` to put the dirty inode in it, and then use `jbd2_journal_dirty_metadata` to write it to the journal. --- file.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/file.c b/file.c index 73131e8..3a8e074 100644 --- a/file.c +++ b/file.c @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -34,6 +35,20 @@ static int simplefs_file_get_block(struct inode *inode, bh_index = sb_bread(sb, ci->ei_block); if (!bh_index) return -EIO; + + handle_t *handle = journal_current_handle(); + if (!handle) { + brelse(bh_index); + return -EIO; + } + + ret = jbd2_journal_get_write_access(handle, bh_index); + if (ret) { + brelse(bh_index); + pr_info("Can't get write access for bh\n"); + return ret; + } + index = (struct simplefs_file_ei_block *) bh_index->b_data; extent = simplefs_ext_search(index, iblock); @@ -63,6 +78,13 @@ static int simplefs_file_get_block(struct inode *inode, extent ? index->extents[extent - 1].ee_block + index->extents[extent - 1].ee_len : 0; + ret = jbd2_journal_dirty_metadata(handle, bh_index); + if (ret) { + brelse(bh_index); + return ret; + } + mark_buffer_dirty_inode(bh_index, inode); + } else { bno = index->extents[extent].ee_start + iblock - index->extents[extent].ee_block; @@ -133,6 +155,7 @@ static int simplefs_write_begin(struct file *file, struct simplefs_sb_info *sbi = SIMPLEFS_SB(file->f_inode->i_sb); int err; uint32_t nr_allocs = 0; + handle_t *handle; /* Check if the write can be completed (enough space?) */ if (pos + len > SIMPLEFS_MAX_FILESIZE) @@ -146,6 +169,16 @@ static int simplefs_write_begin(struct file *file, if (nr_allocs > sbi->nr_free_blocks) return -ENOSPC; + // handle journal start here + /* + * Fix me: the metadata type we should store into journal + * In the current situation, we only record the location of the extent + * and write that metadata to the journal. + */ + handle = jbd2_journal_start(sbi->journal, 1); + if (IS_ERR(handle)) + return PTR_ERR(handle); + /* prepare the write */ #if SIMPLEFS_AT_LEAST(5, 19, 0) err = block_write_begin(mapping, pos, len, pagep, simplefs_file_get_block); @@ -174,6 +207,16 @@ static int simplefs_write_end(struct file *file, struct inode *inode = file->f_inode; struct simplefs_inode_info *ci = SIMPLEFS_INODE(inode); struct super_block *sb = inode->i_sb; + + handle_t *handle; + + // handle journal start here + handle = journal_current_handle(); + if (!handle) { + pr_err("can't get journal handle\n"); + return -EIO; + } + #if SIMPLEFS_AT_LEAST(6, 6, 0) struct timespec64 cur_time; #endif @@ -223,6 +266,14 @@ static int simplefs_write_end(struct file *file, nr_blocks_old - inode->i_blocks); goto end; } + + int retval = jbd2_journal_get_write_access(handle, bh_index); + if (WARN_ON(retval)) { + brelse(bh_index); + pr_info("cant get journal write access\n"); + return retval; + } + index = (struct simplefs_file_ei_block *) bh_index->b_data; first_ext = simplefs_ext_search(index, inode->i_blocks - 1); @@ -238,9 +289,13 @@ static int simplefs_write_end(struct file *file, index->extents[i].ee_len); memset(&index->extents[i], 0, sizeof(struct simplefs_extent)); } + jbd2_journal_dirty_metadata(handle, bh_index); mark_buffer_dirty(bh_index); brelse(bh_index); } + + jbd2_journal_stop(handle); + end: return ret; } From 668696c2f31c0bf6925101d55353f51b1d56270d Mon Sep 17 00:00:00 2001 From: jason50123 Date: Sat, 22 Jun 2024 05:20:03 +0800 Subject: [PATCH 3/3] Add support for loading external journal in v6.5 Based on feedback from previous pull requests, addressed the mentioned issues and ensured that the related external device journal loading function operates correctly in kernel version 6.5. --- file.c | 6 ++-- simplefs.h | 4 ++- super.c | 94 ++++++++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 83 insertions(+), 21 deletions(-) diff --git a/file.c b/file.c index 3a8e074..668a5d5 100644 --- a/file.c +++ b/file.c @@ -169,9 +169,9 @@ static int simplefs_write_begin(struct file *file, if (nr_allocs > sbi->nr_free_blocks) return -ENOSPC; - // handle journal start here + /* handle journal starting here */ /* - * Fix me: the metadata type we should store into journal + * FIXME: the metadata type we should store into journal * In the current situation, we only record the location of the extent * and write that metadata to the journal. */ @@ -210,7 +210,7 @@ static int simplefs_write_end(struct file *file, handle_t *handle; - // handle journal start here + /* handle journal start here */ handle = journal_current_handle(); if (!handle) { pr_err("can't get journal handle\n"); diff --git a/simplefs.h b/simplefs.h index 11105f3..6062ca2 100644 --- a/simplefs.h +++ b/simplefs.h @@ -73,7 +73,9 @@ struct simplefs_sb_info { #ifdef __KERNEL__ journal_t *journal; - struct bdev_handle *s_journal_bdev_handle; + struct block_device *s_journal_bdev; /* v6.5 external journal device */ + struct bdev_handle + *s_journal_bdev_handle; /* v6.8 external journal device */ unsigned long *ifree_bitmap; /* In-memory free inodes bitmap */ unsigned long *bfree_bitmap; /* In-memory free blocks bitmap */ #endif diff --git a/super.c b/super.c index 1c03443..715a101 100644 --- a/super.c +++ b/super.c @@ -203,7 +203,7 @@ static int simplefs_statfs(struct dentry *dentry, struct kstatfs *stat) /* journal relation code */ - +#if SIMPLEFS_AT_LEAST(6, 8, 0) static struct bdev_handle *simplefs_get_journal_blkdev( struct super_block *sb, dev_t jdev, @@ -254,9 +254,10 @@ static struct bdev_handle *simplefs_get_journal_blkdev( *j_start = sb_block; /* - *Fix me: This number is calculated by dividing the external device capacity - *(8MB) by the external device block size (4096). Currently, I don't know - *how to obtain the capacity size of the external device. + * FIXME: + * Since I currently cannot find where the variable for the size of the + * external block device is stored, I am using device size (8MB) / device + * block size (4096 bytes) = 2048 to fill in j_len. */ *j_len = 2048; @@ -268,7 +269,7 @@ static struct bdev_handle *simplefs_get_journal_blkdev( bdev_release(bdev_handle); return ERR_PTR(errno); } - +#endif #if SIMPLEFS_AT_LEAST(6, 8, 0) static journal_t *simplefs_get_dev_journal(struct super_block *sb, @@ -322,7 +323,75 @@ static journal_t *simplefs_get_dev_journal(struct super_block *sb, bdev_release(bdev_handle); return ERR_PTR(errno); } +#elif SIMPLEFS_AT_LEAST(6, 5, 0) +static journal_t *simplefs_get_dev_journal(struct super_block *sb, + dev_t journal_dev) +{ + struct simplefs_sb_info *sbi = SIMPLEFS_SB(sb); + struct buffer_head *bh; + struct block_device *bdev; + int hblock, blocksize; + unsigned long long sb_block, start, len; + unsigned long offset; + journal_t *journal; + int errno = 0; + bdev = blkdev_get_by_dev(journal_dev, BLK_OPEN_READ | BLK_OPEN_WRITE, sb, + NULL); + if (IS_ERR(bdev)) { + printk(KERN_ERR "failed to open block device (%u:%u), error: %ld\n", + MAJOR(journal_dev), MINOR(journal_dev), PTR_ERR(bdev)); + return ERR_CAST(bdev); + } + + blocksize = sb->s_blocksize; + hblock = bdev_logical_block_size(bdev); + + if (blocksize < hblock) { + pr_err("blocksize too small for journal device\n"); + errno = -EINVAL; + goto out_bdev; + } + + sb_block = SIMPLEFS_BLOCK_SIZE / blocksize; + offset = SIMPLEFS_BLOCK_SIZE % blocksize; + set_blocksize(bdev, blocksize); + bh = __bread(bdev, sb_block, blocksize); + + if (!bh) { + pr_err("couldn't read superblock of external journal\n"); + errno = -EINVAL; + goto out_bdev; + } + + /* + * FIXME: + * Since I currently cannot find where the variable for the size of the + * external block device is stored, I am using device size (8MB) / device + * block size (4096 bytes) = 2048 to fill in j_len. + */ + len = 2048; + start = sb_block; + brelse(bh); + + journal = jbd2_journal_init_dev(bdev, sb->s_bdev, start, len, blocksize); + if (IS_ERR(journal)) { + pr_err( + "simplefs_get_dev_journal: failed to initialize journal, error " + "%ld\n", + PTR_ERR(journal)); + errno = PTR_ERR(journal); + goto out_bdev; + } + + sbi->s_journal_bdev = bdev; + journal->j_private = sb; + return journal; + +out_bdev: + blkdev_put(bdev, sb); + return NULL; +} #endif static int simplefs_load_journal(struct super_block *sb, @@ -332,8 +401,7 @@ static int simplefs_load_journal(struct super_block *sb, struct simplefs_sb_info *sbi = SIMPLEFS_SB(sb); dev_t journal_dev; int err = 0; - pr_info("simplefs_load_journal: loading journal from device number %lu\n", - journal_devnum); + journal_dev = new_decode_dev(journal_devnum); journal = simplefs_get_dev_journal(sb, journal_dev); @@ -342,7 +410,6 @@ static int simplefs_load_journal(struct super_block *sb, PTR_ERR(journal)); return PTR_ERR(journal); } - pr_info("simplefs_load_journal: journal obtained successfully\n"); err = jbd2_journal_load(journal); if (err) { @@ -351,7 +418,6 @@ static int simplefs_load_journal(struct super_block *sb, } sbi->journal = journal; - pr_info("simplefs_load_journal: journal loaded successfully\n"); return 0; @@ -360,8 +426,7 @@ static int simplefs_load_journal(struct super_block *sb, return err; } -// we use SIMPLEFS_OPT_JOURNAL_PATH case to load external journal device now -/**/ +/* we use SIMPLEFS_OPT_JOURNAL_PATH case to load external journal device now */ #define SIMPLEFS_OPT_JOURNAL_DEV 1 #define SIMPLEFS_OPT_JOURNAL_PATH 2 static const match_table_t tokens = { @@ -376,15 +441,13 @@ static int simplefs_parse_options(struct super_block *sb, char *options) pr_info("simplefs_parse_options: parsing options '%s'\n", options); - while ((p = strsep(&options, ",")) != NULL) { + while ((p = strsep(&options, ","))) { if (!*p) continue; args[0].to = args[0].from = NULL; token = match_token(p, tokens, args); - pr_info("simplefs_parse_options: token '%s' parsed as %d\n", p, token); - switch (token) { case SIMPLEFS_OPT_JOURNAL_DEV: if (args->from && match_int(args, &arg)) { @@ -406,8 +469,6 @@ static int simplefs_parse_options(struct super_block *sb, char *options) struct inode *journal_inode; struct path path; - pr_info("simplefs_parse_options: parsing journal path\n"); - journal_path = match_strdup(&args[0]); if (!journal_path) { pr_err("simplefs_parse_options: match_strdup failed\n"); @@ -443,7 +504,6 @@ static int simplefs_parse_options(struct super_block *sb, char *options) } } - pr_info("simplefs_parse_options: options parsed successfully\n"); return 0; } static struct super_operations simplefs_super_ops = {