Index: src/virsh.c
===================================================================
RCS file: /data/cvs/libvirt/src/virsh.c,v
retrieving revision 1.77
diff -u -p -r1.77 virsh.c
--- src/virsh.c	29 May 2007 13:55:19 -0000	1.77
+++ src/virsh.c	1 Jun 2007 08:11:46 -0000
@@ -29,6 +29,10 @@
 #include <ctype.h>
 #include <fcntl.h>
 #include <locale.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <poll.h>
+#include <libgen.h>
 
 #include <libxml/parser.h>
 #include <libxml/tree.h>
@@ -56,6 +60,45 @@ static char *progname;
         ((((int) ((T)->tv_sec - (U)->tv_sec)) * 1000000.0 + \
           ((int) ((T)->tv_usec - (U)->tv_usec))) / 1000.0)
 
+/**
+ * The log configuration
+ */
+#define MAX_LOGSIZE   (1024 * 1024)
+#define ROTATE_NUM    5
+#define MSG_BUFFER    4096
+#define SIGN_NAME     "virsh"
+#define LOCK_NAME     ".log.lck"
+#define LOCK_TIMER    100  /* mili seconds */
+#define DIR_MODE      (S_IWUSR | S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)  /* 0755 */
+#define FILE_MODE     (S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH)                                /* 0644 */
+#define LOCK_MODE     (S_IWUSR | S_IRUSR)                                                    /* 0600 */
+#define LVL_DEBUG     "DEBUG"
+#define LVL_INFO      "INFO"
+#define LVL_NOTICE    "NOTICE"
+#define LVL_WARNING   "WARNING"
+#define LVL_ERROR     "ERROR"
+
+/**
+ * vshErrorLevel:
+ *
+ * Indicates the level of an log massage
+ */
+typedef enum {
+    VSH_ERR_DEBUG = 0,
+    VSH_ERR_INFO,
+    VSH_ERR_NOTICE,
+    VSH_ERR_WARNING,
+    VSH_ERR_ERROR
+} vshErrorLevel;
+
+/*
+ * vshLogDef - log definition
+ */
+typedef struct {
+    int log_fd;   /* log file descriptor */
+    int lock_fd;  /* lock file descriptor */
+} vshLogDef;
+
 /*
  * The error handler for virtsh
  */
@@ -175,16 +218,21 @@ typedef struct __vshControl {
     int readonly;               /* connect readonly (first time only, not
                                  * during explicit connect command)
                                  */
+    char *logfile;              /* log file name */
 } __vshControl;
 
 
 static vshCmdDef commands[];
+static vshLogDef logdef;
 
 static void vshError(vshControl * ctl, int doexit, const char *format, ...)
     ATTRIBUTE_FORMAT(printf, 3, 4);
 static int vshInit(vshControl * ctl);
 static int vshDeinit(vshControl * ctl);
 static void vshUsage(vshControl * ctl, const char *cmdname);
+static void vshOpenLogFile(vshControl *ctl);
+static void vshOutputLogFile(vshControl *ctl, int log_level, const char *format, va_list ap);
+static void vshCloseLogFile(void);
 
 static int vshParseArgv(vshControl * ctl, int argc, char **argv);
 
