Introduce base::TempFile / TempDir
Commonly needed by tests.
Bug: 73283884
Change-Id: I033b3816eb6d0f30c235de26f39b06a7e7bd1f3d
diff --git a/Android.bp b/Android.bp
index b46c4aa..a820f17 100644
--- a/Android.bp
+++ b/Android.bp
@@ -34,6 +34,7 @@
"src/base/file_utils.cc",
"src/base/page_allocator.cc",
"src/base/string_splitter.cc",
+ "src/base/temp_file.cc",
"src/base/thread_checker.cc",
"src/base/unix_task_runner.cc",
"src/base/watchdog.cc",
@@ -136,6 +137,7 @@
"src/base/file_utils.cc",
"src/base/page_allocator.cc",
"src/base/string_splitter.cc",
+ "src/base/temp_file.cc",
"src/base/thread_checker.cc",
"src/base/unix_task_runner.cc",
"src/base/watchdog.cc",
@@ -247,6 +249,7 @@
"src/base/file_utils.cc",
"src/base/page_allocator.cc",
"src/base/string_splitter.cc",
+ "src/base/temp_file.cc",
"src/base/test/test_task_runner.cc",
"src/base/test/vm_test_utils.cc",
"src/base/thread_checker.cc",
@@ -2959,6 +2962,7 @@
"src/base/file_utils.cc",
"src/base/page_allocator.cc",
"src/base/string_splitter.cc",
+ "src/base/temp_file.cc",
"src/base/thread_checker.cc",
"src/base/unix_task_runner.cc",
"src/base/watchdog.cc",
@@ -3074,6 +3078,8 @@
"src/base/string_splitter.cc",
"src/base/string_splitter_unittest.cc",
"src/base/task_runner_unittest.cc",
+ "src/base/temp_file.cc",
+ "src/base/temp_file_unittest.cc",
"src/base/test/test_task_runner.cc",
"src/base/test/vm_test_utils.cc",
"src/base/thread_checker.cc",
diff --git a/include/perfetto/base/temp_file.h b/include/perfetto/base/temp_file.h
new file mode 100644
index 0000000..e862971
--- /dev/null
+++ b/include/perfetto/base/temp_file.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 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 INCLUDE_PERFETTO_BASE_TEMP_FILE_H_
+#define INCLUDE_PERFETTO_BASE_TEMP_FILE_H_
+
+#include <string>
+
+#include "perfetto/base/scoped_file.h"
+
+namespace perfetto {
+namespace base {
+
+class TempFile {
+ public:
+ static TempFile CreateUnlinked();
+ static TempFile Create();
+
+ TempFile(TempFile&&) noexcept;
+ TempFile& operator=(TempFile&&);
+ ~TempFile();
+
+ const std::string& path() const { return path_; }
+ int fd() const { return *fd_; }
+ int operator*() const { return *fd_; }
+
+ // Unlinks the file from the filesystem but keeps the fd() open.
+ // It is safe to call this multiple times.
+ void Unlink();
+
+ // Releases the underlying file descriptor. Will unlink the file from the
+ // filesystem if it was created via CreateUnlinked().
+ ScopedFile ReleaseFD();
+
+ private:
+ TempFile();
+ TempFile(const TempFile&) = delete;
+ TempFile& operator=(const TempFile&) = delete;
+
+ ScopedFile fd_;
+ std::string path_;
+};
+
+class TempDir {
+ public:
+ static TempDir Create();
+
+ TempDir(TempDir&&) noexcept;
+ TempDir& operator=(TempDir&&);
+ ~TempDir();
+
+ const std::string& path() const { return path_; }
+
+ private:
+ TempDir();
+ TempDir(const TempDir&) = delete;
+ TempDir& operator=(const TempDir&) = delete;
+
+ std::string path_;
+};
+
+} // namespace base
+} // namespace perfetto
+
+#endif // INCLUDE_PERFETTO_BASE_TEMP_FILE_H_
diff --git a/src/base/BUILD.gn b/src/base/BUILD.gn
index 2c3f090..1c3890e 100644
--- a/src/base/BUILD.gn
+++ b/src/base/BUILD.gn
@@ -25,6 +25,7 @@
"file_utils.cc",
"page_allocator.cc",
"string_splitter.cc",
+ "temp_file.cc",
"thread_checker.cc",
"unix_task_runner.cc",
]
@@ -102,6 +103,7 @@
"scoped_file_unittest.cc",
"string_splitter_unittest.cc",
"task_runner_unittest.cc",
+ "temp_file_unittest.cc",
"thread_checker_unittest.cc",
"utils_unittest.cc",
"weak_ptr_unittest.cc",
diff --git a/src/base/temp_file.cc b/src/base/temp_file.cc
new file mode 100644
index 0000000..1792de0
--- /dev/null
+++ b/src/base/temp_file.cc
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 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.
+ */
+
+#include "perfetto/base/temp_file.h"
+
+#include <unistd.h>
+
+namespace perfetto {
+namespace base {
+
+namespace {
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+const char kSysTmpPath[] = "/data/local/tmp";
+#else
+const char kSysTmpPath[] = "/tmp";
+#endif
+} // namespace
+
+// static
+TempFile TempFile::Create() {
+ TempFile temp_file;
+ temp_file.path_.assign(kSysTmpPath);
+ temp_file.path_.append("/perfetto-XXXXXXXX");
+ temp_file.fd_.reset(mkstemp(&temp_file.path_[0]));
+ PERFETTO_CHECK(temp_file.fd_);
+ return temp_file;
+}
+
+// static
+TempFile TempFile::CreateUnlinked() {
+ TempFile temp_file = TempFile::Create();
+ temp_file.Unlink();
+ return temp_file;
+}
+
+TempFile::TempFile() = default;
+
+TempFile::~TempFile() {
+ Unlink();
+}
+
+ScopedFile TempFile::ReleaseFD() {
+ Unlink();
+ return std::move(fd_);
+}
+
+void TempFile::Unlink() {
+ if (path_.empty())
+ return;
+ PERFETTO_CHECK(unlink(path_.c_str()) == 0);
+ path_.clear();
+}
+
+TempFile::TempFile(TempFile&&) noexcept = default;
+TempFile& TempFile::operator=(TempFile&&) = default;
+
+// static
+TempDir TempDir::Create() {
+ TempDir temp_dir;
+ temp_dir.path_.assign(kSysTmpPath);
+ temp_dir.path_.append("/perfetto-XXXXXXXX");
+ PERFETTO_CHECK(mkdtemp(&temp_dir.path_[0]));
+ return temp_dir;
+}
+
+TempDir::TempDir() = default;
+
+TempDir::~TempDir() {
+ PERFETTO_CHECK(rmdir(path_.c_str()) == 0);
+}
+
+} // namespace base
+} // namespace perfetto
diff --git a/src/base/temp_file_unittest.cc b/src/base/temp_file_unittest.cc
new file mode 100644
index 0000000..d167b66
--- /dev/null
+++ b/src/base/temp_file_unittest.cc
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 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.
+ */
+
+#include "perfetto/base/temp_file.h"
+
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "gtest/gtest.h"
+
+namespace perfetto {
+namespace base {
+namespace {
+
+bool PathExists(const std::string& path) {
+ struct stat stat_buf;
+ return stat(path.c_str(), &stat_buf) == 0;
+}
+
+TEST(TempFileTest, Create) {
+ std::string path;
+ int fd;
+ {
+ TempFile tf = TempFile::Create();
+ path = tf.path();
+ fd = tf.fd();
+ ASSERT_NE("", path);
+ ASSERT_GE(fd, 0);
+ ASSERT_TRUE(PathExists(path));
+ ASSERT_GE(write(fd, "foo", 4), 0);
+
+ TempFile moved_tf(std::move(tf));
+ ASSERT_EQ("", tf.path());
+ ASSERT_EQ(-1, tf.fd());
+ ASSERT_EQ(path, moved_tf.path());
+ ASSERT_EQ(fd, moved_tf.fd());
+ ASSERT_GE(write(moved_tf.fd(), "foo", 4), 0);
+
+ TempFile moved_tf2 = std::move(moved_tf);
+ ASSERT_EQ("", moved_tf.path());
+ ASSERT_EQ(-1, moved_tf.fd());
+ ASSERT_EQ(path, moved_tf2.path());
+ ASSERT_EQ(fd, moved_tf2.fd());
+ ASSERT_GE(write(moved_tf2.fd(), "foo", 4), 0);
+ }
+
+ // The file should be deleted and closed now.
+ ASSERT_FALSE(PathExists(path));
+
+ ASSERT_EQ(-1, write(fd, "foo", 4));
+}
+
+TEST(TempFileTest, CreateUnlinked) {
+ int fd;
+ {
+ TempFile tf = TempFile::CreateUnlinked();
+ ASSERT_EQ("", tf.path());
+ fd = tf.fd();
+ ASSERT_GE(fd, 0);
+ ASSERT_GE(write(fd, "foo", 4), 0);
+ }
+ ASSERT_EQ(-1, write(fd, "foo", 4));
+}
+
+TEST(TempFileTest, ReleaseUnlinked) {
+ ScopedFile fd;
+ {
+ TempFile tf = TempFile::Create();
+ fd = tf.ReleaseFD();
+ }
+ ASSERT_GE(write(*fd, "foo", 4), 0);
+}
+
+TEST(TempFileTest, ReleaseLinked) {
+ ScopedFile fd;
+ std::string path;
+ {
+ TempFile tf = TempFile::CreateUnlinked();
+ path = tf.path();
+ fd = tf.ReleaseFD();
+ }
+
+ // The file should be unlinked from the filesystem.
+ ASSERT_FALSE(PathExists(path));
+
+ // But still open and writable.
+ ASSERT_GE(write(*fd, "foo", 4), 0);
+}
+
+TEST(TempFileTest, TempDir) {
+ std::string path;
+ {
+ TempDir td = TempDir::Create();
+ ASSERT_NE("", td.path());
+ ASSERT_TRUE(PathExists(td.path()));
+ path = td.path();
+ }
+ ASSERT_FALSE(PathExists(path));
+}
+
+} // namespace
+} // namespace base
+} // namespace perfetto
diff --git a/src/ipc/client_impl_unittest.cc b/src/ipc/client_impl_unittest.cc
index 10f59f9..abe2494 100644
--- a/src/ipc/client_impl_unittest.cc
+++ b/src/ipc/client_impl_unittest.cc
@@ -23,6 +23,7 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
+#include "perfetto/base/temp_file.h"
#include "perfetto/base/utils.h"
#include "perfetto/ipc/service_descriptor.h"
#include "perfetto/ipc/service_proxy.h"
@@ -347,11 +348,10 @@
EXPECT_CALL(proxy_events_, OnConnect()).WillOnce(Invoke(on_connect));
task_runner_->RunUntilCheckpoint("on_connect");
- FILE* tx_file = tmpfile(); // Automatically unlinked from the filesystem.
+ base::TempFile tx_file = base::TempFile::CreateUnlinked();
static constexpr char kFileContent[] = "shared file";
- fwrite(kFileContent, sizeof(kFileContent), 1, tx_file);
- fflush(tx_file);
- host_->next_reply_fd = fileno(tx_file);
+ write(tx_file.fd(), kFileContent, sizeof(kFileContent));
+ host_->next_reply_fd = tx_file.fd();
EXPECT_CALL(*host_method, OnInvoke(_, _))
.WillOnce(Invoke(
@@ -371,7 +371,7 @@
proxy->BeginInvoke("FakeMethod1", req, std::move(deferred_reply));
task_runner_->RunUntilCheckpoint("on_reply");
- fclose(tx_file);
+ tx_file.ReleaseFD();
base::ScopedFile rx_fd = cli_->TakeReceivedFD();
ASSERT_TRUE(rx_fd);
char buf[sizeof(kFileContent)] = {};
@@ -392,12 +392,9 @@
EXPECT_CALL(proxy_events_, OnConnect()).WillOnce(Invoke(on_connect));
task_runner_->RunUntilCheckpoint("on_connect");
- base::ScopedFstream tx_file(
- tmpfile()); // Automatically unlinked from the filesystem.
+ base::TempFile tx_file = base::TempFile::CreateUnlinked();
static constexpr char kFileContent[] = "shared file";
- fwrite(kFileContent, sizeof(kFileContent), 1, tx_file.get());
- fflush(tx_file.get());
-
+ write(tx_file.fd(), kFileContent, sizeof(kFileContent));
EXPECT_CALL(*host_method, OnInvoke(_, _))
.WillOnce(Invoke(
[](const Frame::InvokeMethod& req, Frame::InvokeMethodReply* reply) {
@@ -414,7 +411,7 @@
on_reply();
});
proxy->BeginInvoke("FakeMethod1", req, std::move(deferred_reply),
- fileno(tx_file.get()));
+ tx_file.fd());
task_runner_->RunUntilCheckpoint("on_reply");
base::ScopedFile rx_fd = std::move(host_->received_fd_);
diff --git a/src/ipc/host_impl_unittest.cc b/src/ipc/host_impl_unittest.cc
index 040eb98..cc46500 100644
--- a/src/ipc/host_impl_unittest.cc
+++ b/src/ipc/host_impl_unittest.cc
@@ -21,6 +21,7 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "perfetto/base/scoped_file.h"
+#include "perfetto/base/temp_file.h"
#include "perfetto/ipc/service.h"
#include "perfetto/ipc/service_descriptor.h"
#include "src/base/test/test_task_runner.h"
@@ -327,21 +328,20 @@
RequestProto req_args;
cli_->InvokeMethod(cli_->last_bound_service_id_, 1, req_args);
auto on_reply_sent = task_runner_->CreateCheckpoint("on_reply_sent");
- FILE* tx_file = tmpfile();
- fwrite(kFileContent, sizeof(kFileContent), 1, tx_file);
- fflush(tx_file);
+ base::TempFile tx_file = base::TempFile::CreateUnlinked();
+ write(tx_file.fd(), kFileContent, sizeof(kFileContent));
EXPECT_CALL(*fake_service, OnFakeMethod1(_, _))
- .WillOnce(Invoke([on_reply_sent, tx_file](const RequestProto& req,
- DeferredBase* reply) {
+ .WillOnce(Invoke([on_reply_sent, &tx_file](const RequestProto& req,
+ DeferredBase* reply) {
std::unique_ptr<ReplyProto> reply_args(new ReplyProto());
auto async_res = AsyncResult<ProtoMessage>(
std::unique_ptr<ProtoMessage>(reply_args.release()));
- async_res.set_fd(fileno(tx_file));
+ async_res.set_fd(tx_file.fd());
reply->Resolve(std::move(async_res));
on_reply_sent();
}));
task_runner_->RunUntilCheckpoint("on_reply_sent");
- fclose(tx_file);
+ tx_file.ReleaseFD();
auto on_fd_received = task_runner_->CreateCheckpoint("on_fd_received");
EXPECT_CALL(*cli_, OnFileDescriptorReceived(_))
@@ -368,12 +368,10 @@
static constexpr char kFileContent[] = "shared file";
RequestProto req_args;
- base::ScopedFstream tx_file(
- tmpfile()); // Automatically unlinked from the filesystem.
- fwrite(kFileContent, sizeof(kFileContent), 1, tx_file.get());
- fflush(tx_file.get());
+ base::TempFile tx_file = base::TempFile::CreateUnlinked();
+ write(tx_file.fd(), kFileContent, sizeof(kFileContent));
cli_->InvokeMethod(cli_->last_bound_service_id_, 1, req_args, false,
- fileno(tx_file.get()));
+ tx_file.fd());
EXPECT_CALL(*cli_, OnInvokeMethodReply(_));
base::ScopedFile rx_fd;
EXPECT_CALL(*fake_service, OnFakeMethod1(_, _))
diff --git a/src/ipc/unix_socket_unittest.cc b/src/ipc/unix_socket_unittest.cc
index a2bdb58..f780123 100644
--- a/src/ipc/unix_socket_unittest.cc
+++ b/src/ipc/unix_socket_unittest.cc
@@ -25,6 +25,7 @@
#include "gtest/gtest.h"
#include "perfetto/base/build_config.h"
#include "perfetto/base/logging.h"
+#include "perfetto/base/temp_file.h"
#include "perfetto/base/utils.h"
#include "src/base/test/test_task_runner.h"
#include "src/ipc/test/test_socket.h"
@@ -253,9 +254,8 @@
if (pid == 0) {
// Child process.
- FILE* tmp = tmpfile();
- ASSERT_NE(nullptr, tmp);
- int tmp_fd = fileno(tmp);
+ base::TempFile scoped_tmp = base::TempFile::CreateUnlinked();
+ int tmp_fd = scoped_tmp.fd();
ASSERT_FALSE(ftruncate(tmp_fd, kTmpSize));
char* mem = reinterpret_cast<char*>(
mmap(nullptr, kTmpSize, PROT_READ | PROT_WRITE, MAP_SHARED, tmp_fd, 0));
diff --git a/src/perfetto_cmd/perfetto_cmd.cc b/src/perfetto_cmd/perfetto_cmd.cc
index 85e3a33..17b4b23 100644
--- a/src/perfetto_cmd/perfetto_cmd.cc
+++ b/src/perfetto_cmd/perfetto_cmd.cc
@@ -322,8 +322,7 @@
if (!dropbox_tag_.empty()) {
// If we are tracing to DropBox, there's no need to make a
// filesystem-visible temporary file.
- // TODO(skyostil): Fall back to mkstemp() + open() + unlink() for older
- // devices.
+ // TODO(skyostil): Fall back to base::TempFile for older devices.
fd.reset(open(kTempDropBoxTraceDir, O_TMPFILE | O_RDWR, 0600));
if (!fd) {
PERFETTO_ELOG("Could not create a temporary trace file in %s",
diff --git a/src/traced/probes/filesystem/fs_mount_unittest.cc b/src/traced/probes/filesystem/fs_mount_unittest.cc
index 1cdebfc..da544e8 100644
--- a/src/traced/probes/filesystem/fs_mount_unittest.cc
+++ b/src/traced/probes/filesystem/fs_mount_unittest.cc
@@ -25,6 +25,7 @@
#include "gtest/gtest.h"
#include "perfetto/base/build_config.h"
#include "perfetto/base/scoped_file.h"
+#include "perfetto/base/temp_file.h"
#include "perfetto/base/utils.h"
namespace perfetto {
@@ -49,19 +50,11 @@
#INVALIDLINE
devfs /dev devfs,local,nobrowse
)";
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
- char tmp_path[PATH_MAX] = "/data/local/tmp/fake_mounts.XXXXXX";
-#else
- char tmp_path[PATH_MAX] = "/tmp/fake_mounts.XXXXXX";
-#endif
- base::ScopedFile tmp_fd(mkstemp(tmp_path));
- ASSERT_GT(*tmp_fd, -1);
- base::ignore_result(write(*tmp_fd, kMounts, sizeof(kMounts)));
- tmp_fd.reset();
-
- std::multimap<BlockDeviceID, std::string> mounts = ParseMounts(tmp_path);
- unlink(tmp_path);
+ base::TempFile tmp_file = base::TempFile::Create();
+ base::ignore_result(write(tmp_file.fd(), kMounts, sizeof(kMounts)));
+ std::multimap<BlockDeviceID, std::string> mounts =
+ ParseMounts(tmp_file.path().c_str());
struct stat dev_stat = {}, root_stat = {};
ASSERT_NE(stat("/dev", &dev_stat), -1);
ASSERT_NE(stat("/", &root_stat), -1);
diff --git a/src/tracing/ipc/posix_shared_memory.cc b/src/tracing/ipc/posix_shared_memory.cc
index 4bfe645..046096b 100644
--- a/src/tracing/ipc/posix_shared_memory.cc
+++ b/src/tracing/ipc/posix_shared_memory.cc
@@ -29,6 +29,7 @@
#include "perfetto/base/build_config.h"
#include "perfetto/base/logging.h"
+#include "perfetto/base/temp_file.h"
#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
#include <linux/memfd.h>
@@ -52,11 +53,8 @@
}
#endif
- if (!fd) {
- FILE* tmp_file = tmpfile();
- PERFETTO_CHECK(tmp_file);
- fd.reset(fileno(tmp_file));
- }
+ if (!fd)
+ fd = base::TempFile::CreateUnlinked().ReleaseFD();
PERFETTO_CHECK(fd);
int res = ftruncate(fd.get(), static_cast<off_t>(size));
diff --git a/src/tracing/ipc/posix_shared_memory_unittest.cc b/src/tracing/ipc/posix_shared_memory_unittest.cc
index 1afb5ef..b07cb00 100644
--- a/src/tracing/ipc/posix_shared_memory_unittest.cc
+++ b/src/tracing/ipc/posix_shared_memory_unittest.cc
@@ -26,6 +26,7 @@
#include "gtest/gtest.h"
#include "perfetto/base/build_config.h"
#include "perfetto/base/scoped_file.h"
+#include "perfetto/base/temp_file.h"
#include "perfetto/base/utils.h"
#include "src/base/test/test_task_runner.h"
#include "src/base/test/vm_test_utils.h"
@@ -65,13 +66,13 @@
}
TEST(PosixSharedMemoryTest, AttachToFd) {
- FILE* tmp_file = tmpfile(); // Creates an unlinked auto-deleting temp file.
- const int fd_num = fileno(tmp_file);
+ base::TempFile tmp_file = base::TempFile::CreateUnlinked();
+ const int fd_num = tmp_file.fd();
ASSERT_EQ(0, ftruncate(fd_num, base::kPageSize));
ASSERT_EQ(7, PERFETTO_EINTR(write(fd_num, "foobar", 7)));
std::unique_ptr<PosixSharedMemory> shm =
- PosixSharedMemory::AttachToFd(base::ScopedFile(fd_num));
+ PosixSharedMemory::AttachToFd(tmp_file.ReleaseFD());
void* const shm_start = shm->start();
const size_t shm_size = shm->size();
ASSERT_NE(nullptr, shm_start);