1// mkvirtualdrive.cpp
2
3#include <ctype.h>
4#include <fcntl.h>
5#include <errno.h>
6#include <stdio.h>
7#include <stdlib.h>
8#include <string.h>
9#include <unistd.h>
10
11#include <Path.h>
12
13#include "virtualdrive.h"
14
15extern "C" const char * const*__libc_argv;
16
17const char *kUsage =
18"Usage: %s [ --install ] [ --size <size> ] <file>\n"
19"       %s --uninstall  <device>\n"
20"       %s --halt <device> (dangerous!)\n"
21"       %s --info  <device>\n"
22"       %s ( --help | -h )\n"
23;
24
25// print_usage
26static
27void
28print_usage(bool error = false)
29{
30	const char *name = "mkvirtualdrive";
31	BPath path;
32	if (path.SetTo(__libc_argv[0]) == B_OK)
33		name = path.Leaf();
34	fprintf((error ? stderr : stdout), kUsage, name, name, name, name);
35}
36
37
38// parse_size
39static
40bool
41parse_size(const char *str, off_t *_size)
42{
43	if (!str || !_size)
44		return false;
45	int32 len = strlen(str);
46	char buffer[22];
47	if (len == 0 || len + 1 > (int32)sizeof(buffer))
48		return false;
49	strcpy(buffer, str);
50	// check the last char for one of the suffixes
51	off_t suffix = 1;
52	if (!isdigit(buffer[len - 1])) {
53		switch (buffer[len - 1]) {
54			case 'K':
55				suffix = 1LL << 10;
56				break;
57			case 'M':
58				suffix = 1LL << 20;
59				break;
60			case 'G':
61				suffix = 1LL << 30;
62				break;
63			case 'T':
64				suffix = 1LL << 40;
65				break;
66			case 'P':
67				suffix = 1LL << 50;
68				break;
69			case 'E':
70				suffix = 1LL << 60;
71				break;
72			default:
73				return false;
74		}
75		buffer[len - 1] = '\0';
76		len--;
77		if (len == 0)
78			return false;
79	}
80	// all other chars must be digits
81	for (int i = 0; i < len; i++) {
82		if (!isdigit(buffer[i]))
83			return false;
84	}
85	off_t size = atoll(buffer);
86	*_size = size * suffix;
87	// check for overflow
88	if (*_size / suffix != size)
89		return false;
90	return true;
91}
92
93
94// install_file
95status_t
96install_file(const char *file, off_t size)
97{
98	// open the control device
99	int fd = open(VIRTUAL_DRIVE_CONTROL_DEVICE, O_RDONLY);
100	if (fd < 0) {
101		fprintf(stderr, "Failed to open control device: %s\n",
102				strerror(errno));
103		return errno;
104	}
105
106	// set up the info
107	virtual_drive_info info;
108	info.magic = VIRTUAL_DRIVE_MAGIC;
109	info.drive_info_size = sizeof(info);
110
111	strcpy(info.file_name, file);
112	if (size >= 0) {
113		info.use_geometry = true;
114		// fill in the geometry
115		// default to 512 bytes block size
116		uint32 blockSize = 512;
117		// Optimally we have only 1 block per sector and only one head.
118		// Since we have only a uint32 for the cylinder count, this won't work
119		// for files > 2TB. So, we set the head count to the minimally possible
120		// value.
121		off_t blocks = size / blockSize;
122		uint32 heads = (blocks + ULONG_MAX - 1) / ULONG_MAX;
123		if (heads == 0)
124			heads = 1;
125		info.geometry.bytes_per_sector = blockSize;
126	    info.geometry.sectors_per_track = 1;
127	    info.geometry.cylinder_count = blocks / heads;
128	    info.geometry.head_count = heads;
129	    info.geometry.device_type = B_DISK;	// TODO: Add a new constant.
130	    info.geometry.removable = false;
131	    info.geometry.read_only = false;
132	    info.geometry.write_once = false;
133	} else {
134		info.use_geometry = false;
135	}
136
137	// issue the ioctl
138	status_t error = B_OK;
139	if (ioctl(fd, VIRTUAL_DRIVE_REGISTER_FILE, &info) != 0) {
140		error = errno;
141		fprintf(stderr, "Failed to install device: %s\n", strerror(error));
142	} else {
143		printf("File `%s' registered as device `%s'.\n", file, info.device_name);
144	}
145	// close the control device
146	close(fd);
147	return error;
148}
149
150
151// uninstall_file
152status_t
153uninstall_file(const char *device, bool immediately)
154{
155	// open the device
156	int fd = open(device, O_RDONLY);
157	if (fd < 0) {
158		fprintf(stderr, "Failed to open device `%s': %s\n",
159				device, strerror(errno));
160		return errno;
161	}
162
163	// issue the ioctl
164	status_t error = B_OK;
165	if (ioctl(fd, VIRTUAL_DRIVE_UNREGISTER_FILE, immediately) != 0) {
166		error = errno;
167		fprintf(stderr, "Failed to uninstall device: %s\n", strerror(error));
168	}
169	// close the control device
170	close(fd);
171	return error;
172}
173
174
175status_t
176info_for_device(const char *device)
177{
178	// open the device
179	int fd = open(device, O_RDONLY);
180	if (fd < 0) {
181		fprintf(stderr, "Failed to open device `%s': %s\n",
182				device, strerror(errno));
183		return errno;
184	}
185
186	// set up the info
187	virtual_drive_info info;
188	info.magic = VIRTUAL_DRIVE_MAGIC;
189	info.drive_info_size = sizeof(info);
190
191	// issue the ioctl
192	status_t error = B_OK;
193	if (ioctl(fd, VIRTUAL_DRIVE_GET_INFO, &info) != 0) {
194		error = errno;
195		fprintf(stderr, "Failed to get device info: %s\n", strerror(error));
196	} else {
197		printf("Device `%s' points to file `%s'.\n", info.device_name, info.file_name);
198		off_t size = (off_t)info.geometry.bytes_per_sector
199				* info.geometry.sectors_per_track
200				* info.geometry.cylinder_count
201				* info.geometry.head_count;
202		printf("\tdisk size is %Ld bytes (%g MB)\n", size, size / (1024.0 * 1024));
203		if (info.halted)
204			printf("\tdevice is currently halted.\n");
205	}
206	// close the control device
207	close(fd);
208	return error;
209}
210
211
212// main
213int
214main(int argc, const char **argv)
215{
216	status_t error = B_OK;
217	int argIndex = 1;
218	enum { INSTALL, UNINSTALL, INFO } mode = INSTALL;
219	bool immediately = false;
220	off_t size = -1;
221
222	// parse options
223	for (; error == B_OK && argIndex < argc
224		   && argv[argIndex][0] == '-'; argIndex++) {
225		const char *arg = argv[argIndex];
226		if (arg[1] == '-') {
227			// "--" option
228			arg += 2;
229			if (!strcmp(arg, "install")) {
230				mode = INSTALL;
231			} else if (!strcmp(arg, "size")) {
232				argIndex++;
233				if (argIndex >= argc) {
234					fprintf(stderr, "Parameter expected for `--%s'.\n", arg);
235					print_usage(1);
236					return 1;
237				}
238				if (!parse_size(argv[argIndex], &size)) {
239					fprintf(stderr, "Parameter for `--%s' must be of the form "
240							"`<number>[K|M|G|T|P|E]'.\n", arg);
241					print_usage(1);
242					return 1;
243				}
244			} else if (!strcmp(arg, "uninstall")) {
245				mode = UNINSTALL;
246			} else if (!strcmp(arg, "halt")) {
247				mode = UNINSTALL;
248				immediately = true;
249			} else if (!strcmp(arg, "info")) {
250				mode = INFO;
251			} else if (!strcmp(arg, "help")) {
252				print_usage();
253				return 0;
254			} else {
255				fprintf(stderr, "Invalid option `-%s'.\n", arg);
256				print_usage(true);
257				return 1;
258			}
259		} else {
260			// "-" options
261			arg++;
262			int32 count = strlen(arg);
263			for (int i = 0; error == B_OK && i < count; i++) {
264				switch (arg[i]) {
265					case 'h':
266						print_usage();
267						return 0;
268					default:
269						fprintf(stderr, "Invalid option `-%c'.\n", arg[i]);
270						print_usage(true);
271						return 1;
272				}
273			}
274		}
275	}
276
277	// parse rest (the file name)
278	if (argIndex != argc - 1) {
279		print_usage(true);
280		return 1;
281	}
282	const char *file = argv[argIndex];
283
284	// do the job
285	switch (mode) {
286		case INSTALL:
287			error = install_file(file, size);
288			break;
289		case UNINSTALL:
290			error = uninstall_file(file, immediately);
291			break;
292		case INFO:
293			error = info_for_device(file);
294			break;
295	}
296
297	return (error == B_OK ? 0 : 1);
298}
299
300