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
|
/*
* Copyright (C) 2016, 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 "wificond/tests/shell_utils.h"
#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include <android-base/logging.h>
#include <android-base/unique_fd.h>
using android::base::unique_fd;
namespace android {
namespace wificond {
namespace tests {
namespace integration {
namespace {
#ifdef __ANDROID__
const char kShellPath[] = "/system/bin/sh";
#else
const char kShellPath[] = "/bin/sh";
#endif
const int kShellTimeoutMs = 30 * 1000;
const int kMillisecondsPerSecond = 1000;
const int kNanosecondsPerMillisecond = 1000 * 1000;
// Represents some arbitrary, non-decreasing time in milliseconds.
int64_t GetCurrentTimeMs() {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (int64_t{ts.tv_sec} * kMillisecondsPerSecond) +
(ts.tv_nsec / kNanosecondsPerMillisecond);
}
} // namespace
int RunShellCommand(const std::string& shell_command, std::string* output) {
int fds[2];
if (pipe2(fds, O_NONBLOCK) != 0) {
LOG(FATAL) << "Failed to create pipe";
}
unique_fd read_fd(fds[0]);
unique_fd write_fd(fds[1]);
fcntl(read_fd.get(), F_SETFL, O_CLOEXEC | O_NONBLOCK);
const pid_t child_pid = fork();
if (child_pid == -1) {
LOG(FATAL) << "Failed to fork child for shell command: " << shell_command;
}
if (child_pid == 0) { // We are in the child process.
close(0); // Don't want to read anything in this process.
dup2(write_fd.get(), 1); // Replace existing stdout with the pipe.
read_fd.reset();
write_fd.reset();
// Note that we're keeping parent stderr.
execl(kShellPath, "sh", "-c", shell_command.c_str(), nullptr);
LOG(FATAL) << "exec() of child failed " << strerror(errno);
}
// We are in the parent process.
write_fd.reset(); // Close this or we never get HUP from child.
struct pollfd shell_output;
memset(&shell_output, 0, sizeof(shell_output));
shell_output.fd = read_fd.get();
shell_output.events = POLLIN;
ssize_t nread;
char buf[512];
int64_t start_time_ms = GetCurrentTimeMs();
while (GetCurrentTimeMs() - start_time_ms < kShellTimeoutMs) {
int64_t time_left_ms = kShellTimeoutMs - (GetCurrentTimeMs() - start_time_ms);
poll(&shell_output, 1, (time_left_ms < 0) ? 0 : time_left_ms);
// Blindly read from this file descriptor until there is no data available.
do {
nread = TEMP_FAILURE_RETRY(read(shell_output.fd, buf, sizeof(buf)));
if (output && nread > 0) {
output->append(buf, nread);
}
} while (nread > 0);
// We're done if the child process has closed its stdout.
if (shell_output.revents & POLLHUP) {
break;
}
}
// Reap our child's exit status.
int wait_status = 0;
int waitpid_ret = 0;
start_time_ms = GetCurrentTimeMs();
auto NeedToWaitForChild = [child_pid, &wait_status, &waitpid_ret]() {
if (waitpid_ret == 0) {
waitpid_ret = waitpid(child_pid, &wait_status, WNOHANG);
if (waitpid_ret == -1) {
LOG(ERROR) << "waitpid() returned -1 on error(" << errno << "): "
<< strerror(errno);
}
}
return waitpid_ret == 0;
};
start_time_ms = GetCurrentTimeMs();
while (NeedToWaitForChild() && GetCurrentTimeMs() - start_time_ms < 1000) {
usleep(1000);
}
// Child still hasn't died. Send our child the big hammer.
if (waitpid_ret != child_pid) {
int kill_ret = kill(child_pid, SIGKILL);
// Allow kill to fail with ESRCH, since it indicated that the child may
// have already died.
if (kill_ret != 0 && errno != ESRCH) {
LOG(ERROR) << "Failed to send signal to child: " << strerror(errno);
}
// Wait for the child to die after receiving that signal.
start_time_ms = GetCurrentTimeMs();
while (NeedToWaitForChild() && GetCurrentTimeMs() - start_time_ms < 1000) {
usleep(1000);
}
}
if (waitpid_ret == child_pid && WIFEXITED(wait_status)) {
return WEXITSTATUS(wait_status);
}
LOG(ERROR) << "Shell command timed out.";
return -1;
}
} // namespace integration
} // namespace tests
} // namespace wificond
} // namespace android
|