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