/*
 * Amanda, The Advanced Maryland Automatic Network Disk Archiver
 * Copyright (c) 1991-1998 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.
 *
 * Author: James da Silva, Systems Design and Analysis Group
 *			   Computer Science Department
 *			   University of Maryland at College Park
 */
/*
 * $Id: logfile.c,v 1.31 2006/06/01 14:54:39 martinea Exp $
 *
 * common log file writing routine
 */
#include "amanda.h"
#include "amutil.h"
#include "conffile.h"

#include "logfile.h"

char *logtype_str[] = {
    "BOGUS",
    "FATAL",		/* program died for some reason, used by error() */
    "ERROR", "WARNING",	"INFO", "SUMMARY",	 /* information messages */
    "START", "FINISH",				   /* start/end of a run */
    "DISK",							 /* disk */
    /* the end of a dump */
    "DONE", "PART", "PARTPARTIAL", "SUCCESS", "PARTIAL", "FAIL", "STRANGE",
    "CHUNK", "CHUNKSUCCESS",                            /* ... continued */
    "STATS",						   /* statistics */
    "CONT",				   /* continuation line; special */
    "RETRY",
    "MARKER",					  /* marker for reporter */
};

char *program_str[] = {
    "UNKNOWN", "planner", "driver", "amreport", "dumper", "chunker",
    "taper", "amflush", "amdump", "amidxtaped", "amfetchdump", "amcheckdump",
    "amvault", "amcleanup", "ambackupd", "amtrmidx", "amtrmlog",
};

int curlinenum;
logtype_t curlog;
program_t curprog;
char *curstr;

int multiline = -1;
static char *logfile;
static int logfd = -1;

 /*
  * Note that technically we could use two locks, a read lock
  * from 0-EOF and a write-lock from EOF-EOF, thus leaving the
  * beginning of the file open for read-only access.  Doing so
  * would open us up to some race conditions unless we're pretty
  * careful, and on top of that the functions here are so far
  * the only accesses to the logfile, so keep things simple.
  */

/* local functions */
static void open_log(void);
static void close_log(void);

void
amanda_log_trace_log(
    GLogLevelFlags log_level,
    const gchar *message)
{
    logtype_t logtype = L_ERROR;

    switch (log_level) {
	case G_LOG_LEVEL_ERROR:
	case G_LOG_LEVEL_CRITICAL:
	    logtype = L_FATAL;
	    break;

	default:
	    return;
    }
    log_add(logtype, "%s", message);
}

static void log_add_full_v(logtype_t typ, char *pname, char *format, va_list argp)
{
    char *leader = NULL;
    char *xlated_fmt = gettext(format);
    char linebuf[STR_SIZE];
    size_t n;
    static gboolean in_log_add = 0;

    /* avoid recursion */
    if (in_log_add)
	return;

    /* format error message */

    if((int)typ <= (int)L_BOGUS || (int)typ > (int)L_MARKER) typ = L_BOGUS;

    if(multiline > 0) {
	leader = g_strdup("  ");		/* continuation line */
    } else {
	leader = g_strjoin(NULL, logtype_str[(int)typ], " ", pname, " ", NULL);
    }

    /* use sizeof(linebuf)-2 to save space for a trailing newline */
    g_vsnprintf(linebuf, sizeof(linebuf)-2, xlated_fmt, argp);
						/* -1 to allow for '\n' */

    /* avoid recursive call from error() */

    in_log_add = 1;

    /* append message to the log file */

    if(multiline == -1) open_log();

    if (full_write(logfd, leader, strlen(leader)) < strlen(leader)) {
	error(_("log file write error: %s"), strerror(errno));
	/*NOTREACHED*/
    }

    amfree(leader);

    /* add a newline if necessary */
    n = strlen(linebuf);
    if(n == 0 || linebuf[n-1] != '\n') linebuf[n++] = '\n';
    linebuf[n] = '\0';

    if (full_write(logfd, linebuf, n) < n) {
	error(_("log file write error: %s"), strerror(errno));
	/*NOTREACHED*/
    }

    if(multiline != -1) multiline++;
    else close_log();

    in_log_add = 0;
}

