Commits

overlord  committed ff85e71

The Logmerge utility (designed with Pidgin in mind).

  • Participants

Comments (0)

Files changed (10)

+/*
+  Argument class for the logmerger
+
+  Copyright (C) 2003 Daybo Logic, all rights reserved.
+  http://www.daybologic.co.uk/mailddrp
+*/
+
+#include <assert.h>
+#include <string.h>
+#include <malloc.h>
+#ifdef HDRSTOP
+# pragma hdrstop
+#endif /*HDRSTOP*/
+
+#include "logmerge.h"
+
+static bool l_SetArgs(struct _S_ARGUMENTS* PArguments, const int ArgC, const char** Argv);
+static const char* l_GetArg(const struct _S_ARGUMENTS* PArguments, unsigned int ArgI);
+static void i_Init(struct _S_ARGUMENTS* PArguments);
+static void i_CleanArgs(struct _S_ARGUMENTS* PArguments);
+
+void S_Arguments_Construct(struct _S_ARGUMENTS* PArguments)
+{
+  if ( PArguments ) {
+    if ( PArguments->size == sizeof(struct _S_ARGUMENTS) ) {
+      i_Init(PArguments);
+      PArguments->SetArgs = l_SetArgs;
+      PArguments->GetArg = l_GetArg;
+    }
+  }
+}
+
+void S_Arguments_Destruct(struct _S_ARGUMENTS* PArguments)
+{
+  if ( PArguments ) {
+    if ( PArguments->size == sizeof(struct _S_ARGUMENTS) )
+      i_Init(PArguments);
+  }
+}
+
+static bool l_SetArgs(struct _S_ARGUMENTS* PObj, const int ArgC, const char** ArgV)
+{
+  bool ret = false;
+  if ( PObj ) {
+    if ( PObj->size == sizeof(struct _S_ARGUMENTS) ) {
+      if ( ArgC ) { /* Any arguments melord? */
+        if ( PObj->i_argv ) /* Any old arguments to get shot of? */
+          i_CleanArgs(PObj);
+
+        PObj->i_argv  = (char**)malloc(ArgC * sizeof(char*));
+        if ( PObj->i_argv ) {
+          int argi;
+          for ( argi = 0; argi < ArgC; argi++ ) {
+            if ( ArgV[argi] ) {
+              PObj->i_argv[argi] = (char*)malloc((strlen(ArgV[argi])+1)*sizeof(char));
+              if ( PObj->i_argv[argi] ) {
+                strcpy(PObj->i_argv[argi], ArgV[argi]);
+                PObj->i_argc++; /* We must keep consistency rather than assign this count at the end incase a malloc() fails */
+              }
+              else { /* Failed to allocate argument at argi */
+                assert( PObj->i_argv[argi] ); /* For debug builds */
+                ret = false;
+                break;
+              }
+            }
+            else { /* This parameter is blank, something is seriously wrong but let's handle it by pretending this is the end of the argument list */
+              ret = true; /* Success, to an extent */
+              break;
+            }
+          }
+          ret = true;
+        }
+        else { /* Failed to allocate a new i_argv */
+          ret = false;
+        }
+      }
+      else { /* No arguments, free up the old arguments */
+        ret = true;
+        if ( PObj->i_argv ) i_CleanArgs(PObj);
+      }
+    }
+  }
+  if ( !ret ) i_CleanArgs(PObj); /* Ensure no leakage, as caller won't clean up after we fail */
+  return ret;
+}
+
+static const char* l_GetArg(const struct _S_ARGUMENTS* PObj, unsigned int ArgI)
+{
+  const char* ret = NULL;
+  if ( PObj ) {
+    if ( PObj->size == sizeof(struct _S_ARGUMENTS) ) {
+      if ( ArgI < PObj->i_argc )
+        ret = PObj->i_argv[ArgI];
+    }
+  }
+  return ret;
+}
+
+static void i_Init(struct _S_ARGUMENTS* PObj)
+{
+  assert(PObj);
+  PObj->i_argc = 0;
+  PObj->i_argv = NULL;
+}
+
+static void i_CleanArgs(struct _S_ARGUMENTS* PArguments)
+{
+  assert(PArguments);
+  assert( (!PArguments->i_argv && PArguments->i_argc) ); /* Trap wayward count! */
+
+  if ( PArguments->i_argv ) { /* Anything in the vector? */
+    /* Clean the inside of the vector before releasing it */
+    int argi;
+
+    for ( argi = 0; argi < PArguments->i_argc; argi++ ) {
+      assert( PArguments->i_argv[argi] );
+      free(PArguments->i_argv[argi]);
+      PArguments->i_argc--;
+    }
+    assert(!PArguments->i_argc); /* Trap pointer count / count mismatch */
+    free(PArguments->i_argv); /* Release the vector */
+    PArguments->i_argv = NULL;
+  }
+}
+/*
+  This log merging app was designed for gaim.
+  Synopsis:
+  logmerge [log dir] [later logs dir]
+
+  log dir - The directory containing gaim's primary logs
+  later logs dir - The directory containing later editions to the logs
+
+  Written by David Duncan Ross Palmer, Daybo Logic.
+  http://www.daybologic.co.uk/
+  (C) Copyright Daybo Logic. all rights reserved.
+*/
+
+#include <assert.h>
+#include <sys/types.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#ifdef HDRSTOP
+# pragma hdrstop
+#endif /*HDRSTOP*/
+
+#include "logmerge.h"
+/*-------------------------------------------------------------------------*/
+/*
+  Arguments contains the raw arguments.  It is a class which will return
+  information on the actual parameters passed the program.  For the parsed
+  options, use Options instead.
+*/
+static S_ARGUMENTS Arguments;
+
+/*
+  Options is a structure which contains the parsed and orderly representation of
+  the command line options
+*/
+static S_OPTIONS Options;
+/*-------------------------------------------------------------------------*/
+/*
+  Title() displays the program title and copyright.
+*/
+static void Title(void);
+
+/*
+  Help() displays the syntax for calling the program.  The ProgramName argument
+  should be the first argument to the program, or at least a suitable name for
+  the final built program.
+*/
+static void Help(const char* ProgramName);
+
+/*
+  Version() displays details build information.  Version and build time/date
+*/
+static void Version(void);
+
+/*
+  Run() should only be called from main()
+*/
+static int Run(void);
+
+/*
+  The PBadParam argument can be used to point to in integer which will receive the
+  index of the parameter which caused the failure, if the function returns a failure.
+  It is meaningless if the function returns paramErrSuccess.  If it is NULL, no further
+  information on the failure but the error code will be available.
+*/
+static enum ParamError ParseArguments(S_OPTIONS* POptions, const S_ARGUMENTS* PArguments, int* PBadParam);
+
+/*
+  DirectoryLoop() is the main directory which does all of the neccersary work, it uses opendir()
+  to loop through all the files in the "new" directory and then open a file for append in the
+  "old" directory of the same name and append the contents of the new file to the old file.
+  This, of course, is the entire point of this program.
+*/
+static void DirectoryLoop(void);
+
+/*
+  CreatePathname() is a generic string concatinator, except that it uses PATH_SEPERATOR
+  to create a division between the directory and the file, creating a path to address a
+  file directly.  The result must be given to DestroyPathname() when it is finished with.
+*/
+static char* CreatePathname(const char* DirectoryName, const char* FileName);
+
+/*
+  DestroyPathname() is used to erase the resources taken by a pointer returned
+  from CreatePathname().
+*/
+static void DestroyPathname(char* Pathname);
+
+/*
+  StripTrailingPathSeperator() is a way to enture that a directory name doesn't
+  look like this "blah/" or "blah\\" and that instead it looks like this:
+  "blah".  Note that we only remove one trailing seperator and the seperator
+  must be PATH_SEPERATOR. The return value is DirectoryEntry, whether the strip
+  found anything or not.
+*/
+static char* StripTrailingPathSeperator(char* DirectoryName);
+
+/*
+  IsSpecialDirEntry() verifies that the DirectoryName is the name for
+  the current directory or the parent directory (special entries) and
+  returns specialFile for any other file.
+*/
+static enum specialEntry IsSpecialDirEntry(const char* DirectoryName);
+
+/*
+  Simple replacement for non-portable strdup()
+*/
+static char* mystrdup(const char* SourceString);
+
+static void Title()
+{
+  unsigned int i;
+  static unsigned int longestLine;
+  static const char* strs[] = {
+    "GAIM Log Merge Utility",
+    "Copyright (c) 2003 Daybo Logic, All Rights Reserved.",
+    "http://www.daybologic.co.uk/"
+  };
+
+  /* Calculate longest line */
+  if ( !longestLine ) {
+    for ( i = 0U; i < sizeof(strs)/sizeof(strs[0]); i++ ) {
+      unsigned int len = strlen(strs[i]);
+      if ( len > longestLine ) longestLine = len;
+    }
+  }
+
+  /* Print banner */
+  for ( i = 0U; i < sizeof(strs)/sizeof(strs[0]); i++ )
+    printf("%s\n", strs[i]);
+
+  /* Draw line under it */
+  for ( i = 0U; i < longestLine; i++ )
+    putchar('-');
+  putchar('\n');
+}
+
+static void Help(const char* ProgramName)
+{
+  printf("%s [options] [logdir] [later_logdir]\n\n"
+         "\t[options]:\n"
+         "\t\t-q (--quiet): No stdout output\n"
+         "\t\t-nse (--no-std-error): stderr messages redirected to stdout\n"
+         "\t\t-v (--verbose): Verbose output (equivillant to -d 1)\n"
+         "\t\t-d # (--debug=#): Debug level %d-%d, 0 is default\n"
+         "\t\t-V (--version): Print detailed version information\n\n"
+         "\tlogdir - gaim's primary log directory\n"
+         "\tlater_logdir - new logs which need to be added\n",
+         (ProgramName) ? (ProgramName) : ("logmerge"),
+         DEBUG_LEVEL_MIN,
+         DEBUG_LEVEL_MAX
+  );
+}
+
+static void Version()
+{
+  printf(
+         "Version: %u.%u-%s\n"
+         "Built: %s - %s\n\n",
+          VERSION_MAJOR,
+          VERSION_MINOR,
+          VERSION_RELEASE,
+          __DATE__,
+          __TIME__
+  );
+}
+
+int Run()
+{
+  int badArg;
+  enum ParamError paramErr = ParseArguments(&Options, &Arguments, &badArg);
+  if ( paramErr != paramErrSuccess ) {
+    const char* opt = Arguments.GetArg(&Arguments, badArg);
+    assert(opt); /* Detect bugs in Arguments class */
+    switch ( paramErr ) {
+      case paramErrUnknownOpt : {
+        printf("Unknown option - \"%s\"\n", opt);
+        break;
+      }
+      case paramErrMoreInfo : {
+        printf("Parameter \"%s\" needs more information.\n", opt);
+        break;
+      }
+      case paramErrRange : {
+        printf("Value %u (parameter %u) is out of range.\n", atoi(opt), badArg);
+        break;
+      }
+      case paramErrTooMany : {
+        printf("Too many parameters - \"%s\"\n", opt);
+        break;
+      }
+      default : {
+        printf("%s : Unknown internal error.  Contact the vendor of this program.\n", opt);
+      }
+    }
+    return EXIT_FAILURE;
+  }
+
+  if ( (Options.flags & OPTION_FLAGS_QUIET) != OPTION_FLAGS_QUIET ) /* Not in quiet mode */
+    Title(); /* Print title and copyright info */
+
+  switch ( Options.operatingMode ) {
+    case oModeHelp : {
+      Help(Arguments.GetArg(&Arguments, 0)); /* Call syntax help */
+      break;
+    }
+    case oModeVersion : {
+      Version();
+      break;
+    }
+    case oModeNormal : {
+      if ( !Options.oldLogsDir ) {
+        printf("Old logs directory not specified\n");
+        break;
+      }
+      else if ( !Options.newLogsDir ) {
+        printf("New logs directory not specified\n");
+        break;
+      }
+      DirectoryLoop(); /* Loop through the new directory */
+      break;
+    }
+    default : {
+      puts("INTERNAL ERROR: Unknown basic operating mode.\n");
+      abort();
+    }
+  }
+
+  puts("Program has ended normally");
+  return EXIT_SUCCESS;
+}
+
+static enum ParamError ParseArguments(S_OPTIONS* POptions, const S_ARGUMENTS* PArguments, int* PBadParam)
+{
+  int argi = 0;
+  const char* arg;
+  int* expectSubArg = NULL; /* Argument to a parameter */
+  int minlimit;
+  int maxlimit;
+  bool useminlimit = false;
+  bool usemaxlimit = false;
+  int debugLevelExplicitAt = -1;
+  bool expectOptions = true;
+  assert(POptions && PArguments);
+
+  memset(POptions, 0, sizeof(S_OPTIONS));
+  POptions->debugLevel = -1; /* For detection of bad levels, changed to 0 at the end */
+
+  do {
+    arg = PArguments->GetArg(PArguments, argi);
+    if ( arg && argi ) { /* Ensure arg is set, skip argument zero */
+      if ( expectOptions && arg[0] == '-' ) { /* Allow options and looks like an option? */
+        if ( expectSubArg ) {
+          *expectSubArg = atoi(arg);
+          if ( useminlimit ) {
+            if ( *expectSubArg < minlimit ) {
+              if ( PBadParam ) *PBadParam = argi;
+              return paramErrRange;
+            }
+          }
+          if ( usemaxlimit ) {
+            if ( *expectSubArg > maxlimit ) {
+              if ( PBadParam ) *PBadParam = argi;
+              return paramErrRange;
+            }
+          }
+          expectSubArg = NULL; /* Go back to normal args */
+          useminlimit = false;
+          usemaxlimit = false;
+        }
+        else { /* Normal argument */
+          if ( strcmp(arg, "-q") == 0 || strcmp(arg, "--quiet") == 0 )
+            POptions->flags |= OPTION_FLAGS_QUIET;
+          else if ( strcmp(arg, "-nse") == 0 || strcmp(arg, "--no-std-error") == 0 )
+            POptions->flags |= OPTION_FLAGS_STDERR_REDIRECT;
+          else if ( strcmp(arg, "-v") == 0 || strcmp(arg, "--verbose") == 0 )
+            POptions->debugLevel = 1;
+          else if ( strcmp(arg, "-d") == 0 ) { /* Short version of debug parameter */
+            expectSubArg = &Options.debugLevel;
+            minlimit = DEBUG_LEVEL_MIN;
+            maxlimit = DEBUG_LEVEL_MAX;
+            useminlimit = usemaxlimit = true;
+            debugLevelExplicitAt = argi;
+          }
+          else if ( strncmp(arg, "--debug", 7) == 0 ) {
+            debugLevelExplicitAt = argi; /* Technically superfluous but helps keep consistency */
+            arg += 7;
+            if ( *arg != '\0' ) arg++;
+            else {
+              if ( PBadParam ) *PBadParam = argi;
+              return paramErrMoreInfo; /* The option expects more info */
+            }
+            POptions->debugLevel = atoi(arg);
+            if ( POptions->debugLevel < DEBUG_LEVEL_MIN || POptions->debugLevel > DEBUG_LEVEL_MAX ) {
+              if ( PBadParam ) *PBadParam = argi;
+              return paramErrRange;
+            }
+          }
+          else if ( strcmp(arg, "-V") == 0 || strcmp(arg, "--version") == 0 )
+            POptions->operatingMode = oModeVersion;
+          else if ( strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0 )
+            POptions->operatingMode = oModeHelp;
+          else if ( strcmp(arg, "--") == 0 )
+            expectOptions = false; /* Don't accept further options */
+          else {
+            if ( PBadParam ) *PBadParam = argi;
+            return paramErrUnknownOpt; /* Unknown option */
+          }
+        }
+      }
+      else { /* Filename not option */
+        /* Handle the filename (directory name) by assigning our two slots for this.  If they get
+        exceeded, there are too many arguments */
+        if ( !POptions->oldLogsDir )
+          POptions->oldLogsDir = arg;
+        else if ( !POptions->newLogsDir )
+          POptions->newLogsDir = arg;
+        else {
+          if ( PBadParam ) *PBadParam = argi;
+          return paramErrTooMany; /* Too many parameters */
+        }
+      }
+    }
+    argi++; /* Next argument */
+  } while ( arg );
+
+  if ( POptions->debugLevel == -1 ) { /* Debug level not set */
+    if ( debugLevelExplicitAt != -1 ) { /* Debug level explictly set!, they obviously didn't specify a level... */
+      if ( PBadParam ) *PBadParam = debugLevelExplicitAt; /* Set error info */
+      return paramErrMoreInfo;
+    }
+    POptions->debugLevel = 0; /* Restore sane debug level */
+  }
+  return paramErrSuccess;
+}
+
+int main(const int argc, const char* argv[])
+{
+  int ret;
+
+  memset(&Arguments, 0, sizeof(Arguments));
+  Arguments.size = sizeof(Arguments);
+  S_Arguments_Construct(&Arguments);
+  if ( Arguments.SetArgs(&Arguments, argc, argv) ) {
+    ret = Run();
+  }
+  else {
+    ret = EXIT_FAILURE;
+    fprintf(stderr, "Error: Cannot store command line arguments\n");
+  }
+  S_Arguments_Destruct(&Arguments);
+  return ret;
+}
+
+static void DirectoryLoop()
+{
+  DIR *newDir = NULL;
+  struct dirent* entry = NULL;
+  unsigned long int lineNumber = 0UL;
+  FILE* oldFile = NULL; /* Handle for the file to append to */
+  FILE* newFile = NULL; /* Handle for the file to read from and append to the old file */
+  char* oldPathName = NULL; /* Pathname of the old file to append to */
+  char* newPathName = NULL; /* Pathname of the new file to read from */
+  char* fgets_result; /* Return value during the inner loop */
+
+  newDir = OpenDirectory(Options.newLogsDir);
+
+  if ( !newDir ) {
+    perror(Options.newLogsDir); /* Produces: "blah/ : No such file or directory" or similar */
+    goto endloop;
+  }
+
+  do {
+    char lineBuff[LINE_BUFFER_MAX+1];
+
+    entry = readdir(newDir);
+    if ( entry ) {
+      if ( IsSpecialDirEntry(entry->d_name) != specialFile )
+	continue; /* Skip . and .. entries */
+
+      oldPathName = CreatePathname(Options.oldLogsDir, entry->d_name); /* Create a path to the old file */
+      newPathName = CreatePathname(Options.newLogsDir, entry->d_name); /* Create a path to the new file */
+      if ( !oldPathName || !newPathName ) {
+	printf("Unable to allocated pathnames for \"%s\", pathname too long?\nGiving up!\n\n", (!oldPathName) ? (Options.oldLogsDir) : (Options.newLogsDir));
+	goto endloop;
+      }
+
+      /* Open both files so we can copy between them, we need different modes of course */
+      oldFile = fopen(oldPathName, "at"); /* Append text mode */
+      newFile = fopen(newPathName, "rt"); /* Read-only text mode */
+
+      if ( !oldFile || !newFile ) {
+	if ( !oldFile )
+	  perror(oldPathName);
+	else
+	  perror(newPathName);
+
+	goto endloop;
+      }
+
+      /* Now to the real work! */
+      do {
+	memset(lineBuff, 0, sizeof(lineBuff));
+	fgets_result = fgets(lineBuff, sizeof(lineBuff)/sizeof(lineBuff[0]), newFile);
+	if ( fgets_result ) {
+          lineNumber++;
+          if ( lineBuff[LINE_BUFFER_MAX-1] != '\n' && lineBuff[LINE_BUFFER_MAX-1] != '\0' ) {
+            printf(
+              "Warning: Line %lu in file \"%s\", line too long; truncated.  Limit is %u characters per line.\n",
+              lineNumber,
+              newPathName,
+              LINE_BUFFER_MAX
+            );
+            lineBuff[LINE_BUFFER_MAX-1] = '\n';
+          }
+          fprintf(oldFile, "%s", lineBuff);
+        }
+      } while (fgets_result);
+      lineNumber = 0UL;
+    } /* fi entry */
+  } while ( entry );
+
+endloop:
+  /* Cleanup operations */
+  if ( oldPathName ) DestroyPathname(oldPathName);
+  if ( newPathName ) DestroyPathname(newPathName);
+  if ( newDir ) closedir(newDir);
+  if ( oldFile ) fclose(oldFile);
+  if ( newFile ) fclose(newFile);
+  return;
+}
+
+static char* CreatePathname(const char* DirectoryName, const char* FileName)
+{
+  unsigned int len = 0U;
+  char* pathname = NULL;
+
+  if ( DirectoryName ) {
+    if ( DirectoryName[0] ) {
+      len = strlen(DirectoryName);
+      len += strlen(PATH_SEPERATOR);
+    }
+  }
+  if ( FileName ) {
+    if ( FileName[0] )
+      len += strlen(FileName);
+  }
+
+  if ( len ) {
+    pathname = (char*)malloc((len+1)*sizeof(char));
+    if ( pathname ) {
+      pathname[0] = '\0';
+      if ( DirectoryName ) {
+        if ( DirectoryName[0] ) {
+          strcpy(pathname, DirectoryName);
+          StripTrailingPathSeperator(pathname);
+          strcat(pathname, PATH_SEPERATOR);
+        }
+      }
+      if ( FileName ) {
+        if ( FileName[0] ) {
+          strcat(pathname, FileName);
+        }
+      }
+    }
+  }
+
+  return pathname;
+}
+
+static void DestroyPathname(char* Pathname)
+{
+  if ( Pathname ) free(Pathname);
+}
+
+static char* StripTrailingPathSeperator(char* DirectoryName)
+{
+  if ( DirectoryName ) {
+    if ( DirectoryName[0] ) {
+      unsigned int len = strlen(DirectoryName);
+      unsigned int sepLen = strlen(PATH_SEPERATOR);
+      if ( len >= sepLen ) {
+        if ( strncmp(&DirectoryName[len-sepLen], PATH_SEPERATOR, sepLen) == 0 ) {
+          unsigned int i;
+          for ( i = 1U; i <= sepLen; i++ )
+            DirectoryName[len-i] = '\0';
+        }
+      }
+    }
+  }
+  return DirectoryName;
+}
+
+static enum specialEntry IsSpecialDirEntry(const char* DirectoryName)
+{
+  enum specialEntry entry;
+
+  char* cpdn = mystrdup(DirectoryName);
+  if ( !cpdn ) return specialError;
+  StripTrailingPathSeperator(cpdn);
+  if ( strcmp(cpdn, ".") == 0 )
+    entry = specialCurrent;
+  else if ( strcmp(cpdn, "..") == 0 )
+    entry = specialParent;
+  else
+    entry = specialFile;
+
+  free(cpdn);
+  return entry;
+}
+
+static char* mystrdup(const char* SourceString)
+{
+  char* ptr = NULL;
+
+  if ( SourceString ) {
+    ptr = (char*)malloc((strlen(SourceString)+1)*sizeof(char));
+    if ( ptr )
+      strcpy(ptr, SourceString);
+  }
+  return ptr;
+}
+#ifndef __INC_LOGHERGE_H
+#define __INC_LOGMERGE_H
+
+#if defined(__UNIX__)
+# include <dirent.h> /* This is the right header on POSIX systems */
+  typedef struct dirent DIRECTORY_ENTRY;
+# define OpenDirectory(d) opendir((d))
+#elif defined(__WATCOMC__)
+# include <direct.h> /* Watcom specific header */
+  typedef struct dirent DIRECTORY_ENTRY;
+# define OpenDirectory(d) opendir((d))
+#elif (defined(__BORLANDC__) || defined(__TURBOC__))
+# include <dirent.h>
+  typedef DIR DIRECTORY_ENTRY;
+# define OpenDirectory(d) opendir((char*)(d))
+#else
+# error ("No opendir() equivillants programmed for this system, contact DDRP.")
+#endif
+
+typedef enum {
+  false = 0,
+  true = 1
+} bool;
+
+#define false false
+#define true true
+
+#define DEBUG_LEVEL_MIN (0)
+#define DEBUG_LEVEL_MAX (10)
+
+#define VERSION_MAJOR (0)
+#define VERSION_MINOR (2)
+#define VERSION_RELEASE "20030812"
+
+#define LINE_BUFFER_MAX (4096)
+
+#ifdef __UNIX__
+# define PATH_SEPERATOR "/"
+#else
+# define PATH_SEPERATOR "\\"
+#endif /*__UNIX__*/
+
+struct _S_ARGUMENTS {
+  unsigned short int size; /* Careful with this, it must be properly set before any function is called, even the constructor */
+
+  /*private:*/
+  int i_argc;
+  char** i_argv;
+
+  /*public:*/
+  bool (*SetArgs)(struct _S_ARGUMENTS* PObj, const int ArgC, const char** ArgV);
+  const char* (*GetArg)(const struct _S_ARGUMENTS* PObj, unsigned int ArgI); /* Please do not modify string pointed to by returned pointer */
+};
+typedef struct _S_ARGUMENTS S_ARGUMENTS;
+
+void S_Arguments_Construct(struct _S_ARGUMENTS* PArguments);
+void S_Arguments_Destruct(struct _S_ARGUMENTS* PArguments);
+
+/* Option bits */
+#define OPTION_FLAGS_QUIET (1) /* No stdout output */
+#define OPTION_FLAGS_STDERR_REDIRECT (2) /* Redirect stderr output to stdout */
+
+enum OperatingMode {
+  oModeNormal, /* Normal operating mode */
+  oModeHelp, /* Display help information */
+  oModeVersion /* Display detailed version information */
+};
+
+enum ParamError {
+  paramErrSuccess, /* No faults with command line parameters */
+  paramErrUnknownOpt, /* Unknown option */
+  paramErrMoreInfo, /* The option expects more information */
+  paramErrRange, /* Parameter out of range */
+  paramErrTooMany /* Too many parameters */
+};
+
+typedef struct _S_OPTIONS {
+  unsigned short int flags; /* From the above */
+  int debugLevel; /* 1 for verbose, maximum 10, minimum 0, default 0 */
+  enum OperatingMode operatingMode; /* What mode we're in */
+  const char* oldLogsDir; /* Primary copy of the logs */
+  const char* newLogsDir; /* Logs which need to be added to the old logs for a merge */
+} S_OPTIONS;
+
+/* For use with IsSpecialDirEntry() */
+enum specialEntry {
+  specialError,     /* Cannot determine */
+  specialFile,      /* An ordinary file */
+  specialCurrent,   /* Current directory entry */
+  specialParent     /* Parent directory entry */
+};
+
+#endif /*!__INC_LOGMERGE_H*/

