/*----------------------------------------------------------------------------*/
/* mcc.c           (C) 1984 Brent Callaghan, 1995-2005 T.Birnthaler OSTC GmbH */
/*                                            tb@ostc.de - http://www.ostc.de */
/*----------------------------------------------------------------------------*/
/* TODO
   * LIZENZ festlegen
   * Mit "texdoc.sh" PDF erzeugen
   DONE
   * emacs mit richtiger Textsuche aufrufen (okay)
   * Temporären Merge-Dateinamen mit Extension der 1. Originaldatei erzeugen,
     damit Syntaxcoloring oky (mkstemp, mktemp, tmpnam, tempnam, tmpfile)
     (setenv("VIMINIT", ":set filetype=c", 1); funktioniert nicht,
      da dann "~/.vimrc" nicht mehr inkludiert wird)
   * Umgebungsvariable DEBUG -> Debug-Modus
   * "mccw" führt auch bei reinen Warnungen zum Editoraufruf
   * "warning:" und "error:" unterscheiden (nix zu tun, erfolgt automatisch)
   * Schalter -c COMPILER, -e EDITOR, -d=DEBUG, -p=PREFIX dazu=
     (bringt's nicht, da die C-Compiler-Optionen "überdeckt" werden).
   END OF TODO + DONE */
/*----------------------------------------------------------------------------*/
/* HEAD:

   \title{mcc (Merge cc)\\
          Interaktiver C-Compiler}
   \author{Brent Callaghan (???) \and
           Thomas Birnthaler (tb@ostc.de) \and
           OSTC GmbH}
   \date{Version \rcs$Revision: 1.5 $\ ---\ \today\ ---\ \timeofday}
   \maketitle

   \noindent
   Startet einen wählbaren C-Compiler mit den angegebenen Quellcode-Dateien,
   (übergibt alle Kommandozeilenargumente an ihn) und fängt alle seine
   Fehlermeldungen ab. Gibt der Compiler wegen Übersetzungsfehlern einen
   Exit-Status ungleich 0 zurück, werden die Fehlermeldungen temporär an der
   passenden Stelle (Zeilennummer) in den Quellcode eingefügt und ein wählbarer
   Editor mit der resultierenden Datei aufgerufen. Der Cursor steht auf der
   ersten fehlerhaften Zeile. Der Quellcode kann gemäß den Fehlermeldungen
   korrigiert werden, ein Wiederholen des Suchkommandos (z.B. \verb|n|=next
   im \verb|vi/vim|) positioniert den Cursor auf die nächste fehlerhafte
   Zeile. Nach dem Speichern der Datei und dem Verlassen des Editors werden
   die Fehlermeldungen wieder entfernt und der C-Compiler erneut aufgerufen
   (sofern mindestens eine Änderung am Quellcode vorgenommen wurde).

   Wird \verb|mcc| unter dem Namen "mccw" aufgerufen, so wird der Editor auch
   dann aufgerufen, wenn beim Übersetzen zwar kein Fehler, aber mindestens
   eine Warnung auftritt.

   Ob der Quelltext geändert wurde, wird anhand einer 32-Bit Checksumme
   über den ganzen Text hinweg ermittelt.

   Diese Compile-Merge-Edit-Unmerge-Schleife endet, sobald der C-Compiler den
   Linker aufruft, die Quelldatei nicht geändert wurde oder der Anwender
   \verb|mcc| nach dem Verlassen des Editors mit Ctrl-C unterbricht.

   Die Umgebungsvariablen \verb|COMPILER|, \verb|EDITOR| und \verb|CFLAGS|
   können zur Auswahl eines Compilers, eines Editors und der standardmäßig zu
   verwendenden C-Flags eingesetzt werden. Die Defaultwerte sind \verb|gcc|,
   \verb|vi/vim| und \verb|-Wall|.

   Eine gesetzte Umgebungsvariable \verb|DEBUG=1| schaltet den Debugmodus ein.

   ACHTUNG:
   Wird mehr als eine Datei beim Aufruf des Compilers angegeben, so kann nur
   die ERSTE fehlerbehaftete Datei editiert werden. Wird sie fehlerfrei,
   so kann die nächste mit Fehlern behaftete Datei editiert werden. Bereits
   korrekte Quelldateien VOR der ersten fehlerbehafteten Datei werden immer
   wieder neu übersetzt, nachdem die fehlerbehaftete Datei verbessert wurde.

   Vor dem allerersten Aufruf von \verb|mcc| das Kommando \verb|stty -tabs|
   absetzen, da sonst die Bildschirmdarstellung nicht korrekt erfolgt.

   Lizenz: Dieses Programm kann frei verwendet und verteilt werden,
           es darf hingegen nicht verkauft werden.

   Erste Version: Juli 1984

   END:
*/
/*----------------------------------------------------------------------------*/
/* $Id: mcc.c,v 1.5 2005/04/08 13:25:34 tsbirn Exp tsbirn $ */
/*----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
/* DESC: Includes. */
/*----------------------------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <assert.h>

/*----------------------------------------------------------------------------*/
/* DESC: Konstanten (bei Bedarf umdefinieren). */
/*----------------------------------------------------------------------------*/
#define STD_COMPILER "/usr/bin/gcc"     /* Umgebungs-Variable COMPILER */
#define STD_EDITOR   "/bin/vim"         /* Umgebungs-Variable EDITOR */
#define STD_CFLAGS   "-Wall"            /* Umgebungs-Variable CFLAGS */
#define STD_PREFIX   ">>>"              /* Umgebungs-Variable PREFIX */
#define STD_POSTFIX  "<<<"              /* Umgebungs-Variable POSTFIX */
#define TMPDIR       "/tmp"
#define MRGFILE      "mcc"

