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