mirror of
https://github.com/nestriness/cdc-file-transfer.git
synced 2026-01-30 12:25:35 +02:00
[cdc_stream] Automatically start service (#28)
Starts the streaming service if it's not up and running. This required adding the ability to run a detached process. By default, all child processes are killed when the parent process exits. Since detached child processes don't run with a console, they need to create sub- processes with CREATE_NO_WINDOW since otherwise a new console pops up, e.g. for every ssh command. Polls for 20 seconds while the service starts up. For this purpose, a BackgroundServiceClient is added. This will be reused in a future CL by a new stop-service command to exit the service. Also adds --service-port as additional argument to start-service.
This commit is contained in:
@@ -213,6 +213,7 @@ absl::StatusOr<std::unordered_set<int>> PortManager::FindAvailableLocalPorts(
|
||||
ProcessStartInfo start_info;
|
||||
start_info.command = "netstat -a -n -p tcp";
|
||||
start_info.name = "netstat";
|
||||
start_info.flags = ProcessFlags::kNoWindow;
|
||||
|
||||
std::string output;
|
||||
start_info.stdout_handler = [&output](const char* data, size_t data_size) {
|
||||
@@ -246,6 +247,7 @@ absl::StatusOr<std::unordered_set<int>> PortManager::FindAvailableRemotePorts(
|
||||
ProcessStartInfo start_info =
|
||||
remote_util->BuildProcessStartInfoForSsh(remote_command);
|
||||
start_info.name = "netstat";
|
||||
start_info.flags = ProcessFlags::kNoWindow;
|
||||
|
||||
std::string output;
|
||||
start_info.stdout_handler = [&output](const char* data, size_t data_size) {
|
||||
|
||||
@@ -33,6 +33,12 @@ namespace cdc_ft {
|
||||
absl::Status LogOutput(const char* name, const char* data, size_t data_size,
|
||||
absl::optional<LogLevel> log_level = {});
|
||||
|
||||
enum class ProcessFlags {
|
||||
kNone = 0,
|
||||
kDetached = 1 << 0,
|
||||
kNoWindow = 1 << 1,
|
||||
};
|
||||
|
||||
struct ProcessStartInfo {
|
||||
// Handler for stdout/stderr. |data| is guaranteed to be NULL terminated, so
|
||||
// it may be used like a C-string if it's known to be text, e.g. for printf().
|
||||
@@ -63,8 +69,14 @@ struct ProcessStartInfo {
|
||||
OutputHandler stdout_handler;
|
||||
OutputHandler stderr_handler;
|
||||
|
||||
// Flags that define additional properties of the process.
|
||||
ProcessFlags flags = ProcessFlags::kNone;
|
||||
|
||||
// Returns |name| if set, otherwise |command|.
|
||||
const std::string& Name() const;
|
||||
|
||||
// Tests ALL flags (flags & flag) == flag.
|
||||
bool HasFlag(ProcessFlags flag) const;
|
||||
};
|
||||
|
||||
// Runs a background process and pipes stdin/stdout/stderr.
|
||||
@@ -75,6 +87,8 @@ class Process {
|
||||
static constexpr uint32_t kExitCodeFailedToGetExitCode = 4000000002;
|
||||
|
||||
explicit Process(const ProcessStartInfo& start_info);
|
||||
|
||||
// Terminates the process unless it's running with ProcessFlags::kDetached.
|
||||
virtual ~Process();
|
||||
|
||||
// Start the background process.
|
||||
@@ -140,6 +154,16 @@ class WinProcessFactory : public ProcessFactory {
|
||||
std::unique_ptr<Process> Create(const ProcessStartInfo& start_info) override;
|
||||
};
|
||||
|
||||
inline ProcessFlags operator|(ProcessFlags a, ProcessFlags b) {
|
||||
using T = std::underlying_type_t<ProcessFlags>;
|
||||
return static_cast<ProcessFlags>(static_cast<T>(a) | static_cast<T>(b));
|
||||
}
|
||||
|
||||
inline ProcessFlags operator&(ProcessFlags a, ProcessFlags b) {
|
||||
using T = std::underlying_type_t<ProcessFlags>;
|
||||
return static_cast<ProcessFlags>(static_cast<T>(a) & static_cast<T>(b));
|
||||
}
|
||||
|
||||
} // namespace cdc_ft
|
||||
|
||||
#endif // COMMON_PROCESS_H_
|
||||
|
||||
@@ -49,6 +49,24 @@ void SetThreadName(const std::string& name) {
|
||||
}
|
||||
}
|
||||
|
||||
int ToCreationFlags(ProcessFlags pflags) {
|
||||
#define HANDLE_FLAG(pflag, cflag) \
|
||||
if ((pflags & pflag) == pflag) { \
|
||||
cflags |= cflag; \
|
||||
pdone = pdone | pflag; \
|
||||
}
|
||||
|
||||
int cflags = 0;
|
||||
ProcessFlags pdone = ProcessFlags::kNone;
|
||||
HANDLE_FLAG(ProcessFlags::kDetached, DETACHED_PROCESS);
|
||||
HANDLE_FLAG(ProcessFlags::kNoWindow, CREATE_NO_WINDOW);
|
||||
assert(pflags == pdone);
|
||||
|
||||
#undef HANDLE_FLAG
|
||||
|
||||
return cflags;
|
||||
}
|
||||
|
||||
std::atomic_int g_pipe_serial_number{0};
|
||||
|
||||
// Creates a pipe suitable for overlapped IO. Regular anonymous pipes in Windows
|
||||
@@ -567,6 +585,10 @@ const std::string& ProcessStartInfo::Name() const {
|
||||
return !name.empty() ? name : command;
|
||||
}
|
||||
|
||||
bool ProcessStartInfo::HasFlag(ProcessFlags flag) const {
|
||||
return (flags & flag) == flag;
|
||||
}
|
||||
|
||||
Process::Process(const ProcessStartInfo& start_info)
|
||||
: start_info_(start_info) {}
|
||||
|
||||
@@ -593,6 +615,8 @@ class WinProcess : public Process {
|
||||
absl::Status GetStatus() const override;
|
||||
|
||||
private:
|
||||
void Reset();
|
||||
|
||||
std::unique_ptr<ProcessInfo> process_info_;
|
||||
std::unique_ptr<MessagePumpThread> message_pump_;
|
||||
};
|
||||
@@ -600,7 +624,14 @@ class WinProcess : public Process {
|
||||
WinProcess::WinProcess(const ProcessStartInfo& start_info)
|
||||
: Process(start_info) {}
|
||||
|
||||
WinProcess::~WinProcess() { Terminate().IgnoreError(); }
|
||||
WinProcess::~WinProcess() {
|
||||
if (start_info_.HasFlag(ProcessFlags::kDetached)) {
|
||||
// If the process runs detached, just reset handles, don't terminate it.
|
||||
Reset();
|
||||
} else {
|
||||
Terminate().IgnoreError();
|
||||
}
|
||||
}
|
||||
|
||||
absl::Status WinProcess::Start() {
|
||||
LOG_INFO("Starting process %s", start_info_.command.c_str());
|
||||
@@ -676,10 +707,13 @@ absl::Status WinProcess::Start() {
|
||||
}
|
||||
|
||||
JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = {0};
|
||||
jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
|
||||
if (!start_info_.HasFlag(ProcessFlags::kDetached)) {
|
||||
jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
|
||||
}
|
||||
bool success = SetInformationJobObject(process_info_->job.Get(),
|
||||
JobObjectExtendedLimitInformation,
|
||||
&jeli, sizeof(jeli));
|
||||
|
||||
if (!success) {
|
||||
return MakeStatus("SetInformationJobObject() failed: %s",
|
||||
Util::GetLastWin32Error());
|
||||
@@ -691,7 +725,7 @@ absl::Status WinProcess::Start() {
|
||||
NULL, // Process handle not inheritable
|
||||
NULL, // Thread handle not inheritable
|
||||
TRUE, // Inherit handles
|
||||
0, // No creation flags
|
||||
ToCreationFlags(start_info_.flags),
|
||||
NULL, // Use parent's environment block
|
||||
NULL, // Use parent's starting directory
|
||||
&si, &process_info_->pi);
|
||||
@@ -785,32 +819,39 @@ absl::Status WinProcess::Terminate() {
|
||||
message_pump_.reset();
|
||||
}
|
||||
|
||||
if (process_info_) {
|
||||
bool result = true;
|
||||
if (should_terminate) {
|
||||
result = TerminateProcess(process_info_->pi.hProcess, 0);
|
||||
if (!result && GetLastError() == ERROR_ACCESS_DENIED) {
|
||||
// This means that the process has already exited, but in a way that
|
||||
// the exit wasn't properly reported to this code (e.g. the process got
|
||||
// killed somewhere). Just handle this silently.
|
||||
LOG_DEBUG("Process '%s' already exited", start_info_.Name());
|
||||
result = true;
|
||||
}
|
||||
std::string error_msg;
|
||||
if (process_info_ && should_terminate &&
|
||||
!TerminateProcess(process_info_->pi.hProcess, 0)) {
|
||||
if (GetLastError() == ERROR_ACCESS_DENIED) {
|
||||
// This means that the process has already exited, but in a way that
|
||||
// the exit wasn't properly reported to this code (e.g. the process got
|
||||
// killed somewhere). Just handle this silently.
|
||||
LOG_DEBUG("Process '%s' already exited", start_info_.Name());
|
||||
} else {
|
||||
error_msg = Util::GetLastWin32Error();
|
||||
}
|
||||
}
|
||||
|
||||
// Reset handles.
|
||||
Reset();
|
||||
|
||||
if (!error_msg.empty()) {
|
||||
return MakeStatus("TerminateProcess() failed: %s", error_msg);
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
void WinProcess::Reset() {
|
||||
// Shut down message pump.
|
||||
message_pump_.reset();
|
||||
|
||||
if (process_info_) {
|
||||
// Close the handles that are not scoped handles.
|
||||
ScopedHandle(process_info_->pi.hProcess).Close();
|
||||
ScopedHandle(process_info_->pi.hThread).Close();
|
||||
|
||||
process_info_.reset();
|
||||
|
||||
if (!result) {
|
||||
return MakeStatus("TerminateProcess() failed: %s",
|
||||
Util::GetLastWin32Error());
|
||||
}
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
ProcessFactory::~ProcessFactory() = default;
|
||||
|
||||
@@ -75,6 +75,7 @@ absl::Status RemoteUtil::Scp(std::vector<std::string> source_filepaths,
|
||||
|
||||
// -p preserves timestamps. This enables timestamp-based up-to-date checks.
|
||||
ProcessStartInfo start_info;
|
||||
start_info.flags = ProcessFlags::kNoWindow;
|
||||
start_info.command = absl::StrFormat(
|
||||
"%s "
|
||||
"%s %s -p -T "
|
||||
@@ -147,6 +148,7 @@ ProcessStartInfo RemoteUtil::BuildProcessStartInfoForSshInternal(
|
||||
QuoteForWindows(ssh_command_), quiet_ || verbosity_ < 2 ? "-q" : "",
|
||||
forward_arg, QuoteForWindows(user_host_), ssh_port_, remote_command_arg);
|
||||
start_info.forward_output_to_log = forward_output_to_log_;
|
||||
start_info.flags = ProcessFlags::kNoWindow;
|
||||
return start_info;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user