Merge "Tee output of assemble_cvd and run_cvd to a log file"
diff --git a/common/libs/fs/Android.bp b/common/libs/fs/Android.bp
index 9f5d55e..0e49e5a 100644
--- a/common/libs/fs/Android.bp
+++ b/common/libs/fs/Android.bp
@@ -18,6 +18,7 @@
     srcs: [
         "shared_buf.cc",
         "shared_fd.cpp",
+        "tee.cpp",
     ],
     shared: {
         shared_libs: [
@@ -46,6 +47,7 @@
     srcs: [
         "shared_buf.cc",
         "shared_fd.cpp",
+        "tee.cpp",
     ],
     shared_libs: [
         "libbase",
diff --git a/common/libs/fs/shared_buf.cc b/common/libs/fs/shared_buf.cc
index 84397a0..7400f03 100644
--- a/common/libs/fs/shared_buf.cc
+++ b/common/libs/fs/shared_buf.cc
@@ -28,6 +28,22 @@
 
 const size_t BUFF_SIZE = 1 << 14;
 
+static ssize_t WriteAll(SharedFD fd, const char* buf, size_t size) {
+  size_t total_written = 0;
+  ssize_t written = 0;
+  while ((written = fd->Write((void*)&(buf[total_written]), size - total_written)) > 0) {
+    if (written < 0) {
+      errno = fd->GetErrno();
+      return written;
+    }
+    total_written += written;
+    if (total_written == size) {
+      break;
+    }
+  }
+  return total_written;
+}
+
 } // namespace
 
 ssize_t ReadAll(SharedFD fd, std::string* buf) {
@@ -63,19 +79,11 @@
 }
 
 ssize_t WriteAll(SharedFD fd, const std::string& buf) {
-  size_t total_written = 0;
-  ssize_t written = 0;
-  while ((written = fd->Write((void*)&(buf[total_written]), buf.size() - total_written)) > 0) {
-    if (written < 0) {
-      errno = fd->GetErrno();
-      return written;
-    }
-    total_written += written;
-    if (total_written == buf.size()) {
-      break;
-    }
-  }
-  return total_written;
+  return WriteAll(fd, buf.data(), buf.size());
+}
+
+ssize_t WriteAll(SharedFD fd, const std::vector<char>& buf) {
+  return WriteAll(fd, buf.data(), buf.size());
 }
 
 } // namespace cvd
diff --git a/common/libs/fs/shared_buf.h b/common/libs/fs/shared_buf.h
index 3577fa1..bebb5bc 100644
--- a/common/libs/fs/shared_buf.h
+++ b/common/libs/fs/shared_buf.h
@@ -52,4 +52,14 @@
  */
 ssize_t WriteAll(SharedFD fd, const std::string& buf);
 
+/**
+ * Writes to fd until writing all bytes in buf.
+ *
+ * On a successful write, returns buf.size().
+ *
+ * If a write error is encountered, returns -1. Some data may have already been
+ * written to fd at that point.
+ */
+ssize_t WriteAll(SharedFD fd, const std::vector<char>& buf);
+
 } // namespace cvd
