xref: /haiku/src/tests/kits/storage/virtualdrive/virtualdrive.cpp (revision 4c8e85b316c35a9161f5a1c50ad70bc91c83a76f)
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