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.
5 #include "nacl_io/fusefs/fuse_fs.h"
13 #include "nacl_io/getdents_helper.h"
14 #include "nacl_io/kernel_handle.h"
15 #include "nacl_io/log.h"
16 #include "sdk_util/macros.h"
23 FillDirInfo(GetDentsHelper* getdents, size_t num_bytes)
24 : getdents(getdents), num_bytes(num_bytes), wrote_offset(false) {}
26 GetDentsHelper* getdents;
33 FuseFs::FuseFs() : fuse_ops_(NULL), fuse_user_data_(NULL) {
36 Error FuseFs::Init(const FsInitArgs& args) {
37 Error error = Filesystem::Init(args);
41 fuse_ops_ = args.fuse_ops;
42 if (fuse_ops_ == NULL) {
43 LOG_ERROR("fuse_ops_ is NULL.");
47 if (fuse_ops_->init) {
48 struct fuse_conn_info info;
49 fuse_user_data_ = fuse_ops_->init(&info);
55 void FuseFs::Destroy() {
56 if (fuse_ops_ && fuse_ops_->destroy)
57 fuse_ops_->destroy(fuse_user_data_);
60 Error FuseFs::OpenWithMode(const Path& path, int open_flags, mode_t mode,
61 ScopedNode* out_node) {
62 std::string path_str = path.Join();
63 const char* path_cstr = path_str.c_str();
66 struct fuse_file_info fi;
67 memset(&fi, 0, sizeof(fi));
68 fi.flags = open_flags;
70 if (open_flags & (O_CREAT | O_EXCL)) {
71 // According to the FUSE docs, open() is not called when O_CREAT or O_EXCL
73 if (fuse_ops_->create) {
74 result = fuse_ops_->create(path_cstr, mode, &fi);
77 } else if (fuse_ops_->mknod) {
78 result = fuse_ops_->mknod(path_cstr, mode, dev_);
82 LOG_TRACE("fuse_ops_->create and fuse_ops_->mknod are NULL.");
86 // First determine if this is a regular file or a directory.
87 if (fuse_ops_->getattr) {
89 result = fuse_ops_->getattr(path_cstr, &statbuf);
93 if ((statbuf.st_mode & S_IFMT) == S_IFDIR) {
94 // This is a directory. Don't try to open, just create a new node with
96 ScopedNode node(new DirFuseFsNode(this, fuse_ops_, fi, path_cstr));
97 Error error = node->Init(open_flags);
105 mode = statbuf.st_mode & ~S_IFMT;
109 if (open_flags & O_TRUNC) {
110 // According to the FUSE docs, O_TRUNC does two calls: first truncate()
112 if (!fuse_ops_->truncate) {
113 LOG_TRACE("fuse_ops_->truncate is NULL.");
116 result = fuse_ops_->truncate(path_cstr, 0);
121 if (!fuse_ops_->open) {
122 LOG_TRACE("fuse_ops_->open is NULL.");
125 result = fuse_ops_->open(path_cstr, &fi);
130 ScopedNode node(new FileFuseFsNode(this, fuse_ops_, fi, path_cstr));
131 Error error = node->Init(open_flags);
140 Error FuseFs::Unlink(const Path& path) {
141 if (!fuse_ops_->unlink) {
142 LOG_TRACE("fuse_ops_->unlink is NULL.");
146 int result = fuse_ops_->unlink(path.Join().c_str());
153 Error FuseFs::Mkdir(const Path& path, int perm) {
154 if (!fuse_ops_->mkdir) {
155 LOG_TRACE("fuse_ops_->mkdir is NULL.");
159 int result = fuse_ops_->mkdir(path.Join().c_str(), perm);
166 Error FuseFs::Rmdir(const Path& path) {
167 if (!fuse_ops_->rmdir) {
168 LOG_TRACE("fuse_ops_->rmdir is NULL.");
172 int result = fuse_ops_->rmdir(path.Join().c_str());
179 Error FuseFs::Remove(const Path& path) {
181 Error error = Open(path, O_RDONLY, &node);
186 error = node->GetStat(&statbuf);
192 if ((statbuf.st_mode & S_IFMT) == S_IFDIR) {
199 Error FuseFs::Rename(const Path& path, const Path& newpath) {
200 if (!fuse_ops_->rename) {
201 LOG_TRACE("fuse_ops_->rename is NULL.");
205 int result = fuse_ops_->rename(path.Join().c_str(), newpath.Join().c_str());
212 FuseFsNode::FuseFsNode(Filesystem* filesystem,
213 struct fuse_operations* fuse_ops,
214 struct fuse_file_info& info,
215 const std::string& path)
216 : Node(filesystem), fuse_ops_(fuse_ops), info_(info), path_(path) {
219 bool FuseFsNode::CanOpen(int open_flags) {
221 Error error = GetStat(&statbuf);
225 // GetStat cached the mode in stat_.st_mode. Forward to Node::CanOpen,
226 // which will check this mode against open_flags.
227 return Node::CanOpen(open_flags);
230 Error FuseFsNode::GetStat(struct stat* stat) {
232 if (fuse_ops_->fgetattr) {
233 result = fuse_ops_->fgetattr(path_.c_str(), stat, &info_);
236 } else if (fuse_ops_->getattr) {
237 result = fuse_ops_->getattr(path_.c_str(), stat);
241 LOG_TRACE("fuse_ops_->fgetattr and fuse_ops_->getattr are NULL.");
245 // Also update the cached stat values.
250 Error FuseFsNode::Futimens(const struct timespec times[2]) {
252 if (!fuse_ops_->utimens) {
253 LOG_TRACE("fuse_ops_->utimens is NULL.");
257 result = fuse_ops_->utimens(path_.c_str(), times);
264 Error FuseFsNode::Fchmod(mode_t mode) {
266 if (!fuse_ops_->chmod) {
267 LOG_TRACE("fuse_ops_->chmod is NULL.");
271 result = fuse_ops_->chmod(path_.c_str(), mode);
278 Error FuseFsNode::VIoctl(int request, va_list args) {
279 LOG_ERROR("Ioctl not implemented for fusefs.");
283 Error FuseFsNode::Tcflush(int queue_selector) {
284 LOG_ERROR("Tcflush not implemented for fusefs.");
288 Error FuseFsNode::Tcgetattr(struct termios* termios_p) {
289 LOG_ERROR("Tcgetattr not implemented for fusefs.");
293 Error FuseFsNode::Tcsetattr(int optional_actions,
294 const struct termios* termios_p) {
295 LOG_ERROR("Tcsetattr not implemented for fusefs.");
299 Error FuseFsNode::GetSize(off_t* out_size) {
301 Error error = GetStat(&statbuf);
305 *out_size = stat_.st_size;
309 FileFuseFsNode::FileFuseFsNode(Filesystem* filesystem,
310 struct fuse_operations* fuse_ops,
311 struct fuse_file_info& info,
312 const std::string& path)
313 : FuseFsNode(filesystem, fuse_ops, info, path) {
316 void FileFuseFsNode::Destroy() {
317 if (!fuse_ops_->release)
319 fuse_ops_->release(path_.c_str(), &info_);
322 Error FileFuseFsNode::FSync() {
323 if (!fuse_ops_->fsync) {
324 LOG_ERROR("fuse_ops_->fsync is NULL.");
329 int result = fuse_ops_->fsync(path_.c_str(), datasync, &info_);
335 Error FileFuseFsNode::FTruncate(off_t length) {
336 if (!fuse_ops_->ftruncate) {
337 LOG_ERROR("fuse_ops_->ftruncate is NULL.");
341 int result = fuse_ops_->ftruncate(path_.c_str(), length, &info_);
347 Error FileFuseFsNode::Read(const HandleAttr& attr,
351 if (!fuse_ops_->read) {
352 LOG_ERROR("fuse_ops_->read is NULL.");
356 char* cbuf = static_cast<char*>(buf);
358 int result = fuse_ops_->read(path_.c_str(), cbuf, count, attr.offs, &info_);
362 // TODO(binji): support the direct_io flag
363 if (static_cast<size_t>(result) < count)
364 memset(&cbuf[result], 0, count - result);
370 Error FileFuseFsNode::Write(const HandleAttr& attr,
374 if (!fuse_ops_->write) {
375 LOG_ERROR("fuse_ops_->write is NULL.");
379 int result = fuse_ops_->write(
380 path_.c_str(), static_cast<const char*>(buf), count, attr.offs, &info_);
384 // TODO(binji): support the direct_io flag
389 DirFuseFsNode::DirFuseFsNode(Filesystem* filesystem,
390 struct fuse_operations* fuse_ops,
391 struct fuse_file_info& info,
392 const std::string& path)
393 : FuseFsNode(filesystem, fuse_ops, info, path) {
396 void DirFuseFsNode::Destroy() {
397 if (!fuse_ops_->releasedir)
399 fuse_ops_->releasedir(path_.c_str(), &info_);
402 Error DirFuseFsNode::FSync() {
403 if (!fuse_ops_->fsyncdir) {
404 LOG_ERROR("fuse_ops_->fsyncdir is NULL.");
409 int result = fuse_ops_->fsyncdir(path_.c_str(), datasync, &info_);
415 Error DirFuseFsNode::GetDents(size_t offs,
419 if (!fuse_ops_->readdir) {
420 LOG_ERROR("fuse_ops_->readdir is NULL.");
424 bool opened_dir = false;
427 // Opendir is not necessary, only readdir. Call it anyway, if it is defined.
428 if (fuse_ops_->opendir) {
429 result = fuse_ops_->opendir(path_.c_str(), &info_);
437 GetDentsHelper getdents;
438 FillDirInfo fill_info(&getdents, count);
439 result = fuse_ops_->readdir(
440 path_.c_str(), &fill_info, &DirFuseFsNode::FillDirCallback, offs, &info_);
444 // If the fill function ever wrote an entry with |offs| != 0, then assume it
445 // was not given the full list of entries. In that case, GetDentsHelper's
446 // buffers start with the entry at offset |offs|, so the call to
447 // GetDentsHelpers::GetDents should use an offset of 0.
448 if (fill_info.wrote_offset)
451 // The entries have been filled in from the FUSE filesystem, now write them
452 // out to the buffer.
453 error = getdents.GetDents(offs, pdir, count, out_bytes);
460 if (opened_dir && fuse_ops_->releasedir) {
461 // Ignore this result, we're already failing.
462 fuse_ops_->releasedir(path_.c_str(), &info_);
468 int DirFuseFsNode::FillDirCallback(void* buf,
470 const struct stat* stbuf,
472 FillDirInfo* fill_info = static_cast<FillDirInfo*>(buf);
474 // It is OK for the FUSE filesystem to pass a NULL stbuf. In that case, just
476 ino_t ino = stbuf ? stbuf->st_ino : 1;
478 // The FUSE docs say that the implementor of readdir can choose to ignore the
479 // offset given, and instead return all entries. To do this, they pass
480 // |off| == 0 for each call.
482 if (fill_info->num_bytes < sizeof(dirent))
483 return 1; // 1 => buffer is full
485 fill_info->wrote_offset = true;
486 fill_info->getdents->AddDirent(ino, name, strlen(name));
487 fill_info->num_bytes -= sizeof(dirent);
488 // return 0 => request more data. return 1 => buffer full.
489 return fill_info->num_bytes > 0 ? 0 : 1;
491 fill_info->getdents->AddDirent(ino, name, strlen(name));
492 fill_info->num_bytes -= sizeof(dirent);
493 // According to the docs, we can never return 1 (buffer full) when the
494 // offset is zero (the user is probably ignoring the result anyway).
499 } // namespace nacl_io