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