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
45static int dev_index_for_path(const char *path);
46
47/* -----
48	null-terminated array of device names supported by this driver
49----- */
50
51static 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
66int32 api_version = B_CUR_DRIVER_API_VERSION;
67extern device_hooks sVirtualDriveHooks;
68
69lock driverlock;
70
71typedef 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)
85struct device_info			gDeviceInfos[kDeviceCount];
86
87static int32		gRegistrationCount	= 0;
88static int			gControlDeviceFD	= -1;
89
90static thread_id	gLockOwner			= -1;
91static int32		gLockOwnerNesting	= 0;
92
93
94// lock_driver
95void
96lock_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
108void
109unlock_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
120static inline
121bool
122is_valid_device_index(int32 index)
123{
124	return (index >= 0 && index < kDeviceCount);
125}
126
127
128// is_valid_data_device_index
129static inline
130bool
131is_valid_data_device_index(int32 index)
132{
133	return (is_valid_device_index(index) && index != kControlDevice);
134}
135
136
137// dev_index_for_path
138static
139int
140dev_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
152static
153void
154clear_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
171static
172status_t
173init_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
284static
285status_t
286uninit_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
305status_t
306init_hardware(void)
307{
308	TRACE(("virtualdrive: init_hardware\n"));
309	return B_OK;
310}
311
312
313status_t
314init_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
328void
329uninit_driver(void)
330{
331	TRACE(("virtualdrive: uninit\n"));
332	free_lock(&driverlock);
333}
334
335
336const char **
337publish_devices(void)
338{
339	TRACE(("virtualdrive: publish_devices\n"));
340	return sVirtualDriveName;
341}
342
343
344device_hooks *
345find_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
356static status_t
357virtualdrive_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
397static status_t
398virtualdrive_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
420static status_t
421virtualdrive_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
453static status_t
454virtualdrive_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
486static status_t
487virtualdrive_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
748static status_t
749virtualdrive_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
760device_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