--- /dev/null
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef ANDROID_AUDIO_FD_TO_STRING_H
+#define ANDROID_AUDIO_FD_TO_STRING_H
+
+#include <fcntl.h>
+#include <future>
+#include <sstream>
+#include <unistd.h>
+
+namespace android {
+namespace audio_utils {
+
+/**
+ * FdToString
+ *
+ * Captures string data written to a file descriptor.
+ * The class will furnish a writable file descriptor by fd().
+ * The string may be read through getStringAndClose().
+ */
+
+class FdToString {
+public:
+ /**
+ * \param prefix is the prefix string prepended to each new line.
+ */
+ explicit FdToString(const std::string &prefix = "- ", int timeoutMs = 1000)
+ : mPrefix(prefix)
+ , mTimeoutMs(timeoutMs) {
+ const int status = pipe2(mPipeFd, O_CLOEXEC);
+ if (status == 0) {
+ mOutput = std::async(std::launch::async, reader, mPipeFd[0], mPrefix);
+ }
+ // on initialization failure fd() returns -1.
+ }
+
+ ~FdToString() {
+ for (auto &fd : mPipeFd) {
+ if (fd >= 0) {
+ close(fd);
+ fd = -1;
+ }
+ }
+ }
+
+ /**
+ * Returns the write end of the pipe as a file descriptor or -1 if invalid or already closed.
+ *
+ * Do not close this fd directly as this class should own the fd. Instead, use
+ * getStringAndClose() to close the fd and return the string.
+ */
+ int fd() const {
+ return mPipeFd[1];
+ }
+
+ /**
+ * Returns the string representation of data written to the fd.
+ *
+ * An empty string is returned on failure (or timeout). It is acceptable to call this
+ * method multiple times to obtain the final string; the fd is closed after the first call.
+ */
+ std::string getStringAndClose() {
+ if (!mOutput.valid()) return "";
+ if (mPipeFd[1] >= 0) {
+ close(mPipeFd[1]);
+ mPipeFd[1] = -1;
+ }
+ std::future_status status = mOutput.wait_for(std::chrono::milliseconds(mTimeoutMs));
+ return status == std::future_status::ready ? mOutput.get() : "";
+ }
+
+private:
+ static std::string reader(int fd, std::string prefix) {
+ char buf[4096];
+ int red;
+ std::stringstream ss;
+ bool requiresPrefix = true;
+ while ((red = read(fd, buf, sizeof(buf))) > 0) {
+ char *delim, *bptr = buf;
+ while (!prefix.empty() && (delim = (char *)memchr(bptr, '\n', red)) != nullptr) {
+ if (requiresPrefix) ss << prefix;
+ const size_t line = delim - bptr + 1;
+ ss.write(bptr, line);
+ bptr += line;
+ red -= line;
+ requiresPrefix = true;
+ }
+ if (red > 0) {
+ ss << prefix;
+ ss.write(bptr, red);
+ requiresPrefix = false;
+ }
+ }
+ return ss.str();
+ }
+
+ const std::string mPrefix;
+ const int mTimeoutMs;
+ int mPipeFd[2] = {-1, -1};
+ std::future<std::string> mOutput;
+};
+
+} // namespace audio_utils
+} // namespace android
+
+#endif // !ANDROID_AUDIO_FD_TO_STRING_H
--- /dev/null
+/*
+ * Copyright 2018 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "audio_utils_fdtostring_tests"
+
+#include <audio_utils/FdToString.h>
+#include <gtest/gtest.h>
+#include <log/log.h>
+
+using namespace android::audio_utils;
+
+TEST(audio_utils_fdtostring, basic) {
+ const std::string PREFIX{"aa "};
+ const std::string TEST_STRING{"hello world"};
+
+ FdToString fdToString(PREFIX);
+ const int fd = fdToString.fd();
+ ASSERT_TRUE(fd >= 0);
+
+ write(fd, TEST_STRING.c_str(), TEST_STRING.size());
+
+ const std::string result = fdToString.getStringAndClose();
+
+ ASSERT_EQ((PREFIX + TEST_STRING), result);
+}
+
+TEST(audio_utils_fdtostring, multilines) {
+ const std::string PREFIX{"aa "};
+ const std::string DELIM{"\n"};
+ const std::string TEST_STRING1{"hello world\n"};
+ const std::string TEST_STRING2{"goodbye\n"};
+
+ FdToString fdToString(PREFIX);
+ const int fd = fdToString.fd();
+ ASSERT_TRUE(fd >= 0);
+
+ write(fd, TEST_STRING1.c_str(), TEST_STRING1.size());
+ write(fd, DELIM.c_str(), DELIM.size()); // double newline
+ write(fd, TEST_STRING2.c_str(), TEST_STRING2.size());
+
+ const std::string result = fdToString.getStringAndClose();
+
+ ASSERT_EQ((PREFIX + TEST_STRING1 + PREFIX + DELIM + PREFIX + TEST_STRING2), result);
+}