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