chromium/chrome/common/logging_chrome.cc

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <string_view>

#include "build/build_config.h"
// Need to include this before most other files because it defines
// IPC_MESSAGE_LOG_ENABLED. We need to use it to define
// IPC_MESSAGE_MACROS_LOG_ENABLED so render_messages.h will generate the
// ViewMsgLog et al. functions.
#include "ipc/ipc_buildflags.h"

// On Windows, the about:ipc dialog shows IPCs; on POSIX, we hook up a
// logger in this file.  (We implement about:ipc on Mac but implement
// the loggers here anyway).  We need to do this real early to be sure
// IPC_MESSAGE_MACROS_LOG_ENABLED doesn't get undefined.
#if BUILDFLAG(IS_POSIX) && BUILDFLAG(IPC_MESSAGE_LOG_ENABLED)
#define IPC_MESSAGE_MACROS_LOG_ENABLED
#include "content/public/common/content_ipc_logging.h"
#define IPC_LOG_TABLE_ADD_ENTRY(msg_id, logger)
#include "chrome/common/all_messages.h"
#endif

#if BUILDFLAG(IS_WIN)
#include <windows.h>
#endif

#include <fstream>
#include <memory>
#include <string>

#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/debug/debugger.h"
#include "base/debug/dump_without_crashing.h"
#include "base/environment.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/metrics/statistics_recorder.h"
#include "base/path_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "build/chromeos_buildflags.h"
#include "chrome/common/buildflags.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/env_vars.h"
#include "chrome/common/logging_chrome.h"
#include "content/public/common/content_switches.h"
#include "ipc/ipc_logging.h"

#if BUILDFLAG(IS_CHROMEOS)
#include "base/i18n/time_formatting.h"
#include "third_party/icu/source/i18n/unicode/timezone.h"
#endif

#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ash/constants/ash_switches.h"
#endif

#if BUILDFLAG(IS_WIN)
#include <initguid.h>

#include "base/logging_win.h"
#include "base/process/process_info.h"
#include "base/syslog_logging.h"
#include "base/win/scoped_handle.h"
#include "base/win/win_util.h"
#include "chrome/common/win/eventlog_messages.h"
#include "chrome/install_static/install_details.h"
#include "sandbox/policy/switches.h"
#endif