File logmerge.lk1

+FIL args.obj,logmerge.obj
+
+project : w:\logmerge\logmerge.exe .SYMBOLIC
+
+!include w:\logmerge\logmerge.mk1

File logmerge.mk1

+!define BLANK ""
+w:\logmerge\args.obj : w:\logmerge\args.c .AUTODEPEND
+ @w:
+ cd w:\logmerge
+ *wcc386 args.c -i=C:\watcom\h;C:\watcom\h\nt -w4 -e25 -zq -od -d2 -5r -bt=n&
+t -mf
+
+w:\logmerge\logmerge.obj : w:\logmerge\logmerge.c .AUTODEPEND
+ @w:
+ cd w:\logmerge
+ *wcc386 logmerge.c -i=C:\watcom\h;C:\watcom\h\nt -w4 -e25 -zq -od -d2 -5r -&
+bt=nt -mf
+
+w:\logmerge\logmerge.exe : w:\logmerge\args.obj w:\logmerge\logmerge.obj .AU&
+TODEPEND
+ @w:
+ cd w:\logmerge
+ @%write logmerge.lk1 FIL args.obj,logmerge.obj
+ @%append logmerge.lk1 
+!ifneq BLANK ""
+ *wlib -q -n -b logmerge.imp 
+ @%append logmerge.lk1 LIBR logmerge.imp
+!endif
+!ifneq BLANK ""
+ @%append logmerge.lk1 op resource=
+!endif
+ *wlink name logmerge d all op inc SYS nt op m op maxe=25 op q op symf @logm&
+erge.lk1
+

