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