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