void log_add(logtype_t typ, char *format, ...)
{
    va_list argp;

    arglist_start(argp, format);
    log_add_full_v(typ, get_pname(), format, argp);
    arglist_end(argp);
}

void log_add_full(logtype_t typ, char *pname, char *format, ...)
{
    va_list argp;

    arglist_start(argp, format);
    log_add_full_v(typ, pname, format, argp);
    arglist_end(argp);
}

void
log_start_multiline(void)
{
    assert(multiline == -1);

    multiline = 0;
    open_log();
}


void
log_end_multiline(void)
{
    assert(multiline != -1);
    multiline = -1;
    close_log();
}

char *
make_logname(
    char *process,
    char *datestamp)
{
    char *conf_logdir;
    char *fname = NULL;

    if (datestamp == NULL)
	datestamp = g_strdup("error-00000000");

    conf_logdir = config_dir_relative(getconf_str(CNF_LOGDIR));
    fname = g_strjoin(NULL, conf_logdir, "/log", NULL);
    while (1) {
	int fd;
        g_free(logfile);
        logfile = g_strconcat(fname, ".", datestamp, ".0", NULL);
	/* try to create it */
	fd = open(logfile, O_EXCL | O_CREAT | O_WRONLY, 0600);
	if (fd > -1) {
	    FILE *file;
	    file = fdopen(fd, "w");
	    if (file) {
		gchar *text = g_strdup_printf("INFO %s %s pid %ld\n",
				 get_pname(), process, (long)getpid());
		fprintf(file, "%s", text);
		fclose(file);
		file = fopen(logfile, "r");
		if (file) {
		    char line[1000];
		    if (fgets(line, 1000, file)) {
			if (g_str_equal(line, text)) {
			    /* the file is for us */
			    g_free(text);
			    fclose(file);
			    break;
			}
		    }
		    fclose(file);
		}
		g_free(text);
	    }
	}
	if (errno == ENOENT) {
	    g_critical("logdir '%s' do not exists", conf_logdir);
	}

	/* increase datestamp */
	datestamp[13]++;
	if (datestamp[13] == ':') {
	    datestamp[13] = '0';
	    datestamp[12]++;
	    if (datestamp[12] == '6') {
		datestamp[12] = '0';
		datestamp[11]++;
		if (datestamp[11] == ':') {
		    datestamp[11] = '0';
		    datestamp[10]++;
		    if (datestamp[10] == '6') {
			datestamp[10] = '0';
			datestamp[9]++;
			if (datestamp[9] == ':') {
			    datestamp[9] = '0';
			    datestamp[8]++;
			}
		    }
		}
	    }
	}
    }

    if (strcmp(process, "checkdump") != 0 &&
	strcmp(process, "fetchdump") != 0) {
	char *logf = g_strdup(rindex(logfile,'/')+1);
	unlink(fname);
	if (symlink(logf, fname) == -1) {
	    g_debug("Can't symlink '%s' to '%s': %s", fname, logf,
		    strerror(errno));
	}
	amfree(logf);
    }

    amfree(fname);
    amfree(conf_logdir);

    return (datestamp);
}

char *
get_logname(void)
{
    return g_strdup(logfile);
}

void
set_logname(
    char *filename)
{
    logfile = g_strdup(filename);
}

void
log_rename(
    char *	datestamp)
{
    char *conf_logdir;
    char *logfile;
    char *fname = NULL;
    char seq_str[NUM_STR_SIZE];
    unsigned int seq;
    struct stat statbuf;

    if(datestamp == NULL) datestamp = "error";

    conf_logdir = config_dir_relative(getconf_str(CNF_LOGDIR));
    logfile = g_strjoin(NULL, conf_logdir, "/log", NULL);

    if (lstat(logfile, &statbuf) == 0 && S_ISLNK(statbuf.st_mode)) {
	g_debug("Remove symbolic link %s", logfile);
	unlink(logfile);
	return;
    }

    for(seq = 0; 1; seq++) {	/* if you've got MAXINT files in your dir... */
	g_snprintf(seq_str, sizeof(seq_str), "%u", seq);
        g_free(fname);
        fname = g_strconcat(logfile, ".", datestamp, ".", seq_str, NULL);
	if(stat(fname, &statbuf) == -1 && errno == ENOENT) break;
    }

    if(rename(logfile, fname) == -1) {
	g_debug(_("could not rename \"%s\" to \"%s\": %s"),
	      logfile, fname, strerror(errno));
    }

    amfree(fname);
    amfree(logfile);
    amfree(conf_logdir);
}


