// rclean Copyright (c) 2002 Lapo Luchini <lapo@lapo.it>
// $Header: /usr/local/cvsroot/rclean/rclean.c,v 1.14 2002/12/22 17:12:30 lapo Exp $
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of Lapo Luchini nor the names of his contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
/*
** $Log: rclean.c,v $
** Revision 1.14 2002/12/22 17:12:30 lapo
** - FreeBSD needs sys/types.h defined before regexp.h, very strange
**
** Revision 1.13 2002/12/22 17:00:25 lapo
** - Removes rclean.h, it was too small to have sense
** - Changed indentation to be more 'standard', in a K&R way
** - Reversed include order, as suggested by Bruce Eckel's books
** - Defined a couple of string to avoid redundancy in the code
** - Generalized the first (and only) key name exception
** - Corrected the bug that did halt the program when a unknown exception key name was found
** - Added command line option to print exceptions list
** - Added line numbers in parse errors of the conf files
**
** Revision 1.12 2002/07/07 17:18:51 lapo
** - Added comments
** - Order of 'exception' options is now preserved
** - Default files are now printed in help
** - Changed help so that '-a' no more occupies more than 80 columns
**
** Revision 1.11 2002/04/29 14:16:14 lapo
** - Added '-a' option to print all lines present in rc.conf, even if with default value
** - Added '-w' option to write to file the output
** - Fixed tab creation for comments
**
** Revision 1.10 2002/04/29 10:54:50 lapo
** - Fixed bug in options code (long lines got no comment)
** - Added generic support for element "exceptions"
** - Added '-h' command line option for 'help'
**
** Revision 1.9 2002/04/28 23:40:18 lapo
** - license relaxed (now it's BSD-style)
** - getopt instead of popt (smaller executable and no required packages)
**
** Revision 1.8 2002/04/28 12:42:14 lapo
** - Added javadoc-style comments
** - Removed extra backslashes
**
** Revision 1.7 2002/04/28 10:58:03 lapo
** - Added header to generated output
** - Removed reference to REG_OKAY (was not present in FreeBSD's regex.h)
**
** Revision 1.6 2002/04/27 23:27:00 lapo
** - Added verbosity options
** - Changed default file values to fit FreeBSD
**
** Revision 1.5 2002/04/27 22:47:04 lapo
** - Added command line parsing using the popt library
** - Added GPL notes in source
**
** Revision 1.4 2002/04/27 21:13:10 lapo
** - Added generic support for keys not present in defaults file
**
** Revision 1.3 2002/04/27 21:02:53 lapo
** - Simplified using regex.h
** - Corrected error in memory allocation (strlen+1 instead of strlen)
**
** Revision 1.2 2002/04/25 07:37-07:48 lapo
** - speedup in final difference printing
**
** Revision 1.1 2002/04/25 00:40-03:26 lapo
** - hardcoded filenames
** - no options
** - strange problems with xntpd_enable and inetd_enable
** - sometimes names get followed by a nonprintable char (|>)
*/
/*** includes ****************************************************************/
#include <sys/types.h>
#include <regex.h>
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
/*** structures **************************************************************/
/**
* Structure for an element of rc.conf (a line)
*/
struct element {
char * key; // key
char * value_def; // default value
char * value; // value
struct element * next; // next key
};
/**
* Structure for an exception element
* (and element that is valid but it's not defined in default rc.conf)
*/
struct exception {
char * match; // regular expression to match the key
char * previous; // key of the element after which must be put
regex_t compiled; // regular expression to match the key
};
/*** settings ****************************************************************/
// copyright text
#define COPYRIGHT "rclean Copyright (c) 2002 Lapo Luchini <lapo@lapo.it>"
// string used as default value when not defined in default file
#define NOT_DEFINED "<not defined>"
// first match is 'key', second match is 'value'
#define RE_DEFAULT "^([a-z0-9_]+)=\"([^\"]*)\""
#define RE_CURRENT "^([a-z0-9_]+)=\"([^\"]*)\""
// max line length in rc.conf files
#define MAX_LINE 256
// debug level: 0 to 4
// 0: no debug
// 1: main phases of the program
// 2: sub-phases
// 3: find results
// 4: parsing infos
#define DEBUG 0
struct exception exceptions[] = {
// match for exception key put after this key
{"^ifconfig_[a-z]+[0-9]+(_alias[0-9]+)?$", "ifconfig_lo0"}
};
#define NUM_EXCEPTIONS (sizeof(exceptions) / sizeof(exceptions[0]))
/*** program *****************************************************************/
#if DEBUG > 0
#define DEBUG_POINT_s(v, a) if(DEBUG >= v) fprintf(stderr, "[debug] [%s]\n", a);
#define DEBUG_POINT_ss(v, a, b) if(DEBUG >= v) fprintf(stderr, "[debug] [%s] [%s]\n", a, b);
#define DEBUG_POINT_sss(v, a, b, c) if(DEBUG >= v) fprintf(stderr, "[debug] [%s] [%s] [%s]\n", a, b, c);
#define DEBUG_POINT_ssss(v, a, b, c, d) if(DEBUG >= v) fprintf(stderr, "[debug] [%s] [%s] [%s] [%s]\n", a, b, c, d);
#else
#define DEBUG_POINT_s(v, a) ;
#define DEBUG_POINT_ss(v, a, b) ;
#define DEBUG_POINT_sss(v, a, b, c) ;
#define DEBUG_POINT_ssss(v, a, b, c, d) ;
#endif
struct element * head, * tail;
/**
* malloc with error management
*/
void * malloc_chk(size_t size) {
void * u = malloc(size);
if(u == NULL) {
fprintf(stderr, "Cannot allocate memory [%d bytes].\n", size);
exit(2);
}
return u;
}
/**
* Creates a new element, given the content
* @param key key of the element (it is copied)
* @param value value of the element (it is copied and used both for value and value_def)
* @return the new element, dynamically allocated
*/
struct element * element_new(const char * key, const char * value) {
struct element * u = malloc_chk(sizeof(struct element));
DEBUG_POINT_sss(1, "element_new", key, value);
u->key = strcpy(malloc_chk(sizeof(char) * (strlen(key) + 1)), key);
u->value = strcpy(malloc_chk(sizeof(char) * (strlen(value) + 1)), value);
u->value_def = u->value;
u->next = 0;
return u;
}
/**
* Creates a new element and add it to the list
* @param key key of the element (it is copied)
* @param value value of the element (it is copied and used both for 'value' and 'value_def' fields)
* @return the new element (that is also the tail of the list)
*/
struct element * element_add(const char * key, const char * value) {
DEBUG_POINT_sss(2, "element_add", key, value);
if (!head) {
head = element_new(key, value);
tail = head;
} else {
tail->next = element_new(key, value);
tail = tail->next;
}
return tail;
}
/**
* Search the list for an element, given the key
* @param key key of the element
* @return the first element with such a key, NULL if not found
*/
struct element * element_find(const char * key) {
struct element * el = head;
DEBUG_POINT_ss(2, "element_find", key);
while((el) && (strcmp(el->key, key) != 0))
el = el->next;
if(!el) {
DEBUG_POINT_ssss(3, "element_find NOT FOUND", el->key, el->value, el->value_def);
return NULL;
}
DEBUG_POINT_ssss(3, "element_find FOUND", el->key, el->value, el->value_def);
return el;
}
/**
* Search the list for an element and adds a new element after it
* appends the new element if previous_key is not found
* @param key key of the element
* @param value value of the element (it is copied and used only for 'value' field)
* @return the new element
*/
struct element * element_insert(const char * previous_key, const char * new_key) {
struct element * el = element_find(previous_key), * new_el;
DEBUG_POINT_sss(1, "element_insert", previous_key, new_key);
if(el) {
DEBUG_POINT_ss(2, "element_insert INSERTED after", el->key);
new_el = element_new(new_key, NOT_DEFINED);
new_el->next = el->next;
el->next = new_el;
} else {
DEBUG_POINT_ss(2, "element_insert APPEND after", tail->key);
new_el = element_add(new_key, NOT_DEFINED);
}
return new_el;
}
/**
* Search the list for an element and changes it's value
* appends a new element if key is not found
* @param key key of the element
* @param value value of the element (it is copied and used only for 'value' field)
* @return the modified element
*/
struct element * element_change(const char * key, const char * value) {
struct element * el = element_find(key);
if(!el) { // if element doesn't exist let's check for a known exception
int i, err, found;
found = 0;
for(i = 0; (i < NUM_EXCEPTIONS) && !found; i++)
if((err = regexec(&exceptions[i].compiled, key, 0, NULL, 0)) == 0 /*REG_OKAY*/) {
el = element_insert(exceptions[i].previous, key);
exceptions[i].previous = strcpy(malloc_chk(sizeof(char) * (strlen(key) + 1)), key);
found = 1;
} else if(err != REG_NOMATCH) {
int err_dim = regerror(err, &exceptions[i].compiled, NULL, 0);
char * err_text = malloc_chk(sizeof(char) * err_dim);
regerror(err, &exceptions[i].compiled, err_text, err_dim);
fprintf(stderr, "Cannot execute regular expression [%s] on [%s]: %s.\n", exceptions[i].match, key, err_text);
exit(4);
}
if(!found)
el = element_add(key, NOT_DEFINED);
}
DEBUG_POINT_sss(1, "element_change", key, value);
DEBUG_POINT_ssss(2, "element_change PRE", el->key, el->value, el->value_def);
if (el->value != el->value_def)
free(el->value);
el->value = strcpy(malloc_chk(sizeof(char) * (strlen(value) + 1)), value);
DEBUG_POINT_ssss(2, "element_change POST", el->key, el->value, el->value_def);
return el;
}
/**
* Compiles a regular expression with error management
* @param expression the regular expression to be compiled
* @param compiled the struct to fill with compiled expression
*/
void re_compile(regex_t * compiled, const char * expression, int options) {
int err;
if(err = regcomp(compiled, expression, options)) {
int err_dim = regerror(err, compiled, NULL, 0);
char * err_text = malloc_chk(sizeof(char) * err_dim);
regerror(err, compiled, err_text, err_dim);
fprintf(stderr, "Cannot compile regular expression [%s]: %s.\n", expression, err_text);
exit(5);
}
}
/**
* Opens a text file for reading, with error management
* @param filename filename of the file to be opened
* @param mode file mode (see "man 3 fopen")
* @return the opened file
*/
FILE * file_open(const char * filename, const char * mode) {
FILE * u;
if ((u = fopen(filename, mode)) == NULL) {
fprintf(stderr, "Cannot open default file [%s].\n", filename);
exit(6);
}
return u;
}
/**
* Parse a line
* @param re regular expression used to parse the line
* @param line line to be parsed (it modified)
* @param funct function to be called with the two parsed parameters (as key and value, respectively)
*/
void line_parse(regex_t * re, char * line, struct element * (* funct)(const char *, const char *), unsigned int linenum) {
int err, len;
regmatch_t match[3];
if(!line)
return;
DEBUG_POINT_ss(4, "parsing line", line)
if((err = regexec(re, line, 3, match, 0)) != 0 /*REG_OKAY*/) {
if(err == REG_NOMATCH)
return;
else {
int err_dim = regerror(err, re, NULL, 0);
char * err_text = malloc_chk(sizeof(char) * err_dim);
regerror(err, re, err_text, err_dim);
fprintf(stderr, "Cannot execute regular expression on line %u: %s.\n", err_text, line);
exit(7);
}
}
// ok reusing line this way is a little dirty but copying each string two times is worse, moreover...
line[match[1].rm_eo] = 0; // ...there is always a '=' char after the match
line[match[2].rm_eo] = 0; // ...there is always a '"' char after the match
funct(line + match[1].rm_so, line + match[2].rm_so);
}
char * optarg; // getopt parameters
int main(const int ac, const char *av[]) {
char * rc_conf_default = "/etc/defaults/rc.conf";
char * rc_conf_current = "/etc/rc.conf";
char * rc_conf_out = NULL;
int no_comments = 0, full = 0, all = 0;
regex_t re_default, re_current, * re_exceptions;
FILE * in, * out = stdout;
char line[MAX_LINE];
struct element * el;
int i, tabs;
time_t now;
unsigned int linenum;
DEBUG_POINT_s(1, "parsing command-line")
while ((i = getopt(ac, av, "d:c:w:anfeh?")) != -1)
switch (i) {
case 'd': rc_conf_default = optarg; break;
case 'c': rc_conf_current = optarg; break;
case 'w': rc_conf_out = optarg; break;
case 'n': no_comments = 1; break;
case 'f': full = 1; break;
case 'a': all = 1; break;
case 'e':
fprintf(stderr,
COPYRIGHT "\n"
"\n"
"Exceptions list:\n"
);
for(i = 0; i < NUM_EXCEPTIONS; i++)
fprintf(stderr, "%2d. /%s/ after '%s'\n", i + 1, exceptions[i].match, exceptions[i].previous);
exit(1);
case 'h': // intentional fall-throughs
case '?': // undefined options get returned as '?' by getopt
default: // help or unrecognized option, let's write usage and exit
fprintf(stderr,
COPYRIGHT "\n"
"\n"
"Usage: %s [OPTION...]\n"
" -d filename default rc.conf file [%s]\n"
" -c filename current rc.conf file [%s]\n"
" -w filename write output to file\n"
" -n (no-comments) doesn't print comments with default value\n"
" -f (full) prints also options with default value\n"
" -a (all) prints all options already present in rc.conf\n"
" -h prints this help\n"
" -e prints exception list\n",
av[0], rc_conf_default, rc_conf_current
);
exit(1);
}
DEBUG_POINT_s(1, "compiling regular expressions")
re_compile(&re_default, RE_DEFAULT, REG_EXTENDED);
re_compile(&re_current, RE_CURRENT, REG_EXTENDED);
for(i = 0; i < NUM_EXCEPTIONS; i++)
re_compile(&exceptions[i].compiled, exceptions[i].match, REG_EXTENDED | REG_NOSUB);
DEBUG_POINT_s(1, "parsing default file")
in = file_open(rc_conf_default, "rt");
linenum = 1;
while(!feof(in))
line_parse(&re_default, fgets(line, MAX_LINE, in), element_add, linenum++);
fclose(in);
DEBUG_POINT_s(1, "parsing config file")
in = file_open(rc_conf_current, "rt");
linenum = 1;
while(!feof(in))
line_parse(&re_current, fgets(line, MAX_LINE, in), element_change, linenum++);
fclose(in);
DEBUG_POINT_s(1, "generating output")
if(rc_conf_out)
out = file_open(rc_conf_out, "wt");
time(&now);
fprintf(out,
"# Reordered by rclean on %s"
"# " COPYRIGHT "\n"
"# Please make all changes to this file, not to /etc/defaults/rc.conf\n"
"# This file contains just the overrides from /etc/defaults/rc.conf\n",
ctime(&now)
);
el = head;
// elaborates the full list
while(el) {
// prints only if 'full' is activated or if value is different from default value
if(full || ((el->value != el->value_def) && (all || (strcmp(el->value, el->value_def) != 0)))) {
fprintf(out, "%s=\"%s\"", el->key, el->value);
tabs = 52 - strlen(el->key) - strlen(el->value);
// print comment only if no 'no-comments' and (not 'full' or option is different from default)
if((!no_comments) && ((!full) || (strcmp(el->value, el->value_def) != 0))) {
if(tabs >= 8) {
tabs >>= 3;
while(tabs--)
fputc('\t', out);
} else {
// at least a space if no tab is needed
fputc(' ', out);
}
fprintf(out, "# \"%s\"\n", el->value_def);
} else {
fputc('\n', out);
}
}
el = el->next;
}
DEBUG_POINT_s(1, "exiting")
exit(0);
}
syntax highlighted by Code2HTML, v. 0.9.1