1 ///////////////////////////////////////////////////////////////////////////////
2 // Copyright (c) 2000-2015 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
7 ///////////////////////////////////////////////////////////////////////////////
8 /**************************
10 written by Gabor Tatarka
11 **************************/
19 #include "../common/memory.h"
20 #include "../common/version_internal.h"
23 #include "../common/license.h"
27 /* On MinGW seeking is not working in files opened in text mode due to a
28 * "feature" in the underlying MSVCRT. So we open all files in binary mode. */
29 # define FOPEN_READ "rb"
30 # define FOPEN_WRITE "wb"
32 # define FOPEN_READ "r"
33 # define FOPEN_WRITE "w"
40 /* These lengths below represent the number of characters in the log file.
41 * No NUL terminator is included in the length. */
43 #define SECONDLENGTH 9
44 #define DATETIMELENGTH 27
45 #define MAXTIMESTAMPLENGTH 27
47 #define BUFFERSIZE 1024
48 #define YYYYMONDD 1 /* format of Date: year/month/day*/
50 static const char * const MON
[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
51 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
53 static const char *progname
;
54 static FILE *outfile
= NULL
;
56 enum TimeStampFormats
{ TSF_Undefined
= -1, TSF_Seconds
, TSF_Time
,
58 static int TimeStampLength
;
59 static enum TimeStampFormats TimeStampUsed
= TSF_Undefined
;
61 static int IsSecond(const char *str
)/*Is timestamp format Seconds*/
64 if (*str
>= '0' && *str
<= '9') str
++; /* first digit */
66 while (*str
>= '0' && *str
<= '9') str
++; /* other digits */
67 if (*str
== '.') str
++;
68 else return False
; /* '.' */
69 for (i
= 0; i
< 6; i
++, str
++)
70 if (*str
< '0' || *str
> '9') return False
; /* microseconds(6 digits) */
74 static int IsSecond2_6(const char *str
)/*does string contain sec(2).usec(6)*/
77 for(a
=0;a
<SECONDLENGTH
;a
++) {
83 if(*str
<'0'||*str
>'9')return False
;
89 static int IsTime(const char *str
)/*Is timestamp format Time*/
92 if(False
==IsSecond2_6(str
+6))return False
;
99 if(*str
<'0'||*str
>'9')return False
;
105 #ifdef YYYYMONDD /*Date format: year/month/day*/
108 #else /*Date format: day/month/year*/
113 static int IsDateTime(const char *str
)/*is timestamp format Date/Time*/
116 if(False
==IsTime(str
+12))return False
;
117 for(a
=0;a
<FIRST_LEN
;a
++) {/*YYYY or DD*/
118 if(*str
<'0'||*str
>'9')return False
;
121 if(*str
!='/')return False
;/* '/' */
123 for(a
=0,b
=0;a
<12;a
++)if(0==strncmp(str
,MON
[a
],3)){b
=1;break;}/*MON*/
126 if(*str
!='/')return False
;/* '/' */
128 for(a
=0;a
<THIRD_LEN
;a
++) {/*DD or YYYY*/
129 if(*str
<'0'||*str
>'9')return False
;
135 static int FormatMatch(const char *str
,int format
)/*does format of timestamp match format*/
139 case TSF_Undefined
:return False
;
140 case TSF_Seconds
:if(False
==IsSecond(str
))return False
;else return True
;
141 case TSF_Time
:if(False
==IsTime(str
))return False
;else return True
;
142 case TSF_DateTime
:if(False
==IsDateTime(str
))return False
;
144 default:return False
;
150 DateTime: yyyy/Mon/dd hh:mm:ss.us
154 static enum TimeStampFormats
GetTimeStampFormat(const char *filename
)
156 /*get timestamp format used in file*/
157 enum TimeStampFormats ret_val
= TSF_Undefined
;
158 char str
[MAXTIMESTAMPLENGTH
+ 1];
159 FILE *fp
= fopen(filename
, FOPEN_READ
);
161 fprintf(stderr
, "%s: warning: cannot open %s: %s\n", progname
,
162 filename
, strerror(errno
));
163 return TSF_Undefined
;
165 if (fgets(str
, sizeof(str
), fp
) != NULL
) {
166 if (IsSecond(str
)) ret_val
= TSF_Seconds
;
167 else if (IsTime(str
)) ret_val
= TSF_Time
;
168 else if (IsDateTime(str
)) ret_val
= TSF_DateTime
;
174 static char *GetComponentIdentifier(const char *path_name
)
177 size_t name_len
= strlen(path_name
);
178 size_t filename_begin
= 0, i
;
179 size_t compid_begin
, compid_end
;
181 /* find the first character of the file name */
182 for (i
= 0; i
< name_len
; i
++)
183 if (path_name
[i
] == '/') filename_begin
= i
+ 1;
184 /* fallback values if neither '-' nor '.' is found */
185 compid_begin
= filename_begin
;
186 compid_end
= name_len
;
187 /* find the last '-' character in the file name */
188 for (i
= name_len
; i
> filename_begin
; i
--)
189 if (path_name
[i
- 1] == '-') {
195 /* find the first '.' character after the '-' */
196 for (i
= compid_begin
; i
< name_len
; i
++)
197 if (path_name
[i
] == '.') {
202 /* find the last '.' in the file name */
203 for (i
= name_len
; i
> filename_begin
; i
--)
204 if (path_name
[i
- 1] == '.') {
208 /* find the last but one '.' in the file name */
209 for (i
= compid_end
; i
> filename_begin
; i
--)
210 if (path_name
[i
- 1] == '.') {
215 if (compid_end
> compid_begin
) {
216 size_t compid_len
= compid_end
- compid_begin
;
217 ret_val
= (char*)Malloc(compid_len
+ 1);
218 memcpy(ret_val
, path_name
+ compid_begin
, compid_len
);
219 ret_val
[compid_len
] = '\0';
220 } else ret_val
= NULL
;
224 static FILE *OpenTempFile(char **filename
)
228 /* Function mkstemp() is not supported on MinGW */
229 char *temp_name
= tempnam(NULL
, NULL
);
230 if (temp_name
== NULL
) {
231 fprintf(stderr
, "%s: creation of a temporary file failed: %s\n", progname
,
235 fp
= fopen(temp_name
, FOPEN_WRITE
);
237 fprintf(stderr
, "%s: opening of temporary file `%s' failed: %s\n",
238 progname
, temp_name
, strerror(errno
));
242 *filename
= mcopystr(temp_name
);
246 *filename
= mcopystr("/tmp/logmerge_XXXXXX");
247 fd
= mkstemp(*filename
);
249 fprintf(stderr
, "%s: creation of a temporary file based on template `%s' "
250 "failed: %s\n", progname
, *filename
, strerror(errno
));
254 fp
= fdopen(fd
, FOPEN_WRITE
);
256 fprintf(stderr
, "%s: system call fdopen() failed on temporary file `%s' "
257 "(file descriptor %d): %s\n", progname
, *filename
, fd
, strerror(errno
));
265 static FILE **fp_list_in
= NULL
, *fpout
;
266 static char **name_list_in
= NULL
;
267 static int fpout_is_closeable
= 0,must_use_temp
= 0;
268 static char **temp_file_list
= NULL
;
269 static int num_tempfiles
= 0, num_infiles
= 0, num_allfiles
= 0,start_file
= 0;
270 static int infiles_processed
= False
;
273 char timestamp
[MAXTIMESTAMPLENGTH
+1];/*text of timestamp*/
274 time_t sec
; /*seconds in timestamp (since 1970)*/
275 unsigned long usec
; /*microseconds in timestamp (0L..1000000L)*/
276 int wrap
;/*if current timestamp is smaller than prev. timestamp -> wrap++;*/
277 expstring_t data
;/*text of logged event*/
278 char *str_to_add
;/*part of original filename*/
279 int ignore
; /* if true -> EOF */
280 long start_line
,line_ctr
;/*line of event start (timestamp), line counter*/
283 static LogEvent
**EventList
;
285 static int OpenMaxFiles(int argc
,char *argv
[])
289 fp_list_in
=(FILE **)Realloc(fp_list_in
,(a
+1)*sizeof(FILE *));
291 fp_list_in
[a
]=fopen(argv
[a
], FOPEN_READ
);
292 if(fp_list_in
[a
]==NULL
) {
295 /* Solaris may not set errno if libc cannot create a stdio
296 stream because the underlying fd is greater than 255 */
299 /*more infiles than can be opened->close one and create a tempfile for output*/
301 Free(EventList
[--a
]->str_to_add
);
303 fclose(fp_list_in
[a
]);
304 temp_file_list
= (char**)Realloc(temp_file_list
,
305 (num_tempfiles
+ 1) * sizeof(*temp_file_list
));
306 fpout
= OpenTempFile(temp_file_list
+ num_tempfiles
);
308 fpout_is_closeable
=1;
313 fprintf(stderr
,"%s: error opening input file %s: %s\n",
314 progname
, argv
[a
], strerror(errno
));
318 EventList
=(LogEvent
**)Realloc(EventList
,
319 (a
+1)*sizeof(LogEvent
*));
320 EventList
[a
]=(LogEvent
*)Malloc(sizeof(LogEvent
));
321 if (infiles_processed
) EventList
[a
]->str_to_add
= NULL
;
323 /* cutting the component identifier portion out from the
325 EventList
[a
]->str_to_add
=
326 GetComponentIdentifier(name_list_in
[a
+ start_file
]);
328 EventList
[a
]->ignore
=True
;
329 EventList
[a
]->data
=NULL
;
330 EventList
[a
]->wrap
=0;
332 EventList
[a
]->usec
=0L;
333 EventList
[a
]->line_ctr
=1L;
334 EventList
[a
]->start_line
=1L;
340 temp_file_list
= (char**)Realloc(temp_file_list
,
341 (num_tempfiles
+ 1) * sizeof(*temp_file_list
));
342 fpout
= OpenTempFile(temp_file_list
+ num_tempfiles
);
344 fpout_is_closeable
= 1;
347 if (outfile
!=stdout
) fpout_is_closeable
= 1;
348 else fpout_is_closeable
= 0;
351 return a
;/*nr. of opened files*/
354 static void CloseAllFiles(void)
357 if (fpout_is_closeable
) fclose(fpout
);
358 for (i
= 0; i
< num_infiles
; i
++) {
359 Free(EventList
[i
]->data
);
360 Free(EventList
[i
]->str_to_add
);
368 static int EventCmp(LogEvent
*e1
,LogEvent
*e2
)
369 /*Returns: if(event1<event2)-1;
371 if(event1>event2)1;*/
373 time_t tmpsec1
,tmpsec2
;
376 if(tmpsec1
<tmpsec2
)return -1;
377 if(tmpsec1
>tmpsec2
)return 1;
378 if(e1
->usec
<e2
->usec
)return -1;
379 if(e1
->usec
>e2
->usec
)return 1;
393 static void TS2long(time_t *sec
, unsigned long *usec
, const char *TSstr
)
394 /*converts timestamp string to two long values*/
398 char *ptr
,str
[MAXTIMESTAMPLENGTH
+1];
399 strncpy(str
,TSstr
,MAXTIMESTAMPLENGTH
);
400 str
[MAXTIMESTAMPLENGTH
] = '\0';
401 /*->this way only a copy of the timestamp string will be modified*/
402 switch(TimeStampUsed
) {
404 ptr
=strpbrk(str
,".");
405 *ptr
='\0';ptr
++;*(ptr
+6)='\0';
406 *sec
=(time_t)atol(str
);
418 TM
.tm_hour
= atoi(str
);
419 TM
.tm_min
= atoi(str
+3);
420 TM
.tm_sec
= atoi(str
+6);
424 *(str
+YearOffset
+4)='\0';*(str
+MonOffset
+3)='\0';
425 *(str
+DayOffset
+2)='\0';
426 TM
.tm_year
=atoi(str
+YearOffset
)-1900;
427 for(a
=0;a
<12;a
++)if(!strcmp(MON
[a
],str
+MonOffset
)) {
430 TM
.tm_mday
=atoi(str
+DayOffset
);TM
.tm_isdst
=-1;
432 *(ptr
+2)='\0';*(ptr
+5)='\0';*(ptr
+8)='\0';
434 TM
.tm_hour
=atoi(ptr
);TM
.tm_min
=atoi(ptr
+3);
435 TM
.tm_sec
=atoi(ptr
+6);*usec
=atol(ptr
+9);
445 static int GetEvent(FILE *fp
, LogEvent
*event
)
448 unsigned long prev_usec
;
450 /* find and read timestamp */
451 if (fgets(event
->timestamp
, TimeStampLength
+ 1, fp
) == NULL
) {
452 event
->ignore
= True
;
455 event
->start_line
=event
->line_ctr
;
456 if (FormatMatch(event
->timestamp
, TimeStampUsed
)) break;
458 prev_sec
= event
->sec
;
459 prev_usec
= event
->usec
;
460 TS2long(&event
->sec
, &event
->usec
, event
->timestamp
);
461 if (event
->sec
< prev_sec
||
462 (event
->sec
== prev_sec
&& event
->usec
< prev_usec
)) {
465 event
->ignore
= False
;
468 char buf
[BUFFERSIZE
];
469 /* read the log-event */
470 if (fgets(buf
, sizeof(buf
), fp
) == NULL
) {
471 /* EOF was detected */
472 if (event
->data
== NULL
) event
->data
= mcopystr("\n");
473 else if (event
->data
[mstrlen(event
->data
) - 1] != '\n')
474 event
->data
= mputc(event
->data
, '\n');
478 if(FormatMatch(buf
,TimeStampUsed
)) {/*Did we read the next event's timestamp?*/
479 fseek(fp
, -1L * a
, SEEK_CUR
);/*"unread" next event*/
481 } else if (buf
[a
- 1] == '\n') event
->line_ctr
++;
482 event
->data
=mputstr(event
->data
, buf
);/*append buffer to event-data*/
487 static void WriteError(void)
489 fprintf(stderr
, "%s: error: writing to %s file failed: %s\n",
490 progname
, fpout
== outfile
? "output" : "temporary", strerror(errno
));
494 static void FlushEvent(LogEvent
*event
)
496 if (fputs(event
->timestamp
, fpout
) == EOF
) WriteError();
497 if (!infiles_processed
&& event
->str_to_add
!= NULL
) {
498 if (putc(' ', fpout
) == EOF
) WriteError();
499 if (fputs(event
->str_to_add
, fpout
) == EOF
) WriteError();
501 if (fputs(event
->data
, fpout
) == EOF
) WriteError();
504 event
->ignore
= True
;
507 static void ProcessOpenFiles(void)
508 /*merge all opened files to fpout (that is stdout or outfile or a tempfile),
512 for (i
= 0; i
< num_infiles
; i
++) {
513 /* read first logged event from all opened files */
514 if (!GetEvent(fp_list_in
[i
], EventList
[i
])) {
515 /* EOF or read error (e.g. file contained only one log event) */
516 fclose(fp_list_in
[i
]);
517 fp_list_in
[i
] = NULL
;
521 /* find the earliest timestamp */
523 for (i
= 0; i
< num_infiles
; i
++) {
524 if (!EventList
[i
]->ignore
&& (min_index
< 0 ||
525 EventCmp(EventList
[min_index
], EventList
[i
]) > 0))
528 if (min_index
< 0) break; /* no more events */
529 FlushEvent(EventList
[min_index
]);
530 if (fp_list_in
[min_index
] != NULL
) {
531 /* read the next event from that file */
532 EventList
[min_index
]->wrap
= 0;
533 if (!GetEvent(fp_list_in
[min_index
], EventList
[min_index
])) {
534 /*EOF or read error*/
535 fclose(fp_list_in
[min_index
]);
536 fp_list_in
[min_index
] = NULL
;
538 if (!infiles_processed
&& EventList
[min_index
]->wrap
> 0) {
539 fprintf(stderr
,"%s: warning: timestamp is in wrong order "
540 "in file %s line %ld\n", progname
,
541 name_list_in
[min_index
+ start_file
],
542 EventList
[min_index
]->start_line
);
546 for (i
= 0; i
< num_infiles
; i
++) {
547 if (fp_list_in
[i
] != NULL
) fclose(fp_list_in
[i
]);
553 static void DelTemp(void)
556 for(a
=0;a
<num_tempfiles
;a
++) {
557 fprintf(stderr
, "%s: deleting temporary file %s\n", progname
,
559 remove(temp_file_list
[a
]);
560 Free(temp_file_list
[a
]);
562 Free(temp_file_list
);
565 static void Usage(void)
568 "Usage: %s [-o outfile] file1.log [file2.log ...]\n"
571 " -o outfile: write merged logs into file outfile\n"
572 " -v: print version\n"
573 "If there is no outfile specified output is stdout.\n\n",
577 static void ControlChandler(int x
)
580 /* the temporary files will be deleted by exit() */
584 int main(int argc
,char *argv
[])
586 int a
,b
,c
,processed_files
=0,filename_count
=0;
587 char *outfile_name
=NULL
;
594 signal(SIGINT
,ControlChandler
);
595 while ((c
= getopt(argc
, argv
, "vo:")) != -1) {
604 default: Usage();return 0;
607 if(oflag
&&vflag
){Usage();return 0;}/*both switches are used*/
609 fputs("Log Merger for the TTCN-3 Test Executor\n"
610 "Product number: " PRODUCT_NUMBER
"\n"
611 "Build date: " __DATE__
" " __TIME__
"\n"
612 "Compiled with: " C_COMPILER_VERSION
"\n\n"
613 COPYRIGHT_STRING
"\n\n", stderr
);
615 print_license_info();
622 if (!verify_license(&lstr
)) {
627 if (!check_feature(&lstr
, FEATURE_LOGFORMAT
)) {
628 fputs("The license key does not allow the merging of log files.\n",
635 argc
-=optind
-1;argv
+=optind
-1;
636 if(argc
<2){Usage();return 0;}/*executed when no input file is given*/
637 for(a
=1;a
<argc
;a
++) {/*find first file with a valid timestamp*/
638 TimeStampUsed
=GetTimeStampFormat(argv
[a
]);
639 if(TimeStampUsed
!=TSF_Undefined
)break;
641 switch(TimeStampUsed
) {
642 case TSF_Seconds
: fputs("Merging logs with timestamp "
643 "format \"seconds\" has no sense.\n", stderr
); return 0;
644 case TSF_Time
: TimeStampLength
=TIMELENGTH
;break;
645 case TSF_DateTime
: TimeStampLength
=DATETIMELENGTH
;break;
646 default: fputs("Unsupported timestamp format.\n", stderr
); return 1;
648 for(a
=1,c
=0;a
<argc
;a
++) {/*get files with valid timestamp format*/
649 b
=GetTimeStampFormat(argv
[a
]);
650 if(TimeStampUsed
==b
) {/*file conains at least one valid timestamp*/
652 name_list_in
=(char **)Realloc(name_list_in
,c
*sizeof(char *));
653 name_list_in
[c
-1] = mcopystr(argv
[a
]);
654 } else if(b
==TSF_Undefined
)/*file contains no timestamp or uses a
655 different format than the first match*/
656 fprintf(stderr
,"Warning: unknown format in %s\n",argv
[a
]);
657 else fprintf(stderr
,"Warning: format mismatch in %s\n",argv
[a
]);
660 if(num_allfiles
<1){Usage();return 0;}/*no valid log file found*/
661 if(oflag
){/*switch [-o outfile] is used -> create outfile*/
662 outfile
= fopen(outfile_name
, FOPEN_WRITE
);
664 fprintf(stderr
,"Error creating %s %s\n",outfile_name
,strerror(errno
));
671 filename_count
=num_allfiles
;start_file
=0;
672 while(num_allfiles
>0) {/*process files in name_list_in*/
673 processed_files
=OpenMaxFiles(num_allfiles
,name_list_in
+start_file
);
674 must_use_temp
=True
;/*if there are infiles remaining use tempfiles
676 if((processed_files
<2)&&(num_allfiles
>1)){fprintf(stderr
,"Error: "
677 "can not open enough files.\nMore descriptors required "
678 "(set with the command `limit descriptors\')\n");return 1;}
679 if(infiles_processed
==True
)
680 for(a
=0;a
<processed_files
;a
++) {
681 Free(EventList
[a
]->str_to_add
);
682 EventList
[a
]->str_to_add
= NULL
;
684 num_allfiles
-=processed_files
;
687 start_file
+=processed_files
;
689 must_use_temp
=False
;/*all infiles processed*/
690 /*remove temporary files used in previous step*/
691 if(infiles_processed
==True
)
692 for(a
=0;a
<filename_count
;a
++)remove(name_list_in
[a
]);
693 infiles_processed
=True
;
694 for(a
=0;a
<filename_count
;a
++)Free(name_list_in
[a
]);
696 if(num_tempfiles
==0)break;/*no more file to process*/
697 name_list_in
=temp_file_list
;/*process tempfiles*/
698 num_allfiles
=num_tempfiles
;
699 num_tempfiles
=0;temp_file_list
=NULL
;
701 check_mem_leak(progname
);