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