[file_util] Add CreateAndOpenTemporaryFileInDir from chromium src/base

This brings CreateAndOpenTemporaryFileInDir over from upstream
chromium's file_util since it will be useful for performing "atomic
writes" (i.e. write to temp + ReplaceFile) in GN.

Slight modifications were made since GN's src/base is older than or has
drifted from chromium's in parts. base::File::Flags::FLAG_CREATE was
added as it was needed by the Windows temp file implementation (only
FLAG_CREATE_ALWAYS existed so far), and the support for this flag was
added to both the Posix and Windows File::DoInitialize() impls.
The upstream impl also makes use of base::File's exclusive
read/write/delete flags and DeleteOnClose support that only work on
Windows, but these are unneeded and so were not ported over in this
change.

Change-Id: I90b5c41f86900bce2aafdea8cb8bb59604998ef0
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/14320
Reviewed-by: Brett Wilson <brettw@chromium.org>
Commit-Queue: Brett Wilson <brettw@chromium.org>
diff --git a/src/base/files/file.cc b/src/base/files/file.cc
index cecb76d..020c40f 100644
--- a/src/base/files/file.cc
+++ b/src/base/files/file.cc
@@ -23,6 +23,14 @@
   Initialize(path, flags);
 }
 
+File::File(ScopedPlatformFile platform_file)
+    : file_(std::move(platform_file)),
+      error_details_(FILE_OK) {
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+  DCHECK_GE(file_.get(), -1);
+#endif
+}
+
 File::File(PlatformFile platform_file)
     : file_(platform_file),
       error_details_(FILE_OK) {
diff --git a/src/base/files/file.h b/src/base/files/file.h
index 2c94eb4..419ba87 100644
--- a/src/base/files/file.h
+++ b/src/base/files/file.h
@@ -41,10 +41,12 @@
 class File {
  public:
   // FLAG_(OPEN|CREATE).* are mutually exclusive. You should specify exactly one
-  // of the five (possibly combining with other flags) when opening or creating
+  // of the three (possibly combining with other flags) when opening or creating
   // a file.
   enum Flags {
     FLAG_OPEN = 1 << 0,           // Opens a file, only if it exists.
+    FLAG_CREATE = 1 << 1,         // Creates a new file, only if it does not
+                                  // already exist.
     FLAG_CREATE_ALWAYS = 1 << 3,  // May overwrite an old file.
     FLAG_READ = 1 << 4,
     FLAG_WRITE = 1 << 5,
@@ -122,6 +124,7 @@
   File(const FilePath& path, uint32_t flags);
 
   // Takes ownership of |platform_file|.
+  explicit File(ScopedPlatformFile platform_file);
   explicit File(PlatformFile platform_file);
 
   // Creates an object with a specific error_details code.
diff --git a/src/base/files/file_posix.cc b/src/base/files/file_posix.cc
index b1f9f5e..a1d256c 100644
--- a/src/base/files/file_posix.cc
+++ b/src/base/files/file_posix.cc
@@ -330,6 +330,8 @@
   DCHECK(!IsValid());
 
   int open_flags = 0;
+  if (flags & FLAG_CREATE)
+    open_flags = O_CREAT | O_EXCL;
 
   if (flags & FLAG_CREATE_ALWAYS) {
     DCHECK(!open_flags);
diff --git a/src/base/files/file_util.h b/src/base/files/file_util.h
index b44129d..dbb69ec 100644
--- a/src/base/files/file_util.h
+++ b/src/base/files/file_util.h
@@ -124,6 +124,15 @@
                                  std::string* contents,
                                  size_t max_size);
 
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+
+// Performs the same function as CreateAndOpenTemporaryFileInDir(), but
+// returns the file-descriptor wrapped in a ScopedFD rather than a File.
+ScopedFD CreateAndOpenFdForTemporaryFileInDir(const FilePath& dir,
+                                              FilePath* path);
+
+#endif
+
 #if defined(OS_POSIX)
 
 // Creates a symbolic link at |symlink| pointing to |target|.  Returns
@@ -173,6 +182,10 @@
 // Get the temporary directory provided by the system.
 bool GetTempDir(FilePath* path);
 
+// Returns a new temporary file in |dir| with a unique name. On success,
+// |temp_file| is populated with the full path to the created file.
+File CreateAndOpenTemporaryFileInDir(const FilePath& dir, FilePath* temp_file);
+
 // Create a new directory. If prefix is provided, the new directory name is in
 // the format of prefixyyyy.
 // NOTE: prefix is ignored in the POSIX implementation.
diff --git a/src/base/files/file_util_posix.cc b/src/base/files/file_util_posix.cc
index aa54731..b618151 100644
--- a/src/base/files/file_util_posix.cc
+++ b/src/base/files/file_util_posix.cc
@@ -274,6 +274,16 @@
   return S_ISDIR(file_info.st_mode);
 }
 
+ScopedFD CreateAndOpenFdForTemporaryFileInDir(const FilePath& directory,
+                                              FilePath* path) {
+  *path = directory.Append(TempFileName());
+  const std::string& tmpdir_string = path->value();
+  // this should be OK since mkstemp just replaces characters in place
+  char* buffer = const_cast<char*>(tmpdir_string.c_str());
+
+  return ScopedFD(HANDLE_EINTR(mkstemp(buffer)));
+}
+
 #if !defined(OS_FUCHSIA)
 bool CreateSymbolicLink(const FilePath& target_path,
                         const FilePath& symlink_path) {
@@ -376,6 +386,11 @@
 }
 #endif  // !defined(OS_MACOSX)
 
+File CreateAndOpenTemporaryFileInDir(const FilePath& dir, FilePath* temp_file) {
+  ScopedFD fd = CreateAndOpenFdForTemporaryFileInDir(dir, temp_file);
+  return fd.is_valid() ? File(std::move(fd)) : File(File::GetLastFileError());
+}
+
 static bool CreateTemporaryDirInDirImpl(const FilePath& base_dir,
                                         const FilePath::StringType& name_tmpl,
                                         FilePath* new_dir) {
diff --git a/src/base/files/file_util_win.cc b/src/base/files/file_util_win.cc
index 9f4bf5b..150ddf6 100644
--- a/src/base/files/file_util_win.cc
+++ b/src/base/files/file_util_win.cc
@@ -283,6 +283,48 @@
   return true;
 }
 
+File CreateAndOpenTemporaryFileInDir(const FilePath& dir, FilePath* temp_file) {
+  constexpr uint32_t kFlags =
+      File::FLAG_CREATE | File::FLAG_READ | File::FLAG_WRITE;
+
+  // Use GUID instead of ::GetTempFileName() to generate unique file names.
+  // "Due to the algorithm used to generate file names, GetTempFileName can
+  // perform poorly when creating a large number of files with the same prefix.
+  // In such cases, it is recommended that you construct unique file names based
+  // on GUIDs."
+  // https://msdn.microsoft.com/library/windows/desktop/aa364991.aspx
+
+  FilePath temp_name;
+  File file;
+
+  // Although it is nearly impossible to get a duplicate name with GUID, we
+  // still use a loop here in case it happens.
+  for (int i = 0; i < 100; ++i) {
+    temp_name = dir.Append(FormatTemporaryFileName(UTF8ToWide(GenerateGUID())));
+    file.Initialize(temp_name, kFlags);
+    if (file.IsValid())
+      break;
+  }
+
+  if (!file.IsValid()) {
+    DPLOG(WARNING) << "Failed to get temporary file name in " << dir.value();
+    return file;
+  }
+
+  wchar_t long_temp_name[MAX_PATH + 1];
+  const DWORD long_name_len =
+      GetLongPathName(temp_name.value().c_str(), long_temp_name, MAX_PATH);
+  if (long_name_len != 0 && long_name_len <= MAX_PATH) {
+    *temp_file =
+        FilePath(FilePath::StringPieceType(long_temp_name, long_name_len));
+  } else {
+    // GetLongPathName() failed, but we still have a temporary file.
+    *temp_file = std::move(temp_name);
+  }
+
+  return file;
+}
+
 bool CreateTemporaryDirInDir(const FilePath& base_dir,
                              const FilePath::StringType& prefix,
                              FilePath* new_dir) {
diff --git a/src/base/files/file_win.cc b/src/base/files/file_win.cc
index b68370b..c58e071 100644
--- a/src/base/files/file_win.cc
+++ b/src/base/files/file_win.cc
@@ -274,6 +274,11 @@
   if (flags & FLAG_OPEN)
     disposition = OPEN_EXISTING;
 
+  if (flags & FLAG_CREATE) {
+    DCHECK(!disposition);
+    disposition = CREATE_NEW;
+  }
+
   if (flags & FLAG_CREATE_ALWAYS) {
     DCHECK(!disposition);
     DCHECK(flags & FLAG_WRITE);