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_node.h"
6
7#include <errno.h>
8#include <fcntl.h>
9#include <ppapi/c/pp_completion_callback.h>
10#include <ppapi/c/pp_directory_entry.h>
11#include <ppapi/c/pp_errors.h>
12#include <ppapi/c/pp_file_info.h>
13#include <ppapi/c/ppb_file_io.h>
14#include <string.h>
15#include <vector>
16
17#include "nacl_io/filesystem.h"
18#include "nacl_io/getdents_helper.h"
19#include "nacl_io/html5fs/html5_fs.h"
20#include "nacl_io/kernel_handle.h"
21#include "nacl_io/osdirent.h"
22#include "nacl_io/pepper_interface.h"
23#include "sdk_util/auto_lock.h"
24
25namespace nacl_io {
26
27namespace {
28
29struct OutputBuffer {
30  void* data;
31  int element_count;
32};
33
34void* GetOutputBuffer(void* user_data, uint32_t count, uint32_t size) {
35  OutputBuffer* output = static_cast<OutputBuffer*>(user_data);
36  output->element_count = count;
37  if (count) {
38    output->data = malloc(count * size);
39    if (!output->data)
40      output->element_count = 0;
41  } else {
42    output->data = NULL;
43  }
44  return output->data;
45}
46
47int32_t OpenFlagsToPPAPIOpenFlags(int open_flags) {
48  int32_t ppapi_flags = 0;
49
50  switch (open_flags & 3) {
51    default:
52    case O_RDONLY:
53      ppapi_flags = PP_FILEOPENFLAG_READ;
54      break;
55    case O_WRONLY:
56      ppapi_flags = PP_FILEOPENFLAG_WRITE;
57      break;
58    case O_RDWR:
59      ppapi_flags = PP_FILEOPENFLAG_READ | PP_FILEOPENFLAG_WRITE;
60      break;
61  }
62
63  if (open_flags & O_CREAT)
64    ppapi_flags |= PP_FILEOPENFLAG_CREATE;
65  if (open_flags & O_TRUNC)
66    ppapi_flags |= PP_FILEOPENFLAG_TRUNCATE;
67  if (open_flags & O_EXCL)
68    ppapi_flags |= PP_FILEOPENFLAG_EXCLUSIVE;
69
70  return ppapi_flags;
71}
72
73}  // namespace
74
75Error Html5FsNode::FSync() {
76  // Cannot call Flush on a directory; simply do nothing.
77  if (IsaDir())
78    return 0;
79
80  int32_t result =
81      file_io_iface_->Flush(fileio_resource_, PP_BlockUntilComplete());
82  if (result != PP_OK)
83    return PPErrorToErrno(result);
84  return 0;
85}
86
87Error Html5FsNode::GetDents(size_t offs,
88                            struct dirent* pdir,
89                            size_t size,
90                            int* out_bytes) {
91  *out_bytes = 0;
92
93  // If this is not a directory, fail
94  if (!IsaDir())
95    return ENOTDIR;
96
97  // TODO(binji): Better handling of ino numbers.
98  const ino_t kCurDirIno = -1;
99  const ino_t kParentDirIno = -2;
100  GetDentsHelper helper(kCurDirIno, kParentDirIno);
101
102  OutputBuffer output_buf = {NULL, 0};
103  PP_ArrayOutput output = {&GetOutputBuffer, &output_buf};
104  int32_t result = file_ref_iface_->ReadDirectoryEntries(
105      fileref_resource_, output, PP_BlockUntilComplete());
106  if (result != PP_OK)
107    return PPErrorToErrno(result);
108
109  PP_DirectoryEntry* entries = static_cast<PP_DirectoryEntry*>(output_buf.data);
110
111  for (int i = 0; i < output_buf.element_count; ++i) {
112    PP_Var file_name_var = file_ref_iface_->GetName(entries[i].file_ref);
113
114    // Release the file reference.
115    filesystem_->ppapi()->ReleaseResource(entries[i].file_ref);
116
117    if (file_name_var.type != PP_VARTYPE_STRING)
118      continue;
119
120    uint32_t file_name_length;
121    const char* file_name =
122        var_iface_->VarToUtf8(file_name_var, &file_name_length);
123
124    if (file_name) {
125      file_name_length =
126          std::min(static_cast<size_t>(file_name_length),
127                   MEMBER_SIZE(dirent, d_name) - 1);  // -1 for NULL.
128
129      // The INO is based on the running hash of fully qualified path, so
130      // a childs INO must be the parent directories hash, plus '/', plus
131      // the filename.
132      ino_t child_ino = Html5Fs::HashPathSegment(stat_.st_ino, file_name,
133                                                 file_name_length);
134
135      helper.AddDirent(child_ino, file_name, file_name_length);
136    }
137
138    var_iface_->Release(file_name_var);
139  }
140
141  // Release the output buffer.
142  free(output_buf.data);
143
144  return helper.GetDents(offs, pdir, size, out_bytes);
145}
146
147Error Html5FsNode::GetStat(struct stat* stat) {
148  AUTO_LOCK(node_lock_);
149
150  PP_FileInfo info;
151  int32_t result =
152      file_ref_iface_->Query(fileref_resource_, &info, PP_BlockUntilComplete());
153  if (result != PP_OK)
154    return PPErrorToErrno(result);
155
156  // Fill in known info here.
157  memcpy(stat, &stat_, sizeof(stat_));
158
159  // Fill in the additional info from ppapi.
160  switch (info.type) {
161    case PP_FILETYPE_REGULAR:
162      stat->st_mode |= S_IFREG;
163      break;
164    case PP_FILETYPE_DIRECTORY:
165      stat->st_mode |= S_IFDIR;
166      break;
167    case PP_FILETYPE_OTHER:
168    default:
169      break;
170  }
171  stat->st_size = static_cast<off_t>(info.size);
172  stat->st_atime = info.last_access_time;
173  stat->st_mtime = info.last_modified_time;
174  stat->st_ctime = info.creation_time;
175
176  return 0;
177}
178
179Error Html5FsNode::Read(const HandleAttr& attr,
180                        void* buf,
181                        size_t count,
182                        int* out_bytes) {
183  *out_bytes = 0;
184
185  if (IsaDir())
186    return EISDIR;
187
188  int32_t result = file_io_iface_->Read(fileio_resource_,
189                                        attr.offs,
190                                        static_cast<char*>(buf),
191                                        static_cast<int32_t>(count),
192                                        PP_BlockUntilComplete());
193  if (result < 0)
194    return PPErrorToErrno(result);
195
196  *out_bytes = result;
197  return 0;
198}
199
200Error Html5FsNode::FTruncate(off_t size) {
201  if (IsaDir())
202    return EISDIR;
203
204  int32_t result = file_io_iface_->SetLength(
205      fileio_resource_, size, PP_BlockUntilComplete());
206  if (result != PP_OK)
207    return PPErrorToErrno(result);
208  return 0;
209}
210
211Error Html5FsNode::Write(const HandleAttr& attr,
212                         const void* buf,
213                         size_t count,
214                         int* out_bytes) {
215  *out_bytes = 0;
216
217  if (IsaDir())
218    return EISDIR;
219
220  int32_t result = file_io_iface_->Write(fileio_resource_,
221                                         attr.offs,
222                                         static_cast<const char*>(buf),
223                                         static_cast<int32_t>(count),
224                                         PP_BlockUntilComplete());
225  if (result < 0)
226    return PPErrorToErrno(result);
227
228  *out_bytes = result;
229  return 0;
230}
231
232int Html5FsNode::GetType() {
233  return fileio_resource_ ? S_IFREG : S_IFDIR;
234}
235
236Error Html5FsNode::GetSize(off_t* out_size) {
237  *out_size = 0;
238
239  if (IsaDir())
240    return 0;
241
242  AUTO_LOCK(node_lock_);
243
244  PP_FileInfo info;
245  int32_t result =
246      file_io_iface_->Query(fileio_resource_, &info, PP_BlockUntilComplete());
247  if (result != PP_OK)
248    return PPErrorToErrno(result);
249
250  *out_size = info.size;
251  return 0;
252}
253
254Html5FsNode::Html5FsNode(Filesystem* filesystem, PP_Resource fileref_resource)
255    : Node(filesystem),
256      fileref_resource_(fileref_resource),
257      fileio_resource_(0) {
258}
259
260Error Html5FsNode::Init(int open_flags) {
261  Error error = Node::Init(open_flags);
262  if (error)
263    return error;
264
265  file_io_iface_ = filesystem_->ppapi()->GetFileIoInterface();
266  file_ref_iface_ = filesystem_->ppapi()->GetFileRefInterface();
267  var_iface_ = filesystem_->ppapi()->GetVarInterface();
268
269  if (!(file_io_iface_ && file_ref_iface_ && var_iface_)) {
270    LOG_ERROR("Got NULL interface(s): %s%s%s",
271              file_ref_iface_ ? "" : "FileRef",
272              file_io_iface_ ? "" : "FileIo ",
273              var_iface_ ? "" : "Var ");
274    return EIO;
275  }
276
277  // Set all files and directories to RWX.
278  SetMode(S_IWALL | S_IRALL | S_IXALL);
279
280  // First query the FileRef to see if it is a file or directory.
281  PP_FileInfo file_info;
282  int32_t query_result = file_ref_iface_->Query(
283      fileref_resource_, &file_info, PP_BlockUntilComplete());
284  // If this is a directory, do not get a FileIO.
285  if (query_result == PP_OK && file_info.type == PP_FILETYPE_DIRECTORY) {
286    return 0;
287  }
288
289  fileio_resource_ =
290      file_io_iface_->Create(filesystem_->ppapi()->GetInstance());
291  if (!fileio_resource_) {
292    LOG_ERROR("Couldn't create FileIo resource.");
293    return EIO;
294  }
295
296  int32_t open_result =
297      file_io_iface_->Open(fileio_resource_,
298                           fileref_resource_,
299                           OpenFlagsToPPAPIOpenFlags(open_flags),
300                           PP_BlockUntilComplete());
301  if (open_result != PP_OK)
302    return PPErrorToErrno(open_result);
303  return 0;
304}
305
306void Html5FsNode::Destroy() {
307  FSync();
308
309  if (fileio_resource_) {
310    file_io_iface_->Close(fileio_resource_);
311    filesystem_->ppapi()->ReleaseResource(fileio_resource_);
312  }
313
314  filesystem_->ppapi()->ReleaseResource(fileref_resource_);
315  fileio_resource_ = 0;
316  fileref_resource_ = 0;
317  Node::Destroy();
318}
319
320}  // namespace nacl_io
321