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