Skip to content

Commit 393fc6e

Browse files
committed
Extract RunWithManagedStdio from subprocess.h
https://github.com/google/android-cuttlefish/pull/1432/files#r2226344612 points out that many callers have the same logic around calling the interface. As a first step, extracting to a separate file makes it easier to focus on modifying this interface. Bug: b/433812518
1 parent 80443a1 commit 393fc6e

40 files changed

+241
-135
lines changed

base/cvd/cuttlefish/common/libs/utils/BUILD.bazel

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ cf_cc_library(
2020
deps = [
2121
"//cuttlefish/common/libs/utils:result",
2222
"//cuttlefish/common/libs/utils:subprocess",
23+
"//cuttlefish/common/libs/utils:subprocess_managed_stdio",
2324
"//libbase",
2425
],
2526
)
@@ -74,6 +75,7 @@ cf_cc_library(
7475
deps = [
7576
"//cuttlefish/common/libs/utils:result",
7677
"//cuttlefish/common/libs/utils:subprocess",
78+
"//cuttlefish/common/libs/utils:subprocess_managed_stdio",
7779
"//libbase",
7880
],
7981
)
@@ -204,6 +206,7 @@ cf_cc_library(
204206
"//cuttlefish/common/libs/utils:files",
205207
"//cuttlefish/common/libs/utils:result",
206208
"//cuttlefish/common/libs/utils:subprocess",
209+
"//cuttlefish/common/libs/utils:subprocess_managed_stdio",
207210
"//libbase",
208211
"@fmt",
209212
],
@@ -341,6 +344,17 @@ cf_cc_library(
341344
],
342345
)
343346

347+
cf_cc_library(
348+
name = "subprocess_managed_stdio",
349+
srcs = ["subprocess_managed_stdio.cc"],
350+
hdrs = ["subprocess_managed_stdio.h"],
351+
deps = [
352+
"//cuttlefish/common/libs/fs",
353+
"//cuttlefish/common/libs/utils:subprocess",
354+
"//libbase",
355+
],
356+
)
357+
344358
cf_cc_library(
345359
name = "tcp_socket",
346360
srcs = ["tcp_socket.cpp"],
@@ -445,5 +459,6 @@ cf_cc_library(
445459
"//cuttlefish/common/libs/utils:files",
446460
"//cuttlefish/common/libs/utils:result",
447461
"//cuttlefish/common/libs/utils:subprocess",
462+
"//cuttlefish/common/libs/utils:subprocess_managed_stdio",
448463
],
449464
)

base/cvd/cuttlefish/common/libs/utils/archive.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
#include "cuttlefish/common/libs/utils/result.h"
2929
#include "cuttlefish/common/libs/utils/subprocess.h"
30+
#include "cuttlefish/common/libs/utils/subprocess_managed_stdio.h"
3031

