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
);
105 mode
= statbuf
.st_mode
& S_MODEBITS
;
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 (S_ISDIR(statbuf
.st_mode
)) {
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