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