Make qemu the client in the qemu monitor connection
The launcher creates a unix socket and waits for qemu to connect to it
with a timeout. This allows the launcher to ensure qemu started
successfully before launching more processes. Only the launcher can
stop qemu through the monitor, stop_cvd will kill it with the fuser
command if it can't talk to the launcher.
Bug: 111453282
Test: local & gce
Change-Id: I589b14bb07741fbd9c0810c2b73fdb026f2517f9
diff --git a/host/libs/vm_manager/cf_qemu.sh b/host/libs/vm_manager/cf_qemu.sh
index 4bca7c2..7316643 100755
--- a/host/libs/vm_manager/cf_qemu.sh
+++ b/host/libs/vm_manager/cf_qemu.sh
@@ -46,7 +46,7 @@
-display none
-no-user-config
-nodefaults
- -chardev "socket,id=charmonitor,path=${monitor_path:-${default_dir}/qemu_monitor.sock},server,nowait"
+ -chardev "socket,id=charmonitor,path=${monitor_path:-${default_dir}/qemu_monitor.sock},nowait"
-mon "chardev=charmonitor,id=monitor,mode=control"
-rtc "base=utc"
-no-shutdown
diff --git a/host/libs/vm_manager/libvirt_manager.cpp b/host/libs/vm_manager/libvirt_manager.cpp
index 96415e0..90e221d 100644
--- a/host/libs/vm_manager/libvirt_manager.cpp
+++ b/host/libs/vm_manager/libvirt_manager.cpp
@@ -347,7 +347,7 @@
LibvirtManager::LibvirtManager(vsoc::CuttlefishConfig* config)
: VmManager(config) {}
-bool LibvirtManager::Start() const {
+bool LibvirtManager::Start() {
std::string start_command = GetLibvirtCommand();
start_command += " create /dev/fd/0";
@@ -374,7 +374,7 @@
return true;
}
-bool LibvirtManager::Stop() const {
+bool LibvirtManager::Stop() {
auto stop_command = GetLibvirtCommand();
stop_command += " destroy " + config_->instance_name();
return std::system(stop_command.c_str()) == 0;
diff --git a/host/libs/vm_manager/libvirt_manager.h b/host/libs/vm_manager/libvirt_manager.h
index d0d157e..d582827 100644
--- a/host/libs/vm_manager/libvirt_manager.h
+++ b/host/libs/vm_manager/libvirt_manager.h
@@ -27,8 +27,8 @@
LibvirtManager(vsoc::CuttlefishConfig* config);
virtual ~LibvirtManager() = default;
- bool Start() const override;
- bool Stop() const override;
+ bool Start() override;
+ bool Stop() override;
bool EnsureInstanceDirExists() const override;
bool CleanPriorFiles() const override;
diff --git a/host/libs/vm_manager/qemu_manager.cpp b/host/libs/vm_manager/qemu_manager.cpp
index 8eeb5a6..257e013 100644
--- a/host/libs/vm_manager/qemu_manager.cpp
+++ b/host/libs/vm_manager/qemu_manager.cpp
@@ -33,6 +33,7 @@
#include <gflags/gflags.h>
#include <glog/logging.h>
+#include "common/libs/fs/shared_select.h"
#include "common/libs/utils/files.h"
#include "common/libs/utils/subprocess.h"
#include "common/libs/utils/users.h"
@@ -55,15 +56,14 @@
LOG(INFO) << key << "=" << value;
}
-int BuildAndRunQemuCmd(vsoc::CuttlefishConfig* config) {
+pid_t BuildAndRunQemuCmd(vsoc::CuttlefishConfig* config) {
// Set the config values in the environment
LogAndSetEnv("qemu_binary", FLAGS_qemu_binary);
LogAndSetEnv("instance_name", config->instance_name());
LogAndSetEnv("memory_mb", std::to_string(config->memory_mb()));
LogAndSetEnv("cpus", std::to_string(config->cpus()));
LogAndSetEnv("uuid", config->uuid());
- LogAndSetEnv("monitor_path",
- config->PerInstancePath("qemu_monitor.sock"));
+ LogAndSetEnv("monitor_path", GetMonitorPath(config));
LogAndSetEnv("kernel_image_path", config->kernel_image_path());
LogAndSetEnv("gdb_flag", config->gdb_flag());
LogAndSetEnv("ramdisk_image_path", config->ramdisk_image_path());
@@ -84,7 +84,7 @@
LogAndSetEnv("ivshmem_vector_count",
std::to_string(config->ivshmem_vector_count()));
LogAndSetEnv("usb_v1_socket_name", config->usb_v1_socket_name());
- return cvd::execute({vsoc::DefaultHostArtifactsPath("bin/cf_qemu.sh")});
+ return cvd::subprocess({vsoc::DefaultHostArtifactsPath("bin/cf_qemu.sh")});
}
} // namespace
@@ -94,55 +94,51 @@
QemuManager::QemuManager(vsoc::CuttlefishConfig* config)
: VmManager(config) {}
-bool QemuManager::Start() const {
- // Create a thread that will make the launcher abort if the qemu process
- // crashes, this avoids having the launcher waiting forever for
- // VIRTUAL_DEVICE_BOOT_COMPLETED in this cases.
- auto config = this->config_;
- std::thread waiting_thread([config]() {
- int status = BuildAndRunQemuCmd(config);
- if (status != 0) {
- LOG(FATAL) << "Qemu process exited prematurely";
- } else {
- LOG(INFO) << "Qemu process exited normally";
- }
- });
- waiting_thread.detach();
- return true;
+bool QemuManager::Start() {
+ if (monitor_conn_->IsOpen()) {
+ LOG(ERROR) << "Already started, should call Stop() first";
+ return false;
+ }
+ auto monitor_path = GetMonitorPath(config_);
+ auto monitor_sock = cvd::SharedFD::SocketLocalServer(
+ monitor_path.c_str(), false, SOCK_STREAM, 0666);
+
+ BuildAndRunQemuCmd(config_);
+
+ cvd::SharedFDSet fdset;
+ fdset.Set(monitor_sock);
+ struct timeval timeout {5, 0}; // Wait at most 5 seconds for qemu to connect
+ int select_result = cvd::Select(&fdset, 0, 0, &timeout);
+ if (select_result < 0) {
+ LOG(ERROR) << "Error when calling seletct: " << strerror(errno);
+ return false;
+ }
+ if (select_result == 0) {
+ LOG(ERROR) << "Timed out waiting for qemu to connect to monitor";
+ return false;
+ }
+ monitor_conn_ = cvd::SharedFD::Accept(*monitor_sock);
+ monitor_conn_->Fcntl(F_SETFD, FD_CLOEXEC);
+ return monitor_conn_->IsOpen();
}
-bool QemuManager::Stop() const {
- int errno_;
- int fd = socket(AF_UNIX, SOCK_STREAM, 0);
- if (fd == -1) {
- errno_ = errno;
- LOG(ERROR) << "Error creating socket: " << strerror(errno_);
+bool QemuManager::Stop() {
+ if (!monitor_conn_->IsOpen()) {
+ LOG(ERROR) << "The connection to qemu is closed, is it still running?";
return false;
}
-
- struct sockaddr_un addr;
- memset(&addr, 0, sizeof(addr));
- addr.sun_family = AF_UNIX;
- std::string monitor_path = GetMonitorPath(config_);
- strncpy(addr.sun_path, monitor_path.c_str(), sizeof(addr.sun_path) - 1);
-
- if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
- errno_ = errno;
- LOG(ERROR) << "Error connecting to qemu monitor: " << strerror(errno_);
- return false;
- }
-
char msg[] = "{\"execute\":\"qmp_capabilities\"}{\"execute\":\"quit\"}";
ssize_t len = sizeof(msg) - 1;
while (len > 0) {
- int tmp = TEMP_FAILURE_RETRY(write(fd, msg, len));
+ int tmp = monitor_conn_->Write(msg, len);
if (tmp < 0) {
- LOG(ERROR) << "Error writing to socket";
+ LOG(ERROR) << "Error writing to socket: " << monitor_conn_->StrError();
+ return false;
}
len -= tmp;
}
// Log the reply
char buff[1000];
- while ((len = TEMP_FAILURE_RETRY(read(fd, buff, sizeof(buff) - 1))) > 0) {
+ while ((len = monitor_conn_->Read(buff, sizeof(buff) - 1)) > 0) {
buff[len] = '\0';
LOG(INFO) << "From qemu monitor: " << buff;
}
diff --git a/host/libs/vm_manager/qemu_manager.h b/host/libs/vm_manager/qemu_manager.h
index 20fe26a..7c053e7 100644
--- a/host/libs/vm_manager/qemu_manager.h
+++ b/host/libs/vm_manager/qemu_manager.h
@@ -17,6 +17,8 @@
#include "host/libs/vm_manager/vm_manager.h"
+#include "common/libs/fs/shared_fd.h"
+
namespace vm_manager {
// Starts a guest VM using the qemu command directly. It requires the host
@@ -27,14 +29,16 @@
QemuManager(vsoc::CuttlefishConfig* config);
virtual ~QemuManager() = default;
- bool Start() const override;
- bool Stop() const override;
+ bool Start() override;
+ bool Stop() override;
bool EnsureInstanceDirExists() const override;
bool CleanPriorFiles() const override;
bool ValidateHostConfiguration(
std::vector<std::string>* config_commands) const override;
+ private:
+ cvd::SharedFD monitor_conn_;
};
} // namespace vm_manager
diff --git a/host/libs/vm_manager/vm_manager.h b/host/libs/vm_manager/vm_manager.h
index 7b3490a..d4f7036 100644
--- a/host/libs/vm_manager/vm_manager.h
+++ b/host/libs/vm_manager/vm_manager.h
@@ -39,8 +39,8 @@
virtual ~VmManager() = default;
- virtual bool Start() const = 0;
- virtual bool Stop() const = 0;
+ virtual bool Start() = 0;
+ virtual bool Stop() = 0;
virtual bool EnsureInstanceDirExists() const = 0;
virtual bool CleanPriorFiles() const = 0;