// rclean Copyright (c) 2002 Lapo Luchini // $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 #include #include #include #include /*** 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 " // string used as default value when not defined in default file #define 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); }