/* 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 #include #include #include #include #include #include #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; } static const struct flock pldm_instance_id_cfls = { .l_type = F_RDLCK, .l_whence = SEEK_SET, .l_len = 1, }; static const struct flock pldm_instance_id_cflx = { .l_type = F_WRLCK, .l_whence = SEEK_SET, .l_len = 1, }; static const struct flock pldm_instance_id_cflu = { .l_type = F_UNLCK, .l_whence = SEEK_SET, .l_len = 1, }; LIBPLDM_ABI_STABLE int pldm_instance_id_alloc(struct pldm_instance_db *ctx, pldm_tid_t tid, pldm_instance_id_t *iid) { 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 = pldm_instance_id_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 = pldm_instance_id_cflx; flop.l_start = loff; rc = fcntl(ctx->lock_db_fd, F_OFD_GETLK, &flop); if (rc < 0) { if (errno == EAGAIN || errno == EINTR) { rc = -EAGAIN; goto release_cfls; } rc = -EPROTO; goto release_cfls; } /* 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) { rc = -EPROTO; } release_cfls: flop = pldm_instance_id_cflu; flop.l_start = loff; if (fcntl(ctx->lock_db_fd, F_OFD_SETLK, &flop) < 0) { if (errno == EAGAIN || errno == EINTR) { return -EAGAIN; } return -EPROTO; } if (rc < 0) { return rc; } } /* 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) { 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 = pldm_instance_id_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; }