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