Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / native_client_sdk / src / libraries / nacl_io / fusefs / fuse_fs.cc
blobf19571803c6297465c4a4a37adf5a5c36fd06362
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"
7 #include <errno.h>
8 #include <fcntl.h>
9 #include <string.h>
11 #include <algorithm>
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"
18 namespace nacl_io {
20 namespace {
22 struct FillDirInfo {
23 FillDirInfo(GetDentsHelper* getdents, size_t num_bytes)
24 : getdents(getdents), num_bytes(num_bytes), wrote_offset(false) {}
26 GetDentsHelper* getdents;
27 size_t num_bytes;
28 bool wrote_offset;
31 } // namespace
33 FuseFs::FuseFs() : fuse_ops_(NULL), fuse_user_data_(NULL) {
36 Error FuseFs::Init(const FsInitArgs& args) {
37 Error error = Filesystem::Init(args);
38 if (error)
39 return error;
41 fuse_ops_ = args.fuse_ops;
42 if (fuse_ops_ == NULL) {
43 LOG_ERROR("fuse_ops_ is NULL.");
44 return EINVAL;
47 if (fuse_ops_->init) {
48 struct fuse_conn_info info;
49 fuse_user_data_ = fuse_ops_->init(&info);
52 return 0;
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();
64 int result = 0;
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
72 // is passed.
73 if (fuse_ops_->create) {
74 result = fuse_ops_->create(path_cstr, mode, &fi);
75 if (result < 0)
76 return -result;
77 } else if (fuse_ops_->mknod) {
78 result = fuse_ops_->mknod(path_cstr, mode, dev_);
79 if (result < 0)
80 return -result;
81 } else {
82 LOG_TRACE("fuse_ops_->create and fuse_ops_->mknod are NULL.");
83 return ENOSYS;
85 } else {
86 // First determine if this is a regular file or a directory.
87 if (fuse_ops_->getattr) {
88 struct stat statbuf;
89 result = fuse_ops_->getattr(path_cstr, &statbuf);
90 if (result < 0)
91 return -result;
93 if (S_ISDIR(statbuf.st_mode)) {
94 // This is a directory. Don't try to open, just create a new node with
95 // this path.
96 ScopedNode node(new DirFuseFsNode(this, fuse_ops_, fi, path_cstr));
97 Error error = node->Init(open_flags);
98 if (error)
99 return error;
100 node->stat_ = statbuf;
102 *out_node = node;
103 return 0;
105 // Get mode.
106 mode = statbuf.st_mode & S_MODEBITS;
109 // Existing file.
110 if (open_flags & O_TRUNC) {
111 // According to the FUSE docs, O_TRUNC does two calls: first truncate()
112 // then open().
113 if (!fuse_ops_->truncate) {
114 LOG_TRACE("fuse_ops_->truncate is NULL.");
115 return ENOSYS;
117 result = fuse_ops_->truncate(path_cstr, 0);
118 if (result < 0)
119 return -result;
122 if (!fuse_ops_->open) {
123 LOG_TRACE("fuse_ops_->open is NULL.");
124 return ENOSYS;
126 result = fuse_ops_->open(path_cstr, &fi);
127 if (result < 0)
128 return -result;
131 ScopedNode node(new FileFuseFsNode(this, fuse_ops_, fi, path_cstr));
132 Error error = node->Init(open_flags);
133 if (error)
134 return error;
135 node->SetMode(mode);
137 *out_node = node;
138 return 0;
141 Error FuseFs::Unlink(const Path& path) {
142 if (!fuse_ops_->unlink) {
143 LOG_TRACE("fuse_ops_->unlink is NULL.");
144 return ENOSYS;
147 int result = fuse_ops_->unlink(path.Join().c_str());
148 if (result < 0)
149 return -result;
151 return 0;
154 Error FuseFs::Mkdir(const Path& path, int perm) {
155 if (!fuse_ops_->mkdir) {
156 LOG_TRACE("fuse_ops_->mkdir is NULL.");
157 return ENOSYS;
160 int result = fuse_ops_->mkdir(path.Join().c_str(), perm);
161 if (result < 0)
162 return -result;
164 return 0;
167 Error FuseFs::Rmdir(const Path& path) {
168 if (!fuse_ops_->rmdir) {
169 LOG_TRACE("fuse_ops_->rmdir is NULL.");
170 return ENOSYS;
173 int result = fuse_ops_->rmdir(path.Join().c_str());
174 if (result < 0)
175 return -result;
177 return 0;
180 Error FuseFs::Remove(const Path& path) {
181 ScopedNode node;
182 Error error = Open(path, O_RDONLY, &node);
183 if (error)
184 return error;
186 struct stat statbuf;
187 error = node->GetStat(&statbuf);
188 if (error)
189 return error;
191 node.reset();
193 if (S_ISDIR(statbuf.st_mode)) {
194 return Rmdir(path);
195 } else {
196 return Unlink(path);
200 Error FuseFs::Rename(const Path& path, const Path& newpath) {
201 if (!fuse_ops_->rename) {
202 LOG_TRACE("fuse_ops_->rename is NULL.");
203 return ENOSYS;
206 int result = fuse_ops_->rename(path.Join().c_str(), newpath.Join().c_str());
207 if (result < 0)
208 return -result;
210 return 0;
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) {
221 struct stat statbuf;
222 Error error = GetStat(&statbuf);
223 if (error)
224 return false;
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) {
232 int result;
233 if (fuse_ops_->fgetattr) {
234 result = fuse_ops_->fgetattr(path_.c_str(), stat, &info_);
235 if (result < 0)
236 return -result;
237 } else if (fuse_ops_->getattr) {
238 result = fuse_ops_->getattr(path_.c_str(), stat);
239 if (result < 0)
240 return -result;
241 } else {
242 LOG_TRACE("fuse_ops_->fgetattr and fuse_ops_->getattr are NULL.");
243 return ENOSYS;
246 // Also update the cached stat values.
247 stat_ = *stat;
248 return 0;
251 Error FuseFsNode::Futimens(const struct timespec times[2]) {
252 int result;
253 if (!fuse_ops_->utimens) {
254 LOG_TRACE("fuse_ops_->utimens is NULL.");
255 return ENOSYS;
258 result = fuse_ops_->utimens(path_.c_str(), times);
259 if (result < 0)
260 return -result;
262 return result;
265 Error FuseFsNode::Fchmod(mode_t mode) {
266 int result;
267 if (!fuse_ops_->chmod) {
268 LOG_TRACE("fuse_ops_->chmod is NULL.");
269 return ENOSYS;
272 result = fuse_ops_->chmod(path_.c_str(), mode);
273 if (result < 0)
274 return -result;
276 return result;
279 Error FuseFsNode::VIoctl(int request, va_list args) {
280 LOG_ERROR("Ioctl not implemented for fusefs.");
281 return ENOSYS;
284 Error FuseFsNode::Tcflush(int queue_selector) {
285 LOG_ERROR("Tcflush not implemented for fusefs.");
286 return ENOSYS;
289 Error FuseFsNode::Tcgetattr(struct termios* termios_p) {
290 LOG_ERROR("Tcgetattr not implemented for fusefs.");
291 return ENOSYS;
294 Error FuseFsNode::Tcsetattr(int optional_actions,
295 const struct termios* termios_p) {
296 LOG_ERROR("Tcsetattr not implemented for fusefs.");
297 return ENOSYS;
300 Error FuseFsNode::GetSize(off_t* out_size) {
301 struct stat statbuf;
302 Error error = GetStat(&statbuf);
303 if (error)
304 return error;
306 *out_size = stat_.st_size;
307 return 0;
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)
319 return;
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.");
326 return ENOSYS;
329 int datasync = 0;
330 int result = fuse_ops_->fsync(path_.c_str(), datasync, &info_);
331 if (result < 0)
332 return -result;
333 return 0;
336 Error FileFuseFsNode::FTruncate(off_t length) {
337 if (!fuse_ops_->ftruncate) {
338 LOG_ERROR("fuse_ops_->ftruncate is NULL.");
339 return ENOSYS;
342 int result = fuse_ops_->ftruncate(path_.c_str(), length, &info_);
343 if (result < 0)
344 return -result;
345 return 0;
348 Error FileFuseFsNode::Read(const HandleAttr& attr,
349 void* buf,
350 size_t count,
351 int* out_bytes) {
352 if (!fuse_ops_->read) {
353 LOG_ERROR("fuse_ops_->read is NULL.");
354 return ENOSYS;
357 char* cbuf = static_cast<char*>(buf);
359 int result = fuse_ops_->read(path_.c_str(), cbuf, count, attr.offs, &info_);
360 if (result < 0)
361 return -result;
363 // TODO(binji): support the direct_io flag
364 if (static_cast<size_t>(result) < count)
365 memset(&cbuf[result], 0, count - result);
367 *out_bytes = result;
368 return 0;
371 Error FileFuseFsNode::Write(const HandleAttr& attr,
372 const void* buf,
373 size_t count,
374 int* out_bytes) {
375 if (!fuse_ops_->write) {
376 LOG_ERROR("fuse_ops_->write is NULL.");
377 return ENOSYS;
380 int result = fuse_ops_->write(
381 path_.c_str(), static_cast<const char*>(buf), count, attr.offs, &info_);
382 if (result < 0)
383 return -result;
385 // TODO(binji): support the direct_io flag
386 *out_bytes = result;
387 return 0;
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)
399 return;
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.");
406 return ENOSYS;
409 int datasync = 0;
410 int result = fuse_ops_->fsyncdir(path_.c_str(), datasync, &info_);
411 if (result < 0)
412 return -result;
413 return 0;
416 Error DirFuseFsNode::GetDents(size_t offs,
417 struct dirent* pdir,
418 size_t count,
419 int* out_bytes) {
420 if (!fuse_ops_->readdir) {
421 LOG_ERROR("fuse_ops_->readdir is NULL.");
422 return ENOSYS;
425 bool opened_dir = false;
426 int result;
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_);
431 if (result < 0)
432 return -result;
434 opened_dir = true;
437 Error error = 0;
438 GetDentsHelper getdents;
439 FillDirInfo fill_info(&getdents, count);
440 result = fuse_ops_->readdir(
441 path_.c_str(), &fill_info, &DirFuseFsNode::FillDirCallback, offs, &info_);
442 if (result < 0)
443 goto fail;
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)
450 offs = 0;
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);
455 if (error)
456 goto fail;
458 return 0;
460 fail:
461 if (opened_dir && fuse_ops_->releasedir) {
462 // Ignore this result, we're already failing.
463 fuse_ops_->releasedir(path_.c_str(), &info_);
466 return -result;
469 int DirFuseFsNode::FillDirCallback(void* buf,
470 const char* name,
471 const struct stat* stbuf,
472 off_t off) {
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
476 // use a bogus ino.
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.
482 if (off) {
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;
491 } else {
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).
496 return 0;
500 } // namespace nacl_io