1 /* ldif-filter -- clean up LDIF testdata from stdin */
3 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
5 * Copyright 2009-2015 The OpenLDAP Foundation.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted only as authorized by the OpenLDAP
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>.
21 #include <ac/stdlib.h>
22 #include <ac/string.h>
23 #include <ac/unistd.h>
25 #define DEFAULT_SPECS "ndb=a,null=n"
27 typedef struct { char *val; size_t len, alloc; } String;
28 typedef struct { String *val; size_t len, alloc; } Strings;
30 /* Flags and corresponding program options */
31 enum { SORT_ATTRS = 1, SORT_ENTRIES = 2, NO_OUTPUT = 4, DUMMY_FLAG = 8 };
32 static const char spec_options[] = "aen"; /* option index = log2(enum flag) */
34 static const char *progname = "ldif-filter";
35 static const String null_string = { NULL, 0, 0 };
41 Usage: %s [-b backend] [-s spec[,spec]...]\n\
42 Filter standard input by first <spec> matching '[<backend>]=[a][e][n]':\n\
43 - Remove LDIF comments.\n\
44 - 'a': Sort attributes in entries.\n\
45 - 'e': Sort any entries separated by just one empty line.\n\
46 - 'n': Output nothing.\n\
47 <backend> defaults to the $BACKEND environment variable.\n\
48 Use specs '%s' if no spec on the command line applies.\n",
49 progname, DEFAULT_SPECS );
53 /* Return flags from "backend=flags" in spec; nonzero if backend found */
55 get_flags( const char *backend, const char *spec )
57 size_t len = strlen( backend );
58 unsigned flags = DUMMY_FLAG;
61 while ( '=' != *(spec += strncmp( spec, backend, len ) ? 0 : len) ) {
62 if ( (spec = strchr( spec, ',' )) == NULL ) {
67 while ( *++spec && *spec != ',' ) {
68 if ( (tmp = strchr( spec_options, *spec )) == NULL ) {
71 flags |= 1U << (tmp - spec_options);
76 #define APPEND(s /* String or Strings */, data, count, isString) do { \
77 size_t slen = (s)->len, salloc = (s)->alloc, sz = sizeof *(s)->val; \
78 if ( salloc <= slen + (count) ) { \
79 (s)->alloc = salloc += salloc + ((count)|7) + 1; \
80 (s)->val = xrealloc( (s)->val, sz * salloc ); \
82 memcpy( (s)->val + slen, data, sz * ((count) + !!(isString)) ); \
83 (s)->len = slen + (count); \
87 xrealloc( void *ptr, size_t len )
89 if ( (ptr = realloc( ptr, len )) == NULL ) {
97 cmp( const void *s, const void *t )
99 return strcmp( ((const String *) s)->val, ((const String *) t)->val );
103 sort_strings( Strings *ss, size_t offset )
105 qsort( ss->val + offset, ss->len - offset, sizeof(*ss->val), cmp );
108 /* Build entry ss[n] from attrs ss[n...], and free the attrs */
110 build_entry( Strings *ss, size_t n, unsigned flags, size_t new_len )
112 String *vals = ss->val, *e = &vals[n];
113 size_t end = ss->len;
116 if ( flags & SORT_ATTRS ) {
117 sort_strings( ss, n + 1 );
119 e->val = xrealloc( e->val, e->alloc = new_len + 1 );
120 ptr = e->val + e->len;
123 for ( ; n < end; free( vals[n++].val )) {
124 ptr = strcpy( ptr, vals[n].val ) + vals[n].len;
126 assert( ptr == e->val + new_len );
129 /* Flush entries to stdout and free them */
131 flush_entries( Strings *ss, const char *sep, unsigned flags )
133 size_t i, end = ss->len;
134 const char *prefix = "";
136 if ( flags & SORT_ENTRIES ) {
137 sort_strings( ss, 0 );
139 for ( i = 0; i < end; i++, prefix = sep ) {
140 if ( printf( "%s%s", prefix, ss->val[i].val ) < 0 ) {
142 exit( EXIT_FAILURE );
144 free( ss->val[i].val );
150 filter_stdin( unsigned flags )
153 Strings ss = { NULL, 0, 0 }; /* entries + attrs of partial entry */
154 size_t entries = 0, attrs_totlen = 0, line_len;
155 const char *entry_sep = "\n", *sep = "";
156 int comment = 0, eof = 0, eol, prev_eol = 1; /* flags */
159 /* LDIF = Entries ss[..entries-1] + sep + attrs ss[entries..] + line */
160 for ( ; !eof || ss.len || *sep; prev_eol = eol ) {
161 if ( eof || (eof = !fgets( line, sizeof(line), stdin ))) {
162 strcpy( line, prev_eol ? "" : *sep ? sep : "\n" );
164 line_len = strlen( line );
165 eol = (line_len == 0 || line[line_len - 1] == '\n');
167 if ( *line == ' ' ) { /* continuation line? */
169 } else if ( prev_eol ) { /* start of logical line? */
170 comment = (*line == '#');
172 if ( comment || (flags & NO_OUTPUT) ) {
176 /* Collect attrs for partial entry in ss[entries...] */
177 if ( !prev_eol && attrs_totlen != 0 ) {
179 } else if ( line_len > (*line == '\r' ? 2 : 1) ) {
180 APPEND( &ss, &null_string, 1, 0 ); /* new attr */
182 s = &ss.val[ss.len - 1];
183 APPEND( s, line, line_len, 1 ); /* strcat to attr */
184 attrs_totlen += line_len;
188 /* Empty line - consume sep+attrs or entries+sep */
189 if ( attrs_totlen != 0 ) {
192 fputs( sep, stdout );
193 build_entry( &ss, entries++, flags, attrs_totlen );
196 flush_entries( &ss, entry_sep, flags );
197 fputs( sep, stdout );
200 sep = "\r\n" + 2 - line_len; /* sep = copy(line) */
207 main( int argc, char **argv )
209 const char *backend = getenv( "BACKEND" ), *specs = "", *tmp;
214 progname = (tmp = strrchr( argv[0], '/' )) ? tmp+1 : argv[0];
217 while ( (i = getopt( argc, argv, "b:s:" )) != EOF ) {
229 if ( optind < argc ) {
232 if ( backend == NULL ) {
236 flags = get_flags( backend, specs );
237 filter_stdin( flags ? flags : get_flags( backend, DEFAULT_SPECS ));
238 if ( fclose( stdout ) == EOF ) {