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