fuse_fs.cc revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
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. 4 5#include "nacl_io/fusefs/fuse_fs.h" 6 7#include <errno.h> 8#include <fcntl.h> 9#include <string.h> 10 11#include <algorithm> 12 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" 17 18namespace nacl_io { 19 20namespace { 21 22struct FillDirInfo { 23 FillDirInfo(GetDentsHelper* getdents, size_t num_bytes) 24 : getdents(getdents), num_bytes(num_bytes), wrote_offset(false) {} 25 26 GetDentsHelper* getdents; 27 size_t num_bytes; 28 bool wrote_offset; 29}; 30 31} // namespace 32 33FuseFs::FuseFs() : fuse_ops_(NULL), fuse_user_data_(NULL) { 34} 35 36Error FuseFs::Init(const FsInitArgs& args) { 37 Error error = Filesystem::Init(args); 38 if (error) 39 return error; 40 41 fuse_ops_ = args.fuse_ops; 42 if (fuse_ops_ == NULL) { 43 LOG_ERROR("fuse_ops_ is NULL."); 44 return EINVAL; 45 } 46 47 if (fuse_ops_->init) { 48 struct fuse_conn_info info; 49 fuse_user_data_ = fuse_ops_->init(&info); 50 } 51 52 return 0; 53} 54 55void FuseFs::Destroy() { 56 if (fuse_ops_ && fuse_ops_->destroy) 57 fuse_ops_->destroy(fuse_user_data_); 58} 59 60Error 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; 65 66 struct fuse_file_info fi; 67 memset(&fi, 0, sizeof(fi)); 68 fi.flags = open_flags; 69 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; 84 } 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; 92 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 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 101 *out_node = node; 102 return 0; 103 } 104 // Get mode. 105 mode = statbuf.st_mode & ~S_IFMT; 106 } 107 108 // Existing file. 109 if (open_flags & O_TRUNC) { 110 // According to the FUSE docs, O_TRUNC does two calls: first truncate() 111 // then open(). 112 if (!fuse_ops_->truncate) { 113 LOG_TRACE("fuse_ops_->truncate is NULL."); 114 return ENOSYS; 115 } 116 result = fuse_ops_->truncate(path_cstr, 0); 117 if (result < 0) 118 return -result; 119 } 120 121 if (!fuse_ops_->open) { 122 LOG_TRACE("fuse_ops_->open is NULL."); 123 return ENOSYS; 124 } 125 result = fuse_ops_->open(path_cstr, &fi); 126 if (result < 0) 127 return -result; 128 } 129 130 ScopedNode node(new FileFuseFsNode(this, fuse_ops_, fi, path_cstr)); 131 Error error = node->Init(open_flags); 132 if (error) 133 return error; 134 node->SetMode(mode); 135 136 *out_node = node; 137 return 0; 138} 139 140Error FuseFs::Unlink(const Path& path) { 141 if (!fuse_ops_->unlink) { 142 LOG_TRACE("fuse_ops_->unlink is NULL."); 143 return ENOSYS; 144 } 145 146 int result = fuse_ops_->unlink(path.Join().c_str()); 147 if (result < 0) 148 return -result; 149 150 return 0; 151} 152 153Error FuseFs::Mkdir(const Path& path, int perm) { 154 if (!fuse_ops_->mkdir) { 155 LOG_TRACE("fuse_ops_->mkdir is NULL."); 156 return ENOSYS; 157 } 158 159 int result = fuse_ops_->mkdir(path.Join().c_str(), perm); 160 if (result < 0) 161 return -result; 162 163 return 0; 164} 165 166Error FuseFs::Rmdir(const Path& path) { 167 if (!fuse_ops_->rmdir) { 168 LOG_TRACE("fuse_ops_->rmdir is NULL."); 169 return ENOSYS; 170 } 171 172 int result = fuse_ops_->rmdir(path.Join().c_str()); 173 if (result < 0) 174 return -result; 175 176 return 0; 177} 178 179Error FuseFs::Remove(const Path& path) { 180 ScopedNode node; 181 Error error = Open(path, O_RDONLY, &node); 182 if (error) 183 return error; 184 185 struct stat statbuf; 186 error = node->GetStat(&statbuf); 187 if (error) 188 return error; 189 190 node.reset(); 191 192 if ((statbuf.st_mode & S_IFMT) == S_IFDIR) { 193 return Rmdir(path); 194 } else { 195 return Unlink(path); 196 } 197} 198 199Error FuseFs::Rename(const Path& path, const Path& newpath) { 200 if (!fuse_ops_->rename) { 201 LOG_TRACE("fuse_ops_->rename is NULL."); 202 return ENOSYS; 203 } 204 205 int result = fuse_ops_->rename(path.Join().c_str(), newpath.Join().c_str()); 206 if (result < 0) 207 return -result; 208 209 return 0; 210} 211 212FuseFsNode::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) { 217} 218 219bool FuseFsNode::CanOpen(int open_flags) { 220 struct stat statbuf; 221 Error error = GetStat(&statbuf); 222 if (error) 223 return false; 224 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); 228} 229 230Error FuseFsNode::GetStat(struct stat* stat) { 231 int result; 232 if (fuse_ops_->fgetattr) { 233 result = fuse_ops_->fgetattr(path_.c_str(), stat, &info_); 234 if (result < 0) 235 return -result; 236 } else if (fuse_ops_->getattr) { 237 result = fuse_ops_->getattr(path_.c_str(), stat); 238 if (result < 0) 239 return -result; 240 } else { 241 LOG_TRACE("fuse_ops_->fgetattr and fuse_ops_->getattr are NULL."); 242 return ENOSYS; 243 } 244 245 // Also update the cached stat values. 246 stat_ = *stat; 247 return 0; 248} 249 250Error FuseFsNode::Futimens(const struct timespec times[2]) { 251 int result; 252 if (!fuse_ops_->utimens) { 253 LOG_TRACE("fuse_ops_->utimens is NULL."); 254 return ENOSYS; 255 } 256 257 result = fuse_ops_->utimens(path_.c_str(), times); 258 if (result < 0) 259 return -result; 260 261 return result; 262} 263 264Error FuseFsNode::Fchmod(mode_t mode) { 265 int result; 266 if (!fuse_ops_->chmod) { 267 LOG_TRACE("fuse_ops_->chmod is NULL."); 268 return ENOSYS; 269 } 270 271 result = fuse_ops_->chmod(path_.c_str(), mode); 272 if (result < 0) 273 return -result; 274 275 return result; 276} 277 278Error FuseFsNode::VIoctl(int request, va_list args) { 279 LOG_ERROR("Ioctl not implemented for fusefs."); 280 return ENOSYS; 281} 282 283Error FuseFsNode::Tcflush(int queue_selector) { 284 LOG_ERROR("Tcflush not implemented for fusefs."); 285 return ENOSYS; 286} 287 288Error FuseFsNode::Tcgetattr(struct termios* termios_p) { 289 LOG_ERROR("Tcgetattr not implemented for fusefs."); 290 return ENOSYS; 291} 292 293Error FuseFsNode::Tcsetattr(int optional_actions, 294 const struct termios* termios_p) { 295 LOG_ERROR("Tcsetattr not implemented for fusefs."); 296 return ENOSYS; 297} 298 299Error FuseFsNode::GetSize(off_t* out_size) { 300 struct stat statbuf; 301 Error error = GetStat(&statbuf); 302 if (error) 303 return error; 304 305 *out_size = stat_.st_size; 306 return 0; 307} 308 309FileFuseFsNode::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) { 314} 315 316void FileFuseFsNode::Destroy() { 317 if (!fuse_ops_->release) 318 return; 319 fuse_ops_->release(path_.c_str(), &info_); 320} 321 322Error FileFuseFsNode::FSync() { 323 if (!fuse_ops_->fsync) { 324 LOG_ERROR("fuse_ops_->fsync is NULL."); 325 return ENOSYS; 326 } 327 328 int datasync = 0; 329 int result = fuse_ops_->fsync(path_.c_str(), datasync, &info_); 330 if (result < 0) 331 return -result; 332 return 0; 333} 334 335Error FileFuseFsNode::FTruncate(off_t length) { 336 if (!fuse_ops_->ftruncate) { 337 LOG_ERROR("fuse_ops_->ftruncate is NULL."); 338 return ENOSYS; 339 } 340 341 int result = fuse_ops_->ftruncate(path_.c_str(), length, &info_); 342 if (result < 0) 343 return -result; 344 return 0; 345} 346 347Error FileFuseFsNode::Read(const HandleAttr& attr, 348 void* buf, 349 size_t count, 350 int* out_bytes) { 351 if (!fuse_ops_->read) { 352 LOG_ERROR("fuse_ops_->read is NULL."); 353 return ENOSYS; 354 } 355 356 char* cbuf = static_cast<char*>(buf); 357 358 int result = fuse_ops_->read(path_.c_str(), cbuf, count, attr.offs, &info_); 359 if (result < 0) 360 return -result; 361 362 // TODO(binji): support the direct_io flag 363 if (static_cast<size_t>(result) < count) 364 memset(&cbuf[result], 0, count - result); 365 366 *out_bytes = result; 367 return 0; 368} 369 370Error FileFuseFsNode::Write(const HandleAttr& attr, 371 const void* buf, 372 size_t count, 373 int* out_bytes) { 374 if (!fuse_ops_->write) { 375 LOG_ERROR("fuse_ops_->write is NULL."); 376 return ENOSYS; 377 } 378 379 int result = fuse_ops_->write( 380 path_.c_str(), static_cast<const char*>(buf), count, attr.offs, &info_); 381 if (result < 0) 382 return -result; 383 384 // TODO(binji): support the direct_io flag 385 *out_bytes = result; 386 return 0; 387} 388 389DirFuseFsNode::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) { 394} 395 396void DirFuseFsNode::Destroy() { 397 if (!fuse_ops_->releasedir) 398 return; 399 fuse_ops_->releasedir(path_.c_str(), &info_); 400} 401 402Error DirFuseFsNode::FSync() { 403 if (!fuse_ops_->fsyncdir) { 404 LOG_ERROR("fuse_ops_->fsyncdir is NULL."); 405 return ENOSYS; 406 } 407 408 int datasync = 0; 409 int result = fuse_ops_->fsyncdir(path_.c_str(), datasync, &info_); 410 if (result < 0) 411 return -result; 412 return 0; 413} 414 415Error DirFuseFsNode::GetDents(size_t offs, 416 struct dirent* pdir, 417 size_t count, 418 int* out_bytes) { 419 if (!fuse_ops_->readdir) { 420 LOG_ERROR("fuse_ops_->readdir is NULL."); 421 return ENOSYS; 422 } 423 424 bool opened_dir = false; 425 int result; 426 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_); 430 if (result < 0) 431 return -result; 432 433 opened_dir = true; 434 } 435 436 Error error = 0; 437 GetDentsHelper getdents; 438 FillDirInfo fill_info(&getdents, count); 439 result = fuse_ops_->readdir( 440 path_.c_str(), &fill_info, &DirFuseFsNode::FillDirCallback, offs, &info_); 441 if (result < 0) 442 goto fail; 443 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) 449 offs = 0; 450 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); 454 if (error) 455 goto fail; 456 457 return 0; 458 459fail: 460 if (opened_dir && fuse_ops_->releasedir) { 461 // Ignore this result, we're already failing. 462 fuse_ops_->releasedir(path_.c_str(), &info_); 463 } 464 465 return -result; 466} 467 468int DirFuseFsNode::FillDirCallback(void* buf, 469 const char* name, 470 const struct stat* stbuf, 471 off_t off) { 472 FillDirInfo* fill_info = static_cast<FillDirInfo*>(buf); 473 474 // It is OK for the FUSE filesystem to pass a NULL stbuf. In that case, just 475 // use a bogus ino. 476 ino_t ino = stbuf ? stbuf->st_ino : 1; 477 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. 481 if (off) { 482 if (fill_info->num_bytes < sizeof(dirent)) 483 return 1; // 1 => buffer is full 484 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; 490 } else { 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). 495 return 0; 496 } 497} 498 499} // namespace nacl_io 500