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