xref: /freebsd/stand/common/vdisk.c (revision b0d29bc47dba79f6f38e67eabadfb4b32ffd9390)
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 = NULL
56 };
57 
58 typedef STAILQ_HEAD(vdisk_info_list, vdisk_info) vdisk_info_list_t;
59 
60 typedef struct vdisk_info
61 {
62 	STAILQ_ENTRY(vdisk_info)	vdisk_link; /* link in device list */
63 	char			*vdisk_path;
64 	int			vdisk_unit;
65 	int			vdisk_fd;
66 	uint64_t		vdisk_size;	/* size in bytes */
67 	uint32_t		vdisk_sectorsz;
68 	uint32_t		vdisk_open;	/* reference counter */
69 } vdisk_info_t;
70 
71 static vdisk_info_list_t vdisk_list;	/* list of mapped vdisks. */
72 
73 static vdisk_info_t *
74 vdisk_get_info(struct devdesc *dev)
75 {
76 	vdisk_info_t *vd;
77 
78 	STAILQ_FOREACH(vd, &vdisk_list, vdisk_link) {
79 		if (vd->vdisk_unit == dev->d_unit)
80 			return (vd);
81 	}
82 	return (vd);
83 }
84 
85 COMMAND_SET(map_vdisk, "map-vdisk", "map file as virtual disk", command_mapvd);
86 
87 static int
88 command_mapvd(int argc, char *argv[])
89 {
90 	vdisk_info_t *vd, *p;
91 	struct stat sb;
92 
93 	if (argc != 2) {
94 		printf("usage: %s filename\n", argv[0]);
95 		return (CMD_ERROR);
96 	}
97 
98 	STAILQ_FOREACH(vd, &vdisk_list, vdisk_link) {
99 		if (strcmp(vd->vdisk_path, argv[1]) == 0) {
100 			printf("%s: file %s is already mapped as %s%d\n",
101 			    argv[0], argv[1], vdisk_dev.dv_name,
102 			    vd->vdisk_unit);
103 			return (CMD_ERROR);
104 		}
105 	}
106 
107 	if (stat(argv[1], &sb) < 0) {
108 		/*
109 		 * ENOSYS is really ENOENT because we did try to walk
110 		 * through devsw list to try to open this file.
111 		 */
112 		if (errno == ENOSYS)
113 			errno = ENOENT;
114 
115 		printf("%s: stat failed: %s\n", argv[0], strerror(errno));
116 		return (CMD_ERROR);
117 	}
118 
119 	/*
120 	 * Avoid mapping small files.
121 	 */
122 	if (sb.st_size < 1024 * 1024) {
123 		printf("%s: file %s is too small.\n", argv[0], argv[1]);
124 		return (CMD_ERROR);
125 	}
126 
127 	vd = calloc(1, sizeof (*vd));
128 	if (vd == NULL) {
129 		printf("%s: out of memory\n", argv[0]);
130 		return (CMD_ERROR);
131 	}
132 	vd->vdisk_path = strdup(argv[1]);
133 	if (vd->vdisk_path == NULL) {
134 		free (vd);
135 		printf("%s: out of memory\n", argv[0]);
136 		return (CMD_ERROR);
137 	}
138 	vd->vdisk_fd = open(vd->vdisk_path, O_RDONLY);
139 	if (vd->vdisk_fd < 0) {
140 		printf("%s: open failed: %s\n", argv[0], strerror(errno));
141 		free(vd->vdisk_path);
142 		free(vd);
143 		return (CMD_ERROR);
144 	}
145 
146 	vd->vdisk_size = sb.st_size;
147 	vd->vdisk_sectorsz = DEV_BSIZE;
148 	STAILQ_FOREACH(p, &vdisk_list, vdisk_link) {
149 		vdisk_info_t *n;
150 		if (p->vdisk_unit == vd->vdisk_unit) {
151 			vd->vdisk_unit++;
152 			continue;
153 		}
154 		n = STAILQ_NEXT(p, vdisk_link);
155 		if (p->vdisk_unit < vd->vdisk_unit) {
156 			if (n == NULL) {
157 				/* p is last elem */
158 				STAILQ_INSERT_TAIL(&vdisk_list, vd, vdisk_link);
159 				break;
160 			}
161 			if (n->vdisk_unit > vd->vdisk_unit) {
162 				/* p < vd < n */
163 				STAILQ_INSERT_AFTER(&vdisk_list, p, vd,
164 				    vdisk_link);
165 				break;
166 			}
167 			/* else n < vd or n == vd */
168 			vd->vdisk_unit++;
169 			continue;
170 		}
171 		/* p > vd only if p is the first element */
172 		STAILQ_INSERT_HEAD(&vdisk_list, vd, vdisk_link);
173 		break;
174 	}
175 
176 	/* if the list was empty or contiguous */
177 	if (p == NULL)
178 		STAILQ_INSERT_TAIL(&vdisk_list, vd, vdisk_link);
179 
180 	printf("%s: file %s is mapped as %s%d\n", argv[0], vd->vdisk_path,
181 	    vdisk_dev.dv_name, vd->vdisk_unit);
182 	return (CMD_OK);
183 }
184 
185 COMMAND_SET(unmap_vdisk, "unmap-vdisk", "unmap virtual disk", command_unmapvd);
186 
187 /*
188  * unmap-vdisk vdiskX
189  */
190 static int
191 command_unmapvd(int argc, char *argv[])
192 {
193 	size_t len;
194 	vdisk_info_t *vd;
195 	long unit;
196 	char *end;
197 
198 	if (argc != 2) {
199 		printf("usage: %s %sN\n", argv[0], vdisk_dev.dv_name);
200 		return (CMD_ERROR);
201 	}
202 
203 	len = strlen(vdisk_dev.dv_name);
204 	if (strncmp(vdisk_dev.dv_name, argv[1], len) != 0) {
205 		printf("%s: unknown device %s\n", argv[0], argv[1]);
206 		return (CMD_ERROR);
207 	}
208 	errno = 0;
209 	unit = strtol(argv[1] + len, &end, 10);
210 	if (errno != 0 || (*end != '\0' && strcmp(end, ":") != 0)) {
211 		printf("%s: unknown device %s\n", argv[0], argv[1]);
212 		return (CMD_ERROR);
213 	}
214 
215 	STAILQ_FOREACH(vd, &vdisk_list, vdisk_link) {
216 		if (vd->vdisk_unit == unit)
217 			break;
218 	}
219 
220 	if (vd == NULL) {
221 		printf("%s: unknown device %s\n", argv[0], argv[1]);
222 		return (CMD_ERROR);
223 	}
224 
225 	if (vd->vdisk_open != 0) {
226 		printf("%s: %s is in use, unable to unmap.\n",
227 		    argv[0], argv[1]);
228 		return (CMD_ERROR);
229 	}
230 
231 	STAILQ_REMOVE(&vdisk_list, vd, vdisk_info, vdisk_link);
232 	(void) close(vd->vdisk_fd);
233 	printf("%s (%s) unmapped\n", argv[1], vd->vdisk_path);
234 	free(vd->vdisk_path);
235 	free(vd);
236 
237 	return (CMD_OK);
238 }
239 
240 static int
241 vdisk_init(void)
242 {
243 	STAILQ_INIT(&vdisk_list);
244 	return (0);
245 }
246 
247 static int
248 vdisk_strategy(void *devdata, int rw, daddr_t blk, size_t size,
249     char *buf, size_t *rsize)
250 {
251 	struct disk_devdesc *dev;
252 	vdisk_info_t *vd;
253 	ssize_t rv;
254 
255 	dev = devdata;
256 	if (dev == NULL)
257 		return (EINVAL);
258 	vd = vdisk_get_info((struct devdesc *)dev);
259 	if (vd == NULL)
260 		return (EINVAL);
261 
262 	if (size == 0 || (size % 512) != 0)
263 		return (EIO);
264 
265 	if (dev->dd.d_dev->dv_type == DEVT_DISK) {
266 		daddr_t offset;
267 
268 		offset = dev->d_offset * vd->vdisk_sectorsz;
269 		offset /= 512;
270 		blk += offset;
271 	}
272 	if (lseek(vd->vdisk_fd, blk << 9, SEEK_SET) == -1)
273 		return (EIO);
274 
275 	errno = 0;
276 	switch (rw & F_MASK) {
277 	case F_READ:
278 		rv = read(vd->vdisk_fd, buf, size);
279 		break;
280 	case F_WRITE:
281 		rv = write(vd->vdisk_fd, buf, size);
282 		break;
283 	default:
284 		return (ENOSYS);
285 	}
286 
287 	if (errno == 0 && rsize != NULL) {
288 		*rsize = rv;
289 	}
290 	return (errno);
291 }
292 
293 static int
294 vdisk_open(struct open_file *f, ...)
295 {
296 	va_list args;
297 	struct disk_devdesc *dev;
298 	vdisk_info_t *vd;
299 	int rc = 0;
300 
301 	va_start(args, f);
302 	dev = va_arg(args, struct disk_devdesc *);
303 	va_end(args);
304 	if (dev == NULL)
305 		return (EINVAL);
306 	vd = vdisk_get_info((struct devdesc *)dev);
307 	if (vd == NULL)
308 		return (EINVAL);
309 
310 	if (dev->dd.d_dev->dv_type == DEVT_DISK) {
311 		rc = disk_open(dev, vd->vdisk_size, vd->vdisk_sectorsz);
312 	}
313 	if (rc == 0)
314 		vd->vdisk_open++;
315 	return (rc);
316 }
317 
318 static int
319 vdisk_close(struct open_file *f)
320 {
321 	struct disk_devdesc *dev;
322 	vdisk_info_t *vd;
323 
324 	dev = (struct disk_devdesc *)(f->f_devdata);
325 	if (dev == NULL)
326 		return (EINVAL);
327 	vd = vdisk_get_info((struct devdesc *)dev);
328 	if (vd == NULL)
329 		return (EINVAL);
330 
331 	vd->vdisk_open--;
332 	if (dev->dd.d_dev->dv_type == DEVT_DISK)
333 		return (disk_close(dev));
334 	return (0);
335 }
336 
337 static int
338 vdisk_ioctl(struct open_file *f, u_long cmd, void *data)
339 {
340 	struct disk_devdesc *dev;
341 	vdisk_info_t *vd;
342 	int rc;
343 
344 	dev = (struct disk_devdesc *)(f->f_devdata);
345 	if (dev == NULL)
346 		return (EINVAL);
347 	vd = vdisk_get_info((struct devdesc *)dev);
348 	if (vd == NULL)
349 		return (EINVAL);
350 
351 	if (dev->dd.d_dev->dv_type == DEVT_DISK) {
352 		rc = disk_ioctl(dev, cmd, data);
353 		if (rc != ENOTTY)
354 			return (rc);
355 	}
356 
357 	switch (cmd) {
358 	case DIOCGSECTORSIZE:
359 		*(u_int *)data = vd->vdisk_sectorsz;
360 		break;
361 	case DIOCGMEDIASIZE:
362 		*(uint64_t *)data = vd->vdisk_size;
363 		break;
364 	default:
365 		return (ENOTTY);
366 	}
367 	return (0);
368 }
369 
370 static int
371 vdisk_print(int verbose)
372 {
373 	int ret = 0;
374 	vdisk_info_t *vd;
375 	char line[80];
376 
377 	if (STAILQ_EMPTY(&vdisk_list))
378 		return (ret);
379 
380 	printf("%s devices:", vdisk_dev.dv_name);
381 	if ((ret = pager_output("\n")) != 0)
382 		return (ret);
383 
384 	STAILQ_FOREACH(vd, &vdisk_list, vdisk_link) {
385 		struct disk_devdesc vd_dev;
386 
387 		if (verbose) {
388 			printf("  %s", vd->vdisk_path);
389 			if ((ret = pager_output("\n")) != 0)
390 				break;
391 		}
392 		snprintf(line, sizeof(line),
393 		    "    %s%d", vdisk_dev.dv_name, vd->vdisk_unit);
394 		printf("%s:    %" PRIu64 " X %u blocks", line,
395 		    vd->vdisk_size / vd->vdisk_sectorsz,
396 		    vd->vdisk_sectorsz);
397 		if ((ret = pager_output("\n")) != 0)
398 			break;
399 
400 		vd_dev.dd.d_dev = &vdisk_dev;
401 		vd_dev.dd.d_unit = vd->vdisk_unit;
402 		vd_dev.d_slice = -1;
403 		vd_dev.d_partition = -1;
404 
405 		ret = disk_open(&vd_dev, vd->vdisk_size, vd->vdisk_sectorsz);
406 		if (ret == 0) {
407 			ret = disk_print(&vd_dev, line, verbose);
408 			disk_close(&vd_dev);
409 			if (ret != 0)
410 				break;
411 		} else {
412 			ret = 0;
413 		}
414 	}
415 
416 	return (ret);
417 }
418