1 /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ 2 // NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp) 3 #define _GNU_SOURCE 4 #include <libpldm/instance-id.h> 5 #include <libpldm/pldm.h> 6 7 #include <errno.h> 8 #include <fcntl.h> 9 #include <stdlib.h> 10 #include <sys/stat.h> 11 #include <unistd.h> 12 13 #define BIT(i) (1UL << (i)) 14 15 #define PLDM_TID_MAX 256 16 #define PLDM_INST_ID_MAX 32 17 18 /* We need to track our allocations explicitly due to OFD lock merging/splitting 19 */ 20 struct pldm_tid_state { 21 pldm_instance_id_t prev; 22 uint32_t allocations; 23 }; 24 25 struct pldm_instance_db { 26 struct pldm_tid_state state[PLDM_TID_MAX]; 27 int lock_db_fd; 28 }; 29 30 static inline int iid_next(pldm_instance_id_t cur) 31 { 32 return (cur + 1) % PLDM_INST_ID_MAX; 33 } 34 35 LIBPLDM_ABI_STABLE 36 int pldm_instance_db_init(struct pldm_instance_db **ctx, const char *dbpath) 37 { 38 struct pldm_instance_db *l_ctx; 39 struct stat statbuf; 40 int rc; 41 42 /* Make sure the provided pointer was initialised to NULL. In the future 43 * if we stabilise the ABI and expose the struct definition the caller 44 * can potentially pass a valid pointer to a struct they've allocated 45 */ 46 if (!ctx || *ctx) { 47 return -EINVAL; 48 } 49 50 /* Ensure the underlying file is sized for properly managing allocations 51 */ 52 rc = stat(dbpath, &statbuf); 53 if (rc < 0) { 54 return -EINVAL; 55 } 56 57 if (statbuf.st_size < 58 ((off_t)(PLDM_TID_MAX) * (off_t)(PLDM_INST_ID_MAX))) { 59 return -EINVAL; 60 } 61 62 l_ctx = calloc(1, sizeof(struct pldm_instance_db)); 63 if (!l_ctx) { 64 return -ENOMEM; 65 } 66 67 /* Initialise previous ID values so the next one is zero */ 68 for (int i = 0; i < PLDM_TID_MAX; i++) { 69 l_ctx->state[i].prev = 31; 70 } 71 72 /* Lock database may be read-only, either by permissions or mountpoint 73 */ 74 l_ctx->lock_db_fd = open(dbpath, O_RDONLY | O_CLOEXEC); 75 if (l_ctx->lock_db_fd < 0) { 76 free(l_ctx); 77 return -errno; 78 } 79 *ctx = l_ctx; 80 81 return 0; 82 } 83 84 LIBPLDM_ABI_STABLE 85 int pldm_instance_db_init_default(struct pldm_instance_db **ctx) 86 { 87 return pldm_instance_db_init(ctx, 88 "/usr/share/libpldm/instance-db/default"); 89 } 90 91 LIBPLDM_ABI_STABLE 92 int pldm_instance_db_destroy(struct pldm_instance_db *ctx) 93 { 94 if (!ctx) { 95 return 0; 96 } 97 close(ctx->lock_db_fd); 98 free(ctx); 99 return 0; 100 } 101 102 static const struct flock pldm_instance_id_cfls = { 103 .l_type = F_RDLCK, 104 .l_whence = SEEK_SET, 105 .l_len = 1, 106 }; 107 108 static const struct flock pldm_instance_id_cflx = { 109 .l_type = F_WRLCK, 110 .l_whence = SEEK_SET, 111 .l_len = 1, 112 }; 113 114 static const struct flock pldm_instance_id_cflu = { 115 .l_type = F_UNLCK, 116 .l_whence = SEEK_SET, 117 .l_len = 1, 118 }; 119 120 LIBPLDM_ABI_STABLE 121 int pldm_instance_id_alloc(struct pldm_instance_db *ctx, pldm_tid_t tid, 122 pldm_instance_id_t *iid) 123 { 124 uint8_t l_iid; 125 126 if (!iid) { 127 return -EINVAL; 128 } 129 130 l_iid = ctx->state[tid].prev; 131 if (l_iid >= PLDM_INST_ID_MAX) { 132 return -EPROTO; 133 } 134 135 while ((l_iid = iid_next(l_iid)) != ctx->state[tid].prev) { 136 struct flock flop; 137 off_t loff; 138 int rc; 139 140 /* Have we already allocated this instance ID? */ 141 if (ctx->state[tid].allocations & BIT(l_iid)) { 142 continue; 143 } 144 145 /* Derive the instance ID offset in the lock database */ 146 loff = tid * PLDM_INST_ID_MAX + l_iid; 147 148 /* Reserving the TID's IID. Done via a shared lock */ 149 flop = pldm_instance_id_cfls; 150 flop.l_start = loff; 151 rc = fcntl(ctx->lock_db_fd, F_OFD_SETLK, &flop); 152 if (rc < 0) { 153 if (errno == EAGAIN || errno == EINTR) { 154 return -EAGAIN; 155 } 156 return -EPROTO; 157 } 158 159 /* 160 * If we *may* promote the lock to exclusive then this IID is 161 * only reserved by us. This is now our allocated IID. 162 * 163 * If we *may not* promote the lock to exclusive then this IID 164 * is also reserved on another file descriptor. Move on to the 165 * next IID index. 166 * 167 * Note that we cannot actually *perform* the promotion in 168 * practice because this is prevented by the lock database being 169 * opened O_RDONLY. 170 */ 171 flop = pldm_instance_id_cflx; 172 flop.l_start = loff; 173 rc = fcntl(ctx->lock_db_fd, F_OFD_GETLK, &flop); 174 if (rc < 0) { 175 if (errno == EAGAIN || errno == EINTR) { 176 rc = -EAGAIN; 177 goto release_cfls; 178 } 179 rc = -EPROTO; 180 goto release_cfls; 181 } 182 183 /* F_UNLCK is the type of the lock if we could successfully 184 * promote it to F_WRLCK */ 185 if (flop.l_type == F_UNLCK) { 186 ctx->state[tid].prev = l_iid; 187 ctx->state[tid].allocations |= BIT(l_iid); 188 *iid = l_iid; 189 return 0; 190 } 191 192 if (flop.l_type != F_RDLCK) { 193 rc = -EPROTO; 194 } 195 196 release_cfls: 197 flop = pldm_instance_id_cflu; 198 flop.l_start = loff; 199 if (fcntl(ctx->lock_db_fd, F_OFD_SETLK, &flop) < 0) { 200 if (errno == EAGAIN || errno == EINTR) { 201 return -EAGAIN; 202 } 203 return -EPROTO; 204 } 205 206 if (rc < 0) { 207 return rc; 208 } 209 } 210 211 /* Failed to allocate an IID after a full loop. Make the caller try 212 * again */ 213 return -EAGAIN; 214 } 215 216 LIBPLDM_ABI_STABLE 217 int pldm_instance_id_free(struct pldm_instance_db *ctx, pldm_tid_t tid, 218 pldm_instance_id_t iid) 219 { 220 struct flock flop; 221 int rc; 222 223 /* Trying to free an instance ID that is not currently allocated */ 224 if (!(ctx->state[tid].allocations & BIT(iid))) { 225 return -EINVAL; 226 } 227 228 flop = pldm_instance_id_cflu; 229 flop.l_start = tid * PLDM_INST_ID_MAX + iid; 230 rc = fcntl(ctx->lock_db_fd, F_OFD_SETLK, &flop); 231 if (rc < 0) { 232 if (errno == EAGAIN || errno == EINTR) { 233 return -EAGAIN; 234 } 235 return -EPROTO; 236 } 237 238 /* Mark the instance ID as no-longer allocated */ 239 ctx->state[tid].allocations &= ~BIT(iid); 240 241 return 0; 242 } 243