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);