OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 // This is a terminal client (i.e., a "raw" |mojo.terminal.Terminal| -- e.g., |
| 6 // moterm -- can be asked to talk to this) that prompts the user for a native |
| 7 // (Linux) binary to run and then does so (via mojo:native_support). |
| 8 // |
| 9 // E.g., first run mojo:moterm_example_app (embedded by a window manager). Then, |
| 10 // at the prompt, enter "mojo:native_run_app". At the next prompt, enter "bash" |
| 11 // (or "echo hello mojo"). |
| 12 // |
| 13 // TODO(vtl): Maybe it should optionally be able to extract the binary path (and |
| 14 // args) from the connection URL? |
| 15 |
| 16 #include <string.h> |
| 17 |
| 18 #include <string> |
| 19 #include <vector> |
| 20 |
| 21 #include "base/logging.h" |
| 22 #include "base/macros.h" |
| 23 #include "base/strings/string_split.h" |
| 24 #include "mojo/application/application_runner_chromium.h" |
| 25 #include "mojo/public/c/system/main.h" |
| 26 #include "mojo/public/cpp/application/application_connection.h" |
| 27 #include "mojo/public/cpp/application/application_delegate.h" |
| 28 #include "mojo/public/cpp/application/application_impl.h" |
| 29 #include "mojo/public/cpp/application/interface_factory.h" |
| 30 #include "mojo/public/cpp/bindings/interface_request.h" |
| 31 #include "mojo/public/cpp/bindings/strong_binding.h" |
| 32 #include "mojo/services/files/public/interfaces/files.mojom.h" |
| 33 #include "mojo/services/files/public/interfaces/ioctl.mojom.h" |
| 34 #include "mojo/services/files/public/interfaces/ioctl_terminal.mojom.h" |
| 35 #include "mojo/services/files/public/interfaces/types.mojom.h" |
| 36 #include "mojo/services/native_support/public/interfaces/process.mojom.h" |
| 37 #include "mojo/services/terminal/public/interfaces/terminal_client.mojom.h" |
| 38 |
| 39 using mojo::terminal::TerminalClient; |
| 40 |
| 41 class TerminalConnection { |
| 42 public: |
| 43 explicit TerminalConnection(mojo::files::FilePtr terminal, |
| 44 native_support::Process* native_support_process) |
| 45 : terminal_(terminal.Pass()), |
| 46 native_support_process_(native_support_process) { |
| 47 terminal_.set_connection_error_handler([this]() { delete this; }); |
| 48 Start(); |
| 49 } |
| 50 ~TerminalConnection() {} |
| 51 |
| 52 private: |
| 53 void Write(const char* s, mojo::files::File::WriteCallback callback) { |
| 54 size_t length = strlen(s); |
| 55 mojo::Array<uint8_t> a(length); |
| 56 memcpy(&a[0], s, length); |
| 57 terminal_->Write(a.Pass(), 0, mojo::files::WHENCE_FROM_CURRENT, callback); |
| 58 } |
| 59 |
| 60 void Start() { |
| 61 // TODO(vtl): Check canonical mode (via ioctl) first (or before |Read()|). |
| 62 |
| 63 const char kPrompt[] = "\x1b[0mNative program to run?\n>>> "; |
| 64 Write(kPrompt, [this](mojo::files::Error error, uint32_t) { |
| 65 this->DidWritePrompt(error); |
| 66 }); |
| 67 } |
| 68 void DidWritePrompt(mojo::files::Error error) { |
| 69 if (error != mojo::files::ERROR_OK) { |
| 70 LOG(ERROR) << "Write() error: " << error; |
| 71 delete this; |
| 72 return; |
| 73 } |
| 74 |
| 75 terminal_->Read( |
| 76 1000, 0, mojo::files::WHENCE_FROM_CURRENT, |
| 77 [this](mojo::files::Error error, mojo::Array<uint8_t> bytes_read) { |
| 78 this->DidReadFromPrompt(error, bytes_read.Pass()); |
| 79 }); |
| 80 } |
| 81 void DidReadFromPrompt(mojo::files::Error error, |
| 82 mojo::Array<uint8_t> bytes_read) { |
| 83 if (error != mojo::files::ERROR_OK || !bytes_read.size()) { |
| 84 LOG(ERROR) << "Read() error: " << error; |
| 85 delete this; |
| 86 return; |
| 87 } |
| 88 |
| 89 std::string input(reinterpret_cast<const char*>(&bytes_read[0]), |
| 90 bytes_read.size()); |
| 91 command_line_.clear(); |
| 92 base::SplitStringAlongWhitespace(input, &command_line_); |
| 93 |
| 94 if (command_line_.empty()) { |
| 95 Start(); |
| 96 return; |
| 97 } |
| 98 |
| 99 // Set the terminal to noncanonical mode. Do so by getting the settings, |
| 100 // flipping the flag, and setting them. |
| 101 // TODO(vtl): Should it do other things? |
| 102 // TODO(vtl): I wonder if these ioctls shouldn't be done by the |
| 103 // |SpawnWithTerminal()| implementation instead. Hmmm. |
| 104 mojo::Array<uint32_t> in_values = mojo::Array<uint32_t>::New(1); |
| 105 in_values[0] = mojo::files::kIoctlTerminalGetSettings; |
| 106 terminal_->Ioctl( |
| 107 mojo::files::kIoctlTerminal, in_values.Pass(), |
| 108 [this](mojo::files::Error error, mojo::Array<uint32_t> out_values) { |
| 109 this->DidGetTerminalSettings(error, out_values.Pass()); |
| 110 }); |
| 111 } |
| 112 void DidGetTerminalSettings(mojo::files::Error error, |
| 113 mojo::Array<uint32_t> out_values) { |
| 114 if (error != mojo::files::ERROR_OK || out_values.size() < 6) { |
| 115 LOG(ERROR) << "Ioctl() (terminal get settings) error: " << error; |
| 116 delete this; |
| 117 return; |
| 118 } |
| 119 |
| 120 const size_t kBaseFieldCount = |
| 121 mojo::files::kIoctlTerminalTermiosBaseFieldCount; |
| 122 const uint32_t kLFlagIdx = mojo::files::kIoctlTerminalTermiosLFlagIndex; |
| 123 const uint32_t kLFlagICANON = mojo::files::kIoctlTerminalTermiosLFlagICANON; |
| 124 |
| 125 auto in_values = mojo::Array<uint32_t>::New(1 + kBaseFieldCount); |
| 126 in_values[0] = mojo::files::kIoctlTerminalSetSettings; |
| 127 for (size_t i = 0; i < kBaseFieldCount; i++) |
| 128 in_values[1 + i] = out_values[i]; |
| 129 // Just turn off ICANON, which is in "lflag". |
| 130 in_values[1 + kLFlagIdx] &= ~kLFlagICANON; |
| 131 terminal_->Ioctl( |
| 132 mojo::files::kIoctlTerminal, in_values.Pass(), |
| 133 [this](mojo::files::Error error, mojo::Array<uint32_t> out_values) { |
| 134 this->DidSetTerminalSettings(error, out_values.Pass()); |
| 135 }); |
| 136 } |
| 137 void DidSetTerminalSettings(mojo::files::Error error, |
| 138 mojo::Array<uint32_t> out_values) { |
| 139 if (error != mojo::files::ERROR_OK) { |
| 140 LOG(ERROR) << "Ioctl() (terminal set settings) error: " << error; |
| 141 delete this; |
| 142 return; |
| 143 } |
| 144 |
| 145 // Now, we can spawn. |
| 146 mojo::String path(command_line_[0]); |
| 147 mojo::Array<mojo::String> argv; |
| 148 for (const auto& arg : command_line_) |
| 149 argv.push_back(arg); |
| 150 |
| 151 // TODO(vtl): If the |InterfacePtr| underlying |native_support_process_| |
| 152 // encounters an error, then we're sort of dead in the water. |
| 153 native_support_process_->SpawnWithTerminal( |
| 154 path, argv.Pass(), mojo::Array<mojo::String>(), terminal_.Pass(), |
| 155 GetProxy(&process_controller_), [this](mojo::files::Error error) { |
| 156 this->DidSpawnWithTerminal(error); |
| 157 }); |
| 158 process_controller_.set_connection_error_handler([this]() { delete this; }); |
| 159 } |
| 160 void DidSpawnWithTerminal(mojo::files::Error error) { |
| 161 if (error != mojo::files::ERROR_OK) { |
| 162 LOG(ERROR) << "SpawnWithTerminal() error: " << error; |
| 163 delete this; |
| 164 return; |
| 165 } |
| 166 process_controller_->Wait( |
| 167 [this](mojo::files::Error error, int32_t exit_status) { |
| 168 this->DidWait(error, exit_status); |
| 169 }); |
| 170 } |
| 171 void DidWait(mojo::files::Error error, int32_t exit_status) { |
| 172 if (error != mojo::files::ERROR_OK) |
| 173 LOG(ERROR) << "Wait() error: " << error; |
| 174 else if (exit_status != 0) // |exit_status| only valid if OK. |
| 175 LOG(ERROR) << "Process exit status: " << exit_status; |
| 176 |
| 177 // We're done, regardless. |
| 178 delete this; |
| 179 } |
| 180 |
| 181 mojo::files::FilePtr terminal_; |
| 182 native_support::Process* native_support_process_; |
| 183 native_support::ProcessControllerPtr process_controller_; |
| 184 |
| 185 std::vector<std::string> command_line_; |
| 186 |
| 187 DISALLOW_COPY_AND_ASSIGN(TerminalConnection); |
| 188 }; |
| 189 |
| 190 class TerminalClientImpl : public TerminalClient { |
| 191 public: |
| 192 TerminalClientImpl(mojo::InterfaceRequest<TerminalClient> request, |
| 193 native_support::Process* native_support_process) |
| 194 : binding_(this, request.Pass()), |
| 195 native_support_process_(native_support_process) {} |
| 196 ~TerminalClientImpl() override {} |
| 197 |
| 198 // |TerminalClient| implementation: |
| 199 void ConnectToTerminal(mojo::files::FilePtr terminal) override { |
| 200 if (terminal) { |
| 201 // Owns itself. |
| 202 new TerminalConnection(terminal.Pass(), native_support_process_); |
| 203 } else { |
| 204 LOG(ERROR) << "No terminal"; |
| 205 } |
| 206 } |
| 207 |
| 208 private: |
| 209 mojo::StrongBinding<TerminalClient> binding_; |
| 210 native_support::Process* native_support_process_; |
| 211 |
| 212 DISALLOW_COPY_AND_ASSIGN(TerminalClientImpl); |
| 213 }; |
| 214 |
| 215 class NativeRunApp : public mojo::ApplicationDelegate, |
| 216 public mojo::InterfaceFactory<TerminalClient> { |
| 217 public: |
| 218 NativeRunApp() : application_impl_(nullptr) {} |
| 219 ~NativeRunApp() override {} |
| 220 |
| 221 private: |
| 222 // |mojo::ApplicationDelegate|: |
| 223 void Initialize(mojo::ApplicationImpl* application_impl) override { |
| 224 DCHECK(!application_impl_); |
| 225 application_impl_ = application_impl; |
| 226 application_impl_->ConnectToService("mojo:native_support", |
| 227 &native_support_process_); |
| 228 } |
| 229 |
| 230 bool ConfigureIncomingConnection( |
| 231 mojo::ApplicationConnection* connection) override { |
| 232 connection->AddService<TerminalClient>(this); |
| 233 return true; |
| 234 } |
| 235 |
| 236 // |InterfaceFactory<TerminalClient>| implementation: |
| 237 void Create(mojo::ApplicationConnection* /*connection*/, |
| 238 mojo::InterfaceRequest<TerminalClient> request) override { |
| 239 new TerminalClientImpl(request.Pass(), native_support_process_.get()); |
| 240 } |
| 241 |
| 242 mojo::ApplicationImpl* application_impl_; |
| 243 native_support::ProcessPtr native_support_process_; |
| 244 |
| 245 DISALLOW_COPY_AND_ASSIGN(NativeRunApp); |
| 246 }; |
| 247 |
| 248 MojoResult MojoMain(MojoHandle application_request) { |
| 249 mojo::ApplicationRunnerChromium runner(new NativeRunApp()); |
| 250 return runner.Run(application_request); |
| 251 } |
OLD | NEW |