xref: /freebsd/sys/dev/bhnd/nvram/bhnd_nvram_store_subr.c (revision fdafd315ad0d0f28a11b9fb4476a9ab059c62b92)
1 /*-
2  * Copyright (c) 2015-2016 Landon Fuller <landonf@FreeBSD.org>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer,
10  *    without modification.
11  * 2. Redistributions in binary form must reproduce at minimum a disclaimer
12  *    similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
13  *    redistribution must be conditioned upon including a substantially
14  *    similar Disclaimer requirement for further binary redistribution.
15  *
16  * NO WARRANTY
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19  * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
20  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
21  * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,
22  * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
25  * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
27  * THE POSSIBILITY OF SUCH DAMAGES.
28  */
29 
30 #include <sys/param.h>
31 #include <sys/hash.h>
32 #include <sys/queue.h>
33 
34 #ifdef _KERNEL
35 
36 #include <sys/ctype.h>
37 #include <sys/systm.h>
38 
39 #include <machine/_inttypes.h>
40 
41 #else /* !_KERNEL */
42 
43 #include <ctype.h>
44 #include <errno.h>
45 #include <inttypes.h>
46 #include <stdbool.h>
47 #include <stdio.h>
48 #include <stdint.h>
49 #include <stdlib.h>
50 #include <string.h>
51 
52 #endif /* _KERNEL */
53 
54 #include "bhnd_nvram_private.h"
55 #include "bhnd_nvram_datavar.h"
56 
57 #include "bhnd_nvram_storevar.h"
58 
59 static int bhnd_nvstore_idx_cmp(const void *lhs, const void *rhs, void *ctx);
60 
61 /**
62  * Allocate and initialize a new path instance.
63  *
64  * The caller is responsible for deallocating the instance via
65  * bhnd_nvstore_path_free().
66  *
67  * @param	path_str	The path's canonical string representation.
68  * @param	path_len	The length of @p path_str.
69  *
70  * @retval non-NULL	success
71  * @retval NULL		if allocation fails.
72  */
73 bhnd_nvstore_path *
bhnd_nvstore_path_new(const char * path_str,size_t path_len)74 bhnd_nvstore_path_new(const char *path_str, size_t path_len)
75 {
76 	bhnd_nvstore_path *path;
77 
78 	/* Allocate new entry */
79 	path = bhnd_nv_malloc(sizeof(*path));
80 	if (path == NULL)
81 		return (NULL);
82 
83 	path->index = NULL;
84 	path->num_vars = 0;
85 
86 	path->pending = bhnd_nvram_plist_new();
87 	if (path->pending == NULL)
88 		goto failed;
89 
90 	path->path_str = bhnd_nv_strndup(path_str, path_len);
91 	if (path->path_str == NULL)
92 		goto failed;
93 
94 	return (path);
95 
96 failed:
97 	if (path->pending != NULL)
98 		bhnd_nvram_plist_release(path->pending);
99 
100 	if (path->path_str != NULL)
101 		bhnd_nv_free(path->path_str);
102 
103 	bhnd_nv_free(path);
104 
105 	return (NULL);
106 }
107 
108 /**
109  * Free an NVRAM path instance, releasing all associated resources.
110  */
111 void
bhnd_nvstore_path_free(struct bhnd_nvstore_path * path)112 bhnd_nvstore_path_free(struct bhnd_nvstore_path *path)
113 {
114 	/* Free the per-path index */
115 	if (path->index != NULL)
116 		bhnd_nvstore_index_free(path->index);
117 
118 	bhnd_nvram_plist_release(path->pending);
119 	bhnd_nv_free(path->path_str);
120 	bhnd_nv_free(path);
121 }
122 
123 /**
124  * Allocate and initialize a new index instance with @p capacity.
125  *
126  * The caller is responsible for deallocating the instance via
127  * bhnd_nvstore_index_free().
128  *
129  * @param	capacity	The maximum number of variables to be indexed.
130  *
131  * @retval non-NULL	success
132  * @retval NULL		if allocation fails.
133  */
134 bhnd_nvstore_index *
bhnd_nvstore_index_new(size_t capacity)135 bhnd_nvstore_index_new(size_t capacity)
136 {
137 	bhnd_nvstore_index	*index;
138 	size_t			 bytes;
139 
140 	/* Allocate and populate variable index */
141 	bytes = sizeof(struct bhnd_nvstore_index) + (sizeof(void *) * capacity);
142 	index = bhnd_nv_malloc(bytes);
143 	if (index == NULL) {
144 		BHND_NV_LOG("error allocating %zu byte index\n", bytes);
145 		return (NULL);
146 	}
147 
148 	index->count = 0;
149 	index->capacity = capacity;
150 
151 	return (index);
152 }
153 
154 /**
155  * Free an index instance, releasing all associated resources.
156  *
157  * @param	index	An index instance previously allocated via
158  *			bhnd_nvstore_index_new().
159  */
160 void
bhnd_nvstore_index_free(bhnd_nvstore_index * index)161 bhnd_nvstore_index_free(bhnd_nvstore_index *index)
162 {
163 	bhnd_nv_free(index);
164 }
165 
166 /**
167  * Append a new NVRAM variable's @p cookiep value to @p index.
168  *
169  * After one or more append requests, the index must be prepared via
170  * bhnd_nvstore_index_prepare() before any indexed lookups are performed.
171  *
172  * @param	sc	The NVRAM store from which NVRAM values will be queried.
173  * @param	index	The index to be modified.
174  * @param	cookiep	The cookiep value (as provided by the backing NVRAM
175  *			data instance of @p sc) to be included in @p index.
176  *
177  * @retval 0		success
178  * @retval ENOMEM	if appending an additional entry would exceed the
179  *			capacity of @p index.
180  */
181 int
bhnd_nvstore_index_append(struct bhnd_nvram_store * sc,bhnd_nvstore_index * index,void * cookiep)182 bhnd_nvstore_index_append(struct bhnd_nvram_store *sc,
183     bhnd_nvstore_index *index, void *cookiep)
184 {
185 	BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED);
186 
187 	if (index->count >= index->capacity)
188 		return (ENOMEM);
189 
190 	index->cookiep[index->count] = cookiep;
191 	index->count++;
192 	return (0);
193 }
194 
195 /* sort function for bhnd_nvstore_index_prepare() */
196 static int
bhnd_nvstore_idx_cmp(const void * lhs,const void * rhs,void * ctx)197 bhnd_nvstore_idx_cmp(const void *lhs, const void *rhs, void *ctx)
198 {
199 	struct bhnd_nvram_store	*sc;
200 	void			*l_cookiep, *r_cookiep;
201 	const char		*l_str, *r_str;
202 	const char		*l_name, *r_name;
203 	int			 order;
204 
205 	sc = ctx;
206 	l_cookiep = *(void * const *)lhs;
207 	r_cookiep = *(void * const *)rhs;
208 
209 	BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED);
210 
211 	/* Fetch string pointers from the cookiep values */
212 	l_str = bhnd_nvram_data_getvar_name(sc->data, l_cookiep);
213 	r_str = bhnd_nvram_data_getvar_name(sc->data, r_cookiep);
214 
215 	/* Trim device path prefixes */
216 	if (sc->data_caps & BHND_NVRAM_DATA_CAP_DEVPATHS) {
217 		l_name = bhnd_nvram_trim_path_name(l_str);
218 		r_name = bhnd_nvram_trim_path_name(r_str);
219 	} else {
220 		l_name = l_str;
221 		r_name = r_str;
222 	}
223 
224 	/* Perform comparison */
225 	order = strcmp(l_name, r_name);
226 	if (order != 0 || lhs == rhs)
227 		return (order);
228 
229 	/* If the backing data incorrectly contains variables with duplicate
230 	 * names, we need a sort order that provides stable behavior.
231 	 *
232 	 * Since Broadcom's own code varies wildly on this question, we just
233 	 * use a simple precedence rule: The first declaration of a variable
234 	 * takes precedence. */
235 	return (bhnd_nvram_data_getvar_order(sc->data, l_cookiep, r_cookiep));
236 }
237 
238 /**
239  * Prepare @p index for querying via bhnd_nvstore_index_lookup().
240  *
241  * After one or more append requests, the index must be prepared via
242  * bhnd_nvstore_index_prepare() before any indexed lookups are performed.
243  *
244  * @param	sc	The NVRAM store from which NVRAM values will be queried.
245  * @param	index	The index to be prepared.
246  *
247  * @retval 0		success
248  * @retval non-zero	if preparing @p index otherwise fails, a regular unix
249  *			error code will be returned.
250  */
251 int
bhnd_nvstore_index_prepare(struct bhnd_nvram_store * sc,bhnd_nvstore_index * index)252 bhnd_nvstore_index_prepare(struct bhnd_nvram_store *sc,
253     bhnd_nvstore_index *index)
254 {
255 	BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED);
256 
257 	/* Sort the index table */
258 	qsort_r(index->cookiep, index->count, sizeof(index->cookiep[0]),
259 	    bhnd_nvstore_idx_cmp, sc);
260 
261 	return (0);
262 }
263 
264 /**
265  * Return a borrowed reference to the root path node.
266  *
267  * @param	sc	The NVRAM store.
268  */
269 bhnd_nvstore_path *
bhnd_nvstore_get_root_path(struct bhnd_nvram_store * sc)270 bhnd_nvstore_get_root_path(struct bhnd_nvram_store *sc)
271 {
272 	BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED);
273 	return (sc->root_path);
274 }
275 
276 /**
277  * Return true if @p path is the root path node.
278  *
279  * @param	sc	The NVRAM store.
280  * @param	path	The path to query.
281  */
282 bool
bhnd_nvstore_is_root_path(struct bhnd_nvram_store * sc,bhnd_nvstore_path * path)283 bhnd_nvstore_is_root_path(struct bhnd_nvram_store *sc, bhnd_nvstore_path *path)
284 {
285 	BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED);
286 	return (sc->root_path == path);
287 }
288 
289 /**
290  * Return the update entry matching @p name in @p path, or NULL if no entry
291  * found.
292  *
293  * @param sc	The NVRAM store.
294  * @param path	The path to query.
295  * @param name	The NVRAM variable name to search for in @p path's update list.
296  *
297  * @retval non-NULL	success
298  * @retval NULL		if @p name is not found in @p path.
299  */
300 bhnd_nvram_prop *
bhnd_nvstore_path_get_update(struct bhnd_nvram_store * sc,bhnd_nvstore_path * path,const char * name)301 bhnd_nvstore_path_get_update(struct bhnd_nvram_store *sc,
302     bhnd_nvstore_path *path, const char *name)
303 {
304 	BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED);
305 	return (bhnd_nvram_plist_get_prop(path->pending, name));
306 }
307 
308 /**
309  * Register or remove an update record for @p name in @p path.
310  *
311  * @param sc	The NVRAM store.
312  * @param path	The path to be modified.
313  * @param name	The path-relative variable name to be modified.
314  * @param value	The new value. A value of BHND_NVRAM_TYPE_NULL denotes deletion.
315  *
316  * @retval 0		success
317  * @retval ENOMEM	if allocation fails.
318  * @retval ENOENT	if @p name is unknown.
319  * @retval EINVAL	if @p value is NULL, and deletion of @p is not
320  *			supported.
321  * @retval EINVAL	if @p value cannot be converted to a supported value
322  *			type.
323  */
324 int
bhnd_nvstore_path_register_update(struct bhnd_nvram_store * sc,bhnd_nvstore_path * path,const char * name,bhnd_nvram_val * value)325 bhnd_nvstore_path_register_update(struct bhnd_nvram_store *sc,
326     bhnd_nvstore_path *path, const char *name, bhnd_nvram_val *value)
327 {
328 	bhnd_nvram_val		*prop_val;
329 	const char		*full_name;
330 	void			*cookiep;
331 	char			*namebuf;
332 	int			 error;
333 	bool			 nvram_committed;
334 
335 	namebuf = NULL;
336 	prop_val = NULL;
337 
338 	/* Determine whether the variable is currently defined in the
339 	 * backing NVRAM data, and derive its full path-prefixed name */
340 	nvram_committed = false;
341 	cookiep = bhnd_nvstore_path_data_lookup(sc, path, name);
342 	if (cookiep != NULL) {
343 		/* Variable is defined in the backing data */
344 		nvram_committed = true;
345 
346 		/* Use the existing variable name */
347 		full_name = bhnd_nvram_data_getvar_name(sc->data, cookiep);
348 	} else if (path == sc->root_path) {
349 		/* No prefix required for root path */
350 		full_name = name;
351 	} else {
352 		bhnd_nvstore_alias	*alias;
353 		int			 len;
354 
355 		/* New variable is being set; we need to determine the
356 		 * appropriate path prefix */
357 		alias = bhnd_nvstore_find_alias(sc, path->path_str);
358 		if (alias != NULL) {
359 			/* Use <alias>:name */
360 			len = bhnd_nv_asprintf(&namebuf, "%lu:%s", alias->alias,
361 			    name);
362 		} else {
363 			/* Use path/name */
364 			len = bhnd_nv_asprintf(&namebuf, "%s/%s",
365 			    path->path_str, name);
366 		}
367 
368 		if (len < 0)
369 			return (ENOMEM);
370 
371 		full_name = namebuf;
372 	}
373 
374 	/* Allow the data store to filter the NVRAM operation */
375 	if (bhnd_nvram_val_type(value) == BHND_NVRAM_TYPE_NULL) {
376 		error = bhnd_nvram_data_filter_unsetvar(sc->data, full_name);
377 		if (error) {
378 			BHND_NV_LOG("cannot unset property %s: %d\n", full_name,
379 			    error);
380 			goto cleanup;
381 		}
382 
383 		if ((prop_val = bhnd_nvram_val_copy(value)) == NULL) {
384 			error = ENOMEM;
385 			goto cleanup;
386 		}
387 	} else {
388 		error = bhnd_nvram_data_filter_setvar(sc->data, full_name,
389 		    value,  &prop_val);
390 		if (error) {
391 			BHND_NV_LOG("cannot set property %s: %d\n", full_name,
392 			    error);
393 			goto cleanup;
394 		}
395 	}
396 
397 	/* Add relative variable name to the per-path update list */
398 	if (bhnd_nvram_val_type(value) == BHND_NVRAM_TYPE_NULL &&
399 	    !nvram_committed)
400 	{
401 		/* This is a deletion request for a variable not defined in
402 		 * out backing store; we can simply remove the corresponding
403 		 * update entry. */
404 		bhnd_nvram_plist_remove(path->pending, name);
405 	} else {
406 		/* Update or append a pending update entry */
407 		error = bhnd_nvram_plist_replace_val(path->pending, name,
408 		    prop_val);
409 		if (error)
410 			goto cleanup;
411 	}
412 
413 	/* Success */
414 	error = 0;
415 
416 cleanup:
417 	if (namebuf != NULL)
418 		bhnd_nv_free(namebuf);
419 
420 	if (prop_val != NULL)
421 		bhnd_nvram_val_release(prop_val);
422 
423 	return (error);
424 }
425 
426 /**
427  * Iterate over all variable cookiep values retrievable from the backing
428  * data store in @p path.
429  *
430  * @warning Pending updates in @p path are ignored by this function.
431  *
432  * @param		sc	The NVRAM store.
433  * @param		path	The NVRAM path to be iterated.
434  * @param[in,out]	indexp	A pointer to an opaque indexp value previously
435  *				returned by bhnd_nvstore_path_data_next(), or a
436  *				NULL value to begin iteration.
437  *
438  * @return Returns the next variable name, or NULL if there are no more
439  * variables defined in @p path.
440  */
441 void *
bhnd_nvstore_path_data_next(struct bhnd_nvram_store * sc,bhnd_nvstore_path * path,void ** indexp)442 bhnd_nvstore_path_data_next(struct bhnd_nvram_store *sc,
443      bhnd_nvstore_path *path, void **indexp)
444 {
445 	void **index_ref;
446 
447 	BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED);
448 
449 	/* No index */
450 	if (path->index == NULL) {
451 		/* An index is required for all non-empty, non-root path
452 		 * instances */
453 		BHND_NV_ASSERT(bhnd_nvstore_is_root_path(sc, path),
454 		    ("missing index for non-root path %s", path->path_str));
455 
456 		/* Iterate NVRAM data directly, using the NVRAM data's cookiep
457 		 * value as our indexp context */
458 		if ((bhnd_nvram_data_next(sc->data, indexp)) == NULL)
459 			return (NULL);
460 
461 		return (*indexp);
462 	}
463 
464 	/* Empty index */
465 	if (path->index->count == 0)
466 		return (NULL);
467 
468 	if (*indexp == NULL) {
469 		/* First index entry */
470 		index_ref = &path->index->cookiep[0];
471 	} else {
472 		size_t idxpos;
473 
474 		/* Advance to next index entry */
475 		index_ref = *indexp;
476 		index_ref++;
477 
478 		/* Hit end of index? */
479 		BHND_NV_ASSERT(index_ref > path->index->cookiep,
480 		    ("invalid indexp"));
481 
482 		idxpos = (index_ref - path->index->cookiep);
483 		if (idxpos >= path->index->count)
484 			return (NULL);
485 	}
486 
487 	/* Provide new index position */
488 	*indexp = index_ref;
489 
490 	/* Return the data's cookiep value */
491 	return (*index_ref);
492 }
493 
494 /**
495  * Perform an lookup of @p name in the backing NVRAM data for @p path,
496  * returning the associated cookiep value, or NULL if the variable is not found
497  * in the backing NVRAM data.
498  *
499  * @warning Pending updates in @p path are ignored by this function.
500  *
501  * @param	sc	The NVRAM store from which NVRAM values will be queried.
502  * @param	path	The path to be queried.
503  * @param	name	The variable name to be queried.
504  *
505  * @retval non-NULL	success
506  * @retval NULL		if @p name is not found in @p index.
507  */
508 void *
bhnd_nvstore_path_data_lookup(struct bhnd_nvram_store * sc,bhnd_nvstore_path * path,const char * name)509 bhnd_nvstore_path_data_lookup(struct bhnd_nvram_store *sc,
510     bhnd_nvstore_path *path, const char *name)
511 {
512 	BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED);
513 
514 	/* No index */
515 	if (path->index == NULL) {
516 		/* An index is required for all non-empty, non-root path
517 		 * instances */
518 		BHND_NV_ASSERT(bhnd_nvstore_is_root_path(sc, path),
519 		    ("missing index for non-root path %s", path->path_str));
520 
521 		/* Look up directly in NVRAM data */
522 		return (bhnd_nvram_data_find(sc->data, name));
523 	}
524 
525 	/* Otherwise, delegate to an index-based lookup */
526 	return (bhnd_nvstore_index_lookup(sc, path->index, name));
527 }
528 
529 /**
530  * Perform an index lookup of @p name, returning the associated cookiep
531  * value, or NULL if the variable does not exist.
532  *
533  * @param	sc	The NVRAM store from which NVRAM values will be queried.
534  * @param	index	The index to be queried.
535  * @param	name	The variable name to be queried.
536  *
537  * @retval non-NULL	success
538  * @retval NULL		if @p name is not found in @p index.
539  */
540 void *
bhnd_nvstore_index_lookup(struct bhnd_nvram_store * sc,bhnd_nvstore_index * index,const char * name)541 bhnd_nvstore_index_lookup(struct bhnd_nvram_store *sc,
542     bhnd_nvstore_index *index, const char *name)
543 {
544 	void		*cookiep;
545 	const char	*indexed_name;
546 	size_t		 min, mid, max;
547 	uint32_t	 data_caps;
548 	int		 order;
549 
550 	BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED);
551 	BHND_NV_ASSERT(index != NULL, ("NULL index"));
552 
553 	/*
554 	 * Locate the requested variable using a binary search.
555 	 */
556 	if (index->count == 0)
557 		return (NULL);
558 
559 	data_caps = sc->data_caps;
560 	min = 0;
561 	max = index->count - 1;
562 
563 	while (max >= min) {
564 		/* Select midpoint */
565 		mid = (min + max) / 2;
566 		cookiep = index->cookiep[mid];
567 
568 		/* Fetch variable name */
569 		indexed_name = bhnd_nvram_data_getvar_name(sc->data, cookiep);
570 
571 		/* Trim any path prefix */
572 		if (data_caps & BHND_NVRAM_DATA_CAP_DEVPATHS)
573 			indexed_name = bhnd_nvram_trim_path_name(indexed_name);
574 
575 		/* Determine which side of the partition to search */
576 		order = strcmp(indexed_name, name);
577 		if (order < 0) {
578 			/* Search upper partition */
579 			min = mid + 1;
580 		} else if (order > 0) {
581 			/* Search (non-empty) lower partition */
582 			if (mid == 0)
583 				break;
584 			max = mid - 1;
585 		} else if (order == 0) {
586 			size_t	idx;
587 
588 			/*
589 			 * Match found.
590 			 *
591 			 * If this happens to be a key with multiple definitions
592 			 * in the backing store, we need to find the entry with
593 			 * the highest declaration precedence.
594 			 *
595 			 * Duplicates are sorted in order of descending
596 			 * precedence; to find the highest precedence entry,
597 			 * we search backwards through the index.
598 			 */
599 			idx = mid;
600 			while (idx > 0) {
601 				void		*dup_cookiep;
602 				const char	*dup_name;
603 
604 				/* Fetch preceding index entry */
605 				idx--;
606 				dup_cookiep = index->cookiep[idx];
607 				dup_name = bhnd_nvram_data_getvar_name(sc->data,
608 				    dup_cookiep);
609 
610 				/* Trim any path prefix */
611 				if (data_caps & BHND_NVRAM_DATA_CAP_DEVPATHS) {
612 					dup_name = bhnd_nvram_trim_path_name(
613 					    dup_name);
614 				}
615 
616 				/* If no match, current cookiep is the variable
617 				 * definition with the highest precedence */
618 				if (strcmp(indexed_name, dup_name) != 0)
619 					return (cookiep);
620 
621 				/* Otherwise, prefer this earlier definition,
622 				 * and keep searching for a higher-precedence
623 				 * definitions */
624 				cookiep = dup_cookiep;
625 			}
626 
627 			return (cookiep);
628 		}
629 	}
630 
631 	/* Not found */
632 	return (NULL);
633 }
634 
635 /**
636  * Return the device path entry registered for @p path, if any.
637  *
638  * @param	sc		The NVRAM store to be queried.
639  * @param	path		The device path to search for.
640  * @param	path_len	The length of @p path.
641  *
642  * @retval non-NULL	if found.
643  * @retval NULL		if not found.
644  */
645 bhnd_nvstore_path *
bhnd_nvstore_get_path(struct bhnd_nvram_store * sc,const char * path,size_t path_len)646 bhnd_nvstore_get_path(struct bhnd_nvram_store *sc, const char *path,
647     size_t path_len)
648 {
649 	bhnd_nvstore_path_list	*plist;
650 	bhnd_nvstore_path	*p;
651 	uint32_t		 h;
652 
653 	BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED);
654 
655 	/* Use hash lookup */
656 	h = hash32_strn(path, path_len, HASHINIT);
657 	plist = &sc->paths[h % nitems(sc->paths)];
658 
659 	LIST_FOREACH(p, plist, np_link) {
660 		/* Check for prefix match */
661 		if (strncmp(p->path_str, path, path_len) != 0)
662 			continue;
663 
664 		/* Check for complete match */
665 		if (strnlen(path, path_len) != strlen(p->path_str))
666 			continue;
667 
668 		return (p);
669 	}
670 
671 	/* Not found */
672 	return (NULL);
673 }
674 
675 /**
676  * Resolve @p aval to its corresponding device path entry, if any.
677  *
678  * @param	sc		The NVRAM store to be queried.
679  * @param	aval		The device path alias value to search for.
680  *
681  * @retval non-NULL	if found.
682  * @retval NULL		if not found.
683  */
684 bhnd_nvstore_path *
bhnd_nvstore_resolve_path_alias(struct bhnd_nvram_store * sc,u_long aval)685 bhnd_nvstore_resolve_path_alias(struct bhnd_nvram_store *sc, u_long aval)
686 {
687 	bhnd_nvstore_alias *alias;
688 
689 	BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED);
690 
691 	/* Fetch alias entry */
692 	if ((alias = bhnd_nvstore_get_alias(sc, aval)) == NULL)
693 		return (NULL);
694 
695 	return (alias->path);
696 }
697 
698 /**
699  * Register a device path entry for the path referenced by variable name
700  * @p info, if any.
701  *
702  * @param	sc		The NVRAM store to be updated.
703  * @param	info		The NVRAM variable name info.
704  * @param	cookiep		The NVRAM variable's cookiep value.
705  *
706  * @retval 0		if the path was successfully registered, or an identical
707  *			path or alias entry exists.
708  * @retval EEXIST	if a conflicting entry already exists for the path or
709  *			alias referenced by @p info.
710  * @retval ENOENT	if @p info contains a dangling alias reference.
711  * @retval EINVAL	if @p info contains an unsupported bhnd_nvstore_var_type
712  *			and bhnd_nvstore_path_type combination.
713  * @retval ENOMEM	if allocation fails.
714  */
715 int
bhnd_nvstore_var_register_path(struct bhnd_nvram_store * sc,bhnd_nvstore_name_info * info,void * cookiep)716 bhnd_nvstore_var_register_path(struct bhnd_nvram_store *sc,
717     bhnd_nvstore_name_info *info, void *cookiep)
718 {
719 	switch (info->type) {
720 	case BHND_NVSTORE_VAR:
721 		/* Variable */
722 		switch (info->path_type) {
723 		case BHND_NVSTORE_PATH_STRING:
724 			/* Variable contains a full path string
725 			 * (pci/1/1/varname); register the path */
726 			return (bhnd_nvstore_register_path(sc,
727 			    info->path.str.value, info->path.str.value_len));
728 
729 		case BHND_NVSTORE_PATH_ALIAS:
730 			/* Variable contains an alias reference (0:varname).
731 			 * There's no path to register */
732 			return (0);
733 		}
734 
735 		BHND_NV_PANIC("unsupported path type %d", info->path_type);
736 		break;
737 
738 	case BHND_NVSTORE_ALIAS_DECL:
739 		/* Alias declaration */
740 		return (bhnd_nvstore_register_alias(sc, info, cookiep));
741 	}
742 
743 	BHND_NV_PANIC("unsupported var type %d", info->type);
744 }
745 
746 /**
747  * Resolve the device path entry referenced by @p info.
748  *
749  * @param	sc		The NVRAM store to be updated.
750  * @param	info		Variable name information descriptor containing
751  *				the path or path alias to be resolved.
752  *
753  * @retval non-NULL	if found.
754  * @retval NULL		if not found.
755  */
756 bhnd_nvstore_path *
bhnd_nvstore_var_get_path(struct bhnd_nvram_store * sc,bhnd_nvstore_name_info * info)757 bhnd_nvstore_var_get_path(struct bhnd_nvram_store *sc,
758     bhnd_nvstore_name_info *info)
759 {
760 	switch (info->path_type) {
761 	case BHND_NVSTORE_PATH_STRING:
762 		return (bhnd_nvstore_get_path(sc, info->path.str.value,
763 		    info->path.str.value_len));
764 	case BHND_NVSTORE_PATH_ALIAS:
765 		return (bhnd_nvstore_resolve_path_alias(sc,
766 		    info->path.alias.value));
767 	}
768 
769 	BHND_NV_PANIC("unsupported path type %d", info->path_type);
770 }
771 
772 /**
773  * Return the device path alias entry registered for @p alias_val, if any.
774  *
775  * @param	sc		The NVRAM store to be queried.
776  * @param	alias_val	The alias value to search for.
777  *
778  * @retval non-NULL	if found.
779  * @retval NULL		if not found.
780  */
781 bhnd_nvstore_alias *
bhnd_nvstore_get_alias(struct bhnd_nvram_store * sc,u_long alias_val)782 bhnd_nvstore_get_alias(struct bhnd_nvram_store *sc, u_long alias_val)
783 {
784 	bhnd_nvstore_alias_list	*alist;
785 	bhnd_nvstore_alias	*alias;
786 
787 	BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED);
788 
789 	/* Can use hash lookup */
790 	alist = &sc->aliases[alias_val % nitems(sc->aliases)];
791 	LIST_FOREACH(alias, alist, na_link) {
792 		if (alias->alias == alias_val)
793 			return (alias);
794 	}
795 
796 	/* Not found */
797 	return (NULL);
798 }
799 
800 /**
801  * Return the device path alias entry registered for @p path, if any.
802  *
803  * @param	sc	The NVRAM store to be queried.
804  * @param	path	The alias path to search for.
805  *
806  * @retval non-NULL	if found.
807  * @retval NULL		if not found.
808  */
809 bhnd_nvstore_alias *
bhnd_nvstore_find_alias(struct bhnd_nvram_store * sc,const char * path)810 bhnd_nvstore_find_alias(struct bhnd_nvram_store *sc, const char *path)
811 {
812 	bhnd_nvstore_alias *alias;
813 
814 	BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED);
815 
816 	/* Have to scan the full table */
817 	for (size_t i = 0; i < nitems(sc->aliases); i++) {
818 		LIST_FOREACH(alias, &sc->aliases[i], na_link) {
819 			if (strcmp(alias->path->path_str, path) == 0)
820 				return (alias);
821 		}
822 	}
823 
824 	/* Not found */
825 	return (NULL);
826 }
827 
828 /**
829  * Register a device path entry for @p path.
830  *
831  * @param	sc		The NVRAM store to be updated.
832  * @param	path_str	The absolute device path string.
833  * @param	path_len	The length of @p path_str.
834  *
835  * @retval 0		if the path was successfully registered, or an identical
836  *			path/alias entry already exists.
837  * @retval ENOMEM	if allocation fails.
838  */
839 int
bhnd_nvstore_register_path(struct bhnd_nvram_store * sc,const char * path_str,size_t path_len)840 bhnd_nvstore_register_path(struct bhnd_nvram_store *sc, const char *path_str,
841     size_t path_len)
842 {
843 	bhnd_nvstore_path_list	*plist;
844 	bhnd_nvstore_path	*path;
845 	uint32_t		 h;
846 
847 	BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED);
848 
849 	/* Already exists? */
850 	if (bhnd_nvstore_get_path(sc, path_str, path_len) != NULL)
851 		return (0);
852 
853 	/* Can't represent more than SIZE_MAX paths */
854 	if (sc->num_paths == SIZE_MAX)
855 		return (ENOMEM);
856 
857 	/* Allocate new entry */
858 	path = bhnd_nvstore_path_new(path_str, path_len);
859 	if (path == NULL)
860 		return (ENOMEM);
861 
862 	/* Insert in path hash table */
863 	h = hash32_str(path->path_str, HASHINIT);
864 	plist = &sc->paths[h % nitems(sc->paths)];
865 	LIST_INSERT_HEAD(plist, path, np_link);
866 
867 	/* Increment path count */
868 	sc->num_paths++;
869 
870 	return (0);
871 }
872 
873 /**
874  * Register a device path alias for an NVRAM 'devpathX' variable.
875  *
876  * The path value for the alias will be fetched from the backing NVRAM data.
877  *
878  * @param	sc	The NVRAM store to be updated.
879  * @param	info	The NVRAM variable name info.
880  * @param	cookiep	The NVRAM variable's cookiep value.
881  *
882  * @retval 0		if the alias was successfully registered, or an
883  *			identical alias entry exists.
884  * @retval EEXIST	if a conflicting alias or path entry already exists.
885  * @retval EINVAL	if @p info is not a BHND_NVSTORE_ALIAS_DECL or does
886  *			not contain a BHND_NVSTORE_PATH_ALIAS entry.
887  * @retval ENOMEM	if allocation fails.
888  */
889 int
bhnd_nvstore_register_alias(struct bhnd_nvram_store * sc,const bhnd_nvstore_name_info * info,void * cookiep)890 bhnd_nvstore_register_alias(struct bhnd_nvram_store *sc,
891     const bhnd_nvstore_name_info *info, void *cookiep)
892 {
893 	bhnd_nvstore_alias_list	*alist;
894 	bhnd_nvstore_alias	*alias;
895 	bhnd_nvstore_path	*path;
896 	char			*path_str;
897 	size_t			 path_len;
898 	int			 error;
899 
900 	BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED);
901 
902 	path_str = NULL;
903 	alias = NULL;
904 
905 	/* Can't represent more than SIZE_MAX aliases */
906 	if (sc->num_aliases == SIZE_MAX)
907 		return (ENOMEM);
908 
909 	/* Must be an alias declaration */
910 	if (info->type != BHND_NVSTORE_ALIAS_DECL)
911 		return (EINVAL);
912 
913 	if (info->path_type != BHND_NVSTORE_PATH_ALIAS)
914 		return (EINVAL);
915 
916 	/* Fetch the devpath variable's value length */
917 	error = bhnd_nvram_data_getvar(sc->data, cookiep, NULL, &path_len,
918 	    BHND_NVRAM_TYPE_STRING);
919 	if (error)
920 		return (ENOMEM);
921 
922 	/* Allocate path string buffer */
923 	if ((path_str = bhnd_nv_malloc(path_len)) == NULL)
924 		return (ENOMEM);
925 
926 	/* Decode to our new buffer */
927 	error = bhnd_nvram_data_getvar(sc->data, cookiep, path_str, &path_len,
928 	    BHND_NVRAM_TYPE_STRING);
929 	if (error)
930 		goto failed;
931 
932 	/* Trim trailing '/' character(s) from the path length */
933 	path_len = strnlen(path_str, path_len);
934 	while (path_len > 0 && path_str[path_len-1] == '/') {
935 		path_str[path_len-1] = '\0';
936 		path_len--;
937 	}
938 
939 	/* Is a conflicting alias entry already registered for this alias
940 	 * value? */
941 	alias = bhnd_nvstore_get_alias(sc, info->path.alias.value);
942 	if (alias != NULL) {
943 		if (alias->cookiep != cookiep ||
944 		    strcmp(alias->path->path_str, path_str) != 0)
945 		{
946 			error = EEXIST;
947 			goto failed;
948 		}
949 	}
950 
951 	/* Is a conflicting entry already registered for the alias path? */
952 	if ((alias = bhnd_nvstore_find_alias(sc, path_str)) != NULL) {
953 		if (alias->alias != info->path.alias.value ||
954 		    alias->cookiep != cookiep ||
955 		    strcmp(alias->path->path_str, path_str) != 0)
956 		{
957 			error = EEXIST;
958 			goto failed;
959 		}
960 	}
961 
962 	/* Get (or register) the target path entry */
963 	path = bhnd_nvstore_get_path(sc, path_str, path_len);
964 	if (path == NULL) {
965 		error = bhnd_nvstore_register_path(sc, path_str, path_len);
966 		if (error)
967 			goto failed;
968 
969 		path = bhnd_nvstore_get_path(sc, path_str, path_len);
970 		BHND_NV_ASSERT(path != NULL, ("missing registered path"));
971 	}
972 
973 	/* Allocate alias entry */
974 	alias = bhnd_nv_calloc(1, sizeof(*alias));
975 	if (alias == NULL) {
976 		error = ENOMEM;
977 		goto failed;
978 	}
979 
980 	alias->path = path;
981 	alias->cookiep = cookiep;
982 	alias->alias = info->path.alias.value;
983 
984 	/* Insert in alias hash table */
985 	alist = &sc->aliases[alias->alias % nitems(sc->aliases)];
986 	LIST_INSERT_HEAD(alist, alias, na_link);
987 
988 	/* Increment alias count */
989 	sc->num_aliases++;
990 
991 	bhnd_nv_free(path_str);
992 	return (0);
993 
994 failed:
995 	if (path_str != NULL)
996 		bhnd_nv_free(path_str);
997 
998 	if (alias != NULL)
999 		bhnd_nv_free(alias);
1000 
1001 	return (error);
1002 }
1003 
1004 /**
1005  * If @p child is equal to or a child path of @p parent, return a pointer to
1006  * @p child's path component(s) relative to @p parent; otherwise, return NULL.
1007  */
1008 const char *
bhnd_nvstore_parse_relpath(const char * parent,const char * child)1009 bhnd_nvstore_parse_relpath(const char *parent, const char *child)
1010 {
1011 	size_t prefix_len;
1012 
1013 	/* All paths have an implicit leading '/'; this allows us to treat
1014 	 * our manufactured root path of "/" as a prefix to all NVRAM-defined
1015 	 * paths (which do not necessarily include a leading '/' */
1016 	if (*parent == '/')
1017 		parent++;
1018 
1019 	if (*child == '/')
1020 		child++;
1021 
1022 	/* Is parent a prefix of child? */
1023 	prefix_len = strlen(parent);
1024 	if (strncmp(parent, child, prefix_len) != 0)
1025 		return (NULL);
1026 
1027 	/* A zero-length prefix matches everything */
1028 	if (prefix_len == 0)
1029 		return (child);
1030 
1031 	/* Is child equal to parent? */
1032 	if (child[prefix_len] == '\0')
1033 		return (child + prefix_len);
1034 
1035 	/* Is child actually a child of parent? */
1036 	if (child[prefix_len] == '/')
1037 		return (child + prefix_len + 1);
1038 
1039 	/* No match (e.g. parent=/foo..., child=/fooo...) */
1040 	return (NULL);
1041 }
1042 
1043 /**
1044  * Parse a raw NVRAM variable name and return its @p entry_type, its
1045  * type-specific @p prefix (e.g. '0:', 'pci/1/1', 'devpath'), and its
1046  * type-specific @p suffix (e.g. 'varname', '0').
1047  *
1048  * @param	name		The NVRAM variable name to be parsed. This
1049  *				value must remain valid for the lifetime of
1050  *				@p info.
1051  * @param	type		The NVRAM name type -- either INTERNAL for names
1052  *				parsed from backing NVRAM data, or EXTERNAL for
1053  *				names provided by external NVRAM store clients.
1054  * @param	data_caps	The backing NVRAM data capabilities
1055  *				(see bhnd_nvram_data_caps()).
1056  * @param[out]	info		On success, the parsed variable name info.
1057  *
1058  * @retval 0		success
1059  * @retval non-zero	if parsing @p name otherwise fails, a regular unix
1060  *			error code will be returned.
1061  */
1062 int
bhnd_nvstore_parse_name_info(const char * name,bhnd_nvstore_name_type type,uint32_t data_caps,bhnd_nvstore_name_info * info)1063 bhnd_nvstore_parse_name_info(const char *name, bhnd_nvstore_name_type type,
1064     uint32_t data_caps, bhnd_nvstore_name_info *info)
1065 {
1066 	const char	*p;
1067 	char		*endp;
1068 
1069 	/* Skip path parsing? */
1070 	if (data_caps & BHND_NVRAM_DATA_CAP_DEVPATHS) {
1071 		/* devpath declaration? (devpath0=pci/1/1) */
1072 		if (strncmp(name, "devpath", strlen("devpath")) == 0) {
1073 			u_long alias;
1074 
1075 			/* Perform standard validation on the relative
1076 			 * variable name */
1077 			if (type != BHND_NVSTORE_NAME_INTERNAL &&
1078 			    !bhnd_nvram_validate_name(name))
1079 			{
1080 				return (ENOENT);
1081 			}
1082 
1083 			/* Parse alias value that should follow a 'devpath'
1084 			 * prefix */
1085 			p = name + strlen("devpath");
1086 			alias = strtoul(p, &endp, 10);
1087 			if (endp != p && *endp == '\0') {
1088 				info->type = BHND_NVSTORE_ALIAS_DECL;
1089 				info->path_type = BHND_NVSTORE_PATH_ALIAS;
1090 				info->name = name;
1091 				info->path.alias.value = alias;
1092 
1093 				return (0);
1094 			}
1095 		}
1096 
1097 		/* device aliased variable? (0:varname) */
1098 		if (bhnd_nv_isdigit(*name)) {
1099 			u_long alias;
1100 
1101 			/* Parse '0:' alias prefix */
1102 			alias = strtoul(name, &endp, 10);
1103 			if (endp != name && *endp == ':') {
1104 				/* Perform standard validation on the relative
1105 				 * variable name */
1106 				if (type != BHND_NVSTORE_NAME_INTERNAL &&
1107 				    !bhnd_nvram_validate_name(name))
1108 				{
1109 					return (ENOENT);
1110 				}
1111 
1112 				info->type = BHND_NVSTORE_VAR;
1113 				info->path_type = BHND_NVSTORE_PATH_ALIAS;
1114 
1115 				/* name follows 0: prefix */
1116 				info->name = endp + 1;
1117 				info->path.alias.value = alias;
1118 
1119 				return (0);
1120 			}
1121 		}
1122 
1123 		/* device variable? (pci/1/1/varname) */
1124 		if ((p = strrchr(name, '/')) != NULL) {
1125 			const char	*path, *relative_name;
1126 			size_t		 path_len;
1127 
1128 			/* Determine the path length; 'p' points at the last
1129 			 * path separator in 'name' */
1130 			path_len = p - name;
1131 			path = name;
1132 
1133 			/* The relative variable name directly follows the
1134 			 * final path separator '/' */
1135 			relative_name = path + path_len + 1;
1136 
1137 			/* Now that we calculated the name offset, exclude all
1138 			 * trailing '/' characters from the path length */
1139 			while (path_len > 0 && path[path_len-1] == '/')
1140 				path_len--;
1141 
1142 			/* Perform standard validation on the relative
1143 			 * variable name */
1144 			if (type != BHND_NVSTORE_NAME_INTERNAL &&
1145 			    !bhnd_nvram_validate_name(relative_name))
1146 			{
1147 				return (ENOENT);
1148 			}
1149 
1150 			/* Initialize result with pointers into the name
1151 			 * buffer */
1152 			info->type = BHND_NVSTORE_VAR;
1153 			info->path_type = BHND_NVSTORE_PATH_STRING;
1154 			info->name = relative_name;
1155 			info->path.str.value = path;
1156 			info->path.str.value_len = path_len;
1157 
1158 			return (0);
1159 		}
1160 	}
1161 
1162 	/* If all other parsing fails, the result is a simple variable with
1163 	 * an implicit path of "/" */
1164 	if (type != BHND_NVSTORE_NAME_INTERNAL &&
1165 	    !bhnd_nvram_validate_name(name))
1166 	{
1167 		/* Invalid relative name */
1168 		return (ENOENT);
1169 	}
1170 
1171 	info->type = BHND_NVSTORE_VAR;
1172 	info->path_type = BHND_NVSTORE_PATH_STRING;
1173 	info->name = name;
1174 	info->path.str.value = BHND_NVSTORE_ROOT_PATH;
1175 	info->path.str.value_len = BHND_NVSTORE_ROOT_PATH_LEN;
1176 
1177 	return (0);
1178 }
1179