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 28 #include <stand.h> 29 #include <stdarg.h> 30 #include <inttypes.h> 31 #include <bootstrap.h> 32 #include <sys/disk.h> 33 #include <sys/errno.h> 34 #include <sys/queue.h> 35 #include <sys/param.h> 36 #include <disk.h> 37 38 static int vdisk_init(void); 39 static int vdisk_strategy(void *, int, daddr_t, size_t, char *, size_t *); 40 static int vdisk_open(struct open_file *, ...); 41 static int vdisk_close(struct open_file *); 42 static int vdisk_ioctl(struct open_file *, ulong_t, void *); 43 static int vdisk_print(int); 44 45 struct devsw vdisk_dev = { 46 .dv_name = "vdisk", 47 .dv_type = DEVT_DISK, 48 .dv_init = vdisk_init, 49 .dv_strategy = vdisk_strategy, 50 .dv_open = vdisk_open, 51 .dv_close = vdisk_close, 52 .dv_ioctl = vdisk_ioctl, 53 .dv_print = vdisk_print, 54 .dv_cleanup = NULL 55 }; 56 57 typedef STAILQ_HEAD(vdisk_info_list, vdisk_info) vdisk_info_list_t; 58 59 typedef struct vdisk_info 60 { 61 STAILQ_ENTRY(vdisk_info) vdisk_link; /* link in device list */ 62 char *vdisk_path; 63 int vdisk_unit; 64 int vdisk_fd; 65 uint64_t vdisk_size; /* size in bytes */ 66 uint32_t vdisk_sectorsz; 67 uint32_t vdisk_open; /* reference counter */ 68 } vdisk_info_t; 69 70 static vdisk_info_list_t vdisk_list; /* list of mapped vdisks. */ 71 72 static vdisk_info_t * 73 vdisk_get_info(struct devdesc *dev) 74 { 75 vdisk_info_t *vd; 76 77 STAILQ_FOREACH(vd, &vdisk_list, vdisk_link) { 78 if (vd->vdisk_unit == dev->d_unit) 79 return (vd); 80 } 81 return (vd); 82 } 83 84 COMMAND_SET(map_vdisk, "map-vdisk", "map file as virtual disk", command_mapvd); 85 86 static int 87 command_mapvd(int argc, char *argv[]) 88 { 89 vdisk_info_t *vd, *p; 90 struct stat sb; 91 92 if (argc != 2) { 93 printf("usage: %s filename\n", argv[0]); 94 return (CMD_ERROR); 95 } 96 97 STAILQ_FOREACH(vd, &vdisk_list, vdisk_link) { 98 if (strcmp(vd->vdisk_path, argv[1]) == 0) { 99 printf("%s: file %s is already mapped as %s%d\n", 100 argv[0], argv[1], vdisk_dev.dv_name, 101 vd->vdisk_unit); 102 return (CMD_ERROR); 103 } 104 } 105 106 if (stat(argv[1], &sb) < 0) { 107 /* 108 * ENOSYS is really ENOENT because we did try to walk 109 * through devsw list to try to open this file. 110 */ 111 if (errno == ENOSYS) 112 errno = ENOENT; 113 114 printf("%s: stat failed: %s\n", argv[0], strerror(errno)); 115 return (CMD_ERROR); 116 } 117 118 /* 119 * Avoid mapping small files. 120 */ 121 if (sb.st_size < 1024 * 1024) { 122 printf("%s: file %s is too small.\n", argv[0], argv[1]); 123 return (CMD_ERROR); 124 } 125 126 vd = calloc(1, sizeof (*vd)); 127 if (vd == NULL) { 128 printf("%s: out of memory\n", argv[0]); 129 return (CMD_ERROR); 130 } 131 vd->vdisk_path = strdup(argv[1]); 132 if (vd->vdisk_path == NULL) { 133 free(vd); 134 printf("%s: out of memory\n", argv[0]); 135 return (CMD_ERROR); 136 } 137 vd->vdisk_fd = open(vd->vdisk_path, O_RDONLY); 138 if (vd->vdisk_fd < 0) { 139 printf("%s: open failed: %s\n", argv[0], strerror(errno)); 140 free(vd->vdisk_path); 141 free(vd); 142 return (CMD_ERROR); 143 } 144 145 vd->vdisk_size = sb.st_size; 146 vd->vdisk_sectorsz = DEV_BSIZE; 147 STAILQ_FOREACH(p, &vdisk_list, vdisk_link) { 148 vdisk_info_t *n; 149 if (p->vdisk_unit == vd->vdisk_unit) { 150 vd->vdisk_unit++; 151 continue; 152 } 153 n = STAILQ_NEXT(p, vdisk_link); 154 if (p->vdisk_unit < vd->vdisk_unit) { 155 if (n == NULL) { 156 /* p is last elem */ 157 STAILQ_INSERT_TAIL(&vdisk_list, vd, vdisk_link); 158 break; 159 } 160 if (n->vdisk_unit > vd->vdisk_unit) { 161 /* p < vd < n */ 162 STAILQ_INSERT_AFTER(&vdisk_list, p, vd, 163 vdisk_link); 164 break; 165 } 166 /* else n < vd or n == vd */ 167 vd->vdisk_unit++; 168 continue; 169 } 170 /* p > vd only if p is the first element */ 171 STAILQ_INSERT_HEAD(&vdisk_list, vd, vdisk_link); 172 break; 173 } 174 175 /* if the list was empty or contiguous */ 176 if (p == NULL) 177 STAILQ_INSERT_TAIL(&vdisk_list, vd, vdisk_link); 178 179 printf("%s: file %s is mapped as %s%d\n", argv[0], vd->vdisk_path, 180 vdisk_dev.dv_name, vd->vdisk_unit); 181 return (CMD_OK); 182 } 183 184 COMMAND_SET(unmap_vdisk, "unmap-vdisk", "unmap virtual disk", command_unmapvd); 185 186 /* 187 * unmap-vdisk vdiskX 188 */ 189 static int 190 command_unmapvd(int argc, char *argv[]) 191 { 192 size_t len; 193 vdisk_info_t *vd; 194 long unit; 195 char *end; 196 197 if (argc != 2) { 198 printf("usage: %s %sN\n", argv[0], vdisk_dev.dv_name); 199 return (CMD_ERROR); 200 } 201 202 len = strlen(vdisk_dev.dv_name); 203 if (strncmp(vdisk_dev.dv_name, argv[1], len) != 0) { 204 printf("%s: unknown device %s\n", argv[0], argv[1]); 205 return (CMD_ERROR); 206 } 207 errno = 0; 208 unit = strtol(argv[1] + len, &end, 10); 209 if (errno != 0 || (*end != '\0' && strcmp(end, ":") != 0)) { 210 printf("%s: unknown device %s\n", argv[0], argv[1]); 211 return (CMD_ERROR); 212 } 213 214 STAILQ_FOREACH(vd, &vdisk_list, vdisk_link) { 215 if (vd->vdisk_unit == unit) 216 break; 217 } 218 219 if (vd == NULL) { 220 printf("%s: unknown device %s\n", argv[0], argv[1]); 221 return (CMD_ERROR); 222 } 223 224 if (vd->vdisk_open != 0) { 225 printf("%s: %s is in use, unable to unmap.\n", 226 argv[0], argv[1]); 227 return (CMD_ERROR); 228 } 229 230 STAILQ_REMOVE(&vdisk_list, vd, vdisk_info, vdisk_link); 231 (void) close(vd->vdisk_fd); 232 printf("%s (%s) unmapped\n", argv[1], vd->vdisk_path); 233 free(vd->vdisk_path); 234 free(vd); 235 236 return (CMD_OK); 237 } 238 239 static int 240 vdisk_init(void) 241 { 242 STAILQ_INIT(&vdisk_list); 243 return (0); 244 } 245 246 static int 247 vdisk_strategy(void *devdata, int rw, daddr_t blk, size_t size, 248 char *buf, size_t *rsize) 249 { 250 struct disk_devdesc *dev; 251 vdisk_info_t *vd; 252 ssize_t rv; 253 254 dev = devdata; 255 if (dev == NULL) 256 return (EINVAL); 257 vd = vdisk_get_info((struct devdesc *)dev); 258 if (vd == NULL) 259 return (EINVAL); 260 261 if (size == 0 || (size % 512) != 0) 262 return (EIO); 263 264 if (dev->dd.d_dev->dv_type == DEVT_DISK) { 265 daddr_t offset; 266 267 offset = dev->d_offset * vd->vdisk_sectorsz; 268 offset /= 512; 269 blk += offset; 270 } 271 if (lseek(vd->vdisk_fd, blk << 9, SEEK_SET) == -1) 272 return (EIO); 273 274 errno = 0; 275 switch (rw & F_MASK) { 276 case F_READ: 277 rv = read(vd->vdisk_fd, buf, size); 278 break; 279 case F_WRITE: 280 rv = write(vd->vdisk_fd, buf, size); 281 break; 282 default: 283 return (ENOSYS); 284 } 285 286 if (errno == 0 && rsize != NULL) { 287 *rsize = rv; 288 } 289 return (errno); 290 } 291 292 static int 293 vdisk_open(struct open_file *f, ...) 294 { 295 va_list args; 296 struct disk_devdesc *dev; 297 vdisk_info_t *vd; 298 int rc = 0; 299 300 va_start(args, f); 301 dev = va_arg(args, struct disk_devdesc *); 302 va_end(args); 303 if (dev == NULL) 304 return (EINVAL); 305 vd = vdisk_get_info((struct devdesc *)dev); 306 if (vd == NULL) 307 return (EINVAL); 308 309 if (dev->dd.d_dev->dv_type == DEVT_DISK) { 310 rc = disk_open(dev, vd->vdisk_size, vd->vdisk_sectorsz); 311 } 312 if (rc == 0) 313 vd->vdisk_open++; 314 return (rc); 315 } 316 317 static int 318 vdisk_close(struct open_file *f) 319 { 320 struct disk_devdesc *dev; 321 vdisk_info_t *vd; 322 323 dev = (struct disk_devdesc *)(f->f_devdata); 324 if (dev == NULL) 325 return (EINVAL); 326 vd = vdisk_get_info((struct devdesc *)dev); 327 if (vd == NULL) 328 return (EINVAL); 329 330 vd->vdisk_open--; 331 if (dev->dd.d_dev->dv_type == DEVT_DISK) 332 return (disk_close(dev)); 333 return (0); 334 } 335 336 static int 337 vdisk_ioctl(struct open_file *f, ulong_t cmd, void *data) 338 { 339 struct disk_devdesc *dev; 340 vdisk_info_t *vd; 341 int rc; 342 343 dev = (struct disk_devdesc *)(f->f_devdata); 344 if (dev == NULL) 345 return (EINVAL); 346 vd = vdisk_get_info((struct devdesc *)dev); 347 if (vd == NULL) 348 return (EINVAL); 349 350 if (dev->dd.d_dev->dv_type == DEVT_DISK) { 351 rc = disk_ioctl(dev, cmd, data); 352 if (rc != ENOTTY) 353 return (rc); 354 } 355 356 switch (cmd) { 357 case DIOCGSECTORSIZE: 358 *(uint_t *)data = vd->vdisk_sectorsz; 359 break; 360 case DIOCGMEDIASIZE: 361 *(uint64_t *)data = vd->vdisk_size; 362 break; 363 default: 364 return (ENOTTY); 365 } 366 return (0); 367 } 368 369 static int 370 vdisk_print(int verbose) 371 { 372 int ret = 0; 373 vdisk_info_t *vd; 374 char line[80]; 375 376 if (STAILQ_EMPTY(&vdisk_list)) 377 return (ret); 378 379 printf("%s devices:", vdisk_dev.dv_name); 380 if ((ret = pager_output("\n")) != 0) 381 return (ret); 382 383 STAILQ_FOREACH(vd, &vdisk_list, vdisk_link) { 384 struct disk_devdesc vd_dev; 385 386 if (verbose) { 387 printf(" %s", vd->vdisk_path); 388 if ((ret = pager_output("\n")) != 0) 389 break; 390 } 391 snprintf(line, sizeof (line), 392 " %s%d", vdisk_dev.dv_name, vd->vdisk_unit); 393 printf("%s: %" PRIu64 " X %u blocks", line, 394 vd->vdisk_size / vd->vdisk_sectorsz, 395 vd->vdisk_sectorsz); 396 if ((ret = pager_output("\n")) != 0) 397 break; 398 399 vd_dev.dd.d_dev = &vdisk_dev; 400 vd_dev.dd.d_unit = vd->vdisk_unit; 401 vd_dev.d_slice = -1; 402 vd_dev.d_partition = -1; 403 404 ret = disk_open(&vd_dev, vd->vdisk_size, vd->vdisk_sectorsz); 405 if (ret == 0) { 406 ret = disk_print(&vd_dev, line, verbose); 407 disk_close(&vd_dev); 408 if (ret != 0) 409 break; 410 } else { 411 ret = 0; 412 } 413 } 414 415 return (ret); 416 } 417