fuse_fs.cc revision 5d1f7b1de12d16ceb2c938c56701a3e8bfa558f7
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 "sdk_util/macros.h" 16 17namespace nacl_io { 18 19namespace { 20 21struct FillDirInfo { 22 FillDirInfo(GetDentsHelper* getdents, size_t num_bytes) 23 : getdents(getdents), num_bytes(num_bytes), wrote_offset(false) {} 24 25 GetDentsHelper* getdents; 26 size_t num_bytes; 27 bool wrote_offset; 28}; 29 30} // namespace 31 32FuseFs::FuseFs() : fuse_ops_(NULL), fuse_user_data_(NULL) {} 33 34Error FuseFs::Init(const FsInitArgs& args) { 35 Error error = Filesystem::Init(args); 36 if (error) 37 return error; 38 39 fuse_ops_ = args.fuse_ops; 40 if (fuse_ops_ == NULL) 41 return EINVAL; 42 43 if (fuse_ops_->init) { 44 struct fuse_conn_info info; 45 fuse_user_data_ = fuse_ops_->init(&info); 46 } 47 48 return 0; 49} 50 51void FuseFs::Destroy() { 52 if (fuse_ops_ && fuse_ops_->destroy) 53 fuse_ops_->destroy(fuse_user_data_); 54} 55 56Error FuseFs::Access(const Path& path, int a_mode) { 57 if (!fuse_ops_->access) 58 return ENOSYS; 59 60 int result = fuse_ops_->access(path.Join().c_str(), a_mode); 61 if (result < 0) 62 return -result; 63 64 return 0; 65} 66 67Error FuseFs::Open(const Path& path, int open_flags, ScopedNode* out_node) { 68 std::string path_str = path.Join(); 69 const char* path_cstr = path_str.c_str(); 70 int result = 0; 71 72 struct fuse_file_info fi; 73 memset(&fi, 0, sizeof(fi)); 74 fi.flags = open_flags; 75 76 if (open_flags & (O_CREAT | O_EXCL)) { 77 // According to the FUSE docs, open() is not called when O_CREAT or O_EXCL 78 // is passed. 79 mode_t mode = S_IRALL | S_IWALL; 80 if (fuse_ops_->create) { 81 result = fuse_ops_->create(path_cstr, mode, &fi); 82 if (result < 0) 83 return -result; 84 } else if (fuse_ops_->mknod) { 85 result = fuse_ops_->mknod(path_cstr, mode, dev_); 86 if (result < 0) 87 return -result; 88 } else { 89 return ENOSYS; 90 } 91 } else { 92 // First determine if this is a regular file or a directory. 93 if (fuse_ops_->getattr) { 94 struct stat statbuf; 95 result = fuse_ops_->getattr(path_cstr, &statbuf); 96 if (result < 0) 97 return -result; 98 99 if ((statbuf.st_mode & S_IFMT) == S_IFDIR) { 100 // This is a directory. Don't try to open, just create a new node with 101 // this path. 102 ScopedNode node(new DirFuseFsNode(this, fuse_ops_, fi, path_cstr)); 103 Error error = node->Init(open_flags); 104 if (error) 105 return error; 106 107 *out_node = node; 108 return 0; 109 } 110 } 111 112 // Existing file. 113 if (open_flags & O_TRUNC) { 114 // According to the FUSE docs, O_TRUNC does two calls: first truncate() 115 // then open(). 116 if (!fuse_ops_->truncate) 117 return ENOSYS; 118 result = fuse_ops_->truncate(path_cstr, 0); 119 if (result < 0) 120 return -result; 121 } 122 123 if (!fuse_ops_->open) 124 return ENOSYS; 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 135 *out_node = node; 136 return 0; 137} 138 139Error FuseFs::Unlink(const Path& path) { 140 if (!fuse_ops_->unlink) 141 return ENOSYS; 142 143 int result = fuse_ops_->unlink(path.Join().c_str()); 144 if (result < 0) 145 return -result; 146 147 return 0; 148} 149 150Error FuseFs::Mkdir(const Path& path, int perm) { 151 if (!fuse_ops_->mkdir) 152 return ENOSYS; 153 154 int result = fuse_ops_->mkdir(path.Join().c_str(), perm); 155 if (result < 0) 156 return -result; 157 158 return 0; 159} 160 161Error FuseFs::Rmdir(const Path& path) { 162 if (!fuse_ops_->rmdir) 163 return ENOSYS; 164 165 int result = fuse_ops_->rmdir(path.Join().c_str()); 166 if (result < 0) 167 return -result; 168 169 return 0; 170} 171 172Error FuseFs::Remove(const Path& path) { 173 ScopedNode node; 174 Error error = Open(path, O_RDONLY, &node); 175 if (error) 176 return error; 177 178 struct stat statbuf; 179 error = node->GetStat(&statbuf); 180 if (error) 181 return error; 182 183 node.reset(); 184 185 if ((statbuf.st_mode & S_IFMT) == S_IFDIR) { 186 return Rmdir(path); 187 } else { 188 return Unlink(path); 189 } 190} 191 192Error FuseFs::Rename(const Path& path, const Path& newpath) { 193 if (!fuse_ops_->rename) 194 return ENOSYS; 195 196 int result = fuse_ops_->rename(path.Join().c_str(), newpath.Join().c_str()); 197 if (result < 0) 198 return -result; 199 200 return 0; 201} 202 203FuseFsNode::FuseFsNode(Filesystem* filesystem, 204 struct fuse_operations* fuse_ops, 205 struct fuse_file_info& info, 206 const std::string& path) 207 : Node(filesystem), fuse_ops_(fuse_ops), info_(info), path_(path) {} 208 209bool FuseFsNode::CanOpen(int open_flags) { 210 struct stat statbuf; 211 Error error = GetStat(&statbuf); 212 if (error) 213 return false; 214 215 // GetStat cached the mode in stat_.st_mode. Forward to Node::CanOpen, 216 // which will check this mode against open_flags. 217 return Node::CanOpen(open_flags); 218} 219 220Error FuseFsNode::GetStat(struct stat* stat) { 221 int result; 222 if (fuse_ops_->fgetattr) { 223 result = fuse_ops_->fgetattr(path_.c_str(), stat, &info_); 224 if (result < 0) 225 return -result; 226 } else if (fuse_ops_->getattr) { 227 result = fuse_ops_->getattr(path_.c_str(), stat); 228 if (result < 0) 229 return -result; 230 } else { 231 return ENOSYS; 232 } 233 234 // Also update the cached stat values. 235 stat_ = *stat; 236 return 0; 237} 238 239Error FuseFsNode::VIoctl(int request, va_list args) { 240 // TODO(binji): implement 241 return ENOSYS; 242} 243 244Error FuseFsNode::Tcflush(int queue_selector) { 245 // TODO(binji): use ioctl for this? 246 return ENOSYS; 247} 248 249Error FuseFsNode::Tcgetattr(struct termios* termios_p) { 250 // TODO(binji): use ioctl for this? 251 return ENOSYS; 252} 253 254Error FuseFsNode::Tcsetattr(int optional_actions, 255 const struct termios* termios_p) { 256 // TODO(binji): use ioctl for this? 257 return ENOSYS; 258} 259 260Error FuseFsNode::GetSize(size_t* out_size) { 261 struct stat statbuf; 262 Error error = GetStat(&statbuf); 263 if (error) 264 return error; 265 266 *out_size = stat_.st_size; 267 return 0; 268} 269 270FileFuseFsNode::FileFuseFsNode(Filesystem* filesystem, 271 struct fuse_operations* fuse_ops, 272 struct fuse_file_info& info, 273 const std::string& path) 274 : FuseFsNode(filesystem, fuse_ops, info, path) {} 275 276void FileFuseFsNode::Destroy() { 277 if (!fuse_ops_->release) 278 return; 279 fuse_ops_->release(path_.c_str(), &info_); 280} 281 282Error FileFuseFsNode::FSync() { 283 if (!fuse_ops_->fsync) 284 return ENOSYS; 285 286 int datasync = 0; 287 int result = fuse_ops_->fsync(path_.c_str(), datasync, &info_); 288 if (result < 0) 289 return -result; 290 return 0; 291} 292 293Error FileFuseFsNode::FTruncate(off_t length) { 294 if (!fuse_ops_->ftruncate) 295 return ENOSYS; 296 297 int result = fuse_ops_->ftruncate(path_.c_str(), length, &info_); 298 if (result < 0) 299 return -result; 300 return 0; 301} 302 303Error FileFuseFsNode::Read(const HandleAttr& attr, 304 void* buf, 305 size_t count, 306 int* out_bytes) { 307 if (!fuse_ops_->read) 308 return ENOSYS; 309 310 char* cbuf = static_cast<char*>(buf); 311 312 int result = fuse_ops_->read(path_.c_str(), cbuf, count, attr.offs, &info_); 313 if (result < 0) 314 return -result; 315 316 // Fuse docs say that a read() call will always completely fill the buffer 317 // (padding with zeroes) unless the direct_io filesystem flag is set. 318 // TODO(binji): support the direct_io flag 319 if (static_cast<size_t>(result) < count) 320 memset(&cbuf[result], 0, count - result); 321 322 *out_bytes = count; 323 return 0; 324} 325 326Error FileFuseFsNode::Write(const HandleAttr& attr, 327 const void* buf, 328 size_t count, 329 int* out_bytes) { 330 if (!fuse_ops_->write) 331 return ENOSYS; 332 333 int result = fuse_ops_->write( 334 path_.c_str(), static_cast<const char*>(buf), count, attr.offs, &info_); 335 if (result < 0) 336 return -result; 337 338 // Fuse docs say that a write() call will always write the entire buffer 339 // unless the direct_io filesystem flag is set. 340 // TODO(binji): What should we do if the user breaks this contract? Warn? 341 // TODO(binji): support the direct_io flag 342 *out_bytes = result; 343 return 0; 344} 345 346DirFuseFsNode::DirFuseFsNode(Filesystem* filesystem, 347 struct fuse_operations* fuse_ops, 348 struct fuse_file_info& info, 349 const std::string& path) 350 : FuseFsNode(filesystem, fuse_ops, info, path) {} 351 352void DirFuseFsNode::Destroy() { 353 if (!fuse_ops_->releasedir) 354 return; 355 fuse_ops_->releasedir(path_.c_str(), &info_); 356} 357 358Error DirFuseFsNode::FSync() { 359 if (!fuse_ops_->fsyncdir) 360 return ENOSYS; 361 362 int datasync = 0; 363 int result = fuse_ops_->fsyncdir(path_.c_str(), datasync, &info_); 364 if (result < 0) 365 return -result; 366 return 0; 367} 368 369Error DirFuseFsNode::GetDents(size_t offs, 370 struct dirent* pdir, 371 size_t count, 372 int* out_bytes) { 373 if (!fuse_ops_->readdir) 374 return ENOSYS; 375 376 bool opened_dir = false; 377 int result; 378 379 // Opendir is not necessary, only readdir. Call it anyway, if it is defined. 380 if (fuse_ops_->opendir) { 381 result = fuse_ops_->opendir(path_.c_str(), &info_); 382 if (result < 0) 383 return -result; 384 385 opened_dir = true; 386 } 387 388 Error error = 0; 389 GetDentsHelper getdents; 390 FillDirInfo fill_info(&getdents, count); 391 result = fuse_ops_->readdir( 392 path_.c_str(), &fill_info, &DirFuseFsNode::FillDirCallback, offs, &info_); 393 if (result < 0) 394 goto fail; 395 396 // If the fill function ever wrote an entry with |offs| != 0, then assume it 397 // was not given the full list of entries. In that case, GetDentsHelper's 398 // buffers start with the entry at offset |offs|, so the call to 399 // GetDentsHelpers::GetDents should use an offset of 0. 400 if (fill_info.wrote_offset) 401 offs = 0; 402 403 // The entries have been filled in from the FUSE filesystem, now write them 404 // out to the buffer. 405 error = getdents.GetDents(offs, pdir, count, out_bytes); 406 if (error) 407 goto fail; 408 409 return 0; 410 411fail: 412 if (opened_dir && fuse_ops_->releasedir) { 413 // Ignore this result, we're already failing. 414 fuse_ops_->releasedir(path_.c_str(), &info_); 415 } 416 417 return -result; 418} 419 420int DirFuseFsNode::FillDirCallback(void* buf, 421 const char* name, 422 const struct stat* stbuf, 423 off_t off) { 424 FillDirInfo* fill_info = static_cast<FillDirInfo*>(buf); 425 426 // It is OK for the FUSE filesystem to pass a NULL stbuf. In that case, just 427 // use a bogus ino. 428 ino_t ino = stbuf ? stbuf->st_ino : 1; 429 430 // The FUSE docs say that the implementor of readdir can choose to ignore the 431 // offset given, and instead return all entries. To do this, they pass 432 // |off| == 0 for each call. 433 if (off) { 434 if (fill_info->num_bytes < sizeof(dirent)) 435 return 1; // 1 => buffer is full 436 437 fill_info->wrote_offset = true; 438 fill_info->getdents->AddDirent(ino, name, strlen(name)); 439 fill_info->num_bytes -= sizeof(dirent); 440 // return 0 => request more data. return 1 => buffer full. 441 return fill_info->num_bytes > 0 ? 0 : 1; 442 } else { 443 fill_info->getdents->AddDirent(ino, name, strlen(name)); 444 fill_info->num_bytes -= sizeof(dirent); 445 // According to the docs, we can never return 1 (buffer full) when the 446 // offset is zero (the user is probably ignoring the result anyway). 447 return 0; 448 } 449} 450 451} // namespace nacl_io 452