1 // ---------------------------------------------------------------------- 2 // This software is part of the Haiku distribution and is covered 3 // by the MIT License. 4 // 5 // File Name: virtualdrive.c 6 // 7 // Description: Driver that emulates virtual drives. 8 // 9 // Author: Marcus Overhagen <Marcus@Overhagen.de> 10 // Ingo Weinhold <bonefish@users.sf.net> 11 // Axel Doerfler <axeld@pinc-software.de> 12 // ---------------------------------------------------------------------- 13 14 #include <fcntl.h> 15 #include <errno.h> 16 #include <malloc.h> 17 #include <stdio.h> 18 #include <string.h> 19 #include <unistd.h> 20 21 #include <KernelExport.h> 22 #include <Drivers.h> 23 #include <Errors.h> 24 25 #include "lock.h" 26 #include "virtualdrive.h" 27 #include "virtualdrive_icon.h" 28 29 /* 30 [2:07] <geist> when you open the file in the driver, use stat() to see if it's a file. if it is, call ioctl 10000 on the underlying file 31 [2:07] <geist> that's the disable-cache ioctl 32 [2:08] <geist> bfs is probably doing the same algorithm, and seeing that you are a device and not a file, and so it doesn't call 10000 on you 33 [2:08] <marcus_o> thanks, I will try calling it 34 [2:08] <geist> and dont bother using dosfs as a host fs, it wont work 35 [2:09] <geist> bfs is the only fs that's reasonably safe being reentered like that, but only if the underlying one is opened with the cache disabled on that file 36 [2:09] <geist> that ioctl is used on the swap file as well 37 [2:10] <marcus_o> I'm currently allocating memory in the driver's write() function hook 38 [2:10] <geist> cant do that 39 */ 40 41 //#define TRACE(x) dprintf x 42 #define TRACE(x) ; 43 #define MB (1024LL * 1024LL) 44 45 static int dev_index_for_path(const char *path); 46 47 /* ----- 48 null-terminated array of device names supported by this driver 49 ----- */ 50 51 static const char *sVirtualDriveName[] = { 52 VIRTUAL_DRIVE_DIRECTORY_REL "/0", 53 VIRTUAL_DRIVE_DIRECTORY_REL "/1", 54 VIRTUAL_DRIVE_DIRECTORY_REL "/2", 55 VIRTUAL_DRIVE_DIRECTORY_REL "/3", 56 VIRTUAL_DRIVE_DIRECTORY_REL "/4", 57 VIRTUAL_DRIVE_DIRECTORY_REL "/5", 58 VIRTUAL_DRIVE_DIRECTORY_REL "/6", 59 VIRTUAL_DRIVE_DIRECTORY_REL "/7", 60 VIRTUAL_DRIVE_DIRECTORY_REL "/8", 61 VIRTUAL_DRIVE_DIRECTORY_REL "/9", 62 VIRTUAL_DRIVE_CONTROL_DEVICE_REL, 63 NULL 64 }; 65 66 int32 api_version = B_CUR_DRIVER_API_VERSION; 67 extern device_hooks sVirtualDriveHooks; 68 69 lock driverlock; 70 71 typedef struct device_info { 72 int32 open_count; 73 int fd; 74 off_t size; 75 bool unused; 76 bool registered; 77 char file[B_PATH_NAME_LENGTH]; 78 const char *device_path; 79 device_geometry geometry; 80 } device_info; 81 82 #define kDeviceCount 11 83 #define kDataDeviceCount (kDeviceCount - 1) 84 #define kControlDevice (kDeviceCount - 1) 85 struct device_info gDeviceInfos[kDeviceCount]; 86 87 static int32 gRegistrationCount = 0; 88 static int gControlDeviceFD = -1; 89 90 static thread_id gLockOwner = -1; 91 static int32 gLockOwnerNesting = 0; 92 93 94 // lock_driver 95 void 96 lock_driver() 97 { 98 thread_id thread = find_thread(NULL); 99 if (gLockOwner != thread) { 100 LOCK(driverlock); 101 gLockOwner = thread; 102 } 103 gLockOwnerNesting++; 104 } 105 106 107 // unlock_driver 108 void 109 unlock_driver() 110 { 111 thread_id thread = find_thread(NULL); 112 if (gLockOwner == thread && --gLockOwnerNesting == 0) { 113 gLockOwner = -1; 114 UNLOCK(driverlock); 115 } 116 } 117 118 119 // is_valid_device_index 120 static inline 121 bool 122 is_valid_device_index(int32 index) 123 { 124 return (index >= 0 && index < kDeviceCount); 125 } 126 127 128 // is_valid_data_device_index 129 static inline 130 bool 131 is_valid_data_device_index(int32 index) 132 { 133 return (is_valid_device_index(index) && index != kControlDevice); 134 } 135 136 137 // dev_index_for_path 138 static 139 int 140 dev_index_for_path(const char *path) 141 { 142 int i; 143 for (i = 0; i < kDeviceCount; i++) { 144 if (!strcmp(path, gDeviceInfos[i].device_path)) 145 return i; 146 } 147 return -1; 148 } 149 150 151 // clear_device_info 152 static 153 void 154 clear_device_info(int32 index) 155 { 156 TRACE(("virtualdrive: clear_device_info(%ld)\n", index)); 157 158 device_info &info = gDeviceInfos[index]; 159 info.open_count = 0; 160 info.fd = -1; 161 info.size = 0; 162 info.unused = (index != kDeviceCount - 1); 163 info.registered = !info.unused; 164 info.file[0] = '\0'; 165 info.device_path = sVirtualDriveName[index]; 166 info.geometry.read_only = true; 167 } 168 169 170 // init_device_info 171 static 172 status_t 173 init_device_info(int32 index, virtual_drive_info *initInfo) 174 { 175 if (!is_valid_data_device_index(index) || !initInfo) 176 return B_BAD_VALUE; 177 178 device_info &info = gDeviceInfos[index]; 179 if (!info.unused) 180 return B_BAD_VALUE; 181 182 bool readOnly = (initInfo->use_geometry && initInfo->geometry.read_only); 183 184 // open the file 185 int fd = open(initInfo->file_name, (readOnly ? O_RDONLY : O_RDWR)); 186 if (fd < 0) 187 return errno; 188 189 status_t error = B_OK; 190 191 // get the file size 192 off_t fileSize = 0; 193 struct stat st; 194 if (fstat(fd, &st) == 0) 195 fileSize = st.st_size; 196 else 197 error = errno; 198 199 // If we shall use the supplied geometry, we enlarge the file, if 200 // necessary. Otherwise we fill in the geometry according to the size of the file. 201 off_t size = 0; 202 if (error == B_OK) { 203 if (initInfo->use_geometry) { 204 // use the supplied geometry 205 info.geometry = initInfo->geometry; 206 size = (off_t)info.geometry.bytes_per_sector 207 * info.geometry.sectors_per_track 208 * info.geometry.cylinder_count 209 * info.geometry.head_count; 210 if (size > fileSize) { 211 if (!readOnly) { 212 if (ftruncate(fd, size) != 0) 213 error = errno; 214 } else 215 error = B_NOT_ALLOWED; 216 } 217 } else { 218 // fill in the geometry 219 // default to 512 bytes block size 220 uint32 blockSize = 512; 221 // Optimally we have only 1 block per sector and only one head. 222 // Since we have only a uint32 for the cylinder count, this won't work 223 // for files > 2TB. So, we set the head count to the minimally possible 224 // value. 225 off_t blocks = fileSize / blockSize; 226 uint32 heads = (blocks + ULONG_MAX - 1) / ULONG_MAX; 227 if (heads == 0) 228 heads = 1; 229 info.geometry.bytes_per_sector = blockSize; 230 info.geometry.sectors_per_track = 1; 231 info.geometry.cylinder_count = blocks / heads; 232 info.geometry.head_count = heads; 233 info.geometry.device_type = B_DISK; // TODO: Add a new constant. 234 info.geometry.removable = false; 235 info.geometry.read_only = false; 236 info.geometry.write_once = false; 237 size = (off_t)info.geometry.bytes_per_sector 238 * info.geometry.sectors_per_track 239 * info.geometry.cylinder_count 240 * info.geometry.head_count; 241 } 242 } 243 244 if (error == B_OK) { 245 // Disable caching for underlying file! (else this driver will deadlock) 246 // We probably cannot resize the file once the cache has been disabled! 247 248 // This applies to BeOS only: 249 // Work around a bug in BFS: the file is not synced before the cache is 250 // turned off, and thus causing possible inconsistencies. 251 // Unfortunately, this only solves one half of the issue; there is 252 // no way to remove the blocks in the cache, so changes made to the 253 // image have the chance to get lost. 254 fsync(fd); 255 256 // This is a special reserved ioctl() opcode not defined anywhere in 257 // the Be headers. 258 if (ioctl(fd, 10000) != 0) { 259 dprintf("virtualdrive: disable caching ioctl failed\n"); 260 return errno; 261 } 262 } 263 264 // fill in the rest of the device_info structure 265 if (error == B_OK) { 266 // open_count doesn't have to be changed here (virtualdrive_open() will do that for us) 267 info.fd = fd; 268 info.size = size; 269 info.unused = false; 270 info.registered = true; 271 strcpy(info.file, initInfo->file_name); 272 info.device_path = sVirtualDriveName[index]; 273 } else { 274 // cleanup on error 275 close(fd); 276 if (info.open_count == 0) 277 clear_device_info(index); 278 } 279 return error; 280 } 281 282 283 // uninit_device_info 284 static 285 status_t 286 uninit_device_info(int32 index) 287 { 288 if (!is_valid_data_device_index(index)) 289 return B_BAD_VALUE; 290 291 device_info &info = gDeviceInfos[index]; 292 if (info.unused) 293 return B_BAD_VALUE; 294 295 close(info.fd); 296 clear_device_info(index); 297 return B_OK; 298 } 299 300 301 // #pragma mark - 302 // public driver API 303 304 305 status_t 306 init_hardware(void) 307 { 308 TRACE(("virtualdrive: init_hardware\n")); 309 return B_OK; 310 } 311 312 313 status_t 314 init_driver(void) 315 { 316 TRACE(("virtualdrive: init\n")); 317 318 new_lock(&driverlock, "virtualdrive lock"); 319 320 // init the device infos 321 for (int32 i = 0; i < kDeviceCount; i++) 322 clear_device_info(i); 323 324 return B_OK; 325 } 326 327 328 void 329 uninit_driver(void) 330 { 331 TRACE(("virtualdrive: uninit\n")); 332 free_lock(&driverlock); 333 } 334 335 336 const char ** 337 publish_devices(void) 338 { 339 TRACE(("virtualdrive: publish_devices\n")); 340 return sVirtualDriveName; 341 } 342 343 344 device_hooks * 345 find_device(const char* name) 346 { 347 TRACE(("virtualdrive: find_device(%s)\n", name)); 348 return &sVirtualDriveHooks; 349 } 350 351 352 // #pragma mark - 353 // the device hooks 354 355 356 static status_t 357 virtualdrive_open(const char *name, uint32 flags, void **cookie) 358 { 359 TRACE(("virtualdrive: open %s\n",name)); 360 361 *cookie = (void *)-1; 362 363 lock_driver(); 364 365 int32 devIndex = dev_index_for_path(name); 366 367 TRACE(("virtualdrive: devIndex %ld!\n", devIndex)); 368 369 if (!is_valid_device_index(devIndex)) { 370 TRACE(("virtualdrive: wrong index!\n")); 371 unlock_driver(); 372 return B_ERROR; 373 } 374 375 if (gDeviceInfos[devIndex].unused) { 376 TRACE(("virtualdrive: device is unused!\n")); 377 unlock_driver(); 378 return B_ERROR; 379 } 380 381 if (!gDeviceInfos[devIndex].registered) { 382 TRACE(("virtualdrive: device has been unregistered!\n")); 383 unlock_driver(); 384 return B_ERROR; 385 } 386 387 // store index in cookie 388 *cookie = (void *)devIndex; 389 390 gDeviceInfos[devIndex].open_count++; 391 392 unlock_driver(); 393 return B_OK; 394 } 395 396 397 static status_t 398 virtualdrive_close(void *cookie) 399 { 400 int32 devIndex = (int)cookie; 401 402 TRACE(("virtualdrive: close() devIndex = %ld\n", devIndex)); 403 if (!is_valid_data_device_index(devIndex)) 404 return B_OK; 405 406 lock_driver(); 407 408 gDeviceInfos[devIndex].open_count--; 409 if (gDeviceInfos[devIndex].open_count == 0 && !gDeviceInfos[devIndex].registered) { 410 // The last FD is closed and the device has been unregistered. Free its info. 411 uninit_device_info(devIndex); 412 } 413 414 unlock_driver(); 415 416 return B_OK; 417 } 418 419 420 static status_t 421 virtualdrive_read(void *cookie, off_t position, void *buffer, size_t *numBytes) 422 { 423 TRACE(("virtualdrive: read pos = 0x%08Lx, bytes = 0x%08lx\n", position, *numBytes)); 424 425 // check parameters 426 int devIndex = (int)cookie; 427 if (devIndex == kControlDevice) { 428 TRACE(("virtualdrive: reading from control device not allowed\n")); 429 return B_NOT_ALLOWED; 430 } 431 if (position < 0) 432 return B_BAD_VALUE; 433 434 lock_driver(); 435 device_info &info = gDeviceInfos[devIndex]; 436 // adjust position and numBytes according to the file size 437 if (position > info.size) 438 position = info.size; 439 if (position + *numBytes > info.size) 440 *numBytes = info.size - position; 441 // read 442 status_t error = B_OK; 443 ssize_t bytesRead = read_pos(info.fd, position, buffer, *numBytes); 444 if (bytesRead < 0) 445 error = errno; 446 else 447 *numBytes = bytesRead; 448 unlock_driver(); 449 return error; 450 } 451 452 453 static status_t 454 virtualdrive_write(void *cookie, off_t position, const void *buffer, size_t *numBytes) 455 { 456 TRACE(("virtualdrive: write pos = 0x%08Lx, bytes = 0x%08lx\n", position, *numBytes)); 457 458 // check parameters 459 int devIndex = (int)cookie; 460 if (devIndex == kControlDevice) { 461 TRACE(("virtualdrive: writing to control device not allowed\n")); 462 return B_NOT_ALLOWED; 463 } 464 if (position < 0) 465 return B_BAD_VALUE; 466 467 lock_driver(); 468 device_info &info = gDeviceInfos[devIndex]; 469 // adjust position and numBytes according to the file size 470 if (position > info.size) 471 position = info.size; 472 if (position + *numBytes > info.size) 473 *numBytes = info.size - position; 474 // read 475 status_t error = B_OK; 476 ssize_t bytesRead = write_pos(info.fd, position, buffer, *numBytes); 477 if (bytesRead < 0) 478 error = errno; 479 else 480 *numBytes = bytesRead; 481 unlock_driver(); 482 return error; 483 } 484 485 486 static status_t 487 virtualdrive_control(void *cookie, uint32 op, void *arg, size_t len) 488 { 489 TRACE(("virtualdrive: ioctl\n")); 490 491 int devIndex = (int)cookie; 492 device_info &info = gDeviceInfos[devIndex]; 493 494 if (devIndex == kControlDevice || info.unused) { 495 // control device or unused data device 496 switch (op) { 497 case B_GET_DEVICE_SIZE: 498 case B_SET_NONBLOCKING_IO: 499 case B_SET_BLOCKING_IO: 500 case B_GET_READ_STATUS: 501 case B_GET_WRITE_STATUS: 502 case B_GET_ICON: 503 case B_GET_GEOMETRY: 504 case B_GET_BIOS_GEOMETRY: 505 case B_GET_MEDIA_STATUS: 506 case B_SET_UNINTERRUPTABLE_IO: 507 case B_SET_INTERRUPTABLE_IO: 508 case B_FLUSH_DRIVE_CACHE: 509 case B_GET_BIOS_DRIVE_ID: 510 case B_GET_DRIVER_FOR_DEVICE: 511 case B_SET_DEVICE_SIZE: 512 case B_SET_PARTITION: 513 case B_FORMAT_DEVICE: 514 case B_EJECT_DEVICE: 515 case B_LOAD_MEDIA: 516 case B_GET_NEXT_OPEN_DEVICE: 517 TRACE(("virtualdrive: another ioctl: %lx (%lu)\n", op, op)); 518 return B_BAD_VALUE; 519 520 case VIRTUAL_DRIVE_REGISTER_FILE: 521 { 522 TRACE(("virtualdrive: VIRTUAL_DRIVE_REGISTER_FILE\n")); 523 524 virtual_drive_info *driveInfo = (virtual_drive_info *)arg; 525 if (devIndex != kControlDevice || driveInfo == NULL 526 || driveInfo->magic != VIRTUAL_DRIVE_MAGIC 527 || driveInfo->drive_info_size != sizeof(virtual_drive_info)) 528 return B_BAD_VALUE; 529 530 status_t error = B_ERROR; 531 int32 i; 532 533 lock_driver(); 534 535 // first, look if we already have opened that file and see 536 // if it's available to us which happens when it has been 537 // halted but is still in use by other components 538 for (i = 0; i < kDataDeviceCount; i++) { 539 if (!gDeviceInfos[i].unused 540 && gDeviceInfos[i].fd == -1 541 && !gDeviceInfos[i].registered 542 && !strcmp(gDeviceInfos[i].file, driveInfo->file_name)) { 543 // mark device as unused, so that init_device_info() will succeed 544 gDeviceInfos[i].unused = true; 545 error = B_OK; 546 break; 547 } 548 } 549 550 if (error != B_OK) { 551 // find an unused data device 552 for (i = 0; i < kDataDeviceCount; i++) { 553 if (gDeviceInfos[i].unused) { 554 error = B_OK; 555 break; 556 } 557 } 558 } 559 560 if (error == B_OK) { 561 // we found a device slot, let's initialize it 562 error = init_device_info(i, driveInfo); 563 if (error == B_OK) { 564 // return the device path 565 strcpy(driveInfo->device_name, "/dev/"); 566 strcat(driveInfo->device_name, gDeviceInfos[i].device_path); 567 568 // on the first registration we need to open the 569 // control device to stay loaded 570 if (gRegistrationCount++ == 0) { 571 char path[B_PATH_NAME_LENGTH]; 572 strcpy(path, "/dev/"); 573 strcat(path, info.device_path); 574 gControlDeviceFD = open(path, O_RDONLY); 575 } 576 } 577 } 578 579 unlock_driver(); 580 return error; 581 } 582 case VIRTUAL_DRIVE_UNREGISTER_FILE: 583 case VIRTUAL_DRIVE_GET_INFO: 584 TRACE(("virtualdrive: VIRTUAL_DRIVE_UNREGISTER_FILE/" 585 "VIRTUAL_DRIVE_GET_INFO on control device\n")); 586 // these are called on used data files only! 587 return B_BAD_VALUE; 588 589 default: 590 TRACE(("virtualdrive: unknown ioctl: %lx (%lu)\n", op, op)); 591 return B_BAD_VALUE; 592 } 593 } else { 594 // used data device 595 switch (op) { 596 case B_GET_DEVICE_SIZE: 597 TRACE(("virtualdrive: B_GET_DEVICE_SIZE\n")); 598 *(size_t*)arg = info.size; 599 return B_OK; 600 601 case B_SET_NONBLOCKING_IO: 602 TRACE(("virtualdrive: B_SET_NONBLOCKING_IO\n")); 603 return B_OK; 604 605 case B_SET_BLOCKING_IO: 606 TRACE(("virtualdrive: B_SET_BLOCKING_IO\n")); 607 return B_OK; 608 609 case B_GET_READ_STATUS: 610 TRACE(("virtualdrive: B_GET_READ_STATUS\n")); 611 *(bool*)arg = true; 612 return B_OK; 613 614 case B_GET_WRITE_STATUS: 615 TRACE(("virtualdrive: B_GET_WRITE_STATUS\n")); 616 *(bool*)arg = true; 617 return B_OK; 618 619 case B_GET_ICON: 620 { 621 TRACE(("virtualdrive: B_GET_ICON\n")); 622 device_icon *icon = (device_icon *)arg; 623 624 if (icon->icon_size == kPrimaryImageWidth) { 625 memcpy(icon->icon_data, kPrimaryImageBits, kPrimaryImageWidth * kPrimaryImageHeight); 626 } else if (icon->icon_size == kSecondaryImageWidth) { 627 memcpy(icon->icon_data, kSecondaryImageBits, kSecondaryImageWidth * kSecondaryImageHeight); 628 } else 629 return B_ERROR; 630 631 return B_OK; 632 } 633 634 case B_GET_GEOMETRY: 635 TRACE(("virtualdrive: B_GET_GEOMETRY\n")); 636 *(device_geometry *)arg = info.geometry; 637 return B_OK; 638 639 case B_GET_BIOS_GEOMETRY: 640 { 641 TRACE(("virtualdrive: B_GET_BIOS_GEOMETRY\n")); 642 device_geometry *dg = (device_geometry *)arg; 643 dg->bytes_per_sector = 512; 644 dg->sectors_per_track = info.size / (512 * 1024); 645 dg->cylinder_count = 1024; 646 dg->head_count = 1; 647 dg->device_type = info.geometry.device_type; 648 dg->removable = info.geometry.removable; 649 dg->read_only = info.geometry.read_only; 650 dg->write_once = info.geometry.write_once; 651 return B_OK; 652 } 653 654 case B_GET_MEDIA_STATUS: 655 TRACE(("virtualdrive: B_GET_MEDIA_STATUS\n")); 656 *(status_t*)arg = B_NO_ERROR; 657 return B_OK; 658 659 case B_SET_UNINTERRUPTABLE_IO: 660 TRACE(("virtualdrive: B_SET_UNINTERRUPTABLE_IO\n")); 661 return B_OK; 662 663 case B_SET_INTERRUPTABLE_IO: 664 TRACE(("virtualdrive: B_SET_INTERRUPTABLE_IO\n")); 665 return B_OK; 666 667 case B_FLUSH_DRIVE_CACHE: 668 TRACE(("virtualdrive: B_FLUSH_DRIVE_CACHE\n")); 669 return B_OK; 670 671 case B_GET_BIOS_DRIVE_ID: 672 TRACE(("virtualdrive: B_GET_BIOS_DRIVE_ID\n")); 673 *(uint8*)arg = 0xF8; 674 return B_OK; 675 676 case B_GET_DRIVER_FOR_DEVICE: 677 case B_SET_DEVICE_SIZE: 678 case B_SET_PARTITION: 679 case B_FORMAT_DEVICE: 680 case B_EJECT_DEVICE: 681 case B_LOAD_MEDIA: 682 case B_GET_NEXT_OPEN_DEVICE: 683 TRACE(("virtualdrive: another ioctl: %lx (%lu)\n", op, op)); 684 return B_BAD_VALUE; 685 686 case VIRTUAL_DRIVE_REGISTER_FILE: 687 TRACE(("virtualdrive: VIRTUAL_DRIVE_REGISTER_FILE (data)\n")); 688 return B_BAD_VALUE; 689 case VIRTUAL_DRIVE_UNREGISTER_FILE: 690 { 691 TRACE(("virtualdrive: VIRTUAL_DRIVE_UNREGISTER_FILE\n")); 692 lock_driver(); 693 694 bool immediately = (bool)arg; 695 bool wasRegistered = info.registered; 696 697 info.registered = false; 698 699 // on the last unregistration we need to close the 700 // control device 701 if (wasRegistered && --gRegistrationCount == 0) { 702 close(gControlDeviceFD); 703 gControlDeviceFD = -1; 704 } 705 706 // if we "immediately" is true, we will stop our service immediately 707 // and close the underlying file, open it for other uses 708 if (immediately) { 709 TRACE(("virtualdrive: close file descriptor\n")); 710 // we cannot use uninit_device_info() here, since that does 711 // a little too much and would open the device for other 712 // uses. 713 close(info.fd); 714 info.fd = -1; 715 } 716 717 unlock_driver(); 718 return B_OK; 719 } 720 case VIRTUAL_DRIVE_GET_INFO: 721 { 722 TRACE(("virtualdrive: VIRTUAL_DRIVE_GET_INFO\n")); 723 724 virtual_drive_info *driveInfo = (virtual_drive_info *)arg; 725 if (driveInfo == NULL 726 || driveInfo->magic != VIRTUAL_DRIVE_MAGIC 727 || driveInfo->drive_info_size != sizeof(virtual_drive_info)) 728 return B_BAD_VALUE; 729 730 strcpy(driveInfo->file_name, info.file); 731 strcpy(driveInfo->device_name, "/dev/"); 732 strcat(driveInfo->device_name, info.device_path); 733 driveInfo->geometry = info.geometry; 734 driveInfo->use_geometry = true; 735 driveInfo->halted = info.fd == -1; 736 return B_OK; 737 } 738 739 default: 740 TRACE(("virtualdrive: unknown ioctl: %lx (%lu)\n", op, op)); 741 return B_BAD_VALUE; 742 } 743 } 744 745 } 746 747 748 static status_t 749 virtualdrive_free(void *cookie) 750 { 751 TRACE(("virtualdrive: free cookie()\n")); 752 return B_OK; 753 } 754 755 756 /* ----- 757 function pointers for the device hooks entry points 758 ----- */ 759 760 device_hooks sVirtualDriveHooks = { 761 virtualdrive_open, /* -> open entry point */ 762 virtualdrive_close, /* -> close entry point */ 763 virtualdrive_free, /* -> free cookie */ 764 virtualdrive_control, /* -> control entry point */ 765 virtualdrive_read, /* -> read entry point */ 766 virtualdrive_write /* -> write entry point */ 767 }; 768 769