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
62static 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 */
77bhnd_nvstore_path *
78bhnd_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
100failed:
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 */
115void
116bhnd_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 */
138bhnd_nvstore_index *
139bhnd_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 */
164void
165bhnd_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 */
185int
186bhnd_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() */
200static int
201bhnd_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 */
255int
256bhnd_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 */
273bhnd_nvstore_path *
274bhnd_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 */
286bool
287bhnd_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 */
304bhnd_nvram_prop *
305bhnd_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 */
328int
329bhnd_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
420cleanup:
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 */
445void *
446bhnd_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 */
512void *
513bhnd_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 */
544void *
545bhnd_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 */
649bhnd_nvstore_path *
650bhnd_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 */
688bhnd_nvstore_path *
689bhnd_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 */
719int
720bhnd_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 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 */
760bhnd_nvstore_path *
761bhnd_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 */
785bhnd_nvstore_alias *
786bhnd_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 */
813bhnd_nvstore_alias *
814bhnd_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 */
843int
844bhnd_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 */
893int
894bhnd_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
998failed:
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 */
1012const char *
1013bhnd_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 */
1066int
1067bhnd_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