xref: /netbsd-src/lib/libc/stdio/fparseln.c (revision 8b0f9554ff8762542c4defc4f70e1eb76fb508fa)
1 /*	$NetBSD: fparseln.c,v 1.7 2007/03/08 19:57:53 drochner 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.7 2007/03/08 19:57:53 drochner 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 0;
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 continuation char */
159 				cnt = 1;
160 			}
161 		}
162 
163 		if (s == 0) {
164 			/*
165 			 * nothing to add, skip realloc except in case
166 			 * we need a minimal buf to return an empty line
167 			 */
168 			if (cnt || buf != NULL)
169 				continue;
170 		}
171 
172 		if ((cp = realloc(buf, len + s + 1)) == NULL) {
173 			FUNLOCKFILE(fp);
174 			free(buf);
175 			return NULL;
176 		}
177 		buf = cp;
178 
179 		(void) memcpy(buf + len, ptr, s);
180 		len += s;
181 		buf[len] = '\0';
182 	}
183 
184 	FUNLOCKFILE(fp);
185 
186 	if ((flags & FPARSELN_UNESCALL) != 0 && esc && buf != NULL &&
187 	    strchr(buf, esc) != NULL) {
188 		ptr = cp = buf;
189 		while (cp[0] != '\0') {
190 			int skipesc;
191 
192 			while (cp[0] != '\0' && cp[0] != esc)
193 				*ptr++ = *cp++;
194 			if (cp[0] == '\0' || cp[1] == '\0')
195 				break;
196 
197 			skipesc = 0;
198 			if (cp[1] == com)
199 				skipesc += (flags & FPARSELN_UNESCCOMM);
200 			if (cp[1] == con)
201 				skipesc += (flags & FPARSELN_UNESCCONT);
202 			if (cp[1] == esc)
203 				skipesc += (flags & FPARSELN_UNESCESC);
204 			if (cp[1] != com && cp[1] != con && cp[1] != esc)
205 				skipesc = (flags & FPARSELN_UNESCREST);
206 
207 			if (skipesc)
208 				cp++;
209 			else
210 				*ptr++ = *cp++;
211 			*ptr++ = *cp++;
212 		}
213 		*ptr = '\0';
214 		len = strlen(buf);
215 	}
216 
217 	if (size)
218 		*size = len;
219 	return buf;
220 }
221 
222 #ifdef TEST
223 
224 int main(int, char **);
225 
226 int
227 main(int argc, char **argv)
228 {
229 	char   *ptr;
230 	size_t	size, line;
231 
232 	line = 0;
233 	while ((ptr = fparseln(stdin, &size, &line, NULL,
234 	    FPARSELN_UNESCALL)) != NULL)
235 		printf("line %d (%d) |%s|\n", line, size, ptr);
236 	return 0;
237 }
238 
239 /*
240 
241 # This is a test
242 line 1
243 line 2 \
244 line 3 # Comment
245 line 4 \# Not comment \\\\
246 
247 # And a comment \
248 line 5 \\\
249 line 6
250 
251 */
252 
253 #endif /* TEST */
254 #endif	/* ! HAVE_FPARSELN */
255