Close `watch print` gracefully with user interrupt
The print functionality of watch command needed to be stopped with a
SIGINT. However, this could lead to some resources (for example the
output file descriptor) not being closed properly.
This CL adds a wrapper around the poll function allowing print command
to wait for the user to press the return key and quit gracefully on user
input.
Bug: 199746421
Test: Tested Manually
Change-Id: I2bb3ffd6b0711f40ecafe3a94c21e756ac2cd4e6
diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp
index 152729c..c721074 100644
--- a/services/camera/libcameraservice/CameraService.cpp
+++ b/services/camera/libcameraservice/CameraService.cpp
@@ -28,6 +28,7 @@
#include <sys/types.h>
#include <inttypes.h>
#include <pthread.h>
+#include <poll.h>
#include <android/hardware/ICamera.h>
#include <android/hardware/ICameraClient.h>
@@ -4596,7 +4597,7 @@
} else if (args.size() >= 2 && args[0] == String16("set-camera-mute")) {
return handleSetCameraMute(args);
} else if (args.size() >= 2 && args[0] == String16("watch")) {
- return handleWatchCommand(args, out);
+ return handleWatchCommand(args, in, out);
} else if (args.size() == 1 && args[0] == String16("help")) {
printHelp(out);
return OK;
@@ -4743,7 +4744,7 @@
return OK;
}
-status_t CameraService::handleWatchCommand(const Vector<String16>& args, int outFd) {
+status_t CameraService::handleWatchCommand(const Vector<String16>& args, int inFd, int outFd) {
if (args.size() >= 3 && args[1] == String16("start")) {
return startWatchingTags(args, outFd);
} else if (args.size() == 2 && args[1] == String16("dump")) {
@@ -4751,7 +4752,7 @@
} else if (args.size() == 2 && args[1] == String16("stop")) {
return stopWatchingTags(outFd);
} else if (args.size() >= 2 && args[1] == String16("print")) {
- return printWatchedTags(args, outFd);
+ return printWatchedTags(args, inFd, outFd);
} else if (args.size() == 2 && args[1] == String16("clear")) {
return clearCachedMonitoredTagDumps(outFd);
}
@@ -4764,7 +4765,7 @@
" stop stops watching all tags\n"
" print [-n <refresh_interval_ms>]\n"
" prints the monitored information in real time\n"
- " Hit Ctrl+C to exit\n"
+ " Hit return to exit\n"
" clear clears all buffers storing information for watch command");
return BAD_VALUE;
}
@@ -4841,9 +4842,9 @@
return OK;
}
-status_t CameraService::printWatchedTags(const Vector<String16> &args, int outFd) {
+status_t CameraService::printWatchedTags(const Vector<String16> &args, int inFd, int outFd) {
// Figure outFd refresh interval, if present in args
- useconds_t refreshTimeoutMs = 1000; // refresh every 1s by default
+ long refreshTimeoutMs = 1000L; // refresh every 1s by default
if (args.size() > 2) {
size_t intervalIdx; // index of '-n'
for (intervalIdx = 2; intervalIdx < args.size() && String16("-n") != args[intervalIdx];
@@ -4856,6 +4857,9 @@
}
}
+ // Set min timeout of 10s. This prevents edge cases in polling when timeout of 0 is passed.
+ refreshTimeoutMs = refreshTimeoutMs < 10 ? 10 : refreshTimeoutMs;
+
std::set<String16> connectedMonitoredClients;
{
Mutex::Autolock logLock(mLogLock);
@@ -4890,11 +4894,109 @@
}
// For connected watched clients, print monitored tags live
- return printWatchedTagsUntilInterrupt(refreshTimeoutMs * 1000, outFd);
+ return printWatchedTagsUntilInterrupt(refreshTimeoutMs, inFd, outFd);
}
-status_t CameraService::printWatchedTagsUntilInterrupt(useconds_t refreshMicros, int outFd) {
- dprintf(outFd, "Press Ctrl + C to exit...\n\n");
+// Print all events in vector `events' that came after lastPrintedEvent
+void printNewWatchedEvents(int outFd,
+ const char *cameraId,
+ const String16 &packageName,
+ const std::vector<std::string> &events,
+ const std::string &lastPrintedEvent) {
+ if (events.empty()) { return; }
+
+ // index of lastPrintedEvent in events.
+ // lastPrintedIdx = events.size() if lastPrintedEvent is not in events
+ size_t lastPrintedIdx;
+ for (lastPrintedIdx = 0;
+ lastPrintedIdx < events.size() && lastPrintedEvent != events[lastPrintedIdx];
+ lastPrintedIdx++);
+
+ if (lastPrintedIdx == 0) { return; } // early exit if no new event in `events`
+
+ String8 packageName8(packageName);
+ const char *printablePackageName = packageName8.lockBuffer(packageName8.size());
+
+ // print events in chronological order (latest event last)
+ size_t idxToPrint = lastPrintedIdx;
+ do {
+ idxToPrint--;
+ dprintf(outFd, "%s:%s %s", cameraId, printablePackageName, events[idxToPrint].c_str());
+ } while (idxToPrint != 0);
+
+ packageName8.unlockBuffer();
+}
+
+// Returns true if adb shell cmd watch should be interrupted based on data in inFd. The watch
+// command should be interrupted if the user presses the return key, or if user loses any way to
+// signal interrupt.
+// If timeoutMs == 0, this function will always return false
+bool shouldInterruptWatchCommand(int inFd, int outFd, long timeoutMs) {
+ struct timeval startTime;
+ int startTimeError = gettimeofday(&startTime, nullptr);
+ if (startTimeError) {
+ dprintf(outFd, "Failed waiting for interrupt, aborting.\n");
+ return true;
+ }
+
+ const nfds_t numFds = 1;
+ struct pollfd pollFd = { .fd = inFd, .events = POLLIN, .revents = 0 };
+
+ struct timeval currTime;
+ char buffer[2];
+ while(true) {
+ int currTimeError = gettimeofday(&currTime, nullptr);
+ if (currTimeError) {
+ dprintf(outFd, "Failed waiting for interrupt, aborting.\n");
+ return true;
+ }
+
+ long elapsedTimeMs = ((currTime.tv_sec - startTime.tv_sec) * 1000L)
+ + ((currTime.tv_usec - startTime.tv_usec) / 1000L);
+ int remainingTimeMs = (int) (timeoutMs - elapsedTimeMs);
+
+ if (remainingTimeMs <= 0) {
+ // No user interrupt within timeoutMs, don't interrupt watch command
+ return false;
+ }
+
+ int numFdsUpdated = poll(&pollFd, numFds, remainingTimeMs);
+ if (numFdsUpdated < 0) {
+ dprintf(outFd, "Failed while waiting for user input. Exiting.\n");
+ return true;
+ }
+
+ if (numFdsUpdated == 0) {
+ // No user input within timeoutMs, don't interrupt watch command
+ return false;
+ }
+
+ if (!(pollFd.revents & POLLIN)) {
+ dprintf(outFd, "Failed while waiting for user input. Exiting.\n");
+ return true;
+ }
+
+ ssize_t sizeRead = read(inFd, buffer, sizeof(buffer) - 1);
+ if (sizeRead < 0) {
+ dprintf(outFd, "Error reading user input. Exiting.\n");
+ return true;
+ }
+
+ if (sizeRead == 0) {
+ // Reached end of input fd (can happen if input is piped)
+ // User has no way to signal an interrupt, so interrupt here
+ return true;
+ }
+
+ if (buffer[0] == '\n') {
+ // User pressed return, interrupt watch command.
+ return true;
+ }
+ }
+}
+
+status_t CameraService::printWatchedTagsUntilInterrupt(long refreshMillis, int inFd, int outFd) {
+ dprintf(outFd, "Press return to exit...\n\n");
std::map<String16, std::string> packageNameToLastEvent;
while (true) {
@@ -4929,40 +5031,13 @@
cameraId.unlockBuffer();
}
}
- usleep(refreshMicros);
+ if (shouldInterruptWatchCommand(inFd, outFd, refreshMillis)) {
+ break;
+ }
}
return OK;
}
-void CameraService::printNewWatchedEvents(int outFd,
- const char *cameraId,
- const String16 &packageName,
- const std::vector<std::string> &events,
- const std::string &lastPrintedEvent) {
- if (events.empty()) { return; }
-
- // index of lastPrintedEvent in events.
- // lastPrintedIdx = events.size() if lastPrintedEvent is not in events
- size_t lastPrintedIdx;
- for (lastPrintedIdx = 0;
- lastPrintedIdx < events.size() && lastPrintedEvent != events[lastPrintedIdx];
- lastPrintedIdx++);
-
- if (lastPrintedIdx == 0) { return; } // early exit if no new event in `events`
-
- String8 packageName8(packageName);
- const char *printablePackageName = packageName8.lockBuffer(packageName8.size());
-
- // print events in chronological order (latest event last)
- size_t idxToPrint = lastPrintedIdx;
- do {
- idxToPrint--;
- dprintf(outFd, "%s:%s %s", cameraId, printablePackageName, events[idxToPrint].c_str());
- } while (idxToPrint != 0);
-
- packageName8.unlockBuffer();
-}
-
void CameraService::parseClientsToWatchLocked(String8 clients) {
mWatchedClientPackages.clear();