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