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