]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - stand/common/vdisk.c
Merge llvm-project main llvmorg-15-init-17826-g1f8ae9d7e7e4
[FreeBSD/FreeBSD.git] / stand / common / vdisk.c
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 }