#include <fcntl.h>
#include <errno.h>
#include <malloc.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <KernelExport.h>
#include <Drivers.h>
#include <Errors.h>
#include "lock.h"
#include "virtualdrive.h"
#include "virtualdrive_icon.h"
[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
[2:07] <geist> that's the disable-cache ioctl
[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
[2:08] <marcus_o> thanks, I will try calling it
[2:08] <geist> and dont bother using dosfs as a host fs, it wont work
[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
[2:09] <geist> that ioctl is used on the swap file as well
[2:10] <marcus_o> I'm currently allocating memory in the driver's write() function hook
[2:10] <geist> cant do that
*/
#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;
void
lock_driver()
{
thread_id thread = find_thread(NULL);
if (gLockOwner != thread) {
LOCK(driverlock);
gLockOwner = thread;
}
gLockOwnerNesting++;
}
void
unlock_driver()
{
thread_id thread = find_thread(NULL);
if (gLockOwner == thread && --gLockOwnerNesting == 0) {
gLockOwner = -1;
UNLOCK(driverlock);
}
}
static inline
bool
is_valid_device_index(int32 index)
{
return (index >= 0 && index < kDeviceCount);
}
static inline
bool
is_valid_data_device_index(int32 index)
{
return (is_valid_device_index(index) && index != kControlDevice);
}
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;
}
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;
}
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);
int fd = open(initInfo->file_name, (readOnly ? O_RDONLY : O_RDWR));
if (fd < 0)
return errno;
status_t error = B_OK;
off_t fileSize = 0;
struct stat st;
if (fstat(fd, &st) == 0)
fileSize = st.st_size;
else
error = errno;
off_t size = 0;
if (error == B_OK) {
if (initInfo->use_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 {
uint32 blockSize = 512;
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;
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) {
fsync(fd);
if (ioctl(fd, 10000) != 0) {
dprintf("virtualdrive: disable caching ioctl failed\n");
return errno;
}
}
if (error == B_OK) {
info.fd = fd;
info.size = size;
info.unused = false;
info.registered = true;
strcpy(info.file, initInfo->file_name);
info.device_path = sVirtualDriveName[index];
} else {
close(fd);
if (info.open_count == 0)
clear_device_info(index);
}
return error;
}
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;
}
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");
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;
}
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;
}
*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) {
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));
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];
if (position > info.size)
position = info.size;
if (position + *numBytes > info.size)
*numBytes = info.size - position;
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));
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];
if (position > info.size)
position = info.size;
if (position + *numBytes > info.size)
*numBytes = info.size - position;
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) {
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();
for (i = 0; i < kDataDeviceCount; i++) {
if (!gDeviceInfos[i].unused
&& gDeviceInfos[i].fd == -1
&& !gDeviceInfos[i].registered
&& !strcmp(gDeviceInfos[i].file, driveInfo->file_name)) {
gDeviceInfos[i].unused = true;
error = B_OK;
break;
}
}
if (error != B_OK) {
for (i = 0; i < kDataDeviceCount; i++) {
if (gDeviceInfos[i].unused) {
error = B_OK;
break;
}
}
}
if (error == B_OK) {
error = init_device_info(i, driveInfo);
if (error == B_OK) {
strcpy(driveInfo->device_name, "/dev/");
strcat(driveInfo->device_name, gDeviceInfos[i].device_path);
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"));
return B_BAD_VALUE;
default:
TRACE(("virtualdrive: unknown ioctl: %lx (%lu)\n", op, op));
return B_BAD_VALUE;
}
} else {
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;
if (wasRegistered && --gRegistrationCount == 0) {
close(gControlDeviceFD);
gControlDeviceFD = -1;
}
if (immediately) {
TRACE(("virtualdrive: close file descriptor\n"));
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,
virtualdrive_close,
virtualdrive_free,
virtualdrive_control,
virtualdrive_read,
virtualdrive_write
};