add support for displaying log files on standard input

Change-Id: I5b4ec4a170fa79089d250dbda5ef4daaaa2ea742
diff --git a/aos/linux_code/logging/binary_log_file.cc b/aos/linux_code/logging/binary_log_file.cc
index 2738c16..0142db3 100644
--- a/aos/linux_code/logging/binary_log_file.cc
+++ b/aos/linux_code/logging/binary_log_file.cc
@@ -36,8 +36,8 @@
 }
 
 bool LogFileAccessor::IsLastPage() {
-  if (is_last_page_ != 0) {
-    return is_last_page_ == 2;
+  if (is_last_page_ != Maybe::kUnknown) {
+    return is_last_page_ == Maybe::kYes;
   }
 
   struct stat info;
@@ -45,7 +45,7 @@
     PLOG(FATAL, "fstat(%d, %p) failed", fd_, &info);
   }
   bool r = offset_ == static_cast<off_t>(info.st_size - kPageSize);
-  is_last_page_ = r ? 2 : 1;
+  is_last_page_ = r ? Maybe::kYes : Maybe::kNo;
   return r;
 }
 
@@ -55,26 +55,55 @@
       PLOG(FATAL, "ftruncate(%d, %zd) failed", fd_, kPageSize);
     }
   }
-  current_ = static_cast<char *>(
-      mmap(NULL, kPageSize, PROT_READ | (writable_ ? PROT_WRITE : 0),
-           MAP_SHARED, fd_, offset_));
-  if (current_ == MAP_FAILED) {
-    PLOG(FATAL,
-         "mmap(NULL, %zd, PROT_READ [ | PROT_WRITE], MAP_SHARED, %d, %jd)"
-         " failed", kPageSize, fd_, static_cast<intmax_t>(offset_));
-  }
-  if (madvise(current_, kPageSize, MADV_SEQUENTIAL | MADV_WILLNEED) == -1) {
-    PLOG(WARNING, "madvise(%p, %zd, MADV_SEQUENTIAL | MADV_WILLNEED) failed",
-         current_, kPageSize);
+
+  if (use_read_ == Maybe::kYes) {
+    ssize_t todo = kPageSize;
+    while (todo > 0) {
+      ssize_t result = read(fd_, current_ + (kPageSize - todo), todo);
+      if (result == -1) {
+        PLOG(FATAL, "read(%d, %p, %zu) failed", fd_,
+             current_ + (kPageSize - todo), todo);
+      }
+      todo -= result;
+    }
+    CHECK_EQ(0, todo);
+  } else {
+    current_ = static_cast<char *>(
+        mmap(NULL, kPageSize, PROT_READ | (writable_ ? PROT_WRITE : 0),
+             MAP_SHARED, fd_, offset_));
+    if (current_ == MAP_FAILED) {
+      if (!writable_ && use_read_ == Maybe::kUnknown && errno == ENODEV) {
+        LOG(INFO, "Falling back to reading the file using read(2).\n");
+        use_read_ = Maybe::kYes;
+        current_ = new char[kPageSize];
+        MapNextPage();
+        return;
+      } else {
+        PLOG(FATAL,
+             "mmap(NULL, %zd, PROT_READ [ | PROT_WRITE], MAP_SHARED, %d, %jd)"
+             " failed",
+             kPageSize, fd_, static_cast<intmax_t>(offset_));
+      }
+    } else {
+      use_read_ = Maybe::kNo;
+    }
+    if (madvise(current_, kPageSize, MADV_SEQUENTIAL | MADV_WILLNEED) == -1) {
+      PLOG(WARNING, "madvise(%p, %zd, MADV_SEQUENTIAL | MADV_WILLNEED) failed",
+           current_, kPageSize);
+    }
   }
   offset_ += kPageSize;
 }
 
 void LogFileAccessor::Unmap(void *location) {
-  if (munmap(location, kPageSize) == -1) {
-    PLOG(FATAL, "munmap(%p, %zd) failed", location, kPageSize);
+  CHECK_NE(Maybe::kUnknown, use_read_);
+
+  if (use_read_ == Maybe::kNo) {
+    if (munmap(location, kPageSize) == -1) {
+      PLOG(FATAL, "munmap(%p, %zd) failed", location, kPageSize);
+    }
   }
-  is_last_page_ = 0;
+  is_last_page_ = Maybe::kUnknown;
   position_ = 0;
 }
 
@@ -123,6 +152,8 @@
 
 }  // namespace
 void LogFileReader::CheckCurrentPageReadable() {
+  if (definitely_use_read()) return;
+
   if (sigsetjmp(jump_context, 1) == 0) {
     struct sigaction action;
     action.sa_sigaction = CheckCurrentPageReadableHandler;
diff --git a/aos/linux_code/logging/binary_log_file.h b/aos/linux_code/logging/binary_log_file.h
index 54e64d8..ea4651b 100644
--- a/aos/linux_code/logging/binary_log_file.h
+++ b/aos/linux_code/logging/binary_log_file.h
@@ -46,7 +46,7 @@
   // for readers keeping up with a live writer.
   //
   // Gets initialized to 0 by ftruncate.
-  // 
+  //
   // There will be something here after the last message on a "page" set to 2
   // (by the futex_set) to indicate that the next message is on the next page.
   mutex marker;
@@ -85,6 +85,11 @@
 class LogFileAccessor {
  public:
   LogFileAccessor(int fd, bool writable);
+  ~LogFileAccessor() {
+    if (use_read_ == Maybe::kYes) {
+      delete[] current_;
+    }
+  }
 
   // Asynchronously syncs all open mappings.
   void Sync() const;
@@ -118,7 +123,15 @@
     position_ += kAlignment - (position_ % kAlignment);
   }
 
+ protected:
+  bool definitely_use_read() const { return use_read_ == Maybe::kYes; }
+  bool definitely_use_mmap() const { return use_read_ == Maybe::kNo; }
+
  private:
+  // Used for representing things that we might know to be true/false or we
+  // might not know (yet).
+  enum class Maybe { kUnknown, kYes, kNo };
+
   const int fd_;
   const bool writable_;
 
@@ -127,8 +140,10 @@
   char *current_;
   size_t position_;
 
-  // 0 = unknown, 1 = no, 2 = yes
-  int is_last_page_ = 0;
+  Maybe is_last_page_ = Maybe::kUnknown;
+
+  // Use read instead of mmap (necessary for fds that don't support mmap).
+  Maybe use_read_ = Maybe::kUnknown;
 };
 
 class LogFileReader : public LogFileAccessor {
diff --git a/aos/linux_code/logging/log_displayer.cc b/aos/linux_code/logging/log_displayer.cc
index bc00ddc..05a12fe 100644
--- a/aos/linux_code/logging/log_displayer.cc
+++ b/aos/linux_code/logging/log_displayer.cc
@@ -23,6 +23,7 @@
 const char *kArgsHelp = "[OPTION]... [FILE]\n"
     "Display log file FILE (created by BinaryLogReader) to stdout.\n"
     "FILE is \"aos_log-current\" by default.\n"
+    "FILE can also be \"-\" to read from standard input.\n"
     "\n"
     "  -n, --name NAME       only display entries from processes named NAME\n"
     "  -l, --level LEVEL     "
@@ -215,7 +216,12 @@
   fprintf(stderr, "displaying down to level %s from file '%s'\n",
       ::aos::logging::log_str(filter_level), filename);
 
-  int fd = open(filename, O_RDONLY);
+  int fd;
+  if (strcmp(filename, "-") == 0) {
+    fd = STDIN_FILENO;
+  } else {
+    fd = open(filename, O_RDONLY);
+  }
 
   if (fd == -1) {
     PLOG(FATAL, "couldn't open file '%s' for reading", filename);