blob: 3ffaf967848120ad50afdb02183a486b6e8af3a7 [file] [log] [blame] [edit]
/*
* Copyright (C) 2023 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 "host/commands/cvd/interruptible_terminal.h"
#include <errno.h>
#include <android-base/scopeguard.h>
#include "common/libs/fs/shared_select.h"
namespace cuttlefish {
InterruptibleTerminal::InterruptibleTerminal(SharedFD stdin_fd)
: stdin_fd_(std::move(stdin_fd)), interrupt_event_fd_(SharedFD::Event()) {}
Result<void> InterruptibleTerminal::Interrupt() {
std::unique_lock lock(terminal_mutex_);
interrupted_ = true;
if (owner_tid_) {
CF_EXPECT_EQ(interrupt_event_fd_->EventfdWrite(1), 0,
interrupt_event_fd_->StrError());
}
readline_done_.wait(lock, [this]() { return owner_tid_ == std::nullopt; });
{ SharedFD discard = std::move(stdin_fd_); }
return {};
}
// only up to one thread can call this function
Result<std::string> InterruptibleTerminal::ReadLine() {
{
std::lock_guard lock(terminal_mutex_);
CF_EXPECT(interrupted_ == false, "Interrupted");
CF_EXPECTF(owner_tid_ == std::nullopt,
"This InterruptibleTerminal is already owned by {}",
owner_tid_.value());
CF_EXPECT(stdin_fd_->IsOpen(),
"The copy of client stdin fd has been already closed.");
owner_tid_ = std::this_thread::get_id();
}
SharedFDSet read_set;
std::string line_buf;
while (true) {
read_set.Set(interrupt_event_fd_);
read_set.Set(stdin_fd_);
int num_fds = Select(&read_set, nullptr, nullptr, nullptr);
std::lock_guard lock(terminal_mutex_);
auto return_action = android::base::ScopeGuard([this]() {
owner_tid_ = std::nullopt;
readline_done_.notify_one();
});
CF_EXPECT(interrupted_ == false, "Interrupted");
CF_EXPECTF(num_fds >= 0,
"Select call to read the user input returned error: {}",
strerror(errno));
if (read_set.IsSet(interrupt_event_fd_)) {
eventfd_t val;
CF_EXPECT_EQ(interrupt_event_fd_->EventfdRead(&val), 0);
return CF_ERR("Terminal input interrupted.");
}
CF_EXPECT(read_set.IsSet(stdin_fd_));
char c = 0;
const char end_of_transmission = 4;
auto n_read = stdin_fd_->Read(&c, sizeof(c));
CF_EXPECT(n_read >= 0,
"Read from stdin returned an error: " << stdin_fd_->StrError());
if (n_read > 0) {
CF_EXPECTF(n_read == 1, "Expected to read 1 byte but read: {} bytes",
n_read);
if (c == '\n' || c == 0 || c == end_of_transmission) {
return line_buf;
}
line_buf.append(1, c);
continue;
}
return line_buf;
}
}
} // namespace cuttlefish