1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "nacl_io/html5fs/html5_fs.h"
6
7#include <errno.h>
8#include <fcntl.h>
9#include <stdlib.h>
10#include <string.h>
11
12#include <algorithm>
13
14#include <ppapi/c/pp_completion_callback.h>
15#include <ppapi/c/pp_errors.h>
16
17#include "nacl_io/html5fs/html5_fs_node.h"
18#include "sdk_util/auto_lock.h"
19
20namespace nacl_io {
21
22namespace {
23
24#if defined(WIN32)
25int64_t strtoull(const char* nptr, char** endptr, int base) {
26  return _strtoui64(nptr, endptr, base);
27}
28#endif
29
30}  // namespace
31
32// Continuing DJB2a hash
33ino_t Html5Fs::HashPathSegment(ino_t hash, const char *str, size_t len) {
34  // First add the path seperator
35  hash = (hash * static_cast<ino_t>(33)) ^ '/';
36  while (len--) {
37    hash = (hash * static_cast<ino_t>(33)) ^ *str++;
38  }
39  return hash;
40}
41
42ino_t Html5Fs::HashPath(const Path& path) {
43  // Prime the DJB2a hash
44  ino_t hash = 5381;
45
46  // Apply a running DJB2a to each part of the path
47  for (size_t segment = 0; segment < path.Size(); segment++) {
48    const char *ptr = path.Part(segment).c_str();
49    size_t len = path.Part(segment).length();
50    hash = HashPathSegment(hash, ptr, len);
51  }
52  return hash;
53}
54
55
56// For HTML5, the INO should be the one used by the system, however PPAPI
57// does not provide access to the real INO.  Instead, since HTML5 does not
58// suport links, we assume that files are unique based on path to the base
59// of the mount.
60void Html5Fs::OnNodeCreated(Node* node) {
61  node->stat_.st_dev = dev_;
62}
63
64void Html5Fs::OnNodeDestroyed(Node* node) {}
65
66
67Error Html5Fs::OpenWithMode(const Path& path, int open_flags, mode_t mode,
68                            ScopedNode* out_node) {
69  out_node->reset(NULL);
70  Error error = BlockUntilFilesystemOpen();
71  if (error)
72    return error;
73
74  PP_Resource fileref = file_ref_iface_->Create(
75      filesystem_resource_, GetFullPath(path).Join().c_str());
76  if (!fileref)
77    return ENOENT;
78
79  ScopedNode node(new Html5FsNode(this, fileref));
80  error = node->Init(open_flags);
81
82  // Set the INO based on the path
83  node->stat_.st_ino = HashPath(path);
84
85  if (error)
86    return error;
87
88  *out_node = node;
89  return 0;
90}
91
92Path Html5Fs::GetFullPath(const Path& path) {
93  Path full_path(path);
94  full_path.Prepend(prefix_);
95  return full_path;
96}
97
98Error Html5Fs::Unlink(const Path& path) {
99  return RemoveInternal(path, REMOVE_FILE);
100}
101
102Error Html5Fs::Mkdir(const Path& path, int permissions) {
103  Error error = BlockUntilFilesystemOpen();
104  if (error)
105    return error;
106
107  // FileRef returns PP_ERROR_NOACCESS which is translated to EACCES if you
108  // try to create the root directory. EEXIST is a better errno here.
109  if (path.IsRoot())
110    return EEXIST;
111
112  ScopedResource fileref_resource(
113      ppapi(),
114      file_ref_iface_->Create(filesystem_resource_,
115                              GetFullPath(path).Join().c_str()));
116  if (!fileref_resource.pp_resource())
117    return ENOENT;
118
119  int32_t result = file_ref_iface_->MakeDirectory(
120      fileref_resource.pp_resource(), PP_FALSE, PP_BlockUntilComplete());
121  if (result != PP_OK)
122    return PPErrorToErrno(result);
123
124  return 0;
125}
126
127Error Html5Fs::Rmdir(const Path& path) {
128  return RemoveInternal(path, REMOVE_DIR);
129}
130
131Error Html5Fs::Remove(const Path& path) {
132  return RemoveInternal(path, REMOVE_ALL);
133}
134
135Error Html5Fs::RemoveInternal(const Path& path, int remove_type) {
136  Error error = BlockUntilFilesystemOpen();
137  if (error)
138    return error;
139
140  ScopedResource fileref_resource(
141      ppapi(),
142      file_ref_iface_->Create(filesystem_resource_,
143                              GetFullPath(path).Join().c_str()));
144  if (!fileref_resource.pp_resource())
145    return ENOENT;
146
147  // Check file type
148  if (remove_type != REMOVE_ALL) {
149    PP_FileInfo file_info;
150    int32_t query_result = file_ref_iface_->Query(
151        fileref_resource.pp_resource(), &file_info, PP_BlockUntilComplete());
152    if (query_result != PP_OK) {
153      LOG_ERROR("Error querying file type");
154      return EINVAL;
155    }
156    switch (file_info.type) {
157      case PP_FILETYPE_DIRECTORY:
158        if (!(remove_type & REMOVE_DIR))
159          return EISDIR;
160        break;
161      case PP_FILETYPE_REGULAR:
162        if (!(remove_type & REMOVE_FILE))
163          return ENOTDIR;
164        break;
165      default:
166        LOG_ERROR("Invalid file type: %d", file_info.type);
167        return EINVAL;
168    }
169  }
170
171  int32_t result = file_ref_iface_->Delete(fileref_resource.pp_resource(),
172                                           PP_BlockUntilComplete());
173  if (result != PP_OK)
174    return PPErrorToErrno(result);
175
176  return 0;
177}
178
179Error Html5Fs::Rename(const Path& path, const Path& newpath) {
180  Error error = BlockUntilFilesystemOpen();
181  if (error)
182    return error;
183
184  std::string oldpath_full = GetFullPath(path).Join();
185  ScopedResource fileref_resource(
186      ppapi(),
187      file_ref_iface_->Create(filesystem_resource_, oldpath_full.c_str()));
188  if (!fileref_resource.pp_resource())
189    return ENOENT;
190
191  std::string newpath_full = GetFullPath(newpath).Join();
192  ScopedResource new_fileref_resource(
193      ppapi(),
194      file_ref_iface_->Create(filesystem_resource_, newpath_full.c_str()));
195  if (!new_fileref_resource.pp_resource())
196    return ENOENT;
197
198  int32_t result = file_ref_iface_->Rename(fileref_resource.pp_resource(),
199                                           new_fileref_resource.pp_resource(),
200                                           PP_BlockUntilComplete());
201  if (result != PP_OK)
202    return PPErrorToErrno(result);
203
204  return 0;
205}
206
207Html5Fs::Html5Fs()
208    : filesystem_iface_(NULL),
209      file_ref_iface_(NULL),
210      file_io_iface_(NULL),
211      filesystem_resource_(0),
212      filesystem_open_has_result_(false),
213      filesystem_open_error_(0) {
214}
215
216Error Html5Fs::Init(const FsInitArgs& args) {
217  pthread_cond_init(&filesystem_open_cond_, NULL);
218
219  Error error = Filesystem::Init(args);
220  if (error)
221    return error;
222
223  if (!args.ppapi) {
224    LOG_ERROR("ppapi is NULL.");
225    return ENOSYS;
226  }
227
228  core_iface_ = ppapi()->GetCoreInterface();
229  filesystem_iface_ = ppapi()->GetFileSystemInterface();
230  file_io_iface_ = ppapi()->GetFileIoInterface();
231  file_ref_iface_ = ppapi()->GetFileRefInterface();
232
233  if (!(core_iface_ && filesystem_iface_ && file_io_iface_ &&
234        file_ref_iface_)) {
235    LOG_ERROR("Got NULL interface(s): %s%s%s%s",
236              core_iface_ ? "" : "Core ",
237              filesystem_iface_ ? "" : "FileSystem ",
238              file_ref_iface_ ? "" : "FileRef",
239              file_io_iface_ ? "" : "FileIo ");
240    return ENOSYS;
241  }
242
243  // Parse filesystem args.
244  PP_FileSystemType filesystem_type = PP_FILESYSTEMTYPE_LOCALPERSISTENT;
245  int64_t expected_size = 0;
246  for (StringMap_t::const_iterator iter = args.string_map.begin();
247       iter != args.string_map.end();
248       ++iter) {
249    if (iter->first == "type") {
250      if (iter->second == "PERSISTENT") {
251        filesystem_type = PP_FILESYSTEMTYPE_LOCALPERSISTENT;
252      } else if (iter->second == "TEMPORARY") {
253        filesystem_type = PP_FILESYSTEMTYPE_LOCALTEMPORARY;
254      } else if (iter->second == "") {
255        filesystem_type = PP_FILESYSTEMTYPE_LOCALPERSISTENT;
256      } else {
257        LOG_ERROR("Unknown filesystem type: '%s'", iter->second.c_str());
258        return EINVAL;
259      }
260    } else if (iter->first == "expected_size") {
261      expected_size = strtoull(iter->second.c_str(), NULL, 10);
262    } else if (iter->first == "filesystem_resource") {
263      PP_Resource resource = strtoull(iter->second.c_str(), NULL, 10);
264      if (!filesystem_iface_->IsFileSystem(resource))
265        return EINVAL;
266
267      filesystem_resource_ = resource;
268      ppapi_->AddRefResource(filesystem_resource_);
269    } else if (iter->first == "SOURCE") {
270      prefix_ = iter->second;
271    } else {
272      LOG_ERROR("Invalid mount param: %s", iter->first.c_str());
273      return EINVAL;
274    }
275  }
276
277  if (filesystem_resource_ != 0) {
278    filesystem_open_has_result_ = true;
279    filesystem_open_error_ = PP_OK;
280    return 0;
281  }
282
283  // Initialize filesystem.
284  filesystem_resource_ =
285      filesystem_iface_->Create(ppapi_->GetInstance(), filesystem_type);
286  if (filesystem_resource_ == 0)
287    return ENOSYS;
288
289  // We can't block the main thread, so make an asynchronous call if on main
290  // thread. If we are off-main-thread, then don't make an asynchronous call;
291  // otherwise we require a message loop.
292  bool main_thread = core_iface_->IsMainThread();
293  PP_CompletionCallback cc =
294      main_thread ? PP_MakeCompletionCallback(
295                        &Html5Fs::FilesystemOpenCallbackThunk, this)
296                  : PP_BlockUntilComplete();
297
298  int32_t result =
299      filesystem_iface_->Open(filesystem_resource_, expected_size, cc);
300
301  if (!main_thread) {
302    filesystem_open_has_result_ = true;
303    filesystem_open_error_ = PPErrorToErrno(result);
304
305    return filesystem_open_error_;
306  }
307
308  // We have to assume the call to Open will succeed; there is no better
309  // result to return here.
310  return 0;
311}
312
313void Html5Fs::Destroy() {
314  if (ppapi_ != NULL && filesystem_resource_ != 0)
315    ppapi_->ReleaseResource(filesystem_resource_);
316  pthread_cond_destroy(&filesystem_open_cond_);
317}
318
319Error Html5Fs::BlockUntilFilesystemOpen() {
320  AUTO_LOCK(filesysem_open_lock_);
321  while (!filesystem_open_has_result_) {
322    pthread_cond_wait(&filesystem_open_cond_, filesysem_open_lock_.mutex());
323  }
324  return filesystem_open_error_;
325}
326
327// static
328void Html5Fs::FilesystemOpenCallbackThunk(void* user_data, int32_t result) {
329  Html5Fs* self = static_cast<Html5Fs*>(user_data);
330  self->FilesystemOpenCallback(result);
331}
332
333void Html5Fs::FilesystemOpenCallback(int32_t result) {
334  AUTO_LOCK(filesysem_open_lock_);
335  filesystem_open_has_result_ = true;
336  filesystem_open_error_ = PPErrorToErrno(result);
337  pthread_cond_signal(&filesystem_open_cond_);
338}
339
340}  // namespace nacl_io
341