/*
* Copyright 2017 Yann Weber
*
* This file is part of Ttail.
*
* Ttail is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Ttail is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Ttail. If not, see .
*/
#include "ttail_init.h"
void usage()
{
static struct option opts[] = TTAIL_LONG_OPT;
static char *help[][2] = TTAIL_OPT_HELP;
size_t i;
printf("Usage : ttail -d DATE [OPTIONS] [LOGFILES]\n");
printf("\t\n");
printf("Options list\n");
i=0;
while(opts[i].name)
{
printf("\t-%c, --%s", opts[i].val, opts[i].name);
if(opts[i].has_arg == required_argument)
{
printf("=%s\n",
help[i][1]?help[i][1]:"ARG");
}
else if(opts[i].has_arg == optional_argument)
{
printf("[=%s]\n",
help[i][1]?help[i][1]:"ARG");
}
else
{
printf("\n");
}
printf("\t\t%s\n\n", help[i][0]);
i++;
}
printf(TTAIL_HELP_TEXT);
}
ttail_t *ttail_init(int argc, char **argv)
{
ttail_t *res;
int c, opt_i, ret;
size_t i;
char *dates[2] = { NULL, NULL };
char *date;
res = malloc(sizeof(ttail_t));
if(!res)
{
perror("Unable to allocate memory");
goto ttail_init_alloc_err;
}
opterr = 0;
optind = 0;
res->verbose = 0;
res->fmt = NULL;
res->flag = 0;
res->logfile = NULL;
res->logfile_name = NULL;
res->logfile_sz = 0;
res->prefix_sz = 0;
res->session = NULL;
ttail_tm_init(&(res->date_min));
ttail_tm_init(&(res->date_max));
while(1)
{
static struct option long_options[] = TTAIL_LONG_OPT;
c = getopt_long(argc, argv, TTAIL_SHORT_OPT, long_options, &opt_i);
if(c == -1)
break;
switch(c)
{
case 'v':
res->verbose++;
break;
case 'r':
if(ttail_set_prefix(res, optarg))
{
goto ttail_init_err;
}
break;
case 'p':
if(res->flag & TTAIL_FLAG_PREFIX)
{
fprintf(stderr, "Preffix allready \
set\n");
goto ttail_init_err;
}
res->flag |= TTAIL_FLAG_PREFIX;
res->prefix_sz = atoi(optarg);
if(res->prefix_sz <= 0)
{
fprintf(stderr, "Prefix size have \
to be > 0");
goto ttail_init_err;
}
break;
case 'E':
if(ttail_set_flag_re_ex(res) < 0)
{
goto ttail_init_err;
}
break;
case 'I':
if(ttail_set_flag_re_ci(res) < 0)
{
goto ttail_init_err;
}
break;
case 'f':
if(res->flag & TTAIL_FLAG_FORMAT)
{
fprintf(stderr,"Multiple date format \
given\n");
goto ttail_init_err;
}
if(ttail_set_fmt(res, optarg) < 0)
{
goto ttail_init_err;
}
break;
case 'h':
usage();
goto ttail_init_err;
case 'd':
case 'm':
date = malloc(sizeof(char)*(strlen(optarg)+1));
if(!date)
{
goto ttail_init_err;
}
strcpy(date, optarg);
dates[c=='d'?0:1] = date;
break;
case '?':
optind--;
goto init_badarg;
break;
default:
fprintf(stderr, "Bad argument\n");
goto init_badarg;
}
}
for(i=optind; ilogfile_sz)
{
ret = 0;
for(i=0; ilogfile_sz; i++)
{
if(t->logfile[i])
{
ret = 1;
break;
}
}
if(!ret)
{
fprintf(stderr, "Unable to read from any of the files \
given as argument\n");
return -1;
}
}
if(!(t->flag & TTAIL_FLAG_DATE_MIN) && !(t->flag & TTAIL_FLAG_DATE_MAX))
{
fprintf(stderr, "one of --date-min -d --date-max -m is \
mandatory\n");
return -1;
}
if((t->flag & TTAIL_FLAG_DATE_MIN) && (t->flag & TTAIL_FLAG_DATE_MAX))
{
if(ttail_tm_cmp(&(t->date_min), &(t->date_max)) > 0)
{
fprintf(stderr, "date-min > date-max.\n");
return -1;
}
}
return 0;
}
int ttail_add_logfile(ttail_t* res, const char* filename)
{
void *tmp;
FILE *fp;
char *fname;
int ret, i;
for(i=0; ilogfile_sz; i++)
{
if(strcmp(filename, res->logfile_name[i]) == 0)
{
fprintf(stderr, "File '%s' allready added\n",
filename);
return -1;
}
}
res->logfile_sz++;
tmp = res->logfile;
res->logfile = realloc(res->logfile,
sizeof(FILE*)*res->logfile_sz);
ret = 0;
if(!res->logfile)
{
perror("Unable to allocate memory for logfiles");
res->logfile = tmp;
goto ttail_add_logfile_fpalloc_err;
}
fp = fopen(filename, "r");
if(!fp)
{
fprintf(stderr, "Unable to open file : %s\n", filename);
res->logfile[res->logfile_sz-1] = NULL;
ret = 1;
}
res->logfile[res->logfile_sz-1] = fp;
tmp = res->logfile_name;
res->logfile_name = realloc(res->logfile_name,
sizeof(char*)*res->logfile_sz);
if(!res->logfile_name)
{
perror("Unable to allocate memory for logfiles");
res->logfile_name = tmp;
goto ttail_add_logfile_fnalloc_err;
}
fname = malloc(sizeof(char)*(strlen(filename)+1));
if(!fname)
{
perror("Unable to allocate memory for logfiles");
goto ttail_add_logfile_fnalloc_err;
}
strcpy(fname, filename);
res->logfile_name[res->logfile_sz-1] = fname;
return ret;
ttail_add_logfile_fnalloc_err:
fclose(res->logfile[res->logfile_sz-2]);
ttail_add_logfile_fpalloc_err:
res->logfile_sz--;
return -1;
}
int ttail_set_prefix(ttail_t* res, const char* regex)
{
int ret, cflags;
char *re_errbuff;
size_t re_errbuff_sz;
if(res->flag & TTAIL_FLAG_PREFIX)
{
fprintf(stderr, "Regex prefix allready set");
return 1;
}
res->flag |= TTAIL_FLAG_PREFIX;
cflags = 0; /** @todo checks */
if(res->flag & TTAIL_FLAG_EXTENDED_RE)
{
cflags |= REG_EXTENDED;
}
if(res->flag & TTAIL_FLAG_CI_RE)
{
cflags |= REG_ICASE;
}
ret = regcomp(&res->date_prefix, regex, cflags);
if(!ret)
{
res->prefix_sz = -1;
return 0;
}
/*compilation error */
res->flag ^= TTAIL_FLAG_PREFIX;
re_errbuff_sz = regerror(ret,
&res->date_prefix, NULL, 0);
re_errbuff = malloc(
sizeof(char)*re_errbuff_sz);
if(!re_errbuff)
{
perror("Failed to allocate memory for regex compilation \
error message");
goto ttail_set_prefix_err;
}
regerror(ret, &res->date_prefix,
re_errbuff,
sizeof(char)*re_errbuff_sz);
fprintf(stderr, "Regex compilation fails : %s", re_errbuff);
free(re_errbuff);
ttail_set_prefix_err:
return -1;
}
int ttail_set_fmt(ttail_t* t, const char* fmt)
{
size_t len;
if(t->flag & TTAIL_FLAG_FORMAT)
{
return -1;
}
len = strlen(fmt);
t->fmt = malloc(sizeof(char)*(len+1));
if(!t->fmt)
{
return -1;
}
strncpy(t->fmt, fmt, len);
t->fmt[len] = '\0';
t->flag |= TTAIL_FLAG_FORMAT;
return 0;
}
/**@brief This function is used by bot @ref ttail_set_flag_re_ex() and
*ttail_set_flag_re_ci() function
*@param t ttail_t*
*@return -1 on error else 0
*/
static int _ttail_common_set_flag_re(ttail_t* ttail)
{
if(ttail->flag & TTAIL_FLAG_PREFIX)
{
fprintf(stderr, "Regex flags has to be set BEFORE the \
prefix\n");
return -1;
}
return 0;
}
int ttail_set_flag_re_ex(ttail_t* ttail)
{
if(_ttail_common_set_flag_re(ttail) < 0)
{
return -1;
}
ttail->flag |= TTAIL_FLAG_EXTENDED_RE;
return 0;
}
int ttail_set_flag_re_ci(ttail_t* ttail)
{
if(_ttail_common_set_flag_re(ttail) < 0)
{
return -1;
}
ttail->flag |= TTAIL_FLAG_CI_RE;
return 0;
}
int ttail_set_dates(ttail_t* res, char* dates[2])
{
int c, ret;
char *date;
for(c=0;c<2;c++)
{
ttail_tm_init(c==0?&(res->date_min):&(res->date_max));
if(!dates[c])
{
continue;
}
date = dates[c];
if(!strncmp(date, "#-", 2))
{
/* relative date */
ret = _ttail_set_date_relative(res, date, c);
}
else
{
/* date from format */
ret = _ttail_set_date_fmt(res, date, c);
}
if(ret < 0)
{
return -1;
}
res->flag |= c?TTAIL_FLAG_DATE_MAX:TTAIL_FLAG_DATE_MIN;
}
if(dates[0]) { free(dates[0]); dates[0] = NULL; }
if(dates[1]) { free(dates[1]); dates[1] = NULL; }
return 0;
}
int _ttail_set_date_fmt(ttail_t* res, char* date, int c)
{
int ret;
char *endp;
char *fmt[] = TTAIL_DEFAULT_FORMATS;
if(!(res->flag & TTAIL_FLAG_FORMAT))
{
/* no format specified */
ret = ttail_format_guess(date,
c==0?&(res->date_min):&(res->date_max));
if(ret < 0)
{
fprintf(stderr, "Unable to guess format for \
date '%s'\n", date);
return -1;
}
res->fmt = malloc(sizeof(char)*(strlen(fmt[ret])+1));
if(!res->fmt)
{
perror("Unable to allocate memory for date format");
res->flag ^= TTAIL_FLAG_FORMAT;
}
strcpy(res->fmt, fmt[ret]);
res->flag |= TTAIL_FLAG_FORMAT;
res->flag |= c?TTAIL_FLAG_DATE_MAX:TTAIL_FLAG_DATE_MIN;
return 0;
}
endp = strptime(date, res->fmt,
c==0?&(res->date_min):&(res->date_max));
if(!endp)
{
fprintf(stderr, "Unable to parse date-%s '%s' with \
format '%s'\n", c==0?"min":"max", date, res->fmt);
return -1;
}
else if(*endp != '\0')
{
fprintf(stderr, "Leading caracters for date-%s : '%s' \
is not matched in '%s'\n",\
c==0?"min":"max", endp, date);
return -1;
}
return 0;
}
int _ttail_set_date_relative(ttail_t* res, char* date, int c)
{
time_t now;
char *unit, str_date[64];
int value;
struct tm *tm, *tm_p;
short mod;
tm = c==0?&(res->date_min):&(res->date_max);
ttail_tm_init(tm);
if(time(&now) < 0)
{
fprintf(stderr, "Unable to retrieve time using time()\n");
return -1;
}
if(!(tm_p = localtime(&now)))
{
fprintf(stderr, "Unable to retrieve localtime using localtime()\n");
return -1;
}
memcpy(tm, tm_p, sizeof(struct tm));
mod = 0;
unit = NULL;
value = (int)strtol(date+2, &unit, 10);
switch(*unit)
{
case 's':
if(*(unit+1) == '\0' || !strcmp(unit, "sec"))
{
tm->tm_sec -= value;
if(tm->tm_sec < 0)
{
mod = 1;
value = abs(tm->tm_sec);
tm->tm_sec = 60 - (value % 60);
value /= 60;
value++;
}
if(!mod)
{
break;
}
}
case 'm':
if(mod || *(unit+1) == '\0' || !strcmp(unit, "min"))
{
mod = 0;
tm->tm_min -= value;
if(tm->tm_min < 0)
{
mod = 1;
value = abs(tm->tm_min);
tm->tm_min = 60 - (value % 60);
value /= 60;
value++;
printf("Value left : %d\n", value);
}
if(!mod)
{
break;
}
}
case 'h':
if(mod || *(unit+1) == '\0' || !strcmp(unit, "hour"))
{
mod = 0;
tm->tm_hour -= value;
if(tm->tm_hour < 0)
{
mod = 1;
value = abs(tm->tm_hour);
tm->tm_hour = 24 - (value % 24);
value /= 24;
value++;
}
if(!mod)
{
break;
}
}
case 'd':
if(mod || *(unit+1) == '\0' || !strcmp(unit, "day"))
{
mod = 0;
tm->tm_mday -= value;
if(tm->tm_mday < 0)
{
mod = 1;
value = abs(tm->tm_mday);
tm->tm_mday = 31 - (value % 31);
value /= 31;
value++;
}
if(!mod)
{
break;
}
}
case 'M':
if(*(unit+1) == '\0' || !strcmp(unit, "Month"))
{
mod = 0;
tm->tm_mon -= value;
if(tm->tm_mon < 0)
{
mod = 1;
value = abs(tm->tm_mon);
tm->tm_mon = 12 - (value % 12);
value /= 12;
value++;
}
if(!mod)
{
break;
}
}
case 'y':
if(mod || *(unit+1) == '\0' || !strcmp(unit, "year"))
{
tm->tm_year -= value;
break;
}
default:
fprintf(stderr,"Invalid relative date '%s'\n", date);
return -1;
}
if(res->verbose > 0)
{
strftime(str_date, 64, "%c", tm);
fprintf(stderr, "%s date set to %s\n",
c?"stop":"start", str_date);
}
return 0;
}
int ttail_norm_dates(ttail_t* ttail)
{
struct tm *tm_p, tm;
/* Huge buffer but no way to make a difference between error and
buffer too small using strftime */
char date[4096], *endp;
size_t date_len;
time_t now;
if(time(&now) < 0)
{
perror("Unable to retrieve time using time()\n");
return -1;
}
if(!(tm_p = localtime(&now)))
{
perror("Unable to retrieve localtime using localtime()\n");
return -1;
}
if(!(date_len = strftime(date, sizeof(date), ttail->fmt, tm_p)))
{
fprintf(stderr, "Unable to print the current date using strftime()\n");
return -1;
}
ttail_tm_init(&tm);
endp = strptime(date, ttail->fmt, &tm);
if(!endp || *endp != '\0')
{
fprintf(stderr, "An error occured, unable to strptime() a date\
we just strftime() !\n");
exit(EXIT_FAILURE);
}
TTAIL_NORMDATE(ttail, &tm, tm_sec);
TTAIL_NORMDATE(ttail, &tm, tm_min);
TTAIL_NORMDATE(ttail, &tm, tm_hour);
TTAIL_NORMDATE(ttail, &tm, tm_mday);
TTAIL_NORMDATE(ttail, &tm, tm_mon);
TTAIL_NORMDATE(ttail, &tm, tm_year);
return 0;
}
int ttail_format_guess(const char* date_str, struct tm* tm)
{
int i, res;
char *res_ret, *ret;
char *fmt[] = TTAIL_DEFAULT_FORMATS;
struct tm dte;
memset(&dte, 0, sizeof(struct tm));
res = -1;
res_ret = NULL;
i=0;
while(fmt[i])
{
ret = strptime(date_str, fmt[i], &dte);
if(ret)
{
if(!res_ret || strlen(res_ret) > strlen(ret))
{
res_ret = ret;
res = i;
if(tm)
{
memcpy(tm, &dte, sizeof(struct tm));
}
}
}
i++;
}
if(!res_ret)
{
if(tm)
{
memset(tm, 0, sizeof(struct tm));
}
return -1;
}
return res;
}
void ttail_free(ttail_t* t)
{
size_t i;
if(t->flag & TTAIL_FLAG_PREFIX && t->prefix_sz < 0)
{
regfree(&(t->date_prefix));
}
for(i=0; ilogfile_sz; i++)
{
if(t->logfile[i])
{
fclose(t->logfile[i]);
}
if(t->logfile_name[i])
{
free(t->logfile_name[i]);
}
}
free(t->logfile);
free(t->logfile_name);
t->logfile_sz = 0;
if(t->fmt != NULL)
{
free(t->fmt);
}
free(t);
optind=0;
}