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