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/fusefs/fuse_fs.h"
6
7#include <errno.h>
8#include <fcntl.h>
9#include <string.h>
10
11#include <algorithm>
12
13#include "nacl_io/getdents_helper.h"
14#include "nacl_io/kernel_handle.h"
15#include "nacl_io/log.h"
16#include "sdk_util/macros.h"
17
18namespace nacl_io {
19
20namespace {
21
22struct FillDirInfo {
23  FillDirInfo(GetDentsHelper* getdents, size_t num_bytes)
24      : getdents(getdents), num_bytes(num_bytes), wrote_offset(false) {}
25
26  GetDentsHelper* getdents;
27  size_t num_bytes;
28  bool wrote_offset;
29};
30
31}  // namespace
32
33FuseFs::FuseFs() : fuse_ops_(NULL), fuse_user_data_(NULL) {
34}
35
36Error FuseFs::Init(const FsInitArgs& args) {
37  Error error = Filesystem::Init(args);
38  if (error)
39    return error;
40
41  fuse_ops_ = args.fuse_ops;
42  if (fuse_ops_ == NULL) {
43    LOG_ERROR("fuse_ops_ is NULL.");
44    return EINVAL;
45  }
46
47  if (fuse_ops_->init) {
48    struct fuse_conn_info info;
49    fuse_user_data_ = fuse_ops_->init(&info);
50  }
51
52  return 0;
53}
54
55void FuseFs::Destroy() {
56  if (fuse_ops_ && fuse_ops_->destroy)
57    fuse_ops_->destroy(fuse_user_data_);
58}
59
60Error FuseFs::OpenWithMode(const Path& path, int open_flags, mode_t mode,
61                           ScopedNode* out_node) {
62  std::string path_str = path.Join();
63  const char* path_cstr = path_str.c_str();
64  int result = 0;
65
66  struct fuse_file_info fi;
67  memset(&fi, 0, sizeof(fi));
68  fi.flags = open_flags;
69
70  if (open_flags & (O_CREAT | O_EXCL)) {
71    // According to the FUSE docs, open() is not called when O_CREAT or O_EXCL
72    // is passed.
73    if (fuse_ops_->create) {
74      result = fuse_ops_->create(path_cstr, mode, &fi);
75      if (result < 0)
76        return -result;
77    } else if (fuse_ops_->mknod) {
78      result = fuse_ops_->mknod(path_cstr, mode, dev_);
79      if (result < 0)
80        return -result;
81    } else {
82      LOG_TRACE("fuse_ops_->create and fuse_ops_->mknod are NULL.");
83      return ENOSYS;
84    }
85  } else {
86    // First determine if this is a regular file or a directory.
87    if (fuse_ops_->getattr) {
88      struct stat statbuf;
89      result = fuse_ops_->getattr(path_cstr, &statbuf);
90      if (result < 0)
91        return -result;
92
93      if ((statbuf.st_mode & S_IFMT) == S_IFDIR) {
94        // This is a directory. Don't try to open, just create a new node with
95        // this path.
96        ScopedNode node(new DirFuseFsNode(this, fuse_ops_, fi, path_cstr));
97        Error error = node->Init(open_flags);
98        if (error)
99          return error;
100
101        *out_node = node;
102        return 0;
103      }
104      // Get mode.
105      mode = statbuf.st_mode & ~S_IFMT;
106    }
107
108    // Existing file.
109    if (open_flags & O_TRUNC) {
110      // According to the FUSE docs, O_TRUNC does two calls: first truncate()
111      // then open().
112      if (!fuse_ops_->truncate) {
113        LOG_TRACE("fuse_ops_->truncate is NULL.");
114        return ENOSYS;
115      }
116      result = fuse_ops_->truncate(path_cstr, 0);
117      if (result < 0)
118        return -result;
119    }
120
121    if (!fuse_ops_->open) {
122      LOG_TRACE("fuse_ops_->open is NULL.");
123      return ENOSYS;
124    }
125    result = fuse_ops_->open(path_cstr, &fi);
126    if (result < 0)
127      return -result;
128  }
129
130  ScopedNode node(new FileFuseFsNode(this, fuse_ops_, fi, path_cstr));
131  Error error = node->Init(open_flags);
132  if (error)
133    return error;
134  node->SetMode(mode);
135
136  *out_node = node;
137  return 0;
138}
139
140Error FuseFs::Unlink(const Path& path) {
141  if (!fuse_ops_->unlink) {
142    LOG_TRACE("fuse_ops_->unlink is NULL.");
143    return ENOSYS;
144  }
145
146  int result = fuse_ops_->unlink(path.Join().c_str());
147  if (result < 0)
148    return -result;
149
150  return 0;
151}
152
153Error FuseFs::Mkdir(const Path& path, int perm) {
154  if (!fuse_ops_->mkdir) {
155    LOG_TRACE("fuse_ops_->mkdir is NULL.");
156    return ENOSYS;
157  }
158
159  int result = fuse_ops_->mkdir(path.Join().c_str(), perm);
160  if (result < 0)
161    return -result;
162
163  return 0;
164}
165
166Error FuseFs::Rmdir(const Path& path) {
167  if (!fuse_ops_->rmdir) {
168    LOG_TRACE("fuse_ops_->rmdir is NULL.");
169    return ENOSYS;
170  }
171
172  int result = fuse_ops_->rmdir(path.Join().c_str());
173  if (result < 0)
174    return -result;
175
176  return 0;
177}
178
179Error FuseFs::Remove(const Path& path) {
180  ScopedNode node;
181  Error error = Open(path, O_RDONLY, &node);
182  if (error)
183    return error;
184
185  struct stat statbuf;
186  error = node->GetStat(&statbuf);
187  if (error)
188    return error;
189
190  node.reset();
191
192  if ((statbuf.st_mode & S_IFMT) == S_IFDIR) {
193    return Rmdir(path);
194  } else {
195    return Unlink(path);
196  }
197}
198
199Error FuseFs::Rename(const Path& path, const Path& newpath) {
200  if (!fuse_ops_->rename) {
201    LOG_TRACE("fuse_ops_->rename is NULL.");
202    return ENOSYS;
203  }
204
205  int result = fuse_ops_->rename(path.Join().c_str(), newpath.Join().c_str());
206  if (result < 0)
207    return -result;
208
209  return 0;
210}
211
212FuseFsNode::FuseFsNode(Filesystem* filesystem,
213                       struct fuse_operations* fuse_ops,
214                       struct fuse_file_info& info,
215                       const std::string& path)
216    : Node(filesystem), fuse_ops_(fuse_ops), info_(info), path_(path) {
217}
218
219bool FuseFsNode::CanOpen(int open_flags) {
220  struct stat statbuf;
221  Error error = GetStat(&statbuf);
222  if (error)
223    return false;
224
225  // GetStat cached the mode in stat_.st_mode. Forward to Node::CanOpen,
226  // which will check this mode against open_flags.
227  return Node::CanOpen(open_flags);
228}
229
230Error FuseFsNode::GetStat(struct stat* stat) {
231  int result;
232  if (fuse_ops_->fgetattr) {
233    result = fuse_ops_->fgetattr(path_.c_str(), stat, &info_);
234    if (result < 0)
235      return -result;
236  } else if (fuse_ops_->getattr) {
237    result = fuse_ops_->getattr(path_.c_str(), stat);
238    if (result < 0)
239      return -result;
240  } else {
241    LOG_TRACE("fuse_ops_->fgetattr and fuse_ops_->getattr are NULL.");
242    return ENOSYS;
243  }
244
245  // Also update the cached stat values.
246  stat_ = *stat;
247  return 0;
248}
249
250Error FuseFsNode::Futimens(const struct timespec times[2]) {
251  int result;
252  if (!fuse_ops_->utimens) {
253    LOG_TRACE("fuse_ops_->utimens is NULL.");
254    return ENOSYS;
255  }
256
257  result = fuse_ops_->utimens(path_.c_str(), times);
258  if (result < 0)
259    return -result;
260
261  return result;
262}
263
264Error FuseFsNode::Fchmod(mode_t mode) {
265  int result;
266  if (!fuse_ops_->chmod) {
267    LOG_TRACE("fuse_ops_->chmod is NULL.");
268    return ENOSYS;
269  }
270
271  result = fuse_ops_->chmod(path_.c_str(), mode);
272  if (result < 0)
273    return -result;
274
275  return result;
276}
277
278Error FuseFsNode::VIoctl(int request, va_list args) {
279  LOG_ERROR("Ioctl not implemented for fusefs.");
280  return ENOSYS;
281}
282
283Error FuseFsNode::Tcflush(int queue_selector) {
284  LOG_ERROR("Tcflush not implemented for fusefs.");
285  return ENOSYS;
286}
287
288Error FuseFsNode::Tcgetattr(struct termios* termios_p) {
289  LOG_ERROR("Tcgetattr not implemented for fusefs.");
290  return ENOSYS;
291}
292
293Error FuseFsNode::Tcsetattr(int optional_actions,
294                            const struct termios* termios_p) {
295  LOG_ERROR("Tcsetattr not implemented for fusefs.");
296  return ENOSYS;
297}
298
299Error FuseFsNode::GetSize(off_t* out_size) {
300  struct stat statbuf;
301  Error error = GetStat(&statbuf);
302  if (error)
303    return error;
304
305  *out_size = stat_.st_size;
306  return 0;
307}
308
309FileFuseFsNode::FileFuseFsNode(Filesystem* filesystem,
310                               struct fuse_operations* fuse_ops,
311                               struct fuse_file_info& info,
312                               const std::string& path)
313    : FuseFsNode(filesystem, fuse_ops, info, path) {
314}
315
316void FileFuseFsNode::Destroy() {
317  if (!fuse_ops_->release)
318    return;
319  fuse_ops_->release(path_.c_str(), &info_);
320}
321
322Error FileFuseFsNode::FSync() {
323  if (!fuse_ops_->fsync) {
324    LOG_ERROR("fuse_ops_->fsync is NULL.");
325    return ENOSYS;
326  }
327
328  int datasync = 0;
329  int result = fuse_ops_->fsync(path_.c_str(), datasync, &info_);
330  if (result < 0)
331    return -result;
332  return 0;
333}
334
335Error FileFuseFsNode::FTruncate(off_t length) {
336  if (!fuse_ops_->ftruncate) {
337    LOG_ERROR("fuse_ops_->ftruncate is NULL.");
338    return ENOSYS;
339  }
340
341  int result = fuse_ops_->ftruncate(path_.c_str(), length, &info_);
342  if (result < 0)
343    return -result;
344  return 0;
345}
346
347Error FileFuseFsNode::Read(const HandleAttr& attr,
348                           void* buf,
349                           size_t count,
350                           int* out_bytes) {
351  if (!fuse_ops_->read) {
352    LOG_ERROR("fuse_ops_->read is NULL.");
353    return ENOSYS;
354  }
355
356  char* cbuf = static_cast<char*>(buf);
357
358  int result = fuse_ops_->read(path_.c_str(), cbuf, count, attr.offs, &info_);
359  if (result < 0)
360    return -result;
361
362  // TODO(binji): support the direct_io flag
363  if (static_cast<size_t>(result) < count)
364    memset(&cbuf[result], 0, count - result);
365
366  *out_bytes = result;
367  return 0;
368}
369
370Error FileFuseFsNode::Write(const HandleAttr& attr,
371                            const void* buf,
372                            size_t count,
373                            int* out_bytes) {
374  if (!fuse_ops_->write) {
375    LOG_ERROR("fuse_ops_->write is NULL.");
376    return ENOSYS;
377  }
378
379  int result = fuse_ops_->write(
380      path_.c_str(), static_cast<const char*>(buf), count, attr.offs, &info_);
381  if (result < 0)
382    return -result;
383
384  // TODO(binji): support the direct_io flag
385  *out_bytes = result;
386  return 0;
387}
388
389DirFuseFsNode::DirFuseFsNode(Filesystem* filesystem,
390                             struct fuse_operations* fuse_ops,
391                             struct fuse_file_info& info,
392                             const std::string& path)
393    : FuseFsNode(filesystem, fuse_ops, info, path) {
394}
395
396void DirFuseFsNode::Destroy() {
397  if (!fuse_ops_->releasedir)
398    return;
399  fuse_ops_->releasedir(path_.c_str(), &info_);
400}
401
402Error DirFuseFsNode::FSync() {
403  if (!fuse_ops_->fsyncdir) {
404    LOG_ERROR("fuse_ops_->fsyncdir is NULL.");
405    return ENOSYS;
406  }
407
408  int datasync = 0;
409  int result = fuse_ops_->fsyncdir(path_.c_str(), datasync, &info_);
410  if (result < 0)
411    return -result;
412  return 0;
413}
414
415Error DirFuseFsNode::GetDents(size_t offs,
416                              struct dirent* pdir,
417                              size_t count,
418                              int* out_bytes) {
419  if (!fuse_ops_->readdir) {
420    LOG_ERROR("fuse_ops_->readdir is NULL.");
421    return ENOSYS;
422  }
423
424  bool opened_dir = false;
425  int result;
426
427  // Opendir is not necessary, only readdir. Call it anyway, if it is defined.
428  if (fuse_ops_->opendir) {
429    result = fuse_ops_->opendir(path_.c_str(), &info_);
430    if (result < 0)
431      return -result;
432
433    opened_dir = true;
434  }
435
436  Error error = 0;
437  GetDentsHelper getdents;
438  FillDirInfo fill_info(&getdents, count);
439  result = fuse_ops_->readdir(
440      path_.c_str(), &fill_info, &DirFuseFsNode::FillDirCallback, offs, &info_);
441  if (result < 0)
442    goto fail;
443
444  // If the fill function ever wrote an entry with |offs| != 0, then assume it
445  // was not given the full list of entries. In that case, GetDentsHelper's
446  // buffers start with the entry at offset |offs|, so the call to
447  // GetDentsHelpers::GetDents should use an offset of 0.
448  if (fill_info.wrote_offset)
449    offs = 0;
450
451  // The entries have been filled in from the FUSE filesystem, now write them
452  // out to the buffer.
453  error = getdents.GetDents(offs, pdir, count, out_bytes);
454  if (error)
455    goto fail;
456
457  return 0;
458
459fail:
460  if (opened_dir && fuse_ops_->releasedir) {
461    // Ignore this result, we're already failing.
462    fuse_ops_->releasedir(path_.c_str(), &info_);
463  }
464
465  return -result;
466}
467
468int DirFuseFsNode::FillDirCallback(void* buf,
469                                   const char* name,
470                                   const struct stat* stbuf,
471                                   off_t off) {
472  FillDirInfo* fill_info = static_cast<FillDirInfo*>(buf);
473
474  // It is OK for the FUSE filesystem to pass a NULL stbuf. In that case, just
475  // use a bogus ino.
476  ino_t ino = stbuf ? stbuf->st_ino : 1;
477
478  // The FUSE docs say that the implementor of readdir can choose to ignore the
479  // offset given, and instead return all entries. To do this, they pass
480  // |off| == 0 for each call.
481  if (off) {
482    if (fill_info->num_bytes < sizeof(dirent))
483      return 1;  // 1 => buffer is full
484
485    fill_info->wrote_offset = true;
486    fill_info->getdents->AddDirent(ino, name, strlen(name));
487    fill_info->num_bytes -= sizeof(dirent);
488    // return 0 => request more data. return 1 => buffer full.
489    return fill_info->num_bytes > 0 ? 0 : 1;
490  } else {
491    fill_info->getdents->AddDirent(ino, name, strlen(name));
492    fill_info->num_bytes -= sizeof(dirent);
493    // According to the docs, we can never return 1 (buffer full) when the
494    // offset is zero (the user is probably ignoring the result anyway).
495    return 0;
496  }
497}
498
499}  // namespace nacl_io
500