From 5ed4e219dd1af7c7fd33d4d47e28dba45e68e425 Mon Sep 17 00:00:00 2001
From: Vincent Becker <vincentx.becker@intel.com>
Date: Fri, 19 Nov 2010 02:46:04 -0800
Subject: [PATCH] Pulseaudio :Add a target to PA log feature and optimize it
 Signed-off-by: Vincent Becker <vincentx.becker@intel.com>

---
 src/daemon/cmdline.c     |   98 ++++++++++++++--------------
 src/daemon/daemon-conf.c |  106 +++++++++++++++++++++++++++++-
 src/pulsecore/log.c      |  162 ++++++++++++++++++++++++++++++++--------------
 src/pulsecore/log.h      |    7 ++
 4 files changed, 274 insertions(+), 99 deletions(-)

diff --git a/src/daemon/cmdline.c b/src/daemon/cmdline.c
index f6cdcdc..7f72b8c 100644
--- a/src/daemon/cmdline.c
+++ b/src/daemon/cmdline.c
@@ -114,59 +114,59 @@ void pa_cmdline_help(const char *argv0) {
 
     printf(_("%s [options]\n\n"
            "COMMANDS:\n"
-           "  -h, --help                            Show this help\n"
-           "      --version                         Show version\n"
-           "      --dump-conf                       Dump default configuration\n"
-           "      --dump-modules                    Dump list of available modules\n"
-           "      --dump-resample-methods           Dump available resample methods\n"
-           "      --cleanup-shm                     Cleanup stale shared memory segments\n"
-           "      --start                           Start the daemon if it is not running\n"
-           "  -k  --kill                            Kill a running daemon\n"
-           "      --check                           Check for a running daemon (only returns exit code)\n\n"
+           "  -h, --help                                       Show this help\n"
+           "      --version                                    Show version\n"
+           "      --dump-conf                                  Dump default configuration\n"
+           "      --dump-modules                               Dump list of available modules\n"
+           "      --dump-resample-methods                      Dump available resample methods\n"
+           "      --cleanup-shm                                Cleanup stale shared memory segments\n"
+           "      --start                                      Start the daemon if it is not running\n"
+           "  -k  --kill                                       Kill a running daemon\n"
+           "      --check                                      Check for a running daemon (only returns exit code)\n\n"
 
            "OPTIONS:\n"
-           "      --system[=BOOL]                   Run as system-wide instance\n"
-           "  -D, --daemonize[=BOOL]                Daemonize after startup\n"
-           "      --fail[=BOOL]                     Quit when startup fails\n"
-           "      --high-priority[=BOOL]            Try to set high nice level\n"
-           "                                        (only available as root, when SUID or\n"
-           "                                        with elevated RLIMIT_NICE)\n"
-           "      --realtime[=BOOL]                 Try to enable realtime scheduling\n"
-           "                                        (only available as root, when SUID or\n"
-           "                                        with elevated RLIMIT_RTPRIO)\n"
-           "      --disallow-module-loading[=BOOL]  Disallow module user requested module\n"
-           "                                        loading/unloading after startup\n"
-           "      --disallow-exit[=BOOL]            Disallow user requested exit\n"
-           "      --exit-idle-time=SECS             Terminate the daemon when idle and this\n"
-           "                                        time passed\n"
-           "      --module-idle-time=SECS           Unload autoloaded modules when idle and\n"
-           "                                        this time passed\n"
-           "      --scache-idle-time=SECS           Unload autoloaded samples when idle and\n"
-           "                                        this time passed\n"
-           "      --log-level[=LEVEL]               Increase or set verbosity level\n"
-           "  -v                                    Increase the verbosity level\n"
-           "      --log-target={auto,syslog,stderr} Specify the log target\n"
-           "      --log-meta[=BOOL]                 Include code location in log messages\n"
-           "      --log-time[=BOOL]                 Include timestamps in log messages\n"
-           "      --log-backtrace=FRAMES            Include a backtrace in log messages\n"
-           "  -p, --dl-search-path=PATH             Set the search path for dynamic shared\n"
-           "                                        objects (plugins)\n"
-           "      --resample-method=METHOD          Use the specified resampling method\n"
-           "                                        (See --dump-resample-methods for\n"
-           "                                        possible values)\n"
-           "      --use-pid-file[=BOOL]             Create a PID file\n"
-           "      --no-cpu-limit[=BOOL]             Do not install CPU load limiter on\n"
-           "                                        platforms that support it.\n"
-           "      --disable-shm[=BOOL]              Disable shared memory support.\n\n"
+           "      --system[=BOOL]                              Run as system-wide instance\n"
+           "  -D, --daemonize[=BOOL]                           Daemonize after startup\n"
+           "      --fail[=BOOL]                                Quit when startup fails\n"
+           "      --high-priority[=BOOL]                       Try to set high nice level\n"
+           "                                                   (only available as root, when SUID or\n"
+           "                                                   with elevated RLIMIT_NICE)\n"
+           "      --realtime[=BOOL]                            Try to enable realtime scheduling\n"
+           "                                                   (only available as root, when SUID or\n"
+           "                                                   with elevated RLIMIT_RTPRIO)\n"
+           "      --disallow-module-loading[=BOOL]             Disallow module user requested module\n"
+           "                                                   loading/unloading after startup\n"
+           "      --disallow-exit[=BOOL]                       Disallow user requested exit\n"
+           "      --exit-idle-time=SECS                        Terminate the daemon when idle and this\n"
+           "                                                   time passed\n"
+           "      --module-idle-time=SECS                      Unload autoloaded modules when idle and\n"
+           "                                                   this time passed\n"
+           "      --scache-idle-time=SECS                      Unload autoloaded samples when idle and\n"
+           "                                                   this time passed\n"
+           "      --log-level[=LEVEL]                          Increase or set verbosity level\n"
+           "  -v                                               Increase the verbosity level\n"
+           "      --log-target={auto,syslog,stderr,FILE PATH}  Specify the log target\n"
+           "      --log-meta[=BOOL]                            Include code location in log messages\n"
+           "      --log-time[=BOOL]                            Include timestamps in log messages\n"
+           "      --log-backtrace=FRAMES                       Include a backtrace in log messages\n"
+           "  -p, --dl-search-path=PATH                        Set the search path for dynamic shared\n"
+           "                                                   objects (plugins)\n"
+           "      --resample-method=METHOD                     Use the specified resampling method\n"
+           "                                                   (See --dump-resample-methods for\n"
+           "                                                   possible values)\n"
+           "      --use-pid-file[=BOOL]                        Create a PID file\n"
+           "      --no-cpu-limit[=BOOL]                        Do not install CPU load limiter on\n"
+           "                                                   platforms that support it.\n"
+           "      --disable-shm[=BOOL]                         Disable shared memory support.\n\n"
 
            "STARTUP SCRIPT:\n"
-           "  -L, --load=\"MODULE ARGUMENTS\"         Load the specified plugin module with\n"
-           "                                        the specified argument\n"
-           "  -F, --file=FILENAME                   Run the specified script\n"
-           "  -C                                    Open a command line on the running TTY\n"
-           "                                        after startup\n\n"
+           "  -L, --load=\"MODULE ARGUMENTS\"                  Load the specified plugin module with\n"
+           "                                                   the specified argument\n"
+           "  -F, --file=FILENAME                              Run the specified script\n"
+           "  -C                                               Open a command line on the running TTY\n"
+           "                                                   after startup\n\n"
 
-           "  -n                                    Don't load default script file\n"),
+           "  -n                                               Don't load default script file\n"),
            pa_path_get_filename(argv0));
 }
 
