From 29b94d6ba370beae1c628510362e18e52c63c541 Mon Sep 17 00:00:00 2001 From: Ondrej Kozina Date: Tue, 19 Mar 2019 14:31:44 +0100 Subject: [PATCH] Add arbitrary resource locking (named locks). It's complementary to current device locking. It'll be used for mutual exclusion of two or more reencryption resume processes --- lib/utils_device_locking.c | 160 ++++++++++++++++++++++++++++++++++--- lib/utils_device_locking.h | 5 ++ 2 files changed, 152 insertions(+), 13 deletions(-) diff --git a/lib/utils_device_locking.c b/lib/utils_device_locking.c index 9586b7e1..1699a9e8 100644 --- a/lib/utils_device_locking.c +++ b/lib/utils_device_locking.c @@ -49,7 +49,8 @@ enum lock_type { enum lock_mode { DEV_LOCK_FILE = 0, - DEV_LOCK_BDEV + DEV_LOCK_BDEV, + DEV_LOCK_NAME }; struct crypt_lock_handle { @@ -61,9 +62,24 @@ struct crypt_lock_handle { struct { dev_t devno; } bdev; + struct { + char *name; + } name; } u; }; +static int resource_by_name(char *res, size_t res_size, const char *name, bool fullpath) +{ + int r; + + if (fullpath) + r = snprintf(res, res_size, "%s/LN_%s", DEFAULT_LUKS2_LOCK_PATH, name); + else + r = snprintf(res, res_size, "LN_%s", name); + + return (r < 0 || (size_t)r >= res_size) ? -EINVAL : 0; +} + static int resource_by_devno(char *res, size_t res_size, dev_t devno, unsigned fullpath) { int r; @@ -176,11 +192,48 @@ static int acquire_lock_handle(struct crypt_device *cd, struct device *device, s return 0; } +static int acquire_lock_handle_by_name(struct crypt_device *cd, const char *name, struct crypt_lock_handle *h) +{ + char res[PATH_MAX]; + int fd; + + h->u.name.name = strdup(name); + if (!h->u.name.name) + return -ENOMEM; + + if (resource_by_name(res, sizeof(res), name, false)) { + free(h->u.name.name); + return -EINVAL; + } + + fd = open_resource(cd, res); + if (fd < 0) { + free(h->u.name.name); + return fd; + } + + h->flock_fd = fd; + h->mode = DEV_LOCK_NAME; + + return 0; +} + static void release_lock_handle(struct crypt_device *cd, struct crypt_lock_handle *h) { char res[PATH_MAX]; struct stat buf_a, buf_b; + if ((h->mode == DEV_LOCK_NAME) && /* was it name lock */ + !flock(h->flock_fd, LOCK_EX | LOCK_NB) && /* lock to drop the file */ + !resource_by_name(res, sizeof(res), h->u.name.name, true) && /* acquire lock resource name */ + !fstat(h->flock_fd, &buf_a) && /* read inode id referred by fd */ + !stat(res, &buf_b) && /* does path file still exist? */ + same_inode(buf_a, buf_b)) { /* is it same id as the one referenced by fd? */ + /* coverity[toctou] */ + if (unlink(res)) /* yes? unlink the file */ + log_dbg(cd, "Failed to unlink resource file: %s", res); + } + if ((h->mode == DEV_LOCK_BDEV) && /* was it block device */ !flock(h->flock_fd, LOCK_EX | LOCK_NB) && /* lock to drop the file */ !resource_by_devno(res, sizeof(res), h->u.bdev.devno, 1) && /* acquire lock resource name */ @@ -192,6 +245,9 @@ static void release_lock_handle(struct crypt_device *cd, struct crypt_lock_handl log_dbg(cd, "Failed to unlink resource file: %s", res); } + if (h->mode == DEV_LOCK_NAME) + free(h->u.name.name); + if (close(h->flock_fd)) log_dbg(cd, "Failed to close lock resource fd (%d).", h->flock_fd); } @@ -215,7 +271,13 @@ static int verify_lock_handle(const char *device_path, struct crypt_lock_handle if (h->mode == DEV_LOCK_FILE) return 0; - if (resource_by_devno(res, sizeof(res), h->u.bdev.devno, 1)) + if (h->mode == DEV_LOCK_NAME) { + if (resource_by_name(res, sizeof(res), h->u.name.name, true)) + return -EINVAL; + } else if (h->mode == DEV_LOCK_BDEV) { + if (resource_by_devno(res, sizeof(res), h->u.bdev.devno, true)) + return -EINVAL; + } else return -EINVAL; if (fstat(h->flock_fd, &lck_st)) @@ -236,27 +298,30 @@ static unsigned device_lock_dec(struct crypt_lock_handle *h) return --h->refcnt; } -static int acquire_and_verify(struct crypt_device *cd, struct device *device, int flock_op, struct crypt_lock_handle **lock) +static int acquire_and_verify(struct crypt_device *cd, struct device *device, const char *resource, int flock_op, struct crypt_lock_handle **lock) { int r; struct crypt_lock_handle *h = malloc(sizeof(*h)); + if (device && resource) + return -EINVAL; + if (!h) return -ENOMEM; do { - r = acquire_lock_handle(cd, device, h); + r = device ? acquire_lock_handle(cd, device, h) : acquire_lock_handle_by_name(cd, resource, h); if (r) break; if (flock(h->flock_fd, flock_op)) { log_dbg(cd, "Flock on fd %d failed with errno %d.", h->flock_fd, errno); - r = -EINVAL; + r = (errno == EWOULDBLOCK) ? -EBUSY : -EINVAL; release_lock_handle(cd, h); break; } - log_dbg(cd, "Verifying lock handle for %s.", device_path(device)); + log_dbg(cd, "Verifying lock handle for %s.", device ? device_path(device) : resource); /* * check whether another libcryptsetup process removed resource file before this @@ -298,7 +363,7 @@ int device_read_lock_internal(struct crypt_device *cd, struct device *device) log_dbg(cd, "Acquiring read lock for device %s.", device_path(device)); - r = acquire_and_verify(cd, device, LOCK_SH, &h); + r = acquire_and_verify(cd, device, NULL, LOCK_SH, &h); if (r < 0) return r; @@ -329,7 +394,7 @@ int device_write_lock_internal(struct crypt_device *cd, struct device *device) log_dbg(cd, "Acquiring write lock for device %s.", device_path(device)); - r = acquire_and_verify(cd, device, LOCK_EX, &h); + r = acquire_and_verify(cd, device, NULL, LOCK_EX, &h); if (r < 0) return r; @@ -342,23 +407,92 @@ int device_write_lock_internal(struct crypt_device *cd, struct device *device) return 0; } +int crypt_read_lock(struct crypt_device *cd, const char *resource, bool blocking, struct crypt_lock_handle **lock) +{ + int r; + struct crypt_lock_handle *h; + + if (!resource) + return -EINVAL; + + log_dbg(cd, "Acquiring %sblocking read lock for resource %s.", blocking ? "" : "non", resource); + + r = acquire_and_verify(cd, NULL, resource, LOCK_SH | (blocking ? 0 : LOCK_NB), &h); + if (r < 0) + return r; + + h->type = DEV_LOCK_READ; + h->refcnt = 1; + + log_dbg(cd, "READ lock for resource %s taken.", resource); + + *lock = h; + + return 0; +} + +int crypt_write_lock(struct crypt_device *cd, const char *resource, bool blocking, struct crypt_lock_handle **lock) +{ + int r; + struct crypt_lock_handle *h; + + if (!resource) + return -EINVAL; + + log_dbg(cd, "Acquiring %sblocking write lock for resource %s.", blocking ? "" : "non", resource); + + r = acquire_and_verify(cd, NULL, resource, LOCK_EX | (blocking ? 0 : LOCK_NB), &h); + if (r < 0) + return r; + + h->type = DEV_LOCK_WRITE; + h->refcnt = 1; + + log_dbg(cd, "WRITE lock for resource %s taken.", resource); + + *lock = h; + + return 0; +} + +static void unlock_internal(struct crypt_device *cd, struct crypt_lock_handle *h) +{ + if (flock(h->flock_fd, LOCK_UN)) + log_dbg(cd, "flock on fd %d failed.", h->flock_fd); + release_lock_handle(cd, h); + free(h); +} + +void crypt_unlock_internal(struct crypt_device *cd, struct crypt_lock_handle *h) +{ + if (!h) + return; + + /* nested locks are illegal */ + assert(!device_lock_dec(h)); + + log_dbg(cd, "Unlocking %s lock for resource %s.", + device_locked_readonly(h) ? "READ" : "WRITE", h->u.name.name); + + unlock_internal(cd, h); +} + void device_unlock_internal(struct crypt_device *cd, struct device *device) { + bool readonly; struct crypt_lock_handle *h = device_get_lock_handle(device); unsigned u = device_lock_dec(h); if (u) return; - if (flock(h->flock_fd, LOCK_UN)) - log_dbg(cd, "flock on fd %d failed.", h->flock_fd); + readonly = device_locked_readonly(h); - release_lock_handle(cd, h); + unlock_internal(cd, h); log_dbg(cd, "Device %s %s lock released.", device_path(device), - device_locked_readonly(h) ? "READ" : "WRITE"); + readonly ? "READ" : "WRITE"); - free(h); device_set_lock_handle(device, NULL); } diff --git a/lib/utils_device_locking.h b/lib/utils_device_locking.h index d3e5034d..606dbc3e 100644 --- a/lib/utils_device_locking.h +++ b/lib/utils_device_locking.h @@ -35,6 +35,11 @@ void device_unlock_internal(struct crypt_device *cd, struct device *device); int device_locked_verify(struct crypt_device *cd, int fd, struct crypt_lock_handle *h); +int crypt_read_lock(struct crypt_device *cd, const char *name, bool blocking, struct crypt_lock_handle **lock); +int crypt_write_lock(struct crypt_device *cd, const char *name, bool blocking, struct crypt_lock_handle **lock); +void crypt_unlock_internal(struct crypt_device *cd, struct crypt_lock_handle *h); + + /* Used only in device internal allocation */ void device_set_lock_handle(struct device *device, struct crypt_lock_handle *h); struct crypt_lock_handle *device_get_lock_handle(struct device *device);