xref: /openbmc/libpldm/src/requester/instance-id.c (revision 9c57ef5d371cec85075c7524a34c77bb8d8d884b)
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 (!ctx || !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  	/* check if provided context is null */
223  	if (!ctx) {
224  		return -EINVAL;
225  	}
226  
227  	/* Trying to free an instance ID that is not currently allocated */
228  	if (!(ctx->state[tid].allocations & BIT(iid))) {
229  		return -EINVAL;
230  	}
231  
232  	flop = pldm_instance_id_cflu;
233  	flop.l_start = tid * PLDM_INST_ID_MAX + iid;
234  	rc = fcntl(ctx->lock_db_fd, F_OFD_SETLK, &flop);
235  	if (rc < 0) {
236  		if (errno == EAGAIN || errno == EINTR) {
237  			return -EAGAIN;
238  		}
239  		return -EPROTO;
240  	}
241  
242  	/* Mark the instance ID as no-longer allocated */
243  	ctx->state[tid].allocations &= ~BIT(iid);
244  
245  	return 0;
246  }
247