/* Editor-Typen */
#define VI           1
#define VIM          2
#define EMACS        3
#define EVE          4

/*----------------------------------------------------------------------------*/
/* DESC: Datentypen. */
/*----------------------------------------------------------------------------*/
typedef void (*SIGNAL_HANDLER)(int);

/*----------------------------------------------------------------------------*/
/* DESC: Globale Variablen. */
/*----------------------------------------------------------------------------*/
int   debug      = 0;                /* Debugmodus 0=aus/1=an*/

/* Nur wegen Quit() global */
int   child_pid  = 0;                /* Kind-PID (Compiler, Editor) */
char  err_file[] = "/tmp/errXXXXXX"; /* Fehlermeldungen */
char* merge_file;                    /* Quellcode + Fehlermeldungen */

/*----------------------------------------------------------------------------*/
/* DESC: Zu ignorierende Compiler-Meldungen (bei Bedarf erweitern!). */
/*----------------------------------------------------------------------------*/
char* ignore_message[] = {
	"(Each undeclared identifier is reported only once",
	"for each function it appears in.)",
};

/*----------------------------------------------------------------------------*/
/* DESC: Funktions-Prototypen. */
/*----------------------------------------------------------------------------*/
int   Compile      (char* compiler, int argc, char* argv[], char* err_file);
int   Merge        (char* err_file, char* first_src_file, char* merge_file,
                    int err_cnt, char* prefix, char* postfix);
void  Edit         (int editor_type, char* editor, char* merge_file, char* prefix, char* err_file);
int   UnMerge      (char* merge_file, char* src_file, char* prefix);
int   NextError    (FILE* err_fp, char* src_file, char* err_msg);
int   GetErrorCnt  (char* err_file);
void  ListErrors   (char* err_file);
void  AddCflags    (char* cflags, int* argc, char*** argv);
FILE* FopenOrExit  (char* file, char* mode);
long  CheckSum     (long sum, char* line);
void  CatchSignals (SIGNAL_HANDLER handler);
void  Quit         (int dummy);
void  Debug        (char *format, ...);
void  DebugArgv    (int argc, char* argv[]);

