| // 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 <fuchsia/modular/cpp/fidl.h> |
| #include <fuchsia/modular/internal/cpp/fidl.h> |
| #include <glob.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async/cpp/future.h> |
| #include <lib/async/cpp/task.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/fdio/fd.h> |
| #include <lib/fdio/fdio.h> |
| #include <src/lib/fxl/command_line.h> |
| #include <src/lib/fxl/log_settings_command_line.h> |
| #include <src/lib/fxl/strings/string_printf.h> |
| #include <sys/types.h> |
| |
| #include <chrono> |
| #include <iostream> |
| #include <regex> |
| #include <string> |
| #include <thread> |
| #include <vector> |
| |
| #include "peridot/bin/sessionctl/logger.h" |
| #include "peridot/bin/sessionctl/session_ctl_app.h" |
| #include "peridot/bin/sessionctl/session_ctl_constants.h" |
| #include "src/lib/files/file.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> |
| Example: |
| 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 |
| |
| <flags> |
| --story_name=STORY_NAME |
| --mod_name=MOD_NAME |
| --focus_mod=false |
| Don't focus the mod. |
| --focus_story=false |
| Don't focus the story. |
| --json_out |
| If flag is set output json for consuming instead of text. |
| --wait_for_session |
| Blocks progress on completing a command until a guest user is logged in. |
| |
| <command> |
| add_mod |
| 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'. |
| |
| MOD_URL |
| This can be either the mod's full package url or the mod component's name. |
| The mod components name will be converted to the following package url |
| format: fuchsia-pkg://fuchsia.com/MOD_URL#meta/MOD_URL.cmx. |
| |
| optional: --story_name, --mod_name, --focus_mod, --focus_story |
| |
| remove_mod |
| 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. |
| |
| MOD_NAME |
| The name of the mod. |
| |
| optional: --story_name |
| |
| delete_story |
| Usage: delete_story STORY_NAME |
| |
| Deletes the story. |
| |
| STORY_NAME |
| The name of the story. |
| |
| delete_all_stories |
| Deletes all stories in the current session. |
| |
| list_stories |
| List all the stories in the current session. |
| |
| login_guest |
| Logs in a guest user. |
| |
| restart_session |
| Restarts the current session. |
| |
| next_session_shell |
| Toggles to the next defined session shell. |
| |
| help |
| Lists the available commands.)"; |
| } |
| |
| 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; |
| s.name = match[1]; |
| services->push_back(std::move(s)); |
| } |
| globfree(&globbuf); |
| } |
| } |
| |
| // Returns a list of all running sessions. |
| std::vector<DebugService> FindAllSessions() { |
| const char kRegex[] = "/sessionmgr.cmx/(\\d+)"; |
| // See peridot/bin/sessionmgr/sessionmgr_impl.cc's definition of |
| // kSessionCtlDir for "sessionctl". These must match. |
| std::vector<DebugService> sessions; |
| FindDebugServicesForPath(modular::kSessionCtlServiceGlobPath, kRegex, |
| &sessions); |
| |
| // Path to sessionctl service from a virtual console |
| FindDebugServicesForPath( |
| "/hub/r/sys/*/c/sessionmgr.cmx/*/out/debug/sessionctl", kRegex, |
| &sessions); |
| 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, |
| &services); |
| |
| // Path to basemgr debug service from a virtual console |
| FindDebugServicesForPath("/hub/r/sys/*/c/basemgr.cmx/*/out/debug/basemgr", |
| 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) { |
| logger.LogError(modular::kLoginGuestCommandString, |
| "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; |
| } |
| |
| basemgr->LoginAsGuest(); |
| 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, |
| bool wait_for_session) { |
| std::cout << "Logging in as a guest user in the absence of running sessions." |
| << std::endl; |
| LoginAsGuest(/*has_running_sessions=*/false, basemgr, logger); |
| |
| do { |
| // Wait 2 seconds to allow sessionmgr to initialize |
| std::this_thread::sleep_for(std::chrono::seconds(2)); |
| std::cout << "Finding sessions..." << std::endl; |
| *sessions = FindAllSessions(); |
| } while (wait_for_session && sessions->empty()); |
| |
| if (sessions->empty()) { |
| logger.LogError(cmd, |
| "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); |
| fxl::SetLogSettingsFromCommandLine(command_line); |
| const auto& positional_args = command_line.positional_args(); |
| const auto& cmd = positional_args.empty() ? "" : positional_args[0]; |
| |
| if (cmd == modular::kHelpCommandString || cmd.empty()) { |
| std::cout << GetUsage() << std::endl; |
| return 1; |
| } |
| |
| const modular::Logger logger( |
| command_line.HasOption(modular::kJsonOutFlagString)); |
| |
| 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()) { |
| if (cmd == modular::kRestartSessionCommandString) { |
| logger.LogError(cmd, "No session to be restarted."); |
| return 1; |
| } |
| |
| bool wait_for_session = |
| command_line.HasOption(modular::kWaitForSessionFlagString); |
| |
| // Exit here if no sessions were found after logging in a guest user |
| if (!LoginDefaultGuestUser(basemgr.get(), logger, &sessions, cmd, |
| wait_for_session)) { |
| 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.name << ": " << 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; |
| } |
| |
| loop.Run(); |
| |
| return 0; |
| } |