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 close(vd->vdisk_fd); 233 free(vd->vdisk_path); 234 free(vd); 235 printf("%s (%s) unmapped\n", argv[1], vd->vdisk_path); 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