/*----------------------------------------------------------------------------*/
/* DESC: Hauptprogramm: Umgebungsvariablen einlesen, C-Flags hinzufügen,
   Editortyp erkennen, Schleife: Compiler aufrufen, bei Fehler/Warnungen
   Fehlermeldungen in Quelldatei einfügen und Editor damit aufrufen. Abbruch
   falls keine Fehlermeldungen mehr auftreten oder der Quellcode nicht
   editiert wurde. */
/*----------------------------------------------------------------------------*/
int
main(int argc, char* argv[])
{
	char* compiler;
	int   editor_type  = 0;                /* Welchen Editor verwenden? */
	char* cflags;
	char* editor;
	char* ed_name;
	int   warn_flag = 0;      /* Warnungen reichen zum Editieren aus */
	char* prefix;                          /* Präfix gemergter Fehlermeldungen */
	char* postfix;                         /* Postfix gemergter Fehlermeldungen */
	char* ext;
	char* tmp_name;
	char  first_src_file[BUFSIZ];
	int   status;
	int   err_cnt;
	int   i;
	int   old_chksum;   /* Checksumme über Original-Quellcode */
	int   new_chksum;   /* Checksumme über veränderten Quellcode aus Merge */

	/* Debugging ein? */
	if (getenv("DEBUG") != NULL)
		debug = 1;

	Debug("main(");
	DebugArgv(argc, argv);
	Debug(")\n");

	/* Einstellungen einlesen (Kopieren bei cflags notwendig, da verändert) */
	if ((compiler = getenv("COMPILER")) == NULL)
		compiler = STD_COMPILER;
	if ((cflags = getenv("CFLAGS")) != NULL)
		cflags = strdup(cflags);
	else
		cflags = strdup(STD_CFLAGS);
	if ((editor = getenv("EDITOR")) == NULL)
		editor = STD_EDITOR;
	if ((prefix = getenv("PREFIX")) == NULL)
		prefix = STD_PREFIX;
	if ((postfix = getenv("POSTFIX")) == NULL)
		postfix = STD_POSTFIX;

	/* Editortyp bestimmen */
	ed_name = (ed_name = strrchr(editor, '/')) == NULL ? editor : ed_name + 1;
	if (strcmp(ed_name, "vim") == 0)
		editor_type = VIM;
	else if (strcmp(ed_name, "vi") == 0)
		editor_type = VI;
	else if (strcmp(ed_name, "emacs") == 0)
		editor_type = EMACS;
	else if (strcmp(ed_name, "eve") == 0)
		editor_type = EVE;

	/* Warnungen berücksichtigen? */
	warn_flag  = (strstr(argv[0], "mccw") != 0);

	/* Namen  der temporären Dateien sicher erzeugen */
	mkstemp(err_file);

	/* Statt "mkstemp(merge_file);" Original-Datei-Extension anhängen,
       damit Editor-Syntaxcoloring funktioniert. */
	for (i = 1; i < argc; ++i) {
		if ((ext = strchr(argv[i], '.')) != NULL)
			break;
	}
	if (i == argc)
		ext = "";
	tmp_name = tempnam(TMPDIR, MRGFILE);
	merge_file = (char*) malloc(strlen(tmp_name) + strlen(ext) + 1);
	strcpy(merge_file, tmp_name);
	strcat(merge_file, ext);
	Debug("merge_file: %s\n", merge_file);

	/* Zusätzliche C-Flags einfügen */
	AddCflags(cflags, &argc, &argv);

	/* Abbruch bei Signalen (meistens) */
	CatchSignals(Quit);

	/* Übersetzen solange Fehler/Warnung vorhanden */
	while (1) {
		status = Compile(compiler, argc, argv, err_file);
	    err_cnt = GetErrorCnt(err_file);

		/* Keine Warnungen und keine Fehler? -> Abbruch */
		if ((status == 0 && !warn_flag) ||
		    err_cnt == 0)
			break;

		/* Quelldatei + Fehlerliste mergen und Ergebnis editieren */
		if ((old_chksum = Merge(err_file, first_src_file, merge_file,
		                        err_cnt, prefix, postfix)) == EOF)
			break;
		Edit(editor_type, editor, merge_file, prefix, err_file);

		/* Fehlermeldungen wieder entfernen (nicht unterbrechbar!) */
		CatchSignals(SIG_IGN);
		new_chksum = UnMerge(merge_file, first_src_file, prefix);
		CatchSignals(Quit);

		/* Datei unverändert -> Abbruch */
		if (old_chksum == new_chksum)
			break;

		/* Kommandozeile nochmal ausgeben (falls Übersetzen länger dauert) */
		for (i = 0; i < argc; ++i)
			printf("%s ", argv[i]);
		putchar('\n');
	}

	ListErrors(err_file);   /* Fehlermeldungen auf jeden Fall ausgeben */

	exit(status);
}

