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