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