chromeos: logging: maintain chrome.LATEST and chrome.PREVIOUS links

In many situations the chrome log is restarted after a crash, and
the previous log (with the information that matters) is not included
in feedback reports.  This changes logging for Chrome OS only (which
already has a bunch of specialized code) to maintain two symlinks
to the two most recent logs.

BUG=chromium:731158
TEST=tested on chromebook, with changes to debugd and chromeos-login

Change-Id: Ia7d037fcbf04c392f6fdc00394c6f73bf92685d9
Reviewed-on: https://chromium-review.googlesource.com/599026
Commit-Queue: Luigi Semenzato <[email protected]>
Reviewed-by: Dan Erat <[email protected]>
Reviewed-by: Lei Zhang <[email protected]>
Cr-Commit-Position: refs/heads/master@{#493506}
diff --git a/chrome/common/logging_chrome.cc b/chrome/common/logging_chrome.cc
index dc58f8d2..8899916 100644
--- a/chrome/common/logging_chrome.cc
+++ b/chrome/common/logging_chrome.cc
@@ -161,21 +161,51 @@
 base::FilePath SetUpSymlinkIfNeeded(const base::FilePath& symlink_path,
                                     bool new_log) {
   DCHECK(!symlink_path.empty());
+  // For backward compatibility, set up a .../chrome symlink to
+  // .../chrome.LATEST as needed.  This code needs to run only
+  // after the migration (i.e. the addition of chrome.LATEST).
+  if (symlink_path.Extension() == ".LATEST") {
+    base::FilePath extensionless_path = symlink_path.ReplaceExtension("");
+    base::FilePath target_path;
 
-  // If not starting a new log, then just log through the existing
-  // symlink, but if the symlink doesn't exist, create it.  If
-  // starting a new log, then delete the old symlink and make a new
-  // one to a fresh log file.
+    base::ReadSymbolicLink(extensionless_path, &target_path);
+    if (target_path != symlink_path) {
+      // No link, or wrong link.  Clean up.  This should happen only once in
+      // each log directory after the OS version update, but some of those
+      // directories may not be accessed for a long time, so this code needs to
+      // stay in forever :/
+      if (base::PathExists(extensionless_path) &&
+          !base::DeleteFile(extensionless_path, false)) {
+        DPLOG(WARNING) << "Cannot delete " << extensionless_path.value();
+      }
+      // After cleaning up, create the symlink.
+      if (!base::CreateSymbolicLink(symlink_path, extensionless_path)) {
+        DPLOG(ERROR) << "Cannot create " << extensionless_path.value();
+      }
+    }
+  }
+
+  // If not starting a new log, then just log through the existing symlink, but
+  // if the symlink doesn't exist, create it.
+  //
+  // If starting a new log, then rename the old symlink as
+  // symlink_path.PREVIOUS and make a new symlink to a fresh log file.
   base::FilePath target_path;
   bool symlink_exists = base::PathExists(symlink_path);
   if (new_log || !symlink_exists) {
     target_path = GenerateTimestampedName(symlink_path, base::Time::Now());
 
-    // We don't care if the unlink fails; we're going to continue anyway.
-    if (::unlink(symlink_path.value().c_str()) == -1) {
-      if (symlink_exists) // only warn if we might expect it to succeed.
-        DPLOG(WARNING) << "Unable to unlink " << symlink_path.value();
+    if (symlink_exists) {
+      base::FilePath previous_symlink_path =
+          symlink_path.ReplaceExtension(".PREVIOUS");
+      // Rename symlink to .PREVIOUS.  This nukes an existing symlink just like
+      // the rename(2) syscall does.
+      if (!base::ReplaceFile(symlink_path, previous_symlink_path, nullptr)) {
+        DPLOG(WARNING) << "Cannot rename " << symlink_path.value() << " to "
+                       << previous_symlink_path.value();
+      }
     }
+    // If all went well, the symlink no longer exists.  Recreate it.
     if (!base::CreateSymbolicLink(target_path, symlink_path)) {
       DPLOG(ERROR) << "Unable to create symlink " << symlink_path.value()
                    << " pointing at " << target_path.value();
@@ -231,7 +261,6 @@
                        OldFileDeletionState delete_old_log_file) {
   DCHECK(!chrome_logging_initialized_) <<
     "Attempted to initialize logging when it was already initialized.";
-
   LoggingDestination logging_dest = DetermineLoggingDestination(command_line);
   LogLockingState log_locking_state = LOCK_LOG_FILE;
   base::FilePath log_path;
@@ -379,10 +408,17 @@
 bool DialogsAreSuppressed() {
   return dialogs_are_suppressed_;
 }
+
+#if defined(OS_CHROMEOS)
 base::FilePath GenerateTimestampedName(const base::FilePath& base_path,
                                        base::Time timestamp) {
   base::Time::Exploded time_deets;
   timestamp.LocalExplode(&time_deets);
+  base::FilePath new_path = base_path;
+  // Assume that the base_path is "chrome.LATEST", and remove the extension.
+  // Ideally we would also check the value of base_path, but we cannot reliably
+  // log anything here, and aborting seems too harsh a choice.
+  new_path = new_path.ReplaceExtension("");
   std::string suffix = base::StringPrintf("_%02d%02d%02d-%02d%02d%02d",
                                           time_deets.year,
                                           time_deets.month,
@@ -390,7 +426,8 @@
                                           time_deets.hour,
                                           time_deets.minute,
                                           time_deets.second);
-  return base_path.InsertBeforeExtensionASCII(suffix);
+  return new_path.InsertBeforeExtensionASCII(suffix);
 }
+#endif  // defined(OS_CHROMEOS)
 
 }  // namespace logging