1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #ifdef UNSAFE_BUFFERS_BUILD
6 // TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
7 #pragma allow_unsafe_buffers
8 #endif
9
10 #include "base/files/file_util.h"
11
12 #include <algorithm>
13 #include <string_view>
14
15 #include "base/task/sequenced_task_runner.h"
16 #include "build/build_config.h"
17
18 #if BUILDFLAG(IS_WIN)
19 #include <io.h>
20 #endif
21 #include <stdio.h>
22
23 #include <fstream>
24 #include <limits>
25 #include <memory>
26 #include <utility>
27 #include <vector>
28
29 #include "base/bit_cast.h"
30 #include "base/check_op.h"
31 #include "base/containers/contains.h"
32 #include "base/containers/span.h"
33 #include "base/files/file_enumerator.h"
34 #include "base/files/file_path.h"
35 #include "base/functional/function_ref.h"
36 #include "base/notreached.h"
37 #include "base/posix/eintr_wrapper.h"
38 #include "base/ranges/algorithm.h"
39 #include "base/strings/string_util.h"
40 #include "base/strings/stringprintf.h"
41 #include "base/strings/utf_string_conversions.h"
42 #include "base/task/bind_post_task.h"
43 #include "base/threading/scoped_blocking_call.h"
44
45 #if BUILDFLAG(IS_WIN)
46 #include <windows.h>
47 #endif
48
49 namespace base {
50
51 namespace {
52
53 #if !BUILDFLAG(IS_WIN)
54
RunAndReply(OnceCallback<bool ()> action_callback,OnceCallback<void (bool)> reply_callback)55 void RunAndReply(OnceCallback<bool()> action_callback,
56 OnceCallback<void(bool)> reply_callback) {
57 bool result = std::move(action_callback).Run();
58 if (!reply_callback.is_null()) {
59 std::move(reply_callback).Run(result);
60 }
61 }
62
63 #endif // !BUILDFLAG(IS_WIN)
64
ReadStreamToSpanWithMaxSize(FILE * stream,size_t max_size,FunctionRef<span<uint8_t> (size_t)> resize_span)65 bool ReadStreamToSpanWithMaxSize(
66 FILE* stream,
67 size_t max_size,
68 FunctionRef<span<uint8_t>(size_t)> resize_span) {
69 if (!stream) {
70 return false;
71 }
72
73 // Seeking to the beginning is best-effort -- it is expected to fail for
74 // certain non-file stream (e.g., pipes).
75 HANDLE_EINTR(fseek(stream, 0, SEEK_SET));
76
77 // Many files have incorrect size (proc files etc). Hence, the file is read
78 // sequentially as opposed to a one-shot read, using file size as a hint for
79 // chunk size if available.
80 constexpr size_t kDefaultChunkSize = 1 << 16;
81 size_t chunk_size = kDefaultChunkSize - 1;
82 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
83 #if BUILDFLAG(IS_WIN)
84 BY_HANDLE_FILE_INFORMATION file_info = {};
85 if (::GetFileInformationByHandle(
86 reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(stream))),
87 &file_info)) {
88 LARGE_INTEGER size;
89 size.HighPart = static_cast<LONG>(file_info.nFileSizeHigh);
90 size.LowPart = file_info.nFileSizeLow;
91 if (size.QuadPart > 0) {
92 chunk_size = static_cast<size_t>(size.QuadPart);
93 }
94 }
95 #else // BUILDFLAG(IS_WIN)
96 // In cases where the reported file size is 0, use a smaller chunk size to
97 // minimize memory allocated and cost of string::resize() in case the read
98 // size is small (i.e. proc files). If the file is larger than this, the read
99 // loop will reset |chunk_size| to kDefaultChunkSize.
100 constexpr size_t kSmallChunkSize = 4096;
101 chunk_size = kSmallChunkSize - 1;
102 stat_wrapper_t file_info = {};
103 if (!File::Fstat(fileno(stream), &file_info) && file_info.st_size > 0) {
104 chunk_size = static_cast<size_t>(file_info.st_size);
105 }
106 #endif // BUILDFLAG(IS_WIN)
107
108 // We need to attempt to read at EOF for feof flag to be set so here we use
109 // |chunk_size| + 1.
110 chunk_size = std::min(chunk_size, max_size) + 1;
111 size_t bytes_read_this_pass;
112 size_t bytes_read_so_far = 0;
113 bool read_status = true;
114 span<uint8_t> bytes_span = resize_span(chunk_size);
115 DCHECK_EQ(bytes_span.size(), chunk_size);
116
117 while ((bytes_read_this_pass = fread(bytes_span.data() + bytes_read_so_far, 1,
118 chunk_size, stream)) > 0) {
119 if ((max_size - bytes_read_so_far) < bytes_read_this_pass) {
120 // Read more than max_size bytes, bail out.
121 bytes_read_so_far = max_size;
122 read_status = false;
123 break;
124 }
125 // In case EOF was not reached, iterate again but revert to the default
126 // chunk size.
127 if (bytes_read_so_far == 0) {
128 chunk_size = kDefaultChunkSize;
129 }
130
131 bytes_read_so_far += bytes_read_this_pass;
132 // Last fread syscall (after EOF) can be avoided via feof, which is just a
133 // flag check.
134 if (feof(stream)) {
135 break;
136 }
137 bytes_span = resize_span(bytes_read_so_far + chunk_size);
138 DCHECK_EQ(bytes_span.size(), bytes_read_so_far + chunk_size);
139 }
140 read_status = read_status && !ferror(stream);
141
142 // Trim the container down to the number of bytes that were actually read.
143 bytes_span = resize_span(bytes_read_so_far);
144 DCHECK_EQ(bytes_span.size(), bytes_read_so_far);
145
146 return read_status;
147 }
148
149 } // namespace
150
151 #if !BUILDFLAG(IS_WIN)
152
GetDeleteFileCallback(const FilePath & path,OnceCallback<void (bool)> reply_callback)153 OnceClosure GetDeleteFileCallback(const FilePath& path,
154 OnceCallback<void(bool)> reply_callback) {
155 return BindOnce(&RunAndReply, BindOnce(&DeleteFile, path),
156 reply_callback.is_null()
157 ? std::move(reply_callback)
158 : BindPostTask(SequencedTaskRunner::GetCurrentDefault(),
159 std::move(reply_callback)));
160 }
161
GetDeletePathRecursivelyCallback(const FilePath & path,OnceCallback<void (bool)> reply_callback)162 OnceClosure GetDeletePathRecursivelyCallback(
163 const FilePath& path,
164 OnceCallback<void(bool)> reply_callback) {
165 return BindOnce(&RunAndReply, BindOnce(&DeletePathRecursively, path),
166 reply_callback.is_null()
167 ? std::move(reply_callback)
168 : BindPostTask(SequencedTaskRunner::GetCurrentDefault(),
169 std::move(reply_callback)));
170 }
171
172 #endif // !BUILDFLAG(IS_WIN)
173
ComputeDirectorySize(const FilePath & root_path)174 int64_t ComputeDirectorySize(const FilePath& root_path) {
175 int64_t running_size = 0;
176 FileEnumerator file_iter(root_path, true, FileEnumerator::FILES);
177 while (!file_iter.Next().empty()) {
178 running_size += file_iter.GetInfo().GetSize();
179 }
180 return running_size;
181 }
182
Move(const FilePath & from_path,const FilePath & to_path)183 bool Move(const FilePath& from_path, const FilePath& to_path) {
184 if (from_path.ReferencesParent() || to_path.ReferencesParent()) {
185 return false;
186 }
187 return internal::MoveUnsafe(from_path, to_path);
188 }
189
CopyFileContents(File & infile,File & outfile)190 bool CopyFileContents(File& infile, File& outfile) {
191 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
192 bool retry_slow = false;
193 bool res =
194 internal::CopyFileContentsWithSendfile(infile, outfile, retry_slow);
195 if (res || !retry_slow) {
196 return res;
197 }
198 // Any failures which allow retrying using read/write will not have modified
199 // either file offset or size.
200 #endif
201
202 static constexpr size_t kBufferSize = 32768;
203 std::vector<char> buffer(kBufferSize);
204
205 for (;;) {
206 int bytes_read =
207 infile.ReadAtCurrentPos(buffer.data(), static_cast<int>(buffer.size()));
208 if (bytes_read < 0) {
209 return false;
210 }
211 if (bytes_read == 0) {
212 return true;
213 }
214 // Allow for partial writes
215 int bytes_written_per_read = 0;
216 do {
217 int bytes_written_partial = outfile.WriteAtCurrentPos(
218 &buffer[static_cast<size_t>(bytes_written_per_read)],
219 bytes_read - bytes_written_per_read);
220 if (bytes_written_partial < 0) {
221 return false;
222 }
223
224 bytes_written_per_read += bytes_written_partial;
225 } while (bytes_written_per_read < bytes_read);
226 }
227
228 NOTREACHED();
229 }
230
ContentsEqual(const FilePath & filename1,const FilePath & filename2)231 bool ContentsEqual(const FilePath& filename1, const FilePath& filename2) {
232 // We open the file in binary format even if they are text files because
233 // we are just comparing that bytes are exactly same in both files and not
234 // doing anything smart with text formatting.
235 #if BUILDFLAG(IS_WIN)
236 std::ifstream file1(filename1.value().c_str(),
237 std::ios::in | std::ios::binary);
238 std::ifstream file2(filename2.value().c_str(),
239 std::ios::in | std::ios::binary);
240 #elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
241 std::ifstream file1(filename1.value(), std::ios::in | std::ios::binary);
242 std::ifstream file2(filename2.value(), std::ios::in | std::ios::binary);
243 #endif // BUILDFLAG(IS_WIN)
244
245 // Even if both files aren't openable (and thus, in some sense, "equal"),
246 // any unusable file yields a result of "false".
247 if (!file1.is_open() || !file2.is_open()) {
248 return false;
249 }
250
251 const int BUFFER_SIZE = 2056;
252 char buffer1[BUFFER_SIZE], buffer2[BUFFER_SIZE];
253 do {
254 file1.read(buffer1, BUFFER_SIZE);
255 file2.read(buffer2, BUFFER_SIZE);
256
257 if ((file1.eof() != file2.eof()) || (file1.gcount() != file2.gcount()) ||
258 (memcmp(buffer1, buffer2, static_cast<size_t>(file1.gcount())))) {
259 file1.close();
260 file2.close();
261 return false;
262 }
263 } while (!file1.eof() || !file2.eof());
264
265 file1.close();
266 file2.close();
267 return true;
268 }
269
TextContentsEqual(const FilePath & filename1,const FilePath & filename2)270 bool TextContentsEqual(const FilePath& filename1, const FilePath& filename2) {
271 #if BUILDFLAG(IS_WIN)
272 std::ifstream file1(filename1.value().c_str(), std::ios::in);
273 std::ifstream file2(filename2.value().c_str(), std::ios::in);
274 #elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
275 std::ifstream file1(filename1.value(), std::ios::in);
276 std::ifstream file2(filename2.value(), std::ios::in);
277 #endif // BUILDFLAG(IS_WIN)
278
279 // Even if both files aren't openable (and thus, in some sense, "equal"),
280 // any unusable file yields a result of "false".
281 if (!file1.is_open() || !file2.is_open()) {
282 return false;
283 }
284
285 do {
286 std::string line1, line2;
287 getline(file1, line1);
288 getline(file2, line2);
289
290 // Check for mismatched EOF states, or any error state.
291 if ((file1.eof() != file2.eof()) || file1.bad() || file2.bad()) {
292 return false;
293 }
294
295 // Trim all '\r' and '\n' characters from the end of the line.
296 std::string::size_type end1 = line1.find_last_not_of("\r\n");
297 if (end1 == std::string::npos) {
298 line1.clear();
299 } else if (end1 + 1 < line1.length()) {
300 line1.erase(end1 + 1);
301 }
302
303 std::string::size_type end2 = line2.find_last_not_of("\r\n");
304 if (end2 == std::string::npos) {
305 line2.clear();
306 } else if (end2 + 1 < line2.length()) {
307 line2.erase(end2 + 1);
308 }
309
310 if (line1 != line2) {
311 return false;
312 }
313 } while (!file1.eof() || !file2.eof());
314
315 return true;
316 }
317
ReadStreamToString(FILE * stream,std::string * contents)318 bool ReadStreamToString(FILE* stream, std::string* contents) {
319 return ReadStreamToStringWithMaxSize(
320 stream, std::numeric_limits<size_t>::max(), contents);
321 }
322
ReadStreamToStringWithMaxSize(FILE * stream,size_t max_size,std::string * contents)323 bool ReadStreamToStringWithMaxSize(FILE* stream,
324 size_t max_size,
325 std::string* contents) {
326 if (contents) {
327 contents->clear();
328 }
329
330 std::string content_string;
331 bool read_successs = ReadStreamToSpanWithMaxSize(
332 stream, max_size, [&content_string](size_t size) {
333 content_string.resize(size);
334 return as_writable_byte_span(content_string);
335 });
336
337 if (contents) {
338 contents->swap(content_string);
339 }
340 return read_successs;
341 }
342
ReadFileToBytes(const FilePath & path)343 std::optional<std::vector<uint8_t>> ReadFileToBytes(const FilePath& path) {
344 if (path.ReferencesParent()) {
345 return std::nullopt;
346 }
347
348 ScopedFILE file_stream(OpenFile(path, "rb"));
349 if (!file_stream) {
350 return std::nullopt;
351 }
352
353 std::vector<uint8_t> bytes;
354 if (!ReadStreamToSpanWithMaxSize(file_stream.get(),
355 std::numeric_limits<size_t>::max(),
356 [&bytes](size_t size) {
357 bytes.resize(size);
358 return span(bytes);
359 })) {
360 return std::nullopt;
361 }
362 return bytes;
363 }
364
ReadFileToString(const FilePath & path,std::string * contents)365 bool ReadFileToString(const FilePath& path, std::string* contents) {
366 return ReadFileToStringWithMaxSize(path, contents,
367 std::numeric_limits<size_t>::max());
368 }
369
ReadFileToStringWithMaxSize(const FilePath & path,std::string * contents,size_t max_size)370 bool ReadFileToStringWithMaxSize(const FilePath& path,
371 std::string* contents,
372 size_t max_size) {
373 if (contents) {
374 contents->clear();
375 }
376 if (path.ReferencesParent()) {
377 return false;
378 }
379 ScopedFILE file_stream(OpenFile(path, "rb"));
380 if (!file_stream) {
381 return false;
382 }
383 return ReadStreamToStringWithMaxSize(file_stream.get(), max_size, contents);
384 }
385
IsDirectoryEmpty(const FilePath & dir_path)386 bool IsDirectoryEmpty(const FilePath& dir_path) {
387 FileEnumerator files(dir_path, false,
388 FileEnumerator::FILES | FileEnumerator::DIRECTORIES);
389 if (files.Next().empty()) {
390 return true;
391 }
392 return false;
393 }
394
CreateTemporaryFile(FilePath * path)395 bool CreateTemporaryFile(FilePath* path) {
396 FilePath temp_dir;
397 return GetTempDir(&temp_dir) && CreateTemporaryFileInDir(temp_dir, path);
398 }
399
CreateAndOpenTemporaryStream(FilePath * path)400 ScopedFILE CreateAndOpenTemporaryStream(FilePath* path) {
401 FilePath directory;
402 if (!GetTempDir(&directory)) {
403 return nullptr;
404 }
405
406 return CreateAndOpenTemporaryStreamInDir(directory, path);
407 }
408
CreateDirectory(const FilePath & full_path)409 bool CreateDirectory(const FilePath& full_path) {
410 return CreateDirectoryAndGetError(full_path, nullptr);
411 }
412
GetFileSize(const FilePath & file_path)413 std::optional<int64_t> GetFileSize(const FilePath& file_path) {
414 File::Info info;
415 if (!GetFileInfo(file_path, &info)) {
416 return std::nullopt;
417 }
418 return info.size;
419 }
420
GetFileSizeCallback(const FilePath & path)421 OnceCallback<std::optional<int64_t>()> GetFileSizeCallback(
422 const FilePath& path) {
423 return BindOnce([](const FilePath& path) { return GetFileSize(path); }, path);
424 }
425
TouchFile(const FilePath & path,const Time & last_accessed,const Time & last_modified)426 bool TouchFile(const FilePath& path,
427 const Time& last_accessed,
428 const Time& last_modified) {
429 uint32_t flags = File::FLAG_OPEN | File::FLAG_WRITE_ATTRIBUTES;
430
431 #if BUILDFLAG(IS_WIN)
432 // On Windows, FILE_FLAG_BACKUP_SEMANTICS is needed to open a directory.
433 if (DirectoryExists(path)) {
434 flags |= File::FLAG_WIN_BACKUP_SEMANTICS;
435 }
436 #elif BUILDFLAG(IS_FUCHSIA)
437 // On Fuchsia, we need O_RDONLY for directories, or O_WRONLY for files.
438 // TODO(crbug.com/40620916): Find a cleaner workaround for this.
439 flags |= (DirectoryExists(path) ? File::FLAG_READ : File::FLAG_WRITE);
440 #endif
441
442 File file(path, flags);
443 if (!file.IsValid()) {
444 return false;
445 }
446
447 return file.SetTimes(last_accessed, last_modified);
448 }
449
CloseFile(FILE * file)450 bool CloseFile(FILE* file) {
451 if (file == nullptr) {
452 return true;
453 }
454 return fclose(file) == 0;
455 }
456
TruncateFile(FILE * file)457 bool TruncateFile(FILE* file) {
458 if (file == nullptr) {
459 return false;
460 }
461 long current_offset = ftell(file);
462 if (current_offset == -1) {
463 return false;
464 }
465 #if BUILDFLAG(IS_WIN)
466 int fd = _fileno(file);
467 if (_chsize(fd, current_offset) != 0) {
468 return false;
469 }
470 #else
471 int fd = fileno(file);
472 if (ftruncate(fd, current_offset) != 0) {
473 return false;
474 }
475 #endif
476 return true;
477 }
478
ReadFile(const FilePath & filename,span<uint8_t> buffer)479 std::optional<uint64_t> ReadFile(const FilePath& filename,
480 span<uint8_t> buffer) {
481 return ReadFile(filename, base::as_writable_chars(buffer));
482 }
483
ReadFile(const FilePath & filename,char * data,int max_size)484 int ReadFile(const FilePath& filename, char* data, int max_size) {
485 if (max_size < 0) {
486 return -1;
487 }
488 std::optional<uint64_t> result =
489 ReadFile(filename, span(data, static_cast<uint32_t>(max_size)));
490 if (!result) {
491 return -1;
492 }
493 return checked_cast<int>(result.value());
494 }
495
WriteFile(const FilePath & filename,std::string_view data)496 bool WriteFile(const FilePath& filename, std::string_view data) {
497 return WriteFile(filename, as_byte_span(data));
498 }
499
GetUniquePath(const FilePath & path)500 FilePath GetUniquePath(const FilePath& path) {
501 return GetUniquePathWithSuffixFormat(path, " (%d)");
502 }
503
GetUniquePathWithSuffixFormat(const FilePath & path,base::cstring_view suffix_format)504 FilePath GetUniquePathWithSuffixFormat(const FilePath& path,
505 base::cstring_view suffix_format) {
506 DCHECK(!path.empty());
507 DCHECK_EQ(base::ranges::count(suffix_format, '%'), 1);
508 DCHECK(base::Contains(suffix_format, "%d"));
509
510 if (!PathExists(path)) {
511 return path;
512 }
513 for (int count = 1; count <= kMaxUniqueFiles; ++count) {
514 FilePath candidate_path = path.InsertBeforeExtensionASCII(
515 StringPrintfNonConstexpr(suffix_format.data(), count));
516 if (!PathExists(candidate_path)) {
517 return candidate_path;
518 }
519 }
520 return FilePath();
521 }
522
523 } // namespace base
524