/*----------------------------------------------------------------------------*/
/* DESC: Compiler <compiler> mit Argumentliste <argv> aufrufen, vorher
   die Fehlerausgabe *stderr* in Datei <err_file> umleiten,
   Exitstatus zurückgeben. */
/*----------------------------------------------------------------------------*/
int
Compile(char* compiler, int argc, char* argv[], char* err_file)
{
	int status;

	Debug("Compile(%s, ", compiler);
	DebugArgv(argc, argv);
	Debug(", %s)\n", err_file);

	switch (child_pid = fork()) {
		case 0:                                   /* Kindprozess */
			freopen(err_file, "w", stderr);       /* stderr -> err_file */
			execvp(compiler, argv);               /* Compiler starten */
			perror("Couldn't exec compiler");
			exit(101);

		case -1:                                  /* Fehler */
			perror("Couldn't fork compiler");
			exit(102);

		default:                                  /* Vaterprozess */
			while (wait(&status) != child_pid)    /* Auf Compilerende warten */
				;
	}
	return ((status >> 8) & 0xFF); /* Signal weg, Exit-Status übrig (Idiom!) */
}

/*----------------------------------------------------------------------------*/
/* DESC: Die Quellcode-Datei <first_src_file> und die Fehlermeldungs-Datei
   <err_file> zur Datei <merge_file> kombinieren, dabei die (alte) Checksumme
   der Quelldatei berechnen und zurückgeben (<first_src_file> ist nur
   ein Rückgabeparameter und enthält den ersten in <err_file> gefundenen
   Dateinamen!).  (WIESO <first_src_file> hier ???) */
/*----------------------------------------------------------------------------*/
int
Merge(char* err_file, char* first_src_file, char* merge_file, int err_cnt, char* prefix, char* postfix)
{
	FILE* err_fp;
	FILE* src_fp;
	FILE* merge_fp;
	char  src_file[BUFSIZ];
	char  src_line[BUFSIZ];
	char  err_msg[BUFSIZ];
	int   src_line_nr;
	int   err_line_nr;
	int   err_nr;
	int   check_sum;

	Debug("Merge(%s, %s, %s, %d)\n", err_file, first_src_file, merge_file, err_cnt);

	/* 1. Fehlerzeile und 1. Quelldateinamen ermitteln */
	err_fp = FopenOrExit(err_file, "r");
	if ((err_line_nr = NextError(err_fp, src_file, err_msg)) == EOF)
		return EOF;
	strcpy(first_src_file, src_file);

	/* Quelldatei muss schreibbar sein! */
	if (access(first_src_file, W_OK) < 0) {
		perror("Erroneous file not writable");   /* Dateiname ergänzen */
		ListErrors(err_file);
		exit(103);
	}

	src_fp      = FopenOrExit(src_file, "r");
	merge_fp    = FopenOrExit(merge_file, "w");
	src_line_nr = 0;
	err_nr      = 0;
	check_sum   = 0;

	/* Dateien mischen und dabei Checksumme der Quelldatei ermitteln */
	while (fgets(src_line, BUFSIZ, src_fp) != NULL) {
		++src_line_nr;
		check_sum = CheckSum(check_sum, src_line);
		fputs(src_line, merge_fp);

		/* Alle Fehlermeldungen mit gleicher Zeilennummer an Quellcodezeile anhängen. */
		while (src_line_nr == err_line_nr) {
			++err_nr;
			fprintf(merge_fp, prefix);
			fprintf(merge_fp, " ");
			fprintf(merge_fp, err_msg);
			fprintf(merge_fp, " ");
			fprintf(merge_fp, postfix);
			if ((err_line_nr = NextError(err_fp, src_file, err_msg)) == EOF ||
			    strcmp(first_src_file, src_file) != 0)
			    fprintf(merge_fp, " (last of %d)\n", err_cnt);
			else
			    fprintf(merge_fp, " (%d of %d)\n", err_nr, err_cnt);
		}
	}

	fclose(merge_fp);
	fclose(src_fp);
	fclose(err_fp);

	return check_sum;
}

