| /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ |
| // NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp) |
| #define _GNU_SOURCE |
| #include <libpldm/instance-id.h> |
| #include <libpldm/pldm.h> |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <stdlib.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| |
| #define BIT(i) (1UL << (i)) |
| |
| #define PLDM_TID_MAX 256 |
| #define PLDM_INST_ID_MAX 32 |
| |
| /* We need to track our allocations explicitly due to OFD lock merging/splitting |
| */ |
| struct pldm_tid_state { |
| pldm_instance_id_t prev; |
| uint32_t allocations; |
| }; |
| |
| struct pldm_instance_db { |
| struct pldm_tid_state state[PLDM_TID_MAX]; |
| int lock_db_fd; |
| }; |
| |
| static inline int iid_next(pldm_instance_id_t cur) |
| { |
| return (cur + 1) % PLDM_INST_ID_MAX; |
| } |
| |
| LIBPLDM_ABI_STABLE |
| int pldm_instance_db_init(struct pldm_instance_db **ctx, const char *dbpath) |
| { |
| struct pldm_instance_db *l_ctx; |
| struct stat statbuf; |
| int rc; |
| |
| /* Make sure the provided pointer was initialised to NULL. In the future |
| * if we stabilise the ABI and expose the struct definition the caller |
| * can potentially pass a valid pointer to a struct they've allocated |
| */ |
| if (!ctx || *ctx) { |
| return -EINVAL; |
| } |
| |
| /* Ensure the underlying file is sized for properly managing allocations |
| */ |
| rc = stat(dbpath, &statbuf); |
| if (rc < 0) { |
| return -EINVAL; |
| } |
| |
| if (statbuf.st_size < |
| ((off_t)(PLDM_TID_MAX) * (off_t)(PLDM_INST_ID_MAX))) { |
| return -EINVAL; |
| } |
| |
| l_ctx = calloc(1, sizeof(struct pldm_instance_db)); |
| if (!l_ctx) { |
| return -ENOMEM; |
| } |
| |
| /* Initialise previous ID values so the next one is zero */ |
| for (int i = 0; i < PLDM_TID_MAX; i++) { |
| l_ctx->state[i].prev = 31; |
| } |
| |
| /* Lock database may be read-only, either by permissions or mountpoint |
| */ |
| l_ctx->lock_db_fd = open(dbpath, O_RDONLY | O_CLOEXEC); |
| if (l_ctx->lock_db_fd < 0) { |
| free(l_ctx); |
| return -errno; |
| } |
| *ctx = l_ctx; |
| |
| return 0; |
| } |
| |
| LIBPLDM_ABI_STABLE |
| int pldm_instance_db_init_default(struct pldm_instance_db **ctx) |
| { |
| return pldm_instance_db_init(ctx, |
| "/usr/share/libpldm/instance-db/default"); |
| } |
| |
| LIBPLDM_ABI_STABLE |
| int pldm_instance_db_destroy(struct pldm_instance_db *ctx) |
| { |
| if (!ctx) { |
| return 0; |
| } |
| close(ctx->lock_db_fd); |
| free(ctx); |
| return 0; |
| } |
| |
| LIBPLDM_ABI_STABLE |
| int pldm_instance_id_alloc(struct pldm_instance_db *ctx, pldm_tid_t tid, |
| pldm_instance_id_t *iid) |
| { |
| static const struct flock cfls = { |
| .l_type = F_RDLCK, |
| .l_whence = SEEK_SET, |
| .l_len = 1, |
| }; |
| static const struct flock cflx = { |
| .l_type = F_WRLCK, |
| .l_whence = SEEK_SET, |
| .l_len = 1, |
| }; |
| uint8_t l_iid; |
| |
| if (!iid) { |
| return -EINVAL; |
| } |
| |
| l_iid = ctx->state[tid].prev; |
| if (l_iid >= PLDM_INST_ID_MAX) { |
| return -EPROTO; |
| } |
| |
| while ((l_iid = iid_next(l_iid)) != ctx->state[tid].prev) { |
| struct flock flop; |
| off_t loff; |
| int rc; |
| |
| /* Have we already allocated this instance ID? */ |
| if (ctx->state[tid].allocations & BIT(l_iid)) { |
| continue; |
| } |
| |
| /* Derive the instance ID offset in the lock database */ |
| loff = tid * PLDM_INST_ID_MAX + l_iid; |
| |
| /* Reserving the TID's IID. Done via a shared lock */ |
| flop = cfls; |
| flop.l_start = loff; |
| rc = fcntl(ctx->lock_db_fd, F_OFD_SETLK, &flop); |
| if (rc < 0) { |
| if (errno == EAGAIN || errno == EINTR) { |
| return -EAGAIN; |
| } |
| return -EPROTO; |
| } |
| |
| /* |
| * If we *may* promote the lock to exclusive then this IID is |
| * only reserved by us. This is now our allocated IID. |
| * |
| * If we *may not* promote the lock to exclusive then this IID |
| * is also reserved on another file descriptor. Move on to the |
| * next IID index. |
| * |
| * Note that we cannot actually *perform* the promotion in |
| * practice because this is prevented by the lock database being |
| * opened O_RDONLY. |
| */ |
| flop = cflx; |
| flop.l_start = loff; |
| rc = fcntl(ctx->lock_db_fd, F_OFD_GETLK, &flop); |
| if (rc < 0) { |
| if (errno == EAGAIN || errno == EINTR) { |
| return -EAGAIN; |
| } |
| return -EPROTO; |
| } |
| |
| /* F_UNLCK is the type of the lock if we could successfully |
| * promote it to F_WRLCK */ |
| if (flop.l_type == F_UNLCK) { |
| ctx->state[tid].prev = l_iid; |
| ctx->state[tid].allocations |= BIT(l_iid); |
| *iid = l_iid; |
| return 0; |
| } |
| if (flop.l_type != F_RDLCK) { |
| return -EPROTO; |
| } |
| } |
| |
| /* Failed to allocate an IID after a full loop. Make the caller try |
| * again */ |
| return -EAGAIN; |
| } |
| |
| LIBPLDM_ABI_STABLE |
| int pldm_instance_id_free(struct pldm_instance_db *ctx, pldm_tid_t tid, |
| pldm_instance_id_t iid) |
| { |
| static const struct flock cflu = { |
| .l_type = F_UNLCK, |
| .l_whence = SEEK_SET, |
| .l_len = 1, |
| }; |
| struct flock flop; |
| int rc; |
| |
| /* Trying to free an instance ID that is not currently allocated */ |
| if (!(ctx->state[tid].allocations & BIT(iid))) { |
| return -EINVAL; |
| } |
| |
| flop = cflu; |
| flop.l_start = tid * PLDM_INST_ID_MAX + iid; |
| rc = fcntl(ctx->lock_db_fd, F_OFD_SETLK, &flop); |
| if (rc < 0) { |
| if (errno == EAGAIN || errno == EINTR) { |
| return -EAGAIN; |
| } |
| return -EPROTO; |
| } |
| |
| /* Mark the instance ID as no-longer allocated */ |
| ctx->state[tid].allocations &= ~BIT(iid); |
| |
| return 0; |
| } |