diff options
Diffstat (limited to 'libmount/src/hooks.c')
-rw-r--r-- | libmount/src/hooks.c | 404 |
1 files changed, 404 insertions, 0 deletions
diff --git a/libmount/src/hooks.c b/libmount/src/hooks.c new file mode 100644 index 0000000..dcfe69f --- /dev/null +++ b/libmount/src/hooks.c @@ -0,0 +1,404 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libmount from util-linux project. + * + * Copyright (C) 2022 Karel Zak <kzak@redhat.com> + * + * libmount is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * + * The "hookset" is a set of callbacks (hooks) that implement some functionality. + * The library defines stages where hooks are called (e.g. when preparing source, post + * mount(2), etc.). An arbitrary hook can, on the fly, define another hook for the + * arbitrary stage. The first hook from the hookset which goes to the game is a + * "firstcall" (defined in struct libmnt_hookset). This first hook controls + * what will happen in the next stages (usually nothing). + * + * The library supports two kinds of data for hooksets: + * + * - global data; accessible for all callbacks. Makes sense for complex + * hooksets with more callbacks in more stages. Usually implemented by + * locally defined 'struct hookset_data' in hook_*.c. + * + * - per-hook data; acessible for specific callback + * Usually implemented by locally defined 'struct hook_data' in hook_*.c. + */ +#include "mountP.h" +#include "mount-api-utils.h" + +/* built-in hooksets */ +static const struct libmnt_hookset *hooksets[] = +{ +#ifdef __linux__ + &hookset_loopdev, +#ifdef HAVE_CRYPTSETUP + &hookset_veritydev, +#endif + &hookset_mkdir, +#ifdef HAVE_LIBSELINUX + &hookset_selinux, +#endif + &hookset_subdir, +#ifdef USE_LIBMOUNT_MOUNTFD_SUPPORT + &hookset_mount, +#endif + &hookset_mount_legacy, +#if defined(HAVE_MOUNTFD_API) && defined(HAVE_LINUX_MOUNT_H) + &hookset_idmap, +#endif + &hookset_owner +#endif +}; + +/* hooksets data (this is global list of hookset data) */ +struct hookset_data { + const struct libmnt_hookset *hookset; + void *data; + + struct list_head datas; +}; + +/* individial callback */ +struct hookset_hook { + const struct libmnt_hookset *hookset; + int stage; + void *data; + const char *after; + + int (*func)(struct libmnt_context *, const struct libmnt_hookset *, void *); + + struct list_head hooks; + unsigned int executed : 1; +}; + +static const char *stagenames[] = { + /* prepare */ + [MNT_STAGE_PREP_SOURCE] = "prep-source", + [MNT_STAGE_PREP_TARGET] = "prep-target", + [MNT_STAGE_PREP_OPTIONS] = "prep-options", + [MNT_STAGE_PREP] = "prep", + + /* mount */ + [MNT_STAGE_MOUNT_PRE] = "pre-mount", + [MNT_STAGE_MOUNT] = "mount", + [MNT_STAGE_MOUNT_POST] = "post-mount", + + /* post */ + [MNT_STAGE_POST] = "post", +}; + +static int call_depend_hooks(struct libmnt_context *cxt, const char *name, int stage); + + +int mnt_context_deinit_hooksets(struct libmnt_context *cxt) +{ + size_t i; + int rc = 0; + + assert(cxt); + + if (list_empty(&cxt->hooksets_datas) && + list_empty(&cxt->hooksets_hooks)) + return 0; + + for (i = 0; i < ARRAY_SIZE(hooksets); i++) { + const struct libmnt_hookset *hs = hooksets[i]; + + rc += hs->deinit(cxt, hs); + } + + assert(list_empty(&cxt->hooksets_datas)); + assert(list_empty(&cxt->hooksets_hooks)); + + INIT_LIST_HEAD(&cxt->hooksets_datas); + INIT_LIST_HEAD(&cxt->hooksets_hooks); + + return rc; +} + +const struct libmnt_hookset *mnt_context_get_hookset( + struct libmnt_context *cxt, const char *name) +{ + size_t i; + + assert(cxt); + assert(name); + + for (i = 0; i < ARRAY_SIZE(hooksets); i++) { + const struct libmnt_hookset *hs = hooksets[i]; + + if (strcmp(name, hs->name) == 0) + return hs; + } + + return NULL; +} + +static struct hookset_data *get_hookset_data( + struct libmnt_context *cxt, + const struct libmnt_hookset *hs) +{ + struct list_head *p; + + assert(cxt); + assert(hs); + + list_for_each(p, &cxt->hooksets_datas) { + struct hookset_data *x = list_entry(p, struct hookset_data, datas); + + if (x->hookset == hs) + return x; + } + return 0; +} + +int mnt_context_set_hookset_data(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + void *data) +{ + struct hookset_data *hd = NULL; + + hd = get_hookset_data(cxt, hs); + + /* deallocate old data */ + if (data == NULL) { + if (hd) { + DBG(CXT, ul_debugobj(cxt, " free '%s' data", hs->name)); + list_del(&hd->datas); + free(hd); + } + return 0; + } + + /* create and append new data */ + if (!hd) { + hd = calloc(1, sizeof(*hd)); + if (!hd) + return -ENOMEM; + + DBG(CXT, ul_debugobj(cxt, " alloc '%s' data", hs->name)); + INIT_LIST_HEAD(&hd->datas); + hd->hookset = hs; + list_add_tail(&hd->datas, &cxt->hooksets_datas); + + } + hd->data = data; + return 0; +} + +void *mnt_context_get_hookset_data(struct libmnt_context *cxt, + const struct libmnt_hookset *hs) +{ + struct hookset_data *hd = get_hookset_data(cxt, hs); + + return hd ? hd->data : NULL; +} + +static int append_hook(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + int stage, + void *data, + int (*func)(struct libmnt_context *, + const struct libmnt_hookset *, + void *), + const char *after) +{ + struct hookset_hook *hook; + + assert(cxt); + assert(hs); + assert(stage); + + hook = calloc(1, sizeof(*hook)); + if (!hook) + return -ENOMEM; + + DBG(CXT, ul_debugobj(cxt, " appending %s hook from %s", + stagenames[stage], hs->name)); + + INIT_LIST_HEAD(&hook->hooks); + + hook->hookset = hs; + hook->data = data; + hook->func = func; + hook->stage = stage; + hook->after = after; + + list_add_tail(&hook->hooks, &cxt->hooksets_hooks); + return 0; +} + +int mnt_context_append_hook(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + int stage, + void *data, + int (*func)(struct libmnt_context *, + const struct libmnt_hookset *, + void *)) +{ + return append_hook(cxt, hs, stage, data, func, NULL); +} + +int mnt_context_insert_hook(struct libmnt_context *cxt, + const char *after, + const struct libmnt_hookset *hs, + int stage, + void *data, + int (*func)(struct libmnt_context *, + const struct libmnt_hookset *, + void *)) +{ + return append_hook(cxt, hs, stage, data, func, after); +} + +static struct hookset_hook *get_hookset_hook(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + int stage, + void *data) +{ + struct list_head *p, *next; + + assert(cxt); + + list_for_each_safe(p, next, &cxt->hooksets_hooks) { + struct hookset_hook *x = list_entry(p, struct hookset_hook, hooks); + + if (hs && x->hookset != hs) + continue; + if (stage && x->stage != stage) + continue; + if (data && x->data != data) + continue; + return x; + } + + return NULL; +} + +int mnt_context_remove_hook(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + int stage, + void **data) +{ + struct hookset_hook *hook; + + assert(cxt); + + hook = get_hookset_hook(cxt, hs, stage, NULL); + if (hook) { + DBG(CXT, ul_debugobj(cxt, " removing %s hook from %s", + stagenames[hook->stage], hook->hookset->name)); + + if (data) + *data = hook->data; + + list_del(&hook->hooks); + free(hook); + return 0; + } + + return 1; +} + +int mnt_context_has_hook(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + int stage, + void *data) +{ + return get_hookset_hook(cxt, hs, stage, data) ? 1 : 0; +} + +static int call_hook(struct libmnt_context *cxt, struct hookset_hook *hook) +{ + int rc = 0; + + if (mnt_context_is_fake(cxt)) + DBG(CXT, ul_debugobj(cxt, " FAKE call")); + else + rc = hook->func(cxt, hook->hookset, hook->data); + + hook->executed = 1; + if (!rc) + rc = call_depend_hooks(cxt, hook->hookset->name, hook->stage); + return rc; +} + +static int call_depend_hooks(struct libmnt_context *cxt, const char *name, int stage) +{ + struct list_head *p = NULL, *next = NULL; + int rc = 0; + + list_for_each_safe(p, next, &cxt->hooksets_hooks) { + struct hookset_hook *x = list_entry(p, struct hookset_hook, hooks); + + if (x->stage != stage || x->executed || + x->after == NULL || strcmp(x->after, name) != 0) + continue; + + DBG(CXT, ul_debugobj(cxt, "calling %s [after]", x->hookset->name)); + rc = call_hook(cxt, x); + if (rc) + break; + } + + return rc; +} + +int mnt_context_call_hooks(struct libmnt_context *cxt, int stage) +{ + struct list_head *p = NULL, *next = NULL; + size_t i; + int rc = 0; + + DBG(CXT, ul_debugobj(cxt, "---> stage:%s", stagenames[stage])); + + /* call initial hooks */ + for (i = 0; i < ARRAY_SIZE(hooksets); i++) { + const struct libmnt_hookset *hs = hooksets[i]; + + if (hs->firststage != stage) + continue; + + DBG(CXT, ul_debugobj(cxt, "calling %s [first]", hs->name)); + + if (mnt_context_is_fake(cxt)) + DBG(CXT, ul_debugobj(cxt, " FAKE call")); + else + rc = hs->firstcall(cxt, hs, NULL); + if (!rc) + rc = call_depend_hooks(cxt, hs->name, stage); + if (rc < 0) + goto done; + } + + /* call already active hooks */ + list_for_each_safe(p, next, &cxt->hooksets_hooks) { + struct hookset_hook *x = list_entry(p, struct hookset_hook, hooks); + + if (x->stage != stage || x->executed) + continue; + + DBG(CXT, ul_debugobj(cxt, "calling %s [active]", x->hookset->name)); + rc = call_hook(cxt, x); + if (rc < 0) + goto done; + } + +done: + /* zeroize status */ + p = next = NULL; + list_for_each_safe(p, next, &cxt->hooksets_hooks) { + struct hookset_hook *x = list_entry(p, struct hookset_hook, hooks); + + if (x->stage != stage) + continue; + x->executed = 0; + } + + DBG(CXT, ul_debugobj(cxt, "<--- stage:%s [rc=%d status=%d]", + stagenames[stage], rc, cxt->syscall_status)); + return rc; +} |