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 } 420