ldif-filter.c revision 1.1.1.5
1/* $NetBSD: ldif-filter.c,v 1.1.1.5 2019/08/08 13:31:30 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-2019 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.1.1.5 2019/08/08 13:31:30 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 35typedef struct { char *val; size_t len, alloc; } String; 36typedef struct { String *val; size_t len, alloc; } Strings; 37 38/* Flags and corresponding program options */ 39enum { SORT_ATTRS = 1, SORT_ENTRIES = 2, NO_OUTPUT = 4, DUMMY_FLAG = 8 }; 40static const char spec_options[] = "aen"; /* option index = log2(enum flag) */ 41 42static const char *progname = "ldif-filter"; 43static const String null_string = { NULL, 0, 0 }; 44 45static void 46usage( void ) 47{ 48 fprintf( stderr, "\ 49Usage: %s [-b backend] [-s spec[,spec]...]\n\ 50Filter 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\ 56Use 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 */ 62static unsigned 63get_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 100static void * 101xrealloc( 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 110static int 111cmp( const void *s, const void *t ) 112{ 113 return strcmp( ((const String *) s)->val, ((const String *) t)->val ); 114} 115 116static void 117sort_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 */ 123static void 124build_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 */ 144static void 145flush_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 163static void 164filter_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 220int 221main( 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