xref: /netbsd-src/lib/libc/stdio/fparseln.c (revision aaf4ece63a859a04e37cf3a7229b5fab0157cc06)
1 /*	$NetBSD: fparseln.c,v 1.5 2004/06/20 22:20:15 jmc Exp $	*/
2 
3 /*
4  * Copyright (c) 1997 Christos Zoulas.  All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  * 3. All advertising materials mentioning features or use of this software
15  *    must display the following acknowledgement:
16  *	This product includes software developed by Christos Zoulas.
17  * 4. The name of the author may not be used to endorse or promote products
18  *    derived from this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
21  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
22  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
24  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
25  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 #include <sys/cdefs.h>
33 #if defined(LIBC_SCCS) && !defined(lint)
34 __RCSID("$NetBSD: fparseln.c,v 1.5 2004/06/20 22:20:15 jmc Exp $");
35 #endif /* LIBC_SCCS and not lint */
36 
37 #include "namespace.h"
38 
39 #include <assert.h>
40 #include <errno.h>
41 #include <stdio.h>
42 #include <string.h>
43 #include <stdlib.h>
44 
45 #ifdef __weak_alias
46 __weak_alias(fparseln,_fparseln)
47 #endif
48 
49 #if ! HAVE_FPARSELN
50 
51 #ifndef HAVE_NBTOOL_CONFIG_H
52 #include "reentrant.h"
53 #include "local.h"
54 #else
55 #define FLOCKFILE(fp)
56 #define FUNLOCKFILE(fp)
57 #endif
58 
59 #if defined(_REENTRANT) && !HAVE_NBTOOL_CONFIG_H
60 #define __fgetln(f, l) __fgetstr(f, l, '\n')
61 #else
62 #define __fgetln(f, l) fgetln(f, l)
63 #endif
64 
65 static int isescaped(const char *, const char *, int);
66 
67 /* isescaped():
68  *	Return true if the character in *p that belongs to a string
69  *	that starts in *sp, is escaped by the escape character esc.
70  */
71 static int
72 isescaped(const char *sp, const char *p, int esc)
73 {
74 	const char     *cp;
75 	size_t		ne;
76 
77 	_DIAGASSERT(sp != NULL);
78 	_DIAGASSERT(p != NULL);
79 
80 	/* No escape character */
81 	if (esc == '\0')
82 		return 1;
83 
84 	/* Count the number of escape characters that precede ours */
85 	for (ne = 0, cp = p; --cp >= sp && *cp == esc; ne++)
86 		continue;
87 
88 	/* Return true if odd number of escape characters */
89 	return (ne & 1) != 0;
90 }
91 
92 
93 /* fparseln():
94  *	Read a line from a file parsing continuations ending in \
95  *	and eliminating trailing newlines, or comments starting with
96  *	the comment char.
97  */
98 char *
99 fparseln(FILE *fp, size_t *size, size_t *lineno, const char str[3], int flags)
100 {
101 	static const char dstr[3] = { '\\', '\\', '#' };
102 
103 	size_t	s, len;
104 	char   *buf;
105 	char   *ptr, *cp;
106 	int	cnt;
107 	char	esc, con, nl, com;
108 
109 	_DIAGASSERT(fp != NULL);
110 
111 	len = 0;
112 	buf = NULL;
113 	cnt = 1;
114 
115 	if (str == NULL)
116 		str = dstr;
117 
118 	esc = str[0];
119 	con = str[1];
120 	com = str[2];
121 	/*
122 	 * XXX: it would be cool to be able to specify the newline character,
123 	 * but unfortunately, fgetln does not let us
124 	 */
125 	nl  = '\n';
126 
127 	FLOCKFILE(fp);
128 
129 	while (cnt) {
130 		cnt = 0;
131 
132 		if (lineno)
133 			(*lineno)++;
134 
135 		if ((ptr = __fgetln(fp, &s)) == NULL)
136 			break;
137 
138 		if (s && com) {		/* Check and eliminate comments */
139 			for (cp = ptr; cp < ptr + s; cp++)
140 				if (*cp == com && !isescaped(ptr, cp, esc)) {
141 					s = cp - ptr;
142 					cnt = s == 0 && buf == NULL;
143 					break;
144 				}
145 		}
146 
147 		if (s && nl) { 		/* Check and eliminate newlines */
148 			cp = &ptr[s - 1];
149 
150 			if (*cp == nl)
151 				s--;	/* forget newline */
152 		}
153 
154 		if (s && con) {		/* Check and eliminate continuations */
155 			cp = &ptr[s - 1];
156 
157 			if (*cp == con && !isescaped(ptr, cp, esc)) {
158 				s--;	/* forget escape */
159 				cnt = 1;
160 			}
161 		}
162 
163 		if (s == 0 && buf != NULL)
164 			continue;
165 
166 		if ((cp = realloc(buf, len + s + 1)) == NULL) {
167 			FUNLOCKFILE(fp);
168 			free(buf);
169 			return NULL;
170 		}
171 		buf = cp;
172 
173 		(void) memcpy(buf + len, ptr, s);
174 		len += s;
175 		buf[len] = '\0';
176 	}
177 
178 	FUNLOCKFILE(fp);
179 
180 	if ((flags & FPARSELN_UNESCALL) != 0 && esc && buf != NULL &&
181 	    strchr(buf, esc) != NULL) {
182 		ptr = cp = buf;
183 		while (cp[0] != '\0') {
184 			int skipesc;
185 
186 			while (cp[0] != '\0' && cp[0] != esc)
187 				*ptr++ = *cp++;
188 			if (cp[0] == '\0' || cp[1] == '\0')
189 				break;
190 
191 			skipesc = 0;
192 			if (cp[1] == com)
193 				skipesc += (flags & FPARSELN_UNESCCOMM);
194 			if (cp[1] == con)
195 				skipesc += (flags & FPARSELN_UNESCCONT);
196 			if (cp[1] == esc)
197 				skipesc += (flags & FPARSELN_UNESCESC);
198 			if (cp[1] != com && cp[1] != con && cp[1] != esc)
199 				skipesc = (flags & FPARSELN_UNESCREST);
200 
201 			if (skipesc)
202 				cp++;
203 			else
204 				*ptr++ = *cp++;
205 			*ptr++ = *cp++;
206 		}
207 		*ptr = '\0';
208 		len = strlen(buf);
209 	}
210 
211 	if (size)
212 		*size = len;
213 	return buf;
214 }
215 
216 #ifdef TEST
217 
218 int main(int, char **);
219 
220 int
221 main(int argc, char **argv)
222 {
223 	char   *ptr;
224 	size_t	size, line;
225 
226 	line = 0;
227 	while ((ptr = fparseln(stdin, &size, &line, NULL,
228 	    FPARSELN_UNESCALL)) != NULL)
229 		printf("line %d (%d) |%s|\n", line, size, ptr);
230 	return 0;
231 }
232 
233 /*
234 
235 # This is a test
236 line 1
237 line 2 \
238 line 3 # Comment
239 line 4 \# Not comment \\\\
240 
241 # And a comment \
242 line 5 \\\
243 line 6
244 
245 */
246 
247 #endif /* TEST */
248 #endif	/* ! HAVE_FPARSELN */
249