aboutsummaryrefslogtreecommitdiff
path: root/host/commands/console_forwarder/main.cpp
blob: f36fd0623584a804231a6cff536e9d4d02c81d2a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <signal.h>

#include <deque>
#include <thread>
#include <vector>

#include <gflags/gflags.h>
#include <android-base/logging.h>

#include <common/libs/fs/shared_fd.h>
#include <common/libs/fs/shared_select.h>
#include <host/libs/config/cuttlefish_config.h>
#include <host/libs/config/logging.h>

DEFINE_int32(console_in_fd,
             -1,
             "File descriptor for the console's input channel");
DEFINE_int32(console_out_fd,
             -1,
             "File descriptor for the console's output channel");

// Handles forwarding the serial console to a socket.
// It receives the socket fd along with a couple of fds for the console (could
// be the same fd twice if, for example a socket_pair were used).
// Data available in the console's output needs to be read immediately to avoid
// the having the VMM blocked on writes to the pipe. To achieve this one thread
// takes care of (and only of) all read calls (from console output and from the
// socket client), using select(2) to ensure it never blocks. Writes are handled
// in a different thread, the two threads communicate through a buffer queue
// protected by a mutex.
class ConsoleForwarder {
 public:
  ConsoleForwarder(cvd::SharedFD socket,
                   cvd::SharedFD console_in,
                   cvd::SharedFD console_out) : socket_(socket),
                                                console_in_(console_in),
                                                console_out_(console_out) {}
  [[noreturn]] void StartServer() {
    // Create a new thread to handle writes to the console and to the any client
    // connected to the socket.
    writer_thread_ = std::thread([this]() { WriteLoop(); });
    // Use the calling thread (likely the process' main thread) to handle
    // reading the console's output and input from the client.
    ReadLoop();
  }
 private:
  void EnqueueWrite(std::vector<char> buffer, cvd::SharedFD fd) {
    std::lock_guard<std::mutex> lock(write_queue_mutex_);
    write_queue_.emplace_back(fd, std::move(buffer));
    condvar_.notify_one();
  }

  [[noreturn]] void WriteLoop() {
    while (true) {
      while (!write_queue_.empty()) {
        std::vector<char> buffer;
        cvd::SharedFD fd;
        {
          std::lock_guard<std::mutex> lock(write_queue_mutex_);
          auto& front = write_queue_.front();
          buffer = std::move(front.second);
          fd = front.first;
          write_queue_.pop_front();
        }
        // Write all bytes to the file descriptor. Writes may block, so the
        // mutex lock should NOT be held while writing to avoid blocking the
        // other thread.
        ssize_t bytes_written = 0;
        ssize_t bytes_to_write = buffer.size();
        while (bytes_to_write > 0) {
          bytes_written =
              fd->Write(buffer.data() + bytes_written, bytes_to_write);
          if (bytes_written < 0) {
            LOG(ERROR) << "Error writing to fd: " << fd->StrError();
            // Don't try to write from this buffer anymore, error handling will
            // be done on the reading thread (failed client will be
            // disconnected, on serial console failure this process will abort).
            break;
          }
          bytes_to_write -= bytes_written;
        }
      }
      {
        std::unique_lock<std::mutex> lock(write_queue_mutex_);
        // Check again before sleeping, state may have changed
        if (write_queue_.empty()) {
          condvar_.wait(lock);
        }
      }
    }
  }

  [[noreturn]] void ReadLoop() {
    cvd::SharedFD client_fd;
    while (true) {
      cvd::SharedFDSet read_set;
      if (client_fd->IsOpen()) {
        read_set.Set(client_fd);
      } else {
        read_set.Set(socket_);
      }
      read_set.Set(console_out_);
      cvd::Select(&read_set, nullptr, nullptr, nullptr);
      if (read_set.IsSet(console_out_)) {
        std::vector<char> buffer(4096);
        auto bytes_read = console_out_->Read(buffer.data(), buffer.size());
        if (bytes_read <= 0) {
          LOG(ERROR) << "Error reading from console output: "
                     << console_out_->StrError();
          // This is likely unrecoverable, so exit here
          std::exit(-4);
        }
        buffer.resize(bytes_read);
        if (client_fd->IsOpen()) {
          EnqueueWrite(std::move(buffer), client_fd);
        }
      }
      if (read_set.IsSet(socket_)) {
        // socket_ will only be included in the select call (and therefore only
        // present in the read set) if there is no client connected, so this
        // assignment is safe.
        client_fd = cvd::SharedFD::Accept(*socket_);
        if (!client_fd->IsOpen()) {
          LOG(ERROR) << "Error accepting connection on socket: "
                     << client_fd->StrError();
        }
      }
      if (read_set.IsSet(client_fd)) {
        std::vector<char> buffer(4096);
        auto bytes_read = client_fd->Read(buffer.data(), buffer.size());
        if (bytes_read <= 0) {
          LOG(ERROR) << "Error reading from client fd: "
                     << client_fd->StrError();
          client_fd->Close(); // ignore errors here
        } else {
          buffer.resize(bytes_read);
          EnqueueWrite(std::move(buffer), console_in_);
        }
      }
    }
  }

  cvd::SharedFD socket_;
  cvd::SharedFD console_in_;
  cvd::SharedFD console_out_;
  std::thread writer_thread_;
  std::mutex write_queue_mutex_;
  std::condition_variable condvar_;
  std::deque<std::pair<cvd::SharedFD, std::vector<char>>> write_queue_;
};

int main(int argc, char** argv) {
  cvd::DefaultSubprocessLogging(argv);
  ::gflags::ParseCommandLineFlags(&argc, &argv, true);

  if (FLAGS_console_in_fd < 0 || FLAGS_console_out_fd < 0) {
    LOG(ERROR) << "Invalid file descriptors: " << FLAGS_console_in_fd << ", "
               << FLAGS_console_out_fd;
    return -1;
  }

  auto console_in = cvd::SharedFD::Dup(FLAGS_console_in_fd);
  close(FLAGS_console_in_fd);
  if (!console_in->IsOpen()) {
    LOG(ERROR) << "Error dupping fd " << FLAGS_console_in_fd << ": "
               << console_in->StrError();
    return -2;
  }
  close(FLAGS_console_in_fd);

  auto console_out = cvd::SharedFD::Dup(FLAGS_console_out_fd);
  close(FLAGS_console_out_fd);
  if (!console_out->IsOpen()) {
    LOG(ERROR) << "Error dupping fd " << FLAGS_console_out_fd << ": "
               << console_out->StrError();
    return -2;
  }

  auto config = vsoc::CuttlefishConfig::Get();
  if (!config) {
    LOG(ERROR) << "Unable to get config object";
    return -3;
  }

  auto instance = config->ForDefaultInstance();
  auto console_socket_name = instance.console_path();
  auto socket = cvd::SharedFD::SocketLocalServer(console_socket_name.c_str(),
                                                 false,
                                                 SOCK_STREAM,
                                                 0600);
  if (!socket->IsOpen()) {
    LOG(ERROR) << "Failed to create console socket at " << console_socket_name
               << ": " << socket->StrError();
    return -5;
  }

  ConsoleForwarder console_forwarder(socket, console_in, console_out);

  // Don't get a SIGPIPE from the clients
  if (sigaction(SIGPIPE, nullptr, nullptr) != 0) {
    LOG(FATAL) << "Failed to set SIGPIPE to be ignored: " << strerror(errno);
    return -6;
  }

  console_forwarder.StartServer();
}