diff --git a/f3brew.c b/f3brew.c index 1a040a3..5238d30 100644 --- a/f3brew.c +++ b/f3brew.c @@ -33,6 +33,10 @@ static struct argp_option options[] = { "Wrap parameter of the emulated drive", 0}, {"debug-block-order", 'b', "ORDER", OPTION_HIDDEN, "Block size of the emulated drive is 2^ORDER Bytes", 0}, + {"debug-cache-order", 'c', "ORDER", OPTION_HIDDEN, + "Cache size of the emulated drive is 2^ORDER blocks", 0}, + {"debug-strict-cache", 'o', NULL, OPTION_HIDDEN, + "Force the cache to be strict", 0}, {"debug-keep-file", 'k', NULL, OPTION_HIDDEN, "Don't remove file used for emulating the drive", 0}, {"reset-type", 's', "TYPE", 0, @@ -66,6 +70,8 @@ struct args { uint64_t fake_size_byte; int wrap; int block_order; + int cache_order; + int strict_cache; /* What to do. */ uint64_t first_block; @@ -118,6 +124,20 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state) args->debug = true; break; + case 'c': + ll = arg_to_ll_bytes(state, arg); + if (ll < -1 || ll > 64) + argp_error(state, + "Cache order must be in the interval [-1, 64]"); + args->cache_order = ll; + args->debug = true; + break; + + case 'o': + args->strict_cache = true; + args->debug = true; + break; + case 'k': args->keep_file = true; args->debug = true; @@ -384,6 +404,8 @@ int main(int argc, char **argv) .fake_size_byte = 1ULL << 34, .wrap = 31, .block_order = 0, + .cache_order = -1, + .strict_cache = false, .first_block = 0, .last_block = -1ULL, }; @@ -397,7 +419,7 @@ int main(int argc, char **argv) dev = args.debug ? create_file_device(args.filename, args.real_size_byte, args.fake_size_byte, args.wrap, args.block_order, - args.keep_file) + args.cache_order, args.strict_cache, args.keep_file) : create_block_device(args.filename, args.reset_type); if (!dev) { fprintf(stderr, "\nApplication cannot continue, finishing...\n"); diff --git a/f3probe.c b/f3probe.c index b3aa776..3a4a0ed 100644 --- a/f3probe.c +++ b/f3probe.c @@ -35,6 +35,10 @@ static struct argp_option options[] = { "Wrap parameter of the emulated drive", 0}, {"debug-block-order", 'b', "ORDER", OPTION_HIDDEN, "Block size of the emulated drive is 2^ORDER Bytes", 0}, + {"debug-cache-order", 'c', "ORDER", OPTION_HIDDEN, + "Cache size of the emulated drive is 2^ORDER blocks", 0}, + {"debug-strict-cache", 'o', NULL, OPTION_HIDDEN, + "Force the cache to be strict", 0}, {"debug-keep-file", 'k', NULL, OPTION_HIDDEN, "Don't remove file used for emulating the drive", 0}, {"debug-unit-test", 'u', NULL, OPTION_HIDDEN, @@ -70,6 +74,8 @@ struct args { uint64_t fake_size_byte; int wrap; int block_order; + int cache_order; + int strict_cache; }; static error_t parse_opt(int key, char *arg, struct argp_state *state) @@ -118,6 +124,20 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state) args->debug = true; break; + case 'c': + ll = arg_to_ll_bytes(state, arg); + if (ll < -1 || ll > 64) + argp_error(state, + "Cache order must be in the interval [-1, 64]"); + args->cache_order = ll; + args->debug = true; + break; + + case 'o': + args->strict_cache = true; + args->debug = true; + break; + case 'k': args->keep_file = true; args->debug = true; @@ -184,29 +204,43 @@ struct unit_test_item { uint64_t fake_size_byte; int wrap; int block_order; + int cache_order; + int strict_cache; }; static const struct unit_test_item ftype_to_params[] = { /* Smallest good drive. */ - {1ULL << 20, 1ULL << 20, 20, 9}, + {1ULL << 21, 1ULL << 21, 21, 9, -1, false}, /* Good, 4KB-block, 1GB drive. */ - {1ULL << 30, 1ULL << 30, 30, 12}, + {1ULL << 30, 1ULL << 30, 30, 12, -1, false}, /* Bad drive. */ - {0, 1ULL << 30, 30, 9}, + {0, 1ULL << 30, 30, 9, -1, false}, /* Geometry of a real limbo drive. */ - {1777645568ULL, 32505331712ULL, 35, 9}, + {1777645568ULL, 32505331712ULL, 35, 9, -1, false}, /* Wraparound drive. */ - {1ULL << 31, 1ULL << 34, 31, 9}, + {1ULL << 31, 1ULL << 34, 31, 9, -1, false}, /* Chain drive. */ - {1ULL << 31, 1ULL << 34, 32, 9}, + {1ULL << 31, 1ULL << 34, 32, 9, -1, false}, /* Extreme case for memory usage (limbo drive). */ - {1ULL << 20, 1ULL << 40, 40, 9}, + {(1ULL<<20)+512,1ULL << 40, 40, 9, -1, false}, + + /* Geomerty of a real limbo drive with 256MB of strict cache. */ + {7600799744ULL, 67108864000ULL, 36, 9, 19, true}, + + /* The drive before with a non-strict cache. */ + {7600799744ULL, 67108864000ULL, 36, 9, 19, false}, + + /* The devil drive I. */ + {0, 1ULL << 40, 40, 9, 21, true}, + + /* The devil drive II. */ + {0, 1ULL << 40, 40, 9, 21, false}, }; #define UNIT_TEST_N_CASES \ @@ -232,7 +266,7 @@ static int unit_test(const char *filename) dev = create_file_device(filename, item->real_size_byte, item->fake_size_byte, item->wrap, item->block_order, - false); + item->cache_order, item->strict_cache, false); assert(dev); max_probe_blocks = probe_device_max_blocks(dev); assert(!probe_device(dev, &real_size_byte, &announced_size_byte, @@ -314,7 +348,7 @@ static int test_device(struct args *args) dev = args->debug ? create_file_device(args->filename, args->real_size_byte, args->fake_size_byte, args->wrap, args->block_order, - args->keep_file) + args->cache_order, args->strict_cache, args->keep_file) : create_block_device(args->filename, args->reset_type); if (!dev) { fprintf(stderr, "\nApplication cannot continue, finishing...\n"); @@ -448,6 +482,8 @@ int main(int argc, char **argv) .fake_size_byte = 1ULL << 34, .wrap = 31, .block_order = 0, + .cache_order = -1, + .strict_cache = false, }; /* Read parameters. */ diff --git a/libdevs.c b/libdevs.c index 07f5a38..4f0b055 100644 --- a/libdevs.c +++ b/libdevs.c @@ -150,10 +150,13 @@ struct file_device { /* This must be the first field. See dev_fdev() for details. */ struct device dev; - const char *filename; - int fd; - uint64_t real_size_byte; - uint64_t address_mask; + const char *filename; + int fd; + uint64_t real_size_byte; + uint64_t address_mask; + uint64_t cache_mask; + uint64_t *cache_entries; + char *cache_blocks; }; static inline struct file_device *dev_fdev(struct device *dev) @@ -170,7 +173,22 @@ static int fdev_read_block(struct device *dev, char *buf, int length, offset &= fdev->address_mask; if (offset >= fdev->real_size_byte) { - memset(buf, 0, length); + int block_order; + uint64_t cache_pos; + + if (!fdev->cache_blocks) + goto no_block; /* No cache available. */ + + block_order = dev_get_block_order(&fdev->dev); + cache_pos = (offset >> block_order) & fdev->cache_mask; + assert(length == (1LL << block_order)); + + if (fdev->cache_entries && + fdev->cache_entries[cache_pos] != offset) + goto no_block; + + memmove(buf, &fdev->cache_blocks[cache_pos << block_order], + length); return 0; } @@ -193,6 +211,10 @@ static int fdev_read_block(struct device *dev, char *buf, int length, } while (done < length); return 0; + +no_block: + memset(buf, 0, length); + return 0; } static int write_all(int fd, const char *buf, int count) @@ -216,8 +238,24 @@ static int fdev_write_block(struct device *dev, const char *buf, int length, off_t off_ret; offset &= fdev->address_mask; - if (offset >= fdev->real_size_byte) + if (offset >= fdev->real_size_byte) { + /* Block beyond real memory. */ + int block_order; + uint64_t cache_pos; + + if (!fdev->cache_blocks) + return 0; /* No cache available. */ + block_order = dev_get_block_order(&fdev->dev); + cache_pos = (offset >> block_order) & fdev->cache_mask; + assert(length == (1LL << block_order)); + memmove(&fdev->cache_blocks[cache_pos << block_order], + buf, length); + + if (fdev->cache_entries) + fdev->cache_entries[cache_pos] = offset; + return 0; + } off_ret = lseek(fdev->fd, offset, SEEK_SET); if (off_ret < 0) @@ -230,6 +268,8 @@ static int fdev_write_block(struct device *dev, const char *buf, int length, static void fdev_free(struct device *dev) { struct file_device *fdev = dev_fdev(dev); + free(fdev->cache_blocks); + free(fdev->cache_entries); free((void *)fdev->filename); assert(!close(fdev->fd)); } @@ -241,7 +281,8 @@ static const char *fdev_get_filename(struct device *dev) struct device *create_file_device(const char *filename, uint64_t real_size_byte, uint64_t fake_size_byte, int wrap, - int block_order, int keep_file) + int block_order, int cache_order, int strict_cache, + int keep_file) { struct file_device *fdev; @@ -253,10 +294,29 @@ struct device *create_file_device(const char *filename, if (!fdev->filename) goto fdev; + fdev->cache_mask = 0; + fdev->cache_entries = NULL; + fdev->cache_blocks = NULL; + if (cache_order >= 0) { + fdev->cache_mask = (((uint64_t)1) << cache_order) - 1; + if (strict_cache) { + size_t size = sizeof(*fdev->cache_entries) << + cache_order; + fdev->cache_entries = malloc(size); + if (!fdev->cache_entries) + goto cache; + memset(fdev->cache_entries, 0, size); + } + fdev->cache_blocks = malloc(((uint64_t)1) << + (cache_order + block_order)); + if (!fdev->cache_blocks) + goto cache; + } + fdev->fd = open(filename, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); if (fdev->fd < 0) { err(errno, "Can't create file `%s'", filename); - goto filename; + goto cache; } if (!keep_file) { /* Unlinking the file now guarantees that it won't exist if @@ -294,7 +354,10 @@ keep_file: if (keep_file) unlink(filename); assert(!close(fdev->fd)); -filename: +cache: + free(fdev->cache_blocks); + free(fdev->cache_entries); +/* filename: this label is not being used. */ free((void *)fdev->filename); fdev: free(fdev); diff --git a/libdevs.h b/libdevs.h index a7796da..324d284 100644 --- a/libdevs.h +++ b/libdevs.h @@ -68,7 +68,8 @@ static inline int dev_write_and_reset(struct device *dev, const char *buf, struct device *create_file_device(const char *filename, uint64_t real_size_byte, uint64_t fake_size_byte, int wrap, - int block_order, int keep_file); + int block_order, int cache_order, int strict_cache, + int keep_file); /* XXX Add support for block devices backed by SCSI and ATA. */ enum reset_type {