diff options
author | binji@chromium.org <binji@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-12-11 20:24:29 +0000 |
---|---|---|
committer | binji@chromium.org <binji@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-12-11 20:24:29 +0000 |
commit | 2b6fc834dc9c17213eb51dc7685340c7cd65ab14 (patch) | |
tree | 226defb51a6f9c6385cb0ed300285fae4264c960 /native_client_sdk | |
parent | 726b63668d783e1e53c6a4d6e0102075a8c28143 (diff) | |
download | chromium_src-2b6fc834dc9c17213eb51dc7685340c7cd65ab14.zip chromium_src-2b6fc834dc9c17213eb51dc7685340c7cd65ab14.tar.gz chromium_src-2b6fc834dc9c17213eb51dc7685340c7cd65ab14.tar.bz2 |
[NaCl SDK] Add FUSE mount.
This is an initial implementation of the FUSE interface, to allow extending the
types of mounts nacl_io supports.
BUG=none
R=sbc@chromium.org
Review URL: https://codereview.chromium.org/108803003
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@240167 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'native_client_sdk')
38 files changed, 1423 insertions, 65 deletions
diff --git a/native_client_sdk/src/libraries/nacl_io/fuse.h b/native_client_sdk/src/libraries/nacl_io/fuse.h new file mode 100644 index 0000000..b7f52d4 --- /dev/null +++ b/native_client_sdk/src/libraries/nacl_io/fuse.h @@ -0,0 +1,221 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBRARIES_NACL_IO_FUSE_H_ +#define LIBRARIES_NACL_IO_FUSE_H_ + +#include "osinttypes.h" +#include "ostypes.h" + +// These interfaces are copied from the FUSE library. +// +// FUSE has two interfaces that can be implemented: low-level and high-level. +// In nacl_io, we only support the high-level interface. +// +// See http://fuse.sourceforge.net/ for more information. + +// This struct is typically passed to functions that would normally use return +// or receive an fd; that is, operations to open/create a node, or operations +// that act on an already opened node. +struct fuse_file_info { + // This is filled with the flags passed to open() + int flags; + // Deprecated in FUSE. Use fh instead. + unsigned long fh_old; + int writepage; + // Currently unsupported + unsigned int direct_io : 1; + // Currently unsupported + unsigned int keep_cache : 1; + // Currently unsupported + unsigned int flush : 1; + // Currently unsupported + unsigned int nonseekable : 1; + // Currently unsupported + unsigned int padding : 27; + // This value is not used by nacl_io. It can be filled by the developer when + // open() is called, and reused for subsequent calls on the same node. + uint64_t fh; + // Currently unsupported + uint64_t lock_owner; + // Currently unsupported + uint32_t poll_events; +}; + +// A dummy structure that currently exists only to match the FUSE interface. +struct fuse_conn_info {}; + +// A function of this type will be passed to readdir (see below). The developer +// should call this function once for each directory entry. +// +// See the documentation for readdir() below for more information on how to use +// this function. +typedef int (*fuse_fill_dir_t)(void* buf, + const char* name, + const struct stat* stbuf, + off_t off); + +// This structure defines the interface to create a user mount. Pass this to +// nacl_io_register_mount_type(). (see nacl_io.h) +// +// Example: +// +// struct fuse_operations g_my_fuse_operations = { ... }; +// ... +// nacl_io_register_mount_type("myfusefs", &g_my_fuse_operations); +// ... +// mount("", "/mnt/fuse", "myfusefs", 0, NULL); +// +// It is not necessary to implement every function -- nacl_io will first check +// if the function pointer is NULL before calling it. If it is NULL and +// required by the current operation, the call will fail and return ENOSYS in +// errno. +// +// Except where specified below, each function should return one of the +// following values: +// == 0: operation completed successfully. +// < 0: operation failed. The error is a negative errno. e.g. -EACCES, -EPERM, +// etc. The sign will be flipped when the error is actually set. +// +// Some functions (e.g. read, write) also return a positive count, which is the +// number of bytes read/written. +// +struct fuse_operations { + // Currently unsupported + unsigned int flag_nopath : 1; + unsigned int flag_reserved : 31; + + // Called when a mount of this type is initialized. + void* (*init)(struct fuse_conn_info* conn); + // Called when a mount of this type is unmounted. + void (*destroy)(void*); + // Called by access() + int (*access)(const char* path, int mode); + // Called when O_CREAT is passed to open() + int (*create)(const char* path, mode_t mode, struct fuse_file_info*); + // Called by stat()/fstat(). If this function pointer is non-NULL, it is + // called, otherwise fuse_operations.getattr will be called. + int (*fgetattr)(const char* path, struct stat*, struct fuse_file_info*); + // Called by fsync(). The datasync paramater is not currently supported. + int (*fsync)(const char* path, int datasync, struct fuse_file_info*); + // Called by ftruncate() + int (*ftruncate)(const char* path, off_t, struct fuse_file_info*); + // Called by stat()/fstat(), but only when fuse_operations.fgetattr is NULL. + // Also called by open() to determine if the path is a directory or a regular + // file. + int (*getattr)(const char* path, struct stat*); + // Called by mkdir() + int (*mkdir)(const char* path, mode_t); + // Called when O_CREAT is passed to open(), but only if fuse_operations.create + // is non-NULL. + int (*mknod)(const char* path, mode_t, dev_t); + // Called by open() + int (*open)(const char* path, struct fuse_file_info*); + // Called by getdents(), which is called by the more standard functions + // opendir()/readdir(). + int (*opendir)(const char* path, struct fuse_file_info*); + // Called by read(). Note that FUSE specifies that all reads will fill the + // entire requested buffer. If this function returns less than that, the + // remainder of the buffer is zeroed. + int (*read)(const char* path, char* buf, size_t count, off_t, + struct fuse_file_info*); + // Called by getdents(), which is called by the more standard function + // readdir(). + // + // NOTE: it is the responsibility of this function to add the "." and ".." + // entries. + // + // This function can be implemented one of two ways: + // 1) Ignore the offset, and always write every entry in a given directory. + // In this case, you should always call filler() with an offset of 0. You + // can ignore the return value of the filler. + // + // int my_readdir(const char* path, void* buf, fuse_fill_dir_t filler, + // off_t offset, struct fuse_file_info*) { + // ... + // filler(buf, ".", NULL, 0); + // filler(buf, "..", NULL, 0); + // filler(buf, "file1", &file1stat, 0); + // filler(buf, "file2", &file2stat, 0); + // return 0; + // } + // + // 2) Only write entries starting from offset. Always pass the correct offset + // to the filler function. When the filler function returns 1, the buffer + // is full so you can exit readdir. + // + // int my_readdir(const char* path, void* buf, fuse_fill_dir_t filler, + // off_t offset, struct fuse_file_info*) { + // ... + // size_t kNumEntries = 4; + // const char* my_entries[] = { ".", "..", "file1", "file2" }; + // int entry_index = offset / sizeof(dirent); + // offset = entry_index * sizeof(dirent); + // while (entry_index < kNumEntries) { + // int result = filler(buf, my_entries[entry_index], NULL, offset); + // if (filler == 1) { + // // buffer filled, we're done. + // return 0; + // } + // offset += sizeof(dirent); + // entry_index++; + // } + // + // // No more entries, we're done. + // return 0; + // } + // + int (*readdir)(const char* path, void* buf, fuse_fill_dir_t filldir, off_t, + struct fuse_file_info*); + // Called when the last reference to this node is released. This is only + // called for regular files. For directories, fuse_operations.releasedir is + // called instead. + int (*release)(const char* path, struct fuse_file_info*); + // Called when the last reference to this node is released. This is only + // called for directories. For regular files, fuse_operations.release is + // called instead. + int (*releasedir)(const char* path, struct fuse_file_info*); + // Called by rename() + int (*rename)(const char* path, const char* new_path); + // Called by rmdir() + int (*rmdir)(const char* path); + // Called by truncate(), as well as open() when O_TRUNC is passed. + int (*truncate)(const char* path, off_t); + // Called by unlink() + int (*unlink)(const char* path); + // Called by write(). Note that FUSE specifies that a write should always + // return the full count, unless an error occurs. + int (*write)(const char* path, const char* buf, size_t count, off_t, + struct fuse_file_info*); + + // The following functions are not currently called by the nacl_io + // implementation of FUSE. + int (*bmap)(const char*, size_t blocksize, uint64_t* idx); + int (*chmod)(const char*, mode_t); + int (*chown)(const char*, uid_t, gid_t); + int (*fallocate)(const char*, int, off_t, off_t, struct fuse_file_info*); + int (*flock)(const char*, struct fuse_file_info*, int op); + int (*flush)(const char*, struct fuse_file_info*); + int (*fsyncdir)(const char*, int, struct fuse_file_info*); + int (*getxattr)(const char*, const char*, char*, size_t); + int (*ioctl)(const char*, int cmd, void* arg, struct fuse_file_info*, + unsigned int flags, void* data); + int (*link)(const char*, const char*); + int (*listxattr)(const char*, char*, size_t); + int (*lock)(const char*, struct fuse_file_info*, int cmd, struct flock*); + int (*poll)(const char*, struct fuse_file_info*, struct fuse_pollhandle* ph, + unsigned* reventsp); + int (*read_buf)(const char*, struct fuse_bufvec** bufp, size_t size, + off_t off, struct fuse_file_info*); + int (*readlink)(const char*, char*, size_t); + int (*removexattr)(const char*, const char*); + int (*setxattr)(const char*, const char*, const char*, size_t, int); + int (*statfs)(const char*, struct statvfs*); + int (*symlink)(const char*, const char*); + int (*utimens)(const char*, const struct timespec tv[2]); + int (*write_buf)(const char*, struct fuse_bufvec* buf, off_t off, + struct fuse_file_info*); +}; + +#endif // LIBRARIES_NACL_IO_FUSE_H_ diff --git a/native_client_sdk/src/libraries/nacl_io/fuse_mount_factory.cc b/native_client_sdk/src/libraries/nacl_io/fuse_mount_factory.cc new file mode 100644 index 0000000..c527883 --- /dev/null +++ b/native_client_sdk/src/libraries/nacl_io/fuse_mount_factory.cc @@ -0,0 +1,28 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "nacl_io/fuse_mount_factory.h" + +#include "nacl_io/mount_fuse.h" + +namespace nacl_io { + +FuseMountFactory::FuseMountFactory(fuse_operations* fuse_ops) + : fuse_ops_(fuse_ops) {} + +Error FuseMountFactory::CreateMount(const MountInitArgs& args, + ScopedMount* out_mount) { + MountInitArgs args_copy(args); + args_copy.fuse_ops = fuse_ops_; + + sdk_util::ScopedRef<MountFuse> mnt(new MountFuse()); + Error error = mnt->Init(args_copy); + if (error) + return error; + + *out_mount = mnt; + return 0; +} + +} // namespace nacl_io diff --git a/native_client_sdk/src/libraries/nacl_io/fuse_mount_factory.h b/native_client_sdk/src/libraries/nacl_io/fuse_mount_factory.h new file mode 100644 index 0000000..24ab4e4 --- /dev/null +++ b/native_client_sdk/src/libraries/nacl_io/fuse_mount_factory.h @@ -0,0 +1,27 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBRARIES_NACL_IO_FUSE_MOUNT_FACTORY_H_ +#define LIBRARIES_NACL_IO_FUSE_MOUNT_FACTORY_H_ + +#include "nacl_io/mount.h" +#include "nacl_io/mount_factory.h" + +struct fuse_operations; + +namespace nacl_io { + +class FuseMountFactory : public MountFactory { + public: + explicit FuseMountFactory(fuse_operations* fuse_ops); + virtual Error CreateMount(const MountInitArgs& args, + ScopedMount* out_mount); + + private: + fuse_operations* fuse_ops_; +}; + +} // namespace nacl_io + +#endif // LIBRARIES_NACL_IO_FUSE_MOUNT_FACTORY_H_ diff --git a/native_client_sdk/src/libraries/nacl_io/getdents_helper.cc b/native_client_sdk/src/libraries/nacl_io/getdents_helper.cc index 6b7098d..87dc32d 100644 --- a/native_client_sdk/src/libraries/nacl_io/getdents_helper.cc +++ b/native_client_sdk/src/libraries/nacl_io/getdents_helper.cc @@ -14,8 +14,15 @@ namespace nacl_io { +GetDentsHelper::GetDentsHelper() + : curdir_ino_(0), parentdir_ino_(0), init_defaults_(false) { + Initialize(); +} + GetDentsHelper::GetDentsHelper(ino_t curdir_ino, ino_t parentdir_ino) - : curdir_ino_(curdir_ino), parentdir_ino_(parentdir_ino) { + : curdir_ino_(curdir_ino), + parentdir_ino_(parentdir_ino), + init_defaults_(true) { Initialize(); } @@ -25,9 +32,11 @@ void GetDentsHelper::Reset() { } void GetDentsHelper::Initialize() { - // Add the default entries: "." and ".." - AddDirent(curdir_ino_, ".", 1); - AddDirent(parentdir_ino_, "..", 2); + if (init_defaults_) { + // Add the default entries: "." and ".." + AddDirent(curdir_ino_, ".", 1); + AddDirent(parentdir_ino_, "..", 2); + } } void GetDentsHelper::AddDirent(ino_t ino, const char* name, size_t namelen) { diff --git a/native_client_sdk/src/libraries/nacl_io/getdents_helper.h b/native_client_sdk/src/libraries/nacl_io/getdents_helper.h index ca611c6..0d5d34f 100644 --- a/native_client_sdk/src/libraries/nacl_io/getdents_helper.h +++ b/native_client_sdk/src/libraries/nacl_io/getdents_helper.h @@ -14,6 +14,8 @@ namespace nacl_io { class GetDentsHelper { public: + // Initialize the helper without any defaults. + GetDentsHelper(); GetDentsHelper(ino_t curdir_ino, ino_t parentdir_ino); void Reset(); @@ -26,6 +28,7 @@ class GetDentsHelper { std::vector<dirent> dirents_; ino_t curdir_ino_; ino_t parentdir_ino_; + bool init_defaults_; }; } // namespace nacl_io diff --git a/native_client_sdk/src/libraries/nacl_io/kernel_intercept.cc b/native_client_sdk/src/libraries/nacl_io/kernel_intercept.cc index fa661a5..b5f894c 100644 --- a/native_client_sdk/src/libraries/nacl_io/kernel_intercept.cc +++ b/native_client_sdk/src/libraries/nacl_io/kernel_intercept.cc @@ -50,6 +50,15 @@ void ki_init_ppapi(void* kp, s_kp->Init(ppapi); } +int ki_register_mount_type(const char* mount_type, + struct fuse_operations* fuse_ops) { + return s_kp->RegisterMountType(mount_type, fuse_ops); +} + +int ki_unregister_mount_type(const char* mount_type) { + return s_kp->UnregisterMountType(mount_type); +} + int ki_is_initialized() { return s_kp != NULL; } diff --git a/native_client_sdk/src/libraries/nacl_io/kernel_intercept.h b/native_client_sdk/src/libraries/nacl_io/kernel_intercept.h index 5b987ed..b135213 100644 --- a/native_client_sdk/src/libraries/nacl_io/kernel_intercept.h +++ b/native_client_sdk/src/libraries/nacl_io/kernel_intercept.h @@ -21,6 +21,8 @@ EXTERN_C_BEGIN +struct fuse_operations; + // The kernel intercept module provides a C->C++ thunk between the libc // kernel calls and the KernelProxy singleton. @@ -31,6 +33,9 @@ void ki_init(void* kernel_proxy); void ki_init_ppapi(void* kernel_proxy, PP_Instance instance, PPB_GetInterface get_browser_interface); +int ki_register_mount_type(const char* mount_type, + struct fuse_operations* fuse_ops); +int ki_unregister_mount_type(const char* mount_type); int ki_is_initialized(); void ki_uninit(); diff --git a/native_client_sdk/src/libraries/nacl_io/kernel_proxy.cc b/native_client_sdk/src/libraries/nacl_io/kernel_proxy.cc index 83023c0..b18f2ad 100644 --- a/native_client_sdk/src/libraries/nacl_io/kernel_proxy.cc +++ b/native_client_sdk/src/libraries/nacl_io/kernel_proxy.cc @@ -18,6 +18,7 @@ #include <iterator> #include <string> +#include "nacl_io/fuse_mount_factory.h" #include "nacl_io/host_resolver.h" #include "nacl_io/kernel_handle.h" #include "nacl_io/kernel_wrap_real.h" @@ -111,9 +112,11 @@ Error KernelProxy::Init(PepperInterface* ppapi) { host_resolver_.Init(ppapi_); #endif - StringMap_t args; + MountInitArgs args; + args.dev = dev_++; + args.ppapi = ppapi_; stream_mount_.reset(new MountStream()); - result = stream_mount_->Init(0, args, ppapi); + result = stream_mount_->Init(args); if (result != 0) { assert(false); rtn = result; @@ -122,6 +125,26 @@ Error KernelProxy::Init(PepperInterface* ppapi) { return rtn; } +bool KernelProxy::RegisterMountType(const char* mount_type, + fuse_operations* fuse_ops) { + MountFactoryMap_t::iterator iter = factories_.find(mount_type); + if (iter != factories_.end()) + return false; + + factories_[mount_type] = new FuseMountFactory(fuse_ops); + return true; +} + +bool KernelProxy::UnregisterMountType(const char* mount_type) { + MountFactoryMap_t::iterator iter = factories_.find(mount_type); + if (iter == factories_.end()) + return false; + + delete iter->second; + factories_.erase(iter); + return true; +} + int KernelProxy::open_resource(const char* path) { ScopedMount mnt; Path rel; @@ -392,8 +415,13 @@ int KernelProxy::mount(const char* source, } } + MountInitArgs args; + args.dev = dev_++; + args.string_map = smap; + args.ppapi = ppapi_; + ScopedMount mnt; - Error error = factory->second->CreateMount(dev_++, smap, ppapi_, &mnt); + Error error = factory->second->CreateMount(args, &mnt); if (error) { errno = error; return -1; diff --git a/native_client_sdk/src/libraries/nacl_io/kernel_proxy.h b/native_client_sdk/src/libraries/nacl_io/kernel_proxy.h index 788370a..e037110 100644 --- a/native_client_sdk/src/libraries/nacl_io/kernel_proxy.h +++ b/native_client_sdk/src/libraries/nacl_io/kernel_proxy.h @@ -18,6 +18,7 @@ #include "nacl_io/ostypes.h" #include "nacl_io/osutime.h" +struct fuse_operations; struct timeval; namespace nacl_io { @@ -49,6 +50,11 @@ class KernelProxy : protected KernelObject { // |ppapi| may be NULL. If so, no mount that uses pepper calls can be mounted. virtual Error Init(PepperInterface* ppapi); + // Register/Unregister a new mount type. See the documentation in nacl_io.h + // for more info. + bool RegisterMountType(const char* mount_type, fuse_operations* fuse_ops); + bool UnregisterMountType(const char* mount_type); + virtual int pipe(int pipefds[2]); // NaCl-only function to read resources specified in the NMF file. diff --git a/native_client_sdk/src/libraries/nacl_io/library.dsc b/native_client_sdk/src/libraries/nacl_io/library.dsc index ecc298a..abddc64 100644 --- a/native_client_sdk/src/libraries/nacl_io/library.dsc +++ b/native_client_sdk/src/libraries/nacl_io/library.dsc @@ -20,6 +20,7 @@ "event_listener.cc", "fifo_char.cc", "fifo_packet.cc", + "fuse_mount_factory.cc", "getdents_helper.cc", "h_errno.cc", "host_resolver.cc", @@ -34,6 +35,7 @@ "kernel_wrap_win.cc", "mount.cc", "mount_dev.cc", + "mount_fuse.cc", "mount_html5fs.cc", "mount_http.cc", "mount_mem.cc", @@ -162,6 +164,8 @@ "fifo_interface.h", "fifo_null.h", "fifo_packet.h", + "fuse.h", + "fuse_mount_factory.h", "getdents_helper.h", "host_resolver.h", "inode_pool.h", @@ -175,6 +179,7 @@ "mount_dev.h", "mount_factory.h", "mount.h", + "mount_fuse.h", "mount_html5fs.h", "mount_http.h", "mount_mem.h", diff --git a/native_client_sdk/src/libraries/nacl_io/mount.cc b/native_client_sdk/src/libraries/nacl_io/mount.cc index 4892662..3c0d7a2 100644 --- a/native_client_sdk/src/libraries/nacl_io/mount.cc +++ b/native_client_sdk/src/libraries/nacl_io/mount.cc @@ -26,9 +26,9 @@ Mount::Mount() : dev_(0) {} Mount::~Mount() {} -Error Mount::Init(int dev, StringMap_t& args, PepperInterface* ppapi) { - dev_ = dev; - ppapi_ = ppapi; +Error Mount::Init(const MountInitArgs& args) { + dev_ = args.dev; + ppapi_ = args.ppapi; return 0; } diff --git a/native_client_sdk/src/libraries/nacl_io/mount.h b/native_client_sdk/src/libraries/nacl_io/mount.h index b13fc08..a99c1fa 100644 --- a/native_client_sdk/src/libraries/nacl_io/mount.h +++ b/native_client_sdk/src/libraries/nacl_io/mount.h @@ -17,6 +17,8 @@ #include "sdk_util/ref_object.h" #include "sdk_util/scoped_ref.h" +struct fuse_operations; + namespace nacl_io { class Mount; @@ -26,6 +28,20 @@ class PepperInterface; typedef sdk_util::ScopedRef<Mount> ScopedMount; typedef std::map<std::string, std::string> StringMap_t; +// This structure is passed to all mounts via the Mount::Init virtual function. +// With it, we can add or remove initialization values without changing the +// function signature. +struct MountInitArgs { + MountInitArgs() : dev(0), ppapi(NULL), fuse_ops(NULL) {} + explicit MountInitArgs(int dev) : dev(dev), ppapi(NULL), fuse_ops(NULL) {} + + // Device number of the new filesystem. + int dev; + StringMap_t string_map; + PepperInterface* ppapi; + fuse_operations* fuse_ops; +}; + // NOTE: The KernelProxy is the only class that should be setting errno. All // other classes should return Error (as defined by nacl_io/error.h). class Mount : public sdk_util::RefObject { @@ -36,9 +52,8 @@ class Mount : public sdk_util::RefObject { virtual ~Mount(); // Init must be called by the factory before the mount is used. - // This function must assign a root node, or replace FindNode. // |ppapi| can be NULL. If so, this mount cannot make any pepper calls. - virtual Error Init(int dev, StringMap_t& args, PepperInterface* ppapi); + virtual Error Init(const MountInitArgs& args); virtual void Destroy(); public: diff --git a/native_client_sdk/src/libraries/nacl_io/mount_dev.cc b/native_client_sdk/src/libraries/nacl_io/mount_dev.cc index 8fee68a..38eb86b 100644 --- a/native_client_sdk/src/libraries/nacl_io/mount_dev.cc +++ b/native_client_sdk/src/libraries/nacl_io/mount_dev.cc @@ -307,8 +307,8 @@ MountDev::MountDev() {} if (error) \ return error; -Error MountDev::Init(int dev, StringMap_t& args, PepperInterface* ppapi) { - Error error = Mount::Init(dev, args, ppapi); +Error MountDev::Init(const MountInitArgs& args) { + Error error = Mount::Init(args); if (error) return error; diff --git a/native_client_sdk/src/libraries/nacl_io/mount_dev.h b/native_client_sdk/src/libraries/nacl_io/mount_dev.h index 640158b..110c8fd 100644 --- a/native_client_sdk/src/libraries/nacl_io/mount_dev.h +++ b/native_client_sdk/src/libraries/nacl_io/mount_dev.h @@ -27,7 +27,7 @@ class MountDev : public Mount { protected: MountDev(); - virtual Error Init(int dev, StringMap_t& args, PepperInterface* ppapi); + virtual Error Init(const MountInitArgs& args); private: ScopedMountNode root_; diff --git a/native_client_sdk/src/libraries/nacl_io/mount_factory.h b/native_client_sdk/src/libraries/nacl_io/mount_factory.h index fd10014..48842ba 100644 --- a/native_client_sdk/src/libraries/nacl_io/mount_factory.h +++ b/native_client_sdk/src/libraries/nacl_io/mount_factory.h @@ -5,8 +5,7 @@ #ifndef LIBRARIES_NACL_IO_MOUNT_FACTORY_H_ #define LIBRARIES_NACL_IO_MOUNT_FACTORY_H_ -#include <map> -#include <string> +#include <errno.h> #include "nacl_io/error.h" #include "sdk_util/scoped_ref.h" @@ -15,15 +14,12 @@ namespace nacl_io { class PepperInterface; class Mount; - -typedef std::map<std::string, std::string> StringMap_t; +struct MountInitArgs; class MountFactory { public: virtual ~MountFactory() {} - virtual Error CreateMount(int dev, - StringMap_t& args, - PepperInterface* ppapi, + virtual Error CreateMount(const MountInitArgs& args, sdk_util::ScopedRef<Mount>* out_mount) = 0; }; diff --git a/native_client_sdk/src/libraries/nacl_io/mount_fuse.cc b/native_client_sdk/src/libraries/nacl_io/mount_fuse.cc new file mode 100644 index 0000000..8bf7664 --- /dev/null +++ b/native_client_sdk/src/libraries/nacl_io/mount_fuse.cc @@ -0,0 +1,462 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "nacl_io/mount_fuse.h" + +#include <errno.h> +#include <fcntl.h> +#include <string.h> + +#include <algorithm> + +#include "nacl_io/getdents_helper.h" +#include "nacl_io/kernel_handle.h" +#include "sdk_util/macros.h" + + +namespace nacl_io { + +namespace { + +struct FillDirInfo { + FillDirInfo(GetDentsHelper* getdents, size_t num_bytes) + : getdents(getdents), num_bytes(num_bytes), wrote_offset(false) {} + + GetDentsHelper* getdents; + size_t num_bytes; + bool wrote_offset; +}; + +} // namespace + +MountFuse::MountFuse() : fuse_ops_(NULL), fuse_user_data_(NULL) {} + +Error MountFuse::Init(const MountInitArgs& args) { + Error error = Mount::Init(args); + if (error) + return error; + + fuse_ops_ = args.fuse_ops; + if (fuse_ops_ == NULL) + return EINVAL; + + if (fuse_ops_->init) { + struct fuse_conn_info info; + fuse_user_data_ = fuse_ops_->init(&info); + } + + return 0; +} + +void MountFuse::Destroy() { + if (fuse_ops_ && fuse_ops_->destroy) + fuse_ops_->destroy(fuse_user_data_); +} + +Error MountFuse::Access(const Path& path, int a_mode) { + if (!fuse_ops_->access) + return ENOSYS; + + int result = fuse_ops_->access(path.Join().c_str(), a_mode); + if (result < 0) + return -result; + + return 0; +} + +Error MountFuse::Open(const Path& path, + int open_flags, + ScopedMountNode* out_node) { + std::string path_str = path.Join(); + const char* path_cstr = path_str.c_str(); + int result = 0; + + struct fuse_file_info fi; + memset(&fi, 0, sizeof(fi)); + fi.flags = open_flags; + + if (open_flags & (O_CREAT | O_EXCL)) { + // According to the FUSE docs, open() is not called when O_CREAT or O_EXCL + // is passed. + mode_t mode = S_IRALL | S_IWALL; + if (fuse_ops_->create) { + result = fuse_ops_->create(path_cstr, mode, &fi); + if (result < 0) + return -result; + } else if (fuse_ops_->mknod) { + result = fuse_ops_->mknod(path_cstr, mode, dev_); + if (result < 0) + return -result; + } else { + return ENOSYS; + } + } else { + // First determine if this is a regular file or a directory. + if (fuse_ops_->getattr) { + struct stat statbuf; + result = fuse_ops_->getattr(path_cstr, &statbuf); + if (result < 0) + return -result; + + if ((statbuf.st_mode & S_IFMT) == S_IFDIR) { + // This is a directory. Don't try to open, just create a new node with + // this path. + ScopedMountNode node( + new MountNodeFuseDir(this, fuse_ops_, fi, path_cstr)); + Error error = node->Init(open_flags); + if (error) + return error; + + *out_node = node; + return 0; + } + } + + // Existing file. + if (open_flags & O_TRUNC) { + // According to the FUSE docs, O_TRUNC does two calls: first truncate() + // then open(). + if (!fuse_ops_->truncate) + return ENOSYS; + result = fuse_ops_->truncate(path_cstr, 0); + if (result < 0) + return -result; + } + + if (!fuse_ops_->open) + return ENOSYS; + result = fuse_ops_->open(path_cstr, &fi); + if (result < 0) + return -result; + } + + ScopedMountNode node(new MountNodeFuseFile(this, fuse_ops_, fi, path_cstr)); + Error error = node->Init(open_flags); + if (error) + return error; + + *out_node = node; + return 0; +} + +Error MountFuse::Unlink(const Path& path) { + if (!fuse_ops_->unlink) + return ENOSYS; + + int result = fuse_ops_->unlink(path.Join().c_str()); + if (result < 0) + return -result; + + return 0; +} + +Error MountFuse::Mkdir(const Path& path, int perm) { + if (!fuse_ops_->mkdir) + return ENOSYS; + + int result = fuse_ops_->mkdir(path.Join().c_str(), perm); + if (result < 0) + return -result; + + return 0; +} + +Error MountFuse::Rmdir(const Path& path) { + if (!fuse_ops_->rmdir) + return ENOSYS; + + int result = fuse_ops_->rmdir(path.Join().c_str()); + if (result < 0) + return -result; + + return 0; +} + +Error MountFuse::Remove(const Path& path) { + ScopedMountNode node; + Error error = Open(path, O_RDONLY, &node); + if (error) + return error; + + struct stat statbuf; + error = node->GetStat(&statbuf); + if (error) + return error; + + node.reset(); + + if ((statbuf.st_mode & S_IFMT) == S_IFDIR) { + return Rmdir(path); + } else { + return Unlink(path); + } +} + +Error MountFuse::Rename(const Path& path, const Path& newpath) { + if (!fuse_ops_->rename) + return ENOSYS; + + int result = fuse_ops_->rename(path.Join().c_str(), newpath.Join().c_str()); + if (result < 0) + return -result; + + return 0; +} + +MountNodeFuse::MountNodeFuse(Mount* mount, + struct fuse_operations* fuse_ops, + struct fuse_file_info& info, + const std::string& path) + : MountNode(mount), + fuse_ops_(fuse_ops), + info_(info), + path_(path) {} + +bool MountNodeFuse::CanOpen(int open_flags) { + struct stat statbuf; + Error error = GetStat(&statbuf); + if (error) + return false; + + // GetStat cached the mode in stat_.st_mode. Forward to MountNode::CanOpen, + // which will check this mode against open_flags. + return MountNode::CanOpen(open_flags); +} + +Error MountNodeFuse::GetStat(struct stat* stat) { + int result; + if (fuse_ops_->fgetattr) { + result = fuse_ops_->fgetattr(path_.c_str(), stat, &info_); + if (result < 0) + return -result; + } else if (fuse_ops_->getattr) { + result = fuse_ops_->getattr(path_.c_str(), stat); + if (result < 0) + return -result; + } else { + return ENOSYS; + } + + // Also update the cached stat values. + stat_ = *stat; + return 0; +} + +Error MountNodeFuse::VIoctl(int request, va_list args) { + // TODO(binji): implement + return ENOSYS; +} + +Error MountNodeFuse::Tcflush(int queue_selector) { + // TODO(binji): use ioctl for this? + return ENOSYS; +} + +Error MountNodeFuse::Tcgetattr(struct termios* termios_p) { + // TODO(binji): use ioctl for this? + return ENOSYS; +} + +Error MountNodeFuse::Tcsetattr(int optional_actions, + const struct termios* termios_p) { + // TODO(binji): use ioctl for this? + return ENOSYS; +} + +Error MountNodeFuse::GetSize(size_t* out_size) { + struct stat statbuf; + Error error = GetStat(&statbuf); + if (error) + return error; + + *out_size = stat_.st_size; + return 0; +} + +MountNodeFuseFile::MountNodeFuseFile(Mount* mount, + struct fuse_operations* fuse_ops, + struct fuse_file_info& info, + const std::string& path) + : MountNodeFuse(mount, fuse_ops, info, path) {} + +void MountNodeFuseFile::Destroy() { + if (!fuse_ops_->release) + return; + fuse_ops_->release(path_.c_str(), &info_); +} + +Error MountNodeFuseFile::FSync() { + if (!fuse_ops_->fsync) + return ENOSYS; + + int datasync = 0; + int result = fuse_ops_->fsync(path_.c_str(), datasync, &info_); + if (result < 0) + return -result; + return 0; +} + +Error MountNodeFuseFile::FTruncate(off_t length) { + if (!fuse_ops_->ftruncate) + return ENOSYS; + + int result = fuse_ops_->ftruncate(path_.c_str(), length, &info_); + if (result < 0) + return -result; + return 0; +} + +Error MountNodeFuseFile::Read(const HandleAttr& attr, + void* buf, + size_t count, + int* out_bytes) { + if (!fuse_ops_->read) + return ENOSYS; + + char* cbuf = static_cast<char*>(buf); + + int result = fuse_ops_->read(path_.c_str(), cbuf, count, attr.offs, &info_); + if (result < 0) + return -result; + + // Fuse docs say that a read() call will always completely fill the buffer + // (padding with zeroes) unless the direct_io mount flag is set. + // TODO(binji): support the direct_io flag + if (static_cast<size_t>(result) < count) + memset(&cbuf[result], 0, count - result); + + *out_bytes = count; + return 0; +} + +Error MountNodeFuseFile::Write(const HandleAttr& attr, + const void* buf, + size_t count, + int* out_bytes) { + if (!fuse_ops_->write) + return ENOSYS; + + int result = fuse_ops_->write( + path_.c_str(), static_cast<const char*>(buf), count, attr.offs, &info_); + if (result < 0) + return -result; + + // Fuse docs say that a write() call will always write the entire buffer + // unless the direct_io mount flag is set. + // TODO(binji): What should we do if the user breaks this contract? Warn? + // TODO(binji): support the direct_io flag + *out_bytes = result; + return 0; +} + +MountNodeFuseDir::MountNodeFuseDir(Mount* mount, + struct fuse_operations* fuse_ops, + struct fuse_file_info& info, + const std::string& path) + : MountNodeFuse(mount, fuse_ops, info, path) {} + +void MountNodeFuseDir::Destroy() { + if (!fuse_ops_->releasedir) + return; + fuse_ops_->releasedir(path_.c_str(), &info_); +} + +Error MountNodeFuseDir::FSync() { + if (!fuse_ops_->fsyncdir) + return ENOSYS; + + int datasync = 0; + int result = fuse_ops_->fsyncdir(path_.c_str(), datasync, &info_); + if (result < 0) + return -result; + return 0; +} + +Error MountNodeFuseDir::GetDents(size_t offs, + struct dirent* pdir, + size_t count, + int* out_bytes) { + if (!fuse_ops_->readdir) + return ENOSYS; + + bool opened_dir = false; + int result; + + // Opendir is not necessary, only readdir. Call it anyway, if it is defined. + if (fuse_ops_->opendir) { + result = fuse_ops_->opendir(path_.c_str(), &info_); + if (result < 0) + return -result; + + opened_dir = true; + } + + Error error = 0; + GetDentsHelper getdents; + FillDirInfo fill_info(&getdents, count); + result = fuse_ops_->readdir(path_.c_str(), + &fill_info, + &MountNodeFuseDir::FillDirCallback, + offs, + &info_); + if (result < 0) + goto fail; + + // If the fill function ever wrote an entry with |offs| != 0, then assume it + // was not given the full list of entries. In that case, GetDentsHelper's + // buffers start with the entry at offset |offs|, so the call to + // GetDentsHelpers::GetDents should use an offset of 0. + if (fill_info.wrote_offset) + offs = 0; + + // The entries have been filled in from the FUSE filesystem, now write them + // out to the buffer. + error = getdents.GetDents(offs, pdir, count, out_bytes); + if (error) + goto fail; + + return 0; + +fail: + if (opened_dir && fuse_ops_->releasedir) { + // Ignore this result, we're already failing. + fuse_ops_->releasedir(path_.c_str(), &info_); + } + + return -result; +} + +int MountNodeFuseDir::FillDirCallback(void* buf, + const char* name, + const struct stat* stbuf, + off_t off) { + FillDirInfo* fill_info = static_cast<FillDirInfo*>(buf); + + // It is OK for the FUSE filesystem to pass a NULL stbuf. In that case, just + // use a bogus ino. + ino_t ino = stbuf ? stbuf->st_ino : 1; + + // The FUSE docs say that the implementor of readdir can choose to ignore the + // offset given, and instead return all entries. To do this, they pass + // |off| == 0 for each call. + if (off) { + if (fill_info->num_bytes < sizeof(dirent)) + return 1; // 1 => buffer is full + + fill_info->wrote_offset = true; + fill_info->getdents->AddDirent(ino, name, strlen(name)); + fill_info->num_bytes -= sizeof(dirent); + // return 0 => request more data. return 1 => buffer full. + return fill_info->num_bytes > 0 ? 0 : 1; + } else { + fill_info->getdents->AddDirent(ino, name, strlen(name)); + fill_info->num_bytes -= sizeof(dirent); + // According to the docs, we can never return 1 (buffer full) when the + // offset is zero (the user is probably ignoring the result anyway). + return 0; + } +} + + +} // namespace nacl_io diff --git a/native_client_sdk/src/libraries/nacl_io/mount_fuse.h b/native_client_sdk/src/libraries/nacl_io/mount_fuse.h new file mode 100644 index 0000000..28e4c3f --- /dev/null +++ b/native_client_sdk/src/libraries/nacl_io/mount_fuse.h @@ -0,0 +1,122 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBRARIES_NACL_IO_MOUNT_FUSE_H_ +#define LIBRARIES_NACL_IO_MOUNT_FUSE_H_ + +#include <map> + +#include "nacl_io/fuse.h" +#include "nacl_io/mount.h" +#include "nacl_io/mount_node.h" +#include "nacl_io/typed_mount_factory.h" + +namespace nacl_io { + +class MountFuse : public Mount { + protected: + MountFuse(); + + virtual Error Init(const MountInitArgs& args); + virtual void Destroy(); + + public: + virtual Error Access(const Path& path, int a_mode); + virtual Error Open(const Path& path, int mode, ScopedMountNode* out_node); + virtual Error Unlink(const Path& path); + virtual Error Mkdir(const Path& path, int perm); + virtual Error Rmdir(const Path& path); + virtual Error Remove(const Path& path); + virtual Error Rename(const Path& path, const Path& newpath); + +private: + struct fuse_operations* fuse_ops_; + void* fuse_user_data_; + + friend class MountNodeFuse; + friend class FuseMountFactory; + DISALLOW_COPY_AND_ASSIGN(MountFuse); +}; + +class MountNodeFuse : public MountNode { + protected: + MountNodeFuse(Mount* mount, + struct fuse_operations* fuse_ops, + struct fuse_file_info& info, + const std::string& path); + + public: + virtual bool CanOpen(int open_flags); + virtual Error GetStat(struct stat* stat); + virtual Error VIoctl(int request, va_list args); + virtual Error Tcflush(int queue_selector); + virtual Error Tcgetattr(struct termios* termios_p); + virtual Error Tcsetattr(int optional_actions, + const struct termios *termios_p); + virtual Error GetSize(size_t* out_size); + + protected: + struct fuse_operations* fuse_ops_; + struct fuse_file_info info_; + std::string path_; +}; + +class MountNodeFuseFile : public MountNodeFuse { + public: + MountNodeFuseFile(Mount* mount, + struct fuse_operations* fuse_ops, + struct fuse_file_info& info, + const std::string& path); + + protected: + virtual void Destroy(); + + public: + virtual Error FSync(); + virtual Error FTruncate(off_t length); + virtual Error Read(const HandleAttr& attr, + void* buf, + size_t count, + int* out_bytes); + virtual Error Write(const HandleAttr& attr, + const void* buf, + size_t count, + int* out_bytes); + + private: + friend class MountFuse; + DISALLOW_COPY_AND_ASSIGN(MountNodeFuseFile); +}; + +class MountNodeFuseDir : public MountNodeFuse { + public: + MountNodeFuseDir(Mount* mount, + struct fuse_operations* fuse_ops, + struct fuse_file_info& info, + const std::string& path); + + protected: + virtual void Destroy(); + + public: + virtual Error FSync(); + virtual Error GetDents(size_t offs, + struct dirent* pdir, + size_t count, + int* out_bytes); + + private: + static int FillDirCallback(void* buf, + const char* name, + const struct stat* stbuf, + off_t off); + + private: + friend class MountFuse; + DISALLOW_COPY_AND_ASSIGN(MountNodeFuseDir); +}; + +} // namespace nacl_io + +#endif // LIBRARIES_NACL_IO_MOUNT_FUSE_H_ diff --git a/native_client_sdk/src/libraries/nacl_io/mount_html5fs.cc b/native_client_sdk/src/libraries/nacl_io/mount_html5fs.cc index 3c9695f..a54c183 100644 --- a/native_client_sdk/src/libraries/nacl_io/mount_html5fs.cc +++ b/native_client_sdk/src/libraries/nacl_io/mount_html5fs.cc @@ -123,12 +123,12 @@ MountHtml5Fs::MountHtml5Fs() filesystem_open_has_result_(false), filesystem_open_error_(0) {} -Error MountHtml5Fs::Init(int dev, StringMap_t& args, PepperInterface* ppapi) { - Error error = Mount::Init(dev, args, ppapi); +Error MountHtml5Fs::Init(const MountInitArgs& args) { + Error error = Mount::Init(args); if (error) return error; - if (!ppapi) + if (!args.ppapi) return ENOSYS; pthread_cond_init(&filesystem_open_cond_, NULL); @@ -136,7 +136,8 @@ Error MountHtml5Fs::Init(int dev, StringMap_t& args, PepperInterface* ppapi) { // Parse mount args. PP_FileSystemType filesystem_type = PP_FILESYSTEMTYPE_LOCALPERSISTENT; int64_t expected_size = 0; - for (StringMap_t::iterator iter = args.begin(), end = args.end(); iter != end; + for (StringMap_t::const_iterator iter = args.string_map.begin(); + iter != args.string_map.end(); ++iter) { if (iter->first == "type") { if (iter->second == "PERSISTENT") { @@ -150,7 +151,7 @@ Error MountHtml5Fs::Init(int dev, StringMap_t& args, PepperInterface* ppapi) { } // Initialize filesystem. - filesystem_resource_ = ppapi->GetFileSystemInterface() + filesystem_resource_ = args.ppapi->GetFileSystemInterface() ->Create(ppapi_->GetInstance(), filesystem_type); if (filesystem_resource_ == 0) return ENOSYS; @@ -158,13 +159,13 @@ Error MountHtml5Fs::Init(int dev, StringMap_t& args, PepperInterface* ppapi) { // We can't block the main thread, so make an asynchronous call if on main // thread. If we are off-main-thread, then don't make an asynchronous call; // otherwise we require a message loop. - bool main_thread = ppapi->GetCoreInterface()->IsMainThread(); + bool main_thread = args.ppapi->GetCoreInterface()->IsMainThread(); PP_CompletionCallback cc = main_thread ? PP_MakeCompletionCallback( &MountHtml5Fs::FilesystemOpenCallbackThunk, this) : PP_BlockUntilComplete(); - int32_t result = ppapi->GetFileSystemInterface() + int32_t result = args.ppapi->GetFileSystemInterface() ->Open(filesystem_resource_, expected_size, cc); if (!main_thread) { diff --git a/native_client_sdk/src/libraries/nacl_io/mount_html5fs.h b/native_client_sdk/src/libraries/nacl_io/mount_html5fs.h index 0408c79..06dad78 100644 --- a/native_client_sdk/src/libraries/nacl_io/mount_html5fs.h +++ b/native_client_sdk/src/libraries/nacl_io/mount_html5fs.h @@ -31,7 +31,7 @@ class MountHtml5Fs : public Mount { protected: MountHtml5Fs(); - virtual Error Init(int dev, StringMap_t& args, PepperInterface* ppapi); + virtual Error Init(const MountInitArgs& args); virtual void Destroy(); Error BlockUntilFilesystemOpen(); diff --git a/native_client_sdk/src/libraries/nacl_io/mount_http.cc b/native_client_sdk/src/libraries/nacl_io/mount_http.cc index dcc1332..4d613e9 100644 --- a/native_client_sdk/src/libraries/nacl_io/mount_http.cc +++ b/native_client_sdk/src/libraries/nacl_io/mount_http.cc @@ -201,13 +201,15 @@ MountHttp::MountHttp() cache_stat_(true), cache_content_(true) {} -Error MountHttp::Init(int dev, StringMap_t& args, PepperInterface* ppapi) { - Error error = Mount::Init(dev, args, ppapi); +Error MountHttp::Init(const MountInitArgs& args) { + Error error = Mount::Init(args); if (error) return error; // Parse mount args. - for (StringMap_t::iterator iter = args.begin(); iter != args.end(); ++iter) { + for (StringMap_t::const_iterator iter = args.string_map.begin(); + iter != args.string_map.end(); + ++iter) { if (iter->first == "SOURCE") { url_root_ = iter->second; diff --git a/native_client_sdk/src/libraries/nacl_io/mount_http.h b/native_client_sdk/src/libraries/nacl_io/mount_http.h index 4946d9f..145140d 100644 --- a/native_client_sdk/src/libraries/nacl_io/mount_http.h +++ b/native_client_sdk/src/libraries/nacl_io/mount_http.h @@ -34,7 +34,7 @@ class MountHttp : public Mount { protected: MountHttp(); - virtual Error Init(int dev, StringMap_t& args, PepperInterface* ppapi); + virtual Error Init(const MountInitArgs& args); virtual void Destroy(); Error FindOrCreateDir(const Path& path, ScopedMountNode* out_node); Error LoadManifest(const std::string& path, char** out_manifest); diff --git a/native_client_sdk/src/libraries/nacl_io/mount_mem.cc b/native_client_sdk/src/libraries/nacl_io/mount_mem.cc index dd4a402..f00aa15 100644 --- a/native_client_sdk/src/libraries/nacl_io/mount_mem.cc +++ b/native_client_sdk/src/libraries/nacl_io/mount_mem.cc @@ -23,8 +23,8 @@ namespace nacl_io { MountMem::MountMem() : root_(NULL) {} -Error MountMem::Init(int dev, StringMap_t& args, PepperInterface* ppapi) { - Error error = Mount::Init(dev, args, ppapi); +Error MountMem::Init(const MountInitArgs& args) { + Error error = Mount::Init(args); if (error) return error; diff --git a/native_client_sdk/src/libraries/nacl_io/mount_mem.h b/native_client_sdk/src/libraries/nacl_io/mount_mem.h index 4966975..8ad87ca 100644 --- a/native_client_sdk/src/libraries/nacl_io/mount_mem.h +++ b/native_client_sdk/src/libraries/nacl_io/mount_mem.h @@ -14,7 +14,8 @@ class MountMem : public Mount { protected: MountMem(); - virtual Error Init(int dev, StringMap_t& args, PepperInterface* ppapi); + using Mount::Init; + virtual Error Init(const MountInitArgs& args); // The protected functions are only used internally and will not // acquire or release the mount's lock themselves. The caller is diff --git a/native_client_sdk/src/libraries/nacl_io/mount_node.h b/native_client_sdk/src/libraries/nacl_io/mount_node.h index fe5493c..80e27fa 100644 --- a/native_client_sdk/src/libraries/nacl_io/mount_node.h +++ b/native_client_sdk/src/libraries/nacl_io/mount_node.h @@ -132,6 +132,7 @@ class MountNode : public sdk_util::RefObject { friend class Mount; friend class MountDev; + friend class MountFuse; friend class MountHtml5Fs; friend class MountHttp; friend class MountMem; diff --git a/native_client_sdk/src/libraries/nacl_io/mount_passthrough.cc b/native_client_sdk/src/libraries/nacl_io/mount_passthrough.cc index 33e6675..9f91763 100644 --- a/native_client_sdk/src/libraries/nacl_io/mount_passthrough.cc +++ b/native_client_sdk/src/libraries/nacl_io/mount_passthrough.cc @@ -111,10 +111,8 @@ class MountNodePassthrough : public MountNode { MountPassthrough::MountPassthrough() {} -Error MountPassthrough::Init(int dev, - StringMap_t& args, - PepperInterface* ppapi) { - return Mount::Init(dev, args, ppapi); +Error MountPassthrough::Init(const MountInitArgs& args) { + return Mount::Init(args); } void MountPassthrough::Destroy() {} diff --git a/native_client_sdk/src/libraries/nacl_io/mount_passthrough.h b/native_client_sdk/src/libraries/nacl_io/mount_passthrough.h index 7b9aa61..3cc3285 100644 --- a/native_client_sdk/src/libraries/nacl_io/mount_passthrough.h +++ b/native_client_sdk/src/libraries/nacl_io/mount_passthrough.h @@ -14,7 +14,7 @@ class MountPassthrough : public Mount { protected: MountPassthrough(); - virtual Error Init(int dev, StringMap_t& args, PepperInterface* ppapi); + virtual Error Init(const MountInitArgs& args); virtual void Destroy(); public: diff --git a/native_client_sdk/src/libraries/nacl_io/nacl_io.cc b/native_client_sdk/src/libraries/nacl_io/nacl_io.cc index 5e58a60..2263d1b 100644 --- a/native_client_sdk/src/libraries/nacl_io/nacl_io.cc +++ b/native_client_sdk/src/libraries/nacl_io/nacl_io.cc @@ -16,3 +16,11 @@ void nacl_io_init_ppapi(PP_Instance instance, ki_init_ppapi(NULL, instance, get_interface); } +int nacl_io_register_mount_type(const char* mount_type, + fuse_operations* fuse_ops) { + return ki_register_mount_type(mount_type, fuse_ops); +} + +int nacl_io_unregister_mount_type(const char* mount_type) { + return ki_unregister_mount_type(mount_type); +} diff --git a/native_client_sdk/src/libraries/nacl_io/nacl_io.h b/native_client_sdk/src/libraries/nacl_io/nacl_io.h index 71fb928..98490f2 100644 --- a/native_client_sdk/src/libraries/nacl_io/nacl_io.h +++ b/native_client_sdk/src/libraries/nacl_io/nacl_io.h @@ -117,6 +117,67 @@ void nacl_io_init_ppapi(PP_Instance instance, * unsigned long mountflags, const void *data) NOTHROW; */ +/** + * Register a new mount type, using a FUSE interface to implement it. + * + * Example: + * int my_open(const char* path, struct fuse_file_info*) { + * ... + * } + * + * int my_read(const char* path, char* buf, size_t count, off_t offset, struct + * fuse_file_info* info) { + * ... + * } + * + * struct fuse_operations my_fuse_ops = { + * ... + * my_open, + * NULL, // opendir() not implemented. + * my_read, + * ... + * }; + * + * ... + * + * const char mount_type[] = "my_mount"; + * int result = nacl_io_register_mount_type(mount_type, &my_fuse_ops); + * if (!result) { + * fprintf(stderr, "Error registering mount type %s.\n", mount_type); + * exit(1); + * } + * + * ... + * + * int result = mount("", "/mnt/foo", mount_type, 0, NULL); + * if (!result) { + * fprintf(stderr, "Error mounting %s.\n", mount_type); + * exit(1); + * } + * + * See fuse.h for more information about the FUSE interface. + * Also see fuse.sourceforge.net for more information about FUSE in general. + * + * @param[in] mount_type The name of the new mount type. + * @param[in] fuse_ops A pointer to the FUSE interface that will be used to + * implement this mount type. This pointer must be valid for the lifetime + * of all mounts and nodes that are created with it. + * @return 0 on success, -1 on failure (with errno set). + */ +struct fuse_operations; +int nacl_io_register_mount_type(const char* mount_type, + struct fuse_operations* fuse_ops); + +/** + * Unregister a mount type, previously registered by + * nacl_io_register_mount_type(). + * + * @param[in] mount_type The name of the mount type; the same identifier that + * was passed to nacl_io_register_mount_type(). + * @return 0 on success, -1 on failure (with errno set). + */ +int nacl_io_unregister_mount_type(const char* mount_type); + EXTERN_C_END #endif /* LIBRARIES_NACL_IO_NACL_IO_H_ */ diff --git a/native_client_sdk/src/libraries/nacl_io/typed_mount_factory.h b/native_client_sdk/src/libraries/nacl_io/typed_mount_factory.h index 824c20e..805429c 100644 --- a/native_client_sdk/src/libraries/nacl_io/typed_mount_factory.h +++ b/native_client_sdk/src/libraries/nacl_io/typed_mount_factory.h @@ -13,12 +13,10 @@ namespace nacl_io { template <typename T> class TypedMountFactory : public MountFactory { public: - virtual Error CreateMount(int dev, - StringMap_t& args, - PepperInterface* ppapi, + virtual Error CreateMount(const MountInitArgs& args, ScopedMount* out_mount) { sdk_util::ScopedRef<T> mnt(new T()); - Error error = mnt->Init(dev, args, ppapi); + Error error = mnt->Init(args); if (error) return error; diff --git a/native_client_sdk/src/tests/nacl_io_test/example.dsc b/native_client_sdk/src/tests/nacl_io_test/example.dsc index d270b0f..f2a73e1 100644 --- a/native_client_sdk/src/tests/nacl_io_test/example.dsc +++ b/native_client_sdk/src/tests/nacl_io_test/example.dsc @@ -27,6 +27,7 @@ 'main.cc', 'mock_util.h', 'mount_dev_mock.h', + 'mount_fuse_test.cc', 'mount_html5fs_test.cc', 'mount_http_test.cc', 'mount_mock.cc', diff --git a/native_client_sdk/src/tests/nacl_io_test/kernel_proxy_test.cc b/native_client_sdk/src/tests/nacl_io_test/kernel_proxy_test.cc index 470ad17..2707ad4 100644 --- a/native_client_sdk/src/tests/nacl_io_test/kernel_proxy_test.cc +++ b/native_client_sdk/src/tests/nacl_io_test/kernel_proxy_test.cc @@ -422,13 +422,15 @@ TEST_F(KernelProxyTest, MemMountDup) { namespace { -StringMap_t g_StringMap; +StringMap_t g_string_map; class MountMockInit : public MountMem { public: - virtual Error Init(int dev, StringMap_t& args, PepperInterface* ppapi) { - g_StringMap = args; - if (args.find("false") != args.end()) + using MountMem::Init; + + virtual Error Init(const MountInitArgs& args) { + g_string_map = args.string_map; + if (g_string_map.find("false") != g_string_map.end()) return EINVAL; return 0; } @@ -465,13 +467,13 @@ class KernelProxyMountTest : public ::testing::Test { TEST_F(KernelProxyMountTest, MountInit) { int res1 = ki_mount("/", "/mnt1", "initfs", 0, "false,foo=bar"); - EXPECT_EQ("bar", g_StringMap["foo"]); + EXPECT_EQ("bar", g_string_map["foo"]); EXPECT_EQ(-1, res1); EXPECT_EQ(EINVAL, errno); int res2 = ki_mount("/", "/mnt2", "initfs", 0, "true,bar=foo,x=y"); EXPECT_NE(-1, res2); - EXPECT_EQ("y", g_StringMap["x"]); + EXPECT_EQ("y", g_string_map["x"]); } namespace { @@ -591,9 +593,7 @@ class SingletonMountFactory : public MountFactory { public: SingletonMountFactory(const ScopedMount& mount) : mount_(mount) {} - virtual Error CreateMount(int dev, - StringMap_t& args, - PepperInterface* ppapi, + virtual Error CreateMount(const MountInitArgs& args, ScopedMount* out_mount) { *out_mount = mount_; return 0; diff --git a/native_client_sdk/src/tests/nacl_io_test/mount_dev_mock.h b/native_client_sdk/src/tests/nacl_io_test/mount_dev_mock.h index 10ee830..72ef88c 100644 --- a/native_client_sdk/src/tests/nacl_io_test/mount_dev_mock.h +++ b/native_client_sdk/src/tests/nacl_io_test/mount_dev_mock.h @@ -15,8 +15,7 @@ class MountDevMock : public nacl_io::MountDev { public: MountDevMock() { - nacl_io::StringMap_t map; - Init(1, map, NULL); + Init(nacl_io::MountInitArgs(1)); } int num_nodes() { return (int) inode_pool_.size(); } }; diff --git a/native_client_sdk/src/tests/nacl_io_test/mount_fuse_test.cc b/native_client_sdk/src/tests/nacl_io_test/mount_fuse_test.cc new file mode 100644 index 0000000..a0ac553 --- /dev/null +++ b/native_client_sdk/src/tests/nacl_io_test/mount_fuse_test.cc @@ -0,0 +1,345 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <fcntl.h> + +#include <gtest/gtest.h> + +#include <string> +#include <vector> + +#include "nacl_io/fuse.h" +#include "nacl_io/kernel_handle.h" +#include "nacl_io/kernel_intercept.h" +#include "nacl_io/kernel_proxy.h" +#include "nacl_io/mount_fuse.h" + +using namespace nacl_io; + +namespace { + +class MountFuseForTesting : public MountFuse { + public: + explicit MountFuseForTesting(fuse_operations* fuse_ops) { + MountInitArgs args; + args.fuse_ops = fuse_ops; + EXPECT_EQ(0, Init(args)); + } +}; + +// Implementation of a simple flat memory filesystem. +struct File { + std::string name; + std::vector<uint8_t> data; +}; + +typedef std::vector<File> Files; +Files g_files; + +bool IsValidPath(const char* path) { + if (path == NULL) + return false; + + if (strlen(path) <= 1) + return false; + + if (path[0] != '/') + return false; + + return true; +} + +File* FindFile(const char* path) { + if (!IsValidPath(path)) + return NULL; + + for (Files::iterator iter = g_files.begin(); iter != g_files.end(); ++iter) { + if (iter->name == &path[1]) + return &*iter; + } + + return NULL; +} + +int testfs_getattr(const char* path, struct stat* stbuf) { + memset(stbuf, 0, sizeof(struct stat)); + + if (strcmp(path, "/") == 0) { + stbuf->st_mode = S_IFDIR | 0755; + return 0; + } + + File* file = FindFile(path); + if (file == NULL) + return -ENOENT; + + stbuf->st_mode = S_IFREG | 0666; + stbuf->st_size = file->data.size(); + return 0; +} + +int testfs_readdir(const char* path, void* buf, fuse_fill_dir_t filler, + off_t offset, struct fuse_file_info*) { + if (strcmp(path, "/") != 0) + return -ENOENT; + + filler(buf, ".", NULL, 0); + filler(buf, "..", NULL, 0); + for (Files::iterator iter = g_files.begin(); iter != g_files.end(); ++iter) { + filler(buf, iter->name.c_str(), NULL, 0); + } + return 0; +} + +int testfs_create(const char* path, mode_t, struct fuse_file_info* fi) { + if (!IsValidPath(path)) + return -ENOENT; + + File* file = FindFile(path); + if (file != NULL) { + if (fi->flags & O_EXCL) + return -EEXIST; + } else { + g_files.push_back(File()); + file = &g_files.back(); + file->name = &path[1]; // Skip initial / + } + + return 0; +} + +int testfs_open(const char* path, struct fuse_file_info*) { + // open is only called to open an existing file, otherwise create is + // called. We don't need to do any additional work here, the path will be + // passed to any other operations. + return FindFile(path) != NULL; +} + +int testfs_read(const char* path, char* buf, size_t size, off_t offset, + struct fuse_file_info* fi) { + File* file = FindFile(path); + if (file == NULL) + return -ENOENT; + + size_t filesize = file->data.size(); + // Trying to read past the end of the file. + if (offset >= filesize) + return 0; + + if (offset + size > filesize) + size = filesize - offset; + + memcpy(buf, file->data.data() + offset, size); + return size; +} + +int testfs_write(const char* path, const char* buf, size_t size, + off_t offset, struct fuse_file_info*) { + File* file = FindFile(path); + if (file == NULL) + return -ENOENT; + + size_t filesize = file->data.size(); + + if (offset + size > filesize) + file->data.resize(offset + size); + + memcpy(file->data.data() + offset, buf, size); + return size; +} + +const char hello_world[] = "Hello, World!\n"; + +fuse_operations g_fuse_operations = { + 0, // flag_nopath + 0, // flag_reserved + NULL, // init + NULL, // destroy + NULL, // access + testfs_create, // create + NULL, // fgetattr + NULL, // fsync + NULL, // ftruncate + testfs_getattr, // getattr + NULL, // mkdir + NULL, // mknod + testfs_open, // open + NULL, // opendir + testfs_read, // read + testfs_readdir, // readdir + NULL, // release + NULL, // releasedir + NULL, // rename + NULL, // rmdir + NULL, // truncate + NULL, // unlink + testfs_write, // write +}; + +class MountFuseTest : public ::testing::Test { + public: + MountFuseTest(); + + void SetUp(); + + protected: + MountFuseForTesting mnt_; +}; + +MountFuseTest::MountFuseTest() : mnt_(&g_fuse_operations) {} + +void MountFuseTest::SetUp() { + // Reset the filesystem. + g_files.clear(); + + // Add a built-in file. + size_t hello_len = strlen(hello_world); + + File hello; + hello.name = "hello"; + hello.data.resize(hello_len); + memcpy(hello.data.data(), hello_world, hello_len); + g_files.push_back(hello); +} + +} // namespace + +TEST_F(MountFuseTest, OpenAndRead) { + ScopedMountNode node; + ASSERT_EQ(0, mnt_.Open(Path("/hello"), O_RDONLY, &node)); + + char buffer[15] = {0}; + int bytes_read = 0; + HandleAttr attr; + ASSERT_EQ(0, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read)); + // FUSE always fills the buffer (padding with \0) unless in direct_io mode. + ASSERT_EQ(sizeof(buffer), bytes_read); + ASSERT_STREQ(hello_world, buffer); +} + +TEST_F(MountFuseTest, CreateAndWrite) { + ScopedMountNode node; + ASSERT_EQ(0, mnt_.Open(Path("/foobar"), O_RDWR | O_CREAT, &node)); + + HandleAttr attr; + const char message[] = "Something interesting"; + int bytes_written; + ASSERT_EQ(0, node->Write(attr, &message[0], strlen(message), &bytes_written)); + ASSERT_EQ(bytes_written, strlen(message)); + + // Now try to read the data back. + char buffer[40] = {0}; + int bytes_read = 0; + ASSERT_EQ(0, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read)); + // FUSE always fills the buffer (padding with \0) unless in direct_io mode. + ASSERT_EQ(sizeof(buffer), bytes_read); + ASSERT_STREQ(message, buffer); +} + +TEST_F(MountFuseTest, GetStat) { + struct stat statbuf; + ScopedMountNode node; + + ASSERT_EQ(0, mnt_.Open(Path("/hello"), O_RDONLY, &node)); + EXPECT_EQ(0, node->GetStat(&statbuf)); + EXPECT_EQ(S_IFREG, statbuf.st_mode & S_IFMT); + EXPECT_EQ(0666, statbuf.st_mode & ~S_IFMT); + EXPECT_EQ(strlen(hello_world), statbuf.st_size); + + ASSERT_EQ(0, mnt_.Open(Path("/"), O_RDONLY, &node)); + EXPECT_EQ(0, node->GetStat(&statbuf)); + EXPECT_EQ(S_IFDIR, statbuf.st_mode & S_IFMT); + EXPECT_EQ(0755, statbuf.st_mode & ~S_IFMT); + + // Create a file and stat. + ASSERT_EQ(0, mnt_.Open(Path("/foobar"), O_RDWR | O_CREAT, &node)); + EXPECT_EQ(0, node->GetStat(&statbuf)); + EXPECT_EQ(S_IFREG, statbuf.st_mode & S_IFMT); + EXPECT_EQ(0666, statbuf.st_mode & ~S_IFMT); + EXPECT_EQ(0, statbuf.st_size); +} + +TEST_F(MountFuseTest, GetDents) { + ScopedMountNode root; + + ASSERT_EQ(0, mnt_.Open(Path("/"), O_RDONLY, &root)); + + struct dirent entries[4]; + int bytes_read; + + // Try reading everything. + ASSERT_EQ(0, root->GetDents(0, &entries[0], sizeof(entries), &bytes_read)); + ASSERT_EQ(3 * sizeof(dirent), bytes_read); + EXPECT_STREQ(".", entries[0].d_name); + EXPECT_STREQ("..", entries[1].d_name); + EXPECT_STREQ("hello", entries[2].d_name); + + // Try reading from an offset. + memset(&entries, 0, sizeof(entries)); + ASSERT_EQ(0, root->GetDents(sizeof(dirent), &entries[0], 2 * sizeof(dirent), + &bytes_read)); + ASSERT_EQ(2 * sizeof(dirent), bytes_read); + EXPECT_STREQ("..", entries[0].d_name); + EXPECT_STREQ("hello", entries[1].d_name); + + // Add a file and read again. + ScopedMountNode node; + ASSERT_EQ(0, mnt_.Open(Path("/foobar"), O_RDWR | O_CREAT, &node)); + ASSERT_EQ(0, root->GetDents(0, &entries[0], sizeof(entries), &bytes_read)); + ASSERT_EQ(4 * sizeof(dirent), bytes_read); + EXPECT_STREQ(".", entries[0].d_name); + EXPECT_STREQ("..", entries[1].d_name); + EXPECT_STREQ("hello", entries[2].d_name); + EXPECT_STREQ("foobar", entries[3].d_name); +} + +namespace { + +class KernelProxyFuseTest : public ::testing::Test { + public: + KernelProxyFuseTest() {} + + void SetUp(); + void TearDown(); + + private: + KernelProxy kp_; +}; + +void KernelProxyFuseTest::SetUp() { + ki_init(&kp_); + + // Register a fuse filesystem. + ki_register_mount_type("flatfs", &g_fuse_operations); + + // Unmount the passthrough FS and mount our fuse filesystem. + EXPECT_EQ(0, kp_.umount("/")); + EXPECT_EQ(0, kp_.mount("", "/", "flatfs", 0, NULL)); +} + +void KernelProxyFuseTest::TearDown() { + ki_unregister_mount_type("flatfs"); + ki_uninit(); +} + +} // namespace + +TEST_F(KernelProxyFuseTest, Basic) { + // Write a file. + int fd = ki_open("/hello", O_WRONLY | O_CREAT); + ASSERT_GT(fd, -1); + ASSERT_EQ(sizeof(hello_world), + ki_write(fd, hello_world, sizeof(hello_world))); + EXPECT_EQ(0, ki_close(fd)); + + // Then read it back in. + fd = ki_open("/hello", O_RDONLY); + ASSERT_GT(fd, -1); + + char buffer[30]; + memset(buffer, 0, sizeof(buffer)); + ASSERT_EQ(sizeof(buffer), ki_read(fd, buffer, sizeof(buffer))); + EXPECT_STREQ(hello_world, buffer); + EXPECT_EQ(0, ki_close(fd)); +} diff --git a/native_client_sdk/src/tests/nacl_io_test/mount_html5fs_test.cc b/native_client_sdk/src/tests/nacl_io_test/mount_html5fs_test.cc index 3f1bd11..b770892 100644 --- a/native_client_sdk/src/tests/nacl_io_test/mount_html5fs_test.cc +++ b/native_client_sdk/src/tests/nacl_io_test/mount_html5fs_test.cc @@ -40,8 +40,11 @@ namespace { class MountHtml5FsForTesting : public MountHtml5Fs { public: - MountHtml5FsForTesting(StringMap_t& args, PepperInterface* ppapi) { - Error error = Init(1, args, ppapi); + MountHtml5FsForTesting(StringMap_t& string_map, PepperInterface* ppapi) { + MountInitArgs args; + args.string_map = string_map; + args.ppapi = ppapi; + Error error = Init(args); EXPECT_EQ(0, error); } }; diff --git a/native_client_sdk/src/tests/nacl_io_test/mount_http_test.cc b/native_client_sdk/src/tests/nacl_io_test/mount_http_test.cc index 735a17b..a679bbc 100644 --- a/native_client_sdk/src/tests/nacl_io_test/mount_http_test.cc +++ b/native_client_sdk/src/tests/nacl_io_test/mount_http_test.cc @@ -26,7 +26,10 @@ namespace { class MountHttpForTesting : public MountHttp { public: MountHttpForTesting(StringMap_t map, PepperInterface* ppapi) { - EXPECT_EQ(0, Init(1, map, ppapi)); + MountInitArgs args(1); + args.string_map = map; + args.ppapi = ppapi; + EXPECT_EQ(0, Init(args)); } using MountHttp::GetNodeCacheForTesting; diff --git a/native_client_sdk/src/tests/nacl_io_test/mount_mock.h b/native_client_sdk/src/tests/nacl_io_test/mount_mock.h index b0e5700..cde3bbb 100644 --- a/native_client_sdk/src/tests/nacl_io_test/mount_mock.h +++ b/native_client_sdk/src/tests/nacl_io_test/mount_mock.h @@ -12,6 +12,7 @@ class MountMock : public nacl_io::Mount { public: typedef nacl_io::Error Error; + typedef nacl_io::MountInitArgs MountInitArgs; typedef nacl_io::Path Path; typedef nacl_io::PepperInterface PepperInterface; typedef nacl_io::ScopedMountNode ScopedMountNode; @@ -20,7 +21,7 @@ class MountMock : public nacl_io::Mount { MountMock(); virtual ~MountMock(); - MOCK_METHOD3(Init, Error(int, StringMap_t&, PepperInterface*)); + MOCK_METHOD1(Init, Error(const MountInitArgs&)); MOCK_METHOD0(Destroy, void()); MOCK_METHOD2(Access, Error(const Path&, int)); MOCK_METHOD3(Open, Error(const Path&, int, ScopedMountNode*)); diff --git a/native_client_sdk/src/tests/nacl_io_test/mount_node_test.cc b/native_client_sdk/src/tests/nacl_io_test/mount_node_test.cc index 751e9fe..efa9c80 100644 --- a/native_client_sdk/src/tests/nacl_io_test/mount_node_test.cc +++ b/native_client_sdk/src/tests/nacl_io_test/mount_node_test.cc @@ -30,8 +30,8 @@ static int s_AllocNum = 0; class MockMount : public MountMem { public: MockMount() { - StringMap_t map; - EXPECT_EQ(0, Init(1, map, NULL)); + MountInitArgs args(1); + EXPECT_EQ(0, Init(args)); } int num_nodes() { return inode_pool_.size(); } diff --git a/native_client_sdk/src/tests/nacl_io_test/mount_test.cc b/native_client_sdk/src/tests/nacl_io_test/mount_test.cc index ed6d674..bbea69e 100644 --- a/native_client_sdk/src/tests/nacl_io_test/mount_test.cc +++ b/native_client_sdk/src/tests/nacl_io_test/mount_test.cc @@ -24,8 +24,8 @@ namespace { class MountMemMock : public MountMem { public: MountMemMock() { - StringMap_t map; - EXPECT_EQ(0, Init(1, map, NULL)); + MountInitArgs args(1); + EXPECT_EQ(0, Init(args)); } int num_nodes() { return (int) inode_pool_.size(); } |