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