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
iid_next(pldm_instance_id_t cur)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
pldm_instance_db_init(struct pldm_instance_db ** ctx,const char * dbpath)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
pldm_instance_db_init_default(struct pldm_instance_db ** ctx)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
pldm_instance_db_destroy(struct pldm_instance_db * ctx)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 LIBPLDM_ABI_STABLE
pldm_instance_id_alloc(struct pldm_instance_db * ctx,pldm_tid_t tid,pldm_instance_id_t * iid)103 int pldm_instance_id_alloc(struct pldm_instance_db *ctx, pldm_tid_t tid,
104 pldm_instance_id_t *iid)
105 {
106 static const struct flock cfls = {
107 .l_type = F_RDLCK,
108 .l_whence = SEEK_SET,
109 .l_len = 1,
110 };
111 static const struct flock cflx = {
112 .l_type = F_WRLCK,
113 .l_whence = SEEK_SET,
114 .l_len = 1,
115 };
116 uint8_t l_iid;
117
118 if (!iid) {
119 return -EINVAL;
120 }
121
122 l_iid = ctx->state[tid].prev;
123 if (l_iid >= PLDM_INST_ID_MAX) {
124 return -EPROTO;
125 }
126
127 while ((l_iid = iid_next(l_iid)) != ctx->state[tid].prev) {
128 struct flock flop;
129 off_t loff;
130 int rc;
131
132 /* Have we already allocated this instance ID? */
133 if (ctx->state[tid].allocations & BIT(l_iid)) {
134 continue;
135 }
136
137 /* Derive the instance ID offset in the lock database */
138 loff = tid * PLDM_INST_ID_MAX + l_iid;
139
140 /* Reserving the TID's IID. Done via a shared lock */
141 flop = cfls;
142 flop.l_start = loff;
143 rc = fcntl(ctx->lock_db_fd, F_OFD_SETLK, &flop);
144 if (rc < 0) {
145 if (errno == EAGAIN || errno == EINTR) {
146 return -EAGAIN;
147 }
148 return -EPROTO;
149 }
150
151 /*
152 * If we *may* promote the lock to exclusive then this IID is
153 * only reserved by us. This is now our allocated IID.
154 *
155 * If we *may not* promote the lock to exclusive then this IID
156 * is also reserved on another file descriptor. Move on to the
157 * next IID index.
158 *
159 * Note that we cannot actually *perform* the promotion in
160 * practice because this is prevented by the lock database being
161 * opened O_RDONLY.
162 */
163 flop = cflx;
164 flop.l_start = loff;
165 rc = fcntl(ctx->lock_db_fd, F_OFD_GETLK, &flop);
166 if (rc < 0) {
167 if (errno == EAGAIN || errno == EINTR) {
168 return -EAGAIN;
169 }
170 return -EPROTO;
171 }
172
173 /* F_UNLCK is the type of the lock if we could successfully
174 * promote it to F_WRLCK */
175 if (flop.l_type == F_UNLCK) {
176 ctx->state[tid].prev = l_iid;
177 ctx->state[tid].allocations |= BIT(l_iid);
178 *iid = l_iid;
179 return 0;
180 }
181 if (flop.l_type != F_RDLCK) {
182 return -EPROTO;
183 }
184 }
185
186 /* Failed to allocate an IID after a full loop. Make the caller try
187 * again */
188 return -EAGAIN;
189 }
190
191 LIBPLDM_ABI_STABLE
pldm_instance_id_free(struct pldm_instance_db * ctx,pldm_tid_t tid,pldm_instance_id_t iid)192 int pldm_instance_id_free(struct pldm_instance_db *ctx, pldm_tid_t tid,
193 pldm_instance_id_t iid)
194 {
195 static const struct flock cflu = {
196 .l_type = F_UNLCK,
197 .l_whence = SEEK_SET,
198 .l_len = 1,
199 };
200 struct flock flop;
201 int rc;
202
203 /* Trying to free an instance ID that is not currently allocated */
204 if (!(ctx->state[tid].allocations & BIT(iid))) {
205 return -EINVAL;
206 }
207
208 flop = cflu;
209 flop.l_start = tid * PLDM_INST_ID_MAX + iid;
210 rc = fcntl(ctx->lock_db_fd, F_OFD_SETLK, &flop);
211 if (rc < 0) {
212 if (errno == EAGAIN || errno == EINTR) {
213 return -EAGAIN;
214 }
215 return -EPROTO;
216 }
217
218 /* Mark the instance ID as no-longer allocated */
219 ctx->state[tid].allocations &= ~BIT(iid);
220
221 return 0;
222 }
223