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
iid_next(pldm_instance_id_t cur)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
pldm_instance_db_init(struct pldm_instance_db ** ctx,const char * dbpath)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
pldm_instance_db_init_default(struct pldm_instance_db ** ctx)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
pldm_instance_db_destroy(struct pldm_instance_db * ctx)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
pldm_instance_id_alloc(struct pldm_instance_db * ctx,pldm_tid_t tid,pldm_instance_id_t * iid)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
pldm_instance_id_free(struct pldm_instance_db * ctx,pldm_tid_t tid,pldm_instance_id_t iid)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