Python script which checks for syntax error in rc.conf

Hello all,

I have made a simple Python scripts which checks your rc.conf file for syntax errors (basically it checks all the lines in the file and output the ones which contain syntax errors).

The script itself is very simple but I guess that many people will find it useful (I am pretty sure that I am not the only one who got "stuck" in the single user mode after the reboot, because of the syntax error(s) in the rc.conf file :)).

Oh and as far as I know, rc.conf is just a shell script, so my syntax checking script would break on some "special cases" (if statements, brackets, etc.), but I assume that most of the users want to keep their rc.conf short and clean and don't use this statements / constructs in their config file.

The script can be found on GitHub - http://github.com/Kami/rc-conf-syntax-checker.

P.S. As this problem is pretty common I still find it strange, that after all those years FreeBSD still does not offer something like this "natively" (for example, pfctl syntax checker is useful and works great).
 
Thanks Kami. I'll try to run it after every edit of /etc/rc.conf. Since I use devel/rvi for RCS versioning of my conf files already, I'll have to figure out how to migrate the two utilities for an error free configuration file versioning system.
 
No problem.

Using version control probably makes your life even easier :)

If you are using for example SVN / GIT, you could easily set up a pre-commit hook which would run this script on your config file and only commit the file is no syntax errors are detected.
 
devel/rvi uses RCS and it's fully automated. I have it aliased in roots shell to 'vi' just in case I forget. BTW, some configuration files will break under version control, so be careful. One side benefit is that backups also back up the changed version control files found on the system. So If I need to recover a ',V' it is extremely simple.

Back on the subject, I will have to take a look into the C code of devel/rvi to see if I can hook to an external script and check it's exit code to see what to do and to also avoid versioning certain files.
 
To be honest I have no experience with RCS, but it does look interesting and more appropriate for version controlling the configuration files then other systems (svn / git / mercurial / ...) so I will check it out.
 
Kami said:
P.S. As this problem is pretty common I still find it strange, that after all those years FreeBSD still does not offer something like this "natively" (for example, pfctl syntax checker is useful and works great).

Talk to FreeBSD developers at lists.freebsd.org for example on freebsd-questions, maybe they will like your idea and after several changes it will be incorporated as a base system utility.
 
vermaden said:
Talk to FreeBSD developers at lists.freebsd.org for example on freebsd-questions, maybe they will like your idea and after several changes it will be incorporated as a base system utility.

Not a python script...
If OP rewrite it in sh or C....
 
The current way to check for syntax errors is to run rc.conf as a script with sh:
Code:
sh /etc/rc.conf
. As it is in sh format, it should give no errors. I think sh outputs the line number on which it finds a syntax error.

However, a script that verifies that the entries match defaults/rc.conf and man rc.conf specs would be usefull, although it would be a complex piece of script.
 
Due to rbelk proposal, I have checked the RCS version control system and RVI and made minor modifications to the latter (I am not a C programmer so the few lines I have added might not be optimal - I basically included a Python interpreter in the code and called a function from my script).

Now, when editing the /etc/rc.conf file using the RVI tool and after saving it (before accepting or discarding the changes), it is checked for syntax errors and if any syntax errors are found, a warning is printed to the console.
 
I just realized, that few days ago, I wrote C module, to load config files, which are very similar to rc.conf....
the only difference is that my config files MUST use double quotes for values and after VALUE you need to end line with ; (as in C)

This module doesn't check any variables, it only checks if syntax is OK, and if it's not it will return line number of error.

look at confLoad function source

config.c
Code:
// Copyright (c) 2010, Aldis Berjoza <aldis@bsdroot.lv>
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// 2. 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.
// 3. Neither the name of the  nor the names of its
//    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.

#include <assert.h>

#include <ctype.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

#include "config.h"

#define CONF_LOADABLE_FILE_MAX_SIZE		10240	// 10KB should be nough for config file;


struct s_KeyValue *confLoad(const char *conf_file) {	// {{{1
	if (conf_file == NULL) {
		fprintf(stderr, "NULL pointer to faile name\n");
		assert(0);
		exit(1);
	}

	if (strlen(conf_file) == 0) {
		fprintf(stderr, "NULL file name provided\n");
		assert(0);
		exit(1);
	}

	int file;

	if ((file = open(conf_file, O_RDONLY)) == -1) {
		perror(NULL);
		assert(0);
		exit(1);
	}

	struct stat sb;

	if (fstat(file, &sb) == -1) {
		perror(NULL);
		assert(0);
		exit(1);
	}

	if (fstat(file, &sb) > CONF_LOADABLE_FILE_MAX_SIZE) {
		fprintf(stderr, "Config file '%s' is to big. Are you sure it's config file?\n", conf_file);
		assert(0);
		exit(1);
	}

	char *buff;

	if ((buff = malloc(sb.st_size + 1)) == NULL) {
		perror(NULL);
		assert(0);
		exit(1);
	}