@@ -3240,11 +3288,10 @@ vshDebug(vshControl * ctl, int level, co
 {
     va_list ap;
 
-    if (level > ctl->debug)
-        return;
-
     va_start(ap, format);
-    vfprintf(stdout, format, ap);
+    if (level <= ctl->debug)
+        vfprintf(stdout, format, ap);
+    vshOutputLogFile(ctl, VSH_ERR_DEBUG, format, ap);
     va_end(ap);
 }
 
@@ -3274,6 +3321,7 @@ vshError(vshControl * ctl, int doexit, c
 
     va_start(ap, format);
     vfprintf(stderr, format, ap);
+    vshOutputLogFile(ctl, VSH_ERR_ERROR, format, ap);
     va_end(ap);
 
     fputc('\n', stderr);
@@ -3334,6 +3382,8 @@ vshInit(vshControl * ctl)
 
     ctl->uid = getuid();
 
+    vshOpenLogFile(ctl);
+
     /* set up the library error handler */
     virSetErrorFunc(NULL, virshErrorHandler);
 
@@ -3353,6 +3403,235 @@ vshInit(vshControl * ctl)
     return TRUE;
 }
 
+/**
+ * vshOpenLogFile:
+ *
+ * Open log file.
+ */
+static void
+vshOpenLogFile(vshControl *ctl)
+{
+    int ret;
+    struct stat st;
+    char path_buf[PATH_MAX];
+    char file_buf[PATH_MAX + NAME_MAX];
+
+    if (ctl->logfile[0] != '/') {
+        vshError(ctl, TRUE, _("the log file name has to be full path"));
+    }
+    if (ctl->logfile[strlen(ctl->logfile) - 1] == '/') {
+        vshError(ctl, TRUE, _("the log path is not a file name"));
+    }
+
+    do {
+        /* check log directory */
+        snprintf(file_buf, sizeof(file_buf), "%s", ctl->logfile);
+        snprintf(path_buf, sizeof(path_buf), "%s", dirname(file_buf));
+        while ((ret = stat(path_buf, &st)) == -1) {
+            switch (errno) {
+                case ENOENT:
+                    if (mkdir(path_buf, DIR_MODE) == -1) {
+                        switch (errno) {
+                            case ENOENT:
+                                snprintf(file_buf, sizeof(file_buf), "%s", path_buf);
+                                snprintf(path_buf, sizeof(path_buf), "%s", dirname(file_buf));
+                                continue;
+                            default:
+                                vshError(ctl, TRUE, _("failed to create the log directory"));
+                                break;
+                        }
+                    }
+                    break;
+                default:
+                    vshError(ctl, TRUE, _("failed to get the log directory information"));
+                    break;
+            }
+            break;
+        }
+        if (ret == 0 && !S_ISDIR(st.st_mode)) {
+            vshError(ctl, TRUE, _("no such the log directory"));
+        }
+    } while (ret == -1);
+
+    /* check log file */
+    if (stat(ctl->logfile, &st) == -1) {
+        switch (errno) {
+            case ENOENT:
+                break;
+            default:
+                vshError(ctl, TRUE, _("failed to get the log file information"));
+                break;
+        }
+    } else {
+        if (!S_ISREG(st.st_mode)) {
+            vshError(ctl, TRUE, _("the log path is not a file"));
+        }
+    }
+
+    /* lock file open */
+    snprintf(file_buf, sizeof(file_buf), "%s/%s", path_buf, LOCK_NAME);
+    if ((logdef.lock_fd = open(file_buf, O_WRONLY | O_CREAT, LOCK_MODE)) < 0) {
+        vshError(ctl, TRUE, _("failed to open the log lock file"));
+    }
+    /* log file open */
+    if ((logdef.log_fd = open(ctl->logfile, O_WRONLY | O_APPEND | O_CREAT | O_SYNC, FILE_MODE)) < 0) {
+        vshError(ctl, TRUE, _("failed to open the log file"));
+    }
+}
+
+/**
+ * vshOutputLogFile:
+ *
+ * Outputting an error to log file.
+ */
+static void
+vshOutputLogFile(vshControl *ctl, int log_level, const char *msg_format, va_list ap)
+{
+    int msg_len = 0;
+    int i = 0;
+    char bak_old_path[PATH_MAX + NAME_MAX];
+    char bak_new_path[PATH_MAX + NAME_MAX];
+    char msg_buf[MSG_BUFFER];
+    const char *lvl = "";
+    struct stat st;
+    struct flock flk;
+    struct timeval stTimeval;
+    struct tm *stTm;
+
+    if (logdef.lock_fd == -1 || logdef.log_fd == -1)
+        return;
+
+    /* lock */
+    memset(&flk, 0x00, sizeof(struct flock));
+    flk.l_type = F_WRLCK;
+    while (fcntl(logdef.lock_fd, F_SETLK, &flk) < 0) {
+        switch (errno) {
+            case EAGAIN:
+            case EACCES:
+                poll(NULL, 0, LOCK_TIMER);
+                break;
+            default:
+                vshCloseLogFile();
+                vshError(ctl, FALSE, _("failed to lock the log file"));
+                return;
+        }
+    }
+
+    /**
+     * create log format
+     *
+     * [YYYY.MM.DD HH:MM:SS SIGNATURE PID] LOG_LEVEL message
+    */
+    gettimeofday(&stTimeval, NULL);
+    stTm = localtime(&stTimeval.tv_sec);
+    snprintf(msg_buf, sizeof(msg_buf),
+             "[%d.%02d.%02d %02d:%02d:%02d ",
+             (1900 + stTm->tm_year),
+             (1 + stTm->tm_mon),
+             (stTm->tm_mday),
+             (stTm->tm_hour),
+             (stTm->tm_min),
+             (stTm->tm_sec));
+    snprintf(msg_buf + strlen(msg_buf), sizeof(msg_buf) - strlen(msg_buf),
+             "%s %d] ", SIGN_NAME, getpid());
+    switch (log_level) {
+        case VSH_ERR_DEBUG:
+            lvl = LVL_DEBUG;
+            break;
+        case VSH_ERR_INFO:
+            lvl = LVL_INFO;
+            break;
+        case VSH_ERR_NOTICE:
+            lvl = LVL_INFO;
+            break;
+        case VSH_ERR_WARNING:
+            lvl = LVL_WARNING;
+            break;
+        case VSH_ERR_ERROR:
+            lvl = LVL_ERROR;
+            break;
+        default:
+            lvl = LVL_DEBUG;
+            break;
+    }
+    snprintf(msg_buf + strlen(msg_buf), sizeof(msg_buf) - strlen(msg_buf),
+             "%s ", lvl);
+    vsnprintf(msg_buf + strlen(msg_buf), sizeof(msg_buf) - strlen(msg_buf),
+              msg_format, ap);
+
+    if (msg_buf[strlen(msg_buf) - 1] != '\n')
+        snprintf(msg_buf + strlen(msg_buf), sizeof(msg_buf) - strlen(msg_buf), "\n");
+
+    msg_len = strlen(msg_buf);
+    /* write log */
+    if (write(logdef.log_fd, msg_buf, msg_len) == -1) {
+        /* unlock file */
+        flk.l_type = F_UNLCK;
+        fcntl(logdef.lock_fd, F_SETLK, &flk);
+        vshCloseLogFile();
+        vshError(ctl, FALSE, _("failed to write the log file"));
+        return;
+    }
+
+    /* Check log size */
+    if (stat(ctl->logfile, &st) == 0) {
+        if (st.st_size >= MAX_LOGSIZE) {
+            /* rotate logs */
+            for (i = ROTATE_NUM - 1; i >= 1; i--) {
+                snprintf(bak_old_path, sizeof(bak_old_path), "%s.%d", ctl->logfile, i);
+                snprintf(bak_new_path, sizeof(bak_new_path), "%s.%d", ctl->logfile, i+1);
+                if (rename(bak_old_path, bak_new_path) == -1) {
+                    switch (errno) {
+                        case ENOENT:
+                            break;
+                        default:
+                            /* unlock file */
+                            flk.l_type = F_UNLCK;
+                            fcntl(logdef.lock_fd, F_SETLK, &flk);
+                            vshCloseLogFile();
+                            vshError(ctl, FALSE, _("failed to rename the log file"));
+                            return;
+                    }
+                }
+            }
+            snprintf(bak_new_path, sizeof(bak_new_path), "%s.%d", ctl->logfile, 1);
+            if (rename(ctl->logfile, bak_new_path) == -1) {
+                /* unlock file */
+                flk.l_type = F_UNLCK;
+                fcntl(logdef.lock_fd, F_SETLK, &flk);
+                vshCloseLogFile();
+                vshError(ctl, FALSE, _("failed to rename the log file"));
+                return;
+            }
+        }
+    }
+
+    /* unlock file */
+    flk.l_type = F_UNLCK;
+    fcntl(logdef.lock_fd, F_SETLK, &flk);
+}
+
+/**
+ * vshCloseLogFile:
+ *
+ * Close log file.
+ */
+static void
+vshCloseLogFile()
+{
+    /* log file close */
+    if (logdef.log_fd >= 0) {
+        close(logdef.log_fd);
+        logdef.log_fd = -1;
+    }
+
+    /* lock file close */
+    if (logdef.lock_fd >= 0) {
+        close(logdef.lock_fd);
+        logdef.lock_fd = -1;
+    }
+}
+
 /* -----------------
  * Readline stuff
  * -----------------
@@ -3483,6 +3762,11 @@ vshDeinit(vshControl * ctl)
                      "failed to disconnect from the hypervisor");
         }
     }
+
+    if (ctl->logfile) {
+        free(ctl->logfile);
+    }
+    vshCloseLogFile();
     return TRUE;
 }
 
@@ -3504,6 +3788,7 @@ vshUsage(vshControl * ctl, const char *c
                           "    -h | --help             this help\n"
                           "    -q | --quiet            quiet mode\n"
                           "    -t | --timing           print timing information\n"
+                          "    -l | --log <file>       output logging to file\n"
                           "    -v | --version          program version\n\n"
                           "  commands (non interactive mode):\n"), progname);
 
@@ -3538,6 +3823,7 @@ vshParseArgv(vshControl * ctl, int argc,
         {"version", 0, 0, 'v'},
         {"connect", 1, 0, 'c'},
         {"readonly", 0, 0, 'r'},
+        {"log", 1, 0, 'l'},
         {0, 0, 0, 0}
     };
 
@@ -3580,7 +3866,7 @@ vshParseArgv(vshControl * ctl, int argc,
     end = end ? : argc;
 
     /* standard (non-command) options */
-    while ((arg = getopt_long(end, argv, "d:hqtc:vr", opt, &idx)) != -1) {
+    while ((arg = getopt_long(end, argv, "d:hqtc:vrl:", opt, &idx)) != -1) {
         switch (arg) {
         case 'd':
             ctl->debug = atoi(optarg);
@@ -3603,6 +3889,9 @@ vshParseArgv(vshControl * ctl, int argc,
         case 'r':
             ctl->readonly = TRUE;
             break;
+        case 'l':
+            ctl->logfile = vshStrdup(ctl, optarg);
+            break;
         default:
             vshError(ctl, TRUE,
                      _("unsupported option '-%c'. See --help."), arg);
@@ -3649,6 +3938,10 @@ main(int argc, char **argv)
     char *defaultConn;
     int ret = TRUE;
 
+    /* Initialize log file descriptor */
+    logdef.log_fd = -1;
+    logdef.lock_fd = -1;
+
     if (!setlocale(LC_ALL, "")) {
         perror("setlocale");
         return -1;
