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