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