	buff[sb.st_size + 1] = 0;
	int read_bytes = read(file, buff, sb.st_size);
	close(file);
	enum field_type {wspace1, option, wspace2, wspace3, value, wspace4, comment} field = wspace1;
	int i;
	int j = 0;
	int k = 0;
	int line = 1;
	const char MSG_SYNTAX_ERROR[] = "Syntax error in configuration file '%s', at line %d\n";

	for (i = 0; i < read_bytes; i++) {
		switch (field) {
			case wspace1:
				if	(isalpha(buff[i])) {
					field = option;
				}
				else if (buff[i] == '#') {
					j++;
					field = comment;
				}
				else if (buff[i] == '\n' || buff[i] == '\r') {
					j++;
					line++;
				}
				else if (buff[i] == '\r' && buff[i+1] == '\n') {
					j += 2;
					i++;
					line++;
				}
				else if	(isblank(buff[i])) {
					j++;
				}
				else {
					fprintf(stderr, MSG_SYNTAX_ERROR, conf_file, line);
					assert(0);
					exit(1);
				}
				break;

			case option:
				if	(isblank(buff[i])) {
					j++;
					field = wspace2;
				}
				else if (buff[i] == '=') {
					field = wspace3;
				}
				else if (!(isalnum(buff[i]) || buff[i] == '_')) {
					fprintf(stderr, MSG_SYNTAX_ERROR, conf_file, line);
					assert(0);
					exit(1);
				}
				break;

			case wspace2:
				if	(isblank(buff[i])) {
					j++;
				}
				else if	(buff[i] == '=') {
					field = wspace3;
				}
				else {
					fprintf(stderr, MSG_SYNTAX_ERROR, conf_file, line);
					assert(0);
					exit(1);
				}
				break;

			case wspace3:
				if	(isblank(buff[i])) {
					j++;
				}
				else if	(buff[i] == '"') {
					j++;
					field = value;
				}
				else if (buff[i] == '\n' || buff[i] == '\r') {
					j++;
					line++;
				}
				else if (buff[i] == '\r' && buff[i+1] == '\n') {
					j += 2;
					i++;
					line++;
				}
				else {
					fprintf(stderr, MSG_SYNTAX_ERROR, conf_file, line);
					assert(0);
					exit(1);
				}
				break;

			case value:
				if	(buff[i] == '"') {
					buff[i] = 0;
					field = wspace4;
				}
				else if	(buff[i] == '\\') {
					buff[i-j] = buff[i+1];
					j++;
					i++;
				}
				else if (buff[i] == '\n' || buff[i] == '\r') {
					j++;
					line++;
				}
				else if (buff[i] == '\r' && buff[i+1] == '\n') {
					j += 2;
					i++;
					line++;
				}
				break;

			case wspace4:
				if	(isblank(buff[i])) {
					j++;
				}
				else if (buff[i] == ';') {
					j++;
					field = wspace1;
				}
				else if (buff[i] == '\n' || buff[i] == '\r') {
					j++;
					line++;
				}
				else if (buff[i] == '\r' && buff[i+1] == '\n') {
					j += 2;
					i++;
					line++;
				}
				else {
					fprintf(stderr, MSG_SYNTAX_ERROR, conf_file, line);
					assert(0);
					exit(1);
				}
				break;

			default:	// comment
				if	(buff[i] == '\n' || buff[i] == '\r') {
					j++;
					line++;
					field = wspace1;
				}
				else if	(buff[i] == '\r' && buff[i+1] == '\n') {
					j += 2;
					i++;
					line++;
					field = wspace1;
				}
				else {
					j++;
				}
				break;
		}

		if (j != 0) {
			if (j == k) {
				buff[i-j] = buff[i];
				buff[i] = 0;
			}

			k = j;
		}
	}

	if (field != wspace1 && field != comment) {
		fprintf(stderr, MSG_SYNTAX_ERROR, conf_file, line);
		assert(0);
		exit(1);
	}

	struct s_KeyValue *config = NULL;

	if ((config = malloc(sizeof(struct s_KeyValue))) == NULL) {
		perror(NULL);
		assert(0);
		exit(1);
	}

	config->len = i - j + 1;

	if ((config->data = malloc(config->len)) == NULL) {
		perror(NULL);
		assert(0);
		exit(1);
	}

	memcpy(config->data, buff, config->len - 1);
	config->data[config->len] = 0;

	free(buff);
	return config;
}
// 1}}}

char *Value(const char *restrict key, const struct s_KeyValue *restrict kv_ptr) {	// {{{1
	if (key == NULL) {
		fprintf(stderr, "Invalid pointer to key\n");
		assert(0);
		exit(1);
	}

	if (strlen(key) == 0) {
		fprintf(stderr, "Invalid option name\n");
		assert(0);
		exit(1);
	}

	if (kv_ptr == NULL) return NULL;

	int l = strlen(key);

	for (int i = 0; i < kv_ptr->len; i++) {
		if (key[0] == (kv_ptr->data)[i]) {
			int found = 1;

			for (int j = 1; j < l; j++) {
				if (key[j] != (kv_ptr->data)[i+j]) {
					found = 0;
					break;
				}
			}

			if (found == 1 && (kv_ptr->data)[i+l] == '=') return kv_ptr->data + i + l + 1;
		}
	}

	fprintf(stderr, "Option '%s' not found\n", key);
	return NULL;
}
// 1}}}

