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 (S_ISDIR(statbuf
.st_mode
)) {
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
);
100 node
->stat_
= statbuf
;
106 mode
= statbuf
.st_mode
& S_MODEBITS
;
110 if (open_flags
& O_TRUNC
) {
111 // According to the FUSE docs, O_TRUNC does two calls: first truncate()
113 if (!fuse_ops_
->truncate
) {
114 LOG_TRACE("fuse_ops_->truncate is NULL.");
117 result
= fuse_ops_
->truncate(path_cstr
, 0);
122 if (!fuse_ops_
->open
) {
123 LOG_TRACE("fuse_ops_->open is NULL.");
126 result
= fuse_ops_
->open(path_cstr
, &fi
);
131 ScopedNode
node(new FileFuseFsNode(this, fuse_ops_
, fi
, path_cstr
));
132 Error error
= node
->Init(open_flags
);
141 Error
FuseFs::Unlink(const Path
& path
) {
142 if (!fuse_ops_
->unlink
) {
143 LOG_TRACE("fuse_ops_->unlink is NULL.");
147 int result
= fuse_ops_
->unlink(path
.Join().c_str());
154 Error
FuseFs::Mkdir(const Path
& path
, int perm
) {
155 if (!fuse_ops_
->mkdir
) {
156 LOG_TRACE("fuse_ops_->mkdir is NULL.");
160 int result
= fuse_ops_
->mkdir(path
.Join().c_str(), perm
);
167 Error
FuseFs::Rmdir(const Path
& path
) {
168 if (!fuse_ops_
->rmdir
) {
169 LOG_TRACE("fuse_ops_->rmdir is NULL.");
173 int result
= fuse_ops_
->rmdir(path
.Join().c_str());
180 Error
FuseFs::Remove(const Path
& path
) {
182 Error error
= Open(path
, O_RDONLY
, &node
);
187 error
= node
->GetStat(&statbuf
);
193 if (S_ISDIR(statbuf
.st_mode
)) {
200 Error
FuseFs::Rename(const Path
& path
, const Path
& newpath
) {
201 if (!fuse_ops_
->rename
) {
202 LOG_TRACE("fuse_ops_->rename is NULL.");
206 int result
= fuse_ops_
->rename(path
.Join().c_str(), newpath
.Join().c_str());
213 FuseFsNode::FuseFsNode(Filesystem
* filesystem
,
214 struct fuse_operations
* fuse_ops
,
215 struct fuse_file_info
& info
,
216 const std::string
& path
)
217 : Node(filesystem
), fuse_ops_(fuse_ops
), info_(info
), path_(path
) {
220 bool FuseFsNode::CanOpen(int open_flags
) {
222 Error error
= GetStat(&statbuf
);
226 // GetStat cached the mode in stat_.st_mode. Forward to Node::CanOpen,
227 // which will check this mode against open_flags.
228 return Node::CanOpen(open_flags
);
231 Error
FuseFsNode::GetStat(struct stat
* stat
) {
233 if (fuse_ops_
->fgetattr
) {
234 result
= fuse_ops_
->fgetattr(path_
.c_str(), stat
, &info_
);
237 } else if (fuse_ops_
->getattr
) {
238 result
= fuse_ops_
->getattr(path_
.c_str(), stat
);
242 LOG_TRACE("fuse_ops_->fgetattr and fuse_ops_->getattr are NULL.");
246 // Also update the cached stat values.
251 Error
FuseFsNode::Futimens(const struct timespec times
[2]) {
253 if (!fuse_ops_
->utimens
) {
254 LOG_TRACE("fuse_ops_->utimens is NULL.");
258 result
= fuse_ops_
->utimens(path_
.c_str(), times
);
265 Error
FuseFsNode::Fchmod(mode_t mode
) {
267 if (!fuse_ops_
->chmod
) {
268 LOG_TRACE("fuse_ops_->chmod is NULL.");
272 result
= fuse_ops_
->chmod(path_
.c_str(), mode
);
279 Error
FuseFsNode::VIoctl(int request
, va_list args
) {
280 LOG_ERROR("Ioctl not implemented for fusefs.");
284 Error
FuseFsNode::Tcflush(int queue_selector
) {
285 LOG_ERROR("Tcflush not implemented for fusefs.");
289 Error
FuseFsNode::Tcgetattr(struct termios
* termios_p
) {
290 LOG_ERROR("Tcgetattr not implemented for fusefs.");
294 Error
FuseFsNode::Tcsetattr(int optional_actions
,
295 const struct termios
* termios_p
) {
296 LOG_ERROR("Tcsetattr not implemented for fusefs.");
300 Error
FuseFsNode::GetSize(off_t
* out_size
) {
302 Error error
= GetStat(&statbuf
);
306 *out_size
= stat_
.st_size
;
310 FileFuseFsNode::FileFuseFsNode(Filesystem
* filesystem
,
311 struct fuse_operations
* fuse_ops
,
312 struct fuse_file_info
& info
,
313 const std::string
& path
)
314 : FuseFsNode(filesystem
, fuse_ops
, info
, path
) {
317 void FileFuseFsNode::Destroy() {
318 if (!fuse_ops_
->release
)
320 fuse_ops_
->release(path_
.c_str(), &info_
);
323 Error
FileFuseFsNode::FSync() {
324 if (!fuse_ops_
->fsync
) {
325 LOG_ERROR("fuse_ops_->fsync is NULL.");
330 int result
= fuse_ops_
->fsync(path_
.c_str(), datasync
, &info_
);
336 Error
FileFuseFsNode::FTruncate(off_t length
) {
337 if (!fuse_ops_
->ftruncate
) {
338 LOG_ERROR("fuse_ops_->ftruncate is NULL.");
342 int result
= fuse_ops_
->ftruncate(path_
.c_str(), length
, &info_
);
348 Error
FileFuseFsNode::Read(const HandleAttr
& attr
,
352 if (!fuse_ops_
->read
) {
353 LOG_ERROR("fuse_ops_->read is NULL.");
357 char* cbuf
= static_cast<char*>(buf
);
359 int result
= fuse_ops_
->read(path_
.c_str(), cbuf
, count
, attr
.offs
, &info_
);
363 // TODO(binji): support the direct_io flag
364 if (static_cast<size_t>(result
) < count
)
365 memset(&cbuf
[result
], 0, count
- result
);
371 Error
FileFuseFsNode::Write(const HandleAttr
& attr
,
375 if (!fuse_ops_
->write
) {
376 LOG_ERROR("fuse_ops_->write is NULL.");
380 int result
= fuse_ops_
->write(
381 path_
.c_str(), static_cast<const char*>(buf
), count
, attr
.offs
, &info_
);
385 // TODO(binji): support the direct_io flag
390 DirFuseFsNode::DirFuseFsNode(Filesystem
* filesystem
,
391 struct fuse_operations
* fuse_ops
,
392 struct fuse_file_info
& info
,
393 const std::string
& path
)
394 : FuseFsNode(filesystem
, fuse_ops
, info
, path
) {
397 void DirFuseFsNode::Destroy() {
398 if (!fuse_ops_
->releasedir
)
400 fuse_ops_
->releasedir(path_
.c_str(), &info_
);
403 Error
DirFuseFsNode::FSync() {
404 if (!fuse_ops_
->fsyncdir
) {
405 LOG_ERROR("fuse_ops_->fsyncdir is NULL.");
410 int result
= fuse_ops_
->fsyncdir(path_
.c_str(), datasync
, &info_
);
416 Error
DirFuseFsNode::GetDents(size_t offs
,
420 if (!fuse_ops_
->readdir
) {
421 LOG_ERROR("fuse_ops_->readdir is NULL.");
425 bool opened_dir
= false;
428 // Opendir is not necessary, only readdir. Call it anyway, if it is defined.
429 if (fuse_ops_
->opendir
) {
430 result
= fuse_ops_
->opendir(path_
.c_str(), &info_
);
438 GetDentsHelper getdents
;
439 FillDirInfo
fill_info(&getdents
, count
);
440 result
= fuse_ops_
->readdir(
441 path_
.c_str(), &fill_info
, &DirFuseFsNode::FillDirCallback
, offs
, &info_
);
445 // If the fill function ever wrote an entry with |offs| != 0, then assume it
446 // was not given the full list of entries. In that case, GetDentsHelper's
447 // buffers start with the entry at offset |offs|, so the call to
448 // GetDentsHelpers::GetDents should use an offset of 0.
449 if (fill_info
.wrote_offset
)
452 // The entries have been filled in from the FUSE filesystem, now write them
453 // out to the buffer.
454 error
= getdents
.GetDents(offs
, pdir
, count
, out_bytes
);
461 if (opened_dir
&& fuse_ops_
->releasedir
) {
462 // Ignore this result, we're already failing.
463 fuse_ops_
->releasedir(path_
.c_str(), &info_
);
469 int DirFuseFsNode::FillDirCallback(void* buf
,
471 const struct stat
* stbuf
,
473 FillDirInfo
* fill_info
= static_cast<FillDirInfo
*>(buf
);
475 // It is OK for the FUSE filesystem to pass a NULL stbuf. In that case, just
477 ino_t ino
= stbuf
? stbuf
->st_ino
: 1;
479 // The FUSE docs say that the implementor of readdir can choose to ignore the
480 // offset given, and instead return all entries. To do this, they pass
481 // |off| == 0 for each call.
483 if (fill_info
->num_bytes
< sizeof(dirent
))
484 return 1; // 1 => buffer is full
486 fill_info
->wrote_offset
= true;
487 fill_info
->getdents
->AddDirent(ino
, name
, strlen(name
));
488 fill_info
->num_bytes
-= sizeof(dirent
);
489 // return 0 => request more data. return 1 => buffer full.
490 return fill_info
->num_bytes
> 0 ? 0 : 1;
492 fill_info
->getdents
->AddDirent(ino
, name
, strlen(name
));
493 fill_info
->num_bytes
-= sizeof(dirent
);
494 // According to the docs, we can never return 1 (buffer full) when the
495 // offset is zero (the user is probably ignoring the result anyway).
500 } // namespace nacl_io