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