[go: nahoru, domu]

[base] Add ReadFileToBytes() helper function

This adds a helper function to read a file as a vector of bytes,
rather than as a string.

Bug: 1330701
Change-Id: I6252cc2171167c75b9270488b1f5a86a03604f41
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3855357
Commit-Queue: Fabrice de Gans <fdegans@chromium.org>
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1041043}
diff --git a/base/files/file_util.cc b/base/files/file_util.cc
index b3d4ffc5..533c9d0 100644
--- a/base/files/file_util.cc
+++ b/base/files/file_util.cc
@@ -17,9 +17,12 @@
 #include <utility>
 #include <vector>
 
+#include "base/bit_cast.h"
 #include "base/check_op.h"
+#include "base/containers/span.h"
 #include "base/files/file_enumerator.h"
 #include "base/files/file_path.h"
+#include "base/functional/function_ref.h"
 #include "base/notreached.h"
 #include "base/posix/eintr_wrapper.h"
 #include "base/strings/string_piece.h"
@@ -29,7 +32,6 @@
 #include "base/task/bind_post_task.h"
 #include "base/threading/scoped_blocking_call.h"
 #include "base/threading/sequenced_task_runner_handle.h"
-#include "build/build_config.h"
 
 #if BUILDFLAG(IS_WIN)
 #include <windows.h>
@@ -37,10 +39,10 @@
 
 namespace base {
 
-#if !BUILDFLAG(IS_WIN)
-
 namespace {
 
+#if !BUILDFLAG(IS_WIN)
+
 void RunAndReply(OnceCallback<bool()> action_callback,
                  OnceCallback<void(bool)> reply_callback) {
   bool result = std::move(action_callback).Run();
@@ -48,8 +50,92 @@
     std::move(reply_callback).Run(result);
 }
 
+#endif  // !BUILDFLAG(IS_WIN)
+
+bool ReadStreamToSpanWithMaxSize(
+    FILE* stream,
+    size_t max_size,
+    FunctionRef<span<uint8_t>(size_t)> resize_span) {
+  if (!stream) {
+    return false;
+  }
+
+  // Seeking to the beginning is best-effort -- it is expected to fail for
+  // certain non-file stream (e.g., pipes).
+  HANDLE_EINTR(fseek(stream, 0, SEEK_SET));
+
+  // Many files have incorrect size (proc files etc). Hence, the file is read
+  // sequentially as opposed to a one-shot read, using file size as a hint for
+  // chunk size if available.
+  constexpr size_t kDefaultChunkSize = 1 << 16;
+  size_t chunk_size = kDefaultChunkSize - 1;
+  ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
+#if BUILDFLAG(IS_WIN)
+  BY_HANDLE_FILE_INFORMATION file_info = {};
+  if (::GetFileInformationByHandle(
+          reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(stream))),
+          &file_info)) {
+    LARGE_INTEGER size;
+    size.HighPart = static_cast<LONG>(file_info.nFileSizeHigh);
+    size.LowPart = file_info.nFileSizeLow;
+    if (size.QuadPart > 0)
+      chunk_size = static_cast<size_t>(size.QuadPart);
+  }
+#else   // BUILDFLAG(IS_WIN)
+  // In cases where the reported file size is 0, use a smaller chunk size to
+  // minimize memory allocated and cost of string::resize() in case the read
+  // size is small (i.e. proc files). If the file is larger than this, the read
+  // loop will reset |chunk_size| to kDefaultChunkSize.
+  constexpr size_t kSmallChunkSize = 4096;
+  chunk_size = kSmallChunkSize - 1;
+  stat_wrapper_t file_info = {};
+  if (!File::Fstat(fileno(stream), &file_info) && file_info.st_size > 0)
+    chunk_size = static_cast<size_t>(file_info.st_size);
+#endif  // BUILDFLAG(IS_WIN)
+
+  // We need to attempt to read at EOF for feof flag to be set so here we use
+  // |chunk_size| + 1.
+  chunk_size = std::min(chunk_size, max_size) + 1;
+  size_t bytes_read_this_pass;
+  size_t bytes_read_so_far = 0;
+  bool read_status = true;
+  span<uint8_t> bytes_span = resize_span(chunk_size);
+  DCHECK_EQ(bytes_span.size(), chunk_size);
+
+  while ((bytes_read_this_pass = fread(bytes_span.data() + bytes_read_so_far, 1,
+                                       chunk_size, stream)) > 0) {
+    if ((max_size - bytes_read_so_far) < bytes_read_this_pass) {
+      // Read more than max_size bytes, bail out.
+      bytes_read_so_far = max_size;
+      read_status = false;
+      break;
+    }
+    // In case EOF was not reached, iterate again but revert to the default
+    // chunk size.
+    if (bytes_read_so_far == 0)
+      chunk_size = kDefaultChunkSize;
+
+    bytes_read_so_far += bytes_read_this_pass;
+    // Last fread syscall (after EOF) can be avoided via feof, which is just a
+    // flag check.
+    if (feof(stream))
+      break;
+    bytes_span = resize_span(bytes_read_so_far + chunk_size);
+    DCHECK_EQ(bytes_span.size(), bytes_read_so_far + chunk_size);
+  }
+  read_status = read_status && !ferror(stream);
+
+  // Trim the container down to the number of bytes that were actually read.
+  bytes_span = resize_span(bytes_read_so_far);
+  DCHECK_EQ(bytes_span.size(), bytes_read_so_far);
+
+  return read_status;
+}
+
 }  // namespace
 
