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