void KeyValueFree(struct s_KeyValue *kv_ptr) {	// {{{1
	if (kv_ptr != NULL) {
		free(kv_ptr->data);
		free(kv_ptr);
	}
}
// 1}}}

// vim:tabstop=4:shiftwidth=4

config.h
Code:
// Copyright (c) 2010, Aldis Berjoza <aldis@bsdroot.lv>
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
// 
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// 2. 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.
// 3. Neither the name of the  nor the names of its
//    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.

#ifndef _CONFIG_H_
#define _CONFIG_H_	1

struct s_KeyValue {
	char *data;
	unsigned int len;
};


struct s_KeyValue *confLoad(const char *conf_file);
char *Value(const char *restrict key, const struct s_KeyValue *restrict kv_ptr);
void KeyValueFree(struct s_KeyValue *kv_ptr);

#endif
// vim:tabstop=4:shiftwidth=4


P.S.
I didn't edit this file, just copy/past it from my repository on PC to forum.
I write this stuff for University
I might give a shot to write syntax checker in C after 15.04 if nobody will write it in C till then


EDIT:
sorry for no comments in source, I was developing this very rapidly
 
after confLoad is finished it will return simple structure.... in which ... ->data config will be stored in format:

Code:
option1=value1\0option2=value2\0

to get lenght of data you can check ... ->len (for more info look at Value function, which returns Value for given key
 
Kami said:
Due to rbelk proposal, I have checked the RCS version control system and RVI and made minor modifications to the latter (I am not a C programmer so the few lines I have added might not be optimal - I basically included a Python interpreter in the code and called a function from my script).

Now, when editing the /etc/rc.conf file using the RVI tool and after saving it (before accepting or discarding the changes), it is checked for syntax errors and if any syntax errors are found, a warning is printed to the console.

WOW, thanks Kami for a fantastic birthday present, yes April 22'nd is my birthday!
 
I use /* */ to comment bocks.
I hate when you use /* comment */ in middle of code, and when you want to comment some big part of code, you have to remove that silly /* oneliners */ that could have been simply commented as // and no harm ever happens

Well that's my style


EDIT:
you may as well notice, that I'm writing C code in Java style ;), which I think it most readable
 
killasmurf86 said:
I use /* */ to comment bocks.
I hate when you use /* comment */ in middle of code, and when you want to comment some big part of code, you have to remove that silly /* oneliners */ that could have been simply commented as // and no harm ever happens

Well that's my style


EDIT:
you may as well notice, that I'm writing C code in Java style ;), which I think it most readable

Keep in mind that strict ANSI C standards(C89/C90) forbid usage of non C-style comments and I personally try not to use C++ ones at all in C code its a bad habit.
 
rbelk said:
WOW, thanks Kami for a fantastic birthday present, yes April 22'nd is my birthday!

No problem, I'm glad that you find it useful.

I'll see what I am going to do in the future - I might try and integrate some of the code from killasmurf86 to RVI so it won't not require extra (Python) dependency.
 
http://github.com/expl/rcch/blob/master/main.c

This probably will need some more tuning, if you notice something wrong tell me.

Enforces [KEY]="[VALUE]" structure, allows comments and spaces also has inbuild table with variables that come with freebsd base and tells if a unknown key has been found, also has ruleset for the values and tells if value is in wrong format.

To add 3rd party variables simple add something like
Code:
{"ifconfig_*", T_STR},
to the table, notice you can use * operator in definition. Value types are T_STR(string), T_INT(integer) and T_BOOL(boolean) for YES/NO.

compile: cc -ansi -Wall -O3 -o rcch main.c
open: ./rcch /etc/rc.conf
 
Carpetsmoker said:
Why make a Python script, and not just use % sh /etc/rc.conf?

But then again correctness of general syntax doesn't guarantee you will have a bootable rc.conf.
 
expl said:
But then again correctness of general syntax doesn't guarantee you will have a bootable rc.conf.

The Python script does not check that either. While running /bin/sh is a more complete/accurate check of the correct syntax.
I don't want to discourage Kami in his Python coding efforts, but in all honesty this looks like a rather useless script ... I once created a Python script to play MP3's randomly from a playlist with mpg123, only to discover mpg123 has an option to do exacly that, do'h!
 
Well, if you read my first post you will see that I already knew that a rc.conf is just a normal shell script and executing it would report the syntax errors :)

My initial idea was to make script "advanced" so it would recognize the basic entries and would try to recommend a proper fix if it recognizes a syntax error.
 
Back
Top