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