1 /*
2 * SARG Squid Analysis Report Generator http://sarg.sourceforge.net
3 * 1998, 2015
4 *
5 * SARG donations:
6 * please look at http://sarg.sourceforge.net/donations.php
7 * Support:
8 * http://sourceforge.net/projects/sarg/forums/forum/363374
9 * ---------------------------------------------------------------------
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
24 *
25 */
26
27 #include "include/conf.h"
28 #include "include/defs.h"
29
30 #ifdef HAVE_LSTAT
31 #define MY_LSTAT lstat
32 #else
33 #define MY_LSTAT stat
34 #endif
35
36
37 static void make_date_index(void);
38 static void make_file_index(void);
39 static void file_index_to_date_index(const char *entry);
40 static void date_index_to_file_index(const char *entry);
41
make_index(void)42 void make_index(void)
43 {
44 DIR *dirp;
45 struct dirent *direntp;
46 char wdir[MAXLEN];
47
48 if (LastLog > 0) mklastlog(outdir);
49
50 if (Index == INDEX_NO) {
51 if (snprintf(wdir,sizeof(wdir),"%s"INDEX_HTML_FILE,outdir)>=sizeof(wdir)) {
52 debuga(__FILE__,__LINE__,_("Path too long: "));
53 debuga_more("%s"INDEX_HTML_FILE,outdir);
54 exit(EXIT_FAILURE);
55 }
56 if (access(wdir, R_OK) == 0) {
57 if (unlink(wdir)) {
58 debuga(__FILE__,__LINE__,_("Cannot delete \"%s\": %s\n"),wdir,strerror(errno));
59 exit(EXIT_FAILURE);
60 }
61 }
62 return;
63 }
64
65 if (debug) {
66 // TRANSLATORS: The %s is the name of the html index file (index.html).
67 debuga(__FILE__,__LINE__,_("Making %s\n"),INDEX_HTML_FILE);
68 }
69
70 // convert any old report hierarchy
71 if ((dirp = opendir(outdir)) == NULL) {
72 debuga(__FILE__,__LINE__,_("Cannot open directory \"%s\": %s\n"),outdir,strerror(errno));
73 exit(EXIT_FAILURE);
74 }
75 while ((direntp = readdir( dirp )) != NULL) {
76 if (isdigit(direntp->d_name[0]) && isdigit(direntp->d_name[1])) {
77 if (IndexTree == INDEX_TREE_DATE)
78 file_index_to_date_index(direntp->d_name);
79 else
80 date_index_to_file_index(direntp->d_name);
81 }
82 }
83 closedir(dirp);
84
85 if (IndexTree == INDEX_TREE_DATE) {
86 make_date_index();
87 } else {
88 make_file_index();
89 }
90 }
91
92 /*!
93 * Get the effective size of a regular file or directory.
94 *
95 * \param statb The structure filled by lstat(2).
96 *
97 * \return The size occupied on the disk (more or less).
98 *
99 * The actual size occupied on disk by a file or a directory table is not a
100 * trivial computation. It must take into account sparse files, compression,
101 * deduplication and probably many more.
102 *
103 * Here, we assume the file takes a whole number of blocks (which is not the
104 * case of ReiserFS); the block size is constant (which is not the case of
105 * ZFS); every data block is stored in one individal block (no deduplication as
106 * is done by btrfs); data are not compressed (unlike ReiserFS and ZFS).
107 *
108 * As we are dealing with directories containing mostly text and a few
109 * compressed pictures, we don't worry about sparse files with lot of zeros
110 * that would take less blocks than the actual file size.
111 */
get_file_size(struct stat * statb)112 static long long int get_file_size(struct stat *statb)
113 {
114 #ifdef __linux__
115 long long int blocks;
116
117 //return(statb->st_size);//the size of the file content
118 //return(statb->st_blocks*512);//what is the purpose of this size?
119 if (statb->st_blksize==0) return(statb->st_size);
120 blocks=(statb->st_size+statb->st_blksize-1)/statb->st_blksize;
121 return(blocks*statb->st_blksize);//how many bytes occupied on disk
122 #else
123 return(statb->st_size);
124 #endif
125 }
126
127 /*!
128 * Get the size of a directory.
129 *
130 * The size is the size of the directory content excluding the directory table.
131 * The "du" tool on Linux returns the content size including the directory
132 * table.
133 *
134 * \param path The directory whose size is computed. This is a buffer that must be
135 * big enough to contains the deepest path as directory entries are appended to
136 * the string this buffer contains.
137 * \param path_size The number of bytes available in the \a path buffer.
138 *
139 * \return The number of bytes occupied by the directory content.
140 */
get_size(char * path,int path_size)141 static long long int get_size(char *path,int path_size)
142 {
143 int path_len;
144 DIR *dirp;
145 struct dirent *direntp;
146 struct stat statb;
147 int name_len;
148 long long int total_size=0;
149 char *dir_list=NULL;
150 int dir_filled=0;
151 int dir_allocated=0;
152
153 path_len=strlen(path);
154 if (path_len+2>=path_size) {
155 debuga(__FILE__,__LINE__,_("Path too long: "));
156 debuga_more("%s\n",path);
157 exit(EXIT_FAILURE);
158 }
159 if ((dirp=opendir(path))==NULL) {
160 debuga(__FILE__,__LINE__,_("Cannot open directory \"%s\": %s\n"),path,strerror(errno));
161 exit(EXIT_FAILURE);
162 }
163 path[path_len++]='/';
164 while ((direntp=readdir(dirp))!=NULL) {
165 if (direntp->d_name[0]=='.' && (direntp->d_name[1]=='\0' || (direntp->d_name[1]=='.' && direntp->d_name[2]=='\0'))) continue;
166 name_len=strlen(direntp->d_name);
167 if (path_len+name_len+1>=path_size) {
168 debuga(__FILE__,__LINE__,_("Path too long: "));
169 debuga_more("%s%s\n",path,direntp->d_name);
170 exit(EXIT_FAILURE);
171 }
172 strcpy(path+path_len,direntp->d_name);
173 if (MY_LSTAT(path,&statb) == -1) {
174 debuga(__FILE__,__LINE__,_("Failed to get the statistics of file \"%s\": %s\n"),path,strerror(errno));
175 continue;
176 }
177 if (S_ISDIR(statb.st_mode))
178 {
179 if (!dir_list || dir_filled+name_len>=dir_allocated)
180 {
181 int size=3*(name_len+1);//make room for three file names like this one
182 if (size<256) size=256;
183 dir_allocated+=size;
184 dir_list=realloc(dir_list,dir_allocated);
185 if (!dir_list) {
186 debuga(__FILE__,__LINE__,_("Not enough memory to recurse into subdirectory \"%s\"\n"),path);
187 exit(EXIT_FAILURE);
188 }
189 }
190 strcpy(dir_list+dir_filled,direntp->d_name);
191 dir_filled+=name_len+1;
192 total_size+=get_file_size(&statb);
193 }
194 else if (S_ISREG(statb.st_mode))
195 {
196 total_size+=get_file_size(&statb);
197 }
198 }
199 closedir(dirp);
200
201 if (dir_list)
202 {
203 int start=0;
204
205 while (start<dir_filled)
206 {
207 name_len=strlen(dir_list+start);
208 strcpy(path+path_len,dir_list+start);
209 total_size+=get_size(path,path_size);
210 start+=name_len+1;
211 }
212 free(dir_list);
213 }
214
215 path[path_len-1]='\0';//restore original string
216 return (total_size);
217 }
218
219 /*!
220 * Rebuild the html index file for a day when the reports are grouped in a date tree.
221 *
222 * \param monthdir The buffer containing the path where the html index file must be rebuild.
223 * The buffer must be big enough to contain the deepest path in that directory as the buffer is
224 * used to concatenate the directory entries.
225 * \param monthdir_size The size, in byte, of the \a monthdir buffer.
226 * \param order A postive number to sort the index file in positive order. A negative value sort it
227 * in decreasing order.
228 * \param yearnum The string naming the year in the date tree.
229 * \param monthnum The string naming the month in the date tree.
230 *
231 * \return The approximate size occupied by the directory.
232 */
make_date_index_day(char * monthdir,int monthdir_size,int order,const char * yearnum,const char * monthnum)233 static long long int make_date_index_day(char *monthdir,int monthdir_size,int order,const char *yearnum,const char *monthnum)
234 {
235 int monthdir_len;
236 int ndays;
237 DIR *dirp3;
238 struct dirent *direntp;
239 struct stat statb;
240 int i;
241 int daysort[31*31];
242 int d1, d2, day;
243 FILE *fp_ou;
244 char title[80];
245 char daynum[10];
246 int d;
247 long long int total_size=0;
248 long long int sub_size;
249 int name_len;
250
251 ndays=0;
252 if ((dirp3 = opendir(monthdir)) == NULL) {
253 debuga(__FILE__,__LINE__,_("Cannot open directory \"%s\": %s\n"),monthdir,strerror(errno));
254 exit(EXIT_FAILURE);
255 }
256 monthdir_len=strlen(monthdir);
257 if (monthdir_len+strlen(INDEX_HTML_FILE)+2>=monthdir_size) {
258 debuga(__FILE__,__LINE__,_("Path too long: "));
259 debuga_more("%s/%s\n",monthdir,INDEX_HTML_FILE);
260 exit(EXIT_FAILURE);
261 }
262 monthdir[monthdir_len++]='/';
263 while ((direntp = readdir( dirp3 )) != NULL) {
264 if (direntp->d_name[0]=='.' && (direntp->d_name[1]=='\0' || (direntp->d_name[1]=='.' && direntp->d_name[2]=='\0'))) continue;
265 name_len=strlen(direntp->d_name);
266 if (monthdir_len+name_len+1>=monthdir_size) {
267 debuga(__FILE__,__LINE__,_("Path too long: "));
268 debuga_more("%s%s\n",monthdir,direntp->d_name);
269 exit(EXIT_FAILURE);
270 }
271 strcpy(monthdir+monthdir_len,direntp->d_name);
272 if (MY_LSTAT(monthdir,&statb) == -1) {
273 debuga(__FILE__,__LINE__,_("Failed to get the statistics of file \"%s\": %s\n"),monthdir,strerror(errno));
274 continue;
275 }
276 if (S_ISDIR(statb.st_mode))
277 {
278 if (!isdigit(direntp->d_name[0]) && !isdigit(direntp->d_name[1])) continue;
279 i=-1;
280 if (sscanf(direntp->d_name,"%d%n",&d1,&i)!=1 || d1<1 || d1>31 || i<0) continue;
281 if (direntp->d_name[i]=='-') {
282 if (sscanf(direntp->d_name+i+1,"%d",&d2)!=1 || d2<1 || d2>31) continue;
283 } else if (direntp->d_name[i]!='\0') {
284 continue;
285 } else {
286 d2=0;
287 }
288 if (ndays>=sizeof(daysort)/sizeof(daysort[0])) {
289 debuga(__FILE__,__LINE__,_("Too many day directories in %s\nSupernumerary entries are ignored\n"),monthdir);
290 break;
291 }
292 day=(d1 << 5) | d2;
293 for (i=ndays ; i>0 && day<daysort[i-1] ; i--) {
294 daysort[i]=daysort[i-1];
295 }
296 daysort[i]=day;
297 ndays++;
298 total_size+=get_file_size(&statb);
299 }
300 else if (S_ISREG(statb.st_mode))
301 {
302 total_size+=get_file_size(&statb);
303 }
304 }
305 closedir(dirp3);
306
307 strcpy(monthdir+monthdir_len,INDEX_HTML_FILE);
308 if ((fp_ou=fopen(monthdir,"w"))==NULL) {
309 debuga(__FILE__,__LINE__,_("Cannot open file \"%s\": %s\n"),monthdir,strerror(errno));
310 exit(EXIT_FAILURE);
311 }
312 snprintf(title,sizeof(title),ngettext("SARG: report for %s/%s","SARG: reports for %s/%s",ndays),yearnum,monthnum);
313 write_html_header(fp_ou,2,title,HTML_JS_NONE);
314 close_html_header(fp_ou);
315 fputs("<div class=\"index\"><table cellpadding=\"1\" cellspacing=\"2\">\n<tr><td></td><td></td></tr>\n",fp_ou);
316 fprintf(fp_ou,"<tr><th class=\"header_l\">%s/%s/%s</th>",_("YEAR"),_("MONTH"),_("DAYS"));
317 if (IndexFields & INDEXFIELDS_DIRSIZE)
318 fprintf(fp_ou,"<th class=\"header_l\">%s</th>",_("SIZE"));
319 fputs("</tr>\n",fp_ou);
320 for (d=0 ; d<ndays ; d++) {
321 if (order>0)
322 day=daysort[d];
323 else
324 day=daysort[ndays-1-d];
325 d1=(day >> 5) & 0x1F;
326 if ((day & 0x1F) != 0) {
327 d2=day & 0x1F;
328 snprintf(daynum,sizeof(daynum),"%02d-%02d",d1,d2);
329 } else {
330 snprintf(daynum,sizeof(daynum),"%02d",d1);
331 }
332 strcpy(monthdir+monthdir_len,daynum);
333 sub_size=get_size(monthdir,monthdir_size);
334
335 fprintf(fp_ou,"<tr><td class=\"data2\"><a href=\"%s/%s\">%s %s %s</a></td>",daynum,INDEX_HTML_FILE,yearnum,monthnum,daynum);
336 if (IndexFields & INDEXFIELDS_DIRSIZE)
337 {
338 char size_str[40];
339
340 strncpy(size_str,fixnum(sub_size,1),sizeof(size_str)-1);
341 size_str[sizeof(size_str)-1]='\0';
342 fprintf(fp_ou,"<td class=\"data2\">%s</td>",size_str);
343 }
344 fputs("</tr>\n",fp_ou);
345 total_size+=sub_size;
346 }
347 fputs("</table></div>\n",fp_ou);
348 monthdir[monthdir_len-1]='\0';
349 write_html_trailer(fp_ou);
350 if (fclose(fp_ou)==EOF) {
351 strcpy(monthdir+monthdir_len,INDEX_HTML_FILE);
352 debuga(__FILE__,__LINE__,_("Write error in \"%s\": %s\n"),monthdir,strerror(errno));
353 exit(EXIT_FAILURE);
354 }
355 return(total_size);
356 }
357
358 /*!
359 * Get the name of a month based on its number.
360 *
361 * \param month The month number starting from one.
362 * \param month_name The buffer to store the month name.
363 * \param month_size The size of the \a month_name buffer.
364 */
name_month(int month,char * month_name,int month_size)365 static void name_month(int month,char *month_name,int month_size)
366 {
367 const char *m[12]={N_("January"),N_("February"),N_("March"),N_("April"),N_("May"),N_("June"),N_("July"),
368 N_("August"),N_("September"),N_("October"),N_("November"),N_("December")};
369
370 if (month<1 || month>12) {
371 debuga(__FILE__,__LINE__,_("The internal list of month names is invalid. Please report this bug to the translator.\n"));
372 exit(EXIT_FAILURE);
373 }
374 strncpy(month_name,_(m[month-1]),month_size-1);
375 month_name[month_size-1]='\0';
376 }
377
378 /*!
379 * Rebuild the html index file for a month when the reports are grouped in a date tree.
380 *
381 * \param yeardir The buffer containing the path where the html index file must be rebuild.
382 * The buffer must be big enough to contain the deepest path in that directory as the buffer is
383 * used to concatenate the directory entries.
384 * \param yeardir_size The size, in byte, of the \a yeardir buffer.
385 * \param order A postive number to sort the index file in positive order. A negative value sort it
386 * in decreasing order.
387 * \param yearnum The string naming the year in the date tree.
388 *
389 * \return The approximate size occupied by the directory.
390 */
make_date_index_month(char * yeardir,int yeardir_size,int order,const char * yearnum)391 static long long int make_date_index_month(char *yeardir,int yeardir_size,int order,const char *yearnum)
392 {
393 int yeardir_len;
394 int nmonths;
395 DIR *dirp2;
396 struct dirent *direntp;
397 struct stat statb;
398 int i;
399 int monthsort[144];
400 int m1, m2, month;
401 FILE *fp_ou;
402 char title[80];
403 char monthname1[9], monthname2[9];
404 char nmonth[30];
405 char monthnum[10];
406 int m;
407 long long int total_size=0;
408 long long int sub_size;
409 int name_len;
410
411 nmonths=0;
412 if ((dirp2 = opendir(yeardir)) == NULL) {
413 debuga(__FILE__,__LINE__,_("Cannot open directory \"%s\": %s\n"),yeardir,strerror(errno));
414 exit(EXIT_FAILURE);
415 }
416 yeardir_len=strlen(yeardir);
417 if (yeardir_len+strlen(INDEX_HTML_FILE)+2>=yeardir_size) {
418 debuga(__FILE__,__LINE__,_("Path too long: "));
419 debuga_more("%s/%s\n",yeardir,INDEX_HTML_FILE);
420 exit(EXIT_FAILURE);
421 }
422 yeardir[yeardir_len++]='/';
423 while ((direntp = readdir( dirp2 )) != NULL) {
424 if (direntp->d_name[0]=='.' && (direntp->d_name[1]=='\0' || (direntp->d_name[1]=='.' && direntp->d_name[2]=='\0'))) continue;
425 name_len=strlen(direntp->d_name);
426 if (yeardir_len+name_len+1>=yeardir_size) {
427 debuga(__FILE__,__LINE__,_("Path too long: "));
428 debuga_more("%s%s\n",yeardir,direntp->d_name);
429 exit(EXIT_FAILURE);
430 }
431 strcpy(yeardir+yeardir_len,direntp->d_name);
432 if (MY_LSTAT(yeardir,&statb) == -1) {
433 debuga(__FILE__,__LINE__,_("Failed to get the statistics of file \"%s\": %s\n"),yeardir,strerror(errno));
434 continue;
435 }
436 if (S_ISDIR(statb.st_mode))
437 {
438 if (!isdigit(direntp->d_name[0]) || !isdigit(direntp->d_name[1])) continue;
439 i=-1;
440 if (sscanf(direntp->d_name,"%d%n",&m1,&i)!=1 || m1<1 || m1>12 || i<0) continue;
441 if (direntp->d_name[i]=='-') {
442 if (sscanf(direntp->d_name+i+1,"%d",&m2)!=1 || m2<1 || m2>12) continue;
443 } else if (direntp->d_name[i]!='\0') {
444 continue;
445 } else {
446 m2=0;
447 }
448 if (nmonths>=sizeof(monthsort)/sizeof(monthsort[0])) {
449 debuga(__FILE__,__LINE__,_("Too many month directories in %s\nSupernumerary entries are ignored\n"),yeardir);
450 break;
451 }
452 month=(m1<<4) | m2;
453 for (i=nmonths ; i>0 && month<monthsort[i-1] ; i--) {
454 monthsort[i]=monthsort[i-1];
455 }
456 monthsort[i]=month;
457 nmonths++;
458 total_size+=get_file_size(&statb);
459 }
460 else if (S_ISREG(statb.st_mode))
461 {
462 total_size+=get_file_size(&statb);
463 }
464 }
465 closedir(dirp2);
466
467 strcpy(yeardir+yeardir_len,INDEX_HTML_FILE);
468 if ((fp_ou=fopen(yeardir,"w"))==NULL) {
469 debuga(__FILE__,__LINE__,_("Cannot open file \"%s\": %s\n"),yeardir,strerror(errno));
470 exit(EXIT_FAILURE);
471 }
472 snprintf(title,sizeof(title),ngettext("SARG: report for %s","SARG: reports for %s",nmonths),yearnum);
473 write_html_header(fp_ou,1,title,HTML_JS_NONE);
474 close_html_header(fp_ou);
475 fputs("<div class=\"index\"><table cellpadding=\"1\" cellspacing=\"2\">\n<tr><td></td><td></td></tr>\n",fp_ou);
476 fprintf(fp_ou,"<tr><th class=\"header_l\">%s/%s</th>",_("YEAR"),_("MONTH"));
477 if (IndexFields & INDEXFIELDS_DIRSIZE)
478 fprintf(fp_ou,"<th class=\"header_l\">%s</th>",_("SIZE"));
479 fputs("</tr>\n",fp_ou);
480 for (m=0 ; m<nmonths ; m++) {
481 if (order>0)
482 month=monthsort[m];
483 else
484 month=monthsort[nmonths-1-m];
485 m1=(month >> 4) & 0x0F;
486 if ((month & 0x0F) != 0) {
487 m2=month & 0x0F;
488 snprintf(monthnum,sizeof(monthnum),"%02d-%02d",m1,m2);
489 name_month(m1,monthname1,sizeof(monthname1));
490 name_month(m2,monthname2,sizeof(monthname2));
491 snprintf(nmonth,sizeof(nmonth),"%s-%s",monthname1,monthname2);
492 } else {
493 snprintf(monthnum,sizeof(monthnum),"%02d",m1);
494 name_month(m1,nmonth,sizeof(nmonth));
495 }
496 if (yeardir_len+strlen(monthnum)+1>=yeardir_size) {
497 yeardir[yeardir_len]='\0';
498 debuga(__FILE__,__LINE__,_("Path too long: "));
499 debuga_more("%s%s\n",yeardir,monthnum);
500 exit(EXIT_FAILURE);
501 }
502 strcpy(yeardir+yeardir_len,monthnum);
503 sub_size=make_date_index_day(yeardir,yeardir_size,order,yearnum,nmonth);
504
505 fprintf(fp_ou,"<tr><td class=\"data2\"><a href=\"%s/%s\">%s %s</a></td>",monthnum,INDEX_HTML_FILE,yearnum,nmonth);
506 if (IndexFields & INDEXFIELDS_DIRSIZE)
507 {
508 char size_str[40];
509
510 strncpy(size_str,fixnum(sub_size,1),sizeof(size_str)-1);
511 size_str[sizeof(size_str)-1]='\0';
512 fprintf(fp_ou,"<td class=\"data2\">%s</td>",size_str);
513 }
514 fputs("</tr>\n",fp_ou);
515 total_size+=sub_size;
516 }
517 fputs("</table></div>\n",fp_ou);
518 yeardir[yeardir_len-1]='\0';
519 write_html_trailer(fp_ou);
520 if (fclose(fp_ou)==EOF) {
521 strcpy(yeardir+yeardir_len,INDEX_HTML_FILE);
522 debuga(__FILE__,__LINE__,_("Write error in \"%s\": %s\n"),yeardir,strerror(errno));
523 exit(EXIT_FAILURE);
524 }
525 return(total_size);
526 }
527
528 /*!
529 * Rebuild a date index tree in the output directory.
530 */
make_date_index(void)531 static void make_date_index(void)
532 {
533 FILE *fp_ou;
534 DIR *dirp;
535 struct dirent *direntp;
536 char yearindex[MAXLEN];
537 char yeardir[MAXLEN];
538 char yearnum[10];
539 int yearsort[150];
540 int nyears;
541 int year;
542 int i, y;
543 int order;
544 int yeardirlen;
545 long long int total_size;
546
547 nyears=0;
548 if ((dirp = opendir(outdir)) == NULL) {
549 debuga(__FILE__,__LINE__,_("Cannot open directory \"%s\": %s\n"),outdir,strerror(errno));
550 exit(EXIT_FAILURE);
551 }
552 while ((direntp = readdir( dirp )) != NULL) {
553 if (!isdigit(direntp->d_name[0]) || !isdigit(direntp->d_name[1]) ||
554 !isdigit(direntp->d_name[2]) || !isdigit(direntp->d_name[3])) continue;
555 year=atoi(direntp->d_name) << 10;
556 if (direntp->d_name[4]=='-')
557 {
558 if (!isdigit(direntp->d_name[5]) || !isdigit(direntp->d_name[6]) ||
559 !isdigit(direntp->d_name[7]) || !isdigit(direntp->d_name[8])) continue;
560 if (direntp->d_name[9]) continue;
561 year|=atoi(direntp->d_name+5);
562 }
563 else
564 {
565 if (direntp->d_name[4]) continue;
566 }
567 if (nyears>=sizeof(yearsort)/sizeof(yearsort[0])) {
568 /*
569 If too many years are listed in the directory, we ignore the earliest years. The yearsort array
570 is big enough to accomodate the most ambitious use of sarg but this safety is added to prevent
571 a crash should the directory be polluted by other entries.
572 */
573 if (year>yearsort[0]) {
574 for (i=1 ; i<nyears && year>yearsort[i] ; i++)
575 yearsort[i-1]=yearsort[i];
576 yearsort[i-1]=year;
577 }
578 } else {
579 for (i=nyears ; i>0 && year<yearsort[i-1] ; i--) {
580 yearsort[i]=yearsort[i-1];
581 }
582 yearsort[i]=year;
583 nyears++;
584 }
585 }
586 closedir( dirp );
587
588 order=(strcmp(IndexSortOrder,"A") == 0) ? 1 : -1;
589
590 if (snprintf(yearindex,sizeof(yearindex),"%s"INDEX_HTML_FILE,outdir)>=sizeof(yearindex)) {
591 debuga(__FILE__,__LINE__,_("Resulting index file name too long. File name is \"%s/%s\""),outdir,INDEX_HTML_FILE);
592 exit(EXIT_FAILURE);
593 }
594 if ((fp_ou=fopen(yearindex,"w"))==NULL) {
595 debuga(__FILE__,__LINE__,_("Cannot open file \"%s\": %s\n"),yearindex,strerror(errno));
596 exit(EXIT_FAILURE);
597 }
598 write_html_header(fp_ou,0,ngettext("SARG report","SARG reports",nyears),HTML_JS_NONE);
599 close_html_header(fp_ou);
600 fputs("<div class=\"index\"><table cellpadding=\"1\" cellspacing=\"2\">\n",fp_ou);
601 fprintf(fp_ou,"<tr><th class=\"header_l\">%s</th>",_("YEAR"));
602 if (IndexFields & INDEXFIELDS_DIRSIZE)
603 fprintf(fp_ou,"<th class=\"header_l\">%s</th>",_("SIZE"));
604 fputs("</tr>\n",fp_ou);
605
606 yeardirlen=strlen(outdir);
607 if (yeardirlen>=sizeof(yeardir)) {
608 debuga(__FILE__,__LINE__,_("Path too long: "));
609 debuga_more("%s",outdir);
610 exit(EXIT_FAILURE);
611 }
612 strcpy(yeardir,outdir);
613
614 for (y=0 ; y<nyears ; y++) {
615 if (order>0)
616 year=yearsort[y];
617 else
618 year=yearsort[nyears-1-y];
619 if ((year & 0x3FF)==0)
620 snprintf(yearnum,sizeof(yearnum),"%04d",year>>10);
621 else
622 snprintf(yearnum,sizeof(yearnum),"%04d-%04d",year>>10,year & 0x3FF);
623 strcpy(yeardir+yeardirlen,yearnum);
624 total_size=make_date_index_month(yeardir,sizeof(yeardir),order,yearnum);
625
626 fprintf(fp_ou,"<tr><td class=\"data2\"><a href=\"%s/%s\">%s</a></td>",yearnum,INDEX_HTML_FILE,yearnum);
627 if (IndexFields & INDEXFIELDS_DIRSIZE)
628 {
629 char size_str[40];
630
631 strncpy(size_str,fixnum(total_size,1),sizeof(size_str)-1);
632 size_str[sizeof(size_str)-1]='\0';
633 fprintf(fp_ou,"<td class=\"data2\">%s</td>",size_str);
634 }
635 fputs("</tr>\n",fp_ou);
636 }
637
638 fputs("</table></div>\n",fp_ou);
639 write_html_trailer(fp_ou);
640 if (fclose(fp_ou)==EOF) {
641 debuga(__FILE__,__LINE__,_("Write error in \"%s\": %s\n"),yearindex,strerror(errno));
642 exit(EXIT_FAILURE);
643 }
644 }
645
make_file_index(void)646 static void make_file_index(void)
647 {
648 #define MAX_CREATION_DATE 15
649 FILE *fp_ou;
650 DIR *dirp;
651 struct dirent *direntp;
652 char wdir[MAXLEN];
653 char data[80];
654 char ftime[9];
655 char day[6], mon[8], year[40], hour[10];
656 long long int tbytes;
657 long long int media;
658 int iyear, imonth, iday, ihour, iminute, isecond, idst;
659 int nsort;
660 int nallocated;
661 int order;
662 int i;
663 int tuser;
664 struct getwordstruct gwarea;
665 struct sortstruct
666 {
667 int year, month, day, sortnum;
668 char creationdate[MAX_CREATION_DATE];
669 char *dirname;
670 char date[60];
671 } **sortlist, *item, **tempsort;
672
673 if (snprintf(wdir,sizeof(wdir),"%s"INDEX_HTML_FILE,outdir)>=sizeof(wdir)) {
674 debuga(__FILE__,__LINE__,_("Path too long: "));
675 debuga_more("%s"INDEX_HTML_FILE,outdir);
676 exit(EXIT_FAILURE);
677 }
678
679 order=(strcmp(IndexSortOrder,"A") == 0) ? 1 : -1;
680
681 if ((dirp = opendir(outdir)) == NULL) {
682 debuga(__FILE__,__LINE__,_("Cannot open directory \"%s\": %s\n"),outdir,strerror(errno));
683 exit(EXIT_FAILURE);
684 }
685
686 nsort=0;
687 nallocated=0;
688 sortlist=NULL;
689 while ((direntp = readdir( dirp )) != NULL) {
690 if (strchr(direntp->d_name,'-') == 0) continue;
691 if (obtdate(outdir,direntp->d_name,data)<0) {
692 debuga(__FILE__,__LINE__,_("The directory \"%s%s\" looks like a report directory but doesn't contain a sarg-date file. You should delete it\n"),outdir,direntp->d_name);
693 continue;
694 }
695 item=malloc(sizeof(*item));
696 if (!item) {
697 debuga(__FILE__,__LINE__,_("not enough memory to sort the index\n"));
698 exit(EXIT_FAILURE);
699 }
700 if (df=='u') {
701 item->year=atoi(direntp->d_name);
702 item->month=conv_month(direntp->d_name+4);
703 item->day=atoi(direntp->d_name+7);
704 } else {
705 item->year=atoi(direntp->d_name+5);
706 item->month=conv_month(direntp->d_name+2);
707 item->day=atoi(direntp->d_name);
708 }
709 item->sortnum=(item->year*16+item->month)*32+item->day;
710 if (sscanf(data,"%d-%d-%d %d:%d:%d %d",&iyear,&imonth,&iday,&ihour,&iminute,&isecond,&idst)==7) {
711 formatdate(data,sizeof(data),iyear,imonth,iday,ihour,iminute,isecond,idst);
712 snprintf(item->creationdate,sizeof(item->creationdate),"%04d%02d%02d%02d%02d%02d",iyear,imonth,iday,ihour,iminute,isecond);
713 } else {
714 /*
715 Old code to parse a date stored by sarg before 2.2.6.1 in the sarg-date file of each report directory.
716 */
717 getword_start(&gwarea,data);
718 if (getword_skip(16,&gwarea,' ')<0) {
719 debuga(__FILE__,__LINE__,_("Invalid date in file \"%s%s/sarg-date\"\n"),outdir,direntp->d_name);
720 exit(EXIT_FAILURE);
721 }
722 if (getword_multisep(mon,sizeof(mon),&gwarea,' ')<0) {
723 debuga(__FILE__,__LINE__,_("Invalid date in file \"%s%s/sarg-date\"\n"),outdir,direntp->d_name);
724 exit(EXIT_FAILURE);
725 }
726 if (getword_multisep(day,sizeof(day),&gwarea,' ')<0) {
727 debuga(__FILE__,__LINE__,_("Invalid date in file \"%s%s/sarg-date\"\n"),outdir,direntp->d_name);
728 exit(EXIT_FAILURE);
729 }
730 if (getword_multisep(hour,sizeof(hour),&gwarea,' ')<0) {
731 debuga(__FILE__,__LINE__,_("Invalid time in file \"%s%s/sarg-date\"\n"),outdir,direntp->d_name);
732 exit(EXIT_FAILURE);
733 }
734 do {
735 if (getword_multisep(year,sizeof(year),&gwarea,' ')<0) {
736 debuga(__FILE__,__LINE__,_("Invalid date in file \"%s%s/sarg-date\"\n"),outdir,direntp->d_name);
737 exit(EXIT_FAILURE);
738 }
739 } while (year[0] && !isdigit(year[0])); //skip time zone information with spaces until the year is found
740 if (sscanf(hour,"%d:%d:%d",&ihour,&iminute,&isecond)!=3) {
741 debuga(__FILE__,__LINE__,_("Invalid time in file \"%s%s/sarg-date\"\n"),outdir,direntp->d_name);
742 exit(EXIT_FAILURE);
743 }
744 buildymd(day,mon,year,ftime,sizeof(ftime));
745 snprintf(item->creationdate,sizeof(item->creationdate),"%s%02d%02d%02d",ftime, ihour, iminute, isecond);
746 }
747 item->dirname=strdup(direntp->d_name);
748 if (!item->dirname) {
749 debuga(__FILE__,__LINE__,_("Not enough memory to store the directory name \"%s\" in the index\n"),direntp->d_name);
750 exit(EXIT_FAILURE);
751 }
752 safe_strcpy(item->date,data,sizeof(item->date));
753 if (nsort+1>nallocated) {
754 nallocated+=10;
755 tempsort=realloc(sortlist,nallocated*sizeof(*item));
756 if (!tempsort) {
757 debuga(__FILE__,__LINE__,_("not enough memory to sort the index\n"));
758 exit(EXIT_FAILURE);
759 }
760 sortlist=tempsort;
761 }
762 for (i=nsort ; i>0 ; i--) {
763 if (item->sortnum>sortlist[i-1]->sortnum) break;
764 if (item->sortnum==sortlist[i-1]->sortnum) {
765 if (strcmp(item->creationdate,sortlist[i-1]->creationdate)>=0) break;
766 }
767 sortlist[i]=sortlist[i-1];
768 }
769 sortlist[i]=item;
770 nsort++;
771 }
772
773 closedir( dirp );
774
775 if ((fp_ou=fopen(wdir,"w"))==NULL) {
776 debuga(__FILE__,__LINE__,_("Cannot open file \"%s\": %s\n"),wdir,strerror(errno));
777 exit(EXIT_FAILURE);
778 }
779 write_html_header(fp_ou,0,ngettext("SARG report","SARG reports",nsort),HTML_JS_SORTTABLE);
780 close_html_header(fp_ou);
781 fputs("<div class=\"index\"><table cellpadding=\"1\" cellspacing=\"2\"",fp_ou);
782 if (SortTableJs[0]) fputs(" class=\"sortable\"",fp_ou);
783 fputs(">\n",fp_ou);
784 fprintf(fp_ou,"<thead><tr><th class=\"header_l\">%s</th><th class=\"header_l\">%s</th><th class=\"header_l\">%s</th><th class=\"header_l\">%s</th><th class=\"header_l\">%s</th></tr></thead>\n",
785 _("FILE/PERIOD"),_("CREATION DATE"),_("USERS"),_("BYTES"),_("AVERAGE"));
786 for (i=0 ; i<nsort ; i++) {
787 if (order>0)
788 item=sortlist[i];
789 else
790 item=sortlist[nsort-i-1];
791 tuser=obtuser(outdir,item->dirname);
792 obttotal(outdir,item->dirname,tuser,&tbytes,&media);
793 fputs("<tr><td class=\"data2\"",fp_ou);
794 if (SortTableJs[0]) fprintf(fp_ou," sorttable_customkey=\"%d\"",item->sortnum);
795 fprintf(fp_ou,"><a href='%s/%s'>%s</a></td>",item->dirname,ReplaceIndex,item->dirname);
796 fputs("<td class=\"data2\"",fp_ou);
797 if (SortTableJs[0]) fprintf(fp_ou," sorttable_customkey=\"%s\"",item->creationdate);
798 fprintf(fp_ou,">%s</td>",item->date);
799 fprintf(fp_ou,"<td class=\"data\">%d</td>",tuser);
800 fputs("<td class=\"data\"",fp_ou);
801 if (SortTableJs[0]) fprintf(fp_ou," sorttable_customkey=\"%"PRId64"\"",(int64_t)tbytes);
802 fprintf(fp_ou,">%s</td>",fixnum(tbytes,1));
803 fputs("<td class=\"data\"",fp_ou);
804 if (SortTableJs[0]) fprintf(fp_ou," sorttable_customkey=\"%"PRId64"\"",(int64_t)media);
805 fprintf(fp_ou,">%s</td></tr>\n",fixnum(media,1));
806 }
807 fputs("</table></div>\n",fp_ou);
808 write_html_trailer(fp_ou);
809 if (fclose(fp_ou)==EOF)
810 debuga(__FILE__,__LINE__,_("Write error in \"%s\": %s\n"),wdir,strerror(errno));
811
812 if (sortlist) {
813 for (i=0 ; i<nsort ; i++) {
814 free(sortlist[i]->dirname);
815 free(sortlist[i]);
816 }
817 free(sortlist);
818 }
819 }
820
file_index_to_date_index(const char * entry)821 static void file_index_to_date_index(const char *entry)
822 {
823 int y1, y2, m1, m2, d1, d2;
824 int i, j;
825 int ndirlen;
826 int monthlen;
827 char sm1[8], sm2[8];
828 char olddir[MAXLEN], newdir[MAXLEN];
829
830 if (strlen(entry) < 19) return;
831
832 y1=0;
833 y2=0;
834 memset(sm1,0,sizeof(sm1));
835 memset(sm2,0,sizeof(sm2));
836 d1=0;
837 d2=0;
838 i=0;
839 if (df=='u') {
840 for (j=0 ; entry[i] && isdigit(entry[i]) ; j++)
841 y1=y1*10+(entry[i++]-'0');
842 if (j!=4) return;
843 for (j=0 ; j<sizeof(sm1)-1 && entry[i] && isalpha(entry[i]) ; j++)
844 sm1[j]=entry[i++];
845 if (j!=3) return;
846 sm1[j]='\0';
847 for (j=0 ; entry[i] && isdigit(entry[i]) ; j++)
848 d1=d1*10+(entry[i++]-'0');
849 if (j!=2) return;
850
851 if (entry[i++]!='-') return;
852
853 for (j=0 ; entry[i] && isdigit(entry[i]) ; j++)
854 y2=y2*10+(entry[i++]-'0');
855 if (j!=4) return;
856 for (j=0 ; j<sizeof(sm2)-1 && entry[i] && isalpha(entry[i]) ; j++)
857 sm2[j]=entry[i++];
858 if (j!=3) return;
859 sm2[j]='\0';
860 for (j=0 ; entry[i] && isdigit(entry[i]) ; j++)
861 d2=d2*10+(entry[i++]-'0');
862 if (j!=2) return;
863 } else if (df=='e') {
864 for (j=0 ; entry[i] && isdigit(entry[i]) ; j++)
865 d1=d1*10+(entry[i++]-'0');
866 if (j!=2) return;
867 for (j=0 ; j<sizeof(sm1)-1 && entry[i] && isalpha(entry[i]) ; j++)
868 sm1[j]=entry[i++];
869 if (j!=3) return;
870 sm1[j]='\0';
871 for (j=0 ; entry[i] && isdigit(entry[i]) ; j++)
872 y1=y1*10+(entry[i++]-'0');
873 if (j!=4) return;
874
875 if (entry[i++]!='-') return;
876
877 for (j=0 ; entry[i] && isdigit(entry[i]) ; j++)
878 d2=d2*10+(entry[i++]-'0');
879 if (j!=2) return;
880 for (j=0 ; j<sizeof(sm2)-1 && entry[i] && isalpha(entry[i]) ; j++)
881 sm2[j]=entry[i++];
882 if (j!=3) return;
883 sm2[j]='\0';
884 for (j=0 ; entry[i] && isdigit(entry[i]) ; j++)
885 y2=y2*10+(entry[i++]-'0');
886 if (j!=4) return;
887 } else
888 return;
889
890 m1=conv_month(sm1);
891 m2=conv_month(sm2);
892 ndirlen=snprintf(newdir,sizeof(newdir),"%s%04d",outdir,y1);
893 if (ndirlen>=sizeof(newdir)) {
894 debuga(__FILE__,__LINE__,_("Path too long: "));
895 debuga_more("%s%04d",outdir,y1);
896 exit(EXIT_FAILURE);
897 }
898 if (access(newdir, R_OK) != 0) {
899 if (PortableMkDir(newdir,0755)) {
900 debuga(__FILE__,__LINE__,_("Cannot create directory \"%s\": %s\n"),newdir,strerror(errno));
901 exit(EXIT_FAILURE);
902 }
903 }
904 if (m1 != m2) ndirlen+=snprintf(newdir+ndirlen,sizeof(newdir)-ndirlen,"/%02d-%02d",m1,m2);
905 else ndirlen+=snprintf(newdir+ndirlen,sizeof(newdir)-ndirlen,"/%02d",m1);
906 if (ndirlen>=sizeof(newdir)) {
907 debuga(__FILE__,__LINE__,_("Path too long: "));
908 debuga_more("%s",newdir);
909 exit(EXIT_FAILURE);
910 }
911 if (access(newdir, R_OK) != 0) {
912 if (PortableMkDir(newdir,0755)) {
913 debuga(__FILE__,__LINE__,_("Cannot create directory \"%s\": %s\n"),newdir,strerror(errno));
914 exit(EXIT_FAILURE);
915 }
916 }
917 monthlen=ndirlen;
918 if (d1!=d2) ndirlen+=snprintf(newdir+ndirlen,sizeof(newdir)-ndirlen,"/%02d-%02d",d1,d2);
919 else ndirlen+=snprintf(newdir+ndirlen,sizeof(newdir)-ndirlen,"/%02d",d1);
920 if (ndirlen>=sizeof(newdir)) {
921 debuga(__FILE__,__LINE__,_("Path too long: "));
922 debuga_more("%s",newdir);
923 exit(EXIT_FAILURE);
924 }
925
926 if (snprintf(olddir,sizeof(olddir),"%s%s",outdir,entry)>=sizeof(olddir)) {
927 debuga(__FILE__,__LINE__,_("Path too long: "));
928 debuga_more("%s%s",outdir,entry);
929 exit(EXIT_FAILURE);
930 }
931 if (rename(olddir,newdir)) {
932 debuga(__FILE__,__LINE__,_("Error renaming \"%s\" to \"%s\": %s\n"),olddir,newdir,strerror(errno));
933 exit(EXIT_FAILURE);
934 }
935
936 strcpy(newdir+monthlen,"/images");
937 if (access(newdir, R_OK) != 0) {
938 #ifdef HAVE_SYMLINK
939 char linkdir[MAXLEN];
940
941 format_path(__FILE__, __LINE__, linkdir, sizeof(linkdir), "%simages", outdir);
942 if (symlink(linkdir,newdir)) {
943 debuga(__FILE__,__LINE__,_("Failed to create link \"%s\" to \"%s\": %s\n"),linkdir,newdir,strerror(errno));
944 exit(EXIT_FAILURE);
945 }
946 #else
947 char cmd[MAXLEN];
948 int cstatus;
949
950 sprintf(cmd,"ln -s \"%simages\" \"%s/images\"",outdir,newdir);
951 cstatus=system(cmd);
952 if (!WIFEXITED(cstatus) || WEXITSTATUS(cstatus)) {
953 debuga(__FILE__,__LINE__,_("command return status %d\n"),WEXITSTATUS(cstatus));
954 debuga(__FILE__,__LINE__,_("command: %s\n"),cmd);
955 exit(EXIT_FAILURE);
956 }
957 #endif
958 }
959 }
960
date_index_to_file_index(const char * entry)961 static void date_index_to_file_index(const char *entry)
962 {
963 int y1, next;
964 int m1, m2;
965 int d1, d2;
966 int val1len;
967 int i, j;
968 char val1[MAXLEN];
969 const char *sm1, *sm2;
970 char *str;
971 char newdir[MAXLEN], olddir[MAXLEN];
972 DIR *dirp2, *dirp3;
973 struct dirent *direntp2;
974 struct dirent *direntp3;
975
976 if (strlen(entry) != 4) return;
977
978 next=-1;
979 if (sscanf(entry,"%d%n",&y1,&next)!=1 || next<0 || entry[next]) return;
980
981 val1len=snprintf(val1,sizeof(val1),"%s%s",outdir,entry);
982 dirp2 = opendir(val1);
983 if (!dirp2) return;
984 while ((direntp2 = readdir( dirp2 )) != NULL) {
985 if (!isdigit(direntp2->d_name[0]) || !isdigit(direntp2->d_name[1])) continue;
986 i=0;
987 str=direntp2->d_name;
988 m1=0;
989 for (j=0 ; j<2 && str[i] && isdigit(str[i]) ; j++)
990 m1=(m1*10)+(str[i++]-'0');
991 if (j>=2) continue;
992 sm1=conv_month_name(m1);
993 if (str[i]=='-') {
994 i++;
995 m2=0;
996 for (j=0 ; j<2 && str[i] && isdigit(str[i]) ; j++)
997 m2=(m2*10)+(str[i++]-'0');
998 if (j>=2) continue;
999 sm2=conv_month_name(m2);
1000 } else if (!str[i]) {
1001 sm2=sm1;
1002 } else {
1003 continue;
1004 }
1005
1006 sprintf(val1+val1len,"/%s",direntp2->d_name);
1007 dirp3 = opendir(val1);
1008 if (!dirp3) continue;
1009 while ((direntp3 = readdir( dirp3 )) != NULL) {
1010 if (!isdigit(direntp3->d_name[0]) || !isdigit(direntp3->d_name[1])) continue;
1011 i=0;
1012 str=direntp3->d_name;
1013 d1=0;
1014 for (j=0 ; str[i] && isdigit(str[i]) ; j++)
1015 d1=d1*10+(str[i++]-'0');
1016 if (j!=2) continue;
1017 if (str[i]=='-') {
1018 i++;
1019 d2=0;
1020 for (j=0 ; str[i] && isdigit(str[i]) ; j++)
1021 d2=d2*10+(str[i++]-'0');
1022 if (j!=2) continue;
1023 } else if (!str[i]) {
1024 d2=d1;
1025 } else {
1026 continue;
1027 }
1028
1029 if (df=='u') {
1030 format_path(__FILE__, __LINE__, newdir, sizeof(newdir), "%s%04d%s%02d-%04d%s%02d", outdir, y1, sm1, d1, y1, sm2, d2);
1031 } else if (df=='e') {
1032 format_path(__FILE__, __LINE__, newdir, sizeof(newdir), "%s%02d%s%04d-%02d%s%04d", outdir, d1, sm1, y1, d2, sm2, y1);
1033 } else {
1034 continue;
1035 }
1036 format_path(__FILE__, __LINE__, olddir, sizeof(olddir), "%s%04d/%s/%s", outdir, y1, direntp2->d_name, direntp3->d_name);
1037 if (rename(olddir,newdir)) {
1038 debuga(__FILE__,__LINE__,_("Error renaming \"%s\" to \"%s\": %s\n"),olddir,newdir,strerror(errno));
1039 exit(EXIT_FAILURE);
1040 }
1041 }
1042 closedir(dirp3);
1043 }
1044 closedir(dirp2);
1045
1046 /*!
1047 \bug The links to the images in the reports are broken after moving the directories
1048 as the the HTML files are not at the right level for the images any more.
1049 */
1050 }
1051
1052