Shaka Packager SDK
Loading...
Searching...
No Matches
file.cc
1// Copyright 2014 Google LLC. All rights reserved.
2//
3// Use of this source code is governed by a BSD-style
4// license that can be found in the LICENSE file or at
5// https://developers.google.com/open-source/licenses/bsd
6
7#include <packager/file.h>
8
9#include <algorithm>
10#include <cinttypes>
11#include <cstddef>
12#include <cstdint>
13#include <cstring>
14#include <filesystem>
15#include <limits>
16#include <memory>
17#include <string>
18#include <string_view>
19#include <system_error>
20#include <utility>
21
22#include <absl/flags/flag.h>
23#include <absl/log/check.h>
24#include <absl/log/log.h>
25#include <absl/strings/numbers.h>
26#include <absl/strings/str_format.h>
27
28#include <packager/buffer_callback_params.h>
29#include <packager/file/callback_file.h>
30#include <packager/file/file_closer.h>
31#include <packager/file/file_util.h>
32#include <packager/file/http_file.h>
33#include <packager/file/local_file.h>
34#include <packager/file/memory_file.h>
35#include <packager/file/threaded_io_file.h>
36#include <packager/file/udp_file.h>
37#include <packager/macros/logging.h>
38
39ABSL_FLAG(uint64_t,
40 io_cache_size,
41 32ULL << 20,
42 "Size of the threaded I/O cache, in bytes. Specify 0 to disable "
43 "threaded I/O.");
44ABSL_FLAG(uint64_t,
45 io_block_size,
46 1ULL << 16,
47 "Size of the block size used for threaded I/O, in bytes.");
48
49namespace shaka {
50
51const char* kCallbackFilePrefix = "callback://";
52const char* kLocalFilePrefix = "file://";
53const char* kMemoryFilePrefix = "memory://";
54const char* kUdpFilePrefix = "udp://";
55const char* kHttpFilePrefix = "http://";
56const char* kHttpsFilePrefix = "https://";
57
58namespace {
59
60typedef File* (*FileFactoryFunction)(const char* file_name, const char* mode);
61typedef bool (*FileDeleteFunction)(const char* file_name);
62typedef bool (*FileAtomicWriteFunction)(const char* file_name,
63 const std::string& contents);
64
65struct FileTypeInfo {
66 const char* type;
67 const FileFactoryFunction factory_function;
68 const FileDeleteFunction delete_function;
69 const FileAtomicWriteFunction atomic_write_function;
70};
71
72File* CreateCallbackFile(const char* file_name, const char* mode) {
73 return new CallbackFile(file_name, mode);
74}
75
76File* CreateLocalFile(const char* file_name, const char* mode) {
77 return new LocalFile(file_name, mode);
78}
79
80bool DeleteLocalFile(const char* file_name) {
81 return LocalFile::Delete(file_name);
82}
83
84bool WriteLocalFileAtomically(const char* file_name,
85 const std::string& contents) {
86 std::error_code ec;
87 // To atomically move the temporary file to the target location after write,
88 // they must be on the same device. For relative paths without a directory
89 // part, the parent path is empty, which fails TempFilePath's empty directory
90 // path check, thus falling back to /tmp that is on a potentially different
91 // device. We prevent this by resolving the absolute file path.
92 const auto file_path =
93 std::filesystem::absolute(std::filesystem::u8path(file_name), ec);
94 if (ec) {
95 LOG(ERROR) << "Failed to resolve file path '" << file_name
96 << "', error: " << ec;
97 return false;
98 }
99 const auto dir_path = file_path.parent_path();
100
101 std::string temp_file_name;
102 if (!TempFilePath(dir_path.string(), &temp_file_name))
103 return false;
104 if (!File::WriteStringToFile(temp_file_name.c_str(), contents))
105 return false;
106
107 auto temp_file_path = std::filesystem::u8path(temp_file_name);
108 std::filesystem::rename(temp_file_path, file_name, ec);
109 if (ec) {
110 LOG(ERROR) << "Failed to replace file '" << file_name << "' with '"
111 << temp_file_name << "', error: " << ec;
112 return false;
113 }
114 return true;
115}
116
117File* CreateUdpFile(const char* file_name, const char* mode) {
118 if (strcmp(mode, "r")) {
119 NOTIMPLEMENTED() << "UdpFile only supports read (receive) mode.";
120 return NULL;
121 }
122 return new UdpFile(file_name);
123}
124
125File* CreateHttpsFile(const char* file_name, const char* mode) {
126 HttpMethod method = HttpMethod::kGet;
127 if (strcmp(mode, "r") != 0) {
128 method = HttpMethod::kPut;
129 }
130 return new HttpFile(method, std::string("https://") + file_name);
131}
132
133bool DeleteHttpsFile(const char* file_name) {
134 return HttpFile::Delete(std::string("https://") + file_name);
135}
136
137File* CreateHttpFile(const char* file_name, const char* mode) {
138 HttpMethod method = HttpMethod::kGet;
139 if (strcmp(mode, "r") != 0) {
140 method = HttpMethod::kPut;
141 }
142 return new HttpFile(method, std::string("http://") + file_name);
143}
144
145bool DeleteHttpFile(const char* file_name) {
146 return HttpFile::Delete(std::string("http://") + file_name);
147}
148
149File* CreateMemoryFile(const char* file_name, const char* mode) {
150 return new MemoryFile(file_name, mode);
151}
152
153bool DeleteMemoryFile(const char* file_name) {
154 return MemoryFile::Delete(file_name);
155}
156
157static const FileTypeInfo kFileTypeInfo[] = {
158 {
159 kLocalFilePrefix,
160 &CreateLocalFile,
161 &DeleteLocalFile,
162 &WriteLocalFileAtomically,
163 },
164 {kUdpFilePrefix, &CreateUdpFile, nullptr, nullptr},
165 {kMemoryFilePrefix, &CreateMemoryFile, &DeleteMemoryFile, nullptr},
166 {kCallbackFilePrefix, &CreateCallbackFile, nullptr, nullptr},
167 {kHttpFilePrefix, &CreateHttpFile, &DeleteHttpFile, nullptr},
168 {kHttpsFilePrefix, &CreateHttpsFile, &DeleteHttpsFile, nullptr},
169};
170
171std::string_view GetFileTypePrefix(std::string_view file_name) {
172 size_t pos = file_name.find("://");
173 return (pos == std::string::npos) ? "" : file_name.substr(0, pos + 3);
174}
175
176const FileTypeInfo* GetFileTypeInfo(std::string_view file_name,
177 std::string_view* real_file_name) {
178 std::string_view file_type_prefix = GetFileTypePrefix(file_name);
179 for (const FileTypeInfo& file_type : kFileTypeInfo) {
180 if (file_type_prefix == file_type.type) {
181 *real_file_name = file_name.substr(file_type_prefix.size());
182 return &file_type;
183 }
184 }
185 // Otherwise we default to the first file type, which is LocalFile.
186 *real_file_name = file_name;
187 return &kFileTypeInfo[0];
188}
189
190} // namespace
191
192File* File::Create(const char* file_name, const char* mode) {
193 std::unique_ptr<File, FileCloser> internal_file(
194 CreateInternalFile(file_name, mode));
195
196 std::string_view file_type_prefix = GetFileTypePrefix(file_name);
197 if (file_type_prefix == kMemoryFilePrefix ||
198 file_type_prefix == kCallbackFilePrefix) {
199 // Disable caching for memory and callback files.
200 return internal_file.release();
201 }
202
203 if (absl::GetFlag(FLAGS_io_cache_size)) {
204 // Enable threaded I/O for "r", "w", and "a" modes only.
205 if (!strcmp(mode, "r")) {
206 return new ThreadedIoFile(std::move(internal_file),
207 ThreadedIoFile::kInputMode,
208 absl::GetFlag(FLAGS_io_cache_size),
209 absl::GetFlag(FLAGS_io_block_size));
210 } else if (!strcmp(mode, "w") || !strcmp(mode, "a")) {
211 return new ThreadedIoFile(std::move(internal_file),
212 ThreadedIoFile::kOutputMode,
213 absl::GetFlag(FLAGS_io_cache_size),
214 absl::GetFlag(FLAGS_io_block_size));
215 }
216 }
217
218 // Threaded I/O is disabled.
219 DLOG(WARNING) << "Threaded I/O is disabled. Performance may be decreased.";
220 return internal_file.release();
221}
222
223File* File::CreateInternalFile(const char* file_name, const char* mode) {
224 std::string_view real_file_name;
225 const FileTypeInfo* file_type = GetFileTypeInfo(file_name, &real_file_name);
226 DCHECK(file_type);
227 // Calls constructor for the derived File class.
228 return file_type->factory_function(real_file_name.data(), mode);
229}
230
231File* File::Open(const char* file_name, const char* mode) {
232 File* file = File::Create(file_name, mode);
233 if (!file)
234 return NULL;
235 if (!file->Open()) {
236 delete file;
237 return NULL;
238 }
239 return file;
240}
241
242File* File::OpenWithNoBuffering(const char* file_name, const char* mode) {
243 File* file = File::CreateInternalFile(file_name, mode);
244 if (!file)
245 return NULL;
246 if (!file->Open()) {
247 delete file;
248 return NULL;
249 }
250 return file;
251}
252
253bool File::Delete(const char* file_name) {
254 static bool logged = false;
255 std::string_view real_file_name;
256 const FileTypeInfo* file_type = GetFileTypeInfo(file_name, &real_file_name);
257 DCHECK(file_type);
258 if (file_type->delete_function) {
259 return file_type->delete_function(real_file_name.data());
260 } else {
261 if (!logged) {
262 logged = true;
263 LOG(WARNING) << "File::Delete: file type for " << file_name << " ('"
264 << file_type->type << "') "
265 << "has no 'delete' function.";
266 }
267 return true;
268 }
269}
270
271int64_t File::GetFileSize(const char* file_name) {
272 File* file = File::Open(file_name, "r");
273 if (!file)
274 return -1;
275 int64_t res = file->Size();
276 file->Close();
277 return res;
278}
279
280bool File::ReadFileToString(const char* file_name, std::string* contents) {
281 DCHECK(contents);
282
283 File* file = File::Open(file_name, "r");
284 if (!file)
285 return false;
286
287 const size_t kBufferSize = 0x40000; // 256KB.
288 std::unique_ptr<char[]> buf(new char[kBufferSize]);
289
290 int64_t len;
291 while ((len = file->Read(buf.get(), kBufferSize)) > 0)
292 contents->append(buf.get(), len);
293
294 file->Close();
295 return len == 0;
296}
297
298bool File::WriteStringToFile(const char* file_name,
299 const std::string& contents) {
300 VLOG(2) << "File::WriteStringToFile: " << file_name;
301 std::unique_ptr<File, FileCloser> file(File::Open(file_name, "w"));
302 if (!file) {
303 LOG(ERROR) << "Failed to open file " << file_name;
304 return false;
305 }
306 int64_t bytes_written = file->Write(contents.data(), contents.size());
307 if (bytes_written < 0) {
308 LOG(ERROR) << "Failed to write to file '" << file_name << "' ("
309 << bytes_written << ").";
310 return false;
311 }
312 if (static_cast<size_t>(bytes_written) != contents.size()) {
313 LOG(ERROR) << "Failed to write the whole file to " << file_name
314 << ". Wrote " << bytes_written << " but expecting "
315 << contents.size() << " bytes.";
316 return false;
317 }
318 if (!file.release()->Close()) {
319 LOG(ERROR)
320 << "Failed to close file '" << file_name
321 << "', possibly file permission issue or running out of disk space.";
322 return false;
323 }
324 return true;
325}
326
327bool File::WriteFileAtomically(const char* file_name,
328 const std::string& contents) {
329 VLOG(2) << "File::WriteFileAtomically: " << file_name;
330 std::string_view real_file_name;
331 const FileTypeInfo* file_type = GetFileTypeInfo(file_name, &real_file_name);
332 DCHECK(file_type);
333 if (file_type->atomic_write_function)
334 return file_type->atomic_write_function(real_file_name.data(), contents);
335
336 // Provide a default implementation which may not be atomic unfortunately.
337
338 // Skip the warning message for memory files, which is meant for testing
339 // anyway..
340 // Also check for http files, as they can't do atomic writes.
341 if (strncmp(file_name, kMemoryFilePrefix, strlen(kMemoryFilePrefix)) != 0 &&
342 strncmp(file_name, kHttpFilePrefix, strlen(kHttpFilePrefix)) != 0 &&
343 strncmp(file_name, kHttpsFilePrefix, strlen(kHttpsFilePrefix)) != 0) {
344 LOG(WARNING) << "Writing to " << file_name
345 << " is not guaranteed to be atomic.";
346 }
347 return WriteStringToFile(file_name, contents);
348}
349
350bool File::Copy(const char* from_file_name, const char* to_file_name) {
351 std::string content;
352 VLOG(2) << "File::Copy from " << from_file_name << " to " << to_file_name;
353 if (!ReadFileToString(from_file_name, &content)) {
354 LOG(ERROR) << "Failed to open file " << from_file_name;
355 return false;
356 }
357
358 std::unique_ptr<File, FileCloser> output_file(File::Open(to_file_name, "w"));
359 if (!output_file) {
360 LOG(ERROR) << "Failed to write to " << to_file_name;
361 return false;
362 }
363
364 uint64_t bytes_left = content.size();
365 uint64_t total_bytes_written = 0;
366 const char* content_cstr = content.c_str();
367 while (bytes_left > total_bytes_written) {
368 const int64_t bytes_written =
369 output_file->Write(content_cstr + total_bytes_written, bytes_left);
370 if (bytes_written < 0) {
371 LOG(ERROR) << "Failure while writing to " << to_file_name;
372 return false;
373 }
374
375 total_bytes_written += bytes_written;
376 }
377 if (!output_file.release()->Close()) {
378 LOG(ERROR)
379 << "Failed to close file '" << to_file_name
380 << "', possibly file permission issue or running out of disk space.";
381 return false;
382 }
383 return true;
384}
385
386int64_t File::Copy(File* source, File* destination) {
387 return Copy(source, destination, kWholeFile);
388}
389
390int64_t File::Copy(File* source, File* destination, int64_t max_copy) {
391 DCHECK(source);
392 DCHECK(destination);
393 if (max_copy < 0)
394 max_copy = std::numeric_limits<int64_t>::max();
395
396 VLOG(2) << "File::Copy from " << source->file_name() << " to "
397 << destination->file_name();
398
399 const int64_t kBufferSize = 0x40000; // 256KB.
400 std::unique_ptr<uint8_t[]> buffer(new uint8_t[kBufferSize]);
401 int64_t bytes_copied = 0;
402 while (bytes_copied < max_copy) {
403 const int64_t size = std::min(kBufferSize, max_copy - bytes_copied);
404 const int64_t bytes_read = source->Read(buffer.get(), size);
405 if (bytes_read < 0)
406 return bytes_read;
407 if (bytes_read == 0)
408 break;
409
410 int64_t total_bytes_written = 0;
411 while (total_bytes_written < bytes_read) {
412 const int64_t bytes_written = destination->Write(
413 buffer.get() + total_bytes_written, bytes_read - total_bytes_written);
414 if (bytes_written < 0)
415 return bytes_written;
416
417 total_bytes_written += bytes_written;
418 }
419
420 DCHECK_EQ(total_bytes_written, bytes_read);
421 bytes_copied += bytes_read;
422 }
423
424 return bytes_copied;
425}
426
427bool File::IsLocalRegularFile(const char* file_name) {
428 std::string_view real_file_name;
429 const FileTypeInfo* file_type = GetFileTypeInfo(file_name, &real_file_name);
430 DCHECK(file_type);
431
432 if (file_type->type != kLocalFilePrefix)
433 return false;
434
435 std::error_code ec;
436 auto real_file_path = std::filesystem::u8path(real_file_name);
437 return std::filesystem::is_regular_file(real_file_path, ec);
438}
439
440std::string File::MakeCallbackFileName(
441 const BufferCallbackParams& callback_params,
442 const std::string& name) {
443 if (name.empty())
444 return "";
445 return absl::StrFormat("%s%" PRIdPTR "/%s", kCallbackFilePrefix,
446 reinterpret_cast<intptr_t>(&callback_params),
447 name.c_str());
448}
449
450bool File::ParseCallbackFileName(const std::string& callback_file_name,
451 const BufferCallbackParams** callback_params,
452 std::string* name) {
453 size_t pos = callback_file_name.find("/");
454 int64_t callback_address = 0;
455 if (pos == std::string::npos ||
456 !absl::SimpleAtoi(callback_file_name.substr(0, pos), &callback_address)) {
457 LOG(ERROR) << "Expecting CallbackFile with name like "
458 "'<callback address>/<entity name>', but seeing "
459 << callback_file_name;
460 return false;
461 }
462 *callback_params = reinterpret_cast<BufferCallbackParams*>(callback_address);
463 *name = callback_file_name.substr(pos + 1);
464 return true;
465}
466
467} // namespace shaka
static bool Delete(const char *file_name)
static bool Delete(const std::string &file_name)
All the methods that are virtual are virtual for mocking.
bool TempFilePath(const std::string &temp_dir, std::string *temp_file_path)
Definition file_util.cc:48