1 /******************************************************************************
2 * Copyright (c) 2000-2016 Ericsson Telecom AB
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
15 * Szabo, Janos Zoltan – initial implementation
18 ******************************************************************************/
19 /**************************
21 written by Gabor Tatarka
22 **************************/
30 #include "../common/memory.h"
31 #include "../common/version_internal.h"
34 #include "../common/license.h"
38 /* On MinGW seeking is not working in files opened in text mode due to a
39 * "feature" in the underlying MSVCRT. So we open all files in binary mode. */
40 # define FOPEN_READ "rb"
41 # define FOPEN_WRITE "wb"
43 # define FOPEN_READ "r"
44 # define FOPEN_WRITE "w"
51 /* These lengths below represent the number of characters in the log file.
52 * No NUL terminator is included in the length. */
54 #define SECONDLENGTH 9
55 #define DATETIMELENGTH 27
56 #define MAXTIMESTAMPLENGTH 27
58 #define BUFFERSIZE 1024
59 #define YYYYMONDD 1 /* format of Date: year/month/day*/
61 static const char * const MON
[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
62 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
64 static const char *progname
;
65 static FILE *outfile
= NULL
;
67 enum TimeStampFormats
{ TSF_Undefined
= -1, TSF_Seconds
, TSF_Time
,
69 static int TimeStampLength
;
70 static enum TimeStampFormats TimeStampUsed
= TSF_Undefined
;
72 static int IsSecond(const char *str
)/*Is timestamp format Seconds*/
75 if (*str
>= '0' && *str
<= '9') str
++; /* first digit */
77 while (*str
>= '0' && *str
<= '9') str
++; /* other digits */
78 if (*str
== '.') str
++;
79 else return False
; /* '.' */
80 for (i
= 0; i
< 6; i
++, str
++)
81 if (*str
< '0' || *str
> '9') return False
; /* microseconds(6 digits) */
85 static int IsSecond2_6(const char *str
)/*does string contain sec(2).usec(6)*/
88 for(a
=0;a
<SECONDLENGTH
;a
++) {
94 if(*str
<'0'||*str
>'9')return False
;
100 static int IsTime(const char *str
)/*Is timestamp format Time*/
103 if(False
==IsSecond2_6(str
+6))return False
;
110 if(*str
<'0'||*str
>'9')return False
;
116 #ifdef YYYYMONDD /*Date format: year/month/day*/
119 #else /*Date format: day/month/year*/
124 static int IsDateTime(const char *str
)/*is timestamp format Date/Time*/
127 if(False
==IsTime(str
+12))return False
;
128 for(a
=0;a
<FIRST_LEN
;a
++) {/*YYYY or DD*/
129 if(*str
<'0'||*str
>'9')return False
;
132 if(*str
!='/')return False
;/* '/' */
134 for(a
=0,b
=0;a
<12;a
++)if(0==strncmp(str
,MON
[a
],3)){b
=1;break;}/*MON*/
137 if(*str
!='/')return False
;/* '/' */
139 for(a
=0;a
<THIRD_LEN
;a
++) {/*DD or YYYY*/
140 if(*str
<'0'||*str
>'9')return False
;
146 static int FormatMatch(const char *str
,int format
)/*does format of timestamp match format*/
150 case TSF_Undefined
:return False
;
151 case TSF_Seconds
:if(False
==IsSecond(str
))return False
;else return True
;
152 case TSF_Time
:if(False
==IsTime(str
))return False
;else return True
;
153 case TSF_DateTime
:if(False
==IsDateTime(str
))return False
;
155 default:return False
;
161 DateTime: yyyy/Mon/dd hh:mm:ss.us
165 static enum TimeStampFormats
GetTimeStampFormat(const char *filename
)
167 /*get timestamp format used in file*/
168 enum TimeStampFormats ret_val
= TSF_Undefined
;
169 char str
[MAXTIMESTAMPLENGTH
+ 1];
170 FILE *fp
= fopen(filename
, FOPEN_READ
);
172 fprintf(stderr
, "%s: warning: cannot open %s: %s\n", progname
,
173 filename
, strerror(errno
));
174 return TSF_Undefined
;
176 if (fgets(str
, sizeof(str
), fp
) != NULL
) {
177 if (IsSecond(str
)) ret_val
= TSF_Seconds
;
178 else if (IsTime(str
)) ret_val
= TSF_Time
;
179 else if (IsDateTime(str
)) ret_val
= TSF_DateTime
;
185 static char *GetComponentIdentifier(const char *path_name
)
188 size_t name_len
= strlen(path_name
);
189 size_t filename_begin
= 0, i
;
190 size_t compid_begin
, compid_end
;
192 /* find the first character of the file name */
193 for (i
= 0; i
< name_len
; i
++)
194 if (path_name
[i
] == '/') filename_begin
= i
+ 1;
195 /* fallback values if neither '-' nor '.' is found */
196 compid_begin
= filename_begin
;
197 compid_end
= name_len
;
198 /* find the last '-' character in the file name */
199 for (i
= name_len
; i
> filename_begin
; i
--)
200 if (path_name
[i
- 1] == '-') {
206 /* find the first '.' character after the '-' */
207 for (i
= compid_begin
; i
< name_len
; i
++)
208 if (path_name
[i
] == '.') {
213 /* find the last '.' in the file name */
214 for (i
= name_len
; i
> filename_begin
; i
--)
215 if (path_name
[i
- 1] == '.') {
219 /* find the last but one '.' in the file name */
220 for (i
= compid_end
; i
> filename_begin
; i
--)
221 if (path_name
[i
- 1] == '.') {
226 if (compid_end
> compid_begin
) {
227 size_t compid_len
= compid_end
- compid_begin
;
228 ret_val
= (char*)Malloc(compid_len
+ 1);
229 memcpy(ret_val
, path_name
+ compid_begin
, compid_len
);
230 ret_val
[compid_len
] = '\0';
231 } else ret_val
= NULL
;
235 static FILE *OpenTempFile(char **filename
)
239 /* Function mkstemp() is not supported on MinGW */
240 char *temp_name
= tempnam(NULL
, NULL
);
241 if (temp_name
== NULL
) {
242 fprintf(stderr
, "%s: creation of a temporary file failed: %s\n", progname
,
246 fp
= fopen(temp_name
, FOPEN_WRITE
);
248 fprintf(stderr
, "%s: opening of temporary file `%s' failed: %s\n",
249 progname
, temp_name
, strerror(errno
));
253 *filename
= mcopystr(temp_name
);
257 *filename
= mcopystr("/tmp/logmerge_XXXXXX");
258 fd
= mkstemp(*filename
);
260 fprintf(stderr
, "%s: creation of a temporary file based on template `%s' "
261 "failed: %s\n", progname
, *filename
, strerror(errno
));
265 fp
= fdopen(fd
, FOPEN_WRITE
);
267 fprintf(stderr
, "%s: system call fdopen() failed on temporary file `%s' "
268 "(file descriptor %d): %s\n", progname
, *filename
, fd
, strerror(errno
));
276 static FILE **fp_list_in
= NULL
, *fpout
;
277 static char **name_list_in
= NULL
;
278 static int fpout_is_closeable
= 0,must_use_temp
= 0;
279 static char **temp_file_list
= NULL
;
280 static int num_tempfiles
= 0, num_infiles
= 0, num_allfiles
= 0,start_file
= 0;
281 static int infiles_processed
= False
;
284 char timestamp
[MAXTIMESTAMPLENGTH
+1];/*text of timestamp*/
285 time_t sec
; /*seconds in timestamp (since 1970)*/
286 unsigned long usec
; /*microseconds in timestamp (0L..1000000L)*/
287 int wrap
;/*if current timestamp is smaller than prev. timestamp -> wrap++;*/
288 expstring_t data
;/*text of logged event*/
289 char *str_to_add
;/*part of original filename*/
290 int ignore
; /* if true -> EOF */
291 long start_line
,line_ctr
;/*line of event start (timestamp), line counter*/
294 static LogEvent
**EventList
;
296 static int OpenMaxFiles(int argc
,char *argv
[])
300 fp_list_in
=(FILE **)Realloc(fp_list_in
,(a
+1)*sizeof(FILE *));
302 fp_list_in
[a
]=fopen(argv
[a
], FOPEN_READ
);
303 if(fp_list_in
[a
]==NULL
) {
306 /* Solaris may not set errno if libc cannot create a stdio
307 stream because the underlying fd is greater than 255 */
310 /*more infiles than can be opened->close one and create a tempfile for output*/
312 Free(EventList
[--a
]->str_to_add
);
314 fclose(fp_list_in
[a
]);
315 temp_file_list
= (char**)Realloc(temp_file_list
,
316 (num_tempfiles
+ 1) * sizeof(*temp_file_list
));
317 fpout
= OpenTempFile(temp_file_list
+ num_tempfiles
);
319 fpout_is_closeable
=1;
324 fprintf(stderr
,"%s: error opening input file %s: %s\n",
325 progname
, argv
[a
], strerror(errno
));
329 EventList
=(LogEvent
**)Realloc(EventList
,
330 (a
+1)*sizeof(LogEvent
*));
331 EventList
[a
]=(LogEvent
*)Malloc(sizeof(LogEvent
));
332 if (infiles_processed
) EventList
[a
]->str_to_add
= NULL
;
334 /* cutting the component identifier portion out from the
336 EventList
[a
]->str_to_add
=
337 GetComponentIdentifier(name_list_in
[a
+ start_file
]);
339 EventList
[a
]->ignore
=True
;
340 EventList
[a
]->data
=NULL
;
341 EventList
[a
]->wrap
=0;
343 EventList
[a
]->usec
=0L;
344 EventList
[a
]->line_ctr
=1L;
345 EventList
[a
]->start_line
=1L;
351 temp_file_list
= (char**)Realloc(temp_file_list
,
352 (num_tempfiles
+ 1) * sizeof(*temp_file_list
));
353 fpout
= OpenTempFile(temp_file_list
+ num_tempfiles
);
355 fpout_is_closeable
= 1;
358 if (outfile
!=stdout
) fpout_is_closeable
= 1;
359 else fpout_is_closeable
= 0;
362 return a
;/*nr. of opened files*/
365 static void CloseAllFiles(void)
368 if (fpout_is_closeable
) fclose(fpout
);
369 for (i
= 0; i
< num_infiles
; i
++) {
370 Free(EventList
[i
]->data
);
371 Free(EventList
[i
]->str_to_add
);
379 static int EventCmp(LogEvent
*e1
,LogEvent
*e2
)
380 /*Returns: if(event1<event2)-1;
382 if(event1>event2)1;*/
384 time_t tmpsec1
,tmpsec2
;
387 if(tmpsec1
<tmpsec2
)return -1;
388 if(tmpsec1
>tmpsec2
)return 1;
389 if(e1
->usec
<e2
->usec
)return -1;
390 if(e1
->usec
>e2
->usec
)return 1;
404 static void TS2long(time_t *sec
, unsigned long *usec
, const char *TSstr
)
405 /*converts timestamp string to two long values*/
409 char *ptr
,str
[MAXTIMESTAMPLENGTH
+1];
410 strncpy(str
,TSstr
,MAXTIMESTAMPLENGTH
);
411 str
[MAXTIMESTAMPLENGTH
] = '\0';
412 /*->this way only a copy of the timestamp string will be modified*/
413 switch(TimeStampUsed
) {
415 ptr
=strpbrk(str
,".");
416 *ptr
='\0';ptr
++;*(ptr
+6)='\0';
417 *sec
=(time_t)atol(str
);
429 TM
.tm_hour
= atoi(str
);
430 TM
.tm_min
= atoi(str
+3);
431 TM
.tm_sec
= atoi(str
+6);
435 *(str
+YearOffset
+4)='\0';*(str
+MonOffset
+3)='\0';
436 *(str
+DayOffset
+2)='\0';
437 TM
.tm_year
=atoi(str
+YearOffset
)-1900;
438 for(a
=0;a
<12;a
++)if(!strcmp(MON
[a
],str
+MonOffset
)) {
441 TM
.tm_mday
=atoi(str
+DayOffset
);TM
.tm_isdst
=-1;
443 *(ptr
+2)='\0';*(ptr
+5)='\0';*(ptr
+8)='\0';
445 TM
.tm_hour
=atoi(ptr
);TM
.tm_min
=atoi(ptr
+3);
446 TM
.tm_sec
=atoi(ptr
+6);*usec
=atol(ptr
+9);
456 static int GetEvent(FILE *fp
, LogEvent
*event
)
459 unsigned long prev_usec
;
461 /* find and read timestamp */
462 if (fgets(event
->timestamp
, TimeStampLength
+ 1, fp
) == NULL
) {
463 event
->ignore
= True
;
466 event
->start_line
=event
->line_ctr
;
467 if (FormatMatch(event
->timestamp
, TimeStampUsed
)) break;
469 prev_sec
= event
->sec
;
470 prev_usec
= event
->usec
;
471 TS2long(&event
->sec
, &event
->usec
, event
->timestamp
);
472 if (event
->sec
< prev_sec
||
473 (event
->sec
== prev_sec
&& event
->usec
< prev_usec
)) {
476 event
->ignore
= False
;
479 char buf
[BUFFERSIZE
];
480 /* read the log-event */
481 if (fgets(buf
, sizeof(buf
), fp
) == NULL
) {
482 /* EOF was detected */
483 if (event
->data
== NULL
) event
->data
= mcopystr("\n");
484 else if (event
->data
[mstrlen(event
->data
) - 1] != '\n')
485 event
->data
= mputc(event
->data
, '\n');
489 if(FormatMatch(buf
,TimeStampUsed
)) {/*Did we read the next event's timestamp?*/
490 fseek(fp
, -1L * a
, SEEK_CUR
);/*"unread" next event*/
492 } else if (buf
[a
- 1] == '\n') event
->line_ctr
++;
493 event
->data
=mputstr(event
->data
, buf
);/*append buffer to event-data*/
498 static void WriteError(void)
500 fprintf(stderr
, "%s: error: writing to %s file failed: %s\n",
501 progname
, fpout
== outfile
? "output" : "temporary", strerror(errno
));
505 static void FlushEvent(LogEvent
*event
)
507 if (fputs(event
->timestamp
, fpout
) == EOF
) WriteError();
508 if (!infiles_processed
&& event
->str_to_add
!= NULL
) {
509 if (putc(' ', fpout
) == EOF
) WriteError();
510 if (fputs(event
->str_to_add
, fpout
) == EOF
) WriteError();
512 if (fputs(event
->data
, fpout
) == EOF
) WriteError();
515 event
->ignore
= True
;
518 static void ProcessOpenFiles(void)
519 /*merge all opened files to fpout (that is stdout or outfile or a tempfile),
523 for (i
= 0; i
< num_infiles
; i
++) {
524 /* read first logged event from all opened files */
525 if (!GetEvent(fp_list_in
[i
], EventList
[i
])) {
526 /* EOF or read error (e.g. file contained only one log event) */
527 fclose(fp_list_in
[i
]);
528 fp_list_in
[i
] = NULL
;
532 /* find the earliest timestamp */
534 for (i
= 0; i
< num_infiles
; i
++) {
535 if (!EventList
[i
]->ignore
&& (min_index
< 0 ||
536 EventCmp(EventList
[min_index
], EventList
[i
]) > 0))
539 if (min_index
< 0) break; /* no more events */
540 FlushEvent(EventList
[min_index
]);
541 if (fp_list_in
[min_index
] != NULL
) {
542 /* read the next event from that file */
543 EventList
[min_index
]->wrap
= 0;
544 if (!GetEvent(fp_list_in
[min_index
], EventList
[min_index
])) {
545 /*EOF or read error*/
546 fclose(fp_list_in
[min_index
]);
547 fp_list_in
[min_index
] = NULL
;
549 if (!infiles_processed
&& EventList
[min_index
]->wrap
> 0) {
550 fprintf(stderr
,"%s: warning: timestamp is in wrong order "
551 "in file %s line %ld\n", progname
,
552 name_list_in
[min_index
+ start_file
],
553 EventList
[min_index
]->start_line
);
557 for (i
= 0; i
< num_infiles
; i
++) {
558 if (fp_list_in
[i
] != NULL
) fclose(fp_list_in
[i
]);
564 static void DelTemp(void)
567 for(a
=0;a
<num_tempfiles
;a
++) {
568 fprintf(stderr
, "%s: deleting temporary file %s\n", progname
,
570 remove(temp_file_list
[a
]);
571 Free(temp_file_list
[a
]);
573 Free(temp_file_list
);
576 static void Usage(void)
579 "Usage: %s [-o outfile] file1.log [file2.log ...]\n"
582 " -o outfile: write merged logs into file outfile\n"
583 " -v: print version\n"
584 "If there is no outfile specified output is stdout.\n\n",
588 static void ControlChandler(int x
)
591 /* the temporary files will be deleted by exit() */
595 int main(int argc
,char *argv
[])
597 int a
,b
,c
,processed_files
=0,filename_count
=0;
598 char *outfile_name
=NULL
;
605 signal(SIGINT
,ControlChandler
);
606 while ((c
= getopt(argc
, argv
, "vo:")) != -1) {
615 default: Usage();return 0;
618 if(oflag
&&vflag
){Usage();return 0;}/*both switches are used*/
620 fputs("Log Merger for the TTCN-3 Test Executor\n"
621 "Product number: " PRODUCT_NUMBER
"\n"
622 "Build date: " __DATE__
" " __TIME__
"\n"
623 "Compiled with: " C_COMPILER_VERSION
"\n\n"
624 COPYRIGHT_STRING
"\n\n", stderr
);
626 print_license_info();
633 if (!verify_license(&lstr
)) {
638 if (!check_feature(&lstr
, FEATURE_LOGFORMAT
)) {
639 fputs("The license key does not allow the merging of log files.\n",
646 argc
-=optind
-1;argv
+=optind
-1;
647 if(argc
<2){Usage();return 0;}/*executed when no input file is given*/
648 for(a
=1;a
<argc
;a
++) {/*find first file with a valid timestamp*/
649 TimeStampUsed
=GetTimeStampFormat(argv
[a
]);
650 if(TimeStampUsed
!=TSF_Undefined
)break;
652 switch(TimeStampUsed
) {
653 case TSF_Seconds
: fputs("Merging logs with timestamp "
654 "format \"seconds\" has no sense.\n", stderr
); return 0;
655 case TSF_Time
: TimeStampLength
=TIMELENGTH
;break;
656 case TSF_DateTime
: TimeStampLength
=DATETIMELENGTH
;break;
657 default: fputs("Unsupported timestamp format.\n", stderr
); return 1;
659 for(a
=1,c
=0;a
<argc
;a
++) {/*get files with valid timestamp format*/
660 b
=GetTimeStampFormat(argv
[a
]);
661 if(TimeStampUsed
==b
) {/*file conains at least one valid timestamp*/
663 name_list_in
=(char **)Realloc(name_list_in
,c
*sizeof(char *));
664 name_list_in
[c
-1] = mcopystr(argv
[a
]);
665 } else if(b
==TSF_Undefined
)/*file contains no timestamp or uses a
666 different format than the first match*/
667 fprintf(stderr
,"Warning: unknown format in %s\n",argv
[a
]);
668 else fprintf(stderr
,"Warning: format mismatch in %s\n",argv
[a
]);
671 if(num_allfiles
<1){Usage();return 0;}/*no valid log file found*/
672 if(oflag
){/*switch [-o outfile] is used -> create outfile*/
673 outfile
= fopen(outfile_name
, FOPEN_WRITE
);
675 fprintf(stderr
,"Error creating %s %s\n",outfile_name
,strerror(errno
));
682 filename_count
=num_allfiles
;start_file
=0;
683 while(num_allfiles
>0) {/*process files in name_list_in*/
684 processed_files
=OpenMaxFiles(num_allfiles
,name_list_in
+start_file
);
685 must_use_temp
=True
;/*if there are infiles remaining use tempfiles
687 if((processed_files
<2)&&(num_allfiles
>1)){fprintf(stderr
,"Error: "
688 "can not open enough files.\nMore descriptors required "
689 "(set with the command `limit descriptors\')\n");return 1;}
690 if(infiles_processed
==True
)
691 for(a
=0;a
<processed_files
;a
++) {
692 Free(EventList
[a
]->str_to_add
);
693 EventList
[a
]->str_to_add
= NULL
;
695 num_allfiles
-=processed_files
;
698 start_file
+=processed_files
;
700 must_use_temp
=False
;/*all infiles processed*/
701 /*remove temporary files used in previous step*/
702 if(infiles_processed
==True
)
703 for(a
=0;a
<filename_count
;a
++)remove(name_list_in
[a
]);
704 infiles_processed
=True
;
705 for(a
=0;a
<filename_count
;a
++)Free(name_list_in
[a
]);
707 if(num_tempfiles
==0)break;/*no more file to process*/
708 name_list_in
=temp_file_list
;/*process tempfiles*/
709 num_allfiles
=num_tempfiles
;
710 num_tempfiles
=0;temp_file_list
=NULL
;
712 check_mem_leak(progname
);