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