#include "globals.h"
#include "ini.h"
#include "../shared/log.h"
#include <string.h>
#include <stdlib.h>
#include <inttypes.h>
#include <limits.h>
#include <sys/resource.h>
#include <errno.h>
char *_configDir = NULL;
atomic_bool _shutdown = false;
// [dnbd3]
atomic_int _listenPort = PORT;
char *_basePath = NULL;
atomic_int _serverPenalty = 0;
atomic_int _clientPenalty = 0;
atomic_bool _isProxy = false;
atomic_int _backgroundReplication = BGR_FULL;
atomic_int _bgrMinClients = 0;
atomic_bool _lookupMissingForProxy = true;
atomic_bool _sparseFiles = false;
atomic_bool _removeMissingImages = true;
atomic_int _uplinkTimeout = SOCKET_TIMEOUT_UPLINK;
atomic_int _clientTimeout = SOCKET_TIMEOUT_CLIENT;
atomic_bool _closeUnusedFd = false;
atomic_bool _vmdkLegacyMode = false;
// Not really needed anymore since we have '+' and '-' in alt-servers
atomic_bool _proxyPrivateOnly = false;
// [limits]
atomic_int _maxClients = SERVER_MAX_CLIENTS;
atomic_int _maxImages = SERVER_MAX_IMAGES;
atomic_int _maxPayload = 9000000; // 9MB
atomic_uint_fast64_t _maxReplicationSize = (uint64_t)100000000000LL;
/**
* True when loading config the first time. Consecutive loads will
* ignore certain values which cannot be changed safely at runtime.
*/
static atomic_bool initialLoad = true;
static pthread_mutex_t loadLock = PTHREAD_MUTEX_INITIALIZER;
#define IS_TRUE(value) (atoi(value) != 0 || strcmp(value, "true") == 0 || strcmp(value, "True") == 0 || strcmp(value, "TRUE") == 0)
#define SAVE_TO_VAR_STR(ss, kk) do { if (strcmp(section, #ss) == 0 && strcmp(key, #kk) == 0) { if (_ ## kk != NULL) free(_ ## kk); _ ## kk = strdup(value); } } while (0)
#define SAVE_TO_VAR_BOOL(ss, kk) do { if (strcmp(section, #ss) == 0 && strcmp(key, #kk) == 0) _ ## kk = IS_TRUE(value); } while (0)
#define SAVE_TO_VAR_INT(ss, kk) do { if (strcmp(section, #ss) == 0 && strcmp(key, #kk) == 0) parse32(value, &_ ## kk, #ss); } while (0)
#define SAVE_TO_VAR_UINT(ss, kk) do { if (strcmp(section, #ss) == 0 && strcmp(key, #kk) == 0) parse32u(value, &_ ## kk, #ss); } while (0)
#define SAVE_TO_VAR_UINT64(ss, kk) do { if (strcmp(section, #ss) == 0 && strcmp(key, #kk) == 0) parse64u(value, &_ ## kk, #ss); } while (0)
static void sanitizeFixedConfig();
static void handleMaskString( const char *value, void(*func)(logmask_t) );
static const char* units = "KMGTPEZY";
static bool parse64(const char *in, atomic_int_fast64_t *out, const char *optname);
static bool parse64u(const char *in, atomic_uint_fast64_t *out, const char *optname);
static bool parse32(const char *in, atomic_int *out, const char *optname) UNUSED;
static bool parse32u(const char *in, atomic_int *out, const char *optname);
static int ini_handler(void *custom UNUSED, const char* section, const char* key, const char* value)
{
if ( initialLoad ) {
if ( _basePath == NULL ) SAVE_TO_VAR_STR( dnbd3, basePath );
SAVE_TO_VAR_BOOL( dnbd3, vmdkLegacyMode );
SAVE_TO_VAR_UINT( dnbd3, listenPort );
SAVE_TO_VAR_UINT( limits, maxClients );
SAVE_TO_VAR_UINT( limits, maxImages );
}
SAVE_TO_VAR_BOOL( dnbd3, isProxy );
SAVE_TO_VAR_BOOL( dnbd3, proxyPrivateOnly );
SAVE_TO_VAR_INT( dnbd3, bgrMinClients );
SAVE_TO_VAR_BOOL( dnbd3, lookupMissingForProxy );
SAVE_TO_VAR_BOOL( dnbd3, sparseFiles );
SAVE_TO_VAR_BOOL( dnbd3, removeMissingImages );
SAVE_TO_VAR_BOOL( dnbd3, closeUnusedFd );
SAVE_TO_VAR_UINT( dnbd3, serverPenalty );
SAVE_TO_VAR_UINT( dnbd3, clientPenalty );
SAVE_TO_VAR_UINT( dnbd3, uplinkTimeout );
SAVE_TO_VAR_UINT( dnbd3, clientTimeout );
SAVE_TO_VAR_UINT( limits, maxPayload );
SAVE_TO_VAR_UINT64( limits, maxReplicationSize );
if ( strcmp( section, "dnbd3" ) == 0 && strcmp( key, "backgroundReplication" ) == 0 ) {
if ( strcmp( value, "hashblock" ) == 0 ) {
_backgroundReplication = BGR_HASHBLOCK;
} else if ( IS_TRUE( value ) ) {
_backgroundReplication = BGR_FULL;
} else {
_backgroundReplication = BGR_DISABLED;
}
}
if ( strcmp( section, "logging" ) == 0 && strcmp( key, "fileMask" ) == 0 ) handleMaskString( value, &log_setFileMask );
if ( strcmp( section, "logging" ) == 0 && strcmp( key, "consoleMask" ) == 0 ) handleMaskString( value, &log_setConsoleMask );
if ( strcmp( section, "logging" ) == 0 && strcmp( key, "consoleTimestamps" ) == 0 ) log_setConsoleTimestamps( IS_TRUE(value) );
if ( strcmp( section, "logging" ) == 0 && strcmp( key, "file" ) == 0 ) {
if ( log_openLogFile( value ) ) {
logadd( LOG_INFO, "Opened log file %s", value );
} else {
logadd( LOG_ERROR, "Could not open log file %s", value );
exit( EXIT_FAILURE );
}
}
return 1;
}
void globals_loadConfig()
{
char *name = NULL;
asprintf( &name, "%s/%s", _configDir, CONFIG_FILENAME );
if ( name == NULL ) return;
if ( pthread_mutex_trylock( &loadLock ) != 0 ) {
logadd( LOG_INFO, "Ignoring config reload request due to already running reload" );
return;
}
ini_parse( name, &ini_handler, NULL );
free( name );
if ( initialLoad ) {
sanitizeFixedConfig();
}
if ( _backgroundReplication == BGR_FULL && _sparseFiles && _bgrMinClients < 5 ) {
logadd( LOG_WARNING, "Ignoring 'sparseFiles=true' since backgroundReplication is set to true and bgrMinClients is too low" );
_sparseFiles = false;
}
// Dump config as interpreted
char buffer[2000];
globals_dumpConfig( buffer, sizeof(buffer) );
logadd( LOG_DEBUG1, "Effective configuration:\n%s", buffer );
initialLoad = false;
pthread_mutex_unlock( &loadLock );
}
static void sanitizeFixedConfig()
{
// Validate settings after loading:
// base path for images valid?
if ( _basePath == NULL || _basePath[0] == '\0' ) {
logadd( LOG_WARNING, "No/empty basePath in " CONFIG_FILENAME );
free( _basePath );
_basePath = NULL;
} else if ( _basePath[0] != '/' ) {
logadd( LOG_WARNING, "basePath must be absolute!" );
free( _basePath );
_basePath = NULL;
} else {
char *end = _basePath + strlen( _basePath ) - 1;
while ( end >= _basePath && *end == '/' ) {
*end-- = '\0';
}
}
// listen port
if ( _listenPort < 1 || _listenPort > 65535 ) {
logadd( LOG_ERROR, "listenPort must be 1-65535, but is %d", _listenPort );
exit( EXIT_FAILURE );
}
// Cap to hard limit
if ( _maxClients > SERVER_MAX_CLIENTS ) _maxClients = SERVER_MAX_CLIENTS;
if ( _maxImages > SERVER_MAX_IMAGES ) _maxImages = SERVER_MAX_IMAGES;
// Consider rlimits
struct rlimit limit;
if ( getrlimit( RLIMIT_NOFILE, &limit ) != 0 ) {
logadd( LOG_DEBUG1, "getrlimit failed, errno %d", errno );
} else {
const rlim_t required = (rlim_t)( _maxClients + _maxImages * ( _isProxy ? 2 : 1 ) + 50 );
if ( limit.rlim_cur != RLIM_INFINITY && limit.rlim_cur < required ) {
rlim_t current = limit.rlim_cur;
if ( required <= limit.rlim_max || limit.rlim_max == RLIM_INFINITY ) {
limit.rlim_cur = required;
} else {
limit.rlim_cur = limit.rlim_max;
}
if ( current != limit.rlim_cur && setrlimit( RLIMIT_NOFILE, &limit ) == 0 ) {
current = limit.rlim_cur;
logadd( LOG_INFO, "LIMIT_NOFILE (ulimit -n) soft limit increased to %d", (int)current );
}
if ( current < required ) {
logadd( LOG_WARNING, "This process can only have %d open file handles,"
" which is not enough for the selected maxClients and maxImages counts."
" Consider increasing the limit to at least %d (RLIMIT_NOFILE, ulimit -n)"
" to support the current configuration. maxClients and maxImages have"
" been lowered for this session.", (int)current, (int)required );
do {
if ( _maxClients > 500 && _maxImages > 150 ) {
_maxImages -= _maxImages / 20 + 1;
_maxClients -= _maxClients / 20 + 1;
} else if ( _maxImages > 100 ) {
_maxImages -= _maxImages / 20 + 1;
if ( _maxClients > 200 ) _maxClients -= _maxClients / 25 + 1;
} else {
break;
}
} while ( (rlim_t)( _maxClients + _maxImages * ( _isProxy ? 2 : 1 ) + 50 ) > current );
}
}
}
}
#define SETLOGBIT(name) do { if ( strstr( value, #name ) != NULL ) mask |= LOG_ ## name; } while (0)
static void handleMaskString( const char *value, void(*func)(logmask_t) )
{
logmask_t mask = 0;
SETLOGBIT( ERROR );
SETLOGBIT( WARNING );
SETLOGBIT( MINOR );
SETLOGBIT( INFO );
SETLOGBIT( DEBUG1 );
SETLOGBIT( DEBUG2 );
(*func)( mask );
}
static bool parse64(const char *in, atomic_int_fast64_t *out, const char *optname)
{
if ( *in == '\0' ) {
logadd( LOG_WARNING, "Ignoring empty numeric setting '%s'", optname );
return false;
}
char *end;
long long int num = strtoll( in, &end, 10 );
if ( end == in ) {
logadd( LOG_WARNING, "Ignoring value '%s' for '%s': Not a number", in, optname );
return false;
}
int exp, base = 1024;
while ( *end == ' ' ) end++;
if ( *end == '\0' ) {
exp = 0;
} else {
char *pos = strchr( units, *end > 'Z' ? (*end - 32) : *end );
if ( pos == NULL ) {
logadd( LOG_ERROR, "Invalid unit '%s' for '%s'", end, optname );
return false;
}
exp = (int)( pos - units ) + 1;
end++;
if ( *end == 'B' || *end == 'b' ) {
base = 1000;
}
}
while ( exp-- > 0 ) num *= base;
*out = (int64_t)num;
return true;
}
static bool parse64u(const char *in, atomic_uint_fast64_t *out, const char *optname)
{
atomic_int_fast64_t v;
if ( !parse64( in, &v, optname ) ) return false;
if ( v < 0 ) {
logadd( LOG_WARNING, "Ignoring value '%s' for '%s': Cannot be negative", in, optname );
return false;
}
*out = (uint64_t)v;
return true;
}
static bool parse32(const char *in, atomic_int *out, const char *optname)
{
atomic_int_fast64_t v;
if ( !parse64( in, &v, optname ) ) return false;
if ( v < INT_MIN || v > INT_MAX ) {
logadd( LOG_WARNING, "'%s' must be between %d and %d, but is '%s'", optname, (int)INT_MIN, (int)INT_MAX, in );
return false;
}
*out = (int)v;
return true;
}
static bool parse32u(const char *in, atomic_int *out, const char *optname)
{
atomic_int_fast64_t v;
if ( !parse64( in, &v, optname ) ) return false;
if ( v < 0 || v > INT_MAX ) {
logadd( LOG_WARNING, "'%s' must be between %d and %d, but is '%s'", optname, (int)0, (int)INT_MAX, in );
return false;
}
*out = (int)v;
return true;
}
#define P_ARG(...) do { \
int r = snprintf(buffer, rem, __VA_ARGS__); \
if ( r < 0 || (size_t)r >= rem ) return size - 1; \
rem -= r; \
buffer += r; \
} while (0)
#define PVAR(var,type,cast) P_ARG(#var "=%" type "\n", (cast) _ ## var)
#define PINT(var) PVAR(var, "d", int)
#define PUINT64(var) PVAR(var, PRIu64, uint64_t)
#define PSTR(var) PVAR(var, "s", const char*)
#define PBOOL(var) P_ARG(#var "=%s\n", _ ## var ? "true" : "false")
size_t globals_dumpConfig(char *buffer, size_t size)
{
size_t rem = size;
P_ARG("[dnbd3]\n");
PINT(listenPort);
PSTR(basePath);
PINT(serverPenalty);
PINT(clientPenalty);
PBOOL(isProxy);
if ( _backgroundReplication == BGR_HASHBLOCK ) {
P_ARG("backgroundReplication=hashblock\n");
} else {
PBOOL(backgroundReplication);
}
PINT(bgrMinClients);
PBOOL(lookupMissingForProxy);
PBOOL(sparseFiles);
PBOOL(removeMissingImages);
PINT(uplinkTimeout);
PINT(clientTimeout);
PBOOL(closeUnusedFd);
PBOOL(vmdkLegacyMode);
PBOOL(proxyPrivateOnly);
P_ARG("[limits]\n");
PINT(maxClients);
PINT(maxImages);
PINT(maxPayload);
PUINT64(maxReplicationSize);
return size - rem;
}