diff options
Diffstat (limited to 'libstore/rdwr.c')
-rw-r--r-- | libstore/rdwr.c | 281 |
1 files changed, 281 insertions, 0 deletions
diff --git a/libstore/rdwr.c b/libstore/rdwr.c new file mode 100644 index 00000000..85d2ba2b --- /dev/null +++ b/libstore/rdwr.c @@ -0,0 +1,281 @@ +/* Store I/O + + Copyright (C) 1995, 96, 97, 98, 1999 Free Software Foundation, Inc. + Written by Miles Bader <miles@gnu.ai.mit.edu> + This file is part of the GNU Hurd. + + The GNU Hurd 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, or (at + your option) any later version. + + The GNU Hurd 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, USA. */ + +#include <string.h> +#include <sys/mman.h> + +#include "store.h" + +/* Returns in RUN the tail of STORE's run list, who's first run contains + ADDR, and is not a hole, and in RUNS_END a pointer pointing at the end of + the run list. Returns the offset within it at which ADDR occurs. Also + returns BASE, which should be added to offsets from RUNS. */ +static inline off_t +store_find_first_run (struct store *store, off_t addr, + struct store_run **run, struct store_run **runs_end, + off_t *base, size_t *index) +{ + struct store_run *tail = store->runs, *tail_end = tail + store->num_runs; + off_t wrap_src = store->wrap_src; + + if (addr >= wrap_src && addr < store->end) + /* Locate the correct position within a repeating pattern of runs. */ + { + *base = addr / store->wrap_dst; + addr %= wrap_src; + } + else + *base = 0; + + /* XXX: this isn't going to be very efficient if RUNS is very complex... + But it should do dandy if it's short. For long run lists, we could do a + binary search or something. */ + while (tail < tail_end) + { + off_t run_blocks = tail->length; + + if (run_blocks > addr) + { + *run = tail; + *runs_end = tail_end; + *index = tail - store->runs; + return addr; + } + + /* Not to the right place yet, move on... */ + addr -= run_blocks; + tail++; + } + + return -1; +} + +/* Update RUN, BASE, & INDEX to point to the next elemement in the runs + array. RUNS_END is the point where RUNS will wrap. Returns true if + things are still kosher. */ +static inline int +store_next_run (struct store *store, struct store_run *runs_end, + struct store_run **run, off_t *base, size_t *index) +{ + (*run)++; + (*index)++; + + if (*run == runs_end) + /* Wrap around in a repeating RUNS. */ + { + *run = store->runs; + *base += store->wrap_dst; + *index = 0; + return (*base < store->end); + } + else + return 1; +} + +#include <assert.h> + +/* Write LEN bytes from BUF to STORE at ADDR. Returns the amount written + in AMOUNT. ADDR is in BLOCKS (as defined by STORE->block_size). */ +error_t +store_write (struct store *store, + off_t addr, void *buf, size_t len, size_t *amount) +{ + error_t err; + size_t index; + off_t base; + struct store_run *run, *runs_end; + int block_shift = store->log2_block_size; + store_write_meth_t write = store->class->write; + + if (store->flags & STORE_READONLY) + return EROFS; /* XXX */ + + if (store->block_size != 0) + assert ((len & (store->block_size - 1)) == 0); + + addr = store_find_first_run (store, addr, &run, &runs_end, &base, &index); + if (addr < 0) + err = EIO; + else if ((len >> block_shift) <= run->length - addr) + /* The first run has it all... */ + err = (*write)(store, base + run->start + addr, index, buf, len, amount); + else + /* ARGH, we've got to split up the write ... */ + { + mach_msg_type_number_t try, written; + + /* Write the initial bit in the first run. Errors here are returned. */ + try = (run->length - addr) << block_shift; + err = (*write) (store, base + run->start + addr, index, buf, try, + &written); + + if (!err && written == try) + /* Wrote the first bit successfully, now do the rest. Any errors + will just result in a short write. */ + { + buf += written; + len -= written; + + while (store_next_run (store, runs_end, &run, &base, &index) + && run->start >= 0) /* Check for holes. */ + /* Ok, we can write in this run, at least a bit. */ + { + mach_msg_type_number_t seg_written; + + if ((len >> block_shift) <= run->length) + try = len; + else + try = run->length << block_shift; + + err = (*write)(store, base + run->start, index, buf, try, + &seg_written); + if (err) + break; /* Ack */ + written += seg_written; + + if (seg_written < try) + break; /* Didn't use up the run, we're done. */ + + len -= seg_written; + if (len == 0) + break; /* Nothing left to write! */ + + buf += seg_written; + } + } + + *amount = written; + } + + return err; +} + +/* Read AMOUNT bytes from STORE at ADDR into BUF & LEN (which follows the + usual mach buffer-return semantics) to STORE at ADDR. ADDR is in BLOCKS + (as defined by STORE->block_size). */ +error_t +store_read (struct store *store, + off_t addr, size_t amount, void **buf, size_t *len) +{ + size_t index; + off_t base; + struct store_run *run, *runs_end; + int block_shift = store->log2_block_size; + store_read_meth_t read = store->class->read; + + addr = store_find_first_run (store, addr, &run, &runs_end, &base, &index); + if (addr < 0 || run->start < 0) + return EIO; /* Reading from a hole. */ + + if (store->block_size != 0) + assert ((amount & (store->block_size - 1)) == 0); + + if ((amount >> block_shift) <= run->length - addr) + /* The first run has it all... */ + return (*read) (store, base + run->start + addr, index, amount, buf, len); + else + /* ARGH, we've got to split up the read ... This isn't fun. */ + { + error_t err; + int all; + /* WHOLE_BUF and WHOLE_BUF_LEN will point to a buff that's large enough + to hold the entire request. This is initially whatever the user + passed in, but we'll change it as necessary. */ + void *whole_buf = *buf, *buf_end; + size_t whole_buf_len = *len; + + /* Read LEN bytes from the store address ADDR into BUF_END. BUF_END + and AMOUNT are adjusted by the amount actually read. Whether or not + the amount read is the same as what was request is returned in ALL. */ + inline error_t seg_read (off_t addr, off_t len, int *all) + { + /* SEG_BUF and SEG_LEN are the buffer for a particular bit of the + whole (within one run). */ + void *seg_buf = buf_end; + size_t seg_buf_len = len; + error_t err = + (*read)(store, addr, index, len, &seg_buf, &seg_buf_len); + if (!err) + { + /* If for some bizarre reason, the underlying storage chose not + to use the buffer space we so kindly gave it, bcopy it to + that space. */ + if (seg_buf != buf_end) + { + bcopy (seg_buf, buf_end, seg_buf_len); + munmap (seg_buf, seg_buf_len); + } + buf_end += seg_buf_len; + amount -= seg_buf_len; + *all = (seg_buf_len == len); + } + return err; + } + + if (whole_buf_len < amount) + /* Not enough room in the user's buffer to hold everything, better + make room. */ + { + whole_buf_len = amount; + whole_buf = mmap (0, amount, PROT_READ|PROT_WRITE, MAP_ANON, 0, 0); + if (whole_buf == (void *) -1) + return errno; /* Punt early, there's nothing to clean up. */ + } + + buf_end = whole_buf; + + err = seg_read (base + run->start + addr, + (run->length - addr) << block_shift, &all); + while (!err && all && amount > 0 + && store_next_run (store, runs_end, &run, &base, &index)) + { + if (run->start < 0) + /* A hole! Can't read here. Must stop. */ + break; + else + err = seg_read (base + run->start, + (amount >> block_shift) <= run->length + ? amount /* This run has the rest. */ + : (run->length << block_shift), /* Whole run. */ + &all); + } + + /* The actual amount read. */ + *len = buf_end - whole_buf; + if (*len > 0) + err = 0; /* Return a short read instead of an error. */ + + /* Deallocate any amount of WHOLE_BUF we didn't use. */ + if (whole_buf != *buf) + { + if (err) + munmap (whole_buf, whole_buf_len); + else + { + vm_size_t unused = whole_buf_len - round_page (*len); + if (unused) + munmap (whole_buf + whole_buf_len - unused, unused); + *buf = whole_buf; + } + } + + return err; + } +} |