/*----------------------------------------------------------------------------*/
/* DESC: Editor <editor> mit Datei <merge_file> aufrufen und zum ersten Fehler
   springen. Falls Aufruf nicht möglich, Fehlerliste ausgeben und abbrechen. */
/*----------------------------------------------------------------------------*/
void
Edit(int editor_type, char* editor, char* merge_file, char* prefix, char* err_file)
{
	int  status;
	char cmd[BUFSIZ];

	Debug("Edit(%d, %s, %d, %s, %s)\n", editor_type, editor, merge_file, prefix, err_file);

	switch (child_pid = fork()) {
		case 0:                                  /* Kindprozess */
			switch (editor_type) {
				case VI:
				case VIM:
				    /* vi +/^%s/ %s */
				    sprintf(cmd, "+/^%s/", prefix);
				    execlp(editor, "vim", cmd, merge_file, (char*)NULL);
					break;
				case EMACS:
			    	/* emacs +/^%s/ %s */
				    sprintf(cmd, "+/^%s/", prefix);
				    execlp(editor, "emacs", cmd, merge_file, (char*)NULL);
					break;
				case EVE:
					break;
					/* eve %s +/%s */
					sprintf(cmd, "+/^%s", prefix);
					execlp(editor, "eve", cmd, merge_file, (char*)NULL);
				default:
			    	/* editorname %s */
			    	execlp(editor, editor, merge_file, (char*)NULL);
					break;
			}
			perror("Couldn't exec editor");
			ListErrors(err_file);
			exit(104);

		case -1:                                 /* Fehler */
			perror("Couldn't fork editor");
			ListErrors(err_file);
			exit(105);

		default:                                 /* Vaterprozess */
			while (wait(&status) != child_pid)   /* Auf Editorende warten */
				;
	}
}

/*----------------------------------------------------------------------------*/
/* DESC: Aus Datei <merge_file> die eingemischten Fehlermeldungen wieder
   entfernen und den (evtl. korrigierten) Quelltext in <src_file> abspeichern.
   Dabei (neue) Checksumme berechnen und zurückgeben. */
/*----------------------------------------------------------------------------*/
int
UnMerge(char* merge_file, char* src_file, char* prefix)
{
	FILE* merge_fp;
	FILE* src_fp;
	char  line[BUFSIZ];
	int   check_sum;

	Debug("UnMerge(%s, %s)\n", merge_file, src_file);

	merge_fp = FopenOrExit(merge_file, "r");
	src_fp   = FopenOrExit(src_file, "w");

	/* Zeilen ohne prefix am Anfang kopieren */
	check_sum  = 0;
	while (fgets(line, BUFSIZ, merge_fp) != NULL) {
		if (strncmp(line, prefix, sizeof(prefix) - 1) != 0) {
			check_sum = CheckSum(check_sum, line);
			fputs(line, src_fp);
		}
	}

	fclose(merge_fp);
	fclose(src_fp);
	remove(merge_file);

	return check_sum;
}

