diff --git a/kmod/src/block.c b/kmod/src/block.c index e6ddf0a8..4f82eae9 100644 --- a/kmod/src/block.c +++ b/kmod/src/block.c @@ -280,8 +280,6 @@ static void block_write_end_io(struct buffer_head *bh, int uptodate) * be written again in the next transaction commit. * * Reads can traverse the blocks while they're in flight. - * - * The number of blocks written is returned, or -errno on error. */ int scoutfs_block_write_dirty(struct super_block *sb) { @@ -290,13 +288,11 @@ int scoutfs_block_write_dirty(struct super_block *sb) struct rb_node *node; struct blk_plug plug; unsigned long flags; - int count; - int err; + int ret; atomic_set(&sbi->block_writes, 1); sbi->block_write_err = 0; - count = 0; - err = 0; + ret = 0; blk_start_plug(&plug); @@ -308,7 +304,6 @@ int scoutfs_block_write_dirty(struct super_block *sb) spin_unlock_irqrestore(&sbi->block_lock, flags); atomic_inc(&sbi->block_writes); - count++; scoutfs_block_set_crc(bh); /* @@ -321,10 +316,10 @@ int scoutfs_block_write_dirty(struct super_block *sb) lock_buffer(bh); bh->b_end_io = block_write_end_io; - err = submit_bh(WRITE, bh); /* doesn't actually fail? */ + ret = submit_bh(WRITE, bh); /* doesn't actually fail? */ spin_lock_irqsave(&sbi->block_lock, flags); - if (err) + if (ret) break; } spin_unlock_irqrestore(&sbi->block_lock, flags); @@ -335,10 +330,18 @@ int scoutfs_block_write_dirty(struct super_block *sb) atomic_dec(&sbi->block_writes); wait_event(sbi->block_wq, atomic_read(&sbi->block_writes) == 0); - trace_printk("err %d sbi err %d count %d\n", - err, sbi->block_write_err, count); + trace_printk("ret %d\n", ret); + return ret; +} - return err ?: sbi->block_write_err ?: count; +/* + * The caller knows that it's not racing with writers. + */ +int scoutfs_block_has_dirty(struct super_block *sb) +{ + struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb); + + return !RB_EMPTY_ROOT(&sbi->block_dirty_tree); } /* diff --git a/kmod/src/block.h b/kmod/src/block.h index 8bb2a136..ee38b677 100644 --- a/kmod/src/block.h +++ b/kmod/src/block.h @@ -13,6 +13,7 @@ struct buffer_head *scoutfs_block_dirty_alloc(struct super_block *sb); struct buffer_head *scoutfs_block_dirty_ref(struct super_block *sb, struct scoutfs_block_ref *ref); +int scoutfs_block_has_dirty(struct super_block *sb); int scoutfs_block_write_dirty(struct super_block *sb); void scoutfs_block_set_crc(struct buffer_head *bh); diff --git a/kmod/src/filerw.c b/kmod/src/filerw.c index df946ee3..7ef5ea2a 100644 --- a/kmod/src/filerw.c +++ b/kmod/src/filerw.c @@ -13,8 +13,11 @@ #include #include #include +#include +#include #include "format.h" +#include "super.h" #include "inode.h" #include "key.h" #include "filerw.h" @@ -24,173 +27,512 @@ #include "ioctl.h" /* - * File data is stored in items just like everything else. This is very - * easy to implement but incurs a copying overhead. We'll see how - * expensive that gets. + * scoutfs uses simple fixed size block mapping items to map aligned + * groups of logical file data blocks to physical block locations. * - * By making the max item size a bit less than the block size we can - * still have room for the block header which gets us file data - * checksums. File item key offsets are multiples of this max block - * size though items can be smaller if the data is sparse. This lets us - * do lookups for specific keys and take advantage of the bloom filters. + * The small block size is set to the smallest supported page size. + * This means that our file IO code never has to worry about the + * situation where a page write is smaller than the block size. We + * never have to perform RMW of blocks larger than pages, nor do we have + * to punch a whole and worry about block tracking items that could be + * sharing references to a block on either side of a smaller dirty page. + * We can simply use the kernel's buffer head code, loathed though it + * is, and have a 1:1 relationship between block writes and block + * mapping item entries. * - * This is a minimal first pass and will need more work. It'll need to - * worry about enospc in writepage and cluster access for a start. + * Dirty blocks are only written to free space. The first time a block + * hits write_page in a transaction it gets a newly allocated block. We + * get decent contiguous allocations by having per-task preallocation + * streams. These are trimmed back as the transaction is committed. We + * don't bother worrying about small transactions. + * + * Because we only write to allocated space we can't naively use the + * buffer head get_blocks support functions. They assume that they can + * write dirty buffers to existing clean mappings which is absolutely + * not true for us. We clear mappings for clean pages before we call + * block_write_begin() so that it won't write to blocks that were caned + * from previous reads. We make sure that the page is uptodate ourself + * so that it won't use readpage to read the existing block and then + * turn around and write to it. + * + * Data blocks aren't pinned for the duration of the transaction. They + * can be written out and read back in and redirtied during the lifetime + * of a transaction. As we map dirty pages we see if its current allocation + * is newly allocated in the transaction and can reuse it. + * + * XXX + * - need to wire up dirty inode? + * - enforce writing to free blknos + * - per-task allocation regions + * - tear down dirty blocks left by write errors on unmount + * - should invalidate dirty blocks if freed + * - data block checksumming (stable pages) + * - mmap creating dirty unmapped pages at writepage + * - pack small tails into inline items + * - direct IO */ -/* -* Track the intersection of the logical region of a file with a page -* and file data item. -*/ -struct data_region { - u64 item_key; - unsigned int page_off; - unsigned short len; - unsigned short item_off; -}; /* - * Map the file offset to its intersection with the page and item region. - * Returns false if the byte position is outside the page. -*/ -static bool map_data_region(struct data_region *dr, u64 pos, struct page *page) -{ - if (pos >> PAGE_SHIFT != page->index) - return false; - - dr->page_off = pos & ~PAGE_MASK; - - dr->item_off = do_div(pos, SCOUTFS_MAX_ITEM_LEN); - dr->item_key = pos; - - dr->len = min_t(int, SCOUTFS_MAX_ITEM_LEN - dr->item_off, - PAGE_SIZE - dr->page_off); - - return true; -} - -#define for_each_data_region(dr, page, pos) \ - for (pos = (u64)page->index << PAGE_SHIFT; \ - map_data_region(dr, pos, page); pos += (dr)->len) - -/* - * Copy the contents of file data items into the page. If we don't - * find an item then we zero that region of the page. + * trace_printk() doesn't support %c? * - * XXX i_size? - * XXX async? + * 1 - 1ocked + * a - uptodAte + * d - Dirty + * b - writeBack + * e - Error */ -static int scoutfs_readpage(struct file *file, struct page *page) +#define page_hexflag(page, name, val, shift) \ + (Page##name(page) ? (val << (shift * 4)) : 0) + +#define page_hexflags(page) \ + (page_hexflag(page, Locked, 0x1, 4) | \ + page_hexflag(page, Uptodate, 0xa, 3) | \ + page_hexflag(page, Dirty, 0xd, 2) | \ + page_hexflag(page, Writeback, 0xb, 1) | \ + page_hexflag(page, Error, 0xe, 0)) + +#define PGF "page %p [index %lu flags %x]" +#define PGA(page) \ + (page), (page)->index, page_hexflags(page) \ + +#define BHF "bh %p [blocknr %llu size %zu state %lx]" +#define BHA(bh) \ + (bh), (u64)(bh)->b_blocknr, (bh)->b_size, (bh)->b_state \ + +/* + * For now this is super cheesy. We just have one allocation on the + * super that is consumed as buffered writes make their way through unmapped + * buffer heads and alloc in get_block. + */ +static int alloc_file_block(struct super_block *sb, u64 *blkno) { - struct inode *inode = file->f_mapping->host; - DECLARE_SCOUTFS_BTREE_CURSOR(curs); - struct super_block *sb = inode->i_sb; - struct scoutfs_key key; - struct data_region dr; - int ret = 0; - void *addr; - u64 pos; + struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb); + u64 alloc_blkno; + int order = 0; + int ret; - for_each_data_region(&dr, page, pos) { - scoutfs_set_key(&key, scoutfs_ino(inode), SCOUTFS_DATA_KEY, - dr.item_key); + *blkno = 0; - scoutfs_btree_release(&curs); - ret = scoutfs_btree_lookup(sb, &key, &curs); - if (ret == -ENOENT) { - addr = kmap_atomic(page); - memset(addr + dr.page_off, 0, dr.len); - kunmap_atomic(addr); - continue; + spin_lock(&sbi->file_alloc_lock); + + if (sbi->file_alloc_count == 0) { + spin_unlock(&sbi->file_alloc_lock); + + order = scoutfs_buddy_alloc(sb, &alloc_blkno, + SCOUTFS_BUDDY_ORDERS - 1); + if (order < 0) { + ret = order; + goto out; } - if (ret) - break; - addr = kmap_atomic(page); - memcpy(addr + dr.page_off, curs.val + dr.item_off, dr.len); - kunmap_atomic(addr); + spin_lock(&sbi->file_alloc_lock); + + if (sbi->file_alloc_count == 0) { + sbi->file_alloc_blkno = alloc_blkno; + sbi->file_alloc_count = 1 << order; + order = -1; + } } - scoutfs_btree_release(&curs); + if (sbi->file_alloc_count) { + *blkno = sbi->file_alloc_blkno; + sbi->file_alloc_blkno++; + sbi->file_alloc_count--; + ret = 0; + } else { + ret = -ENOSPC; + } - if (!ret) - SetPageUptodate(page); - unlock_page(page); + spin_unlock(&sbi->file_alloc_lock); + + if (order > 0) + scoutfs_buddy_free(sb, alloc_blkno, order); + +out: + trace_printk("allocated blkno %llu ret %d\n", *blkno, ret); return ret; } /* - * Copy the contents of the page into file items. Data integrity syncs - * will later write the dirty segment to the device. - * -* XXX zeroing regions of data items? -* XXX wbc counters? -* XXX reserve space so dirty item doesn't get enospc -- our "delalloc"? -*/ -static int scoutfs_writepage(struct page *page, struct writeback_control *wbc) + * The caller didn't need an allocated file block after all. We return + * it to the pool. This has to succeed because it's called after we've + * done things that would be annoying to revert. + */ +static void return_file_block(struct super_block *sb, u64 blkno) +{ + struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb); + + spin_lock(&sbi->file_alloc_lock); + + BUG_ON(sbi->file_alloc_count && + sbi->file_alloc_blkno != (blkno + 1)); + + if (sbi->file_alloc_count == 0) + sbi->file_alloc_blkno = blkno + 1; + + sbi->file_alloc_blkno--; + sbi->file_alloc_count++; + + spin_unlock(&sbi->file_alloc_lock); +} + +/* + * The caller ensures that this is serialized against all other callers + * and writers. + */ +void scoutfs_filerw_free_alloc(struct super_block *sb) +{ + struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb); + + trace_printk("blkno %llu count %llu\n", sbi->file_alloc_blkno, + sbi->file_alloc_count); + + if (sbi->file_alloc_count) + scoutfs_buddy_free_extent(sb, sbi->file_alloc_blkno, + sbi->file_alloc_count); + + sbi->file_alloc_blkno = 0; + sbi->file_alloc_count = 0; +} + +static void set_bmap_key(struct scoutfs_key *key, struct inode *inode, + u64 iblock) +{ + scoutfs_set_key(key, scoutfs_ino(inode), SCOUTFS_BMAP_KEY, + iblock >> SCOUTFS_BLOCK_MAP_SHIFT); +} + +/* + * Return the number of contiguously mapped blocks starting from the + * given logical block in the inode. We only return the number + * contained in one block map item. We walk through more items if it + * makes a difference. + */ +static int contig_mapped_blocks(struct inode *inode, u64 iblock, u64 *blkno) { - struct inode *inode = page->mapping->host; - DECLARE_SCOUTFS_BTREE_CURSOR(curs); struct super_block *sb = inode->i_sb; + DECLARE_SCOUTFS_BTREE_CURSOR(curs); + struct scoutfs_block_map *bmap; struct scoutfs_key key; - struct data_region dr; - void *addr; - u64 pos; + int ret; + int i; + + *blkno = 0; + + set_bmap_key(&key, inode, iblock); + ret = scoutfs_btree_lookup(sb, &key, &curs); + if (!ret) { + bmap = curs.val; + + i = iblock & SCOUTFS_BLOCK_MAP_MASK; + *blkno = le64_to_cpu(bmap->blkno[i]); + + while (i < SCOUTFS_BLOCK_MAP_COUNT && bmap->blkno[i]) { + ret++; + i++; + } + scoutfs_btree_release(&curs); + } else if (ret == -ENOENT) { + ret = 0; + } + + trace_printk("ino %llu iblock %llu blkno %llu ret %d\n", + scoutfs_ino(inode), iblock, *blkno, ret); + + return ret; +} + +/* + * Make sure that the mapped block at the given logical block number is + * writable in this transaction. If it's not we allocate and reference + * a new block. If there was a previous stable block we free it. We + * give the caller the writable block number. + * + * Writeback is allowed during a transaction so we can get here with + * buffer heads that are newly allocated and being written to but for + * blocks that were allocated in the current transacation. In that + * case we re-use the existing mapping. None of it will be stable until + * there's a sync that writes all the referencing metadata. + */ +static int map_writable_block(struct inode *inode, u64 iblock, u64 *blkno_ret) +{ + struct super_block *sb = inode->i_sb; + DECLARE_SCOUTFS_BTREE_CURSOR(curs); + struct scoutfs_block_map *bmap; + struct scoutfs_key key; + bool inserted = false; + u64 old_blkno = 0; + u64 new_blkno = 0; + int ret; + int err; + int i; + + set_bmap_key(&key, inode, iblock); + + /* we always need a writable block map item */ + ret = scoutfs_btree_update(sb, &key, &curs); + if (ret < 0 && ret != -ENOENT) + goto out; + + /* might need to create a new item and delete it after errors */ + if (ret == -ENOENT) { + ret = scoutfs_btree_insert(sb, &key, sizeof(*bmap), &curs); + if (ret < 0) + goto out; + memset(curs.val, 0, sizeof(*bmap)); + inserted = true; + } + + bmap = curs.val; + i = iblock & SCOUTFS_BLOCK_MAP_MASK; + old_blkno = le64_to_cpu(bmap->blkno[i]); + + /* + * If the existing block was free in stable then its dirty in + * this trans and we can use it. + */ + if (old_blkno) { + ret = scoutfs_buddy_was_free(sb, old_blkno, 0); + if (ret < 0) + goto out; + if (ret > 0) { + *blkno_ret = old_blkno; + ret = 0; + goto out; + } + } + + ret = alloc_file_block(sb, &new_blkno); + if (ret < 0) + goto out; + + if (old_blkno) { + ret = scoutfs_buddy_free(sb, old_blkno, 0); + if (ret) + goto out; + } + + bmap->blkno[i] = cpu_to_le64(new_blkno); + *blkno_ret = new_blkno; + new_blkno = 0; + ret = 0; +out: + scoutfs_btree_release(&curs); + if (ret) { + if (new_blkno) + return_file_block(sb, new_blkno); + if (inserted) { + err = scoutfs_btree_delete(sb, &key); + BUG_ON(err); /* always succeeds */ + } + } + + return ret; +} + +static int scoutfs_readpage_get_block(struct inode *inode, sector_t iblock, + struct buffer_head *bh, int create) +{ + u64 blkno; int ret; - set_page_writeback(page); + if (WARN_ON_ONCE(create)) + return -EINVAL; + + ret = contig_mapped_blocks(inode, iblock, &blkno); + if (ret > 0) { + map_bh(bh, inode->i_sb, blkno); + bh->b_size = min_t(int, bh->b_size, ret << inode->i_blkbits); + ret = 0; + } + + trace_printk("ino %llu iblock %llu create %d "BHF"\n", + scoutfs_ino(inode), (u64)iblock, create, BHA(bh)); + + return ret; +} + +static int scoutfs_readpage(struct file *file, struct page *page) +{ + trace_printk(PGF"\n", PGA(page)); + + return mpage_readpage(page, scoutfs_readpage_get_block); +} + +static int scoutfs_readpages(struct file *file, struct address_space *mapping, + struct list_head *pages, unsigned nr_pages) +{ + return mpage_readpages(mapping, pages, nr_pages, + scoutfs_readpage_get_block); +} + +/* + * For now we don't know what to do if unmapped blocks make it to + * writepage (mmap?). + */ +static int scoutfs_writepage_get_block(struct inode *inode, sector_t iblock, + struct buffer_head *bh, int create) +{ + trace_printk("ino %llu iblock %llu create %d "BHF"\n", + scoutfs_ino(inode), (u64)iblock, create, BHA(bh)); + + return WARN_ON_ONCE(-EINVAL); +} + +/* + * Dirty file blocks can be written to their newly allocated free blocks + * at any time. They won't be referenced by metadata until the current + * transaction is committed. They can be re-read and re-dirtied at + * their free block number in this transaction. + */ +static int scoutfs_writepage(struct page *page, struct writeback_control *wbc) +{ + trace_printk(PGF"\n", PGA(page)); + + return block_write_full_page(page, scoutfs_writepage_get_block, wbc); +} + +static int scoutfs_writepages(struct address_space *mapping, + struct writeback_control *wbc) +{ + trace_printk("mapping %p\n", mapping); + + return mpage_writepages(mapping, wbc, scoutfs_writepage_get_block); +} + +/* + * Block allocation during buffered writes needs to make sure that the + * dirty block will be written to free space. + */ +static int scoutfs_write_begin_get_block(struct inode *inode, sector_t iblock, + struct buffer_head *bh, int create) +{ + u64 blkno = 0; + int ret; + + if (WARN_ON_ONCE(!create)) + return -EINVAL; + + ret = map_writable_block(inode, iblock, &blkno); + if (ret == 0) { + map_bh(bh, inode->i_sb, blkno); + bh->b_size = SCOUTFS_BLOCK_SIZE; + ret = 0; + } + + trace_printk("ino %llu iblock %llu create %d ret %d "BHF"\n", + scoutfs_ino(inode), (u64)iblock, create, ret, BHA(bh)); + return ret; +} + +/* XXX could make a for_each wrapper if we get a few of these */ +static inline void clear_mapped_page_buffers(struct page *page) +{ + struct buffer_head *head; + struct buffer_head *bh; + + if (!page_has_buffers(page)) + return; + + head = page_buffers(page); + bh = head; + do { + if (buffer_mapped(bh)) { + trace_printk(BHF"\n", BHA(bh)); + clear_buffer_mapped(bh); + } + + bh = bh->b_this_page; + } while (bh != head); +} + +/* + * Dirty blocks have to be mapped to be written out to free space so + * that we don't overwrite live data. We're relying on + * block_write_begin() to call get_block(). There are two problems with + * this. + * + * First, if it's going to be trying to read a partial block before writing + * then we can't give it the location to read. It'll just mark the + * block dirty and write to that same location. We use readpage to make + * the page uptodate if it's going to be satisfying a partial overwrite. + * + * Second, we can't let it use mappings that were used by readpage to + * read the current stable data. We need to have get_block be called + * for existing clean uptodate pages so that we can reallocate them to + * free space. We do this by clearing the buffer mappings for every buffer + * on the page for every call. This is probably unnecessarily expensive + * because we don't need to do it for clean buffers. That optimization + * would need to be done very carefully. + */ +static int scoutfs_write_begin(struct file *file, + struct address_space *mapping, loff_t pos, + unsigned len, unsigned flags, + struct page **pagep, void **fsdata) +{ + struct inode *inode = mapping->host; + struct super_block *sb = inode->i_sb; + pgoff_t index = pos >> PAGE_SHIFT; + struct page *page; + int ret; + +retry: + page = grab_cache_page_write_begin(mapping, index, flags); + if (!page) + return -ENOMEM; + + trace_printk(PGF" pos %llu len %u\n", PGA(page), (u64)pos, len); + + /* + * read in the page if we're going to be dirtying part of the + * page. readpage catches when this is a read past i_size or + * from a hole and zeros the buffer. + */ + if (!PageUptodate(page) && !IS_ALIGNED(pos | len, SCOUTFS_BLOCK_SIZE)) { + ClearPageError(page); + ret = scoutfs_readpage(NULL, page); + if (ret) { + page_cache_release(page); + goto out; + } + + wait_on_page_locked(page); + if (!PageUptodate(page)) { + page_cache_release(page); + ret = -EIO; + goto out; + } + + /* let grabbing deal with weird page states */ + page_cache_release(page); + goto retry; + } ret = scoutfs_hold_trans(sb); if (ret) goto out; - for_each_data_region(&dr, page, pos) { - scoutfs_set_key(&key, scoutfs_ino(inode), SCOUTFS_DATA_KEY, - dr.item_key); + /* can't re-enter fs, have trans */ + flags |= AOP_FLAG_NOFS; - /* XXX dirty */ - scoutfs_btree_release(&curs); - ret = scoutfs_btree_insert(sb, &key, SCOUTFS_MAX_ITEM_LEN, - &curs); - if (ret) - break; + /* make sure our get_block gets a chance to alloc */ + clear_mapped_page_buffers(page); - addr = kmap_atomic(page); - memcpy(curs.val + dr.item_off, addr + dr.page_off, dr.len); - kunmap_atomic(addr); - - } - - scoutfs_btree_release(&curs); - scoutfs_release_trans(sb); + ret = __block_write_begin(page, pos, len, + scoutfs_write_begin_get_block); out: - if (ret) { - SetPageError(page); - mapping_set_error(&inode->i_data, ret); - } + trace_printk(PGF" pos %llu len %u ret %d\n", + PGA(page), (u64)pos, len, ret); + if (ret < 0) { + /* XXX handle truncating? */ + unlock_page(page); + put_page(page); + page = NULL; + } - end_page_writeback(page); - unlock_page(page); - - return ret; -} - -static int scoutfs_write_begin(struct file *file, struct address_space *mapping, - loff_t pos, unsigned len, unsigned flags, - struct page **pagep, void **fsdata) -{ - struct inode *inode = mapping->host; - pgoff_t index = pos >> PAGE_CACHE_SHIFT; - struct page *page; - - trace_scoutfs_write_begin(scoutfs_ino(inode), pos, len); - - page = grab_cache_page_write_begin(mapping, index, flags); - if (!page) - return -ENOMEM; - - *pagep = page; - return 0; + *pagep = page; + return ret; } static int scoutfs_write_end(struct file *file, struct address_space *mapping, @@ -199,45 +541,21 @@ static int scoutfs_write_end(struct file *file, struct address_space *mapping, { struct inode *inode = mapping->host; struct super_block *sb = inode->i_sb; - unsigned off; + int ret; - trace_scoutfs_write_end(scoutfs_ino(inode), pos, len, copied); + trace_printk("ino %llu "PGF" pos %llu len %u copied %d\n", + scoutfs_ino(inode), PGA(page), (u64)pos, len, copied); - off = pos & (PAGE_CACHE_SIZE - 1); - - /* zero the stale part of the page if we did a short copy */ - if (copied < len) - zero_user_segment(page, off + copied, len); - - if (pos + copied > inode->i_size) { - i_size_write(inode, pos + copied); - - /* - * XXX This is a crazy hack that will go away when the - * file data paths are more robust. We're barely - * holding them together with duct tape while building - * up the robust metadata support that's needed to do a - * good job with the data pats. - */ - if (!scoutfs_hold_trans(sb)) { - if (!scoutfs_dirty_inode_item(inode)) - scoutfs_update_inode_item(inode); - scoutfs_release_trans(sb); - } - } - - if (!PageUptodate(page)) - SetPageUptodate(page); - set_page_dirty(page); - unlock_page(page); - page_cache_release(page); - - return copied; + ret = generic_write_end(file, mapping, pos, len, copied, page, fsdata); + scoutfs_release_trans(sb); + return ret; } const struct address_space_operations scoutfs_file_aops = { .readpage = scoutfs_readpage, + .readpages = scoutfs_readpages, .writepage = scoutfs_writepage, + .writepages = scoutfs_writepages, .write_begin = scoutfs_write_begin, .write_end = scoutfs_write_end, }; diff --git a/kmod/src/filerw.h b/kmod/src/filerw.h index 2d9d478e..c182349d 100644 --- a/kmod/src/filerw.h +++ b/kmod/src/filerw.h @@ -4,4 +4,6 @@ extern const struct address_space_operations scoutfs_file_aops; extern const struct file_operations scoutfs_file_fops; +void scoutfs_filerw_free_alloc(struct super_block *sb); + #endif diff --git a/kmod/src/format.h b/kmod/src/format.h index 2a3605c4..fbd09ddd 100644 --- a/kmod/src/format.h +++ b/kmod/src/format.h @@ -100,7 +100,7 @@ struct scoutfs_key { #define SCOUTFS_INODE_KEY 1 #define SCOUTFS_XATTR_KEY 2 #define SCOUTFS_DIRENT_KEY 3 -#define SCOUTFS_DATA_KEY 4 +#define SCOUTFS_BMAP_KEY 4 #define SCOUTFS_MAX_ITEM_LEN 512 @@ -244,4 +244,22 @@ struct scoutfs_xattr { __u8 name[0]; } __packed; +/* + * We use simple block map items to map a aligned fixed group of logical + * block offsets to physical blocks. We make them a decent size to + * reduce the item storage overhead per block referenced, but we don't + * want them so large that they start to take up an extraordinary amount + * of space for small files. 8 block items ranges from around 3% to .3% + * overhead for files that use only one or all of the blocks in the + * mapping item. + */ +#define SCOUTFS_BLOCK_MAP_SHIFT 3 +#define SCOUTFS_BLOCK_MAP_COUNT (1 << SCOUTFS_BLOCK_MAP_SHIFT) +#define SCOUTFS_BLOCK_MAP_MASK (SCOUTFS_BLOCK_MAP_COUNT - 1) + +struct scoutfs_block_map { + __le32 crc[SCOUTFS_BLOCK_MAP_COUNT]; + __le64 blkno[SCOUTFS_BLOCK_MAP_COUNT]; +}; + #endif diff --git a/kmod/src/super.h b/kmod/src/super.h index 7c3617b4..455f397e 100644 --- a/kmod/src/super.h +++ b/kmod/src/super.h @@ -43,6 +43,11 @@ struct scoutfs_sb_info { struct kset *kset; struct scoutfs_counters *counters; + + /* XXX we'd like this to be per task, not per super */ + spinlock_t file_alloc_lock; + u64 file_alloc_blkno; + u64 file_alloc_count; }; static inline struct scoutfs_sb_info *SCOUTFS_SB(struct super_block *sb) diff --git a/kmod/src/trans.c b/kmod/src/trans.c index 3291b83c..9be98ede 100644 --- a/kmod/src/trans.c +++ b/kmod/src/trans.c @@ -15,15 +15,17 @@ #include #include #include +#include #include "super.h" #include "block.h" #include "trans.h" #include "buddy.h" +#include "filerw.h" #include "scoutfs_trace.h" /* - * scoutfs metadata blocks are written in atomic transactions. + * scoutfs blocks are written in atomic transactions. * * Writers hold transactions to dirty blocks. The transaction can't be * written until these active writers release the transaction. We don't @@ -44,11 +46,24 @@ */ /* - * It's critical that this not try to perform IO if there's nothing - * dirty. The sync at unmount can have this work scheduled after sync - * returns and the unmount path starts to tear down supers and block - * devices. We have to safely detect that there's nothing to do using - * nothing in the vfs. + * This work func is responsible for writing out all the dirty blocks + * that make up the current dirty transaction. It prevents writers from + * holding a transaction so it doesn't have to worry about blocks being + * dirtied while it is working. + * + * Any dirty block had to have allocated a new blkno which would have + * created dirty allocator metadata blocks. We can avoid writing + * entirely if we don't have any dirty metadata blocks. This is + * important because we don't try to serialize this work during + * unmount.. we can execute as the vfs is shutting down.. we need to + * decide that nothing is dirty without calling the vfs at all. + * + * We first try to sync the dirty inodes and write their dirty data blocks, + * then we write all our dirty metadata blocks, and only when those succeed + * do we write the new super that references all of these newly written blocks. + * + * If there are write errors then blocks are kept dirty in memory and will + * be written again at the next sync. */ void scoutfs_trans_write_func(struct work_struct *work) { @@ -57,15 +72,26 @@ void scoutfs_trans_write_func(struct work_struct *work) struct super_block *sb = sbi->sb; bool advance = false; int ret = 0; + bool have_umount; wait_event(sbi->trans_hold_wq, atomic_cmpxchg(&sbi->trans_holds, 0, -1) == 0); - /* XXX probably want to write out dirty pages in inodes */ + if (scoutfs_block_has_dirty(sb)) { + /* XXX need writeback errors from inode address spaces? */ - ret = scoutfs_block_write_dirty(sb); - if (ret > 0) { - ret = scoutfs_write_dirty_super(sb); + /* XXX definitely don't understand this */ + have_umount = down_read_trylock(&sb->s_umount); + + sync_inodes_sb(sb); + + if (have_umount) + up_read(&sb->s_umount); + + scoutfs_filerw_free_alloc(sb); + + ret = scoutfs_block_write_dirty(sb) ?: + scoutfs_write_dirty_super(sb); if (!ret) advance = 1; }