1 /* 2 * Copyright 2011, Jérôme Duval, korli@users.berlios.de. 3 * Copyright 2008, Axel Dörfler, axeld@pinc-software.de. 4 * This file may be used under the terms of the MIT License. 5 */ 6 7 8 #include <dirent.h> 9 #include <util/kernel_cpp.h> 10 #include <string.h> 11 12 #include <AutoDeleter.h> 13 #include <fs_cache.h> 14 #include <fs_info.h> 15 #include <io_requests.h> 16 #include <NodeMonitor.h> 17 #include <util/AutoLock.h> 18 19 #include "DirectoryIterator.h" 20 #include "exfat.h" 21 #include "Inode.h" 22 #include "Utility.h" 23 24 25 //#define TRACE_EXFAT 26 #ifdef TRACE_EXFAT 27 # define TRACE(x...) dprintf("\33[34mexfat:\33[0m " x) 28 #else 29 # define TRACE(x...) ; 30 #endif 31 #define ERROR(x...) dprintf("\33[34mexfat:\33[0m " x) 32 33 34 #define EXFAT_IO_SIZE 65536 35 36 37 struct identify_cookie { 38 exfat_super_block super_block; 39 }; 40 41 42 //! exfat_io() callback hook 43 static status_t 44 iterative_io_get_vecs_hook(void* cookie, io_request* request, off_t offset, 45 size_t size, struct file_io_vec* vecs, size_t* _count) 46 { 47 Inode* inode = (Inode*)cookie; 48 49 return file_map_translate(inode->Map(), offset, size, vecs, _count, 50 inode->GetVolume()->BlockSize()); 51 } 52 53 54 //! exfat_io() callback hook 55 static status_t 56 iterative_io_finished_hook(void* cookie, io_request* request, status_t status, 57 bool partialTransfer, size_t bytesTransferred) 58 { 59 Inode* inode = (Inode*)cookie; 60 rw_lock_read_unlock(inode->Lock()); 61 return B_OK; 62 } 63 64 65 // #pragma mark - Scanning 66 67 68 static float 69 exfat_identify_partition(int fd, partition_data *partition, void **_cookie) 70 { 71 exfat_super_block superBlock; 72 status_t status = Volume::Identify(fd, &superBlock); 73 if (status != B_OK) 74 return -1; 75 76 identify_cookie *cookie = new identify_cookie; 77 memcpy(&cookie->super_block, &superBlock, sizeof(exfat_super_block)); 78 79 *_cookie = cookie; 80 return 0.8f; 81 } 82 83 84 static status_t 85 exfat_scan_partition(int fd, partition_data *partition, void *_cookie) 86 { 87 identify_cookie *cookie = (identify_cookie *)_cookie; 88 89 partition->status = B_PARTITION_VALID; 90 partition->flags |= B_PARTITION_FILE_SYSTEM; 91 partition->content_size = cookie->super_block.NumBlocks() 92 << cookie->super_block.BlockShift(); 93 partition->block_size = 1 << cookie->super_block.BlockShift(); 94 // TODO volume name isn't in the superblock 95 partition->content_name = strdup(cookie->super_block.filesystem); 96 if (partition->content_name == NULL) 97 return B_NO_MEMORY; 98 99 return B_OK; 100 } 101 102 103 static void 104 exfat_free_identify_partition_cookie(partition_data* partition, void* _cookie) 105 { 106 delete (identify_cookie*)_cookie; 107 } 108 109 110 // #pragma mark - 111 112 113 static status_t 114 exfat_mount(fs_volume* _volume, const char* device, uint32 flags, 115 const char* args, ino_t* _rootID) 116 { 117 Volume* volume = new(std::nothrow) Volume(_volume); 118 if (volume == NULL) 119 return B_NO_MEMORY; 120 121 // TODO: this is a bit hacky: we can't use publish_vnode() to publish 122 // the root node, or else its file cache cannot be created (we could 123 // create it later, though). Therefore we're using get_vnode() in Mount(), 124 // but that requires us to export our volume data before calling it. 125 _volume->private_volume = volume; 126 _volume->ops = &gExfatVolumeOps; 127 128 status_t status = volume->Mount(device, flags); 129 if (status != B_OK) { 130 ERROR("Failed mounting the volume. Error: %s\n", strerror(status)); 131 delete volume; 132 return status; 133 } 134 135 *_rootID = volume->RootNode()->ID(); 136 return B_OK; 137 } 138 139 140 static status_t 141 exfat_unmount(fs_volume *_volume) 142 { 143 Volume* volume = (Volume *)_volume->private_volume; 144 145 status_t status = volume->Unmount(); 146 delete volume; 147 148 return status; 149 } 150 151 152 static status_t 153 exfat_read_fs_info(fs_volume* _volume, struct fs_info* info) 154 { 155 Volume* volume = (Volume*)_volume->private_volume; 156 157 // File system flags 158 info->flags = B_FS_IS_PERSISTENT 159 | (volume->IsReadOnly() ? B_FS_IS_READONLY : 0); 160 info->io_size = EXFAT_IO_SIZE; 161 info->block_size = volume->BlockSize(); 162 info->total_blocks = volume->SuperBlock().NumBlocks(); 163 info->free_blocks = 0; //volume->NumFreeBlocks(); 164 165 // Volume name 166 strlcpy(info->volume_name, volume->Name(), sizeof(info->volume_name)); 167 168 // File system name 169 strlcpy(info->fsh_name, "exfat", sizeof(info->fsh_name)); 170 171 return B_OK; 172 } 173 174 175 // #pragma mark - 176 177 178 static status_t 179 exfat_get_vnode(fs_volume* _volume, ino_t id, fs_vnode* _node, int* _type, 180 uint32* _flags, bool reenter) 181 { 182 TRACE("get_vnode %lu\n", id); 183 Volume* volume = (Volume*)_volume->private_volume; 184 185 Inode* inode = new(std::nothrow) Inode(volume, id); 186 if (inode == NULL) 187 return B_NO_MEMORY; 188 189 status_t status = inode->InitCheck(); 190 if (status != B_OK) 191 delete inode; 192 193 if (status == B_OK) { 194 _node->private_node = inode; 195 _node->ops = &gExfatVnodeOps; 196 *_type = inode->Mode(); 197 *_flags = 0; 198 } else 199 ERROR("get_vnode: InitCheck() failed. Error: %s\n", strerror(status)); 200 201 return status; 202 } 203 204 205 static status_t 206 exfat_put_vnode(fs_volume* _volume, fs_vnode* _node, bool reenter) 207 { 208 delete (Inode*)_node->private_node; 209 return B_OK; 210 } 211 212 213 static bool 214 exfat_can_page(fs_volume* _volume, fs_vnode* _node, void* _cookie) 215 { 216 return true; 217 } 218 219 220 static status_t 221 exfat_read_pages(fs_volume* _volume, fs_vnode* _node, void* _cookie, 222 off_t pos, const iovec* vecs, size_t count, size_t* _numBytes) 223 { 224 Volume* volume = (Volume*)_volume->private_volume; 225 Inode* inode = (Inode*)_node->private_node; 226 227 if (inode->FileCache() == NULL) 228 return B_BAD_VALUE; 229 230 rw_lock_read_lock(inode->Lock()); 231 232 uint32 vecIndex = 0; 233 size_t vecOffset = 0; 234 size_t bytesLeft = *_numBytes; 235 status_t status; 236 237 while (true) { 238 file_io_vec fileVecs[8]; 239 uint32 fileVecCount = 8; 240 241 status = file_map_translate(inode->Map(), pos, bytesLeft, fileVecs, 242 &fileVecCount, 0); 243 if (status != B_OK && status != B_BUFFER_OVERFLOW) 244 break; 245 246 bool bufferOverflow = status == B_BUFFER_OVERFLOW; 247 248 size_t bytes = bytesLeft; 249 status = read_file_io_vec_pages(volume->Device(), fileVecs, 250 fileVecCount, vecs, count, &vecIndex, &vecOffset, &bytes); 251 if (status != B_OK || !bufferOverflow) 252 break; 253 254 pos += bytes; 255 bytesLeft -= bytes; 256 } 257 258 rw_lock_read_unlock(inode->Lock()); 259 260 return status; 261 } 262 263 264 static status_t 265 exfat_io(fs_volume* _volume, fs_vnode* _node, void* _cookie, io_request* request) 266 { 267 Volume* volume = (Volume*)_volume->private_volume; 268 Inode* inode = (Inode*)_node->private_node; 269 270 #ifndef EXFAT_SHELL 271 if (io_request_is_write(request) && volume->IsReadOnly()) { 272 notify_io_request(request, B_READ_ONLY_DEVICE); 273 return B_READ_ONLY_DEVICE; 274 } 275 #endif 276 277 if (inode->FileCache() == NULL) { 278 #ifndef EXFAT_SHELL 279 notify_io_request(request, B_BAD_VALUE); 280 #endif 281 return B_BAD_VALUE; 282 } 283 284 // We lock the node here and will unlock it in the "finished" hook. 285 rw_lock_read_lock(inode->Lock()); 286 287 return do_iterative_fd_io(volume->Device(), request, 288 iterative_io_get_vecs_hook, iterative_io_finished_hook, inode); 289 } 290 291 292 static status_t 293 exfat_get_file_map(fs_volume* _volume, fs_vnode* _node, off_t offset, 294 size_t size, struct file_io_vec* vecs, size_t* _count) 295 { 296 TRACE("exfat_get_file_map()\n"); 297 Inode* inode = (Inode*)_node->private_node; 298 size_t index = 0, max = *_count; 299 300 while (true) { 301 off_t blockOffset; 302 off_t blockLength; 303 status_t status = inode->FindBlock(offset, blockOffset, &blockLength); 304 if (status != B_OK) 305 return status; 306 307 if (index > 0 && (vecs[index - 1].offset 308 == blockOffset - vecs[index - 1].length)) { 309 vecs[index - 1].length += blockLength; 310 } else { 311 if (index >= max) { 312 // we're out of file_io_vecs; let's bail out 313 *_count = index; 314 return B_BUFFER_OVERFLOW; 315 } 316 317 vecs[index].offset = blockOffset; 318 vecs[index].length = blockLength; 319 index++; 320 } 321 322 offset += blockLength; 323 size -= blockLength; 324 325 if (size <= vecs[index - 1].length || offset >= inode->Size()) { 326 // We're done! 327 *_count = index; 328 TRACE("exfat_get_file_map for inode %lld\n", inode->ID()); 329 return B_OK; 330 } 331 } 332 333 // can never get here 334 return B_ERROR; 335 } 336 337 338 // #pragma mark - 339 340 341 static status_t 342 exfat_lookup(fs_volume* _volume, fs_vnode* _directory, const char* name, 343 ino_t* _vnodeID) 344 { 345 TRACE("exfat_lookup: name address: %p (%s)\n", name, name); 346 Volume* volume = (Volume*)_volume->private_volume; 347 Inode* directory = (Inode*)_directory->private_node; 348 349 // check access permissions 350 status_t status = directory->CheckPermissions(X_OK); 351 if (status < B_OK) 352 return status; 353 354 status = DirectoryIterator(directory).Lookup(name, strlen(name), _vnodeID); 355 if (status != B_OK) { 356 ERROR("exfat_lookup: name %s (%s)\n", name, strerror(status)); 357 return status; 358 } 359 360 TRACE("exfat_lookup: ID %d\n", *_vnodeID); 361 362 return get_vnode(volume->FSVolume(), *_vnodeID, NULL); 363 } 364 365 366 static status_t 367 exfat_ioctl(fs_volume* _volume, fs_vnode* _node, void* _cookie, uint32 cmd, 368 void* buffer, size_t bufferLength) 369 { 370 TRACE("ioctl: %lu\n", cmd); 371 372 /*Volume* volume = (Volume*)_volume->private_volume;*/ 373 return B_DEV_INVALID_IOCTL; 374 } 375 376 377 static status_t 378 exfat_read_stat(fs_volume* _volume, fs_vnode* _node, struct stat* stat) 379 { 380 Inode* inode = (Inode*)_node->private_node; 381 382 stat->st_dev = inode->GetVolume()->ID(); 383 stat->st_ino = inode->ID(); 384 stat->st_nlink = 1; 385 stat->st_blksize = EXFAT_IO_SIZE; 386 387 stat->st_uid = inode->UserID(); 388 stat->st_gid = inode->GroupID(); 389 stat->st_mode = inode->Mode(); 390 stat->st_type = 0; 391 392 inode->GetAccessTime(stat->st_atim); 393 inode->GetModificationTime(stat->st_mtim); 394 inode->GetChangeTime(stat->st_ctim); 395 inode->GetCreationTime(stat->st_crtim); 396 397 stat->st_size = inode->Size(); 398 stat->st_blocks = (inode->Size() + 511) / 512; 399 400 return B_OK; 401 } 402 403 404 static status_t 405 exfat_open(fs_volume* /*_volume*/, fs_vnode* _node, int openMode, 406 void** _cookie) 407 { 408 Inode* inode = (Inode*)_node->private_node; 409 410 // opening a directory read-only is allowed, although you can't read 411 // any data from it. 412 if (inode->IsDirectory() && (openMode & O_RWMASK) != 0) 413 return B_IS_A_DIRECTORY; 414 415 status_t status = inode->CheckPermissions(open_mode_to_access(openMode) 416 | (openMode & O_TRUNC ? W_OK : 0)); 417 if (status != B_OK) 418 return status; 419 420 // Prepare the cookie 421 file_cookie* cookie = new(std::nothrow) file_cookie; 422 if (cookie == NULL) 423 return B_NO_MEMORY; 424 ObjectDeleter<file_cookie> cookieDeleter(cookie); 425 426 cookie->open_mode = openMode & EXFAT_OPEN_MODE_USER_MASK; 427 cookie->last_size = inode->Size(); 428 cookie->last_notification = system_time(); 429 430 if ((openMode & O_NOCACHE) != 0 && inode->FileCache() != NULL) { 431 // Disable the file cache, if requested? 432 status = file_cache_disable(inode->FileCache()); 433 if (status != B_OK) 434 return status; 435 } 436 437 cookieDeleter.Detach(); 438 *_cookie = cookie; 439 440 return B_OK; 441 } 442 443 444 static status_t 445 exfat_read(fs_volume* _volume, fs_vnode* _node, void* _cookie, off_t pos, 446 void* buffer, size_t* _length) 447 { 448 Inode* inode = (Inode*)_node->private_node; 449 450 if (!inode->IsFile()) { 451 *_length = 0; 452 return inode->IsDirectory() ? B_IS_A_DIRECTORY : B_BAD_VALUE; 453 } 454 455 return inode->ReadAt(pos, (uint8*)buffer, _length); 456 } 457 458 459 static status_t 460 exfat_close(fs_volume *_volume, fs_vnode *_node, void *_cookie) 461 { 462 return B_OK; 463 } 464 465 466 static status_t 467 exfat_free_cookie(fs_volume* _volume, fs_vnode* _node, void* _cookie) 468 { 469 file_cookie* cookie = (file_cookie*)_cookie; 470 Volume* volume = (Volume*)_volume->private_volume; 471 Inode* inode = (Inode*)_node->private_node; 472 473 if (inode->Size() != cookie->last_size) 474 notify_stat_changed(volume->ID(), inode->ID(), B_STAT_SIZE); 475 476 delete cookie; 477 return B_OK; 478 } 479 480 481 static status_t 482 exfat_access(fs_volume* _volume, fs_vnode* _node, int accessMode) 483 { 484 Inode* inode = (Inode*)_node->private_node; 485 return inode->CheckPermissions(accessMode); 486 } 487 488 489 static status_t 490 exfat_read_link(fs_volume *_volume, fs_vnode *_node, char *buffer, 491 size_t *_bufferSize) 492 { 493 Inode* inode = (Inode*)_node->private_node; 494 return inode->ReadAt(0, (uint8*)buffer, _bufferSize); 495 } 496 497 498 // #pragma mark - Directory functions 499 500 501 static status_t 502 exfat_open_dir(fs_volume* /*_volume*/, fs_vnode* _node, void** _cookie) 503 { 504 Inode* inode = (Inode*)_node->private_node; 505 status_t status = inode->CheckPermissions(R_OK); 506 if (status < B_OK) 507 return status; 508 509 if (!inode->IsDirectory()) 510 return B_NOT_A_DIRECTORY; 511 512 DirectoryIterator* iterator = new(std::nothrow) DirectoryIterator(inode); 513 if (iterator == NULL || iterator->InitCheck() != B_OK) { 514 delete iterator; 515 return B_NO_MEMORY; 516 } 517 518 *_cookie = iterator; 519 return B_OK; 520 } 521 522 523 static status_t 524 exfat_read_dir(fs_volume *_volume, fs_vnode *_node, void *_cookie, 525 struct dirent *dirent, size_t bufferSize, uint32 *_num) 526 { 527 TRACE("exfat_read_dir\n"); 528 DirectoryIterator* iterator = (DirectoryIterator*)_cookie; 529 530 size_t length = bufferSize; 531 ino_t id; 532 status_t status = iterator->GetNext(dirent->d_name, &length, &id); 533 if (status == B_ENTRY_NOT_FOUND) { 534 *_num = 0; 535 return B_OK; 536 } else if (status != B_OK) 537 return status; 538 539 Volume* volume = (Volume*)_volume->private_volume; 540 dirent->d_dev = volume->ID(); 541 dirent->d_ino = id; 542 dirent->d_reclen = sizeof(struct dirent) + length; 543 *_num = 1; 544 545 TRACE("exfat_read_dir end\n"); 546 547 return B_OK; 548 } 549 550 551 static status_t 552 exfat_rewind_dir(fs_volume * /*_volume*/, fs_vnode * /*node*/, void *_cookie) 553 { 554 DirectoryIterator* iterator = (DirectoryIterator*)_cookie; 555 556 return iterator->Rewind(); 557 } 558 559 560 static status_t 561 exfat_close_dir(fs_volume * /*_volume*/, fs_vnode * /*node*/, void * /*_cookie*/) 562 { 563 return B_OK; 564 } 565 566 567 static status_t 568 exfat_free_dir_cookie(fs_volume *_volume, fs_vnode *_node, void *_cookie) 569 { 570 delete (DirectoryIterator*)_cookie; 571 return B_OK; 572 } 573 574 575 fs_volume_ops gExfatVolumeOps = { 576 &exfat_unmount, 577 &exfat_read_fs_info, 578 NULL, // write_fs_info() 579 NULL, // fs_sync, 580 &exfat_get_vnode, 581 }; 582 583 584 fs_vnode_ops gExfatVnodeOps = { 585 /* vnode operations */ 586 &exfat_lookup, 587 NULL, 588 &exfat_put_vnode, 589 NULL, // exfat_remove_vnode, 590 591 /* VM file access */ 592 &exfat_can_page, 593 &exfat_read_pages, 594 NULL, // exfat_write_pages, 595 596 NULL, // io() 597 NULL, // cancel_io() 598 599 &exfat_get_file_map, 600 601 &exfat_ioctl, 602 NULL, 603 NULL, // fs_select 604 NULL, // fs_deselect 605 NULL, // fs_fsync, 606 607 &exfat_read_link, 608 NULL, // fs_create_symlink, 609 610 NULL, // fs_link, 611 NULL, // fs_unlink, 612 NULL, // fs_rename, 613 614 &exfat_access, 615 &exfat_read_stat, 616 NULL, // fs_write_stat, 617 NULL, // fs_preallocate 618 619 /* file operations */ 620 NULL, // fs_create, 621 &exfat_open, 622 &exfat_close, 623 &exfat_free_cookie, 624 &exfat_read, 625 NULL, // fs_write, 626 627 /* directory operations */ 628 NULL, // fs_create_dir, 629 NULL, // fs_remove_dir, 630 &exfat_open_dir, 631 &exfat_close_dir, 632 &exfat_free_dir_cookie, 633 &exfat_read_dir, 634 &exfat_rewind_dir, 635 636 /* attribute directory operations */ 637 NULL, // fs_open_attr_dir, 638 NULL, // fs_close_attr_dir, 639 NULL, // fs_free_attr_dir_cookie, 640 NULL, // fs_read_attr_dir, 641 NULL, // fs_rewind_attr_dir, 642 643 /* attribute operations */ 644 NULL, // fs_create_attr, 645 NULL, // fs_open_attr, 646 NULL, // fs_close_attr, 647 NULL, // fs_free_attr_cookie, 648 NULL, // fs_read_attr, 649 NULL, // fs_write_attr, 650 NULL, // fs_read_attr_stat, 651 NULL, // fs_write_attr_stat, 652 NULL, // fs_rename_attr, 653 NULL, // fs_remove_attr, 654 }; 655 656 657 static file_system_module_info sExfatFileSystem = { 658 { 659 "file_systems/exfat" B_CURRENT_FS_API_VERSION, 660 0, 661 NULL, 662 }, 663 664 "exfat", // short_name 665 "ExFAT File System", // pretty_name 666 0, // DDM flags 667 668 // scanning 669 exfat_identify_partition, 670 exfat_scan_partition, 671 exfat_free_identify_partition_cookie, 672 NULL, // free_partition_content_cookie() 673 674 &exfat_mount, 675 676 NULL, 677 }; 678 679 680 module_info *modules[] = { 681 (module_info *)&sExfatFileSystem, 682 NULL, 683 }; 684