/*
* Amanda, The Advanced Maryland Automatic Network Disk Archiver
* Copyright (c) 1991-1999 University of Maryland at College Park
* Copyright (c) 2007-2012 Zmanda, Inc. All Rights Reserved.
* Copyright (c) 2013-2016 Carbonite, Inc. All Rights Reserved.
* All Rights Reserved.
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation, and that the name of U.M. not be used in advertising or
* publicity pertaining to distribution of the software without specific,
* written prior permission. U.M. makes no representations about the
* suitability of this software for any purpose. It is provided "as is"
* without express or implied warranty.
*
* U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
* BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* Authors: the Amanda Development Team. Its members are listed in a
* file named AUTHORS, in the root directory of this distribution.
*/
/*
* $Id: server_util.c,v 1.17 2006/05/25 01:47:20 johnfranks Exp $
*
*/
#include "amanda.h"
#include "server_util.h"
#include "logfile.h"
#include "amutil.h"
#include "conffile.h"
#include "diskfile.h"
#include "pipespawn.h"
#include "conffile.h"
#include "infofile.h"
#include "backup_support_option.h"
#include "sys/wait.h"
const char *cmdstr[] = {
"BOGUS", "QUIT", "QUITTING", "DONE", "PARTIAL",
"START", "FILE-DUMP", "PORT-DUMP", "CONTINUE", "ABORT",/* dumper cmds */
"FAILED", "TRY-AGAIN", "NO-ROOM", "RQ-MORE-DISK", /* dumper results */
"ABORT-FINISHED", "BAD-COMMAND", /* dumper results */
"START-TAPER", "FILE-WRITE", "NEW-TAPE", "NO-NEW-TAPE",
"SHM-WRITE", "SHM-DUMP", "SHM-NAME",
"PARTDONE", "PORT-WRITE", "VAULT-WRITE", "DUMPER-STATUS", /* taper cmds */
"PORT", "TAPE-ERROR", "TAPER-OK", /* taper results */
"REQUEST-NEW-TAPE", "DIRECTTCP-PORT", "TAKE-SCRIBE-FROM",
"START-SCAN", "CLOSE-VOLUME", "CLOSED-VOLUME",
"OPENED-SOURCE-VOLUME",
"CLOSE-SOURCE-VOLUME", "CLOSED-SOURCE-VOLUME",
"RETRY", "READY", "LAST_TOK",
NULL
};
struct cmdargs *
getcmd(void)
{
char *line;
cmd_t cmd_i;
struct cmdargs *cmdargs = g_new0(struct cmdargs, 1);
if (isatty(0)) {
g_printf("%s> ", get_pname());
fflush(stdout);
line = agets(stdin);
} else {
line = areads(0);
}
if (line == NULL) {
line = g_strdup("QUIT");
}
dbprintf(_("getcmd: %s\n"), line);
cmdargs->argv = split_quoted_strings(line);
cmdargs->argc = g_strv_length(cmdargs->argv);
cmdargs->cmd = BOGUS;
amfree(line);
if (cmdargs->argc < 1) {
return cmdargs;
}
for(cmd_i=BOGUS; cmdstr[cmd_i] != NULL; cmd_i++)
if(g_str_equal(cmdargs->argv[0], cmdstr[cmd_i])) {
cmdargs->cmd = cmd_i;
return cmdargs;
}
return cmdargs;
}
struct cmdargs *
get_pending_cmd(void)
{
if (!areads_dataready(0))
return NULL;
return getcmd();
}
void
free_cmdargs(
struct cmdargs *cmdargs)
{
if (!cmdargs)
return;
if (cmdargs->argv)
g_strfreev(cmdargs->argv);
g_free(cmdargs);
}
void putresult(cmd_t result, const char *format, ...)
{
va_list argp;
char *msg;
arglist_start(argp, format);
msg = g_strdup_vprintf(format, argp);
arglist_end(argp);
g_debug("putresult: %d %s %s", result, cmdstr[result], msg);
g_printf("%s %s", cmdstr[result], msg);
fflush(stdout);
g_free(msg);
}
char *
amhost_get_security_conf(
char *string,
void *arg G_GNUC_UNUSED)
{
char *result = NULL;
disk_t *dp;
if(!string || !*string)
return(NULL);
if (g_str_equal(string, "krb5principal"))
result = getconf_str(CNF_KRB5PRINCIPAL);
else if (g_str_equal(string, "krb5keytab"))
result = getconf_str(CNF_KRB5KEYTAB);
if (result) {
if (strlen(result) == 0)
result = NULL;
return result;
}
if (!arg || !((am_host_t *)arg)->disks) return(NULL);
for (dp = ((am_host_t *)arg)->disks; dp != NULL; dp = dp->hostnext) {
if (dp->todo)
break;
}
if (!dp) return(NULL);
if (g_str_equal(string, "amandad_path"))
result = dp->amandad_path;
else if (g_str_equal(string, "client_username"))
result = dp->client_username;
else if (g_str_equal(string, "client_port"))
result = dp->client_port;
else if (g_str_equal(string, "src_ip")) {
char *result = interface_get_src_ip(((am_host_t *)arg)->netif->config);
if (g_str_equal(result, "NULL"))
result = NULL;
} else if(g_str_equal(string, "ssh_keys"))
result = dp->ssh_keys;
else if (g_str_equal(string, "ssl_fingerprint_file"))
result = dp->ssl_fingerprint_file;
else if (g_str_equal(string, "ssl_cert_file"))
result = dp->ssl_cert_file;
else if (g_str_equal(string, "ssl_key_file"))
result = dp->ssl_key_file;
else if (g_str_equal(string, "ssl_ca_cert_file"))
result = dp->ssl_ca_cert_file;
else if (g_str_equal(string, "ssl_cipher_list"))
result = dp->ssl_cipher_list;
else if (g_str_equal(string, "ssl_check_certificate_host")) {
if (dp->ssl_check_certificate_host)
result = "1";
else
result = "0";
} else if (g_str_equal(string, "ssl_check_host")) {
if (dp->ssl_check_host)
result = "1";
else
result = "0";
} else if (g_str_equal(string, "ssl_check_fingerprint")) {
if (dp->ssl_check_fingerprint)
result = "1";
else
result = "0";
}
if (result && strlen(result) == 0)
result = NULL;
return(result);
}
int check_infofile(
char *infodir,
disklist_t *dl,
char **errmsg)
{
GList *dlist, *dlist1;
disk_t *dp, *diskp;
char *hostinfodir, *old_hostinfodir;
char *diskdir, *old_diskdir;
char *infofile, *old_infofile;
struct stat statbuf;
int other_dle_match;
if (stat(infodir, &statbuf) != 0) {
return 0;
}
// for (dp = dl->head; dp != NULL; dp = dp->next) {
for (dlist = dl->head; dlist != NULL; dlist = dlist->next) {
dp = dlist->data;
hostinfodir = sanitise_filename(dp->host->hostname);
diskdir = sanitise_filename(dp->name);
infofile = g_strjoin(NULL, infodir, "/", hostinfodir, "/", diskdir,
"/info", NULL);
if (stat(infofile, &statbuf) == -1 && errno == ENOENT) {
old_hostinfodir = old_sanitise_filename(dp->host->hostname);
old_diskdir = old_sanitise_filename(dp->name);
old_infofile = g_strjoin(NULL, infodir, old_hostinfodir, "/",
old_diskdir, "/info", NULL);
if (stat(old_infofile, &statbuf) == 0) {
other_dle_match = 0;
dlist1 = dl->head;
while (dlist1 != NULL) {
char *Xhostinfodir;
char *Xdiskdir;
char *Xinfofile;
diskp = dlist1->data;
Xhostinfodir = sanitise_filename(diskp->host->hostname);
Xdiskdir = sanitise_filename(diskp->name);
Xinfofile = g_strjoin(NULL, infodir, "/", Xhostinfodir, "/",
Xdiskdir, "/info", NULL);
if (g_str_equal(old_infofile, Xinfofile)) {
other_dle_match = 1;
dlist1 = NULL;
}
else {
dlist1 = dlist1->next;
}
amfree(Xhostinfodir);
amfree(Xdiskdir);
amfree(Xinfofile);
}
if (other_dle_match == 0) {
if(mkpdir(infofile, (mode_t)0755, (uid_t)-1,
(gid_t)-1) == -1) {
*errmsg = g_strjoin(NULL, "Can't create directory for ",
infofile, NULL);
amfree(hostinfodir);
amfree(diskdir);
amfree(infofile);
amfree(old_hostinfodir);
amfree(old_diskdir);
amfree(old_infofile);
return -1;
}
if(copy_file(infofile, old_infofile, errmsg) == -1) {
amfree(hostinfodir);
amfree(diskdir);
amfree(infofile);
amfree(old_hostinfodir);
amfree(old_diskdir);
amfree(old_infofile);
return -1;
}
}
}
amfree(old_hostinfodir);
amfree(old_diskdir);
amfree(old_infofile);
}
amfree(diskdir);
amfree(hostinfodir);
amfree(infofile);
}
return 0;
}
void
run_server_script(
pp_script_t *pp_script,
execute_on_t execute_on,
char *config,
char *timestamp,
disk_t *dp,
int level)
{
pid_t scriptpid;
int scriptin, scriptout, scripterr;
char *cmd;
char *command = NULL;
GPtrArray *argv_ptr;
GPtrArray *errarray = NULL;
FILE *streamout;
char *line;
char *plugin;
char level_number[NUM_STR_SIZE];
struct stat cmd_stat;
int result;
backup_support_option_t *bsu;
if ((pp_script_get_execute_on(pp_script) & execute_on) == 0)
return;
if (pp_script_get_execute_where(pp_script) != EXECUTE_WHERE_SERVER)
return;
plugin = pp_script_get_plugin(pp_script);
cmd = g_strjoin(NULL, APPLICATION_DIR, "/", plugin, NULL);
result = stat(cmd, &cmd_stat);
if (result == -1) {
dbprintf("Can't stat script '%s': %s\n", cmd, strerror(errno));
amfree(cmd);
cmd = g_strjoin(NULL, get_config_dir(), "/application/", plugin, NULL);
result = stat(cmd, &cmd_stat);
if (result == -1) {
dbprintf("Can't stat script '%s': %s\n", cmd, strerror(errno));
amfree(cmd);
cmd = g_strjoin(NULL, CONFIG_DIR, "/application/", plugin, NULL);
result = stat(cmd, &cmd_stat);
if (result == -1) {
dbprintf("Can't stat script '%s': %s\n", cmd, strerror(errno));
amfree(cmd);
cmd = g_strjoin(NULL, APPLICATION_DIR, "/", plugin, NULL);
}
}
}
switch (execute_on) {
case EXECUTE_ON_PRE_AMCHECK:
command = "PRE-AMCHECK";
break;
case EXECUTE_ON_PRE_DLE_AMCHECK:
command = "PRE-DLE-AMCHECK";
break;
case EXECUTE_ON_PRE_HOST_AMCHECK:
command = "PRE-HOST-AMCHECK";
break;
case EXECUTE_ON_POST_AMCHECK:
command = "POST-AMCHECK";
break;
case EXECUTE_ON_POST_DLE_AMCHECK:
command = "POST-DLE-AMCHECK";
break;
case EXECUTE_ON_POST_HOST_AMCHECK:
command = "POST-HOST-AMCHECK";
break;
case EXECUTE_ON_PRE_ESTIMATE:
command = "PRE-ESTIMATE";
break;
case EXECUTE_ON_PRE_DLE_ESTIMATE:
command = "PRE-DLE-ESTIMATE";
break;
case EXECUTE_ON_PRE_HOST_ESTIMATE:
command = "PRE-HOST-ESTIMATE";
break;
case EXECUTE_ON_POST_ESTIMATE:
command = "POST-ESTIMATE";
break;
case EXECUTE_ON_POST_DLE_ESTIMATE:
command = "POST-DLE-ESTIMATE";
break;
case EXECUTE_ON_POST_HOST_ESTIMATE:
command = "POST-HOST-ESTIMATE";
break;
case EXECUTE_ON_PRE_BACKUP:
command = "PRE-BACKUP";
break;
case EXECUTE_ON_PRE_DLE_BACKUP:
command = "PRE-DLE-BACKUP";
break;
case EXECUTE_ON_PRE_HOST_BACKUP:
command = "PRE-HOST-BACKUP";
break;
case EXECUTE_ON_POST_BACKUP:
command = "POST-BACKUP";
break;
case EXECUTE_ON_POST_DLE_BACKUP:
command = "POST-DLE-BACKUP";
break;
case EXECUTE_ON_POST_HOST_BACKUP:
command = "POST-HOST-BACKUP";
break;
case EXECUTE_ON_PRE_RECOVER:
case EXECUTE_ON_POST_RECOVER:
case EXECUTE_ON_PRE_LEVEL_RECOVER:
case EXECUTE_ON_POST_LEVEL_RECOVER:
case EXECUTE_ON_INTER_LEVEL_RECOVER:
{
// ERROR these script can't be executed on server.
amfree(cmd);
return;
}
default:
amfree(cmd);
return;
}
bsu = backup_support_option(plugin, &errarray);
if (!bsu) {
guint i;
for (i=0; i < errarray->len; i++) {
char *line = g_ptr_array_index(errarray, i);
g_debug("Script: '%s': %s", plugin, line);
}
if (i == 0) { /* nothing in errarray */
g_debug("Script: '%s': cannot execute support command", plugin);
}
g_ptr_array_free_full(errarray);
}
argv_ptr = g_ptr_array_new();
g_ptr_array_add(argv_ptr, g_strdup(plugin));
g_ptr_array_add(argv_ptr, g_strdup(command));
if (!bsu || bsu->execute_where) {
g_ptr_array_add(argv_ptr, g_strdup("--execute-where"));
g_ptr_array_add(argv_ptr, g_strdup("server"));
}
if (config && (!bsu || bsu->config)) {
g_ptr_array_add(argv_ptr, g_strdup("--config"));
g_ptr_array_add(argv_ptr, g_strdup(config));
}
if (timestamp && (!bsu || bsu->timestamp)) {
g_ptr_array_add(argv_ptr, g_strdup("--timestamp"));
g_ptr_array_add(argv_ptr, g_strdup(timestamp));
}
if (dp->host->hostname && (!bsu || bsu->host)) {
g_ptr_array_add(argv_ptr, g_strdup("--host"));
g_ptr_array_add(argv_ptr, g_strdup(dp->host->hostname));
}
if (dp->name && (!bsu || bsu->disk)) {
g_ptr_array_add(argv_ptr, g_strdup("--disk"));
g_ptr_array_add(argv_ptr, g_strdup(dp->name));
}
if (dp->device) {
g_ptr_array_add(argv_ptr, g_strdup("--device"));
g_ptr_array_add(argv_ptr, g_strdup(dp->device));
}
if (level >= 0) {
g_snprintf(level_number, sizeof(level_number), "%d", level);
g_ptr_array_add(argv_ptr, g_strdup("--level"));
g_ptr_array_add(argv_ptr, g_strdup(level_number));
}
property_add_to_argv(argv_ptr, pp_script_get_property(pp_script));
g_ptr_array_add(argv_ptr, NULL);
scripterr = fileno(stderr);
scriptpid = pipespawnv(cmd, STDIN_PIPE|STDOUT_PIPE, 0, &scriptin,
&scriptout, &scripterr,
(char **)argv_ptr->pdata);
close(scriptin);
streamout = fdopen(scriptout, "r");
if (streamout) {
while((line = agets(streamout)) != NULL) {
dbprintf("script: %s\n", line);
amfree(line);
}
fclose(streamout);
}
waitpid(scriptpid, NULL, 0);
g_ptr_array_free_full(argv_ptr);
amfree(cmd);
g_free(bsu);
}
void
run_server_dle_scripts(
execute_on_t execute_on,
char *config,
char *timestamp,
disk_t *dp,
int level)
{
identlist_t pp_scriptlist;
for (pp_scriptlist = dp->pp_scriptlist; pp_scriptlist != NULL;
pp_scriptlist = pp_scriptlist->next) {
pp_script_t *pp_script = lookup_pp_script((char *)pp_scriptlist->data);
g_assert(pp_script != NULL);
run_server_script(pp_script, execute_on, config, timestamp, dp, level);
}
}
void
run_server_host_scripts(
execute_on_t execute_on,
char *config,
char *timestamp,
am_host_t *hostp)
{
identlist_t pp_scriptlist;
disk_t *dp;
GHashTable* executed = g_hash_table_new_full(g_str_hash, g_str_equal,
NULL, NULL);
for (dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
if (dp->todo) {
for (pp_scriptlist = dp->pp_scriptlist; pp_scriptlist != NULL;
pp_scriptlist = pp_scriptlist->next) {
int todo = 1;
pp_script_t *pp_script = lookup_pp_script((char *)pp_scriptlist->data);
g_assert(pp_script != NULL);
if (pp_script_get_single_execution(pp_script)) {
todo = g_hash_table_lookup(executed,
pp_script_get_plugin(pp_script))
== NULL;
}
if (todo) {
run_server_script(pp_script, execute_on, config, timestamp,
dp, -1);
if (pp_script_get_single_execution(pp_script)) {
g_hash_table_insert(executed,
pp_script_get_plugin(pp_script),
GINT_TO_POINTER(1));
}
}
}
}
}
g_hash_table_destroy(executed);
}
void
run_server_global_scripts(
execute_on_t execute_on,
char *config,
char *timestamp)
{
identlist_t pp_scriptlist;
disk_t *dp;
am_host_t *host;
GHashTable* executed = g_hash_table_new_full(g_str_hash, g_str_equal,
NULL, NULL);
for (host = get_hostlist(); host != NULL; host = host->next) {
for (dp = host->disks; dp != NULL; dp = dp->hostnext) {
if (dp->todo) {
for (pp_scriptlist = dp->pp_scriptlist; pp_scriptlist != NULL;
pp_scriptlist = pp_scriptlist->next) {
int todo = 1;
pp_script_t *pp_script =
lookup_pp_script((char *)pp_scriptlist->data);
g_assert(pp_script != NULL);
if (pp_script_get_single_execution(pp_script)) {
todo = g_hash_table_lookup(executed,
pp_script_get_plugin(pp_script)) == NULL;
}
if (todo) {
run_server_script(pp_script, execute_on, config,
timestamp, dp, -1);
if (pp_script_get_single_execution(pp_script)) {
g_hash_table_insert(executed,
pp_script_get_plugin(pp_script),
GINT_TO_POINTER(1));
}
}
}
}
}
}
g_hash_table_destroy(executed);
}
void
run_amcleanup(
char *config_name)
{
pid_t amcleanup_pid;
char *amcleanup_program;
char *amcleanup_options[4];
char **env;
switch(amcleanup_pid = fork()) {
case -1:
return;
break;
case 0: /* child process */
amcleanup_program = g_strjoin(NULL, sbindir, "/", "amcleanup", NULL);
amcleanup_options[0] = amcleanup_program;
amcleanup_options[1] = "-p";
amcleanup_options[2] = config_name;
amcleanup_options[3] = NULL;
env = safe_env();
execve(amcleanup_program, amcleanup_options, env);
free_env(env);
error("exec %s: %s", amcleanup_program, strerror(errno));
/*NOTREACHED*/
default:
break;
}
waitpid(amcleanup_pid, NULL, 0);
}
char *
get_master_process(
char *logfile)
{
FILE *log;
char line[1024];
char *s, ch;
char *process_name;
log = fopen(logfile, "r");
if (!log)
return g_strdup("UNKNOWN");
while (untaint_fgets(line, 1024, log)) {
if (strncmp_const(line, "INFO ") == 0) {
s = line+5;
ch = *s++;
process_name = s-1;
skip_non_whitespace(s, ch);
s[-1] = '\0';
skip_whitespace(s, ch);
skip_non_whitespace(s, ch);
s[-1] = '\0';
skip_whitespace(s, ch);
if (strncmp_const(s-1, "pid ") == 0) {
process_name = g_strdup(process_name);
fclose(log);
return process_name;
}
}
}
fclose(log);
return g_strdup("UNKNOWN");
}
gint64
internal_server_estimate(
disk_t *dp,
info_t *info,
int level,
int *stats,
tapetype_t *tapetype)
{
int j;
gint64 size = 0;
*stats = 0;
if (level == 0) { /* use latest level 0, should do extrapolation */
gint64 est_size = (gint64)0;
int nb_est = 0;
for (j=NB_HISTORY-2; j>=0; j--) {
if (info->history[j].level == 0) {
if (info->history[j].size <= (gint64)0) continue;
est_size = info->history[j].size;
nb_est++;
}
}
if (nb_est > 0) {
size = est_size;
*stats = 1;
} else if (info->inf[level].size > (gint64)1000) { /* stats */
size = info->inf[level].size;
*stats = 1;
} else {
size = (gint64)1000000;
if (size > tapetype_get_length(tapetype)/2)
size = tapetype_get_length(tapetype)/2;
*stats = 0;
}
} else if (level == info->last_level) {
/* means of all X day at the same level */
#define NB_DAY 30
int nb_day = 0;
gint64 est_size_day[NB_DAY];
int nb_est_day[NB_DAY];
for (j=0; j<NB_DAY; j++) {
est_size_day[j] = (gint64)0;
nb_est_day[j] = 0;
}
for (j=NB_HISTORY-2; j>=0; j--) {
if (info->history[j].level <= 0) continue;
if (info->history[j].size <= (gint64)0) continue;
if (info->history[j].level == info->history[j+1].level) {
if (nb_day <NB_DAY-1) nb_day++;
est_size_day[nb_day] += info->history[j].size;
nb_est_day[nb_day]++;
} else {
nb_day=0;
}
}
nb_day = info->consecutive_runs + 1;
if (nb_day > NB_DAY-1) nb_day = NB_DAY-1;
while (nb_day > 0 && nb_est_day[nb_day] == 0) nb_day--;
if (nb_est_day[nb_day] > 0) {
size = est_size_day[nb_day] / (gint64)nb_est_day[nb_day];
*stats = 1;
}
else if (info->inf[level].size > (gint64)1000) { /* stats */
size = info->inf[level].size;
*stats = 1;
}
else {
int level0_stat;
gint64 level0_size;
level0_size = internal_server_estimate(dp, info, 0, &level0_stat,
tapetype);
size = (gint64)10000;
if (size > tapetype_get_length(tapetype)/2)
size = tapetype_get_length(tapetype)/2;
if (level0_size > 0 && dp->strategy != DS_NOFULL) {
if (size > level0_size/2)
size = level0_size/2;
}
*stats = 0;
}
}
else if (level == info->last_level + 1) {
/* means of all first day at a new level */
gint64 est_size = (gint64)0;
int nb_est = 0;
for (j=NB_HISTORY-2; j>=0; j--) {
if (info->history[j].level <= 0) continue;
if (info->history[j].size <= (gint64)0) continue;
if (info->history[j].level == info->history[j+1].level + 1 ) {
est_size += info->history[j].size;
nb_est++;
}
}
if (nb_est > 0) {
size = est_size / (gint64)nb_est;
*stats = 1;
} else if (info->inf[level].size > (gint64)1000) { /* stats */
size = info->inf[level].size;
*stats = 1;
} else {
int level0_stat;
gint64 level0_size;
level0_size = internal_server_estimate(dp, info, 0, &level0_stat,
tapetype);
size = (gint64)100000;
if (size > tapetype_get_length(tapetype)/2)
size = tapetype_get_length(tapetype)/2;
if (level0_size > 0 && dp->strategy != DS_NOFULL) {
if (size > level0_size/2)
size = level0_size/2;
}
*stats = 0;
}
} else {
size = (gint64)100000;
if (size > tapetype_get_length(tapetype)/2)
size = tapetype_get_length(tapetype)/2;
}
return size;
}
int
server_can_do_estimate(
disk_t *dp,
info_t *info,
int level,
tapetype_t *tapetype)
{
int stats;
internal_server_estimate(dp, info, level, &stats, tapetype);
return stats;
}