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