namespace logging {
namespace {

// When true, this means that error dialogs should not be shown.
bool dialogs_are_suppressed_ =;
ScopedLogAssertHandler* assert_handler_ =;

// This should be true for exactly the period between the end of
// InitChromeLogging() and the beginning of CleanupChromeLogging().
bool chrome_logging_initialized_ =;

// Set if we called InitChromeLogging() but failed to initialize.
bool chrome_logging_failed_ =;

// This should be true for exactly the period between the end of
// InitChromeLogging() and the beginning of CleanupChromeLogging().
bool chrome_logging_redirected_ =;

// The directory on which we do rotation of log files instead of switching
// with symlink. Because this directory doesn't support symlinks and the logic
// doesn't work correctly.
#if BUILDFLAG(IS_CHROMEOS_ASH)
constexpr char kChronosHomeDir[] = "/home/chronos/user/";
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

#if BUILDFLAG(IS_WIN)
// {7FE69228-633E-4f06-80C1-527FEA23E3A7}
const GUID kChromeTraceProviderName = {
    0x7fe69228,
    0x633e,
    0x4f06,
    {0x80, 0xc1, 0x52, 0x7f, 0xea, 0x23, 0xe3, 0xa7}};
#endif

// Assertion handler for logging errors that occur when dialogs are
// silenced.  To record a new error, pass the log string associated
// with that error in the str parameter.
NOINLINE void SilentRuntimeAssertHandler(const char* file,
                                         int line,
                                         const std::string_view message,
                                         const std::string_view stack_trace) {}

// Suppresses error/assertion dialogs and enables the logging of
// those errors into silenced_errors_.
void SuppressDialogs() {}

#if BUILDFLAG(IS_WIN)
base::win::ScopedHandle GetLogInheritedHandle(
    const base::CommandLine& command_line) {
  auto handle_str = command_line.GetSwitchValueNative(switches::kLogFile);
  uint32_t handle_value = 0;
  if (!base::StringToUint(handle_str, &handle_value)) {
    return base::win::ScopedHandle();
  }
  // Duplicate the handle from the command line so that different things can
  // init logging. This means the handle from the parent is never closed, but
  // there will only be one of these in the process.
  HANDLE log_handle = nullptr;
  if (!::DuplicateHandle(GetCurrentProcess(),
                         base::win::Uint32ToHandle(handle_value),
                         GetCurrentProcess(), &log_handle, 0,
                         /*bInheritHandle=*/FALSE, DUPLICATE_SAME_ACCESS)) {
    return base::win::ScopedHandle();
  }
  // Transfer ownership to the caller.
  return base::win::ScopedHandle(log_handle);
}
#endif

// `filename_is_handle`, will be set to `true` if the log-file switch contains
// an inherited handle value rather than a filepath, and `false` otherwise.
LoggingDestination LoggingDestFromCommandLine(
    const base::CommandLine& command_line,
    bool& filename_is_handle) {}

}  // anonymous namespace

LoggingDestination DetermineLoggingDestination(
    const base::CommandLine& command_line) {}

#if BUILDFLAG(IS_CHROMEOS)
bool RotateLogFile(const base::FilePath& target_path) {
  DCHECK(!target_path.empty());
  // If the old log file doesn't exist, do nothing.
  if (!base::PathExists(target_path)) {
    return true;
  }

  // Retrieve the creation time of the old log file.
  base::File::Info info;
  {
    // Opens a file, only if it exists.
    base::File fp(target_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
    if (!fp.IsValid() || !fp.GetInfo(&info)) {
      // On failure, keep using the same file.
      return false;
    }
  }

  // Generate the rotated log path name from the creation time.
  // (eg. "/home/chrome/user/log/chrome_220102-030405")
  base::Time timestamp = info.creation_time;
  base::FilePath rotated_path = GenerateTimestampedName(target_path, timestamp);

  // Rare case: if the target path already exists, generate the alternative by
  // incrementing the timestamp. This may happen when the Chrome restarts
  // multiple times in a second.
  while (base::PathExists(rotated_path)) {
    timestamp += base::Seconds(1);
    rotated_path = GenerateTimestampedName(target_path, timestamp);
  }

  // Rename the old log file: |target_path| => |rotated_path|.
  // We don't use |base::Move|, since we don't consider the inter-filesystem
  // move in this logic. The current logic depends on the fact that the ctime
  // won't be changed after rotation, but ctime may be changed on
  // inter-filesystem move.
  if (!base::ReplaceFile(target_path, rotated_path, nullptr)) {
    PLOG(ERROR) << "Failed to rotate the log files: " << target_path << " => "
                << rotated_path;
    return false;
  }

  return true;
}
#endif  // BUILDFLAG(IS_CHROMEOS)

#if BUILDFLAG(IS_CHROMEOS_ASH)
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;
    bool extensionless_symlink_exists =
        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 (extensionless_symlink_exists &&
          !base::DeleteFile(extensionless_path)) {
        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.

  // Check for existence of the symlink.
  base::FilePath target_path;
  bool symlink_exists = base::ReadSymbolicLink(symlink_path, &target_path);

  if (symlink_exists && !new_log)
    return target_path;

  // Remove any extension before time-stamping.
  target_path = GenerateTimestampedName(symlink_path.RemoveExtension(),
                                        base::Time::Now());

  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.
  base::FilePath relative_target_path = target_path.BaseName();
  if (!base::CreateSymbolicLink(relative_target_path, symlink_path)) {
    DPLOG(ERROR) << "Unable to create symlink " << symlink_path.value()
                 << " pointing at " << relative_target_path.value();
  }
  return target_path;
}

void RemoveSymlinkAndLog(const base::FilePath& link_path,
                         const base::FilePath& target_path) {
  if (::unlink(link_path.value().c_str()) == -1)
    DPLOG(WARNING) << "Unable to unlink symlink " << link_path.value();
  if (target_path != link_path && ::unlink(target_path.value().c_str()) == -1)
    DPLOG(WARNING) << "Unable to unlink log file " << target_path.value();
}

base::FilePath GetSessionLogDir(const base::CommandLine& command_line) {
  std::string log_dir;
  std::unique_ptr<base::Environment> env(base::Environment::Create());
  if (!env->GetVar(env_vars::kSessionLogDir, &log_dir))
    NOTREACHED_IN_MIGRATION();
  return base::FilePath(log_dir);
}

base::FilePath GetSessionLogFile(const base::CommandLine& command_line) {
  return GetSessionLogDir(command_line)
      .Append(GetLogFileName(command_line).BaseName());
}
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

#if BUILDFLAG(IS_CHROMEOS)
base::FilePath SetUpLogFile(const base::FilePath& target_path, bool new_log) {
#if BUILDFLAG(IS_CHROMEOS_ASH)
  const bool supports_symlinks =
      !(target_path.IsAbsolute() &&
        base::StartsWith(target_path.value(), kChronosHomeDir));

  // TODO(crbug.com/40225776): Remove the old symlink logic.
  if (supports_symlinks) {
    // As for now, we keep the original log rotation logic on the file system
    // which supports symlinks.
    return SetUpSymlinkIfNeeded(target_path, new_log);
  }
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

  // Chrome OS doesn't support symlinks on this file system, so that it uses
  // the rotation logic which doesn't use symlinks.
  if (!new_log) {
    // Keep using the same log file without doing anything.
    return target_path;
  }

  // For backward compatibility, ignore a ".LATEST" extension the way
  // |SetUpSymlinkIfNeeded()| does.
  base::FilePath bare_path = target_path;
  if (target_path.Extension() == ".LATEST") {
    bare_path = target_path.ReplaceExtension("");
  }

  // Try to rotate the log.
  if (!RotateLogFile(bare_path)) {
    PLOG(ERROR) << "Failed to rotate the log file: " << bare_path.value()
                << ". Keeping using the same log file without rotating.";
  }

  return bare_path;
}
#endif  // BUILDFLAG(IS_CHROMEOS)

void InitChromeLogging(const base::CommandLine& command_line,
                       OldFileDeletionState delete_old_log_file) {}

// This is a no-op, but we'll keep it around in case
// we need to do more cleanup in the future.
void CleanupChromeLogging() {}

base::FilePath GetLogFileName(const base::CommandLine& command_line) {}

bool DialogsAreSuppressed() {}

#if BUILDFLAG(IS_CHROMEOS)
base::FilePath GenerateTimestampedName(const base::FilePath& base_path,
                                       base::Time timestamp) {
  return base_path.InsertBeforeExtensionASCII(
      base::UnlocalizedTimeFormatWithPattern(timestamp, "_yyMMdd-HHmmss",
                                             icu::TimeZone::getGMT()));
}
#endif  // BUILDFLAG(IS_CHROMEOS)

}  // namespace logging