/*----------------------------------------------------------------------------*/
/* DESC: Liest die nächste Fehlermeldung aus <err_fp> und gibt die
   Zeilennummer der Quelltextzeile als Returnwert, den Namen der Quelldatei
   in <src_file> sowie die Fehlermeldung in <err_msg> zurück (bzw. \verb|EOF|
   nach der letzten Fehlermeldung).

   Das Format der Fehlermeldungen ist abhängig vom Compiler (FÄLLE in [...],
   übersetzt wurde die Datei "testmcc.c"):

   \begin{itemize}
      \item gcc (alt)
      \begin{verbatim}
         [1] t.c: In function `main'
         [2] t.c:9: `a' undeclared (first use this function)
         [2] t.c:9: `b' undeclared (first use this function)
         [2] t.c:12: parse error before "int"
         [3] t.c: At top level
         [4] t.c:17: undefined or invalid # directive
      \end{verbatim}
      \item gcc (neu)
      \begin{verbatim}
		 [2] t.c:4: warning: return type defaults to `int'
         [1] t.c: In function `main':
         [2] t.c:9: error: `a' undeclared (first use in this function)
         [0] t.c:9: error: (Each undeclared identifier is reported only once
         [0] t.c:9: error: for each function it appears in.)
         [2] t.c:9: error: `b' undeclared (first use in this function)
		 [2] t.c:12: warning: implicit declaration of function `test'
         [2] t.c:12: error: parse error before "int"
         [4] t.c:17:2: invalid preprocessing directive #xxx
      \end{verbatim}
      \item cc (Solaris)
      \begin{verbatim}
         [5] "t.c", line 9: a undefined
         [5] "t.c", line 9: b undefined
         [2] "t.c", line 12: parse error before "int"
         [6] t.c: 17: undefined control
      \end{verbatim}
      \item MS-Visual C
      \begin{verbatim}
         [7] t.c
         [8] t.c(9) : error C2065: 'a' undeclared identifier
         [8] t.c(9) : error C2065: 'b' undeclared identifier
         [2] t.c(12) : error C2089: parse error before 'int'
         [9] t.c(17) : fatal error C1021: invalid preprocessor command 'xxx'
      \end{verbatim}
   \end{itemize}
   Im Prinzip handelt es sich dabei immer um eine Sequenz der Form
   \begin{verbatim}
      Quelldatei  Zeilennummer  Fehlermeldung/Warnung
   \end{verbatim}
   getrennt durch unterschiedlichen "`Syntaktischen Zucker"'
   (\verb|: "..." (...)|).
*/
/*----------------------------------------------------------------------------*/
int
NextError(FILE* err_fp, char* src_file, char* err_msg)
{
	char  line[BUFSIZ];
	int   src_line_nr;
	char* cp_start;
	char* cp_end;
	int   i;

	Debug("NextError(");

	while (fgets(line, BUFSIZ, err_fp) != NULL) {

		/* Newline entfernen */
		line[strlen(line) - 1] = '\0';

		/* FALL 0: Hinweismeldungen ignorieren */
		for (i = 0; i < sizeof(ignore_message) / sizeof(char*); ++i) {
			if (strstr(line, ignore_message[i]))
				break;
		}
		if (i != sizeof(ignore_message) / sizeof(char*))
			continue;

		/* Quelldateinamen extrahieren (steht vor Trennzeichen :,( */
		cp_start = line;
		cp_end = strpbrk(cp_start, ":,(");
		if (cp_end == NULL)
			continue; /* FALL 7 */
		*cp_end++ = '\0';

		/* FALL 5 ("..." um Dateinamen ignorieren/entfernen) */
		if (*cp_start == '"') {
			++cp_start;
			if (*(cp_end-2) == '"')
				*(cp_end-2) = '\0';
		}
		strcpy(src_file, cp_start);

		/* Quellzeilennummer extrahieren */
		for (cp_start = cp_end; *cp_start != '\0' && !isdigit(*cp_start); ++cp_start)
			;

        /* FALL 1,3: [1] t.c: In function `main': (d.h. Zeilennummer fehlt) */
		if (*cp_start == '\0')
			continue;

		cp_end = strchr(cp_start, ':');
		assert(cp_end != NULL);   /* Sollte nicht vorkommen! */
		*cp_end = '\0';
		src_line_nr = atoi(cp_start);

		/* FALL 2/4/5/6/8/9: Rest nach den Leerzeichen ist die Fehlermeldung */
		for (++cp_end; *cp_end != '\0' && isspace(*cp_end); ++cp_end)
			;
		strcpy(err_msg, cp_end);

		Debug("%s, %s) -> %d\n", src_file, err_msg, src_line_nr);
		return src_line_nr;
	}

	/* Letzten Fehler erreicht */
	Debug(") -> EOF\n");
	return EOF;
}