diff --git a/common/libs/fs/tee.cpp b/common/libs/fs/tee.cpp
new file mode 100644
index 0000000..87ad432
--- /dev/null
+++ b/common/libs/fs/tee.cpp
@@ -0,0 +1,122 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <algorithm>
+#include <iostream>
+
+#include "common/libs/glog/logging.h"
+#include "common/libs/fs/shared_buf.h"
+#include "common/libs/fs/tee.h"
+
+static const std::size_t READ_SIZE = 512;
+
+namespace cvd {
+
+TeeSubscriber* Tee::AddSubscriber(TeeSubscriber subscriber) {
+  if (reader_.joinable()) {
+    return nullptr;
+  }
+  return &targets_.emplace_back(std::move(subscriber)).handler;
+}
+
+void Tee::Start(SharedFD source) {
+  reader_ = std::thread([this, source]() {
+    while (true) {
+      // TODO(schfufelen): Use multiple buffers at once for readv
+      // TODO(schuffelen): Reuse buffers
+      TeeBufferPtr buffer = std::make_shared<std::vector<char>>(READ_SIZE);
+      ssize_t read = source->Read(buffer->data(), buffer->size());
+      if (read <= 0) {
+        for (auto& target : targets_) {
+          target.content_queue.Push(nullptr);
+        }
+        break;
+      }
+      buffer->resize(read);
+      for (auto& target : targets_) {
+        target.content_queue.Push(buffer);
+      }
+    }
+  });
+  for (auto& target : targets_) {
+    target.runner = std::thread([&target]() {
+      while (true) {
+        auto queue_chunk = target.content_queue.PopAll();
+        // TODO(schuffelen): Pass multiple buffers to support writev
+        for (auto& buffer : queue_chunk) {
+          if (!buffer) {
+            return;
+          }
+          target.handler(buffer);
+        }
+      }
+    });
+  }
+}
+
+Tee::~Tee() {
+  Join();
+}
+
+void Tee::Join() {
+  if (reader_.joinable()) {
+    reader_.join();
+  }
+  auto it = targets_.begin();
+  while (it != targets_.end()) {
+    if (it->runner.joinable()) {
+      it->runner.join();
+    }
+    it = targets_.erase(it);
+  }
+}
+
+TeeSubscriber SharedFDWriter(SharedFD fd) {
+  return [fd](const TeeBufferPtr buffer) { WriteAll(fd, *buffer); };
+}
+
+// An alternative to this would have been to modify the logger, but that would
+// not capture logs from subprocesses.
+TeeStderrToFile::TeeStderrToFile() {
+  original_stderr_ = SharedFD::Dup(2);
+
+  SharedFD stderr_read, stderr_write;
+  SharedFD::Pipe(&stderr_read, &stderr_write);
+  stderr_write->UNMANAGED_Dup2(2);
+  stderr_write->Close();
+
+  tee_.AddSubscriber(SharedFDWriter(original_stderr_));
+  tee_.AddSubscriber(
+      [this](cvd::TeeBufferPtr data) {
+        std::unique_lock lock(mutex_);
+        while (!log_file_->IsOpen()) {
+          notifier_.wait(lock);
+        }
+        cvd::WriteAll(log_file_, *data);
+      });
+  tee_.Start(std::move(stderr_read));
+}
+
+TeeStderrToFile::~TeeStderrToFile() {
+  original_stderr_->UNMANAGED_Dup2(2);
+}
+
+void TeeStderrToFile::SetFile(SharedFD file) {
+  std::lock_guard lock(mutex_);
+  log_file_ = file;
+  notifier_.notify_all();
+}
+
+} // namespace
diff --git a/common/libs/fs/tee.h b/common/libs/fs/tee.h
new file mode 100644
index 0000000..23bc3d1
--- /dev/null
+++ b/common/libs/fs/tee.h
@@ -0,0 +1,70 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <condition_variable>
+#include <functional>
+#include <list>
+#include <memory>
+#include <mutex>
+#include <thread>
+#include <string>
+#include <vector>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/thread_safe_queue/thread_safe_queue.h"
+
+namespace cvd {
+
+using TeeBufferPtr = std::shared_ptr<std::vector<char>>;
+using TeeSubscriber = std::function<void(const TeeBufferPtr)>;
+
+struct TeeTarget {
+  std::thread runner;
+  ThreadSafeQueue<TeeBufferPtr> content_queue;
+  TeeSubscriber handler;
+
+  TeeTarget(TeeSubscriber handler) : handler(handler) {}
+};
+
+class Tee {
+  std::thread reader_;
+  std::list<TeeTarget> targets_;
+public:
+  ~Tee();
+
+  TeeSubscriber* AddSubscriber(TeeSubscriber);
+
+  void Start(SharedFD source);
+  void Join();
+};
+
+TeeSubscriber SharedFDWriter(SharedFD fd);
+
+class TeeStderrToFile {
+  cvd::SharedFD log_file_;
+  cvd::SharedFD original_stderr_;
+  std::condition_variable notifier_;
+  std::mutex mutex_;
+  Tee tee_; // This should be destroyed first, so placed last.
+public:
+  TeeStderrToFile();
+  ~TeeStderrToFile();
+
+  void SetFile(SharedFD file);
+};
+
+} // namespace cvd
diff --git a/common/libs/thread_safe_queue/thread_safe_queue.h b/common/libs/thread_safe_queue/thread_safe_queue.h
index 8662928..9970d31 100644
--- a/common/libs/thread_safe_queue/thread_safe_queue.h
+++ b/common/libs/thread_safe_queue/thread_safe_queue.h
@@ -50,6 +50,14 @@
     return t;
   }
 
