blob: 68944b9417baf2fbe1de66e78f2a34f6d5cfdaa8 [file] [log] [blame] [edit]
// 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.
// The entrypoint for the starboard cast runtime. If loggy is enabled (typically
// used for partner builds), this code forks a separate process to run loggy for
// logging.
//
// Also contains logic for parsing chromium args passed as JSON (used by some
// partner platforms).
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/json/json_reader.h"
#include "base/logging.h"
#include "base/strings/string_util.h"
#include "chromecast/app/cast_main_delegate.h"
#include "chromecast/cast_core/child_log_process.h"
#include "content/public/app/content_main.h"
namespace {
constexpr char kHomeEnvOverride[] = "home-env-override";
constexpr char kRuntimeHomeSubdirUsedFile[] = ".dirty";
constexpr char kParametersKey[] = "parameters";
constexpr char kArgvKey[] = "argv";
// JSONArgsParser determines whether command line arguments were delivered
// as JSON or the standard format and, if necessary, parses the JSON
// arguments.
class JSONArgsParser {
public:
JSONArgsParser(int argc, const char** argv) {
if (!TryParseJson(argc, argv)) {
// If args were not provided as JSON, use defaults.
for (int i = 0; i < argc; i++) {
argv_.push_back(argv[i]);
}
argv_.push_back(nullptr);
}
}
int argc() { return argv_.size() - 1; }
const char** argv() { return argv_.data(); }
private:
// If the command is of the format `<command> <json>`, tries to parse
// parameters from the JSON blob in |argv[1] instead of directly from
// |argv|. Returns true if successful; otherwise, returns false.
bool TryParseJson(int argc, const char** argv) {
// Required format is `<command> <json>`
if (argc != 2) {
return false;
}
// JSON must be the following format. All keys and values are strings.
// {"parameters":{"argv":["arg1", ...]}}
std::string argv1 = std::string(argv[1]);
std::optional<base::Value::Dict> root = base::JSONReader::ReadDict(argv1);
if (!root) {
// Try to fix unquoted JSON
base::ReplaceSubstringsAfterOffset(&argv1, 0, "{", "{\"");
base::ReplaceSubstringsAfterOffset(&argv1, 0, "[", "[\"");
base::ReplaceSubstringsAfterOffset(&argv1, 0, "]", "\"]");
base::ReplaceSubstringsAfterOffset(&argv1, 0, ":", "\":\"");
base::ReplaceSubstringsAfterOffset(&argv1, 0, ",", "\",\"");
base::ReplaceSubstringsAfterOffset(&argv1, 0, ":\"[", ":[");
base::ReplaceSubstringsAfterOffset(&argv1, 0, ":\"{", ":{");
// Special case to handle unix:/tmp. This means that things like
// "valid_key_unix":"/valid_value" will fail to parse. Known issue.
base::ReplaceSubstringsAfterOffset(&argv1, 0, "unix\":\"/", "unix:/");
root = base::JSONReader::ReadDict(argv1);
}
if (!root) {
return false;
}
base::Value::Dict* v = root->FindDict(kParametersKey);
if (!v) {
return false;
}
base::Value::List* argv_list = v->FindList(kArgvKey);
if (!argv_list) {
return false;
}
for (const auto& val : *argv_list) {
// All values must be strings.
if (!val.is_string()) {
return false;
}
argv_parsed_.push_back(val.GetString());
}
// Parsing is successful, so load up |argv_| and return |true|.
argv_.push_back(argv[0]);
for (const std::string& arg : argv_parsed_) {
argv_.push_back(arg.c_str());
}
argv_.push_back(nullptr);
return true;
}
// Persists the backing memory of |argv|.
std::vector<std::string> argv_parsed_;
std::vector<const char*> argv_;
};
} // namespace
int main(int argc, const char** argv) {
JSONArgsParser args(argc, argv);
chromecast::ForkAndRunLogProcessIfSpecified(args.argc(), args.argv());
chromecast::shell::CastMainDelegate delegate;
content::ContentMainParams params(&delegate);
base::CommandLine temp_cmd(args.argc(), args.argv());
std::string home_override = temp_cmd.GetSwitchValueASCII(kHomeEnvOverride);
if (!home_override.empty()) {
LOG(INFO) << "HOME variable was previously \"" << getenv("HOME")
<< "\"; overriding to \"" << home_override << "\".";
setenv("HOME", home_override.c_str(), 1);
base::FilePath home_directory(home_override);
if (!base::DirectoryExists(home_directory)) {
CHECK(base::CreateDirectory(home_directory));
}
base::FilePath home_directory_used_file =
home_directory.AppendASCII(kRuntimeHomeSubdirUsedFile);
if (!base::PathExists(home_directory_used_file)) {
base::File(home_directory_used_file,
base::File::FLAG_CREATE | base::File::FLAG_WRITE);
}
}
params.argc = args.argc();
params.argv = args.argv();
return content::ContentMain(std::move(params));
}