xref: /netbsd-src/external/bsd/openldap/dist/tests/progs/ldif-filter.c (revision 549b59ed3ccf0d36d3097190a0db27b770f3a839)
1 /*	$NetBSD: ldif-filter.c,v 1.3 2021/08/14 16:15:03 christos Exp $	*/
2 
3 /* ldif-filter -- clean up LDIF testdata from stdin */
4 /* $OpenLDAP$ */
5 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
6  *
7  * Copyright 2009-2021 The OpenLDAP Foundation.
8  * All rights reserved.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted only as authorized by the OpenLDAP
12  * Public License.
13  *
14  * A copy of this license is available in file LICENSE in the
15  * top-level directory of the distribution or, alternatively, at
16  * <http://www.OpenLDAP.org/license.html>.
17  */
18 
19 #include <sys/cdefs.h>
20 __RCSID("$NetBSD: ldif-filter.c,v 1.3 2021/08/14 16:15:03 christos Exp $");
21 
22 #include "portable.h"
23 
24 #include <stdio.h>
25 #include <ac/ctype.h>
26 #include <ac/stdlib.h>
27 #include <ac/string.h>
28 #include <ac/unistd.h>
29 #ifdef _WIN32
30 #include <fcntl.h>
31 #endif
32 
33 #define DEFAULT_SPECS "ndb=a,null=n"
34 
35 typedef struct { char   *val; size_t len, alloc; } String;
36 typedef struct { String	*val; size_t len, alloc; } Strings;
37 
38 /* Flags and corresponding program options */
39 enum { SORT_ATTRS = 1, SORT_ENTRIES = 2, NO_OUTPUT = 4, DUMMY_FLAG = 8 };
40 static const char spec_options[] = "aen"; /* option index = log2(enum flag) */
41 
42 static const char *progname = "ldif-filter";
43 static const String null_string = { NULL, 0, 0 };
44 
45 static void
usage(void)46 usage( void )
47 {
48 	fprintf( stderr, "\
49 Usage: %s [-b backend] [-s spec[,spec]...]\n\
50 Filter standard input by first <spec> matching '[<backend>]=[a][e][n]':\n\
51   - Remove LDIF comments.\n\
52   - 'a': Sort attributes in entries.\n\
53   - 'e': Sort any entries separated by just one empty line.\n\
54   - 'n': Output nothing.\n\
55 <backend> defaults to the $BACKEND environment variable.\n\
56 Use specs '%s' if no spec on the command line applies.\n",
57 		progname, DEFAULT_SPECS );
58 	exit( EXIT_FAILURE );
59 }
60 
61 /* Return flags from "backend=flags" in spec; nonzero if backend found */
62 static unsigned
get_flags(const char * backend,const char * spec)63 get_flags( const char *backend, const char *spec )
64 {
65 	size_t len = strlen( backend );
66 	unsigned flags = DUMMY_FLAG;
67 	const char *end, *tmp;
68 
69 	for ( ;; spec = end + ( *end != '\0' )) {
70 		if ( !*spec )
71 			return 0;
72 		end = spec + strcspn( spec, "," );
73 		if ( !(tmp = memchr( spec, '=', end-spec )))
74 			break;
75 		if ( tmp-spec == len && !memcmp( spec, backend, len )) {
76 			spec = tmp+1;
77 			break;
78 		}
79 	}
80 
81 	for ( ; spec < end; spec++ ) {
82 		if ( (tmp = strchr( spec_options, *spec )) == NULL ) {
83 			usage();
84 		}
85 		flags |= 1U << (tmp - spec_options);
86 	}
87 	return flags;
88 }
89 
90 #define APPEND(s /* String or Strings */, data, count, isString) do { \
91 	size_t slen = (s)->len, salloc = (s)->alloc, sz = sizeof *(s)->val; \
92 	if ( salloc <= slen + (count) ) { \
93 		(s)->alloc = salloc += salloc + ((count)|7) + 1; \
94 		(s)->val   = xrealloc( (s)->val, sz * salloc ); \
95 	} \
96 	memcpy( (s)->val + slen, data, sz * ((count) + !!(isString)) ); \
97 	(s)->len = slen + (count); \
98 } while (0)
99 
100 static void *
xrealloc(void * ptr,size_t len)101 xrealloc( void *ptr, size_t len )
102 {
103 	if ( (ptr = realloc( ptr, len )) == NULL ) {
104 		perror( progname );
105 		exit( EXIT_FAILURE );
106 	}
107 	return ptr;
108 }
109 
110 static int
cmp(const void * s,const void * t)111 cmp( const void *s, const void *t )
112 {
113 	return strcmp( ((const String *) s)->val, ((const String *) t)->val );
114 }
115 
116 static void
sort_strings(Strings * ss,size_t offset)117 sort_strings( Strings *ss, size_t offset )
118 {
119 	qsort( ss->val + offset, ss->len - offset, sizeof(*ss->val), cmp );
120 }
121 
122 /* Build entry ss[n] from attrs ss[n...], and free the attrs */
123 static void
build_entry(Strings * ss,size_t n,unsigned flags,size_t new_len)124 build_entry( Strings *ss, size_t n, unsigned flags, size_t new_len )
125 {
126 	String *vals = ss->val, *e = &vals[n];
127 	size_t end = ss->len;
128 	char *ptr;
129 
130 	if ( flags & SORT_ATTRS ) {
131 		sort_strings( ss, n + 1 );
132 	}
133 	e->val = xrealloc( e->val, e->alloc = new_len + 1 );
134 	ptr = e->val + e->len;
135 	e->len = new_len;
136 	ss->len = ++n;
137 	for ( ; n < end; free( vals[n++].val )) {
138 		ptr = strcpy( ptr, vals[n].val ) + vals[n].len;
139 	}
140 	assert( ptr == e->val + new_len );
141 }
142 
143 /* Flush entries to stdout and free them */
144 static void
flush_entries(Strings * ss,const char * sep,unsigned flags)145 flush_entries( Strings *ss, const char *sep, unsigned flags )
146 {
147 	size_t i, end = ss->len;
148 	const char *prefix = "";
149 
150 	if ( flags & SORT_ENTRIES ) {
151 		sort_strings( ss, 0 );
152 	}
153 	for ( i = 0; i < end; i++, prefix = sep ) {
154 		if ( printf( "%s%s", prefix, ss->val[i].val ) < 0 ) {
155 			perror( progname );
156 			exit( EXIT_FAILURE );
157 		}
158 		free( ss->val[i].val );
159 	}
160 	ss->len = 0;
161 }
162 
163 static void
filter_stdin(unsigned flags)164 filter_stdin( unsigned flags )
165 {
166 	char line[256];
167 	Strings ss = { NULL, 0, 0 };	/* entries + attrs of partial entry */
168 	size_t entries = 0, attrs_totlen = 0, line_len;
169 	const char *entry_sep = "\n", *sep = "";
170 	int comment = 0, eof = 0, eol, prev_eol = 1;	/* flags */
171 	String *s;
172 
173 	/* LDIF = Entries ss[..entries-1] + sep + attrs ss[entries..] + line */
174 	for ( ; !eof || ss.len || *sep; prev_eol = eol ) {
175 		if ( eof || (eof = !fgets( line, sizeof(line), stdin ))) {
176 			strcpy( line, prev_eol ? "" : *sep ? sep : "\n" );
177 		}
178 		line_len = strlen( line );
179 		eol = (line_len == 0 || line[line_len - 1] == '\n');
180 
181 		if ( *line == ' ' ) {		/* continuation line? */
182 			prev_eol = 0;
183 		} else if ( prev_eol ) {	/* start of logical line? */
184 			comment = (*line == '#');
185 		}
186 		if ( comment || (flags & NO_OUTPUT) ) {
187 			continue;
188 		}
189 
190 		/* Collect attrs for partial entry in ss[entries...] */
191 		if ( !prev_eol && attrs_totlen != 0 ) {
192 			goto grow_attr;
193 		} else if ( line_len > (*line == '\r' ? 2 : 1) ) {
194 			APPEND( &ss, &null_string, 1, 0 ); /* new attr */
195 		grow_attr:
196 			s = &ss.val[ss.len - 1];
197 			APPEND( s, line, line_len, 1 ); /* strcat to attr */
198 			attrs_totlen += line_len;
199 			continue;
200 		}
201 
202 		/* Empty line - consume sep+attrs or entries+sep */
203 		if ( attrs_totlen != 0 ) {
204 			entry_sep = sep;
205 			if ( entries == 0 )
206 				fputs( sep, stdout );
207 			build_entry( &ss, entries++, flags, attrs_totlen );
208 			attrs_totlen = 0;
209 		} else {
210 			flush_entries( &ss, entry_sep, flags );
211 			fputs( sep, stdout );
212 			entries = 0;
213 		}
214 		sep = "\r\n" + 2 - line_len;	/* sep = copy(line) */
215 	}
216 
217 	free( ss.val );
218 }
219 
220 int
main(int argc,char ** argv)221 main( int argc, char **argv )
222 {
223 	const char *backend = getenv( "BACKEND" ), *specs = "", *tmp;
224 	unsigned flags;
225 	int i;
226 
227 	if ( argc > 0 ) {
228 		progname = (tmp = strrchr( argv[0], '/' )) ? tmp+1 : argv[0];
229 	}
230 
231 	while ( (i = getopt( argc, argv, "b:s:" )) != EOF ) {
232 		switch ( i ) {
233 		case 'b':
234 			backend = optarg;
235 			break;
236 		case 's':
237 			specs = optarg;
238 			break;
239 		default:
240 			usage();
241 		}
242 	}
243 	if ( optind < argc ) {
244 		usage();
245 	}
246 	if ( backend == NULL ) {
247 		backend = "";
248 	}
249 
250 #ifdef _WIN32
251 	_setmode(1, _O_BINARY);	/* don't convert \n to \r\n on stdout */
252 #endif
253 	flags = get_flags( backend, specs );
254 	filter_stdin( flags ? flags : get_flags( backend, DEFAULT_SPECS ));
255 	if ( fclose( stdout ) == EOF ) {
256 		perror( progname );
257 		return EXIT_FAILURE;
258 	}
259 
260 	return EXIT_SUCCESS;
261 }
262