static void
open_log(void)
{
    logfd = open(logfile, O_WRONLY|O_CREAT|O_APPEND, 0600);

    if(logfd == -1) {
	error(_("could not open log file %s: %s"), logfile, strerror(errno));
	/*NOTREACHED*/
    }

    if(amflock(logfd, "log") == -1) {
	error(_("could not lock log file %s: %s"), logfile, strerror(errno));
	/*NOTREACHED*/
    }
}


static void
close_log(void)
{
    if(amfunlock(logfd, "log") == -1) {
	error(_("could not unlock log file %s: %s"), logfile, strerror(errno));
	/*NOTREACHED*/
    }

    if(close(logfd) == -1) {
	error(_("close log file: %s"), strerror(errno));
	/*NOTREACHED*/
    }

    logfd = -1;
}

/* WARNING: Function accesses globals curstr, curlog, and curprog
 * WARNING: Function has static member logline, returned via globals */
int
get_logline(
    FILE *	logf)
{
    static char *logline = NULL;
    static size_t line_size = 0;
    char *lline;
    size_t loffset = 0;
    char *logstr, *progstr;
    char *s;
    int ch;
    int n;

    if (!logline) {
	line_size = 256;
	logline = g_malloc(line_size);
    }

    logline[0] = '\0';
    while(1) {
	lline = untaint_fgets(logline + loffset, line_size - loffset, logf);
	if (lline == NULL) {
	    break; /* EOF */
	}
	if (strlen(logline) == line_size -1 &&
		   logline[strlen(logline)-1] != '\n') {
	    line_size *= 2;
	    logline = g_realloc(logline, line_size);
	    loffset = strlen(logline);
	} else if (strlen(logline) == 0 ||
		   (strlen(logline) == 1 && logline[0] == '\n')) {
	} else {
	    break; /* good line */
	}
	logline[loffset] = '\0';
    }
    if (logline[0] == '\0')
	return 0;

    /* remove \n */
    n = strlen(logline);
    if (logline[n-1] == '\n') logline[n-1] = '\0';

    curlinenum++;
    s = logline;
    ch = *s++;

    /* continuation lines are special */

    if(logline[0] == ' ' && logline[1] == ' ') {
	curlog = L_CONT;
	/* curprog stays the same */
	skip_whitespace(s, ch);
	curstr = s-1;
	return 1;
    }

    /* isolate logtype field */

    skip_whitespace(s, ch);
    logstr = s - 1;
    skip_non_whitespace(s, ch);
    s[-1] = '\0';

    /* isolate program name field */

    skip_whitespace(s, ch);
    progstr = s - 1;
    skip_non_whitespace(s, ch);
    s[-1] = '\0';

    if (strcmp(progstr,"checkdump") == 0) {
	progstr = "amcheckdump";
    } else if (strcmp(progstr,"fetchdump") == 0) {
	progstr = "amfetchdump";
    }
    /* rest of line is logtype dependent string */

    skip_whitespace(s, ch);
    curstr = s - 1;

    /* lookup strings */

    for(curlog = L_MARKER; curlog != L_BOGUS; curlog--)
	if(g_str_equal(logtype_str[curlog], logstr)) break;

    for(curprog = P_LAST; curprog != P_UNKNOWN; curprog--)
	if(g_str_equal(program_str[curprog], progstr)) break;

    return 1;
}

char *
get_logtype_str(
    logtype_t logtype)
{
    return logtype_str[logtype];
}

char *
get_program_str(
    program_t program)
{
    return program_str[program];
}

