/* * Block driver for multiple-file raw images. * Copyright © 2006 Ryan Lortie * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include "vl.h" #include "block_int.h" /* The maximum number of fds to use. */ #define LIMIT_FDS 15 #ifdef LIMIT_FDS #define RING_BUFFER_SIZE LIMIT_FDS struct _RingBuffer { int buffer[RING_BUFFER_SIZE+1]; int head; int tail; }; typedef struct _RingBuffer RingBuffer; static RingBuffer * ring_buffer_new (void) { RingBuffer *ring; ring = qemu_mallocz (sizeof (RingBuffer)); return ring; } static int ring_buffer_get (RingBuffer *ring) { int value; if (ring->head == ring->tail) return -1; value = ring->buffer[ring->head]; ring->head = (ring->head + 1) % (RING_BUFFER_SIZE + 1); return value; } static int ring_buffer_put (RingBuffer *ring, int value) { int new_tail; new_tail = (ring->tail + 1) % (RING_BUFFER_SIZE + 1); if (ring->head == new_tail) return -1; ring->buffer[ring->tail] = value; ring->tail = new_tail; return 0; } #endif struct _MultiStatus { char *level; int *fds; char *filename; int fnlength; char pattern[5]; int64_t filesize; int n_files; int read_only; }; typedef struct _MultiStatus MultiStatus; typedef ssize_t (*disk_operation) (int, const uint8_t *, ssize_t); static int multi_status_fill (MultiStatus *status, const char *filename) { struct stat statbuf; int zeros = 0; int i; if (!strstart(filename, "multi:", NULL)) return -1; filename += 6; for (i = 0; filename[i]; i++) if (filename[i] == '0') zeros++; else zeros = 0; if (zeros > 6) zeros = 6; status->fnlength = strlen (filename); status->filename = qemu_malloc (status->fnlength + 1); status->fnlength -= zeros; strcpy (status->filename, filename); if (zeros == 0) status->pattern[status->fnlength] = '\0'; else sprintf (status->pattern, "%%0%ux", zeros); status->n_files = 1 << (4 * zeros); status->read_only = 0; for (i = 0; i < status->n_files; i++) { sprintf (status->filename + status->fnlength, status->pattern, i); if (stat (status->filename, &statbuf)) { if (errno == ENOENT) status->n_files = i; break; } if (euidaccess (status->filename, W_OK)) /* file is read-only */ status->read_only = 1; if (i == 0) status->filesize = statbuf.st_size / 512; if (status->filesize * 512 != statbuf.st_size) break; } if (i != status->n_files || i == 0) { qemu_free (status->filename); return -1; } status->fds = qemu_malloc (sizeof (int) * i); status->level = qemu_mallocz (i); return 0; } static void multi_status_clear (MultiStatus *status) { qemu_free (status->fds); qemu_free (status->level); qemu_free (status->filename); } static int multi_get_fd (MultiStatus *status, int fileno, char level) { int mode; #ifdef LIMIT_FDS static RingBuffer *fd_buffer; if (fd_buffer == NULL) fd_buffer = ring_buffer_new (); #endif if (level <= status->level[fileno]) return status->fds[fileno]; if (status->level[fileno]) close (status->fds[fileno]); #ifdef LIMIT_FDS else /* add the new open file to the ring buffer */ if (ring_buffer_put (fd_buffer, fileno)) { /* no room in the ring buffer -- close a file */ int closefile; closefile = ring_buffer_get (fd_buffer); close (status->fds[closefile]); status->level[closefile] = '\0'; /* there will be room for us now. */ ring_buffer_put (fd_buffer, fileno); } #endif if (level == 'w') mode = O_RDWR; else mode = O_RDONLY; sprintf (status->filename + status->fnlength, status->pattern, fileno); status->fds[fileno] = open (status->filename, mode); status->level[fileno] = level; /* this might return -1. this should really be fatal since we're * in quite bad shape at this point... */ return status->fds[fileno]; } /* the 'const' in the type signature is a lie if (type == 'r') */ static int multi_disk_operation (BlockDriverState *bs, int64_t sector, const uint8_t *buffer, int sectors, char type) { MultiStatus *status = bs->opaque; disk_operation operation; uint64_t file, offset; uint64_t this_access; ssize_t size; int fd; switch (type) { case 'r': operation = (disk_operation) read; break; case 'w': operation = (disk_operation) write; break; default: return -1; } while (sectors) { offset = sector % status->filesize; file = sector / status->filesize; if (file > status->n_files) return -1; /* maximum possible sectors left in current file */ this_access = status->filesize - offset; /* access no more than the remaining sectors */ if (this_access > sectors) this_access = sectors; /* get the fd of the file in question (possibly opening it) */ if ((fd = multi_get_fd (status, file, type)) < 0) return -1; if (lseek (fd, offset * 512, SEEK_SET) < 0) return -1; size = operation (fd, buffer, this_access * 512); /* make sure we read a whole positive number of sectors */ if (size <= 0 || (size & 511)) return -1; sectors -= (size / 512); sector += (size / 512); buffer += size; } return 0; } /* --- begin BlockDriver methods --- */ static int multi_probe (const uint8_t *buffer, int buffer_size, const char *filename) { MultiStatus status; if (multi_status_fill (&status, filename)) return 0; multi_status_clear (&status); return 1000; } static int multi_open (BlockDriverState *bs, const char *filename) { MultiStatus *status = bs->opaque; if (multi_status_fill (status, filename)) return -1; bs->total_sectors = status->n_files * status->filesize; bs->read_only = status->read_only; return 0; } static int multi_read (BlockDriverState *bs, int64_t sector, uint8_t *buf, int sectors) { return multi_disk_operation (bs, sector, buf, sectors, 'r'); } static int multi_write (BlockDriverState *bs, int64_t sector, const uint8_t *buf, int sectors) { return multi_disk_operation (bs, sector, buf, sectors, 'w'); } static void multi_close (BlockDriverState *bs) { MultiStatus *status = bs->opaque; int i; for (i = 0; i < status->n_files; i++) if (status->level[i]) close (status->fds[i]); multi_status_clear (status); } BlockDriver bdrv_multi = { "multi", sizeof (MultiStatus), multi_probe, multi_open, multi_read, multi_write, multi_close };