/* * Copyright (c) 2016, Peter Haag * 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. * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_STDINT_H #include #endif #include "util.h" #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; 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_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 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: \n"); if ( dirstat->max_lifetime ) printf("Max Life: %s\n", ScaleTime(dirstat->max_lifetime)); else printf("Max Life: \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