// ---------------------------------------------------------------------- // This software is part of the Haiku distribution and is covered // by the MIT License. // // File Name: virtualdrive.c // // Description: Driver that emulates virtual drives. // // Author: Marcus Overhagen // Ingo Weinhold // Axel Doerfler // ---------------------------------------------------------------------- #include #include #include #include #include #include #include #include #include #include "lock.h" #include "virtualdrive.h" #include "virtualdrive_icon.h" /* [2:07] 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 [2:07] that's the disable-cache ioctl [2:08] 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 [2:08] thanks, I will try calling it [2:08] and dont bother using dosfs as a host fs, it wont work [2:09] 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 [2:09] that ioctl is used on the swap file as well [2:10] I'm currently allocating memory in the driver's write() function hook [2:10] cant do that */ //#define TRACE(x) dprintf x #define TRACE(x) ; #define MB (1024LL * 1024LL) static int dev_index_for_path(const char *path); /* ----- null-terminated array of device names supported by this driver ----- */ static const char *sVirtualDriveName[] = { VIRTUAL_DRIVE_DIRECTORY_REL "/0", VIRTUAL_DRIVE_DIRECTORY_REL "/1", VIRTUAL_DRIVE_DIRECTORY_REL "/2", VIRTUAL_DRIVE_DIRECTORY_REL "/3", VIRTUAL_DRIVE_DIRECTORY_REL "/4", VIRTUAL_DRIVE_DIRECTORY_REL "/5", VIRTUAL_DRIVE_DIRECTORY_REL "/6", VIRTUAL_DRIVE_DIRECTORY_REL "/7", VIRTUAL_DRIVE_DIRECTORY_REL "/8", VIRTUAL_DRIVE_DIRECTORY_REL "/9", VIRTUAL_DRIVE_CONTROL_DEVICE_REL, NULL }; int32 api_version = B_CUR_DRIVER_API_VERSION; extern device_hooks sVirtualDriveHooks; lock driverlock; typedef struct device_info { int32 open_count; int fd; off_t size; bool unused; bool registered; char file[B_PATH_NAME_LENGTH]; const char *device_path; device_geometry geometry; } device_info; #define kDeviceCount 11 #define kDataDeviceCount (kDeviceCount - 1) #define kControlDevice (kDeviceCount - 1) struct device_info gDeviceInfos[kDeviceCount]; static int32 gRegistrationCount = 0; static int gControlDeviceFD = -1; static thread_id gLockOwner = -1; static int32 gLockOwnerNesting = 0; // lock_driver void lock_driver() { thread_id thread = find_thread(NULL); if (gLockOwner != thread) { LOCK(driverlock); gLockOwner = thread; } gLockOwnerNesting++; } // unlock_driver void unlock_driver() { thread_id thread = find_thread(NULL); if (gLockOwner == thread && --gLockOwnerNesting == 0) { gLockOwner = -1; UNLOCK(driverlock); } } // is_valid_device_index static inline bool is_valid_device_index(int32 index) { return (index >= 0 && index < kDeviceCount); } // is_valid_data_device_index static inline bool is_valid_data_device_index(int32 index) { return (is_valid_device_index(index) && index != kControlDevice); } // dev_index_for_path static int dev_index_for_path(const char *path) { int i; for (i = 0; i < kDeviceCount; i++) { if (!strcmp(path, gDeviceInfos[i].device_path)) return i; } return -1; } // clear_device_info static void clear_device_info(int32 index) { TRACE(("virtualdrive: clear_device_info(%ld)\n", index)); device_info &info = gDeviceInfos[index]; info.open_count = 0; info.fd = -1; info.size = 0; info.unused = (index != kDeviceCount - 1); info.registered = !info.unused; info.file[0] = '\0'; info.device_path = sVirtualDriveName[index]; info.geometry.read_only = true; } // init_device_info static status_t init_device_info(int32 index, virtual_drive_info *initInfo) { if (!is_valid_data_device_index(index) || !initInfo) return B_BAD_VALUE; device_info &info = gDeviceInfos[index]; if (!info.unused) return B_BAD_VALUE; bool readOnly = (initInfo->use_geometry && initInfo->geometry.read_only); // open the file int fd = open(initInfo->file_name, (readOnly ? O_RDONLY : O_RDWR)); if (fd < 0) return errno; status_t error = B_OK; // get the file size off_t fileSize = 0; struct stat st; if (fstat(fd, &st) == 0) fileSize = st.st_size; else error = errno; // If we shall use the supplied geometry, we enlarge the file, if // necessary. Otherwise we fill in the geometry according to the size of the file. off_t size = 0; if (error == B_OK) { if (initInfo->use_geometry) { // use the supplied geometry info.geometry = initInfo->geometry; size = (off_t)info.geometry.bytes_per_sector * info.geometry.sectors_per_track * info.geometry.cylinder_count * info.geometry.head_count; if (size > fileSize) { if (!readOnly) { if (ftruncate(fd, size) != 0) error = errno; } else error = B_NOT_ALLOWED; } } else { // fill in the geometry // default to 512 bytes block size uint32 blockSize = 512; // Optimally we have only 1 block per sector and only one head. // Since we have only a uint32 for the cylinder count, this won't work // for files > 2TB. So, we set the head count to the minimally possible // value. off_t blocks = fileSize / blockSize; uint32 heads = (blocks + ULONG_MAX - 1) / ULONG_MAX; if (heads == 0) heads = 1; info.geometry.bytes_per_sector = blockSize; info.geometry.sectors_per_track = 1; info.geometry.cylinder_count = blocks / heads; info.geometry.head_count = heads; info.geometry.device_type = B_DISK; // TODO: Add a new constant. info.geometry.removable = false; info.geometry.read_only = false; info.geometry.write_once = false; size = (off_t)info.geometry.bytes_per_sector * info.geometry.sectors_per_track * info.geometry.cylinder_count * info.geometry.head_count; } } if (error == B_OK) { // Disable caching for underlying file! (else this driver will deadlock) // We probably cannot resize the file once the cache has been disabled! // This applies to BeOS only: // Work around a bug in BFS: the file is not synced before the cache is // turned off, and thus causing possible inconsistencies. // Unfortunately, this only solves one half of the issue; there is // no way to remove the blocks in the cache, so changes made to the // image have the chance to get lost. fsync(fd); // This is a special reserved ioctl() opcode not defined anywhere in // the Be headers. if (ioctl(fd, 10000) != 0) { dprintf("virtualdrive: disable caching ioctl failed\n"); return errno; } } // fill in the rest of the device_info structure if (error == B_OK) { // open_count doesn't have to be changed here (virtualdrive_open() will do that for us) info.fd = fd; info.size = size; info.unused = false; info.registered = true; strcpy(info.file, initInfo->file_name); info.device_path = sVirtualDriveName[index]; } else { // cleanup on error close(fd); if (info.open_count == 0) clear_device_info(index); } return error; } // uninit_device_info static status_t uninit_device_info(int32 index) { if (!is_valid_data_device_index(index)) return B_BAD_VALUE; device_info &info = gDeviceInfos[index]; if (info.unused) return B_BAD_VALUE; close(info.fd); clear_device_info(index); return B_OK; } // #pragma mark - // public driver API status_t init_hardware(void) { TRACE(("virtualdrive: init_hardware\n")); return B_OK; } status_t init_driver(void) { TRACE(("virtualdrive: init\n")); new_lock(&driverlock, "virtualdrive lock"); // init the device infos for (int32 i = 0; i < kDeviceCount; i++) clear_device_info(i); return B_OK; } void uninit_driver(void) { TRACE(("virtualdrive: uninit\n")); free_lock(&driverlock); } const char ** publish_devices(void) { TRACE(("virtualdrive: publish_devices\n")); return sVirtualDriveName; } device_hooks * find_device(const char* name) { TRACE(("virtualdrive: find_device(%s)\n", name)); return &sVirtualDriveHooks; } // #pragma mark - // the device hooks static status_t virtualdrive_open(const char *name, uint32 flags, void **cookie) { TRACE(("virtualdrive: open %s\n",name)); *cookie = (void *)-1; lock_driver(); int32 devIndex = dev_index_for_path(name); TRACE(("virtualdrive: devIndex %ld!\n", devIndex)); if (!is_valid_device_index(devIndex)) { TRACE(("virtualdrive: wrong index!\n")); unlock_driver(); return B_ERROR; } if (gDeviceInfos[devIndex].unused) { TRACE(("virtualdrive: device is unused!\n")); unlock_driver(); return B_ERROR; } if (!gDeviceInfos[devIndex].registered) { TRACE(("virtualdrive: device has been unregistered!\n")); unlock_driver(); return B_ERROR; } // store index in cookie *cookie = (void *)devIndex; gDeviceInfos[devIndex].open_count++; unlock_driver(); return B_OK; } static status_t virtualdrive_close(void *cookie) { int32 devIndex = (int)cookie; TRACE(("virtualdrive: close() devIndex = %ld\n", devIndex)); if (!is_valid_data_device_index(devIndex)) return B_OK; lock_driver(); gDeviceInfos[devIndex].open_count--; if (gDeviceInfos[devIndex].open_count == 0 && !gDeviceInfos[devIndex].registered) { // The last FD is closed and the device has been unregistered. Free its info. uninit_device_info(devIndex); } unlock_driver(); return B_OK; } static status_t virtualdrive_read(void *cookie, off_t position, void *buffer, size_t *numBytes) { TRACE(("virtualdrive: read pos = 0x%08Lx, bytes = 0x%08lx\n", position, *numBytes)); // check parameters int devIndex = (int)cookie; if (devIndex == kControlDevice) { TRACE(("virtualdrive: reading from control device not allowed\n")); return B_NOT_ALLOWED; } if (position < 0) return B_BAD_VALUE; lock_driver(); device_info &info = gDeviceInfos[devIndex]; // adjust position and numBytes according to the file size if (position > info.size) position = info.size; if (position + *numBytes > info.size) *numBytes = info.size - position; // read status_t error = B_OK; ssize_t bytesRead = read_pos(info.fd, position, buffer, *numBytes); if (bytesRead < 0) error = errno; else *numBytes = bytesRead; unlock_driver(); return error; } static status_t virtualdrive_write(void *cookie, off_t position, const void *buffer, size_t *numBytes) { TRACE(("virtualdrive: write pos = 0x%08Lx, bytes = 0x%08lx\n", position, *numBytes)); // check parameters int devIndex = (int)cookie; if (devIndex == kControlDevice) { TRACE(("virtualdrive: writing to control device not allowed\n")); return B_NOT_ALLOWED; } if (position < 0) return B_BAD_VALUE; lock_driver(); device_info &info = gDeviceInfos[devIndex]; // adjust position and numBytes according to the file size if (position > info.size) position = info.size; if (position + *numBytes > info.size) *numBytes = info.size - position; // read status_t error = B_OK; ssize_t bytesRead = write_pos(info.fd, position, buffer, *numBytes); if (bytesRead < 0) error = errno; else *numBytes = bytesRead; unlock_driver(); return error; } static status_t virtualdrive_control(void *cookie, uint32 op, void *arg, size_t len) { TRACE(("virtualdrive: ioctl\n")); int devIndex = (int)cookie; device_info &info = gDeviceInfos[devIndex]; if (devIndex == kControlDevice || info.unused) { // control device or unused data device switch (op) { case B_GET_DEVICE_SIZE: case B_SET_NONBLOCKING_IO: case B_SET_BLOCKING_IO: case B_GET_READ_STATUS: case B_GET_WRITE_STATUS: case B_GET_ICON: case B_GET_GEOMETRY: case B_GET_BIOS_GEOMETRY: case B_GET_MEDIA_STATUS: case B_SET_UNINTERRUPTABLE_IO: case B_SET_INTERRUPTABLE_IO: case B_FLUSH_DRIVE_CACHE: case B_GET_BIOS_DRIVE_ID: case B_GET_DRIVER_FOR_DEVICE: case B_SET_DEVICE_SIZE: case B_SET_PARTITION: case B_FORMAT_DEVICE: case B_EJECT_DEVICE: case B_LOAD_MEDIA: case B_GET_NEXT_OPEN_DEVICE: TRACE(("virtualdrive: another ioctl: %lx (%lu)\n", op, op)); return B_BAD_VALUE; case VIRTUAL_DRIVE_REGISTER_FILE: { TRACE(("virtualdrive: VIRTUAL_DRIVE_REGISTER_FILE\n")); virtual_drive_info *driveInfo = (virtual_drive_info *)arg; if (devIndex != kControlDevice || driveInfo == NULL || driveInfo->magic != VIRTUAL_DRIVE_MAGIC || driveInfo->drive_info_size != sizeof(virtual_drive_info)) return B_BAD_VALUE; status_t error = B_ERROR; int32 i; lock_driver(); // first, look if we already have opened that file and see // if it's available to us which happens when it has been // halted but is still in use by other components for (i = 0; i < kDataDeviceCount; i++) { if (!gDeviceInfos[i].unused && gDeviceInfos[i].fd == -1 && !gDeviceInfos[i].registered && !strcmp(gDeviceInfos[i].file, driveInfo->file_name)) { // mark device as unused, so that init_device_info() will succeed gDeviceInfos[i].unused = true; error = B_OK; break; } } if (error != B_OK) { // find an unused data device for (i = 0; i < kDataDeviceCount; i++) { if (gDeviceInfos[i].unused) { error = B_OK; break; } } } if (error == B_OK) { // we found a device slot, let's initialize it error = init_device_info(i, driveInfo); if (error == B_OK) { // return the device path strcpy(driveInfo->device_name, "/dev/"); strcat(driveInfo->device_name, gDeviceInfos[i].device_path); // on the first registration we need to open the // control device to stay loaded if (gRegistrationCount++ == 0) { char path[B_PATH_NAME_LENGTH]; strcpy(path, "/dev/"); strcat(path, info.device_path); gControlDeviceFD = open(path, O_RDONLY); } } } unlock_driver(); return error; } case VIRTUAL_DRIVE_UNREGISTER_FILE: case VIRTUAL_DRIVE_GET_INFO: TRACE(("virtualdrive: VIRTUAL_DRIVE_UNREGISTER_FILE/" "VIRTUAL_DRIVE_GET_INFO on control device\n")); // these are called on used data files only! return B_BAD_VALUE; default: TRACE(("virtualdrive: unknown ioctl: %lx (%lu)\n", op, op)); return B_BAD_VALUE; } } else { // used data device switch (op) { case B_GET_DEVICE_SIZE: TRACE(("virtualdrive: B_GET_DEVICE_SIZE\n")); *(size_t*)arg = info.size; return B_OK; case B_SET_NONBLOCKING_IO: TRACE(("virtualdrive: B_SET_NONBLOCKING_IO\n")); return B_OK; case B_SET_BLOCKING_IO: TRACE(("virtualdrive: B_SET_BLOCKING_IO\n")); return B_OK; case B_GET_READ_STATUS: TRACE(("virtualdrive: B_GET_READ_STATUS\n")); *(bool*)arg = true; return B_OK; case B_GET_WRITE_STATUS: TRACE(("virtualdrive: B_GET_WRITE_STATUS\n")); *(bool*)arg = true; return B_OK; case B_GET_ICON: { TRACE(("virtualdrive: B_GET_ICON\n")); device_icon *icon = (device_icon *)arg; if (icon->icon_size == kPrimaryImageWidth) { memcpy(icon->icon_data, kPrimaryImageBits, kPrimaryImageWidth * kPrimaryImageHeight); } else if (icon->icon_size == kSecondaryImageWidth) { memcpy(icon->icon_data, kSecondaryImageBits, kSecondaryImageWidth * kSecondaryImageHeight); } else return B_ERROR; return B_OK; } case B_GET_GEOMETRY: TRACE(("virtualdrive: B_GET_GEOMETRY\n")); *(device_geometry *)arg = info.geometry; return B_OK; case B_GET_BIOS_GEOMETRY: { TRACE(("virtualdrive: B_GET_BIOS_GEOMETRY\n")); device_geometry *dg = (device_geometry *)arg; dg->bytes_per_sector = 512; dg->sectors_per_track = info.size / (512 * 1024); dg->cylinder_count = 1024; dg->head_count = 1; dg->device_type = info.geometry.device_type; dg->removable = info.geometry.removable; dg->read_only = info.geometry.read_only; dg->write_once = info.geometry.write_once; return B_OK; } case B_GET_MEDIA_STATUS: TRACE(("virtualdrive: B_GET_MEDIA_STATUS\n")); *(status_t*)arg = B_NO_ERROR; return B_OK; case B_SET_UNINTERRUPTABLE_IO: TRACE(("virtualdrive: B_SET_UNINTERRUPTABLE_IO\n")); return B_OK; case B_SET_INTERRUPTABLE_IO: TRACE(("virtualdrive: B_SET_INTERRUPTABLE_IO\n")); return B_OK; case B_FLUSH_DRIVE_CACHE: TRACE(("virtualdrive: B_FLUSH_DRIVE_CACHE\n")); return B_OK; case B_GET_BIOS_DRIVE_ID: TRACE(("virtualdrive: B_GET_BIOS_DRIVE_ID\n")); *(uint8*)arg = 0xF8; return B_OK; case B_GET_DRIVER_FOR_DEVICE: case B_SET_DEVICE_SIZE: case B_SET_PARTITION: case B_FORMAT_DEVICE: case B_EJECT_DEVICE: case B_LOAD_MEDIA: case B_GET_NEXT_OPEN_DEVICE: TRACE(("virtualdrive: another ioctl: %lx (%lu)\n", op, op)); return B_BAD_VALUE; case VIRTUAL_DRIVE_REGISTER_FILE: TRACE(("virtualdrive: VIRTUAL_DRIVE_REGISTER_FILE (data)\n")); return B_BAD_VALUE; case VIRTUAL_DRIVE_UNREGISTER_FILE: { TRACE(("virtualdrive: VIRTUAL_DRIVE_UNREGISTER_FILE\n")); lock_driver(); bool immediately = (bool)arg; bool wasRegistered = info.registered; info.registered = false; // on the last unregistration we need to close the // control device if (wasRegistered && --gRegistrationCount == 0) { close(gControlDeviceFD); gControlDeviceFD = -1; } // if we "immediately" is true, we will stop our service immediately // and close the underlying file, open it for other uses if (immediately) { TRACE(("virtualdrive: close file descriptor\n")); // we cannot use uninit_device_info() here, since that does // a little too much and would open the device for other // uses. close(info.fd); info.fd = -1; } unlock_driver(); return B_OK; } case VIRTUAL_DRIVE_GET_INFO: { TRACE(("virtualdrive: VIRTUAL_DRIVE_GET_INFO\n")); virtual_drive_info *driveInfo = (virtual_drive_info *)arg; if (driveInfo == NULL || driveInfo->magic != VIRTUAL_DRIVE_MAGIC || driveInfo->drive_info_size != sizeof(virtual_drive_info)) return B_BAD_VALUE; strcpy(driveInfo->file_name, info.file); strcpy(driveInfo->device_name, "/dev/"); strcat(driveInfo->device_name, info.device_path); driveInfo->geometry = info.geometry; driveInfo->use_geometry = true; driveInfo->halted = info.fd == -1; return B_OK; } default: TRACE(("virtualdrive: unknown ioctl: %lx (%lu)\n", op, op)); return B_BAD_VALUE; } } } static status_t virtualdrive_free(void *cookie) { TRACE(("virtualdrive: free cookie()\n")); return B_OK; } /* ----- function pointers for the device hooks entry points ----- */ device_hooks sVirtualDriveHooks = { virtualdrive_open, /* -> open entry point */ virtualdrive_close, /* -> close entry point */ virtualdrive_free, /* -> free cookie */ virtualdrive_control, /* -> control entry point */ virtualdrive_read, /* -> read entry point */ virtualdrive_write /* -> write entry point */ };