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