1 /*
2  *  Copyright (c) 2009-2019, Peter Haag
3  *  Copyright (c) 2004-2008, SWITCH - Teleinformatikdienste fuer Lehre und Forschung
4  *  All rights reserved.
5  *
6  *  Redistribution and use in source and binary forms, with or without
7  *  modification, are permitted provided that the following conditions are met:
8  *
9  *   * Redistributions of source code must retain the above copyright notice,
10  *     this list of conditions and the following disclaimer.
11  *   * Redistributions in binary form must reproduce the above copyright notice,
12  *     this list of conditions and the following disclaimer in the documentation
13  *     and/or other materials provided with the distribution.
14  *   * Neither the name of the author nor the names of its contributors may be
15  *     used to endorse or promote products derived from this software without
16  *     specific prior written permission.
17  *
18  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19  *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
22  *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23  *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24  *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25  *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26  *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27  *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28  *  POSSIBILITY OF SUCH DAMAGE.
29  *
30  */
31 
32 #ifdef HAVE_CONFIG_H
33 #include "config.h"
34 #endif
35 
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <sys/types.h>
40 #include <sys/stat.h>
41 #include <sys/file.h>
42 #include <sys/param.h>
43 #include <sys/uio.h>
44 #include <unistd.h>
45 #include <fcntl.h>
46 #include <errno.h>
47 #include <time.h>
48 
49 #ifdef HAVE_STDINT_H
50 #include <stdint.h>
51 #endif
52 
53 #include "util.h"
54 #include "nfstatfile.h"
55 
56 #define stat_filename ".nfstat"
57 
58 typedef struct config_def_s {
59 	char		*name;
60 	// int			type;
61 	uint64_t	*value;
62 } config_def_t;
63 
64 static dirstat_t dirstat_tmpl;
65 
66 static config_def_t config_def[] = {
67 	{ "first", 	   &dirstat_tmpl.first},
68 	{ "last",	   &dirstat_tmpl.last },
69 	{ "size",	   &dirstat_tmpl.filesize },
70 	{ "maxsize",   &dirstat_tmpl.max_size  },
71 	{ "numfiles",  &dirstat_tmpl.numfiles  },
72 	{ "lifetime",  &dirstat_tmpl.max_lifetime },
73 	{ "watermark", &dirstat_tmpl.low_water },
74 	{ "status",    &dirstat_tmpl.status },
75 	{ NULL, 	NULL },
76 };
77 
78 
79 #define STACK_BLOCK_SIZE 32
80 
81 static int	stack_max_entries = 0;
82 static dirstat_env_t *dirstat_stack = NULL;
83 
84 static const double _1K = 1024.0;
85 static const double _1M = 1024.0 * 1024.0;
86 static const double _1G = 1024.0 * 1024.0 * 1024.0;
87 static const double _1T = 1024.0 * 1024.0 * 1024.0 * 1024.0;
88 
89 static const double _1min  = 60.0;
90 static const double _1hour = 3600.0;
91 static const double _1day  = 86400.0;
92 static const double _1week = 604800.0;
93 
94 static inline uint64_t string2uint64(char *s);
95 
96 static int ParseString(char *str, char **key, char **value);
97 
98 static void VerifyStatInfo(dirstat_t *statinfo);
99 
ScaleValue(uint64_t v)100 char *ScaleValue(uint64_t v) {
101 double f = v;
102 static char s[64];
103 
104 	if ( f < _1K ) {	// 1 K 1024
105 		snprintf(s, 63, "%llu B", (unsigned long long)v);
106 	} else if ( f < _1M ) {
107 		snprintf(s, 63, "%llu = %.1f KB", (unsigned long long)v, f / _1K );
108 	} else if ( f < _1G ) {
109 		snprintf(s, 63, "%llu = %.1f MB", (unsigned long long)v, f / _1M );
110 	} else if ( f < _1T ) {
111 		snprintf(s, 63, "%llu = %.1f GB", (unsigned long long)v, f / _1G );
112 	} else {	// everything else in T
113 		snprintf(s, 63, "%llu = %.1f TB", (unsigned long long)v, f / _1T );
114 	}
115 	s[63] = '\0';
116 
117 	return s;
118 
119 } // End of ScaleValue
120 
ScaleTime(uint64_t v)121 char *ScaleTime(uint64_t v) {
122 double f = v;
123 static char s[64];
124 
125 	if ( f < _1min ) {
126 		snprintf(s, 63, "%llu sec", (unsigned long long)v);
127 	} else if ( f < _1hour ) {
128 		snprintf(s, 63, "%llu = %.1f min", (unsigned long long)v, f / _1min );
129 	} else if ( f < _1day ) {
130 		snprintf(s, 63, "%llu = %.1f hours", (unsigned long long)v, f / _1hour );
131 	} else if ( f < _1week ) {
132 		snprintf(s, 63, "%llu = %.1f days", (unsigned long long)v, f / _1day );
133 	} else {	// everything else in weeks
134 		snprintf(s, 63, "%llu = %.1f weeks", (unsigned long long)v, f / _1week );
135 	}
136 	s[63] = '\0';
137 
138 	return s;
139 
140 } // End of ScaleValue
141 
142 
string2uint64(char * s)143 static inline uint64_t string2uint64(char *s) {
144 uint64_t	u=0;
145 char 		*p = s;
146 
147 	while( *p ) {
148 		if ( *p < '0' || *p > '9' )
149 			*p = '0';
150 		u = 10LL*u + (*p++ - 48);
151 	}
152 	return u;
153 
154 } // End of string2uint64
155 
SetFileLock(int fd)156 static int SetFileLock(int fd) {
157     struct flock fl;
158 
159     fl.l_type   = F_WRLCK;  /* F_RDLCK, F_WRLCK, F_UNLCK    */
160     fl.l_whence = SEEK_SET; /* SEEK_SET, SEEK_CUR, SEEK_END */
161     fl.l_start  = 0;        /* Offset from l_whence         */
162     fl.l_len    = 0;        /* length, 0 = to EOF           */
163     fl.l_pid    = getpid(); /* our PID                      */
164 
165     return fcntl(fd, F_SETLKW, &fl);  /* F_GETLK, F_SETLK, F_SETLKW */
166 
167 } // End of SetFileLock
168 
ReleaseFileLock(int fd)169 static int ReleaseFileLock(int fd) {
170     struct flock fl;
171 
172     fl.l_type   = F_UNLCK;  /* F_RDLCK, F_WRLCK, F_UNLCK    */
173     fl.l_whence = SEEK_SET; /* SEEK_SET, SEEK_CUR, SEEK_END */
174     fl.l_start  = 0;        /* Offset from l_whence         */
175     fl.l_len    = 0;        /* length, 0 = to EOF           */
176     fl.l_pid    = getpid(); /* our PID                      */
177 
178 	return fcntl(fd, F_SETLK, &fl); /* set the region to unlocked */
179 
180 } // End of SetFileLock
181 
ParseString(char * str,char ** key,char ** value)182 static int ParseString(char *str, char **key, char **value) {
183 char *k, *v, *w;
184 
185 	k = str;
186 	v = strpbrk(str, "=");
187 	if ( !v ) {
188 		printf("Invalid config line: '%s'\n", str);
189 		*key   = NULL;
190 		*value = NULL;
191 		return 0;
192 	}
193 
194 	*v++ = '\0';
195 
196 	// strip white spaces from end of key
197 	w = strpbrk(k, " ");
198 	if ( w )
199 		*w = '\0';
200 
201 	// strip white spaces from start of value
202 	while ( *v == ' ' ) {
203 		v++;
204 	}
205 
206 	*key   = k;
207 	*value = v;
208 
209 	return 1;
210 
211 } // End of ParseString
212 
VerifyStatInfo(dirstat_t * statinfo)213 static void VerifyStatInfo(dirstat_t *statinfo) {
214 
215 	if ( ( statinfo->first == 0 ) 			  || ( statinfo->first > statinfo->last ||
216 		 ( statinfo->status > FORCE_REBUILD ) || ( statinfo->low_water > 100 ) ) )
217 		statinfo->status = FORCE_REBUILD;	// -> fishy
218 
219 } // End of VerifyStatInfo
220 
221 /*
222  * Reads the stat record from .nfstat file
223  *	dirname: 	directory to read the .nfstat file
224  *	dirstat_p:	Assign a point of the result to this pointer
225  *	lock:		READ_ONLY file is locked while reading, and unlocked and closed thereafter
226  *				CREATE_AND_LOCK if file does not exists, create it - continue as LOCK_IF_EXISTS
227  *				LOCK_IF_EXISTS: lock the file if it exists - file remains open
228  * If file does not exists, an empty record is returned.
229  */
ReadStatInfo(char * dirname,dirstat_t ** dirstat_p,int lock)230 int ReadStatInfo(char *dirname, dirstat_t **dirstat_p, int lock ) {
231 struct stat filestat;
232 char *in_buff, *s, *p, *k, *v;
233 char filename[MAXPATHLEN];
234 int fd, err, r_size, next_free;
235 
236 	*dirstat_p = NULL;
237 
238 	// if the dirstack does not exist, creat it
239 	if ( !dirstat_stack ) {
240 		int i;
241 		dirstat_stack = (dirstat_env_t *)malloc(STACK_BLOCK_SIZE * sizeof(dirstat_env_t));
242 		if ( !dirstat_stack ) {
243 			LogError("malloc() error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
244 			return ERR_FAIL;
245 		}
246 		for ( i=0; i<STACK_BLOCK_SIZE; i++ ) {
247 			dirstat_stack[i].dirstat = NULL;
248 		}
249 		stack_max_entries = STACK_BLOCK_SIZE;
250 	}
251 
252 	// search for next free slot
253 	next_free = 0;
254 	while ( next_free < stack_max_entries && (dirstat_stack[next_free].dirstat != NULL) )
255 		next_free++;
256 
257 	// if too many entries exist, expand the stack
258 	if ( next_free >= stack_max_entries ) {
259 		dirstat_env_t *tmp;
260 		int i;
261 		tmp = realloc((void *)dirstat_stack, (stack_max_entries+STACK_BLOCK_SIZE) * sizeof(dirstat_env_t));
262 		if ( !tmp ) {
263 			LogError("ralloc() error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
264 			return ERR_FAIL;
265 		}
266 		dirstat_stack = tmp;
267 		for ( i=stack_max_entries; i<stack_max_entries+STACK_BLOCK_SIZE; i++ ) {
268 			dirstat_stack[i].dirstat = NULL;
269 		}
270 		next_free = stack_max_entries;
271 		stack_max_entries += STACK_BLOCK_SIZE;
272 	}
273 
274 	dirstat_stack[next_free].dirstat = (dirstat_t *)malloc(sizeof(dirstat_t));
275 	if ( !dirstat_stack[next_free].dirstat ) {
276 		LogError("malloc() error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
277 		return ERR_FAIL;
278 	}
279 
280 	// Initialize config
281 	snprintf(filename, MAXPATHLEN-1, "%s/%s", dirname, stat_filename);
282 	filename[MAXPATHLEN-1] = '\0';
283 
284 	memset((void *)dirstat_stack[next_free].dirstat, 0, sizeof(dirstat_t));
285 	memset((void *)&dirstat_tmpl, 0, sizeof(dirstat_t));
286 	dirstat_tmpl.low_water = 95;	// defaults to 95%
287 	dirstat_tmpl.status = FORCE_REBUILD;	// in case status is not set -> fishy
288 	*dirstat_p = dirstat_stack[next_free].dirstat;
289 	dirstat_stack[next_free].fd = 0;
290 	dirstat_stack[next_free].filename = strdup(filename);
291 
292 
293 	fd =  open(filename, O_RDWR, 0);
294     if ( fd < 0 ) {
295 		if ( errno == ENOENT ) {
296 			if ( lock == READ_ONLY || lock == LOCK_IF_EXISTS) {	// no lock need
297 				return ERR_NOSTATFILE;
298 			} else {	// create the file, to and lock the file
299 				fd =  open(filename, O_RDWR|O_TRUNC|O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
300 				if ( fd < 0 ) {
301 					LogError("open() error on '%s' in %s line %d: %s\n", filename, __FILE__, __LINE__, strerror(errno) );
302 					free(dirstat_stack[next_free].dirstat);
303 					dirstat_stack[next_free].dirstat = NULL;
304 					return ERR_FAIL;
305 				}
306 				err = SetFileLock(fd);
307 				if ( err != 0 ) {
308 					LogError("ioctl(F_WRLCK) error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
309 					close(fd);
310 					free(dirstat_stack[next_free].dirstat);
311 					dirstat_stack[next_free].dirstat = NULL;
312 					return ERR_FAIL;
313 				}
314 				dirstat_stack[next_free].fd = fd;
315 				return ERR_NOSTATFILE;
316 			}
317 		} else {
318 			LogError("open() error on '%s' in %s line %d: %s\n", filename, __FILE__, __LINE__, strerror(errno) );
319 			free(dirstat_stack[next_free].dirstat);
320 			dirstat_stack[next_free].dirstat = NULL;
321 			return ERR_FAIL;
322 		}
323     }
324 
325 	err = SetFileLock(fd);
326 	if ( err != 0 ) {
327 		LogError("ioctl(F_WRLCK) error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
328 		close(fd);
329 		free(dirstat_stack[next_free].dirstat);
330 		dirstat_stack[next_free].dirstat = NULL;
331 		return ERR_FAIL;
332 	}
333 
334 	fstat(fd, &filestat);
335 	// the file is not assumed to be larger than 1MB, otherwise it is likely corrupt
336 	if ( filestat.st_size > 1024*1024 ) {
337 		LogError("File size error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
338 		ReleaseFileLock(fd);
339 		close(fd);
340 		free(dirstat_stack[next_free].dirstat);
341 		dirstat_stack[next_free].dirstat = NULL;
342 		return ERR_FAIL;
343 	}
344 
345 	in_buff = (char *)malloc(filestat.st_size+1);	// +1 for trailing '\0'
346 	if ( !in_buff ) {
347 		LogError("mallow() error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
348 		ReleaseFileLock(fd);
349 		close(fd);
350 		free(dirstat_stack[next_free].dirstat);
351 		dirstat_stack[next_free].dirstat = NULL;
352 		return ERR_FAIL;
353 	}
354 
355 	r_size = read(fd, (void *)in_buff, filestat.st_size);
356 	if ( r_size < 0 ) {
357 		LogError("read() error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
358 		ReleaseFileLock(fd);
359 		close(fd);
360 		free(in_buff);
361 		free(dirstat_stack[next_free].dirstat);
362 		dirstat_stack[next_free].dirstat = NULL;
363 		return ERR_FAIL;
364 	}
365 	in_buff[filestat.st_size] = '\0';
366 
367 	if ( r_size != filestat.st_size ) {
368 		LogError("read() requested size error in %s line %d\n", __FILE__, __LINE__);
369 		ReleaseFileLock(fd);
370 		close(fd);
371 		free(in_buff);
372 		free(dirstat_stack[next_free].dirstat);
373 		dirstat_stack[next_free].dirstat = NULL;
374 		return ERR_FAIL;
375 	}
376 
377 	if ( lock == READ_ONLY ) {
378 		ReleaseFileLock(fd);
379 		close(fd);
380 	} else {
381 		dirstat_stack[next_free].fd = fd;
382 	}
383 
384 	p = in_buff;
385 	while ( p && *p ) {
386 		if ( *p == '#' ) { // skip comments
387 			s = strpbrk(p, "\n");
388 			if ( s ) { // "\n" found - advance p
389 				*s = '\0';
390 				printf("comment: '%s'\n",p);
391 				p = s+1;
392 				continue;	// next line
393 			}
394 		}
395 
396 		// get gext key=value pair
397 		s = strpbrk(p, "\n");
398 		if ( s )
399 			*s = '\0';
400 
401 		if ( ParseString(p, &k, &v) ) {
402 			uint32_t	i;
403 			i = 0;
404 			while ( config_def[i].name ) {
405 				if ( strcmp(config_def[i].name, k) == 0 ) {
406 					*(config_def[i].value) = string2uint64(v);
407 //					printf("key: '%s', value '%s' int: %llu\n", k,v, *(config_def[i].value));
408 					break;
409 				}
410 				i++;
411 			}
412 			if ( config_def[i].name == NULL ) {
413 				printf("Invalid config key: '%s'\n", k);
414 			}
415 		}
416 		p = s;
417 		if ( p )
418 			p++;
419 	}
420 	VerifyStatInfo(&dirstat_tmpl);
421 	*dirstat_stack[next_free].dirstat = dirstat_tmpl;
422 
423 	free(in_buff);
424 	return dirstat_tmpl.status;
425 
426 } // End of ReadStatInfo
427 
WriteStatInfo(dirstat_t * dirstat)428 int WriteStatInfo(dirstat_t *dirstat) {
429 int i, index, fd, err;
430 char *filename, line[256];
431 
432 	// search for entry in dirstat stack
433 	for (i=0; dirstat_stack[i].dirstat != dirstat && i < stack_max_entries; i++ ) {}
434 
435 	if ( i >= stack_max_entries ) {
436 		LogError( "WriteStatInfo(): dirstat entry not found in %s line %d\n", __FILE__, __LINE__ );
437 		return ERR_FAIL;
438 	}
439 
440 	index = i;
441 
442 	fd = dirstat_stack[index].fd;
443 	filename = dirstat_stack[index].filename;
444 
445 	if ( fd == 0 ) {
446 		fd =  open(filename, O_RDWR|O_TRUNC|O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
447     	if ( fd < 0 ) {
448 			LogError( "open() error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
449 			return ERR_FAIL;
450     	}
451 
452 		err = SetFileLock(fd);
453 		if ( err != 0 ) {
454 			LogError( "ioctl(F_WRLCK) error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
455 			close(fd);
456 			return ERR_FAIL;
457 		}
458 	} else {
459 		err = lseek(fd, SEEK_SET, 0);
460 		if ( err == -1 ) {
461 			LogError( "lseek() error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
462 			ReleaseFileLock(fd);
463 			close(fd);
464 			return ERR_FAIL;
465 		}
466 		if ( ftruncate(fd, 0) < 0 ) {
467 			LogError( "ftruncate() error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
468 		}
469 	}
470 
471 	dirstat_tmpl = *dirstat_stack[index].dirstat;
472 	i = 0;
473 	while ( config_def[i].name ) {
474 		size_t len;
475 		snprintf(line, 255, "%s=%llu\n", config_def[i].name, (unsigned long long)*(config_def[i].value));
476 		line[255] = '\0';
477 		len = strlen(line);
478 		if ( write(fd, line, len) < 0 ) {
479 			LogError( "write() error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
480 		}
481 		i++;
482 	}
483 
484 	ReleaseFileLock(fd);
485 	err = close(fd);
486 	dirstat_stack[index].fd = 0;
487 	if ( err == -1 ) {
488 		LogError( "close() error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
489 		return ERR_FAIL;
490 	}
491 
492 	return STATFILE_OK;
493 
494 } // End of WriteStatInfo
495 
ReleaseStatInfo(dirstat_t * dirstat)496 int ReleaseStatInfo(dirstat_t *dirstat) {
497 int i, index;
498 
499 	// search for entry in dirstat stack
500 	for (i=0; dirstat_stack[i].dirstat != dirstat && i < stack_max_entries; i++ ) {}
501 
502 	if ( i >= stack_max_entries ) {
503 		LogError( "ReleaseStatInfo() error in %s line %d: %s\n", __FILE__, __LINE__, "dirstat entry not found" );
504 		return ERR_FAIL;
505 	}
506 
507 	index = i;
508 	if ( dirstat_stack[index].filename == NULL ) {
509 		LogError( "ReleaseStatInfo() error in %s line %d: %s\n", __FILE__, __LINE__, "Attempted to free NULL pointer" );
510 		return ERR_FAIL;
511 	}
512 	free(dirstat_stack[index].filename);
513 
514 	free(dirstat_stack[index].dirstat);
515 	dirstat_stack[index].dirstat = NULL;
516 
517 	return 0;
518 
519 } // End of ReleaseStatInfo
520 
PrintDirStat(dirstat_t * dirstat)521 void PrintDirStat(dirstat_t *dirstat) {
522 struct tm *ts;
523 time_t	t;
524 char	string[32];
525 
526 	t = dirstat->first;
527     ts = localtime(&t);
528     strftime(string, 31, "%Y-%m-%d %H:%M:%S", ts);
529 	string[31] = '\0';
530 	printf("First:     %s\n", string);
531 
532 	t = dirstat->last;
533     ts = localtime(&t);
534     strftime(string, 31, "%Y-%m-%d %H:%M:%S", ts);
535 	string[31] = '\0';
536 	printf("Last:      %s\n", string);
537 
538 	printf("Lifetime:  %s\n", ScaleTime(dirstat->last - dirstat->first));
539 
540 	printf("Numfiles:  %llu\n", (unsigned long long)dirstat->numfiles);
541 	printf("Filesize:  %s\n", ScaleValue(dirstat->filesize));
542 
543 	if ( dirstat->max_size )
544 		printf("Max Size:  %s\n", ScaleValue(dirstat->max_size));
545 	else
546 		printf("Max Size:  <none>\n");
547 
548 	if ( dirstat->max_lifetime )
549 		printf("Max Life:  %s\n", ScaleTime(dirstat->max_lifetime));
550 	else
551 		printf("Max Life:  <none>\n");
552 
553 	printf("Watermark: %llu%%\n", (unsigned long long)dirstat->low_water);
554 
555 	switch(dirstat->status) {
556 		case STATFILE_OK:
557 			printf("Status:    OK\n");
558 			break;
559 		case FORCE_REBUILD:
560 			printf("Status:    Force rebuild\n");
561 			break;
562 		default:
563 			printf("Status:    Unexpected: %llu\n", (unsigned long long)dirstat->status);
564 			break;
565 	}
566 } // End of PrintDirStat
567 
568 
569