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