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