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 37struct identify_cookie { 38 exfat_super_block super_block; 39}; 40 41 42//! exfat_io() callback hook 43static status_t 44iterative_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 55static status_t 56iterative_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 68static float 69exfat_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 84static status_t 85exfat_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 103static void 104exfat_free_identify_partition_cookie(partition_data* partition, void* _cookie) 105{ 106 delete (identify_cookie*)_cookie; 107} 108 109 110// #pragma mark - 111 112 113static status_t 114exfat_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 140static status_t 141exfat_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 152static status_t 153exfat_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 178static status_t 179exfat_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 205static status_t 206exfat_put_vnode(fs_volume* _volume, fs_vnode* _node, bool reenter) 207{ 208 delete (Inode*)_node->private_node; 209 return B_OK; 210} 211 212 213static bool 214exfat_can_page(fs_volume* _volume, fs_vnode* _node, void* _cookie) 215{ 216 return true; 217} 218 219 220static status_t 221exfat_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 264static status_t 265exfat_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 292static status_t 293exfat_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 341static status_t 342exfat_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 366static status_t 367exfat_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 377static status_t 378exfat_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 404static status_t 405exfat_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 444static status_t 445exfat_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 459static status_t 460exfat_close(fs_volume *_volume, fs_vnode *_node, void *_cookie) 461{ 462 return B_OK; 463} 464 465 466static status_t 467exfat_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 481static status_t 482exfat_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 489static status_t 490exfat_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 501static status_t 502exfat_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 523static status_t 524exfat_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 551static status_t 552exfat_rewind_dir(fs_volume * /*_volume*/, fs_vnode * /*node*/, void *_cookie) 553{ 554 DirectoryIterator* iterator = (DirectoryIterator*)_cookie; 555 556 return iterator->Rewind(); 557} 558 559 560static status_t 561exfat_close_dir(fs_volume * /*_volume*/, fs_vnode * /*node*/, void * /*_cookie*/) 562{ 563 return B_OK; 564} 565 566 567static status_t 568exfat_free_dir_cookie(fs_volume *_volume, fs_vnode *_node, void *_cookie) 569{ 570 delete (DirectoryIterator*)_cookie; 571 return B_OK; 572} 573 574 575fs_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 584fs_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 657static 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 680module_info *modules[] = { 681 (module_info *)&sExfatFileSystem, 682 NULL, 683}; 684