Commit 29b94d6b authored by Ondrej Kozina's avatar Ondrej Kozina

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
parent 80a435f0
......@@ -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);
}
......
......@@ -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);
......
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