Support for truncate/append on log files

Summary:
Presently, if a log file already exists, lldb simply starts overwriting bits of it, without
truncating or anything. This patch makes it use eFileOptionFileTruncate by default. It also adds
an --append option, which will append to the file without truncating. A test is included.

Reviewers: clayborg

Subscribers: lldb-commits

Differential Revision: http://reviews.llvm.org/D8450

llvm-svn: 232801
diff --git a/lldb/include/lldb/Core/Log.h b/lldb/include/lldb/Core/Log.h
index 119b29b..e63633b 100644
--- a/lldb/include/lldb/Core/Log.h
+++ b/lldb/include/lldb/Core/Log.h
@@ -47,6 +47,7 @@
 #define LLDB_LOG_OPTION_PREPEND_PROC_AND_THREAD (1u << 5)
 #define LLDB_LOG_OPTION_PREPEND_THREAD_NAME     (1U << 6)
 #define LLDB_LOG_OPTION_BACKTRACE               (1U << 7)
+#define LLDB_LOG_OPTION_APPEND                  (1U << 8)
 
 //----------------------------------------------------------------------
 // Logging Functions
diff --git a/lldb/include/lldb/Core/StreamFile.h b/lldb/include/lldb/Core/StreamFile.h
index d032c0b..55bb361 100644
--- a/lldb/include/lldb/Core/StreamFile.h
+++ b/lldb/include/lldb/Core/StreamFile.h
@@ -37,6 +37,10 @@
 
     StreamFile (const char *path);
 
+    StreamFile (const char *path,
+                uint32_t options,
+                uint32_t permissions = lldb::eFilePermissionsFileDefault);
+
     StreamFile (FILE *fh, bool transfer_ownership);
 
     virtual
diff --git a/lldb/source/Commands/CommandObjectLog.cpp b/lldb/source/Commands/CommandObjectLog.cpp
index d4334d5..dbc1a3e 100644
--- a/lldb/source/Commands/CommandObjectLog.cpp
+++ b/lldb/source/Commands/CommandObjectLog.cpp
@@ -146,6 +146,7 @@
             case 'p':  log_options |= LLDB_LOG_OPTION_PREPEND_PROC_AND_THREAD;break;
             case 'n':  log_options |= LLDB_LOG_OPTION_PREPEND_THREAD_NAME;    break;
             case 'S':  log_options |= LLDB_LOG_OPTION_BACKTRACE;              break;
+            case 'a':  log_options |= LLDB_LOG_OPTION_APPEND;                 break;
             default:
                 error.SetErrorStringWithFormat ("unrecognized option '%c'", short_option);
                 break;
@@ -223,6 +224,7 @@
 { LLDB_OPT_SET_1, false, "pid-tid",    'p', OptionParser::eNoArgument,       NULL, NULL, 0, eArgTypeNone,       "Prepend all log lines with the process and thread ID that generates the log line." },
 { LLDB_OPT_SET_1, false, "thread-name",'n', OptionParser::eNoArgument,       NULL, NULL, 0, eArgTypeNone,       "Prepend all log lines with the thread name for the thread that generates the log line." },
 { LLDB_OPT_SET_1, false, "stack",      'S', OptionParser::eNoArgument,       NULL, NULL, 0, eArgTypeNone,       "Append a stack backtrace to each log line." },
+{ LLDB_OPT_SET_1, false, "append",     'a', OptionParser::eNoArgument,       NULL, NULL, 0, eArgTypeNone,       "Append to the log file instead of overwriting." },
 { 0, false, NULL,                       0,  0,                 NULL, NULL, 0, eArgTypeNone,       NULL }
 };
 