@@ -318,7 +318,7 @@ int pa_cmdline_parse(pa_daemon_conf *conf, int argc, char *const argv [], int *d
 
             case ARG_LOG_TARGET:
                 if (pa_daemon_conf_set_log_target(conf, optarg) < 0) {
-                    pa_log(_("Invalid log target: use either 'syslog', 'stderr' or 'auto'."));
+                    pa_log(_("Invalid log target: use either 'syslog', 'stderr', 'auto' or a valid file path name (./log_file.txt)."));
                     goto fail;
                 }
                 break;
diff --git a/src/daemon/daemon-conf.c b/src/daemon/daemon-conf.c
index 74e8135..d5d60a9 100644
--- a/src/daemon/daemon-conf.c
+++ b/src/daemon/daemon-conf.c
@@ -29,6 +29,8 @@
 #include <string.h>
 #include <unistd.h>
 #include <sched.h>
+#include <sys/stat.h>
+#include <fcntl.h>
 
 #include <pulse/xmalloc.h>
 #include <pulse/timeval.h>
@@ -54,6 +56,80 @@
 #define ENV_CONFIG_FILE "PULSE_CONFIG"
 #define ENV_DL_SEARCH_PATH "PULSE_DLPATH"
 
+
+static char *insert_substr(char *dest, char *substr, int pos) {
+    int len_dest,len_substr;
+
+    pa_assert(pos >= 0);
+    pa_assert(dest);
+    pa_assert(substr);
+
+    len_dest = strlen(dest);
+    len_substr = strlen(substr);
+
+    /* If pos is outside dest string, copy at end of dest string */
+    if (len_dest <= pos)
+        strcpy(dest + len_dest, substr);
+    /* Copy substring into destination string at position pos */
+    else {
+	memmove(dest + pos + len_substr, dest + pos, len_dest - pos + 1);
+	memcpy(dest + pos, substr, len_substr + 1);
+    }
+
+    return dest;
+}
+
+static int rename_file_with_date(char *file_string, int file_size) {
+
+    time_t rawtime;
+    struct tm * timeinfo;
+    char *temp_date;
+    char *string_with_date;
+    char date[30];
+    char file_ext[10];
+    char *p, *slash, *dot, *end_of_str;
+    int length_date;
+
+    pa_assert(file_string);
+    pa_assert(file_size < 512);
+
+    /* Append the date to the filename */
+    time(&rawtime);
+    timeinfo = localtime(&rawtime);
+    temp_date = asctime(timeinfo);
+
+    length_date = strlen(temp_date);
+
+    memcpy(date, temp_date, length_date + 1);
+
+    if((file_size + length_date) >= 512)
+      return -1;
+
+    while((p = strchr(date, ' ')))
+        *p = '_';
+
+    /* Date format ends with '\n'. Replace '\n' by '\0' */
+    date[length_date - 1] = 0;
+
+    if((slash = strrchr(file_string, '/'))) {
+        /* String has an extension, e.g. ./filename.ext
+	   Insert date between file name and extension. */
+         if ((dot = strrchr(slash, '.'))) {
+            end_of_str = strchr(file_string, '\0');
+	    memcpy(file_ext, dot, end_of_str - dot + 1);
+	    string_with_date = insert_substr(file_string, date, dot-file_string);
+	    strncpy(file_string, string_with_date, strlen(string_with_date) + 1);
+	    strncat(file_string, file_ext, strlen(file_ext) + 1);
+	    printf("file_string : %s\n", file_string);
+      }
+      /* String has no extension, e.g. ./filename or /path/filename */
+      else
+	  strncat(file_string, date, strlen(date) + 1);
+    }
+
+    return 0;
+}
+
 static const pa_daemon_conf default_conf = {
     .cmd = PA_CMD_DAEMON,
     .daemonize = FALSE,
@@ -167,6 +243,7 @@ void pa_daemon_conf_free(pa_daemon_conf *c) {
     pa_xfree(c->dl_search_path);
     pa_xfree(c->default_script_file);
     pa_xfree(c->config_file);
+    pa_log_close_fd();
     pa_xfree(c);
 }
 
@@ -182,12 +259,37 @@ int pa_daemon_conf_set_log_target(pa_daemon_conf *c, const char *string) {
     } else if (!strcmp(string, "stderr")) {
         c->auto_log_target = 0;
         c->log_target = PA_LOG_STDERR;
-    } else
-        return -1;
+    } else if (strchr(string, '/')) {
+        int file_fd;
+	char file_path[512];
+	struct stat buf;
+
+	strncpy(file_path, string, sizeof(file_path));
+
+	/* Open or create the file or device and propagate the file descriptor
+	   to the PA log API. If file is not a char device and is an existing
+	   regular file, append the date to the file name. */
+	if(stat(file_path, &buf) == 0 && S_ISREG(buf.st_mode) > 0)
+	    if(rename_file_with_date(file_path, strlen(file_path)) < 0)
+	        printf("Unable to rename file. File has been renamed because it already exists. Try to shorten the file name or path.\n");
+
+	if ((file_fd = open(file_path, O_RDWR|O_TRUNC|O_CREAT, 0777)) >= 0) {
+	    c->auto_log_target = 0;
+	    c->log_target = PA_LOG_FILED;
+	    pa_log_set_fd(file_fd);
+	}
+	else {
+	    printf("Failed to open target file : %s\n", pa_cstrerror(errno));
+	    return -1;
+	}
+    }
+    else
+      return -1;
 
     return 0;
 }
 
+
 int pa_daemon_conf_set_log_level(pa_daemon_conf *c, const char *string) {
     uint32_t u;
     pa_assert(c);
diff --git a/src/pulsecore/log.c b/src/pulsecore/log.c
index 0c5a317..e74feba 100644
--- a/src/pulsecore/log.c
+++ b/src/pulsecore/log.c
@@ -70,6 +70,7 @@ static pa_log_level_t maximum_level = PA_LOG_ERROR, maximum_level_override = PA_
 static unsigned show_backtrace = 0, show_backtrace_override = 0, skip_backtrace = 0;
 static pa_log_flags_t flags = 0, flags_override = 0;
 static pa_bool_t no_rate_limit = FALSE;
+static int fdlog = -1;
 
 #ifdef HAVE_SYSLOG_H
 static const int level_to_syslog[] = {
@@ -128,6 +129,17 @@ void pa_log_set_flags(pa_log_flags_t _flags, pa_log_merge_t merge) {
         flags = _flags;
 }
 
+void pa_log_set_fd(int fd) {
+    pa_assert(fd >= 0);
+
+    fdlog = fd;
+}
+
+void pa_log_close_fd(void) {
+    if (fdlog >= 0)
+        pa_close(fdlog);
+}
+
 void pa_log_set_show_backtrace(unsigned nlevels) {
     show_backtrace = nlevels;
 }
@@ -254,6 +266,7 @@ static void init_defaults(void) {
     } PA_ONCE_END;
 }
 
+
 void pa_log_levelv_meta(
         pa_log_level_t level,
         const char*file,
@@ -269,15 +282,19 @@ void pa_log_levelv_meta(
     pa_log_level_t _maximum_level;
     unsigned _show_backtrace;
     pa_log_flags_t _flags;
+    char *local_t;
+    int metadata_length;
 
     /* We don't use dynamic memory allocation here to minimize the hit
      * in RT threads */
-    char text[16*1024], location[128], timestamp[32];
+    char text[16*1024], location[128], timestamp[32], metadata[256], append_data[256];
 
     pa_assert(level < PA_LOG_LEVEL_MAX);
     pa_assert(format);
 
-    init_defaults();
+    PA_ONCE_BEGIN {
+        init_defaults();
+    } PA_ONCE_END;
 
     _target = target_override_set ? target_override : target;
     _maximum_level = PA_MAX(maximum_level, maximum_level_override);
@@ -289,8 +306,6 @@ void pa_log_levelv_meta(
         return;
     }
 
-    pa_vsnprintf(text, sizeof(text), format, ap);
-
     if ((_flags & PA_LOG_PRINT_META) && file && line > 0 && func)
         pa_snprintf(location, sizeof(location), "[%s:%i %s()] ", file, line, func);
     else if ((_flags & (PA_LOG_PRINT_META|PA_LOG_PRINT_FILE)) && file)
@@ -325,77 +340,127 @@ void pa_log_levelv_meta(
     } else
         timestamp[0] = 0;
 
+
 #ifdef HAVE_EXECINFO_H
     if (_show_backtrace > 0)
         bt = get_backtrace(_show_backtrace);
 #endif
 
-    if (!pa_utf8_valid(text))
+    switch (_target) {
+
+    case PA_LOG_STDERR: {
+        const char *prefix = "", *suffix = "", *grey = "";
+
+#ifndef OS_IS_WIN32
+	/* Yes indeed. Useless, but fun! */
+	if ((_flags & PA_LOG_COLORS) && isatty(STDERR_FILENO)) {
+	    if (level <= PA_LOG_ERROR)
+	        prefix = "\x1B[1;31m";
+	    else if (level <= PA_LOG_WARN)
+	        prefix = "\x1B[1m";
+
+	    if (bt)
+	        grey = "\x1B[2m";
+
+	    if (grey[0] || prefix[0])
+	        suffix = "\x1B[0m";
+	}
+#endif
+        if (_flags & PA_LOG_PRINT_LEVEL)
+            pa_snprintf(metadata, sizeof(metadata), "%s%c: %s%s", timestamp, level_to_char[level], location, prefix);
+	else
+            pa_snprintf(metadata, sizeof(metadata), "%s%s%s", timestamp, location, prefix);
+
+	pa_snprintf(append_data, sizeof(append_data), "%s%s%s", grey, pa_strempty(bt), suffix);
+
+	break;
+    }
+
+#ifdef HAVE_SYSLOG_H
+    case PA_LOG_SYSLOG: {
+        pa_snprintf(metadata, sizeof(metadata), "%s%s", timestamp, location);
+	pa_snprintf(append_data, sizeof(append_data) ,"%s", pa_strempty(bt));
+
+        break;
+    }
+#endif
+
+    case PA_LOG_FILED: {
+	pa_snprintf(metadata, sizeof(metadata), "\n%c %s%s", level_to_char[level], timestamp, location);
+
+        append_data[0] = 0;
+
+        break;
+    }
+
+    default:
+        metadata[0] = 0;
+	append_data[0] = 0;
+
+        break;
+    }
+
+    metadata_length = strlen(metadata);
+
+    /* Format text at metadata_length position */
+    pa_vsnprintf(&text[metadata_length], sizeof(text) - metadata_length, format, ap);
+
+    if (!pa_utf8_valid(&text[metadata_length]))
         pa_logl(level, "Invalid UTF-8 string following below:");
 
-    for (t = text; t; t = n) {
-        if ((n = strchr(t, '\n'))) {
-            *n = 0;
-            n++;
-        }
+    /* We shouldn't be using dynamic allocation here to
+     * minimize the hit in RT threads */
+    if ((local_t = pa_utf8_to_locale(&text[metadata_length]))) {
+	strcpy(&text[metadata_length], local_t);
+	pa_xfree(local_t);
+    }
+
+    for (t = &text[metadata_length]; t; t = n) {
+	if ((n = strchr(t, '\n'))) {
+	    *n = 0;
+	    n++;
+	}
 
         /* We ignore strings only made out of whitespace */
         if (t[strspn(t, "\t ")] == 0)
             continue;
 
-        switch (_target) {
+	/* Rewind to the start of the t buffer */
+	t -= metadata_length;
 
-            case PA_LOG_STDERR: {
-                const char *prefix = "", *suffix = "", *grey = "";
-                char *local_t;
+	/* Prepend metadata into t */
+	memcpy(t, metadata, metadata_length);
 
-#ifndef OS_IS_WIN32
-                /* Yes indeed. Useless, but fun! */
-                if ((_flags & PA_LOG_COLORS) && isatty(STDERR_FILENO)) {
-                    if (level <= PA_LOG_ERROR)
-                        prefix = "\x1B[1;31m";
-                    else if (level <= PA_LOG_WARN)
-                        prefix = "\x1B[1m";
-
-                    if (bt)
-                        grey = "\x1B[2m";
-
-                    if (grey[0] || prefix[0])
-                        suffix = "\x1B[0m";
-                }
-#endif
-
-                /* We shouldn't be using dynamic allocation here to
-                 * minimize the hit in RT threads */
-                if ((local_t = pa_utf8_to_locale(t)))
-                    t = local_t;
-
-                if (_flags & PA_LOG_PRINT_LEVEL)
-                    fprintf(stderr, "%s%c: %s%s%s%s%s%s\n", timestamp, level_to_char[level], location, prefix, t, grey, pa_strempty(bt), suffix);
-                else
-                    fprintf(stderr, "%s%s%s%s%s%s%s\n", timestamp, location, prefix, t, grey, pa_strempty(bt), suffix);
+        switch (_target) {
 
-                pa_xfree(local_t);
+            case PA_LOG_STDERR: {
+		fprintf(stderr, "%s%s\n", t, append_data);
 
-                break;
+		break;
             }
 
 #ifdef HAVE_SYSLOG_H
             case PA_LOG_SYSLOG: {
-                char *local_t;
-
                 openlog(ident, LOG_PID, LOG_USER);
 
-                if ((local_t = pa_utf8_to_locale(t)))
-                    t = local_t;
-
-                syslog(level_to_syslog[level], "%s%s%s%s", timestamp, location, t, pa_strempty(bt));
-                pa_xfree(local_t);
+                syslog(level_to_syslog[level], "%s%s", t, append_data);
 
                 break;
             }
 #endif
 
+	    case PA_LOG_FILED: {
+		if (fdlog != -1) {
+		    if (write(fdlog, t, strlen(t)) < 0) {
+			saved_errno = errno;
+			pa_close(fdlog);
+			fdlog = -1;
+		    }
+		}
+
+		break;
+	    }
+
             case PA_LOG_NULL:
             default:
                 break;
@@ -406,6 +471,7 @@ void pa_log_levelv_meta(
     errno = saved_errno;
 }
 
+
 void pa_log_level_meta(
         pa_log_level_t level,
         const char*file,
diff --git a/src/pulsecore/log.h b/src/pulsecore/log.h
index 2f379f6..988114f 100644
--- a/src/pulsecore/log.h
+++ b/src/pulsecore/log.h
@@ -36,6 +36,7 @@ typedef enum pa_log_target {
     PA_LOG_STDERR,  /* default */
     PA_LOG_SYSLOG,
     PA_LOG_NULL,    /* to /dev/null */
+    PA_LOG_FILED,   /* to a file descriptor, e.g. of a char device */
     PA_LOG_TARGET_MAX
 } pa_log_target_t;
 
@@ -80,6 +81,12 @@ void pa_log_set_show_backtrace(unsigned nlevels);
 /* Skip the first backtrace frames */
 void pa_log_set_skip_backtrace(unsigned nlevels);
 
+/* Set the file descriptor of the logging device.
+   Daemon conf is in charge of opening this device */
+void pa_log_set_fd(int fd);
+
+void pa_log_close_fd(void);
+
 void pa_log_level_meta(
         pa_log_level_t level,
         const char*file,
-- 
1.7.2.2

