xref: /freebsd/stand/common/vdisk.c (revision f374ba41f55c1a127303d92d830dd58eef2f5243)
1 /*-
2  * Copyright 2019 Toomas Soome <tsoome@me.com>
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
17  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23  * SUCH DAMAGE.
24  */
25 
26 #include <sys/cdefs.h>
27 __FBSDID("$FreeBSD$");
28 
29 #include <stand.h>
30 #include <stdarg.h>
31 #include <machine/_inttypes.h>
32 #include <bootstrap.h>
33 #include <sys/disk.h>
34 #include <sys/errno.h>
35 #include <sys/queue.h>
36 #include <sys/param.h>
37 #include <disk.h>
38 
39 static int vdisk_init(void);
40 static int vdisk_strategy(void *, int, daddr_t, size_t, char *, size_t *);
41 static int vdisk_open(struct open_file *, ...);
42 static int vdisk_close(struct open_file *);
43 static int vdisk_ioctl(struct open_file *, u_long, void *);
44 static int vdisk_print(int);
45 
46 struct devsw vdisk_dev = {
47 	.dv_name = "vdisk",
48 	.dv_type = DEVT_DISK,
49 	.dv_init = vdisk_init,
50 	.dv_strategy = vdisk_strategy,
51 	.dv_open = vdisk_open,
52 	.dv_close = vdisk_close,
53 	.dv_ioctl = vdisk_ioctl,
54 	.dv_print = vdisk_print,
55 	.dv_cleanup = nullsys,
56 	.dv_fmtdev = disk_fmtdev,
57 	.dv_parsedev = disk_parsedev,
58 };
59 
60 typedef STAILQ_HEAD(vdisk_info_list, vdisk_info) vdisk_info_list_t;
61 
62 typedef struct vdisk_info
63 {
64 	STAILQ_ENTRY(vdisk_info)	vdisk_link; /* link in device list */
65 	char			*vdisk_path;
66 	int			vdisk_unit;
67 	int			vdisk_fd;
68 	uint64_t		vdisk_size;	/* size in bytes */
69 	uint32_t		vdisk_sectorsz;
70 	uint32_t		vdisk_open;	/* reference counter */
71 } vdisk_info_t;
72 
73 static vdisk_info_list_t vdisk_list;	/* list of mapped vdisks. */
74 
75 static vdisk_info_t *
76 vdisk_get_info(struct devdesc *dev)
77 {
78 	vdisk_info_t *vd;
79 
80 	STAILQ_FOREACH(vd, &vdisk_list, vdisk_link) {
81 		if (vd->vdisk_unit == dev->d_unit)
82 			return (vd);
83 	}
84 	return (vd);
85 }
86 
87 COMMAND_SET(map_vdisk, "map-vdisk", "map file as virtual disk", command_mapvd);
88 
89 static int
90 command_mapvd(int argc, char *argv[])
91 {
92 	vdisk_info_t *vd, *p;
93 	struct stat sb;
94 
95 	if (argc != 2) {
96 		printf("usage: %s filename\n", argv[0]);
97 		return (CMD_ERROR);
98 	}
99 
100 	STAILQ_FOREACH(vd, &vdisk_list, vdisk_link) {
101 		if (strcmp(vd->vdisk_path, argv[1]) == 0) {
102 			printf("%s: file %s is already mapped as %s%d\n",
103 			    argv[0], argv[1], vdisk_dev.dv_name,
104 			    vd->vdisk_unit);
105 			return (CMD_ERROR);
106 		}
107 	}
108 
109 	if (stat(argv[1], &sb) < 0) {
110 		/*
111 		 * ENOSYS is really ENOENT because we did try to walk
112 		 * through devsw list to try to open this file.
113 		 */
114 		if (errno == ENOSYS)
115 			errno = ENOENT;
116 
117 		printf("%s: stat failed: %s\n", argv[0], strerror(errno));
118 		return (CMD_ERROR);
119 	}
120 
121 	/*
122 	 * Avoid mapping small files.
123 	 */
124 	if (sb.st_size < 1024 * 1024) {
125 		printf("%s: file %s is too small.\n", argv[0], argv[1]);
126 		return (CMD_ERROR);
127 	}
128 
129 	vd = calloc(1, sizeof (*vd));
130 	if (vd == NULL) {
131 		printf("%s: out of memory\n", argv[0]);
132 		return (CMD_ERROR);
133 	}
134 	vd->vdisk_path = strdup(argv[1]);
135 	if (vd->vdisk_path == NULL) {
136 		free (vd);
137 		printf("%s: out of memory\n", argv[0]);
138 		return (CMD_ERROR);
139 	}
140 	vd->vdisk_fd = open(vd->vdisk_path, O_RDONLY);
141 	if (vd->vdisk_fd < 0) {
142 		printf("%s: open failed: %s\n", argv[0], strerror(errno));
143 		free(vd->vdisk_path);
144 		free(vd);
145 		return (CMD_ERROR);
146 	}
147 
148 	vd->vdisk_size = sb.st_size;
149 	vd->vdisk_sectorsz = DEV_BSIZE;
150 	STAILQ_FOREACH(p, &vdisk_list, vdisk_link) {
151 		vdisk_info_t *n;
152 		if (p->vdisk_unit == vd->vdisk_unit) {
153 			vd->vdisk_unit++;
154 			continue;
155 		}
156 		n = STAILQ_NEXT(p, vdisk_link);
157 		if (p->vdisk_unit < vd->vdisk_unit) {
158 			if (n == NULL) {
159 				/* p is last elem */
160 				STAILQ_INSERT_TAIL(&vdisk_list, vd, vdisk_link);
161 				break;
162 			}
163 			if (n->vdisk_unit > vd->vdisk_unit) {
164 				/* p < vd < n */
165 				STAILQ_INSERT_AFTER(&vdisk_list, p, vd,
166 				    vdisk_link);
167 				break;
168 			}
169 			/* else n < vd or n == vd */
170 			vd->vdisk_unit++;
171 			continue;
172 		}
173 		/* p > vd only if p is the first element */
174 		STAILQ_INSERT_HEAD(&vdisk_list, vd, vdisk_link);
175 		break;
176 	}
177 
178 	/* if the list was empty or contiguous */
179 	if (p == NULL)
180 		STAILQ_INSERT_TAIL(&vdisk_list, vd, vdisk_link);
181 
182 	printf("%s: file %s is mapped as %s%d\n", argv[0], vd->vdisk_path,
183 	    vdisk_dev.dv_name, vd->vdisk_unit);
184 	return (CMD_OK);
185 }
186 
187 COMMAND_SET(unmap_vdisk, "unmap-vdisk", "unmap virtual disk", command_unmapvd);
188 
189 /*
190  * unmap-vdisk vdiskX
191  */
192 static int
193 command_unmapvd(int argc, char *argv[])
194 {
195 	size_t len;
196 	vdisk_info_t *vd;
197 	long unit;
198 	char *end;
199 
200 	if (argc != 2) {
201 		printf("usage: %s %sN\n", argv[0], vdisk_dev.dv_name);
202 		return (CMD_ERROR);
203 	}
204 
205 	len = strlen(vdisk_dev.dv_name);
206 	if (strncmp(vdisk_dev.dv_name, argv[1], len) != 0) {
207 		printf("%s: unknown device %s\n", argv[0], argv[1]);
208 		return (CMD_ERROR);
209 	}
210 	errno = 0;
211 	unit = strtol(argv[1] + len, &end, 10);
212 	if (errno != 0 || (*end != '\0' && strcmp(end, ":") != 0)) {
213 		printf("%s: unknown device %s\n", argv[0], argv[1]);
214 		return (CMD_ERROR);
215 	}
216 
217 	STAILQ_FOREACH(vd, &vdisk_list, vdisk_link) {
218 		if (vd->vdisk_unit == unit)
219 			break;
220 	}
221 
222 	if (vd == NULL) {
223 		printf("%s: unknown device %s\n", argv[0], argv[1]);
224 		return (CMD_ERROR);
225 	}
226 
227 	if (vd->vdisk_open != 0) {
228 		printf("%s: %s is in use, unable to unmap.\n",
229 		    argv[0], argv[1]);
230 		return (CMD_ERROR);
231 	}
232 
233 	STAILQ_REMOVE(&vdisk_list, vd, vdisk_info, vdisk_link);
234 	(void) close(vd->vdisk_fd);
235 	printf("%s (%s) unmapped\n", argv[1], vd->vdisk_path);
236 	free(vd->vdisk_path);
237 	free(vd);
238 
239 	return (CMD_OK);
240 }
241 
242 static int
243 vdisk_init(void)
244 {
245 	STAILQ_INIT(&vdisk_list);
246 	return (0);
247 }
248 
249 static int
250 vdisk_strategy(void *devdata, int rw, daddr_t blk, size_t size,
251     char *buf, size_t *rsize)
252 {
253 	struct disk_devdesc *dev;
254 	vdisk_info_t *vd;
255 	ssize_t rv;
256 
257 	dev = devdata;
258 	if (dev == NULL)
259 		return (EINVAL);
260 	vd = vdisk_get_info((struct devdesc *)dev);
261 	if (vd == NULL)
262 		return (EINVAL);
263 
264 	if (size == 0 || (size % 512) != 0)
265 		return (EIO);
266 
267 	if (dev->dd.d_dev->dv_type == DEVT_DISK) {
268 		daddr_t offset;
269 
270 		offset = dev->d_offset * vd->vdisk_sectorsz;
271 		offset /= 512;
272 		blk += offset;
273 	}
274 	if (lseek(vd->vdisk_fd, blk << 9, SEEK_SET) == -1)
275 		return (EIO);
276 
277 	errno = 0;
278 	switch (rw & F_MASK) {
279 	case F_READ:
280 		rv = read(vd->vdisk_fd, buf, size);
281 		break;
282 	case F_WRITE:
283 		rv = write(vd->vdisk_fd, buf, size);
284 		break;
285 	default:
286 		return (ENOSYS);
287 	}
288 
289 	if (errno == 0 && rsize != NULL) {
290 		*rsize = rv;
291 	}
292 	return (errno);
293 }
294 
295 static int
296 vdisk_open(struct open_file *f, ...)
297 {
298 	va_list args;
299 	struct disk_devdesc *dev;
300 	vdisk_info_t *vd;
301 	int rc = 0;
302 
303 	va_start(args, f);
304 	dev = va_arg(args, struct disk_devdesc *);
305 	va_end(args);
306 	if (dev == NULL)
307 		return (EINVAL);
308 	vd = vdisk_get_info((struct devdesc *)dev);
309 	if (vd == NULL)
310 		return (EINVAL);
311 
312 	if (dev->dd.d_dev->dv_type == DEVT_DISK) {
313 		rc = disk_open(dev, vd->vdisk_size, vd->vdisk_sectorsz);
314 	}
315 	if (rc == 0)
316 		vd->vdisk_open++;
317 	return (rc);
318 }
319 
320 static int
321 vdisk_close(struct open_file *f)
322 {
323 	struct disk_devdesc *dev;
324 	vdisk_info_t *vd;
325 
326 	dev = (struct disk_devdesc *)(f->f_devdata);
327 	if (dev == NULL)
328 		return (EINVAL);
329 	vd = vdisk_get_info((struct devdesc *)dev);
330 	if (vd == NULL)
331 		return (EINVAL);
332 
333 	vd->vdisk_open--;
334 	if (dev->dd.d_dev->dv_type == DEVT_DISK)
335 		return (disk_close(dev));
336 	return (0);
337 }
338 
339 static int
340 vdisk_ioctl(struct open_file *f, u_long cmd, void *data)
341 {
342 	struct disk_devdesc *dev;
343 	vdisk_info_t *vd;
344 	int rc;
345 
346 	dev = (struct disk_devdesc *)(f->f_devdata);
347 	if (dev == NULL)
348 		return (EINVAL);
349 	vd = vdisk_get_info((struct devdesc *)dev);
350 	if (vd == NULL)
351 		return (EINVAL);
352 
353 	if (dev->dd.d_dev->dv_type == DEVT_DISK) {
354 		rc = disk_ioctl(dev, cmd, data);
355 		if (rc != ENOTTY)
356 			return (rc);
357 	}
358 
359 	switch (cmd) {
360 	case DIOCGSECTORSIZE:
361 		*(u_int *)data = vd->vdisk_sectorsz;
362 		break;
363 	case DIOCGMEDIASIZE:
364 		*(uint64_t *)data = vd->vdisk_size;
365 		break;
366 	default:
367 		return (ENOTTY);
368 	}
369 	return (0);
370 }
371 
372 static int
373 vdisk_print(int verbose)
374 {
375 	int ret = 0;
376 	vdisk_info_t *vd;
377 	char line[80];
378 
379 	if (STAILQ_EMPTY(&vdisk_list))
380 		return (ret);
381 
382 	printf("%s devices:", vdisk_dev.dv_name);
383 	if ((ret = pager_output("\n")) != 0)
384 		return (ret);
385 
386 	STAILQ_FOREACH(vd, &vdisk_list, vdisk_link) {
387 		struct disk_devdesc vd_dev;
388 
389 		if (verbose) {
390 			printf("  %s", vd->vdisk_path);
391 			if ((ret = pager_output("\n")) != 0)
392 				break;
393 		}
394 		snprintf(line, sizeof(line),
395 		    "    %s%d", vdisk_dev.dv_name, vd->vdisk_unit);
396 		printf("%s:    %" PRIu64 " X %u blocks", line,
397 		    vd->vdisk_size / vd->vdisk_sectorsz,
398 		    vd->vdisk_sectorsz);
399 		if ((ret = pager_output("\n")) != 0)
400 			break;
401 
402 		vd_dev.dd.d_dev = &vdisk_dev;
403 		vd_dev.dd.d_unit = vd->vdisk_unit;
404 		vd_dev.d_slice = -1;
405 		vd_dev.d_partition = -1;
406 
407 		ret = disk_open(&vd_dev, vd->vdisk_size, vd->vdisk_sectorsz);
408 		if (ret == 0) {
409 			ret = disk_print(&vd_dev, line, verbose);
410 			disk_close(&vd_dev);
411 			if (ret != 0)
412 				break;
413 		} else {
414 			ret = 0;
415 		}
416 	}
417 
418 	return (ret);
419 }
420