Commit 9f027a6c authored by Lennart Weller's avatar Lennart Weller

New upstream version 1.5.0

parent 218e190c
Version Next
Version 1.5.0 released June 9, 2018
- Add support for recovering dirty blocks in the disk cache (issue #87)
- Replaced boolean 'mounted' flag with a unique 32-bit mount token (issue #87)
- Wait for min_write_delay before access after write error (issue #76)
- Configure TCP keep-alive on HTTP connections (issue #78)
- Added support for server side encryption (pull #81)
Version 1.4.4 released February 1, 2017
......
......@@ -182,7 +182,7 @@ struct cbinfo {
/* s3backer_store functions */
static int block_cache_meta_data(struct s3backer_store *s3b, off_t *file_sizep, u_int *block_sizep);
static int block_cache_set_mounted(struct s3backer_store *s3b, int *old_valuep, int new_value);
static int block_cache_set_mount_token(struct s3backer_store *s3b, int32_t *old_valuep, int32_t new_value);
static int block_cache_read_block(struct s3backer_store *s3b, s3b_block_t block_num, void *dest,
u_char *actual_md5, const u_char *expect_md5, int strict);
static int block_cache_write_block(struct s3backer_store *s3b, s3b_block_t block_num, const void *src, u_char *md5,
......@@ -244,7 +244,7 @@ block_cache_create(struct block_cache_conf *config, struct s3backer_store *inner
goto fail0;
}
s3b->meta_data = block_cache_meta_data;
s3b->set_mounted = block_cache_set_mounted;
s3b->set_mount_token = block_cache_set_mount_token;
s3b->read_block = block_cache_read_block;
s3b->write_block = block_cache_write_block;
s3b->read_block_part = block_cache_read_block_part;
......@@ -290,9 +290,11 @@ block_cache_create(struct block_cache_conf *config, struct s3backer_store *inner
/* Initialize on-disk cache and read in directory */
if (config->cache_file != NULL) {
if ((r = s3b_dcache_open(&priv->dcache, config->log, config->cache_file, config->block_size,
config->cache_size, block_cache_dcache_load, priv)) != 0)
config->cache_size, block_cache_dcache_load, priv, config->perform_flush)) != 0)
goto fail9;
priv->stats.initial_size = priv->num_cleans;
if (config->perform_flush && priv->num_dirties > 0)
(*config->log)(LOG_INFO, "%u dirty blocks in cache file `%s' will be recovered", priv->num_dirties, config->cache_file);
priv->stats.initial_size = priv->num_cleans + priv->num_dirties;
}
/* Grab lock */
......@@ -352,6 +354,7 @@ fail0:
static int
block_cache_dcache_load(void *arg, s3b_block_t dslot, s3b_block_t block_num, const u_char *md5)
{
const u_int dirty = md5 == NULL;
struct block_cache_private *const priv = arg;
struct block_cache_conf *const config = priv->config;
struct cache_entry *entry;
......@@ -359,6 +362,7 @@ block_cache_dcache_load(void *arg, s3b_block_t dslot, s3b_block_t block_num, con
/* Sanity check */
assert(config->cache_file != NULL);
assert(!dirty || config->perform_flush); /* we should never see dirty blocks unless we asked for them */
/* Sanity check a block is not listed twice */
if ((entry = s3b_hash_get(priv->hashtable, block_num)) != NULL) {
......@@ -367,7 +371,7 @@ block_cache_dcache_load(void *arg, s3b_block_t dslot, s3b_block_t block_num, con
return EINVAL;
}
/* Create a new cache entry in state CLEAN[2] */
/* Create a new cache entry */
assert(config->cache_file != NULL);
if ((entry = calloc(1, sizeof(*entry) + (!config->no_verify ? MD5_DIGEST_LENGTH : 0))) == NULL) {
r = errno;
......@@ -376,15 +380,24 @@ block_cache_dcache_load(void *arg, s3b_block_t dslot, s3b_block_t block_num, con
return r;
}
entry->block_num = block_num;
entry->verify = !config->no_verify;
entry->timeout = block_cache_get_time(priv) + priv->clean_timeout;
if (entry->verify)
memcpy(&entry->md5, md5, MD5_DIGEST_LENGTH);
entry->u.dslot = dslot;
TAILQ_INSERT_TAIL(&priv->cleans, entry, link);
priv->num_cleans++;
/* Mark as clean or dirty accordingly */
if (dirty) {
entry->dirty = 1;
TAILQ_INSERT_TAIL(&priv->dirties, entry, link);
priv->num_dirties++;
assert(ENTRY_GET_STATE(entry) == DIRTY);
} else {
entry->verify = !config->no_verify;
if (entry->verify)
memcpy(&entry->md5, md5, MD5_DIGEST_LENGTH);
TAILQ_INSERT_TAIL(&priv->cleans, entry, link);
priv->num_cleans++;
assert(ENTRY_GET_STATE(entry) == (config->no_verify ? CLEAN : CLEAN2));
}
s3b_hash_put_new(priv->hashtable, entry);
assert(ENTRY_GET_STATE(entry) == (config->no_verify ? CLEAN : CLEAN2));
return 0;
}
......@@ -397,11 +410,21 @@ block_cache_meta_data(struct s3backer_store *s3b, off_t *file_sizep, u_int *bloc
}
static int
block_cache_set_mounted(struct s3backer_store *s3b, int *old_valuep, int new_value)
block_cache_set_mount_token(struct s3backer_store *s3b, int32_t *old_valuep, int32_t new_value)
{
struct block_cache_private *const priv = s3b->data;
int r;
/* Set flag in lower layer */
if ((r = (*priv->inner->set_mount_token)(priv->inner, old_valuep, new_value)) != 0)
return r;
return (*priv->inner->set_mounted)(priv->inner, old_valuep, new_value);
/* Update the disk cache file as well, if the value was changed */
if (priv->dcache != NULL && new_value >= 0)
r = s3b_dcache_set_mount_token(priv->dcache, NULL, new_value);
/* Done */
return 0;
}
static int
......@@ -784,10 +807,10 @@ again:
goto again;
}
/* Invalidate disk cache entry */
/* Record dirty disk cache entry */
if (config->cache_file != NULL) {
if ((r = s3b_dcache_erase_block(priv->dcache, entry->u.dslot)) != 0)
(*config->log)(LOG_ERR, "can't erase cached block! %s", strerror(r));
if ((r = s3b_dcache_record_block(priv->dcache, entry->u.dslot, entry->block_num, NULL)) != 0)
(*config->log)(LOG_ERR, "can't dirty cached block %u! %s", block_num, strerror(r));
}
/* Change from CLEAN to DIRTY */
......@@ -855,6 +878,12 @@ again:
priv->num_dirties++;
assert(ENTRY_GET_STATE(entry) == DIRTY);
/* Record dirty disk cache entry */
if (config->cache_file != NULL) {
if ((r = s3b_dcache_record_block(priv->dcache, entry->u.dslot, entry->block_num, NULL)) != 0)
(*config->log)(LOG_ERR, "can't dirty cached block %u! %s", block_num, strerror(r));
}
/* Wake up a worker thread to go write it */
pthread_cond_signal(&priv->worker_work);
......
......@@ -46,6 +46,8 @@ struct block_cache_conf {
u_int read_ahead;
u_int read_ahead_trigger;
u_int no_verify;
u_int recover_dirty_blocks;
u_int perform_flush;
const char *cache_file;
log_func_t *log;
};
......
......@@ -32,7 +32,7 @@
# this exception statement from all source files in the program, then
# also delete it here.
AC_INIT([s3backer FUSE filesystem backed by Amazon S3], [1.4.4], [https://github.com/archiecobbs/s3backer], [s3backer])
AC_INIT([s3backer FUSE filesystem backed by Amazon S3], [1.5.0], [https://github.com/archiecobbs/s3backer], [s3backer])
AC_CONFIG_AUX_DIR(scripts)
AM_INIT_AUTOMAKE(foreign)
dnl AM_MAINTAINER_MODE
......
This diff is collapsed.
......@@ -38,15 +38,19 @@
* Simple on-disk persistent cache.
*/
/* Definitions */
typedef int s3b_dcache_visit_t(void *arg, s3b_block_t dslot, s3b_block_t block_num, const u_char *md5);
/* Declarations */
struct s3b_dcache;
/*
* Startup visitor callback. Each non-empty slot in the disk cache is visited.
*
* The "md5" pointer is NULL for dirty blocks, and not NULL for clean blocks.
*/
typedef int s3b_dcache_visit_t(void *arg, s3b_block_t dslot, s3b_block_t block_num, const u_char *md5);
/* dcache.c */
extern int s3b_dcache_open(struct s3b_dcache **dcachep, log_func_t *log, const char *filename,
u_int block_size, u_int max_blocks, s3b_dcache_visit_t *visitor, void *arg);
u_int block_size, u_int max_blocks, s3b_dcache_visit_t *visitor, void *arg, u_int visit_dirty);
extern void s3b_dcache_close(struct s3b_dcache *dcache);
extern u_int s3b_dcache_size(struct s3b_dcache *dcache);
extern int s3b_dcache_alloc_block(struct s3b_dcache *priv, u_int *dslotp);
......@@ -56,4 +60,6 @@ extern int s3b_dcache_free_block(struct s3b_dcache *dcache, u_int dslot);
extern int s3b_dcache_read_block(struct s3b_dcache *dcache, u_int dslot, void *dest, u_int off, u_int len);
extern int s3b_dcache_write_block(struct s3b_dcache *dcache, u_int dslot, const void *src, u_int off, u_int len);
extern int s3b_dcache_fsync(struct s3b_dcache *dcache);
extern int s3b_dcache_has_mount_token(struct s3b_dcache *priv);
extern int s3b_dcache_set_mount_token(struct s3b_dcache *priv, int32_t *old_valuep, int32_t new_value);
......@@ -130,7 +130,7 @@ struct cbinfo {
/* s3backer_store functions */
static int ec_protect_meta_data(struct s3backer_store *s3b, off_t *file_sizep, u_int *block_sizep);
static int ec_protect_set_mounted(struct s3backer_store *s3b, int *old_valuep, int new_value);
static int ec_protect_set_mount_token(struct s3backer_store *s3b, int32_t *old_valuep, int32_t new_value);
static int ec_protect_read_block(struct s3backer_store *s3b, s3b_block_t block_num, void *dest,
u_char *actual_md5, const u_char *expect_md5, int strict);
static int ec_protect_write_block(struct s3backer_store *s3b, s3b_block_t block_num, const void *src, u_char *md5,
......@@ -183,7 +183,7 @@ ec_protect_create(struct ec_protect_conf *config, struct s3backer_store *inner)
goto fail0;
}
s3b->meta_data = ec_protect_meta_data;
s3b->set_mounted = ec_protect_set_mounted;
s3b->set_mount_token = ec_protect_set_mount_token;
s3b->read_block = ec_protect_read_block;
s3b->write_block = ec_protect_write_block;
s3b->read_block_part = ec_protect_read_block_part;
......@@ -243,11 +243,11 @@ ec_protect_meta_data(struct s3backer_store *s3b, off_t *file_sizep, u_int *block
}
static int
ec_protect_set_mounted(struct s3backer_store *s3b, int *old_valuep, int new_value)
ec_protect_set_mount_token(struct s3backer_store *s3b, int32_t *old_valuep, int32_t new_value)
{
struct ec_protect_private *const priv = s3b->data;
return (*priv->inner->set_mounted)(priv->inner, old_valuep, new_value);
return (*priv->inner->set_mount_token)(priv->inner, old_valuep, new_value);
}
static int
......
......@@ -128,9 +128,9 @@ s3backer_erase(struct s3b_config *config)
goto fail3;
}
/* Clear mounted flag */
if ((r = (*priv->s3b->set_mounted)(priv->s3b, NULL, 0)) != 0) {
warnx("can't clear mounted flag: %s", strerror(r));
/* Clear mount token */
if ((r = (*priv->s3b->set_mount_token)(priv->s3b, NULL, 0)) != 0) {
warnx("can't clear mount token: %s", strerror(r));
goto fail3;
}
......
......@@ -198,11 +198,11 @@ fuse_op_destroy(void *data)
(*config->log)(LOG_ERR, "unmount %s: flushing filesystem failed: %s", s3bconf->mount, strerror(r));
}
/* Clear mounted flag */
/* Clear mount token */
if (!config->read_only) {
(*config->log)(LOG_INFO, "unmount %s: clearing mounted flag", s3bconf->mount);
if ((r = (*s3b->set_mounted)(s3b, NULL, 0)) != 0)
(*config->log)(LOG_ERR, "unmount %s: clearing mounted flag failed: %s", s3bconf->mount, strerror(r));
(*config->log)(LOG_INFO, "unmount %s: clearing mount token", s3bconf->mount);
if ((r = (*s3b->set_mount_token)(s3b, NULL, 0)) != 0)
(*config->log)(LOG_ERR, "unmount %s: clearing mount token failed: %s", s3bconf->mount, strerror(r));
}
/* Shutdown */
......
This diff is collapsed.
......@@ -47,6 +47,8 @@
#define STORAGE_CLASS_STANDARD_IA "STANDARD_IA"
#define STORAGE_CLASS_REDUCED_REDUNDANCY "REDUCED_REDUNDANCY"
/* Server side encryption types */
#define REQUIRED_SSE_VALUE "AES256"
/* Configuration info structure for http_io store */
struct http_io_conf {
......
......@@ -42,17 +42,20 @@
#include "test_io.h"
#include "s3b_config.h"
#include "reset.h"
#include "dcache.h"
int
s3backer_reset(struct s3b_config *config)
{
struct s3backer_store *s3b = NULL;
struct s3b_dcache *dcache = NULL;
struct stat cache_file_stat;
int ok = 0;
int r;
/* Logging */
if (!config->quiet)
warnx("resetting mounted flag for %s", config->description);
warnx("resetting mount token for %s", config->description);
/* Create temporary lower layer */
if ((s3b = config->test ? test_io_create(&config->http_io) : http_io_create(&config->http_io)) == NULL) {
......@@ -60,12 +63,28 @@ s3backer_reset(struct s3b_config *config)
goto fail;
}
/* Clear mounted flag */
if ((r = (*s3b->set_mounted)(s3b, NULL, 0)) != 0) {
warnx("error clearing mounted flag: %s", strerror(r));
/* Clear mount token */
if ((r = (*s3b->set_mount_token)(s3b, NULL, 0)) != 0) {
warnx("error clearing s3 mount token: %s", strerror(r));
goto fail;
}
/* Open disk cache file, if any, and clear the mount token there too */
if (config->block_cache.cache_file != NULL) {
if (stat(config->block_cache.cache_file, &cache_file_stat) == -1) {
if (errno != ENOENT) {
warnx("error opening cache file `%s'", config->block_cache.cache_file);
goto fail;
}
} else {
if ((r = s3b_dcache_open(&dcache, config->log, config->block_cache.cache_file,
config->block_cache.block_size, config->block_cache.cache_size, NULL, NULL, 0)) != 0)
warnx("error opening cache file `%s': %s", config->block_cache.cache_file, strerror(r));
if ((r = s3b_dcache_set_mount_token(dcache, NULL, 0)) != 0)
warnx("error reading mount token from `%s': %s", config->block_cache.cache_file, strerror(r));
}
}
/* Success */
if (!config->quiet)
warnx("done");
......@@ -73,6 +92,8 @@ s3backer_reset(struct s3b_config *config)
fail:
/* Clean up */
if (dcache != NULL)
s3b_dcache_close(dcache);
if (s3b != NULL)
(*s3b->destroy)(s3b);
return ok ? 0 : -1;
......
This diff is collapsed.
......@@ -187,8 +187,9 @@ The block cache is configured by the following command line options:
.Fl \-blockCacheSync ,
.Fl \-blockCacheThreads ,
.Fl \-blockCacheTimeout ,
.Fl \-blockCacheWriteDelay ,
and
.Fl \-blockCacheWriteDelay .
.Fl \-blockCacheRecoverDirtyBlocks .
.Ss Read Ahead
.Nm
implements a simple read-ahead algorithm in the block cache.
......@@ -370,12 +371,19 @@ will automatically expand or shrink the file at startup.
When shrinking, blocks that don't fit in the new, smaller cache are discarded.
This process also compacts the cache file to the extent possible.
.Pp
In any case, only clean cache blocks are recoverable after a restart.
By default, only clean cache blocks are recoverable after a restart.
This means a system crash will cause dirty blocks in the cache to be lost (of course, that is the case
with an in-memory cache as well).
Use
.Fl \-blockCacheWriteDelay
to limit this window.
.Pp
With the newer cache file format introduced in release 1.5.0, you can recover these dirty blocks by specifying the
.Fl \-blockCacheRecoverDirtyBlocks
option.
This will cause any dirty blocks in the cache file to be made writable again on startup.
If your cache file was created with a prior release of
.Nm
or you do not specify this option, dirty blocks in the cache file are discarded on startup.
The window of this data loss can be limited by
.Fl \-blockCacheWriteDelay .
.Pp
By default, when having reloaded the cache from a cache file,
.Nm
......@@ -433,6 +441,22 @@ This flag requires
.Fl \-blockCacheWriteDelay
to be zero.
Using this flag is likely to drastically reduce write performance.
.It Fl \-blockCacheRecoverDirtyBlocks
An unclean dismount may leave dirty blocks (blocks written to the local cache file, but not yet flushed to S3) in the cache file.
.Pp
If this option is set,
.Nm
will recover any such dirty blocks and eventually write them back to S3.
If this option is not specified, all dirty data in the cache file are discarded on startup.
.Pp
If the filesystem has been mounted since the cache file was last used,
.Nm
will refuse to mount.
This is verified by checking a unique 32-bit mount token in the cache file against the 'already mounted' flag in the data store.
.Pp
This flag requires
.Fl \-blockCacheFile
to be set.
.It Fl \-blockSize=SIZE
Specify the block size.
This must be a power of two and should be a multiple of the kernel's native page size.
......@@ -899,7 +923,7 @@ has finished writing back all dirty blocks.
Therefore, when using the block cache, attempts to remount the same bucket and prefix
may fail with an 'already mounted' error while the former
.Nm
process finishes flusing its cache.
process finishes flushing its cache.
Before assuming a false positive and using
.Fl \-reset-mounted-flag,
ensure that any previous
......
......@@ -97,10 +97,6 @@
#define FALLOC_FL_PUNCH_HOLE 0x02
#endif
#define S3BACKER_SSE_HEADER "x-amz-server-side-encryption"
#define S3BACKER_DEFAULT_SSE_VALUE "AES256"
/*
* Integral type for holding a block number.
*/
......@@ -139,18 +135,19 @@ struct s3backer_store {
int (*meta_data)(struct s3backer_store *s3b, off_t *file_sizep, u_int *block_sizep);
/*
* Read and (optionally) set the mounted flag.
* Read and (optionally) set the mount token. The mount token is any 32 bit integer value greater than zero.
*
* Previous value is returned in *old_valuep (if not NULL).
* Previous value, if any, is returned in *old_valuep (if not NULL). A returned value of zero means there was
* no previous value.
*
* new_value can be:
* -1 Don't change it
* 0 Clear it
* 1 Set it
* < 0 Don't change anything, just read the existing value, if any
* = 0 Clear the flag
* > 0 Set flag to new_value
*
* Returns zero on success or a (positive) errno value on error.
*/
int (*set_mounted)(struct s3backer_store *s3b, int *old_valuep, int new_value);
int (*set_mount_token)(struct s3backer_store *s3b, int32_t *old_valuep, int32_t new_value);
/*
* Read one block. Never-written-to blocks will return all zeroes.
......
......@@ -50,7 +50,7 @@ struct test_io_private {
/* s3backer_store functions */
static int test_io_meta_data(struct s3backer_store *s3b, off_t *file_sizep, u_int *block_sizep);
static int test_io_set_mounted(struct s3backer_store *s3b, int *old_valuep, int new_value);
static int test_io_set_mount_token(struct s3backer_store *s3b, int32_t *old_valuep, int32_t new_value);
static int test_io_read_block(struct s3backer_store *s3b, s3b_block_t block_num, void *dest,
u_char *actual_md5, const u_char *expect_md5, int strict);
static int test_io_write_block(struct s3backer_store *s3b, s3b_block_t block_num, const void *src, u_char *md5,
......@@ -76,7 +76,7 @@ test_io_create(struct http_io_conf *config)
if ((s3b = calloc(1, sizeof(*s3b))) == NULL)
return NULL;
s3b->meta_data = test_io_meta_data;
s3b->set_mounted = test_io_set_mounted;
s3b->set_mount_token = test_io_set_mount_token;
s3b->read_block = test_io_read_block;
s3b->write_block = test_io_write_block;
s3b->read_block_part = test_io_read_block_part;
......@@ -106,7 +106,7 @@ test_io_meta_data(struct s3backer_store *s3b, off_t *file_sizep, u_int *block_si
}
static int
test_io_set_mounted(struct s3backer_store *s3b, int *old_valuep, int new_value)
test_io_set_mount_token(struct s3backer_store *s3b, int32_t *old_valuep, int32_t new_value)
{
if (old_valuep != NULL)
*old_valuep = 0;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment