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 #include <stand.h>
28 #include <stdarg.h>
29 #include <machine/_inttypes.h>
30 #include <bootstrap.h>
31 #include <sys/disk.h>
32 #include <sys/errno.h>
33 #include <sys/queue.h>
34 #include <sys/param.h>
35 #include <disk.h>
36
37 static int vdisk_init(void);
38 static int vdisk_strategy(void *, int, daddr_t, size_t, char *, size_t *);
39 static int vdisk_open(struct open_file *, ...);
40 static int vdisk_close(struct open_file *);
41 static int vdisk_ioctl(struct open_file *, u_long, void *);
42 static int vdisk_print(int);
43
44 struct devsw vdisk_dev = {
45 .dv_name = "vdisk",
46 .dv_type = DEVT_DISK,
47 .dv_init = vdisk_init,
48 .dv_strategy = vdisk_strategy,
49 .dv_open = vdisk_open,
50 .dv_close = vdisk_close,
51 .dv_ioctl = vdisk_ioctl,
52 .dv_print = vdisk_print,
53 .dv_cleanup = nullsys,
54 .dv_fmtdev = disk_fmtdev,
55 .dv_parsedev = disk_parsedev,
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 *
vdisk_get_info(struct devdesc * dev)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
command_mapvd(int argc,char * argv[])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
command_unmapvd(int argc,char * argv[])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
vdisk_init(void)241 vdisk_init(void)
242 {
243 STAILQ_INIT(&vdisk_list);
244 return (0);
245 }
246
247 static int
vdisk_strategy(void * devdata,int rw,daddr_t blk,size_t size,char * buf,size_t * rsize)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
vdisk_open(struct open_file * f,...)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
vdisk_close(struct open_file * f)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
vdisk_ioctl(struct open_file * f,u_long cmd,void * data)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
vdisk_print(int verbose)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