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