/*----------------------------------------------------------------------------*/
/* DESC: Liest alle Fehlermeldung/Warnungen aus Fehlerdatei <err_file> und
   gibt ihre Anzahl zurück. */
/*----------------------------------------------------------------------------*/
int
GetErrorCnt(char* err_file)
{
	FILE* fp;
	char  err_msg[BUFSIZ];
	char  src_file[BUFSIZ];       /* Nur damit Speicher da ist, keine Rückgabe */
	int   err_cnt;

	Debug("GetErrorCnt(%s)\n", err_file);

	fp = FopenOrExit(err_file, "r");
	err_cnt     = 0;
	src_file[0] = '\0';   /* notwendig ??? */
	err_msg[0]  = '\0';   /* notwendig ??? */
	while (NextError(fp, src_file, err_msg) != EOF)
		++err_cnt;
	fclose(fp);

	return err_cnt;
}

/*----------------------------------------------------------------------------*/
/* DESC: Fehlerdatei <err_file> auf *stderr* ausgeben und dann löschen. */
/*----------------------------------------------------------------------------*/
void
ListErrors(char* err_file)
{
	FILE* fp;
	char  line[BUFSIZ];

	Debug("ListErrors(%s)\n", err_file);

	if ((fp = fopen(err_file, "r")) == NULL)
		return;

	while (fgets(line, BUFSIZ, fp) != NULL)
		fputs(line, stderr);

	fclose(fp);
	remove(err_file);   /* wichtig! */
}

/*----------------------------------------------------------------------------*/
/* DESC: Flags aus der Umgebungsvariablen <CFLAGS> (oder falls diese leer ist
   aus der Konstanten <STD_CFLAGS>) zu <argv> hinzufügen (nach dem 1. Argument)
   und <argc> entsprechend erhöhen. Jeweils ein oder mehrere Leerzeichen
   trennen einzelne C-Flags, Leerzeichen am Anfang/Ende werden ignoriert. */
/*----------------------------------------------------------------------------*/
void
AddCflags(char* cflags, int* argc, char*** argv)
{
	int    cflags_cnt;
	char*  cp;
	char** new_argv;
	int    i;

	Debug("AddCflags(");
	DebugArgv(*argc, *argv);
	Debug(")\n");

	/* Leerzeichen am Anfang/Ende von cflags entfernen */
	for (; isspace(*cflags); ++cflags)
		;
	for (cp = cflags + strlen(cflags) - 1; isspace(*cp); --cp)
		*cp = '\0';

	/* Anzahl zusätzl. C-Flags ermitteln und sie in Einzelstrings zerlegen,
	   die durch EIN NUL-Byte getrennt hintereinander stehen. */
	cflags_cnt = (cflags[0] != '\0');   /* leer oder mind. ein Flag? */
	for (cp = cflags; *cp; ++cp) {
		/* Leerzeichen? -> Noch ein Flag folgt */
		if (isspace(*cp)) {
			*cp = '\0';
			++cflags_cnt;
			/* Weitere Leerzeichen nach dem ersten ignorieren */
			while (isspace(*(cp+1)))
				strcpy(cp+1, cp+2);
		}
	}

	/* Neuen Argumentvektor anlegen, 1. Argument bleibt stehen */
	new_argv = (char**) malloc((*argc + cflags_cnt + 1) * sizeof(char*));
	new_argv[0] = (*argv)[0];

	/* Zusätzliche C-Flags einfügen */
	cp = cflags;
	for (i = 1; i <= cflags_cnt; ++i) {
		new_argv[i] = cp;
		cp += strlen(cflags) + 1;
	}

	/* Bisherige Argumente anhängen (und Liste mit NULL abschließen!) */
	for (i = 1; i < *argc; ++i)
		new_argv[i + cflags_cnt] = (*argv)[i];
	new_argv[i + cflags_cnt] = NULL;

	/* Argumentvektor ändern */
	*argc += cflags_cnt;
	*argv  = new_argv;

	Debug("AddCflags(");
	DebugArgv(*argc, *argv);
	Debug(")\n");
}

