xref: /freebsd/lib/libutil/property.c (revision c66ec88fed842fbaad62c30d510644ceb7bd2d71)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  *
5  * Simple property list handling code.
6  *
7  * Copyright (c) 1998
8  *	Jordan Hubbard.  All rights reserved.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer,
15  *    verbatim and that no modifications are made prior to this
16  *    point in the file.
17  * 2. Redistributions in binary form must reproduce the above copyright
18  *    notice, this list of conditions and the following disclaimer in the
19  *    documentation and/or other materials provided with the distribution.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR HIS PETS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, LIFE OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  */
33 
34 #include <sys/cdefs.h>
35 __FBSDID("$FreeBSD$");
36 
37 #include <sys/types.h>
38 #include <ctype.h>
39 #include <err.h>
40 #include <libutil.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <unistd.h>
45 
46 static properties
47 property_alloc(char *name, char *value)
48 {
49     properties n;
50 
51     if ((n = (properties)malloc(sizeof(struct _property))) == NULL)
52 	return (NULL);
53     n->next = NULL;
54     if (name != NULL) {
55 	if ((n->name = strdup(name)) == NULL) {
56 	    free(n);
57 	    return (NULL);
58 	}
59     } else
60 	n->name = NULL;
61     if (value != NULL) {
62 	if ((n->value = strdup(value)) == NULL) {
63 	    free(n->name);
64 	    free(n);
65 	    return (NULL);
66 	}
67     } else
68 	n->value = NULL;
69     return (n);
70 }
71 
72 properties
73 properties_read(int fd)
74 {
75     properties head, ptr;
76     char hold_n[PROPERTY_MAX_NAME + 1];
77     char hold_v[PROPERTY_MAX_VALUE + 1];
78     char buf[BUFSIZ * 4];
79     int bp, n, v, max;
80     enum { LOOK, COMMENT, NAME, VALUE, MVALUE, COMMIT, FILL, STOP } state, last_state;
81     int ch = 0, blevel = 0;
82 
83     n = v = bp = max = 0;
84     head = ptr = NULL;
85     state = last_state = LOOK;
86     while (state != STOP) {
87 	if (state != COMMIT) {
88 	    if (bp == max) {
89 		last_state = state;
90 		state = FILL;
91 	    } else
92 		ch = buf[bp++];
93 	}
94 	switch(state) {
95 	case FILL:
96 	    if ((max = read(fd, buf, sizeof buf)) < 0) {
97 		properties_free(head);
98 		return (NULL);
99 	    }
100 	    if (max == 0) {
101 		state = STOP;
102 	    } else {
103 		/*
104 		 * Restore the state from before the fill (which will be
105 		 * initialised to LOOK for the first FILL). This ensures that
106 		 * if we were part-way through eg., a VALUE state, when the
107 		 * buffer ran out, that the previous operation will be allowed
108 		 * to complete.
109 		 */
110 		state = last_state;
111 		ch = buf[0];
112 		bp = 0;
113 	    }
114 	    continue;
115 
116 	case LOOK:
117 	    if (isspace((unsigned char)ch))
118 		continue;
119 	    /* Allow shell or lisp style comments */
120 	    else if (ch == '#' || ch == ';') {
121 		state = COMMENT;
122 		continue;
123 	    }
124 	    else if (isalnum((unsigned char)ch) || ch == '_') {
125 		if (n >= PROPERTY_MAX_NAME) {
126 		    n = 0;
127 		    state = COMMENT;
128 		}
129 		else {
130 		    hold_n[n++] = ch;
131 		    state = NAME;
132 		}
133 	    }
134 	    else
135 		state = COMMENT;	/* Ignore the rest of the line */
136 	    break;
137 
138 	case COMMENT:
139 	    if (ch == '\n')
140 		state = LOOK;
141 	    break;
142 
143 	case NAME:
144 	    if (ch == '\n' || !ch) {
145 		hold_n[n] = '\0';
146 		hold_v[0] = '\0';
147 		v = n = 0;
148 		state = COMMIT;
149 	    }
150 	    else if (isspace((unsigned char)ch))
151 		continue;
152 	    else if (ch == '=') {
153 		hold_n[n] = '\0';
154 		v = n = 0;
155 		state = VALUE;
156 	    }
157 	    else
158 		hold_n[n++] = ch;
159 	    break;
160 
161 	case VALUE:
162 	    if (v == 0 && ch == '\n') {
163 	        hold_v[v] = '\0';
164 	        v = n = 0;
165 	        state = COMMIT;
166 	    }
167 	    else if (v == 0 && isspace((unsigned char)ch))
168 		continue;
169 	    else if (ch == '{') {
170 		state = MVALUE;
171 		++blevel;
172 	    }
173 	    else if (ch == '\n' || !ch) {
174 		hold_v[v] = '\0';
175 		v = n = 0;
176 		state = COMMIT;
177 	    }
178 	    else {
179 		if (v >= PROPERTY_MAX_VALUE) {
180 		    state = COMMENT;
181 		    v = n = 0;
182 		    break;
183 		}
184 		else
185 		    hold_v[v++] = ch;
186 	    }
187 	    break;
188 
189 	case MVALUE:
190 	    /* multiline value */
191 	    if (v >= PROPERTY_MAX_VALUE) {
192 		warn("properties_read: value exceeds max length");
193 		state = COMMENT;
194 		n = v = 0;
195 	    }
196 	    else if (ch == '}' && !--blevel) {
197 		hold_v[v] = '\0';
198 		v = n = 0;
199 		state = COMMIT;
200 	    }
201 	    else {
202 		hold_v[v++] = ch;
203 		if (ch == '{')
204 		    ++blevel;
205 	    }
206 	    break;
207 
208 	case COMMIT:
209 	    if (head == NULL) {
210 		if ((head = ptr = property_alloc(hold_n, hold_v)) == NULL)
211 		    return (NULL);
212 	    } else {
213 		if ((ptr->next = property_alloc(hold_n, hold_v)) == NULL) {
214 		    properties_free(head);
215 		    return (NULL);
216 		}
217 		ptr = ptr->next;
218 	    }
219 	    state = LOOK;
220 	    v = n = 0;
221 	    break;
222 
223 	case STOP:
224 	    /* we don't handle this here, but this prevents warnings */
225 	    break;
226 	}
227     }
228     if (head == NULL && (head = property_alloc(NULL, NULL)) == NULL)
229 	return (NULL);
230 
231     return (head);
232 }
233 
234 char *
235 property_find(properties list, const char *name)
236 {
237     if (list == NULL || name == NULL || !name[0])
238 	return (NULL);
239     while (list != NULL) {
240 	if (list->name != NULL && strcmp(list->name, name) == 0)
241 	    return (list->value);
242 	list = list->next;
243     }
244     return (NULL);
245 }
246 
247 void
248 properties_free(properties list)
249 {
250     properties tmp;
251 
252     while (list) {
253 	tmp = list->next;
254 	if (list->name)
255 	    free(list->name);
256 	if (list->value)
257 	    free(list->value);
258 	free(list);
259 	list = tmp;
260     }
261 }
262