+  QueueImpl PopAll() {
+    std::unique_lock<std::mutex> guard(m_);
+    while (items_.empty()) {
+      new_item_.wait(guard);
+    }
+    return std::move(items_);
+  }
+
   void Push(T&& t) {
     std::lock_guard<std::mutex> guard(m_);
     DropItemsIfAtCapacity();
diff --git a/host/commands/assemble_cvd/assemble_cvd.cc b/host/commands/assemble_cvd/assemble_cvd.cc
index c829395..247aff1 100644
--- a/host/commands/assemble_cvd/assemble_cvd.cc
+++ b/host/commands/assemble_cvd/assemble_cvd.cc
@@ -20,6 +20,7 @@
 
 #include "common/libs/fs/shared_buf.h"
 #include "common/libs/fs/shared_fd.h"
+#include "common/libs/fs/tee.h"
 #include "host/commands/assemble_cvd/assembler_defs.h"
 #include "host/commands/assemble_cvd/flags.h"
 #include "host/libs/config/fetcher_config.h"
@@ -55,11 +56,13 @@
     int error_num = errno;
     if (error_num == EBADF) {
       LOG(FATAL) << "stdin was not a valid file descriptor, expected to be passed the output "
-                 << "of assemble_cvd. Did you mean to run launch_cvd?";
+                 << "of launch_cvd. Did you mean to run launch_cvd?";
       return cvd::AssemblerExitCodes::kInvalidHostConfiguration;
     }
   }
 
+  cvd::TeeStderrToFile stderr_tee;
+
   std::string input_files_str;
   {
     auto input_fd = cvd::SharedFD::Dup(0);
@@ -72,6 +75,9 @@
 
   auto config = InitFilesystemAndCreateConfig(&argc, &argv, FindFetcherConfig(input_files));
 
+  auto assembler_log_path = config->PerInstancePath("assemble_cvd.log");
+  stderr_tee.SetFile(cvd::SharedFD::Creat(assembler_log_path.c_str(), 0755));
+
   std::cout << GetConfigFilePath(*config) << "\n";
   std::cout << std::flush;
 
diff --git a/host/commands/run_cvd/main.cc b/host/commands/run_cvd/main.cc
index e352393..c77e924 100644
--- a/host/commands/run_cvd/main.cc
+++ b/host/commands/run_cvd/main.cc
@@ -42,6 +42,7 @@
 #include "common/libs/fs/shared_buf.h"
 #include "common/libs/fs/shared_fd.h"
 #include "common/libs/fs/shared_select.h"
+#include "common/libs/fs/tee.h"
 #include "common/libs/utils/environment.h"
 #include "common/libs/utils/files.h"
 #include "common/libs/utils/subprocess.h"
@@ -291,6 +292,8 @@
     }
   }
 
+  cvd::TeeStderrToFile stderr_tee;
+
   std::string input_files_str;
   {
     auto input_fd = cvd::SharedFD::Dup(0);
@@ -313,6 +316,9 @@
 
   auto config = vsoc::CuttlefishConfig::Get();
 
+  auto runner_log_path = config->PerInstancePath("run_cvd.log");
+  stderr_tee.SetFile(cvd::SharedFD::Creat(runner_log_path.c_str(), 0755));
+
   // Change working directory to the instance directory as early as possible to
   // ensure all host processes have the same working dir. This helps stop_cvd
   // find the running processes when it can't establish a communication with the