nfdump/bin/nfstatfile.c
2015-10-03 14:06:34 +02:00

580 lines
16 KiB
C

/*
* Copyright (c) 2014, Peter Haag
* Copyright (c) 2009, Peter Haag
* Copyright (c) 2004-2008, SWITCH - Teleinformatikdienste fuer Lehre und Forschung
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of the author nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* $Author: haag $
*
* $Id: nfstatfile.c 39 2009-11-25 08:11:15Z haag $
*
* $LastChangedRevision: 39 $
*
*/
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <sys/param.h>
#include <sys/uio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <time.h>
#ifdef HAVE_STDINT_H
#include <stdint.h>
#endif
#include "nfstatfile.h"
#define stat_filename ".nfstat"
typedef struct config_def_s {
char *name;
// int type;
uint64_t *value;
} config_def_t;
static dirstat_t dirstat_tmpl;
static config_def_t config_def[] = {
{ "first", &dirstat_tmpl.first},
{ "last", &dirstat_tmpl.last },
{ "size", &dirstat_tmpl.filesize },
{ "maxsize", &dirstat_tmpl.max_size },
{ "numfiles", &dirstat_tmpl.numfiles },
{ "lifetime", &dirstat_tmpl.max_lifetime },
{ "watermark", &dirstat_tmpl.low_water },
{ "status", &dirstat_tmpl.status },
{ NULL, NULL },
};
#define STACK_BLOCK_SIZE 32
static int stack_max_entries = 0;
static dirstat_env_t *dirstat_stack = NULL;
static const double _1K = 1024.0;
static const double _1M = 1024.0 * 1024.0;
static const double _1G = 1024.0 * 1024.0 * 1024.0;
static const double _1T = 1024.0 * 1024.0 * 1024.0 * 1024.0;
static const double _1min = 60.0;
static const double _1hour = 3600.0;
static const double _1day = 86400.0;
static const double _1week = 604800.0;
/*
* expire.c is needed for daemon code as well as normal stdio code
* therefore a generic LogError is defined, which maps to the
* approriate logging channel - either stderr or syslog
*/
void LogError(char *format, ...);
static inline uint64_t string2uint64(char *s);
static int ParseString(char *str, char **key, char **value);
static void VerifyStatInfo(dirstat_t *statinfo);
char *ScaleValue(uint64_t v) {
double f = v;
static char s[64];
if ( f < _1K ) { // 1 K 1024
snprintf(s, 63, "%llu B", (unsigned long long)v);
} else if ( f < _1M ) {
snprintf(s, 63, "%llu = %.1f KB", (unsigned long long)v, f / _1K );
} else if ( f < _1G ) {
snprintf(s, 63, "%llu = %.1f MB", (unsigned long long)v, f / _1M );
} else if ( f < _1T ) {
snprintf(s, 63, "%llu = %.1f GB", (unsigned long long)v, f / _1G );
} else { // everything else in T
snprintf(s, 63, "%llu = %.1f TB", (unsigned long long)v, f / _1T );
}
s[63] = '\0';
return s;
} // End of ScaleValue
char *ScaleTime(uint64_t v) {
double f = v;
static char s[64];
if ( f < _1min ) {
snprintf(s, 63, "%llu sec", (unsigned long long)v);
} else if ( f < _1hour ) {
snprintf(s, 63, "%llu = %.1f min", (unsigned long long)v, f / _1min );
} else if ( f < _1day ) {
snprintf(s, 63, "%llu = %.1f hours", (unsigned long long)v, f / _1hour );
} else if ( f < _1week ) {
snprintf(s, 63, "%llu = %.1f days", (unsigned long long)v, f / _1day );
} else { // everything else in weeks
snprintf(s, 63, "%llu = %.1f weeks", (unsigned long long)v, f / _1week );
}
s[63] = '\0';
return s;
} // End of ScaleValue
static inline uint64_t string2uint64(char *s) {
uint64_t u=0;
char *p = s;
while( *p ) {
if ( *p < '0' || *p > '9' )
*p = '0';
u = 10LL*u + (*p++ - 48);
}
return u;
} // End of string2uint64
static int SetFileLock(int fd) {
struct flock fl;
fl.l_type = F_WRLCK; /* F_RDLCK, F_WRLCK, F_UNLCK */
fl.l_whence = SEEK_SET; /* SEEK_SET, SEEK_CUR, SEEK_END */
fl.l_start = 0; /* Offset from l_whence */
fl.l_len = 0; /* length, 0 = to EOF */
fl.l_pid = getpid(); /* our PID */
return fcntl(fd, F_SETLKW, &fl); /* F_GETLK, F_SETLK, F_SETLKW */
} // End of SetFileLock
static int ReleaseFileLock(int fd) {
struct flock fl;
fl.l_type = F_UNLCK; /* F_RDLCK, F_WRLCK, F_UNLCK */
fl.l_whence = SEEK_SET; /* SEEK_SET, SEEK_CUR, SEEK_END */
fl.l_start = 0; /* Offset from l_whence */
fl.l_len = 0; /* length, 0 = to EOF */
fl.l_pid = getpid(); /* our PID */
return fcntl(fd, F_SETLK, &fl); /* set the region to unlocked */
} // End of SetFileLock
static int ParseString(char *str, char **key, char **value) {
char *k, *v, *w;
k = str;
v = strpbrk(str, "=");
if ( !v ) {
printf("Invalid config line: '%s'\n", str);
*key = NULL;
*value = NULL;
return 0;
}
*v++ = '\0';
// strip white spaces from end of key
w = strpbrk(k, " ");
if ( w )
*w = '\0';
// strip white spaces from start of value
while ( *v == ' ' ) {
v++;
}
*key = k;
*value = v;
return 1;
} // End of ParseString
static void VerifyStatInfo(dirstat_t *statinfo) {
if ( ( statinfo->first == 0 ) || ( statinfo->first > statinfo->last ||
( statinfo->status > FORCE_REBUILD ) || ( statinfo->low_water > 100 ) ) )
statinfo->status = FORCE_REBUILD; // -> fishy
} // End of VerifyStatInfo
/*
* Reads the stat record from .nfstat file
* dirname: directory to read the .nfstat file
* dirstat_p: Assign a point of the result to this pointer
* lock: READ_ONLY file is locked while reading, and unlocked and closed thereafter
* CREATE_AND_LOCK if file does not exists, create it - continue as LOCK_IF_EXISTS
* LOCK_IF_EXISTS: lock the file if it exists - file remains open
* If file does not exists, an empty record is returned.
*/
int ReadStatInfo(char *dirname, dirstat_t **dirstat_p, int lock ) {
struct stat filestat;
char *in_buff, *s, *p, *k, *v;
char filename[MAXPATHLEN];
int fd, err, r_size, next_free;
*dirstat_p = NULL;
// if the dirstack does not exist, creat it
if ( !dirstat_stack ) {
int i;
dirstat_stack = (dirstat_env_t *)malloc(STACK_BLOCK_SIZE * sizeof(dirstat_env_t));
if ( !dirstat_stack ) {
LogError("malloc() error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
return ERR_FAIL;
}
for ( i=0; i<STACK_BLOCK_SIZE; i++ ) {
dirstat_stack[i].dirstat = NULL;
}
stack_max_entries = STACK_BLOCK_SIZE;
}
// search for next free slot
next_free = 0;
while ( next_free < stack_max_entries && (dirstat_stack[next_free].dirstat != NULL) )
next_free++;
// if too many entries exist, expand the stack
if ( next_free >= stack_max_entries ) {
dirstat_env_t *tmp;
int i;
tmp = realloc((void *)dirstat_stack, (stack_max_entries+STACK_BLOCK_SIZE) * sizeof(dirstat_env_t));
if ( !tmp ) {
LogError("ralloc() error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
return ERR_FAIL;
}
dirstat_stack = tmp;
for ( i=stack_max_entries; i<stack_max_entries+STACK_BLOCK_SIZE; i++ ) {
dirstat_stack[i].dirstat = NULL;
}
next_free = stack_max_entries;
stack_max_entries += STACK_BLOCK_SIZE;
}
dirstat_stack[next_free].dirstat = (dirstat_t *)malloc(sizeof(dirstat_t));
if ( !dirstat_stack[next_free].dirstat ) {
LogError("malloc() error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
return ERR_FAIL;
}
// Initialize config
snprintf(filename, MAXPATHLEN-1, "%s/%s", dirname, stat_filename);
filename[MAXPATHLEN-1] = '\0';
memset((void *)dirstat_stack[next_free].dirstat, 0, sizeof(dirstat_t));
memset((void *)&dirstat_tmpl, 0, sizeof(dirstat_t));
dirstat_tmpl.low_water = 95; // defaults to 95%
dirstat_tmpl.status = FORCE_REBUILD; // in case status is not set -> fishy
*dirstat_p = dirstat_stack[next_free].dirstat;
dirstat_stack[next_free].fd = 0;
dirstat_stack[next_free].filename = strdup(filename);
fd = open(filename, O_RDWR, 0);
if ( fd < 0 ) {
if ( errno == ENOENT ) {
if ( lock == READ_ONLY || lock == LOCK_IF_EXISTS) { // no lock need
return ERR_NOSTATFILE;
} else { // create the file, to and lock the file
fd = open(filename, O_RDWR|O_TRUNC|O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if ( fd < 0 ) {
LogError("open() error on '%s' in %s line %d: %s\n", filename, __FILE__, __LINE__, strerror(errno) );
free(dirstat_stack[next_free].dirstat);
dirstat_stack[next_free].dirstat = NULL;
return ERR_FAIL;
}
err = SetFileLock(fd);
if ( err != 0 ) {
LogError("ioctl(F_WRLCK) error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
close(fd);
free(dirstat_stack[next_free].dirstat);
dirstat_stack[next_free].dirstat = NULL;
return ERR_FAIL;
}
dirstat_stack[next_free].fd = fd;
return ERR_NOSTATFILE;
}
} else {
LogError("open() error on '%s' in %s line %d: %s\n", filename, __FILE__, __LINE__, strerror(errno) );
free(dirstat_stack[next_free].dirstat);
dirstat_stack[next_free].dirstat = NULL;
return ERR_FAIL;
}
}
err = SetFileLock(fd);
if ( err != 0 ) {
LogError("ioctl(F_WRLCK) error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
close(fd);
free(dirstat_stack[next_free].dirstat);
dirstat_stack[next_free].dirstat = NULL;
return ERR_FAIL;
}
fstat(fd, &filestat);
// the file is not assumed to be larger than 1MB, otherwise it is likely corrupt
if ( filestat.st_size > 1024*1024 ) {
LogError("File size error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
ReleaseFileLock(fd);
close(fd);
free(dirstat_stack[next_free].dirstat);
dirstat_stack[next_free].dirstat = NULL;
return ERR_FAIL;
}
in_buff = (char *)malloc(filestat.st_size+1); // +1 for trailing '\0'
if ( !in_buff ) {
LogError("mallow() error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
ReleaseFileLock(fd);
close(fd);
free(dirstat_stack[next_free].dirstat);
dirstat_stack[next_free].dirstat = NULL;
return ERR_FAIL;
}
r_size = read(fd, (void *)in_buff, filestat.st_size);
if ( r_size < 0 ) {
LogError("read() error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
ReleaseFileLock(fd);
close(fd);
free(in_buff);
free(dirstat_stack[next_free].dirstat);
dirstat_stack[next_free].dirstat = NULL;
return ERR_FAIL;
}
in_buff[filestat.st_size] = '\0';
if ( r_size != filestat.st_size ) {
LogError("read() requested size error in %s line %d\n", __FILE__, __LINE__);
ReleaseFileLock(fd);
close(fd);
free(in_buff);
free(dirstat_stack[next_free].dirstat);
dirstat_stack[next_free].dirstat = NULL;
return ERR_FAIL;
}
if ( lock == READ_ONLY ) {
ReleaseFileLock(fd);
close(fd);
} else {
dirstat_stack[next_free].fd = fd;
}
p = in_buff;
while ( p && *p ) {
if ( *p == '#' ) { // skip comments
s = strpbrk(p, "\n");
if ( s ) { // "\n" found - advance p
*s = '\0';
printf("comment: '%s'\n",p);
p = s+1;
continue; // next line
}
}
// get gext key=value pair
s = strpbrk(p, "\n");
if ( s )
*s = '\0';
if ( ParseString(p, &k, &v) ) {
uint32_t i;
i = 0;
while ( config_def[i].name ) {
if ( strcmp(config_def[i].name, k) == 0 ) {
*(config_def[i].value) = string2uint64(v);
// printf("key: '%s', value '%s' int: %llu\n", k,v, *(config_def[i].value));
break;
}
i++;
}
if ( config_def[i].name == NULL ) {
printf("Invalid config key: '%s'\n", k);
}
}
p = s;
if ( p )
p++;
}
VerifyStatInfo(&dirstat_tmpl);
*dirstat_stack[next_free].dirstat = dirstat_tmpl;
free(in_buff);
return dirstat_tmpl.status;
} // End of ReadStatInfo
int WriteStatInfo(dirstat_t *dirstat) {
int i, index, fd, err;
char *filename, line[256];
// search for entry in dirstat stack
for (i=0; dirstat_stack[i].dirstat != dirstat && i < stack_max_entries; i++ ) {}
if ( i >= stack_max_entries ) {
LogError( "WriteStatInfo(): dirstat entry not found in %s line %d\n", __FILE__, __LINE__ );
return ERR_FAIL;
}
index = i;
fd = dirstat_stack[index].fd;
filename = dirstat_stack[index].filename;
if ( fd == 0 ) {
fd = open(filename, O_RDWR|O_TRUNC|O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if ( fd < 0 ) {
LogError( "open() error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
return ERR_FAIL;
}
err = SetFileLock(fd);
if ( err != 0 ) {
LogError( "ioctl(F_WRLCK) error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
close(fd);
return ERR_FAIL;
}
} else {
err = lseek(fd, SEEK_SET, 0);
if ( err == -1 ) {
LogError( "lseek() error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
ReleaseFileLock(fd);
close(fd);
return ERR_FAIL;
}
if ( ftruncate(fd, 0) < 0 ) {
LogError( "ftruncate() error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
}
}
dirstat_tmpl = *dirstat_stack[index].dirstat;
i = 0;
while ( config_def[i].name ) {
size_t len;
snprintf(line, 255, "%s=%llu\n", config_def[i].name, (unsigned long long)*(config_def[i].value));
line[255] = '\0';
len = strlen(line);
if ( write(fd, line, len) < 0 ) {
LogError( "write() error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
}
i++;
}
ReleaseFileLock(fd);
err = close(fd);
dirstat_stack[index].fd = 0;
if ( err == -1 ) {
LogError( "close() error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
return ERR_FAIL;
}
return STATFILE_OK;
} // End of WriteStatInfo
int ReleaseStatInfo(dirstat_t *dirstat) {
int i, index;
// search for entry in dirstat stack
for (i=0; dirstat_stack[i].dirstat != dirstat && i < stack_max_entries; i++ ) {}
if ( i >= stack_max_entries ) {
LogError( "ReleaseStatInfo() error in %s line %d: %s\n", __FILE__, __LINE__, "dirstat entry not found" );
return ERR_FAIL;
}
index = i;
if ( dirstat_stack[index].filename == NULL ) {
LogError( "ReleaseStatInfo() error in %s line %d: %s\n", __FILE__, __LINE__, "Attempted to free NULL pointer" );
return ERR_FAIL;
}
free(dirstat_stack[index].filename);
free(dirstat_stack[index].dirstat);
dirstat_stack[index].dirstat = NULL;
return 0;
} // End of ReleaseStatInfo
void PrintDirStat(dirstat_t *dirstat) {
struct tm *ts;
time_t t;
char string[32];
t = dirstat->first;
ts = localtime(&t);
strftime(string, 31, "%Y-%m-%d %H:%M:%S", ts);
string[31] = '\0';
printf("First: %s\n", string);
t = dirstat->last;
ts = localtime(&t);
strftime(string, 31, "%Y-%m-%d %H:%M:%S", ts);
string[31] = '\0';
printf("Last: %s\n", string);
printf("Lifetime: %s\n", ScaleTime(dirstat->last - dirstat->first));
printf("Numfiles: %llu\n", (unsigned long long)dirstat->numfiles);
printf("Filesize: %s\n", ScaleValue(dirstat->filesize));
if ( dirstat->max_size )
printf("Max Size: %s\n", ScaleValue(dirstat->max_size));
else
printf("Max Size: <none>\n");
if ( dirstat->max_lifetime )
printf("Max Life: %s\n", ScaleTime(dirstat->max_lifetime));
else
printf("Max Life: <none>\n");
printf("Watermark: %llu%%\n", (unsigned long long)dirstat->low_water);
switch(dirstat->status) {
case STATFILE_OK:
printf("Status: OK\n");
break;
case FORCE_REBUILD:
printf("Status: Force rebuild\n");
break;
default:
printf("Status: Unexpected: %llu\n", (unsigned long long)dirstat->status);
break;
}
} // End of PrintDirStat