/*----------------------------------------------------------------------------*/
/* DESC: Datei <file> im Modus <mode> öffnen und Filepointer <fp>
   zurückgeben, bei Fehler Programm mit Fehlermeldung abbrechen. */
/*----------------------------------------------------------------------------*/
FILE*
FopenOrExit(char* file, char* mode)
{
	FILE* fp;

	if ((fp = fopen(file, mode)) == NULL) {
		perror(file);
		exit(100);
	}

	return fp;
}

/*----------------------------------------------------------------------------*/
/* DESC: 32-Bit Checksumme aus <sum> und allen Zeichen der Zeile <line>
   kombinieren und zurückgeben. */
/*----------------------------------------------------------------------------*/
long
CheckSum(long sum, char* line)
{
	for (; *line; ++line) {
		if (sum & 1)
			sum = (sum >> 1) | 0x80000000;
		else
			sum >>= 1;

		sum = (sum + *line) & 0xFFFFFFFF;
	}

	return sum;
}

/*----------------------------------------------------------------------------*/
/* DESC: Signale \verb|HUP INT QUIT TERMN| mit Funktion <handler> abfangen
   (z.B. Prozess nicht unterbrechbar machen). */
/*----------------------------------------------------------------------------*/
void
CatchSignals(SIGNAL_HANDLER handler)
{
	signal(SIGHUP,  handler);
	signal(SIGINT,  handler);
	signal(SIGQUIT, handler);
	signal(SIGTERM, handler);
}

/*----------------------------------------------------------------------------*/
/* DESC: Programmabbruch: Kindprozess <child_pid> abbrechen und Zwischendateien
   <err_file> und <merge_file> löschen.
   Die Variable <dummy> ist bedeutungslos, sie ist nur für den korrekten
   Funktionsprototyp nötig. */
/*----------------------------------------------------------------------------*/
void
Quit(int dummy)
{
	kill(child_pid, SIGTERM);
	remove(merge_file);
	remove(err_file);

	exit(111);
}

/*----------------------------------------------------------------------------*/
/* DESC: Debug-Ausgabe der Parameter nach <format> auf *stderr*. Text
   \verb|"DBG: "| davorstellen, falls beim vorherigen Aufruf ein Newline '\n'
   vorkam (und beim allerersten Mal!). */
/*----------------------------------------------------------------------------*/
void
Debug(char *format, ...)
{
	static  int nl_flag = 1;
	va_list argp;

	if (!debug) return;

	if (nl_flag)
		fprintf(stderr, "DBG: ");
	nl_flag = (strchr(format, '\n') != NULL);

	va_start(argp, format);
	vfprintf(stderr, format, argp);
	va_end(argp);
}

/*----------------------------------------------------------------------------*/
/* DESC: Argument-Vektor auf *stderr* ausgeben. */
/*----------------------------------------------------------------------------*/
void
DebugArgv(int argc, char* argv[])
{
	int i;

	if (!debug) return;

	fprintf(stderr, "argc=%d", argc);
	for (i = 0; i < argc; ++i)
		fprintf(stderr, " argv[%d]=%s", i, argv[i]);
}
