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 
35 #ifdef _KERNEL
36 
37 #include <sys/systm.h>
38 
39 #else /* !_KERNEL */
40 
41 #include <errno.h>
42 #include <string.h>
43 
44 #endif /* _KERNEL */
45 
46 #include "bhnd_nvram_private.h"
47 #include "bhnd_nvram_valuevar.h"
48 
49 /**
50  * Validate the alignment of a value of @p type.
51  *
52  * @param	inp	The value data.
53  * @param	ilen	The value length, in bytes.
54  * @param	itype	The value type.
55  *
56  * @retval 0		success
57  * @retval EFTYPE	if @p type is not an array type, and @p len is not
58  *			equal to the size of a single element of @p type.
59  * @retval EFAULT	if @p data is not correctly aligned to the required
60  *			host alignment.
61  * @retval EFAULT	if @p len is not aligned to the @p type width.
62  */
63 int
64 bhnd_nvram_value_check_aligned(const void *inp, size_t ilen,
65     bhnd_nvram_type itype)
66 {
67 	size_t align, width;
68 
69 	/* As a special case, NULL values have no alignment, but must
70 	 * always have a length of zero */
71 	if (itype == BHND_NVRAM_TYPE_NULL) {
72 		if (ilen != 0)
73 			return (EFAULT);
74 
75 		return (0);
76 	}
77 
78 	/* Check pointer alignment against the required host alignment */
79 	align = bhnd_nvram_type_host_align(itype);
80 	BHND_NV_ASSERT(align != 0, ("invalid zero alignment"));
81 	if ((uintptr_t)inp % align != 0)
82 		return (EFAULT);
83 
84 	/* If type is not fixed width, nothing else to check */
85 	width = bhnd_nvram_type_width(itype);
86 	if (width == 0)
87 		return (0);
88 
89 	/* Length must be aligned to the element width */
90 	if (ilen % width != 0)
91 		return (EFAULT);
92 
93 	/* If the type is not an array type, the length must be equal to the
94 	 * size of a single element of @p type. */
95 	if (!bhnd_nvram_is_array_type(itype) && ilen != width)
96 			return (EFTYPE);
97 
98 	return (0);
99 }
100 
101 /**
102  * Calculate the number of elements represented by a value of @p ilen bytes
103  * with @p itype.
104  *
105  * @param	inp	The value data.
106  * @param	ilen	The value length.
107  * @param	itype	The value type.
108  * @param[out]	nelem	On success, the number of elements.
109  *
110  * @retval 0		success
111  * @retval EINVAL	if @p inp is NULL and the element count of @p itype
112  *			cannot be determined without parsing the value data.
113  * @retval EFTYPE	if @p itype is not an array type, and @p ilen is not
114  *			equal to the size of a single element of @p itype.
115  * @retval EFAULT	if @p ilen is not correctly aligned for elements of
116  *			@p itype.
117  */
118 int
119 bhnd_nvram_value_nelem(const void *inp, size_t ilen, bhnd_nvram_type itype,
120     size_t *nelem)
121 {
122 	int	error;
123 
124 	BHND_NV_ASSERT(inp != NULL, ("NULL inp"));
125 
126 	/* Check alignment */
127 	if ((error = bhnd_nvram_value_check_aligned(inp, ilen, itype)))
128 		return (error);
129 
130 	switch (itype) {
131 	case BHND_NVRAM_TYPE_DATA:
132 		/* Always exactly one element */
133 		*nelem = 1;
134 		return (0);
135 
136 	case BHND_NVRAM_TYPE_NULL:
137 		/* Must be zero length */
138 		if (ilen != 0)
139 			return (EFAULT);
140 
141 		/* Always exactly one element */
142 		*nelem = 1;
143 		return (0);
144 
145 	case BHND_NVRAM_TYPE_STRING:
146 		/* Always exactly one element */
147 		*nelem = 1;
148 		return (0);
149 
150 	case BHND_NVRAM_TYPE_STRING_ARRAY: {
151 		const char	*p;
152 		size_t		 nleft;
153 
154 		/* Iterate over the NUL-terminated strings to calculate
155 		 * total element count */
156 		p = inp;
157 		nleft = ilen;
158 		*nelem = 0;
159 		while (nleft > 0) {
160 			size_t slen;
161 
162 			/* Increment element count */
163 			(*nelem)++;
164 
165 			/* Determine string length */
166 			slen = strnlen(p, nleft);
167 			nleft -= slen;
168 
169 			/* Advance input */
170 			p += slen;
171 
172 			/* Account for trailing NUL, if we haven't hit the end
173 			 * of the input */
174 			if (nleft > 0) {
175 				nleft--;
176 				p++;
177 			}
178 		}
179 
180 		return (0);
181 	}
182 
183 	case BHND_NVRAM_TYPE_UINT8_ARRAY:
184 	case BHND_NVRAM_TYPE_UINT16_ARRAY:
185 	case BHND_NVRAM_TYPE_UINT32_ARRAY:
186 	case BHND_NVRAM_TYPE_UINT64_ARRAY:
187 	case BHND_NVRAM_TYPE_INT8_ARRAY:
188 	case BHND_NVRAM_TYPE_INT16_ARRAY:
189 	case BHND_NVRAM_TYPE_INT32_ARRAY:
190 	case BHND_NVRAM_TYPE_INT64_ARRAY:
191 	case BHND_NVRAM_TYPE_CHAR_ARRAY:
192 	case BHND_NVRAM_TYPE_BOOL_ARRAY: {
193 		size_t width = bhnd_nvram_type_width(itype);
194 		BHND_NV_ASSERT(width != 0, ("invalid width"));
195 
196 		*nelem = ilen / width;
197 		return (0);
198 	}
199 
200 	case BHND_NVRAM_TYPE_INT8:
201 	case BHND_NVRAM_TYPE_UINT8:
202 	case BHND_NVRAM_TYPE_CHAR:
203 	case BHND_NVRAM_TYPE_INT16:
204 	case BHND_NVRAM_TYPE_UINT16:
205 	case BHND_NVRAM_TYPE_INT32:
206 	case BHND_NVRAM_TYPE_UINT32:
207 	case BHND_NVRAM_TYPE_INT64:
208 	case BHND_NVRAM_TYPE_UINT64:
209 	case BHND_NVRAM_TYPE_BOOL:
210 		/* Length must be equal to the size of exactly one
211 		 * element (arrays can represent zero elements -- non-array
212 		 * types cannot) */
213 		if (ilen != bhnd_nvram_type_width(itype))
214 			return (EFTYPE);
215 		*nelem = 1;
216 		return (0);
217 	}
218 
219 	/* Quiesce gcc4.2 */
220 	BHND_NV_PANIC("bhnd nvram type %u unknown", itype);
221 }
222 
223 /**
224  * Return the size, in bytes, of a value of @p itype with @p nelem elements.
225  *
226  * @param	inp	The actual data to be queried, or NULL if unknown. If
227  *			NULL and the base type is not a fixed width type
228  *			(e.g. BHND_NVRAM_TYPE_STRING), 0 will be returned.
229  * @param	ilen	The size of @p inp, in bytes, or 0 if @p inp is NULL.
230  * @param	itype	The value type.
231  * @param	nelem	The number of elements. If @p itype is not an array
232  *			type, this value must be 1.
233  *
234  * @retval 0		If @p itype has a variable width, and @p inp is NULL.
235  * @retval 0		If a @p nelem value greater than 1 is provided for a
236  *			non-array @p itype.
237  * @retval 0		If a @p nelem value of 0 is provided.
238  * @retval 0		If the result would exceed the maximum value
239  *			representable by size_t.
240  * @retval 0		If @p itype is BHND_NVRAM_TYPE_NULL.
241  * @retval non-zero	The size, in bytes, of @p itype with @p nelem elements.
242  */
243 size_t
244 bhnd_nvram_value_size(const void *inp, size_t ilen, bhnd_nvram_type itype,
245     size_t nelem)
246 {
247 	/* If nelem 0, nothing to do */
248 	if (nelem == 0)
249 		return (0);
250 
251 	/* Non-array types must have an nelem value of 1 */
252 	if (!bhnd_nvram_is_array_type(itype) && nelem != 1)
253 		return (0);
254 
255 	switch (itype) {
256 	case BHND_NVRAM_TYPE_UINT8_ARRAY:
257 	case BHND_NVRAM_TYPE_UINT16_ARRAY:
258 	case BHND_NVRAM_TYPE_UINT32_ARRAY:
259 	case BHND_NVRAM_TYPE_UINT64_ARRAY:
260 	case BHND_NVRAM_TYPE_INT8_ARRAY:
261 	case BHND_NVRAM_TYPE_INT16_ARRAY:
262 	case BHND_NVRAM_TYPE_INT32_ARRAY:
263 	case BHND_NVRAM_TYPE_INT64_ARRAY:
264 	case BHND_NVRAM_TYPE_CHAR_ARRAY:
265 	case BHND_NVRAM_TYPE_BOOL_ARRAY:{
266 		size_t width;
267 
268 		width = bhnd_nvram_type_width(itype);
269 
270 		/* Would nelem * width overflow? */
271 		if (SIZE_MAX / nelem < width) {
272 			BHND_NV_LOG("cannot represent size %s[%zu]\n",
273 			    bhnd_nvram_type_name(bhnd_nvram_base_type(itype)),
274 			    nelem);
275 			return (0);
276 		}
277 
278 		return (nelem * width);
279 	}
280 
281 	case BHND_NVRAM_TYPE_STRING_ARRAY: {
282 		const char	*p;
283 		size_t		 total_size;
284 
285 		if (inp == NULL)
286 			return (0);
287 
288 		/* Iterate over the NUL-terminated strings to calculate
289 		 * total byte length */
290 		p = inp;
291 		total_size = 0;
292 		for (size_t i = 0; i < nelem; i++) {
293 			size_t	elem_size;
294 
295 			elem_size = strnlen(p, ilen - total_size);
296 			p += elem_size;
297 
298 			/* Check for (and skip) terminating NUL */
299 			if (total_size < ilen && *p == '\0') {
300 				elem_size++;
301 				p++;
302 			}
303 
304 			/* Would total_size + elem_size overflow?
305 			 *
306 			 * A memory range larger than SIZE_MAX shouldn't be,
307 			 * possible, but include the check for completeness */
308 			if (SIZE_MAX - total_size < elem_size)
309 				return (0);
310 
311 			total_size += elem_size;
312 		}
313 
314 		return (total_size);
315 	}
316 
317 	case BHND_NVRAM_TYPE_STRING: {
318 		size_t size;
319 
320 		if (inp == NULL)
321 			return (0);
322 
323 		/* Find length */
324 		size = strnlen(inp, ilen);
325 
326 		/* Is there a terminating NUL, or did we just hit the
327 		 * end of the string input */
328 		if (size < ilen)
329 			size++;
330 
331 		return (size);
332 	}
333 
334 	case BHND_NVRAM_TYPE_NULL:
335 		return (0);
336 
337 	case BHND_NVRAM_TYPE_DATA:
338 		if (inp == NULL)
339 			return (0);
340 
341 		return (ilen);
342 
343 	case BHND_NVRAM_TYPE_BOOL:
344 		return (sizeof(bhnd_nvram_bool_t));
345 
346 	case BHND_NVRAM_TYPE_INT8:
347 	case BHND_NVRAM_TYPE_UINT8:
348 	case BHND_NVRAM_TYPE_CHAR:
349 		return (sizeof(uint8_t));
350 
351 	case BHND_NVRAM_TYPE_INT16:
352 	case BHND_NVRAM_TYPE_UINT16:
353 		return (sizeof(uint16_t));
354 
355 	case BHND_NVRAM_TYPE_INT32:
356 	case BHND_NVRAM_TYPE_UINT32:
357 		return (sizeof(uint32_t));
358 
359 	case BHND_NVRAM_TYPE_UINT64:
360 	case BHND_NVRAM_TYPE_INT64:
361 		return (sizeof(uint64_t));
362 	}
363 
364 	/* Quiesce gcc4.2 */
365 	BHND_NV_PANIC("bhnd nvram type %u unknown", itype);
366 }
367 
368 
369 /**
370  * Format a string representation of @p inp using @p fmt, with, writing the
371  * result to @p outp.
372  *
373  * Refer to bhnd_nvram_val_vprintf() for full format string documentation.
374  *
375  * @param		fmt	The format string.
376  * @param		inp	The value to be formatted.
377  * @param		ilen	The size of @p inp, in bytes.
378  * @param		itype	The type of @p inp.
379  * @param[out]		outp	On success, the string value will be written to
380  *				this buffer. This argment may be NULL if the
381  *				value is not desired.
382  * @param[in,out]	olen	The capacity of @p outp. On success, will be set
383  *				to the actual size of the formatted string.
384  *
385  * @retval 0		success
386  * @retval EINVAL	If @p fmt contains unrecognized format string
387  *			specifiers.
388  * @retval ENOMEM	If the @p outp is non-NULL, and the provided @p olen
389  *			is too small to hold the encoded value.
390  * @retval EFTYPE	If value coercion from @p inp to a string value via
391  *			@p fmt is unsupported.
392  * @retval ERANGE	If value coercion of @p value would overflow (or
393  *			underflow) the representation defined by @p fmt.
394  */
395 int
396 bhnd_nvram_value_printf(const char *fmt, const void *inp, size_t ilen,
397     bhnd_nvram_type itype, char *outp, size_t *olen, ...)
398 {
399 	va_list	ap;
400 	int	error;
401 
402 	va_start(ap, olen);
403 	error = bhnd_nvram_value_vprintf(fmt, inp, ilen, itype, outp, olen, ap);
404 	va_end(ap);
405 
406 	return (error);
407 }
408 
409 /**
410  * Format a string representation of @p inp using @p fmt, with, writing the
411  * result to @p outp.
412  *
413  * Refer to bhnd_nvram_val_vprintf() for full format string documentation.
414  *
415  * @param		fmt	The format string.
416  * @param		inp	The value to be formatted.
417  * @param		ilen	The size of @p inp, in bytes.
418  * @param		itype	The type of @p inp.
419  * @param[out]		outp	On success, the string value will be written to
420  *				this buffer. This argment may be NULL if the
421  *				value is not desired.
422  * @param[in,out]	olen	The capacity of @p outp. On success, will be set
423  *				to the actual size of the formatted string.
424  * @param		ap	Argument list.
425  *
426  * @retval 0		success
427  * @retval EINVAL	If @p fmt contains unrecognized format string
428  *			specifiers.
429  * @retval ENOMEM	If the @p outp is non-NULL, and the provided @p olen
430  *			is too small to hold the encoded value.
431  * @retval EFTYPE	If value coercion from @p inp to a string value via
432  *			@p fmt is unsupported.
433  * @retval ERANGE	If value coercion of @p value would overflow (or
434  *			underflow) the representation defined by @p fmt.
435  */
436 int
437 bhnd_nvram_value_vprintf(const char *fmt, const void *inp, size_t ilen,
438     bhnd_nvram_type itype, char *outp, size_t *olen, va_list ap)
439 {
440 	bhnd_nvram_val	val;
441 	int		error;
442 
443 	/* Map input buffer as a value instance */
444 	error = bhnd_nvram_val_init(&val, NULL, inp, ilen, itype,
445 	    BHND_NVRAM_VAL_BORROW_DATA);
446 	if (error)
447 		return (error);
448 
449 	/* Attempt to format the value */
450 	error = bhnd_nvram_val_vprintf(&val, fmt, outp, olen, ap);
451 
452 	/* Clean up */
453 	bhnd_nvram_val_release(&val);
454 	return (error);
455 }
456 
457 /**
458  * Iterate over all elements in @p inp.
459  *
460  * @param		inp	The value to be iterated.
461  * @param		ilen	The size, in bytes, of @p inp.
462  * @param		itype	The data type of @p inp.
463  * @param		prev	The value previously returned by
464  *				bhnd_nvram_value_array_next(), or NULL to begin
465  *				iteration.
466  * @param[in,out]	olen	If @p prev is non-NULL, @p olen must be a
467  *				pointer to the length previously returned by
468  *				bhnd_nvram_value_array_next(). On success, will
469  *				be set to the next element's length, in bytes.
470  *
471  * @retval non-NULL	A borrowed reference to the next element of @p inp.
472  * @retval NULL		If the end of the array is reached.
473  */
474 const void *
475 bhnd_nvram_value_array_next(const void *inp, size_t ilen, bhnd_nvram_type itype,
476     const void *prev, size_t *olen)
477 {
478 	const u_char	*next;
479 	size_t		 offset;
480 
481 	/* Handle first element */
482 	if (prev == NULL) {
483 		/* Zero-length array? */
484 		if (ilen == 0)
485 			return (NULL);
486 
487 		*olen = bhnd_nvram_value_size(inp, ilen, itype, 1);
488 		return (inp);
489 	}
490 
491 	/* Advance to next element */
492 	BHND_NV_ASSERT(prev >= (const void *)inp, ("invalid cookiep"));
493 	next = (const u_char *)prev + *olen;
494 	offset = (size_t)(next - (const u_char *)inp);
495 
496 	if (offset >= ilen) {
497 		/* Hit end of the array */
498 		return (NULL);
499 	}
500 
501 	/* Determine element size */
502 	*olen = bhnd_nvram_value_size(next, ilen - offset, itype, 1);
503 	if (ilen - offset < *olen) {
504 		BHND_NV_LOG("short element of type %s -- misaligned "
505 		    "representation", bhnd_nvram_type_name(itype));
506 		return (NULL);
507 	}
508 
509 	return (next);
510 }
511 
512 /**
513  * Coerce value @p inp of type @p itype to @p otype, writing the
514  * result to @p outp.
515  *
516  * @param		inp	The value to be coerced.
517  * @param		ilen	The size of @p inp, in bytes.
518  * @param		itype	The base data type of @p inp.
519  * @param[out]		outp	On success, the value will be written to this
520  *				buffer. This argment may be NULL if the value
521  *				is not desired.
522  * @param[in,out]	olen	The capacity of @p outp. On success, will be set
523  *				to the actual size of the requested value.
524  * @param		otype	The data type to be written to @p outp.
525  *
526  * @retval 0		success
527  * @retval ENOMEM	If @p outp is non-NULL and a buffer of @p olen is too
528  *			small to hold the requested value.
529  * @retval EFTYPE	If the variable data cannot be coerced to @p otype.
530  * @retval ERANGE	If value coercion would overflow @p otype.
531  */
532 int
533 bhnd_nvram_value_coerce(const void *inp, size_t ilen, bhnd_nvram_type itype,
534     void *outp, size_t *olen, bhnd_nvram_type otype)
535 {
536 	bhnd_nvram_val	val;
537 	int		error;
538 
539 	/* Wrap input buffer in a value instance */
540 	error = bhnd_nvram_val_init(&val, NULL, inp, ilen,
541 	    itype, BHND_NVRAM_VAL_BORROW_DATA|BHND_NVRAM_VAL_FIXED);
542 	if (error)
543 		return (error);
544 
545 	/* Try to encode as requested type */
546 	error = bhnd_nvram_val_encode(&val, outp, olen, otype);
547 
548 	/* Clean up and return error */
549 	bhnd_nvram_val_release(&val);
550 	return (error);
551 }
552