File logmerge.tgt

+40
+targetIdent
+0
+MProject
+1
+MComponent
+0
+2
+WString
+4
+NEXE
+3
+WString
+5
+nc2en
+1
+0
+1
+4
+MCommand
+0
+5
+MCommand
+0
+6
+MItem
+12
+logmerge.exe
+7
+WString
+4
+NEXE
+8
+WVList
+0
+9
+WVList
+1
+10
+ActionStates
+11
+WString
+4
+&Run
+12
+WVList
+1
+13
+MVState
+14
+WString
+3
+RUN
+15
+WString
+28
+?????Application parameters:
+1
+16
+WString
+2
+-h
+0
+-1
+1
+1
+0
+17
+WPickList
+3
+18
+MItem
+3
+*.c
+19
+WString
+4
+COBJ
+20
+WVList
+0
+21
+WVList
+0
+-1
+1
+1
+0
+22
+MItem
+6
+args.c
+23
+WString
+4
+COBJ
+24
+WVList
+0
+25
+WVList
+0
+18
+1
+1
+0
+26
+MItem
+10
+logmerge.c
+27
+WString
+4
+COBJ
+28
+WVList
+0
+29
+WVList
+0
+18
+1
+1
+0

File logmerge.wpj

+40
+projectIdent
+0
+VpeMain
+1
+WRect
+0
+0
+7680
+9200
+2
+MProject
+3
+MCommand
+0
+4
+MCommand
+0
+1
+5
+WFileName
+12
+logmerge.tgt
+6
+WVList
+1
+7
+VComponent
+8
+WRect
+30
+53
+5700
+4280
+0
+0
+9
+WFileName
+12
+logmerge.tgt
+0
+2
+7
+ERASE=rm -f
+CF=-g -D__UNIX__ -ansi -pedantic -Wall
+MASDEP=Makefile logmerge.h
+O=.o
+C=.c
+EXE=
+
+OBJS=logmerge$(O) args$(O)
+
+
+logmerge$(EXE) : $(OBJS) $(MASDEP)
+	$(CC) $(CF) -o logmerge$(EXE) $(OBJS)
+
+logmerge$(O) : logmerge$(C) $(MASDEP)
+	$(CC) $(CF) -c -o logmerge$(O) logmerge$(C)
+
+args$(O) : args$(C) $(MASDEP)
+	$(CC) $(CF) -c -o args$(O) args$(C)
+
+
+clean:
+	$(ERASE) args$(O) logmerge$(O) logmerge$(EXE)
+#!/bin/sh
+make && ./logmerge