chromium/chromecast/cast_core/child_log_process.cc

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

#include "chromecast/cast_core/child_log_process.h"

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include <string>
#include <vector>

#include "base/command_line.h"
#include "base/strings/string_split.h"

namespace chromecast {

constexpr char kChildLogProcess[] = "child_log_process";
constexpr char kChildLogProcessArgs[] = "child_log_process_args";

void ForkAndRunLogProcess(std::string log_process_path,
                          std::string log_process_args) {
  if (log_process_args.size() > 2 &&
      (log_process_args[0] == '\"' && log_process_args.back() == '\"')) {
    log_process_args = log_process_args.substr(1, log_process_args.size() - 2);
  }
  std::vector<std::string> raw_args = base::SplitString(
      log_process_args, " ", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
  std::vector<const char*> argv = {log_process_path.c_str()};
  for (const auto& arg : raw_args) {
    argv.push_back(arg.c_str());
  }
  argv.push_back(nullptr);

  int pipefds[2];
  pid_t cpid;
  if (pipe(pipefds) == -1) {
    fprintf(stderr, "Could not create pipes for logging process: %s\n",
            strerror(errno));
    fflush(stderr);
    return;
  }
  cpid = fork();
  if (cpid == -1) {
    fprintf(stderr, "Could not fork new process for logging process: %s\n",
            strerror(errno));
    fflush(stderr);
    close(pipefds[0]);
    close(pipefds[1]);
    return;
  }

  if (cpid == 0) {
    // child
    close(pipefds[1]);  // writable end
    dup2(pipefds[0], fileno(stdin));
    close(pipefds[0]);
    int ret =
        execv(log_process_path.c_str(), const_cast<char* const*>(&argv[0]));
    if (ret == -1) {
      fprintf(stderr, "Could not start process: %s error: %s\n",
              log_process_path.c_str(), strerror(errno));
      _exit(1);
    }
  } else {
    // parent
    close(pipefds[0]);  // readable end

    int original_stderr = dup(fileno(stderr));
    int original_stdout = dup(fileno(stdout));
    if (dup2(pipefds[1], fileno(stdout)) == -1 ||
        dup2(pipefds[1], fileno(stderr)) == -1) {
      dup2(original_stderr, fileno(stderr));
      dup2(original_stdout, fileno(stdout));
      fprintf(stderr,
              "Failed to map stderr and stdout to writable pipe for logging\n");
      fflush(stderr);
    } else {
      printf("Log client initialized.\n");
      close(original_stderr);
      close(original_stdout);
    }
    close(pipefds[1]);  // stderr now references the pipe, or dup2 failed.
                        // regardless we can close our old fd.
  }
}

void ForkAndRunLogProcessIfSpecified(const int argc, const char* const* argv) {
  base::CommandLine::Init(argc, argv);
  auto* command_line = base::CommandLine::ForCurrentProcess();
  if (command_line->HasSwitch(kChildLogProcess)) {
    ForkAndRunLogProcess(
        command_line->GetSwitchValueASCII(kChildLogProcess),
        command_line->GetSwitchValueASCII(kChildLogProcessArgs));
  }
}

}  // namespace chromecast