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