home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 2 BBS / 02-BBS.zip / usagep24.lzh / USAGE.C < prev    next >
Text File  |  1993-02-04  |  39KB  |  1,153 lines

  1. /*
  2.  
  3. USAGE -- Reads one or more Maximus or Binkley logs and generates a usage graph
  4.  
  5. Version 2.4  (02/04/93)
  6.  
  7. Written by Bob Quinlan of Austin, Texas, USA
  8. Sysop of Red October at 512-834-2593 (1:382/111)
  9. Special thanks to Steve Antonoff for suggestions and code.
  10.  
  11. Copyright 1993 by Bob Quinlan
  12.  
  13. Compatible with Maximus 2.00 and 2.01 and BinkleyTerm 2.55 and 2.56
  14.  
  15.  
  16. This program reads one or more Maximus or Binkley type logs and
  17. generates a BBS-format usage graph based on the data.  By default it
  18. will read MAX.LOG in the current directory and write USAGE.BBS.  The
  19. following switches allow you to configure it for your system and
  20. preferences:
  21.  
  22.     /Cx=y       Set color number 'x' to Avatar color 'y'.  'x' values
  23.                 mean the following:
  24.                     0 = default color
  25.                     1 = title
  26.                     2 = frame
  27.                     3 = reference text (%)
  28.                     4 = reference lines (%)
  29.                     5 = hour text
  30.                     6 = hour dots (odd hour values)
  31.                     7 = hour lines
  32.                     8 = data bars
  33.                     9 = enter prompt
  34.                 Avatar color codes are given in two-digit hexadecimal.
  35.                 The first digit sets the background color (0-7 only) and
  36.                 the second digit sets the foreground color (0-F).  Each
  37.                 digit corresponds to the following colors:
  38.                     0 = black
  39.                     1 = blue
  40.                     2 = green
  41.                     3 = cyan
  42.                     4 = red
  43.                     5 = magenta
  44.                     6 = brown
  45.                     7 = white
  46.                     8 = grey
  47.                     9 = bright blue
  48.                     A = bright green
  49.                     B = bright cyan
  50.                     C = bright red
  51.                     D = bright magenta
  52.                     E = yellow
  53.                     F = bright white
  54.                 Example: To set the data bars to yellow on blue you
  55.                 would use the switch /C8=1E.
  56.  
  57.     /Ddays      Days of history to use.  Any log events or history file
  58.                 entries older than this many days will be ignored.
  59.  
  60.     /Hfile      History file to process.  This eliminates the need to
  61.                 rescan old log data.  Every time USAGE runs a new record
  62.                 is added to the history file for future reference.
  63.                 Records older than the /D parameter specifies will be
  64.                 removed from the history file automatically.
  65.  
  66.                 This information is stored in USAGE.HST by default.  You
  67.                 can specify another name using the /H parameter.  Using
  68.                 /H by itself will prevent history file processing.
  69.  
  70.     /Ifile      Incremental file to process.  This file keeps track of
  71.                 where and in what state the previous log ended.  If the
  72.                 file has been restarted since the last run that will be
  73.                 detected and the location pointer reset to the beginning
  74.                 of the new file.
  75.  
  76.                 Incremental processing means that your maintenance times
  77.                 will be included in the graph as "in use" periods.
  78.                 Without this feature they would never register because
  79.                 the processing would also end with an unresolved active
  80.                 event.
  81.  
  82.                 This information is stored in USAGE.INC by default.  You
  83.                 can specify another name using the /I parameter.  Using
  84.                 /I by itself will prevent incremental file processing.
  85.  
  86.     /Lfile      Log file to process.  Note that the /L parameter can be
  87.                 used more than once to specify several logs.  This
  88.                 feature is particularly useful if you have multiple
  89.                 lines with separate logs.
  90.  
  91.     /M          Military time.  Display the hours as 0-23.
  92.  
  93.     /Sfile      Search file to process.  You can override the default
  94.                 search strings that are used to recognize system
  95.                 activity in the log by specifying a search file.
  96.  
  97.                 A search file is just a text file containing one string
  98.                 per line.  Search strings must begin with the log field
  99.                 immediately following the date.  Add a + as the first
  100.                 character of each line that indicates an activity is
  101.                 starting.  Add a - as the first character of each line
  102.                 that indicates an activity is ending.
  103.  
  104.                 By default USAGE uses a set of search strings that
  105.                 should work with both BinkleyTerm and Maximus.  BINK.S
  106.                 and MAX.S are included both as samples to help you
  107.                 construct your own search files.  Other .S files may
  108.                 also be included.
  109.  
  110.     /Ttitle     Title for graph.  You may want to supply multiple word
  111.                 values for title.  You can separate the words by spaces
  112.                 or by underscores (which will be replaced by spaces).
  113.                 For example, both of these lines would produce the same
  114.                 results:
  115.                     usage /lmax.log /tRed October Usage
  116.                     usage /lmax.log /tRed_October_Usage
  117.  
  118.     /Ufile      The file to which the usage graph will be written.  The
  119.                 .BBS extension is not automatically added.
  120.  
  121.     /V          Verbose diagnostic mode.  Prints out each log entry that
  122.                 triggers a starting or ending time.  Prints the elapsed
  123.                 time between each start/end pair.  Not recommended for
  124.                 normal use.
  125.  
  126.     /Vcount     Volume.  If verbose mode is not turned on a dot is
  127.                 printed every time a new usage entry is found.  Setting
  128.                 'count' to zero turns off the dots.  Setting 'count' to
  129.                 any other number divides down the number of dots by a
  130.                 factor of 'count'.  (For example: /V3 produces one dot
  131.                 for every three usage entries.)
  132.  
  133. If you use WFC within Maximus pass USAGE the Maximus log.  If you use
  134. Binkley to answer the phone pass it the Binkley log.  You can mix log
  135. types if you have lines with different configurations.
  136.  
  137. As an example, lets say a system has two lines.  One runs Binkley and
  138. the runs Maximus with WFC.  A new log is started every month.  Here is
  139. how to generate an overall usage graph:
  140.  
  141. usage /lbink01.log /lmax02.log /tOverall Usage
  142.  
  143. I also want to generate usage graphs for each line separately.  I want to
  144. reprocess the same logs for this purpose and I want to keep the history
  145. data separate, so I specify different history and incremental files for
  146. each:
  147.  
  148. usage /husage01.hst /iusage01.inc /lbink01.log /uusage01.bbs /tLine One
  149. usage /husage02.hst /iusage02.inc /lmax02.log /uusage02.bbs /tLine Two
  150.  
  151.  
  152. USAGE returns ERRORLEVEL 0 after a successful run.  ERRORLEVEL 1 is
  153. returned to indicate an error.
  154.  
  155. NOTICE:  You may use, copy, and distribute this program freely as long
  156. as you insure that both the executable and the documentation (.DOC)
  157. files are included in the distribution package.  The source code does
  158. not need to be included.  You may modify this program and document, so
  159. long as reasonable credit is given to the original author if a
  160. substantial portion of the original remains intact.  The author is not
  161. responsible for any losses which may occur either directly or indirectly
  162. as a result of using this program.
  163.  
  164. HISTORY:
  165. Version 2.4  (02/04/93) -- Added three new default Binkley search
  166.                            strings thanks to Walter Anderson.
  167. Version 2.3  (11/10/92) -- Modified the /I file format (again!) so that
  168.                            file contents are checked against stored
  169.                            positions.  This eliminates the need to worry
  170.                            about new files being shorter than old ones.
  171.                            The default is now /IUSAGE.INC.  /I with no
  172.                            parameter will turn off this feature.  /H now
  173.                            defaults to /HUSAGE.HST.  /H with no
  174.                            parameter will turn history off.
  175. Version 2.2  (11/07/92) -- Modified /I file format so that active usage
  176.                            can be tracked across log boundaries.  This
  177.                            should keep your maintenance period from
  178.                            showing up as a blank spot on the graph.  A
  179.                            CVTINC utility is included to convert 2.1
  180.                            format incremental files to the 2.2 format.
  181. Version 2.1  (11/04/92) -- Added the /I parameter to allow incremental
  182.                            processing of log files.  Added a new /V
  183.                            switch option (volume) to limit screen
  184.                            output.
  185. Version 2.0  (11/03/92) -- Added the /H parameter to specify a
  186.                            self-maintaining history file.  Added the /S
  187.                            parameter to specify a file of user-defined
  188.                            search strings.  Added the /D parameter to
  189.                            specify how many days to include in the
  190.                            graph.  Improved the speed of elapsed time
  191.                            calculations.  Track event times in seconds
  192.                            instead of minutes.  Track total time by
  193.                            seconds intsead of days.  Cleaned up a lot of
  194.                            messy code.  Many of the improvements were
  195.                            suggested and originally coded by Steve
  196.                            Antonoff.
  197. Verison 1.9  (10/28/92) -- Skipped.
  198. Version 1.8   (8/28/92) -- Added the /M switch for military time.
  199. Version 1.7   (8/25/92) -- Added the ability to pass multiple-word
  200.                            parameters using spaces.
  201. Version 1.6   (6/03/92) -- Adjusted graph to start at midnight.
  202. Version 1.5   (5/29/92) -- Improved Binkley support.  Added verbose
  203.                            diagnostic mode.
  204. Version 1.4   (5/27/92) -- Allow custom color selections.
  205. Version 1.3   (5/26/92) -- Removed event and keyboard switches.
  206.                            Added support for Binkley logs.
  207.                            Added log file sharing (again).
  208. Version 1.2   (5/14/92) -- Removed log file sharing due to compiler bug.
  209. Version 1.1   (5/13/92) -- Added log file sharing.
  210. Version 1.0   (4/22/92) -- Original release.  Written in Borland C.
  211.  
  212. Large memory model
  213. */
  214.  
  215.  
  216. #include <ctype.h>
  217. #include <dos.h>
  218. #include <errno.h>
  219. #include <fcntl.h>
  220. #include <io.h>
  221. #include <share.h>
  222. #include <stdio.h>
  223. #include <stdlib.h>
  224. #include <string.h>
  225. #include <sys\stat.h>
  226. #include <sys\types.h>
  227. #include <time.h>
  228.  
  229.  
  230. #define VERSION     "2.4"
  231. #define DATE        "02/04/93"
  232.  
  233. #define MAXLINE     (512)
  234. #define MAXPATH     (128)
  235. #define MAXTITLE    (72)
  236. #define MAXSTAMP    (16)
  237. #define MAXDATE     (6)
  238. #define MAXLOGS     (16)
  239. #define NUM_COLORS  (10)
  240.  
  241. #define TOPROW       0
  242. #define BOTTOMROW    22
  243. #define LEFTCOL      4
  244. #define RIGHTCOL     77
  245. #define FULLHEIGHT   219
  246. #define HALFHEIGHT   220
  247.  
  248. #define HORIZLINE 196
  249. #define VERTLINE 179
  250. #define TOPLEFT 201
  251. #define TOPRIGHT 187
  252. #define BOTTOMLEFT 200
  253. #define BOTTOMRIGHT 188
  254. #define DOUBLEHORIZ 205
  255. #define DOUBLEVERT 186
  256. #define DOT 249
  257.  
  258. #ifndef FALSE
  259.     #define FALSE 0
  260.     #define TRUE 1
  261. #endif
  262.  
  263. #define DAYSECS     (86400L)
  264. #define COLS        (72)
  265. #define CDIV        (DAYSECS/COLS)
  266.  
  267.  
  268. typedef struct _strings
  269. {
  270.     char    *string;
  271.     int     length;
  272.     struct _strings *next;
  273. } strings;
  274.  
  275.  
  276. typedef struct _histories
  277. {
  278.     long    firsts;
  279.     long    lasts;
  280.     long    uses[COLS];
  281.     long    totals[COLS];
  282. } histories;
  283.  
  284. typedef struct _increments
  285. {
  286.     long    lastpos;
  287.     char    last[MAXSTAMP];
  288.     int     online;
  289.     char    start[MAXSTAMP];
  290.     int     slen;
  291. } increments;
  292.  
  293.  
  294. enum
  295. {
  296.     default_color=0,
  297.     title_color,
  298.     frame_color,
  299.     reference_text_color,
  300.     reference_lines_color,
  301.     hour_text_color,
  302.     hour_dots_color,
  303.     hour_lines_color,
  304.     data_bars_color,
  305.     enter_prompt_color
  306. };
  307.  
  308.  
  309. void append_strings(strings **link, char *string);
  310. void stamps_to_columns(long ages, char *start, char  *end, int year,
  311.     long *starts, long *ends, long *columns);
  312. long stamp_to_secs(char *stamp, int year);
  313. long date_to_secs(struct date *datep, struct time *timep);
  314. int draw_graph(histories *h, char colors[], int military, char *usefile,
  315.     char *title);
  316.  
  317.  
  318. char *search_strings[] =
  319. {
  320.     "+BINK Ring",
  321.     "-BINK No Carrier",
  322.     "+BINK Processing node",
  323.     "-BINK End of WaZOO",
  324.     "-BINK End of FTS-0001 compatible session",
  325.     "-BINK End of connection attempt",
  326.     "+BINK Exit after receiving mail with errorlevel",
  327.     "+BINK Exit after compressed mail with errorlevel",
  328.     "+BINK Exit at start of event with errorlevel",
  329.     "+BINK Function key exit - errorlevel",
  330.     "+BINK Exit requested from keyboard",
  331.     "-BINK begin,",
  332.     "+MAX  Event - Exiting with errorlevel",
  333.     "+MAX  Exit by keyboard request",
  334.     "+MAX  Connected at",
  335.     "-MAX  Begin,",
  336.     ""
  337. };
  338.  
  339.  
  340. int main(int argc, char *argv[])
  341. {
  342.     char   histfile[MAXPATH] = {"USAGE.HST"};
  343.     char   incfile[MAXPATH] = {"USAGE.INC"};
  344.     char   *logfile[MAXLOGS];
  345.     char   searchfile[MAXPATH] = {""};
  346.     char   title[MAXTITLE] = {""};
  347.     char   usefile[MAXPATH] = {"USAGE.BBS"};
  348.     int    military = FALSE;
  349.     int    volume = 1;
  350.     int    vloop;
  351.     char   colors[NUM_COLORS] =
  352.                                 {
  353.                                     0x07,  /*  0: default color  */
  354.                                     0x0f,  /*  1: title  */
  355.                                     0x09,  /*  2: frame  */
  356.                                     0x0a,  /*  3: reference text  */
  357.                                     0x02,  /*  4: reference lines  */
  358.                                     0x0b,  /*  5: hour text  */
  359.                                     0x09,  /*  6: hour dots  */
  360.                                     0x03,  /*  7: hour lines  */
  361.                                     0x09,  /*  8: data bars  */
  362.                                     0x02   /*  9: enter prompt  */
  363.                                 };
  364.  
  365.  
  366.     int    comment_offset = 18;
  367.  
  368.     strings *start_strings = NULL;
  369.     strings *end_strings = NULL;
  370.     strings *str;
  371.  
  372.     FILE   *logfile_fp;
  373.     FILE   *searchfile_fp;
  374.     int    histfile_fh;
  375.     int    incfile_fh = -1L;
  376.  
  377.     long   incpos;
  378.     int    found;
  379.  
  380.     int    logs = 0;
  381.     int    curlog = 0;
  382.     long   logpos;
  383.  
  384.     char   *comment;
  385.  
  386.     histories h;
  387.     histories h_new;
  388.     histories h_old;
  389.  
  390.     increments increment;
  391.  
  392.     int    days;
  393.     long   ages = 0L;
  394.     long   read_record = 0L;
  395.     long   write_record = 0L;
  396.  
  397.     struct date datep;
  398.     struct time timep;
  399.     char   first[MAXSTAMP];
  400.     long   starts;
  401.     long   ends;
  402.  
  403.     char   param = '\0';
  404.  
  405.     char   line[MAXLINE];
  406.     char   *ch;
  407.     int    i, j, k;
  408.  
  409.  
  410. /***********/
  411. /*  USAGE  */
  412. /***********/
  413.  
  414.     printf("USAGE %s -- Copyright 1992 by Bob Quinlan (%s)\n\n", VERSION, DATE);
  415.     printf("Special thanks to Steve Antonoff for suggestions and code.\n\n");
  416.  
  417.     /*  Get current date  */
  418.     getdate(&datep);
  419.     gettime(&timep);
  420.  
  421.     /*  Process switches  */
  422.     for (i = 1; i < argc; i++)
  423.     {
  424.         /*  Convert previous param to uppercase so single-pass switches will
  425.             not recognize it more than once  */
  426.         param = toupper(param);
  427.  
  428.         /*  If new parameter set it to lower case for first pass  */
  429.         if (argv[i][0] == '/')
  430.             param = tolower(argv[i][1]);
  431.  
  432.         switch (param)
  433.         {
  434.             case 'c':  /*  color  */
  435.                 if (sscanf(argv[i]+2, "%x=%x", &j, &k) >= 2)
  436.                 {
  437.                     if ((j < NUM_COLORS) && (k <= 0xff))
  438.                     {
  439.                         colors[j] = k;
  440.                     }
  441.                     else
  442.                     {
  443.                         fprintf(stderr, "USAGE: Illegal value in %s\n",
  444.                             argv[i]);
  445.                     }
  446.                 }
  447.                 else
  448.                 {
  449.                     fprintf(stderr, "USAGE: Illegal format in %s\n", argv[i]);
  450.                 }
  451.                 break;
  452.             case 'd':  /*  days  */
  453.                 days = atoi(argv[i]+2);
  454.                 /*  Update ages  */
  455.                 ages = date_to_secs(&datep, &timep)-(days*DAYSECS);
  456.                 break;
  457.             case 'h':  /*  history file  */
  458.                 strncpy(histfile, argv[i]+2, MAXPATH);
  459.                 break;
  460.             case 'i':  /*  incremental log file  */
  461.                 strncpy(incfile, argv[i]+2, MAXPATH);
  462.                 break;
  463.             case 'l':  /*  log file  */
  464.                 if (logs < MAXLOGS)
  465.                 {
  466.                     logfile[logs++] = argv[i]+2;
  467.                 }
  468.                 else
  469.                 {
  470.                     fprintf(stderr, "USAGE: %s exceeds log limit\n", argv[i]+2);
  471.                 }
  472.                 break;
  473.             case 'm':  /*  military time  */
  474.                 military = TRUE;
  475.                 break;
  476.             case 's':  /*  search string file  */
  477.                 strncpy(searchfile, argv[i]+2, MAXPATH);
  478.                 break;
  479.             case 'T':  /*  title: append additional words  */
  480.                 strncat(title, " ", MAXTITLE);
  481.                 /*  Fall through to next case!  */
  482.             case 't':  /*  title  */
  483.                 strncat(title, argv[i]+((islower(param) != 0)*2), MAXTITLE);
  484.                 while ((ch = strchr(title, '_')) != NULL)
  485.                 {
  486.                     *ch = ' ';
  487.                 }
  488.                 break;
  489.             case 'u':  /*  use file  */
  490.                 strncpy(usefile, argv[i]+2, MAXPATH);
  491.                 break;
  492.             case 'v':  /*  verbose and volume  */
  493.                 if (argv[i][2] == '\0')
  494.                 {
  495.                     volume = -1;
  496.                 }
  497.                 else
  498.                 {
  499.                     volume = atoi(argv[i]+2);
  500.                     if (volume < 0)
  501.                     {
  502.                         volume = 0;
  503.                     }
  504.                 }
  505.                 break;
  506.             default:
  507.                 fprintf(stderr, "USAGE: Unknown switch: %s\n", argv[i]);
  508.                 break;
  509.         }
  510.     }
  511.  
  512.     /*    Read in search file or defaults  */
  513.     if (searchfile[0] != '\0')
  514.     {
  515.         if ((searchfile_fp = _fsopen(searchfile, "rt", SH_DENYNO)) == NULL)
  516.         {
  517.             fprintf(stderr, "USAGE: Unable to open %s\n", searchfile);
  518.             exit(1);
  519.         }
  520.  
  521.         while (fgets(line, MAXLINE, searchfile_fp) != NULL)
  522.         {
  523.             ch = strpbrk(line, "\r\n");
  524.             *ch = '\0';
  525.             if (line[0] == '+')
  526.             {
  527.                 append_strings(&start_strings, line+1);
  528.             }
  529.             else if (line[0] == '-')
  530.             {
  531.                 append_strings(&end_strings, line+1);
  532.             }
  533.             else
  534.             {
  535.                 fprintf(stderr, "USAGE: Bad search string \"%s\"\n", line);
  536.             }
  537.         }
  538.         fclose(searchfile_fp);
  539.     }
  540.     else
  541.     /*  Supply default search strings  */
  542.     {
  543.         for (i = 0; search_strings[i][0] != '\0'; i++)
  544.         {
  545.             if (search_strings[i][0] == '+')
  546.             {
  547.                 append_strings(&start_strings, search_strings[i]+1);
  548.             }
  549.             else
  550.             {
  551.                 append_strings(&end_strings, search_strings[i]+1);
  552.             }
  553.         }
  554.     }
  555.  
  556.     /*  If not defined supply defaults  */
  557.     if (*title == '\0')
  558.         strcpy(title, "SYSTEM USAGE");
  559.     if (!logs)
  560.        logfile[logs++] = "BINK.LOG";
  561.  
  562.     /*    Initialize history record  */
  563.     memset(&h, 0, sizeof(histories));
  564.  
  565.     /*  Open incremental log file  */
  566.     if (incfile[0])
  567.     {
  568.         if ((incfile_fh = sopen(incfile, O_RDWR | O_CREAT | O_BINARY | O_DENYALL,
  569.             SH_DENYRW, S_IREAD | S_IWRITE)) == -1)
  570.         {
  571.             fprintf(stderr, "USAGE: Unable to open %s\n", incfile);
  572.             exit(1);
  573.         }
  574.     }
  575.  
  576.     /*  Process all log files  */
  577.     while ((logfile_fp = _fsopen(logfile[curlog], "rt", SH_DENYNO)) != NULL)
  578.     {
  579.         printf("Processing %s ", logfile[curlog]);
  580.  
  581.         memset(&increment, 0, sizeof(increments));
  582.         first[0] = '\0';
  583.  
  584.         if (incfile[0])
  585.         {
  586.             found = FALSE;
  587.             lseek(incfile_fh, 0L, SEEK_SET);
  588.             do
  589.             {
  590.                 /*  Store postion for later update  */
  591.                 incpos = tell(incfile_fh);
  592.                 /*  Read in the incremental data  */
  593.                 if (read(incfile_fh, &increment, sizeof(increments)) <
  594.                     sizeof(increments))
  595.                 {
  596.                     break;
  597.                 }
  598.                 if (read(incfile_fh, line, increment.slen) < increment.slen)
  599.                 {
  600.                     break;
  601.                 }
  602.                 /*  Check to see if the log name matches this record  */
  603.                 if (stricmp(logfile[curlog], line) == 0)
  604.                 {
  605.                     /*  Reposition the log file  */
  606.                     if (increment.lastpos >
  607.                         filelength(fileno(logfile_fp)+MAXSTAMP))
  608.                     {
  609.                         fseek(logfile_fp, increment.lastpos, SEEK_SET);
  610.                         fgets(line, MAXLINE, logfile_fp);
  611.                         /*  The override for a null last string is to
  612.                             simplify conversion from older file formats  */
  613.                         if ((increment.last[0]) &&
  614.                             (strncmpi(line+2, increment.last, MAXSTAMP-1) != 0))
  615.                         {
  616.                             fseek(logfile_fp, 0L, SEEK_SET);
  617.                         }
  618.                     }
  619.                     /*  Set the first date back to where we left off  */
  620.                     if (increment.online)
  621.                     {
  622.                         strcpy(first, increment.start);
  623.                     }
  624.                     /*  Keep track of the record match  */
  625.                     found = TRUE;
  626.                 }
  627.             } while (!found);
  628.         }
  629.  
  630.         vloop = -1;
  631.         while (logpos = ftell(logfile_fp), fgets(line, MAXLINE, logfile_fp) !=
  632.             NULL)
  633.         {
  634.             /*  Ignore blank lines  and abnormal lines */
  635.             if ((line[0] != '\r') && (atoi(line+2) > 0) && (line[11] == ':') &&
  636.                 (line[14] == ':'))
  637.             {
  638.                 /*  Keep track of the current datestamp  */
  639.                 strlwr(line);
  640.                 strncpy(increment.last, line+2, MAXSTAMP-1);
  641.                 increment.last[MAXSTAMP-1] = '\0';
  642.                 increment.lastpos = logpos;
  643.                 /*  Store the first datestamp  */
  644.                 if (first[0] == '\0')
  645.                 {
  646.                     strcpy(first, increment.last);
  647.                 }
  648.  
  649.                 /*  Get the comment section of the entry  */
  650.                 comment = line+comment_offset;
  651.  
  652.                 /**********************************************************
  653.                     If already processing usage, look for either an end
  654.                     string OR a start string
  655.                 ************************************************************/
  656.                 if (increment.online)
  657.                 {
  658.                     str = end_strings;
  659.                     while (str != NULL)
  660.                     {
  661.                         if (strncmp(comment, str->string, str->length) == 0)
  662.                         {
  663.                             stamps_to_columns(ages, increment.start,
  664.                                 increment.last, datep.da_year, &starts, &ends,
  665.                                 h.uses);
  666.  
  667.                             /*  Print line and total for verbose mode  */
  668.                             if (volume == -1)
  669.                             {
  670.                                 printf(
  671.                                     "%s---------------------->elapsed: %ld seconds\n",
  672.                                     line, ends-starts);
  673.                             }
  674.                             else if (volume)
  675.                             {
  676.                                 if ((vloop = (vloop+1)%volume) == 0)
  677.                                 {
  678.                                     printf(".");
  679.                                 }
  680.                             }
  681.  
  682.                             increment.online = FALSE;
  683.                             break;
  684.                         }
  685.                         str = str->next;
  686.                     }
  687.                 }
  688.                 if (increment.online == FALSE)
  689.                 {
  690.                     /*  If looking for new usage...  */
  691.                     str = start_strings;
  692.                     while (str != NULL)
  693.                     {
  694.                         if (strncmp(comment, str->string, str->length) == 0)
  695.                         {
  696.                             strcpy(increment.start, increment.last);
  697.                             increment.online = TRUE;
  698.                             if (volume == -1)
  699.                             {
  700.                                 printf("%s", line);
  701.                             }
  702.                             break;
  703.                         }
  704.                         str = str->next;
  705.                     }
  706.                 }
  707.             }
  708.         }
  709.         printf("\n");
  710.  
  711.         /*  Update incremental log information  */
  712.         if (incfile[0])
  713.         {
  714.             if (found)
  715.             {
  716.                 /*  Update the existing incremental log entry  */
  717.                 lseek(incfile_fh, incpos, SEEK_SET);
  718.                 write(incfile_fh, &increment, sizeof(increments));
  719.             }
  720.             else
  721.             {
  722.                 /*  Append a new incremental log entry  */
  723.                 increment.slen = strlen(logfile[curlog])+1;
  724.                 lseek(incfile_fh, 0L, SEEK_END);
  725.                 write(incfile_fh, &increment, sizeof(increments));
  726.                 write(incfile_fh, logfile[curlog], increment.slen);
  727.             }
  728.         }
  729.  
  730.         /*  Close log file  */
  731.         fclose(logfile_fp);
  732.  
  733.         /*  Update total time array  */
  734.         if (increment.online)
  735.         {
  736.             stamps_to_columns(ages, first, increment.start, datep.da_year,
  737.                 &starts, &ends, h.totals);
  738.         }
  739.         else
  740.         {
  741.             stamps_to_columns(ages, first, increment.last, datep.da_year,
  742.                 &starts, &ends, h.totals);
  743.         }
  744.         if ((h.firsts > starts) || (h.firsts == 0))
  745.         {
  746.             h.firsts = starts;
  747.         }
  748.         if ((h.lasts < ends) || (h.lasts == 0))
  749.         {
  750.             h.lasts = ends;
  751.         }
  752.  
  753.         /*  Print line and total for verbose mode  */
  754.         if (volume == -1)
  755.         {
  756.             printf("======================>total: %ld seconds\n", ends-starts);
  757.         }
  758.  
  759.         /*  Check for last log processed  */
  760.         if (++curlog >= logs)
  761.             break;
  762.     }
  763.  
  764.     /*  Check for finish before last log processed  */
  765.     if (curlog < logs)
  766.     {
  767.         fprintf(stderr, "USAGE: Unable to open the log file: %s\n",
  768.             logfile[curlog]);
  769.         exit(1);
  770.     }
  771.  
  772.     /*  Close the incremental log file  */
  773.     close(incfile_fh);
  774.  
  775.     /*  Save new data for later writing to the history file  */
  776.     memcpy(&h_new, &h, sizeof(histories));
  777.  
  778.     /*  Merge in history data  */
  779.     if (histfile[0] != '\0')
  780.     {
  781.         if ((histfile_fh = sopen(histfile, O_RDWR | O_CREAT | O_BINARY |
  782.             O_DENYALL, SH_DENYRW, S_IREAD | S_IWRITE)) == -1)
  783.         {
  784.             fprintf(stderr, "USAGE: Unable to open %s\n", histfile);
  785.         }
  786.         else
  787.         {
  788.             /*  Read all original records  */
  789.             while (read(histfile_fh, &h_old, sizeof(histories)) >= sizeof(histories))
  790.             {
  791.                 /*  Process records newer than the age cutoff  */
  792.                 if (ages <= h_old.lasts)
  793.                 {
  794.                     /*  Integrate data from saved records  */
  795.                     h.firsts = min(h.firsts, h_old.firsts);
  796.                     h.lasts = max(h.lasts, h_old.lasts);
  797.                     for (i = 0; i < COLS; i++)
  798.                     {
  799.                         h.uses[i] += h_old.uses[i];
  800.                         h.totals[i] += h_old.totals[i];
  801.                     }
  802.                     /*  Write old records into new positions when needed  */
  803.                     if (read_record != write_record)
  804.                     {
  805.                         lseek(histfile_fh, write_record, SEEK_SET);
  806.                         write(histfile_fh, &h_old, sizeof(histories));
  807.                         lseek(histfile_fh, read_record+sizeof(histories),
  808.                             SEEK_SET);
  809.                     }
  810.                     write_record += sizeof(histories);
  811.                 }
  812.                 read_record += sizeof(histories);
  813.             }
  814.             /*  Append the new history data  */
  815.             lseek(histfile_fh, write_record, SEEK_SET);
  816.             write(histfile_fh, &h_new, sizeof(histories));
  817.             write_record += sizeof(histories);
  818.             /*  Update the file size if the new is smaller than the old  */
  819.             if (read_record > write_record)
  820.             {
  821.                 chsize(histfile_fh, write_record);
  822.             }
  823.             close(histfile_fh);
  824.         }
  825.     }
  826.  
  827.     draw_graph(&h, colors, military, usefile, title);
  828.  
  829.     return 0;
  830. }
  831.  
  832.  
  833. void append_strings(strings **link, char *string)
  834. {
  835.     strings **tmp;
  836.  
  837.     /*  Find the end of the linked list  */
  838.     tmp = link;
  839.     while (*tmp != NULL)
  840.     {
  841.         tmp = &((*tmp)->next);
  842.     }
  843.  
  844.     /*  Allocate a new record  */
  845.     *tmp = (strings *)malloc(sizeof(strings));
  846.     /*  Allocate and store the string  */
  847.     (*tmp)->string = malloc(strlen(string)+1);
  848.     strcpy((*tmp)->string, string);
  849.     /*  Convert the string to lower case  */
  850.     strlwr((*tmp)->string);
  851.     /*  Store the length of the string  */
  852.     (*tmp)->length = strlen(string);
  853.     /*  Initialize the next pointer  */
  854.     (*tmp)->next = NULL;
  855. }
  856.  
  857.  
  858. void stamps_to_columns(long ages, char *start, char  *end, int year,
  859.     long *starts, long *ends, long *columns)
  860. {
  861.     long    s;
  862.     long    e;
  863.     long    partial;
  864.     int     i;
  865.  
  866.     *starts = stamp_to_secs(start, year);
  867.     *ends = stamp_to_secs(end, year);
  868.     if (*ends < *starts)
  869.     {
  870.         if ((*starts)-(*ends) < DAYSECS*90)
  871.         {
  872.             *starts = (*ends)-1;
  873.         }
  874.         else
  875.         {
  876.             *starts = stamp_to_secs(start, year-1);
  877.         }
  878.     }
  879.  
  880.     if (*ends <= ages)
  881.     {
  882.         *starts = 0L;
  883.         *ends = 0L;
  884.         return;
  885.     }
  886.     if (*starts < ages)
  887.     {
  888.         *starts = ages;
  889.     }
  890.  
  891.     /*  Normalize to the nearest day  */
  892.     partial = ((*starts)/DAYSECS)*DAYSECS;
  893.     s = (*starts)-partial;
  894.     e = (*ends)-partial;
  895.  
  896.     /*  Increment wraparound totals for each column  */
  897.     partial = ((e-s)/DAYSECS)*CDIV;
  898.     if (partial)
  899.     {
  900.         for (i = 0; i < COLS; i++)
  901.         {
  902.             columns[i] += partial;
  903.         }
  904.     }
  905.  
  906.     e -= ((e-s)/DAYSECS)*DAYSECS;
  907.     /*  Increment non-wrapped totals  */
  908.     for (i = (int)(s/CDIV); i <= (int)(e/CDIV); i++)
  909.     {
  910.         columns[i%COLS] += min(e, (i+1)*CDIV) - max(s, i*CDIV);
  911.     }
  912. }
  913.  
  914.  
  915. long stamp_to_secs(char *stamp, int year)
  916. {
  917.     struct tm tmstamp;
  918.     char   month[12][3] = {
  919.                             "jan",
  920.                             "feb",
  921.                             "mar",
  922.                             "apr",
  923.                             "may",
  924.                             "jun",
  925.                             "jul",
  926.                             "aug",
  927.                             "sep",
  928.                             "oct",
  929.                             "nov",
  930.                             "dec"
  931.                             };
  932.  
  933.     timezone = 0L;
  934.     daylight = 0;
  935.     memset(&tmstamp, 0, sizeof(struct tm));
  936.     tmstamp.tm_year = year-1900;
  937.     for (tmstamp.tm_mon = 0; tmstamp.tm_mon < 12; tmstamp.tm_mon++)
  938.     {
  939.         if (strncmpi(stamp+3, month[tmstamp.tm_mon], 3) == 0)
  940.         {
  941.             break;
  942.         }
  943.     }
  944.     tmstamp.tm_mday = atoi(stamp);
  945.     tmstamp.tm_hour = atoi(stamp+7);
  946.     tmstamp.tm_min = atoi(stamp+10);
  947.     tmstamp.tm_sec = atoi(stamp+13);
  948.  
  949.     return mktime(&tmstamp);
  950. }
  951.  
  952.  
  953. long date_to_secs(struct date *datep, struct time *timep)
  954. {
  955.     struct tm tmstamp;
  956.  
  957.     timezone = 0L;
  958.     daylight = 0;
  959.     memset(&tmstamp, 0, sizeof(struct tm));
  960.     tmstamp.tm_year = datep->da_year-1900;
  961.     tmstamp.tm_mon = datep->da_mon-1;
  962.     tmstamp.tm_mday = datep->da_day;
  963.     tmstamp.tm_hour = timep->ti_hour;
  964.     tmstamp.tm_min = timep->ti_min;
  965.     tmstamp.tm_sec = timep->ti_sec;
  966.  
  967.     return mktime(&tmstamp);
  968. }
  969.  
  970.  
  971. int draw_graph(histories *h, char colors[], int military, char *usefile,
  972.     char *title)
  973. {
  974.     int    max_rows;
  975.     int    numrows;
  976.     int    numcols;
  977.     int    height[COLS];
  978.     char   out[BOTTOMROW - TOPROW][RIGHTCOL + 1];
  979.     char   color[BOTTOMROW - TOPROW][RIGHTCOL + 1];
  980.     char   curcolor = 0;
  981.     long   total_time = 0L;
  982.     int    i,j,k;
  983.     FILE   *usefile_fp;
  984.  
  985.     numrows = BOTTOMROW-TOPROW;
  986.     numcols = RIGHTCOL+1;
  987.  
  988.     /*  Initialize virtual screen  */
  989.     for (i=0; i<numrows; i++)
  990.         for (j=0; j<numcols; j++)
  991.         {
  992.             out[i][j] = ' ';
  993.             color[i][j] = colors[default_color];
  994.         }
  995.  
  996.     /*  Draw frame  */
  997.     for (i=RIGHTCOL-COLS; i<RIGHTCOL; i++)
  998.     {
  999.         out[BOTTOMROW-2][i] = out[TOPROW][i] = DOUBLEHORIZ;
  1000.         color[BOTTOMROW-2][i] = color[TOPROW][i] = colors[frame_color];
  1001.     }
  1002.     for (i=1; i<BOTTOMROW-2; i++)
  1003.     {
  1004.         out[i][RIGHTCOL] = out[i][RIGHTCOL-1-COLS] = DOUBLEVERT;
  1005.         color[i][RIGHTCOL] = color[i][RIGHTCOL-1-COLS] = colors[frame_color];
  1006.     }
  1007.  
  1008.     /*  Draw corners  */
  1009.     out[TOPROW][LEFTCOL] = TOPLEFT;
  1010.     out[TOPROW][RIGHTCOL] = TOPRIGHT;
  1011.     out[BOTTOMROW-2][LEFTCOL] = BOTTOMLEFT;
  1012.     out[BOTTOMROW-2][RIGHTCOL] = BOTTOMRIGHT;
  1013.     color[BOTTOMROW-2][RIGHTCOL] =
  1014.         color[BOTTOMROW-2][LEFTCOL] =
  1015.             color[TOPROW][RIGHTCOL] =
  1016.                 color[TOPROW][LEFTCOL] = colors[frame_color];
  1017.  
  1018.     /*  Draw 20% references  */
  1019.     for (i=1; i<5; i++)
  1020.     {
  1021.         /*  Draw reference text  */
  1022.         out[i*4][RIGHTCOL-4-COLS] = ((5-i)*2)+'0';
  1023.         out[i*4][RIGHTCOL-3-COLS] = '0';
  1024.         out[i*4][RIGHTCOL-2-COLS] = '%';
  1025.         color[i*4][RIGHTCOL-4-COLS] =
  1026.             color[i*4][RIGHTCOL-3-COLS] =
  1027.                 color[i*4][RIGHTCOL-2-COLS] = colors[reference_text_color];
  1028.         /*  Draw reference lines  */
  1029.         for (j=RIGHTCOL-COLS; j<RIGHTCOL; j++)
  1030.         {
  1031.             out[i*4][j] = HORIZLINE;
  1032.             color[i*4][j] = colors[reference_lines_color];
  1033.         }
  1034.     }
  1035.  
  1036.     /*  Draw hour references  */
  1037.     for (i=0; i<24; i++)
  1038.     {
  1039.         if (i%2)
  1040.         {
  1041.             /*  Draw dots for odd hours  */
  1042.             out[BOTTOMROW-1][RIGHTCOL-COLS+(COLS/24*i)] = DOT;
  1043.             color[BOTTOMROW-1][RIGHTCOL-COLS+(COLS/24*i)] =
  1044.                 colors[hour_dots_color];
  1045.         }
  1046.         else
  1047.         {
  1048.             /*  Draw numbers for even hours  */
  1049.             k = (military) ? i : ((i+11)%12)+1;
  1050.             /*  Check for one or two digits  */
  1051.             if (k > 9)
  1052.             {
  1053.                 out[BOTTOMROW-1][RIGHTCOL-COLS+(COLS/24*i)] = (k/10)+'0';
  1054.                 out[BOTTOMROW-1][RIGHTCOL+1-COLS+(COLS/24*i)] = (k%10)+'0';
  1055.                 color[BOTTOMROW-1][RIGHTCOL-COLS+(COLS/24*i)] =
  1056.                     color[BOTTOMROW-1][RIGHTCOL+1-COLS+(COLS/24*i)] =
  1057.                         colors[hour_text_color];
  1058.             }
  1059.             else
  1060.             {
  1061.                 out[BOTTOMROW-1][RIGHTCOL-COLS+(COLS/24*i)] = (k%10)+'0';
  1062.                 color[BOTTOMROW-1][RIGHTCOL-COLS+(COLS/24*i)] =
  1063.                     colors[hour_text_color];
  1064.             }
  1065.         }
  1066.         /*  Draw hour lines  */
  1067.         if ((i%4 == 0) && (i > 0))
  1068.             for (j=1; j<BOTTOMROW-2; j++)
  1069.             {
  1070.                 out[j][RIGHTCOL-COLS+(COLS/24*i)] = VERTLINE;
  1071.                 color[j][RIGHTCOL-COLS+(COLS/24*i)] = colors[hour_lines_color];
  1072.             }
  1073.     }
  1074.  
  1075.     /*  Graph data  */
  1076.     max_rows = ((BOTTOMROW - TOPROW - 2 - 1) * 2) + 1;
  1077.     for (i=0; i<COLS; i++)
  1078.     {
  1079.         /*  Divide use counts by total time (for all logs) and minutes per column  */
  1080.         if (h->totals[i])
  1081.         {
  1082.             height[i] = (int)(h->uses[i]*(long)(max_rows)/h->totals[i]);
  1083.         }
  1084.         else
  1085.         {
  1086.             height[i] = 0;
  1087.         }
  1088.         /*  Accumulate total time  */
  1089.         total_time += h->totals[i];
  1090.         /*  Clip to maximum height (just in case)  */
  1091.         if (height[i] > max_rows - 1)
  1092.             height[i] = max_rows-1;
  1093.         j = 0;
  1094.         while (j*2 < height[i])
  1095.         {
  1096.             /* 219 = full height block, 220 = half height block */
  1097.             out[BOTTOMROW-2-1-j][RIGHTCOL-COLS+i] =
  1098.                 (j*2+1 < height[i] ? FULLHEIGHT : HALFHEIGHT);
  1099.             color[BOTTOMROW-2-1-j][RIGHTCOL-COLS+i] = colors[data_bars_color];
  1100.             j++;
  1101.         }
  1102.     }
  1103.  
  1104. /*  Open use (output) file  */
  1105.     if ((usefile_fp = _fsopen(usefile, "w+b", SH_DENYRW)) == NULL)
  1106.     {
  1107.     fprintf(stderr, "USAGE: Unable to open %s\n", usefile);
  1108.         exit(1);
  1109.     }
  1110.  
  1111.     /*  Write [cls] and [moreoff] tokens  */
  1112.     fprintf(usefile_fp, "\x0c\x0b");
  1113.  
  1114.     /*  Write centered title  */
  1115.     fprintf(usefile_fp, "\x16\x01%c   ", colors[title_color]);
  1116.     j = 39-(strlen(title))/2;
  1117.     for (i = 0; i < j; i++)
  1118.         putc(' ', usefile_fp);
  1119.     fprintf(usefile_fp, "%s\r\n", title);
  1120. /*  Add a switch to enable this option?
  1121.     fprintf(usefile_fp, "%s (%d day%s)\r\n", title,
  1122.         (int)((h->lasts-h->firsts)/DAYSECS+1),
  1123.         ((h->lasts-h->firsts)/DAYSECS+1) > 1 ? "s" : "");
  1124. */
  1125.  
  1126.     /*  Write data  */
  1127.     for (i=0; i<BOTTOMROW-TOPROW; i++)
  1128.     {
  1129.         for (j=0; j<RIGHTCOL+1; j++)
  1130.         {
  1131.             /*  Write color change tokens only when necessary  */
  1132.             if (color[i][j] != curcolor)
  1133.             {
  1134.                 curcolor = color[i][j];
  1135.                 fprintf(usefile_fp, "\x16\x01%c", curcolor);
  1136.             }
  1137.             putc(out[i][j], usefile_fp);
  1138.         }
  1139.     fprintf(usefile_fp, "\r\n");
  1140.     }
  1141.  
  1142.     /*  Add color and [enter] tokens  */
  1143.     fprintf(usefile_fp, "\x16\x01%c                            \x01",
  1144.         colors[enter_prompt_color]);
  1145.  
  1146.     /*  Close use file  */
  1147.     fclose(usefile_fp);
  1148.  
  1149.     return 0;
  1150. }
  1151.  
  1152.  
  1153.