Shaka Packager SDK
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 
30 ABSL_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.");
35 ABSL_FLAG(uint64_t,
36  io_block_size,
37  1ULL << 16,
38  "Size of the block size used for threaded I/O, in bytes.");
39 
40 namespace shaka {
41 
42 const char* kCallbackFilePrefix = "callback://";
43 const char* kLocalFilePrefix = "file://";
44 const char* kMemoryFilePrefix = "memory://";
45 const char* kUdpFilePrefix = "udp://";
46 const char* kHttpFilePrefix = "http://";
47 const char* kHttpsFilePrefix = "https://";
48 
49 
50 namespace {
51 
52 typedef File* (*FileFactoryFunction)(const char* file_name, const char* mode);
53 typedef bool (*FileDeleteFunction)(const char* file_name);
54 typedef bool (*FileAtomicWriteFunction)(const char* file_name,
55  const std::string& contents);
56 
57 struct FileTypeInfo {
58  const char* type;
59  const FileFactoryFunction factory_function;
60  const FileDeleteFunction delete_function;
61  const FileAtomicWriteFunction atomic_write_function;
62 };
63 
64 File* CreateCallbackFile(const char* file_name, const char* mode) {
65  return new CallbackFile(file_name, mode);
66 }
67 
68 File* CreateLocalFile(const char* file_name, const char* mode) {
69  return new LocalFile(file_name, mode);
70 }
71 
72 bool DeleteLocalFile(const char* file_name) {
73  return LocalFile::Delete(file_name);
74 }
75 
76 bool 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 
98 File* 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 
106 File* 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 
114 bool DeleteHttpsFile(const char* file_name) {
115  return HttpFile::Delete(std::string("https://") + file_name);
116 }
117 
118 File* 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 
126 bool DeleteHttpFile(const char* file_name) {
127  return HttpFile::Delete(std::string("http://") + file_name);
128 }
129 
130 File* CreateMemoryFile(const char* file_name, const char* mode) {
131  return new MemoryFile(file_name, mode);
132 }
133 
134 bool DeleteMemoryFile(const char* file_name) {
135  MemoryFile::Delete(file_name);
136  return true;
137 }
138 
139 static 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 
153 std::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 
158 const 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 
174 File* 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 
205 File* 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 
213 File* 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 
224 File* 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 
235 bool 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 
254 int64_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 
263 bool 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 
281 bool 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 
310 bool 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 
333 bool 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 
369 int64_t File::Copy(File* source, File* destination) {
370  return Copy(source, destination, kWholeFile);
371 }
372 
373 int64_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 
410 bool 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 
423 std::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 
433 bool 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)
Definition: local_file.cc:138
static void Delete(const std::string &file_name)
Definition: memory_file.cc:194
All the methods that are virtual are virtual for mocking.
Definition: crypto_flags.cc:66
bool TempFilePath(const std::string &temp_dir, std::string *temp_file_path)
Definition: file_util.cc:43