+#if !BUILDFLAG(IS_WIN)
+
 OnceClosure GetDeleteFileCallback(const FilePath& path,
                                   OnceCallback<void(bool)> reply_callback) {
   return BindOnce(&RunAndReply, BindOnce(&DeleteFile, path),
@@ -219,78 +305,43 @@
 bool ReadStreamToStringWithMaxSize(FILE* stream,
                                    size_t max_size,
                                    std::string* contents) {
-  if (contents)
-    contents->clear();
-  if (!stream)
-    return false;
-  // Seeking to the beginning is best-effort -- it is expected to fail for
-  // certain non-file stream (e.g., pipes).
-  HANDLE_EINTR(fseek(stream, 0, SEEK_SET));
-
-  // Many files have incorrect size (proc files etc). Hence, the file is read
-  // sequentially as opposed to a one-shot read, using file size as a hint for
-  // chunk size if available.
-  constexpr size_t kDefaultChunkSize = 1 << 16;
-  size_t chunk_size = kDefaultChunkSize - 1;
-  ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
-#if BUILDFLAG(IS_WIN)
-  BY_HANDLE_FILE_INFORMATION file_info = {};
-  if (::GetFileInformationByHandle(
-          reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(stream))),
-          &file_info)) {
-    LARGE_INTEGER size;
-    size.HighPart = static_cast<LONG>(file_info.nFileSizeHigh);
-    size.LowPart = file_info.nFileSizeLow;
-    if (size.QuadPart > 0)
-      chunk_size = static_cast<size_t>(size.QuadPart);
-  }
-#else   // BUILDFLAG(IS_WIN)
-  // In cases where the reported file size is 0, use a smaller chunk size to
-  // minimize memory allocated and cost of string::resize() in case the read
-  // size is small (i.e. proc files). If the file is larger than this, the read
-  // loop will reset |chunk_size| to kDefaultChunkSize.
-  constexpr size_t kSmallChunkSize = 4096;
-  chunk_size = kSmallChunkSize - 1;
-  stat_wrapper_t file_info = {};
-  if (!File::Fstat(fileno(stream), &file_info) && file_info.st_size > 0)
-    chunk_size = static_cast<size_t>(file_info.st_size);
-#endif  // BUILDFLAG(IS_WIN)
-  // We need to attempt to read at EOF for feof flag to be set so here we
-  // use |chunk_size| + 1.
-  chunk_size = std::min(chunk_size, max_size) + 1;
-  size_t bytes_read_this_pass;
-  size_t bytes_read_so_far = 0;
-  bool read_status = true;
-  std::string local_contents;
-  local_contents.resize(chunk_size);
-
-  while ((bytes_read_this_pass = fread(&local_contents[bytes_read_so_far], 1,
-                                       chunk_size, stream)) > 0) {
-    if ((max_size - bytes_read_so_far) < bytes_read_this_pass) {
-      // Read more than max_size bytes, bail out.
-      bytes_read_so_far = max_size;
-      read_status = false;
-      break;
-    }
-    // In case EOF was not reached, iterate again but revert to the default
-    // chunk size.
-    if (bytes_read_so_far == 0)
-      chunk_size = kDefaultChunkSize;
-
-    bytes_read_so_far += bytes_read_this_pass;
-    // Last fread syscall (after EOF) can be avoided via feof, which is just a
-    // flag check.
-    if (feof(stream))
-      break;
-    local_contents.resize(bytes_read_so_far + chunk_size);
-  }
-  read_status = read_status && !ferror(stream);
   if (contents) {
-    contents->swap(local_contents);
-    contents->resize(bytes_read_so_far);
+    contents->clear();
   }
 
-  return read_status;
+  std::string content_string;
+  bool read_successs = ReadStreamToSpanWithMaxSize(
+      stream, max_size, [&content_string](size_t size) {
+        content_string.resize(size);
+        return as_writable_bytes(make_span(content_string));
+      });
+
+  if (contents) {
+    contents->swap(content_string);
+  }
+  return read_successs;
+}
+
+absl::optional<std::vector<uint8_t>> ReadFileToBytes(const FilePath& path) {
+  if (path.ReferencesParent()) {
+    return absl::nullopt;
+  }
+
+  ScopedFILE file_stream(OpenFile(path, "rb"));
+  if (!file_stream) {
+    return absl::nullopt;
+  }
+
+  std::vector<uint8_t> bytes;
+  if (!ReadStreamToSpanWithMaxSize(file_stream.get(),
+                                   std::numeric_limits<size_t>::max(),
+                                   [&bytes](size_t size) {
+                                     bytes.resize(size);
+                                     return make_span(bytes);
+                                   })) {
+    return absl::nullopt;
+  }
+  return bytes;
 }
 
 bool ReadFileToString(const FilePath& path, std::string* contents) {