diff --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp
index f93da0e..7134d41 100644
--- a/lldb/source/Core/Debugger.cpp
+++ b/lldb/source/Core/Debugger.cpp
@@ -1331,7 +1331,12 @@
             log_stream_sp = pos->second.lock();
         if (!log_stream_sp)
         {
-            log_stream_sp.reset (new StreamFile (log_file));
+            uint32_t options = File::eOpenOptionWrite | File::eOpenOptionCanCreate
+                                | File::eOpenOptionCloseOnExec | File::eOpenOptionAppend;
+            if (! (log_options & LLDB_LOG_OPTION_APPEND))
+                options |= File::eOpenOptionTruncate;
+
+            log_stream_sp.reset (new StreamFile (log_file, options));
             m_log_streams[log_file] = log_stream_sp;
         }
     }
diff --git a/lldb/source/Core/StreamFile.cpp b/lldb/source/Core/StreamFile.cpp
index 9f8dd62..8c9700c 100644
--- a/lldb/source/Core/StreamFile.cpp
+++ b/lldb/source/Core/StreamFile.cpp
@@ -54,6 +54,13 @@
 {
 }
 
+StreamFile::StreamFile (const char *path,
+                        uint32_t options,
+                        uint32_t permissions) :
+    Stream(),
+    m_file(path, options, permissions)
+{
+}
 
 StreamFile::~StreamFile()
 {
diff --git a/lldb/test/logging/TestLogging.py b/lldb/test/logging/TestLogging.py
index 11d4321..aea077d 100644
--- a/lldb/test/logging/TestLogging.py
+++ b/lldb/test/logging/TestLogging.py
@@ -2,7 +2,7 @@
 Test lldb logging.  This test just makes sure logging doesn't crash, and produces some output.
 """
 
-import os, time
+import os, time, string
 import unittest2
 import lldb
 from lldbtest import *
@@ -10,6 +10,15 @@
 class LogTestCase(TestBase):
 
     mydir = TestBase.compute_mydir(__file__)
+    append_log_file = "lldb-commands-log-append.txt"
+    truncate_log_file = "lldb-commands-log-truncate.txt"
+
+
+    @classmethod
+    def classCleanup(cls):
+        """Cleanup the test byproducts."""
+        cls.RemoveTempFile(cls.truncate_log_file)
+        cls.RemoveTempFile(cls.append_log_file)
 
     @unittest2.skipUnless(sys.platform.startswith("darwin"), "requires Darwin")
     @dsym_test
@@ -56,6 +65,48 @@
 
         self.assertTrue(log_lines > 0, "Something was written to the log file.")
 
+    # Check that lldb truncates its log files
+    def test_log_truncate (self):
+        if (os.path.exists (self.truncate_log_file)):
+            os.remove (self.truncate_log_file)
+
+        # put something in our log file
+        with open(self.truncate_log_file, "w") as f:
+            for i in range(1, 1000):
+                f.write("bacon\n")
+
+        self.runCmd ("log enable -t -f '%s' lldb commands" % (self.truncate_log_file))
+        self.runCmd ("help log")
+        self.runCmd ("log disable lldb")
+
+        self.assertTrue (os.path.isfile (self.truncate_log_file))
+        with open(self.truncate_log_file, "r") as f:
+            contents = f.read ()
+
+        # check that it got removed
+        self.assertTrue(string.find(contents, "bacon") == -1)
+
+    # Check that lldb can append to a log file
+    def test_log_append (self):
+        if (os.path.exists (self.append_log_file)):
+            os.remove (self.append_log_file)
+
+        # put something in our log file
+        with open(self.append_log_file, "w") as f:
+            f.write("bacon\n")
+
+        self.runCmd ("log enable -t -a -f '%s' lldb commands" % (self.append_log_file))
+        self.runCmd ("help log")
+        self.runCmd ("log disable lldb")
+
+        self.assertTrue (os.path.isfile (self.append_log_file))
+        with open(self.append_log_file, "r") as f:
+            contents = f.read ()
+
+        # check that it is still there
+        self.assertTrue(string.find(contents, "bacon") == 0)
+
+
 if __name__ == '__main__':
     import atexit
     lldb.SBDebugger.Initialize()