blob: 9b40727fe79a48be4fca09d0b0b930ae0e05a6f7 [file] [log] [blame]
/* Copyright 1997,2001-2003 Alain Knaff.
* This file is part of mtools.
*
* Mtools 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 3 of the License, or
* (at your option) any later version.
*
* Mtools 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 Mtools. If not, see <http://www.gnu.org/licenses/>.
*
* Buffer read/write module
*/
#include "sysincludes.h"
#include "msdos.h"
#include "mtools.h"
#include "buffer.h"
typedef struct Buffer_t {
struct Stream_t head;
size_t size; /* size of read/write buffer */
int dirty; /* is the buffer dirty? */
size_t sectorSize; /* sector size: all operations happen
* in multiples of this */
size_t cylinderSize; /* cylinder size: preferred alignment,
* but for efficiency, less data may be read */
int ever_dirty; /* was the buffer ever dirty? */
size_t dirty_pos;
size_t dirty_end;
mt_off_t current; /* first sector in buffer */
size_t cur_size; /* the current size */
char *buf; /* disk read/write buffer */
} Buffer_t;
/* Convert position relative to buffer to absolute position */
static mt_off_t abs_pos(Buffer_t *Buffer, size_t rel) {
return Buffer->current + (mt_off_t) rel;
}
/* End of currently valid buffer */
static mt_off_t cur_end(Buffer_t *Buffer) {
return abs_pos(Buffer, Buffer->cur_size);
}
/* distance from absolute position until next full cylinder. If position already
* *is* on a full cylinder boundary, return size of full cylinder */
static size_t pos_to_next_full_cyl(Buffer_t *Buffer, mt_off_t pos) {
return Buffer->cylinderSize -
(size_t) (pos % (mt_off_t) Buffer->cylinderSize);
}
/*
* Flush a dirty buffer to disk. Resets Buffer->dirty to zero.
* All errors are fatal.
*/
static int _buf_flush(Buffer_t *Buffer)
{
ssize_t ret;
#ifdef HAVE_ASSERT_H
assert(Buffer->head.Next != NULL);
#endif
if (!Buffer->dirty)
return 0;
#ifdef DEBUG
fprintf(stderr, "write %08x -- %02x %08x %08x\n",
Buffer,
(unsigned char) Buffer->buf[0],
Buffer->current + Buffer->dirty_pos,
Buffer->dirty_end - Buffer->dirty_pos);
#endif
ret = force_pwrite(Buffer->head.Next,
Buffer->buf + Buffer->dirty_pos,
Buffer->current + (mt_off_t) Buffer->dirty_pos,
Buffer->dirty_end - Buffer->dirty_pos);
if(ret < 0) {
perror("buffer_flush: write");
return -1;
}
if((size_t) ret != Buffer->dirty_end - Buffer->dirty_pos) {
fprintf(stderr,"buffer_flush: short write\n");
return -1;
}
Buffer->dirty = 0;
Buffer->dirty_end = 0;
Buffer->dirty_pos = 0;
return 0;
}
static int invalidate_buffer(Buffer_t *Buffer, mt_off_t start)
{
if(_buf_flush(Buffer) < 0)
return -1;
/* start reading at the beginning of start's sector
* don't start reading too early, or we might not even reach
* start */
Buffer->current = ROUND_DOWN(start, (mt_off_t) Buffer->sectorSize);
Buffer->cur_size = 0;
return 0;
}
#undef OFFSET
#define OFFSET ((size_t)(start - This->current))
typedef enum position_t {
OUTSIDE,
APPEND,
INSIDE,
ERROR
} position_t;
static position_t isInBuffer(Buffer_t *This, mt_off_t start, size_t *len)
{
if(start >= This->current && start < cur_end(This)) {
maximize(*len, This->cur_size - OFFSET);
return INSIDE;
} else if(start == cur_end(This) &&
This->cur_size < This->size &&
*len >= This->sectorSize) {
/* append to the buffer for this, three conditions have to
* be met:
* 1. The start falls exactly at the end of the currently
* loaded data
* 2. There is still space
* 3. We append at least one sector
*/
maximize(*len, This->size - This->cur_size);
*len = ROUND_DOWN(*len, This->sectorSize);
return APPEND;
} else {
if(invalidate_buffer(This, start) < 0)
return ERROR;
maximize(*len, This->cylinderSize - OFFSET);
maximize(*len, pos_to_next_full_cyl(This, This->current));
return OUTSIDE;
}
}
static ssize_t buf_pread(Stream_t *Stream, char *buf,
mt_off_t start, size_t len)
{
size_t length;
size_t offset;
char *disk_ptr;
ssize_t ret;
DeclareThis(Buffer_t);
if(!len)
return 0;
/*fprintf(stderr, "buf read %x %x %x\n", Stream, start, len);*/
switch(isInBuffer(This, start, &len)) {
case OUTSIDE:
case APPEND:
/* always load until the end of the cylinder */
length = pos_to_next_full_cyl(This, cur_end(This));
maximize(length, This->size - This->cur_size);
/* read it! */
ret=PREADS(This->head.Next,
This->buf + This->cur_size,
This->current + (mt_off_t) This->cur_size,
length);
if ( ret < 0 )
return ret;
This->cur_size += (size_t) ret;
if (This->current+(mt_off_t)This->cur_size < start) {
fprintf(stderr, "Short buffer fill\n");
exit(1);
}
break;
case INSIDE:
/* nothing to do */
break;
case ERROR:
return -1;
}
offset = OFFSET;
disk_ptr = This->buf + offset;
maximize(len, This->cur_size - offset);
memcpy(buf, disk_ptr, len);
return (ssize_t) len;
}
static ssize_t buf_pwrite(Stream_t *Stream, char *buf,
mt_off_t start, size_t len)
{
char *disk_ptr;
DeclareThis(Buffer_t);
size_t offset=0;
if(!len)
return 0;
This->ever_dirty = 1;
#ifdef DEBUG
fprintf(stderr, "buf write %x %02x %08x %08x -- %08x %08x -- %08x\n",
Stream, (unsigned char) This->buf[0],
start, len, This->current, This->cur_size, This->size);
fprintf(stderr, "%d %d %d %x %x\n",
start == This->current + This->cur_size,
This->cur_size < This->size,
len >= This->sectorSize, len, This->sectorSize);
#endif
switch(isInBuffer(This, start, &len)) {
case OUTSIDE:
#ifdef DEBUG
fprintf(stderr, "outside\n");
#endif
if(start % (mt_off_t) This->cylinderSize ||
len < This->sectorSize) {
size_t readSize;
ssize_t ret;
size_t bytes_read;
readSize = This->cylinderSize -
(size_t)(This->current % (mt_off_t) This->cylinderSize);
ret=PREADS(This->head.Next, This->buf,
(mt_off_t)This->current, readSize);
/* read it! */
if ( ret < 0 )
return ret;
bytes_read = (size_t) ret;
if(bytes_read % This->sectorSize) {
fprintf(stderr, "Weird: read size (%zd) not a multiple of sector size (%d)\n", bytes_read, (int) This->sectorSize);
bytes_read -= bytes_read % This->sectorSize;
if(bytes_read == 0) {
fprintf(stderr, "Nothing left\n");
exit(1);
}
}
This->cur_size = bytes_read;
/* for dosemu. Autoextend size */
if(!This->cur_size) {
memset(This->buf,0,readSize);
This->cur_size = readSize;
}
offset = OFFSET;
break;
}
/* FALL THROUGH */
case APPEND:
#ifdef DEBUG
fprintf(stderr, "append\n");
#endif
len = ROUND_DOWN(len, This->sectorSize);
offset = OFFSET;
maximize(len, This->size - offset);
This->cur_size += len;
if(This->head.Next->Class->pre_allocate)
PRE_ALLOCATE(This->head.Next, cur_end(This));
break;
case INSIDE:
/* nothing to do */
#ifdef DEBUG
fprintf(stderr, "inside\n");
#endif
offset = OFFSET;
maximize(len, This->cur_size - offset);
break;
case ERROR:
return -1;
#ifdef DEBUG
default:
fprintf(stderr, "Should not happen\n");
exit(1);
#endif
}
disk_ptr = This->buf + offset;
/* extend if we write beyond end */
if(offset + len > This->cur_size) {
len -= (offset + len) % This->sectorSize;
This->cur_size = len + offset;
}
memcpy(disk_ptr, buf, len);
if(!This->dirty || offset < This->dirty_pos)
This->dirty_pos = ROUND_DOWN(offset, This->sectorSize);
if(!This->dirty || offset + len > This->dirty_end)
This->dirty_end = ROUND_UP(offset + len, This->sectorSize);
if(This->dirty_end > This->cur_size) {
fprintf(stderr,
"Internal error, dirty end too big dirty_end=%x cur_size=%x len=%x offset=%d sectorSize=%x\n",
(unsigned int) This->dirty_end,
(unsigned int) This->cur_size,
(unsigned int) len,
(int) offset, (int) This->sectorSize);
fprintf(stderr, "offset + len + grain - 1 = %x\n",
(int) (offset + len + This->sectorSize - 1));
fprintf(stderr, "ROUNDOWN(offset + len + grain - 1) = %x\n",
(int)ROUND_DOWN(offset + len + This->sectorSize - 1,
This->sectorSize));
fprintf(stderr, "This->dirty = %d\n", This->dirty);
exit(1);
}
This->dirty = 1;
return (ssize_t) len;
}
static int buf_flush(Stream_t *Stream)
{
int ret;
DeclareThis(Buffer_t);
if (!This->ever_dirty)
return 0;
ret = _buf_flush(This);
if(ret == 0)
This->ever_dirty = 0;
return ret;
}
static int buf_free(Stream_t *Stream)
{
DeclareThis(Buffer_t);
if(This->buf)
free(This->buf);
This->buf = 0;
return 0;
}
static Class_t BufferClass = {
0,
0,
buf_pread,
buf_pwrite,
buf_flush,
buf_free,
0, /* set_geom */
get_data_pass_through, /* get_data */
0, /* pre-allocate */
get_dosConvert_pass_through, /* dos convert */
0, /* discard */
};
Stream_t *buf_init(Stream_t *Next, size_t size,
size_t cylinderSize,
size_t sectorSize)
{
Buffer_t *Buffer;
#ifdef HAVE_ASSERT_H
assert(size != 0);
assert(cylinderSize != 0);
assert(sectorSize != 0);
assert(Next != NULL);
#endif
if(size % cylinderSize != 0) {
fprintf(stderr, "size not multiple of cylinder size\n");
exit(1);
}
if(cylinderSize % sectorSize != 0) {
fprintf(stderr, "cylinder size not multiple of sector size\n");
exit(1);
}
Buffer = New(Buffer_t);
if(!Buffer)
return 0;
init_head(&Buffer->head, &BufferClass, Next);
Buffer->buf = malloc(size);
if ( !Buffer->buf){
Free(Buffer);
return 0;
}
Buffer->size = size;
Buffer->dirty = 0;
Buffer->cylinderSize = cylinderSize;
Buffer->sectorSize = sectorSize;
Buffer->ever_dirty = 0;
Buffer->dirty_pos = 0;
Buffer->dirty_end = 0;
Buffer->current = 0L;
Buffer->cur_size = 0; /* buffer currently empty */
return &Buffer->head;
}