3132
namespace cuttlefish {
3233
namespace {

base/cvd/cuttlefish/common/libs/utils/disk_usage.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
#include "cuttlefish/common/libs/utils/result.h"
2626
#include "cuttlefish/common/libs/utils/subprocess.h"
27+
#include "cuttlefish/common/libs/utils/subprocess_managed_stdio.h"
2728

2829
namespace cuttlefish {
2930
namespace {

base/cvd/cuttlefish/common/libs/utils/network.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
#include "cuttlefish/common/libs/utils/files.h"
4747
#include "cuttlefish/common/libs/utils/result.h"
4848
#include "cuttlefish/common/libs/utils/subprocess.h"
49+
#include "cuttlefish/common/libs/utils/subprocess_managed_stdio.h"
4950

5051
namespace cuttlefish {
5152
namespace {

base/cvd/cuttlefish/common/libs/utils/subprocess.cpp

Lines changed: 0 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,6 @@
3737
#include <set>
3838
#include <sstream>
3939
#include <string>
40-
#include <thread>
41-
#include <type_traits>
4240
#include <unordered_map>
4341
#include <unordered_set>
4442
#include <utility>
@@ -47,7 +45,6 @@
4745
#include <android-base/logging.h>
4846
#include <android-base/strings.h>
4947

50-
#include "cuttlefish/common/libs/fs/shared_buf.h"
5148
#include "cuttlefish/common/libs/utils/contains.h"
5249
#include "cuttlefish/common/libs/utils/files.h"
5350

@@ -525,109 +522,6 @@ std::string Command::AsBashScript(
525522
return contents;
526523
}
527524

528-
// A class that waits for threads to exit in its destructor.
529-
class ThreadJoiner {
530-
std::vector<std::thread*> threads_;
531-
public:
532-
ThreadJoiner(const std::vector<std::thread*> threads) : threads_(threads) {}
533-
~ThreadJoiner() {
534-
for (auto& thread : threads_) {
535-
if (thread->joinable()) {
536-
thread->join();
537-
}
538-
}
539-
}
540-
};
541-
542-
int RunWithManagedStdio(Command&& cmd_tmp, const std::string* stdin_str,
543-
std::string* stdout_str, std::string* stderr_str,
544-
SubprocessOptions options) {
545-
/*
546-
* The order of these declarations is necessary for safety. If the function
547-
* returns at any point, the Command will be destroyed first, closing all
548-
* of its references to SharedFDs. This will cause the thread internals to fail
549-
* their reads or writes. The ThreadJoiner then waits for the threads to
550-
* complete, as running the destructor of an active std::thread crashes the
551-
* program.
552-
*
553-
* C++ scoping rules dictate that objects are descoped in reverse order to
554-
* construction, so this behavior is predictable.
555-
*/
556-
std::thread stdin_thread, stdout_thread, stderr_thread;
557-
ThreadJoiner thread_joiner({&stdin_thread, &stdout_thread, &stderr_thread});
558-
Command cmd = std::move(cmd_tmp);
559-
bool io_error = false;
560-
if (stdin_str != nullptr) {
561-
SharedFD pipe_read, pipe_write;
562-
if (!SharedFD::Pipe(&pipe_read, &pipe_write)) {
563-
LOG(ERROR) << "Could not create a pipe to write the stdin of \""
564-
<< cmd.GetShortName() << "\"";
565-
return -1;
566-
}
567-
cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdIn, pipe_read);
568-
stdin_thread = std::thread([pipe_write, stdin_str, &io_error]() {
569-
int written = WriteAll(pipe_write, *stdin_str);
570-
if (written < 0) {
571-
io_error = true;
572-
LOG(ERROR) << "Error in writing stdin to process";
573-
}
574-
});
575-
}
576-
if (stdout_str != nullptr) {
577-
SharedFD pipe_read, pipe_write;
578-
if (!SharedFD::Pipe(&pipe_read, &pipe_write)) {
579-
LOG(ERROR) << "Could not create a pipe to read the stdout of \""
580-
<< cmd.GetShortName() << "\"";
581-
return -1;
582-
}
583-
cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, pipe_write);
584-
stdout_thread = std::thread([pipe_read, stdout_str, &io_error]() {
585-
int read = ReadAll(pipe_read, stdout_str);
586-
if (read < 0) {
587-
io_error = true;
588-
LOG(ERROR) << "Error in reading stdout from process";
589-
}
590-
});
591-
}
592-
if (stderr_str != nullptr) {
593-
SharedFD pipe_read, pipe_write;
594-
if (!SharedFD::Pipe(&pipe_read, &pipe_write)) {
595-
LOG(ERROR) << "Could not create a pipe to read the stderr of \""
596-
<< cmd.GetShortName() << "\"";
597-
return -1;
598-
}
599-
cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdErr, pipe_write);
600-
stderr_thread = std::thread([pipe_read, stderr_str, &io_error]() {
601-
int read = ReadAll(pipe_read, stderr_str);
602-
if (read < 0) {
603-
io_error = true;
604-
LOG(ERROR) << "Error in reading stderr from process";
605-
}
606-
});
607-
}
608-
609-
auto subprocess = cmd.Start(std::move(options));
610-
if (!subprocess.Started()) {
611-
return -1;
612-
}
613-
auto cmd_short_name = cmd.GetShortName();
614-
{
615-
// Force the destructor to run by moving it into a smaller scope.
616-
// This is necessary to close the write end of the pipe.
617-
Command forceDelete = std::move(cmd);
618-
}
619-
620-
int code = subprocess.Wait();
621-
{
622-
auto join_threads = std::move(thread_joiner);
623-
}
624-
if (io_error) {
625-
LOG(ERROR) << "IO error communicating with " << cmd_short_name;
626-
return -1;
627-
}
628-
return code;
629-
}
630-
631525
namespace {
632526

633527
struct ExtraParam {

base/cvd/cuttlefish/common/libs/utils/subprocess.h

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -325,23 +325,6 @@ class Command {
325325
};
326326
std::ostream& operator<<(std::ostream& out, const Command& command);
327327

328-
329-
/*
330-
* Consumes a Command and runs it, optionally managing the stdio channels.
331-
*
332-
* If `stdin` is set, the subprocess stdin will be pipe providing its contents.
333-
* If `stdout` is set, the subprocess stdout will be captured and saved to it.
334-
* If `stderr` is set, the subprocess stderr will be captured and saved to it.
335-
*
336-
* If `command` exits normally, the lower 8 bits of the return code will be
337-
* returned in a value between 0 and 255.
338-
* If some setup fails, `command` fails to start, or `command` exits due to a
339-
* signal, the return value will be negative.
340-
*/
341-
int RunWithManagedStdio(Command&& cmd_tmp, const std::string* stdin,
342-
std::string* stdout, std::string* stderr,
343-
SubprocessOptions options = SubprocessOptions());
344-
345328
/**
346329
* Returns the exit status on success, negative values on error
347330
*
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/*
2+
* Copyright (C) 2018 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include "cuttlefish/common/libs/utils/subprocess_managed_stdio.h"
18+
19+
#include <cerrno>
20+
#include <cstring>
21+
#include <ostream>
22+
#include <string>
23+
#include <thread>
24+
#include <utility>
25+
#include <vector>
26+
27+
#include <android-base/logging.h>
28+
#include <android-base/strings.h>
29+
30+
#include "cuttlefish/common/libs/fs/shared_buf.h"
31+
#include "cuttlefish/common/libs/fs/shared_fd.h"
32+
#include "cuttlefish/common/libs/utils/subprocess.h"
33+
34+
namespace cuttlefish {
35+
namespace {
36+
37+
// A class that waits for threads to exit in its destructor.
38+
class ThreadJoiner {
39+
std::vector<std::thread*> threads_;
40+
41+
public:
42+
ThreadJoiner(const std::vector<std::thread*> threads) : threads_(threads) {}
43+
~ThreadJoiner() {
44+
for (auto& thread : threads_) {
45+
if (thread->joinable()) {
46+
thread->join();
47+
}
48+
}
49+
}
50+
};
51+
52+
} // namespace
53+
54+
int RunWithManagedStdio(Command&& cmd_tmp, const std::string* stdin_str,
55+
std::string* stdout_str, std::string* stderr_str,
56+
SubprocessOptions options) {
57+
/*
58+
* The order of these declarations is necessary for safety. If the function
59+
* returns at any point, the Command will be destroyed first, closing all
60+
* of its references to SharedFDs. This will cause the thread internals to
61+
* fail their reads or writes. The ThreadJoiner then waits for the threads to
62+
* complete, as running the destructor of an active std::thread crashes the
63+
* program.
64+
*
65+
* C++ scoping rules dictate that objects are descoped in reverse order to
66+
* construction, so this behavior is predictable.
67+
*/
68+
std::thread stdin_thread, stdout_thread, stderr_thread;
69+
ThreadJoiner thread_joiner({&stdin_thread, &stdout_thread, &stderr_thread});
70+
Command cmd = std::move(cmd_tmp);
71+
bool io_error = false;
72+
if (stdin_str != nullptr) {
73+
SharedFD pipe_read, pipe_write;
74+
if (!SharedFD::Pipe(&pipe_read, &pipe_write)) {
75+
LOG(ERROR) << "Could not create a pipe to write the stdin of \""
76+
<< cmd.GetShortName() << "\"";
77+
return -1;
78+
}
79+
cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdIn, pipe_read);
80+
stdin_thread = std::thread([pipe_write, stdin_str, &io_error]() {
81+
int written = WriteAll(pipe_write, *stdin_str);
82+
if (written < 0) {
83+
io_error = true;
84+
LOG(ERROR) << "Error in writing stdin to process";
85+
}
86+
});
87+
}
88+
if (stdout_str != nullptr) {
89+
SharedFD pipe_read, pipe_write;
90+
if (!SharedFD::Pipe(&pipe_read, &pipe_write)) {
91+
LOG(ERROR) << "Could not create a pipe to read the stdout of \""
92+
<< cmd.GetShortName() << "\"";
93+
return -1;
94+
}
95+
cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, pipe_write);
96+
stdout_thread = std::thread([pipe_read, stdout_str, &io_error]() {
97+
int read = ReadAll(pipe_read, stdout_str);
98+
if (read < 0) {
99+
io_error = true;
100+
LOG(ERROR) << "Error in reading stdout from process";
101+
}
102+
});
103+
}
104+
if (stderr_str != nullptr) {
105+
SharedFD pipe_read, pipe_write;
106+
if (!SharedFD::Pipe(&pipe_read, &pipe_write)) {
107+
LOG(ERROR) << "Could not create a pipe to read the stderr of \""
108+
<< cmd.GetShortName() << "\"";
109+
return -1;
110+
}
111+
cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdErr, pipe_write);
112+
stderr_thread = std::thread([pipe_read, stderr_str, &io_error]() {
113+
int read = ReadAll(pipe_read, stderr_str);
114+
if (read < 0) {
115+
io_error = true;
116+
LOG(ERROR) << "Error in reading stderr from process";
117+
}
118+
});
119+
}
120+
121+
auto subprocess = cmd.Start(std::move(options));
122+
if (!subprocess.Started()) {
123+
return -1;
124+
}
125+
auto cmd_short_name = cmd.GetShortName();
126+
{
127+
// Force the destructor to run by moving it into a smaller scope.
128+
// This is necessary to close the write end of the pipe.
129+
Command forceDelete = std::move(cmd);
130+
}
131+
132+
int code = subprocess.Wait();
133+
{
134+
auto join_threads = std::move(thread_joiner);
135+
}
136+
if (io_error) {
137+
LOG(ERROR) << "IO error communicating with " << cmd_short_name;
138+
return -1;
139+
}
140+
return code;
141+
}
142+
143+
} // namespace cuttlefish

0 commit comments

Comments
 (0)