blob: 792a674b3e1c869b519349f680b03a0243410a3a [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <dirent.h>
#include <glob.h>
#include <sys/types.h>
#include <chrono>
#include <iostream>
#include <regex>
#include <string>
#include <thread>
#include <vector>
#include <fuchsia/modular/cpp/fidl.h>
#include <fuchsia/modular/internal/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async/cpp/future.h>
#include <lib/async/cpp/task.h>
#include <lib/fdio/util.h>
#include <lib/fxl/command_line.h>
#include <lib/fxl/files/file.h>
#include <lib/fxl/log_settings_command_line.h>
#include <lib/fxl/strings/string_printf.h>
#include "lib/fxl/functional/make_copyable.h"
#include "peridot/bin/sessionctl/logger.h"
#include "peridot/bin/sessionctl/session_ctl_app.h"
#include "peridot/bin/sessionctl/session_ctl_constants.h"
using ::fuchsia::modular::PuppetMaster;
using ::fuchsia::modular::PuppetMasterPtr;
struct DebugService {
std::string name;
std::string service_path;
std::string GetUsage() {
return R"(sessionctl <flags> <command> <argument>
sessionctl add_mod slider_mod
sessionctl --mod_name=mod1 --story_name=story1 --focus_mod=false
--focus_story=false add_mod slider_mod
sessionctl --story_name=story1 remove_mod slider_mod
Don't focus the mod.
Don't focus the story.
If flag is set output json for consuming instead of text.
Usage: [--story_name=foo] [--mod_name=bar] [--focus_mod=false] [--focus_story=false] add_mod MOD_URL
Add a new mod or update an existing mod if a mod with --mod_name already
exists in --story_name.
Defaults --story_name and --mod_name to MOD_URL.
Defaults --focus_mod and --focus_story to 'true'.
Mods have a unique "mod_url". It's the mod package's name.
In fuchsia_package_name = "mod_url" or mod_url comes from
flutter_app("mod_url") when there is no fuchsia_package_name set.
optional: --story_name, --mod_name, --focus_mod, --focus_story
Usage: [--story_name=foo] remove_mod MOD_NAME
Removes an existing mod by name. If the mod was added with add_mod,
use the value you passed to add_mod's --mod_name flag or the default
value which would be the mod_url.
Defaults --story_name to MOD_NAME.
The name of the mod.
optional: --story_name
Usage: delete_story STORY_NAME
Deletes the story.
The name of the story.
List all the stories in the current session.
Logs in a guest user.
Restarts the current session.)";
void FindDebugServicesForPath(const char* glob_str, const char* regex_str,
std::vector<DebugService>* services) {
glob_t globbuf;
bool service_exists = glob(glob_str, 0, nullptr, &globbuf) == 0;
std::regex name_regex(regex_str);
if (service_exists) {
for (size_t i = 0; i < globbuf.gl_pathc; ++i) {
DebugService s;
s.service_path = globbuf.gl_pathv[i];
std::smatch match;
FXL_CHECK(std::regex_search(s.service_path, match, name_regex))
<< s.service_path; = match[1];
// Returns a list of all running sessions.
std::vector<DebugService> FindAllSessions() {
const char kRegex[] = "/sessionmgr.cmx/(\\d+)";
// See peridot/bin/sessionmgr/'s definition of
// kSessionCtlDir for "sessionctl". These must match.
std::vector<DebugService> sessions;
FindDebugServicesForPath(modular::kSessionCtlServiceGlobPath, kRegex,
// Path to sessionctl service from a virtual console
"/hub/r/sys/*/c/sessionmgr.cmx/*/out/debug/sessionctl", kRegex,
return sessions;
PuppetMasterPtr ConnectToPuppetMaster(const DebugService& session) {
PuppetMasterPtr puppet_master;
auto request = puppet_master.NewRequest().TakeChannel();
std::string service_path = session.service_path + "/" + PuppetMaster::Name_;
if (fdio_service_connect(service_path.c_str(), request.get()) != ZX_OK) {
FXL_LOG(FATAL) << "Could not connect to PuppetMaster service in "
<< session.service_path;
return puppet_master;
fuchsia::modular::internal::BasemgrDebugPtr ConnectToBasemgr() {
const char kRegex[] = "/basemgr.cmx/(\\d+)";
std::vector<DebugService> services;
FindDebugServicesForPath(modular::kBasemgrDebugServiceGlobPath, kRegex,
// Path to basemgr debug service from a virtual console
kRegex, &services);
if (services.empty()) {
return nullptr;
FXL_CHECK(services.size() == 1);
std::string service_path = services[0].service_path;
fuchsia::modular::internal::BasemgrDebugPtr basemgr;
auto request = basemgr.NewRequest().TakeChannel();
if (fdio_service_connect(service_path.c_str(), request.get()) != ZX_OK) {
FXL_LOG(FATAL) << "Could not connect to basemgr service in "
<< service_path;
return basemgr;
// Returns true if a guest user was logged in.
bool LoginAsGuest(bool has_running_sessions,
fuchsia::modular::internal::BasemgrDebug* basemgr,
modular::Logger logger) {
if (has_running_sessions) {
"A user is already logged in. You may log a guest user out "
"by running 'sessionctl restart_session' or you may issue "
"any other sessionctl command.");
return false;
logger.Log(modular::kLoginGuestCommandString, std::vector<std::string>());
return true;
// Returns true if a guest user was logged in.
bool LoginDefaultGuestUser(fuchsia::modular::internal::BasemgrDebug* basemgr,
modular::Logger logger,
std::vector<DebugService>* sessions,
std::string cmd) {
std::cout << "Logging in as a guest user in the absence of running sessions."
<< std::endl;
LoginAsGuest(/*has_running_sessions=*/false, basemgr, logger);
// Wait 2 seconds to allow sessionmgr to initialize
std::cout << "Finding sessions..." << std::endl;
*sessions = FindAllSessions();
if (sessions->empty()) {
"Unable find a running session after logging in. "
"Please try your command again.");
return false;
return true;
int main(int argc, const char** argv) {
async::Loop loop(&kAsyncLoopConfigAttachToThread);
const auto command_line = fxl::CommandLineFromArgcArgv(argc, argv);
const auto& positional_args = command_line.positional_args();
const auto& cmd = positional_args.empty() ? "" : positional_args[0];
const modular::Logger logger(
auto basemgr = ConnectToBasemgr();
if (!basemgr) {
logger.LogError(cmd, "Could not find a running basemgr. Is it running?");
return 1;
auto sessions = FindAllSessions();
// Continue with log in flow if user issued a login_guest command
if (cmd == modular::kLoginGuestCommandString) {
if (LoginAsGuest(/*has_running_sessions=*/!sessions.empty(), basemgr.get(),
logger)) {
return 0;
return 1;
// Log in a guest user if no session is found before continuing to execute
// the requested command
if (sessions.empty()) {
// Exit here if no sessions were found after logging in a guest user
if (!LoginDefaultGuestUser(basemgr.get(), logger, &sessions, cmd)) {
return 1;
if (!command_line.HasOption(modular::kJsonOutFlagString)) {
std::cout << "Found the following sessions:\n\n";
for (const auto& session : sessions) {
std::cout << "\t" << << ": " << session.service_path
<< std::endl;
std::cout << std::endl;
// To get a PuppetMaster service for a session, use the following code:
PuppetMasterPtr puppet_master = ConnectToPuppetMaster(sessions[0]);
modular::SessionCtlApp app(basemgr.get(), puppet_master.get(), logger,
loop.dispatcher(), [&loop] { loop.Quit(); });
std::string parsing_error = app.ExecuteCommand(cmd, command_line);
if (parsing_error == modular::kGetUsageErrorString) {
// Print help if command doesn't match a valid command.
std::cout << GetUsage() << std::endl;
return 1;
if (!parsing_error.empty()) {
return 1;
return 0;