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