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