1/*  Copyright 1997,2001-2003 Alain Knaff.
2 *  This file is part of mtools.
3 *
4 *  Mtools is free software: you can redistribute it and/or modify
5 *  it under the terms of the GNU General Public License as published by
6 *  the Free Software Foundation, either version 3 of the License, or
7 *  (at your option) any later version.
8 *
9 *  Mtools is distributed in the hope that it will be useful,
10 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 *  GNU General Public License for more details.
13 *
14 *  You should have received a copy of the GNU General Public License
15 *  along with Mtools.  If not, see <http://www.gnu.org/licenses/>.
16 *
17 * Buffer read/write module
18 */
19
20#include "sysincludes.h"
21#include "msdos.h"
22#include "mtools.h"
23#include "buffer.h"
24
25typedef struct Buffer_t {
26	Class_t *Class;
27	int refs;
28	Stream_t *Next;
29	Stream_t *Buffer;
30
31	size_t size;     	/* size of read/write buffer */
32	int dirty;	       	/* is the buffer dirty? */
33
34	size_t sectorSize;	/* sector size: all operations happen
35				 * in multiples of this */
36	size_t cylinderSize;	/* cylinder size: preferred alignemnt,
37				 * but for efficiency, less data may be read */
38	int ever_dirty;	       	/* was the buffer ever dirty? */
39	size_t dirty_pos;
40	size_t dirty_end;
41	mt_off_t current;		/* first sector in buffer */
42	size_t cur_size;		/* the current size */
43	char *buf;		/* disk read/write buffer */
44} Buffer_t;
45
46/*
47 * Flush a dirty buffer to disk.  Resets Buffer->dirty to zero.
48 * All errors are fatal.
49 */
50
51static int _buf_flush(Buffer_t *Buffer)
52{
53	int ret;
54
55	if (!Buffer->Next || !Buffer->dirty)
56		return 0;
57	if(Buffer->current < 0L) {
58		fprintf(stderr,"Should not happen\n");
59		return -1;
60	}
61#ifdef DEBUG
62	fprintf(stderr, "write %08x -- %02x %08x %08x\n",
63		Buffer,
64		(unsigned char) Buffer->buf[0],
65		Buffer->current + Buffer->dirty_pos,
66		Buffer->dirty_end - Buffer->dirty_pos);
67#endif
68
69	ret = force_write(Buffer->Next,
70			  Buffer->buf + Buffer->dirty_pos,
71			  Buffer->current + Buffer->dirty_pos,
72			  Buffer->dirty_end - Buffer->dirty_pos);
73	if(ret != (signed int) (Buffer->dirty_end - Buffer->dirty_pos)) {
74		if(ret < 0)
75			perror("buffer_flush: write");
76		else
77			fprintf(stderr,"buffer_flush: short write\n");
78		return -1;
79	}
80	Buffer->dirty = 0;
81	Buffer->dirty_end = 0;
82	Buffer->dirty_pos = 0;
83	return 0;
84}
85
86static int invalidate_buffer(Buffer_t *Buffer, mt_off_t start)
87{
88	/*fprintf(stderr, "invalidate %x\n", Buffer);*/
89	if(Buffer->sectorSize == 32) {
90		fprintf(stderr, "refreshing directory\n");
91	}
92
93	if(_buf_flush(Buffer) < 0)
94		return -1;
95
96	/* start reading at the beginning of start's sector
97	 * don't start reading too early, or we might not even reach
98	 * start */
99	Buffer->current = ROUND_DOWN(start, Buffer->sectorSize);
100	Buffer->cur_size = 0;
101	return 0;
102}
103
104#undef OFFSET
105#define OFFSET (start - This->current)
106
107typedef enum position_t {
108	OUTSIDE,
109	APPEND,
110	INSIDE,
111	ERROR
112} position_t;
113
114static position_t isInBuffer(Buffer_t *This, mt_off_t start, size_t *len)
115{
116	if(start >= This->current &&
117	   start < This->current + This->cur_size) {
118		maximize(*len, This->cur_size - OFFSET);
119		return INSIDE;
120	} else if(start == This->current + This->cur_size &&
121		  This->cur_size < This->size &&
122		  *len >= This->sectorSize) {
123		/* append to the buffer for this, three conditions have to
124		 * be met:
125		 *  1. The start falls exactly at the end of the currently
126		 *     loaded data
127		 *  2. There is still space
128		 *  3. We append at least one sector
129		 */
130		maximize(*len, This->size - This->cur_size);
131		*len = ROUND_DOWN(*len, This->sectorSize);
132		return APPEND;
133	} else {
134		if(invalidate_buffer(This, start) < 0)
135			return ERROR;
136		maximize(*len, This->cylinderSize - OFFSET);
137		maximize(*len, This->cylinderSize - This->current % This->cylinderSize);
138		return OUTSIDE;
139	}
140}
141
142static int buf_read(Stream_t *Stream, char *buf, mt_off_t start, size_t len)
143{
144	size_t length;
145	int offset;
146	char *disk_ptr;
147	int ret;
148	DeclareThis(Buffer_t);
149
150	if(!len)
151		return 0;
152
153	/*fprintf(stderr, "buf read %x   %x %x\n", Stream, start, len);*/
154	switch(isInBuffer(This, start, &len)) {
155		case OUTSIDE:
156		case APPEND:
157			/* always load until the end of the cylinder */
158			length = This->cylinderSize -
159				(This->current + This->cur_size) % This->cylinderSize;
160			maximize(length, This->size - This->cur_size);
161
162			/* read it! */
163			ret=READS(This->Next,
164				  This->buf + This->cur_size,
165				  This->current + This->cur_size,
166				  length);
167			if ( ret < 0 )
168				return ret;
169			This->cur_size += ret;
170			if (This->current+This->cur_size < start) {
171				fprintf(stderr, "Short buffer fill\n");
172				exit(1);
173			}
174			break;
175		case INSIDE:
176			/* nothing to do */
177			break;
178		case ERROR:
179			return -1;
180	}
181
182	offset = OFFSET;
183	disk_ptr = This->buf + offset;
184	maximize(len, This->cur_size - offset);
185	memcpy(buf, disk_ptr, len);
186	return len;
187}
188
189static int buf_write(Stream_t *Stream, char *buf, mt_off_t start, size_t len)
190{
191	char *disk_ptr;
192	DeclareThis(Buffer_t);
193	size_t offset;
194
195	if(!len)
196		return 0;
197
198	This->ever_dirty = 1;
199
200#ifdef DEBUG
201	fprintf(stderr, "buf write %x   %02x %08x %08x -- %08x %08x -- %08x\n",
202		Stream, (unsigned char) This->buf[0],
203		start, len, This->current, This->cur_size, This->size);
204	fprintf(stderr, "%d %d %d %x %x\n",
205		start == This->current + This->cur_size,
206		This->cur_size < This->size,
207		len >= This->sectorSize, len, This->sectorSize);
208#endif
209	switch(isInBuffer(This, start, &len)) {
210		case OUTSIDE:
211#ifdef DEBUG
212			fprintf(stderr, "outside\n");
213#endif
214			if(start % This->cylinderSize ||
215			   len < This->sectorSize) {
216				size_t readSize;
217				int ret;
218
219				readSize = This->cylinderSize -
220					This->current % This->cylinderSize;
221
222				ret=READS(This->Next, This->buf, This->current, readSize);
223				/* read it! */
224				if ( ret < 0 )
225					return ret;
226				if(ret % This->sectorSize) {
227				  fprintf(stderr, "Weird: read size (%d) not a multiple of sector size (%d)\n", ret, (int) This->sectorSize);
228				    ret -= ret % This->sectorSize;
229				    if(ret == 0) {
230					fprintf(stderr, "Nothing left\n");
231					exit(1);
232				    }
233				}
234				This->cur_size = ret;
235				/* for dosemu. Autoextend size */
236				if(!This->cur_size) {
237					memset(This->buf,0,readSize);
238					This->cur_size = readSize;
239				}
240				offset = OFFSET;
241				break;
242			}
243			/* FALL THROUGH */
244		case APPEND:
245#ifdef DEBUG
246			fprintf(stderr, "append\n");
247#endif
248			len = ROUND_DOWN(len, This->sectorSize);
249			offset = OFFSET;
250			maximize(len, This->size - offset);
251			This->cur_size += len;
252			if(This->Next->Class->pre_allocate)
253				PRE_ALLOCATE(This->Next,
254							 This->current + This->cur_size);
255			break;
256		case INSIDE:
257			/* nothing to do */
258#ifdef DEBUG
259			fprintf(stderr, "inside\n");
260#endif
261			offset = OFFSET;
262			maximize(len, This->cur_size - offset);
263			break;
264		case ERROR:
265			return -1;
266		default:
267#ifdef DEBUG
268			fprintf(stderr, "Should not happen\n");
269#endif
270			exit(1);
271	}
272
273	disk_ptr = This->buf + offset;
274
275	/* extend if we write beyond end */
276	if(offset + len > This->cur_size) {
277		len -= (offset + len) % This->sectorSize;
278		This->cur_size = len + offset;
279	}
280
281	memcpy(disk_ptr, buf, len);
282	if(!This->dirty || offset < This->dirty_pos)
283		This->dirty_pos = ROUND_DOWN(offset, This->sectorSize);
284	if(!This->dirty || offset + len > This->dirty_end)
285		This->dirty_end = ROUND_UP(offset + len, This->sectorSize);
286
287	if(This->dirty_end > This->cur_size) {
288		fprintf(stderr,
289			"Internal error, dirty end too big dirty_end=%x cur_size=%x len=%x offset=%d sectorSize=%x\n",
290			(unsigned int) This->dirty_end,
291			(unsigned int) This->cur_size,
292			(unsigned int) len,
293			(int) offset, (int) This->sectorSize);
294		fprintf(stderr, "offset + len + grain - 1 = %x\n",
295				(int) (offset + len + This->sectorSize - 1));
296		fprintf(stderr, "ROUNDOWN(offset + len + grain - 1) = %x\n",
297				(int)ROUND_DOWN(offset + len + This->sectorSize - 1,
298								This->sectorSize));
299		fprintf(stderr, "This->dirty = %d\n", This->dirty);
300		exit(1);
301	}
302
303	This->dirty = 1;
304	return len;
305}
306
307static int buf_flush(Stream_t *Stream)
308{
309	int ret;
310	DeclareThis(Buffer_t);
311
312	if (!This->ever_dirty)
313		return 0;
314	ret = _buf_flush(This);
315	if(ret == 0)
316		This->ever_dirty = 0;
317	return ret;
318}
319
320
321static int buf_free(Stream_t *Stream)
322{
323	DeclareThis(Buffer_t);
324
325	if(This->buf)
326		free(This->buf);
327	This->buf = 0;
328	return 0;
329}
330
331static Class_t BufferClass = {
332	buf_read,
333	buf_write,
334	buf_flush,
335	buf_free,
336	0, /* set_geom */
337	get_data_pass_through, /* get_data */
338	0, /* pre-allocate */
339	get_dosConvert_pass_through /* dos convert */
340};
341
342Stream_t *buf_init(Stream_t *Next, int size,
343		   int cylinderSize,
344		   int sectorSize)
345{
346	Buffer_t *Buffer;
347	Stream_t *Stream;
348
349
350	if(size % cylinderSize != 0) {
351		fprintf(stderr, "size not multiple of cylinder size\n");
352		exit(1);
353	}
354	if(cylinderSize % sectorSize != 0) {
355		fprintf(stderr, "cylinder size not multiple of sector size\n");
356		exit(1);
357	}
358
359	if(Next->Buffer){
360		Next->refs--;
361		Next->Buffer->refs++;
362		return Next->Buffer;
363	}
364
365	Stream = (Stream_t *) malloc (sizeof(Buffer_t));
366	if(!Stream)
367		return 0;
368	Buffer = (Buffer_t *) Stream;
369	Buffer->buf = malloc(size);
370	if ( !Buffer->buf){
371		Free(Stream);
372		return 0;
373	}
374	Buffer->size = size;
375	Buffer->dirty = 0;
376	Buffer->cylinderSize = cylinderSize;
377	Buffer->sectorSize = sectorSize;
378
379	Buffer->ever_dirty = 0;
380	Buffer->dirty_pos = 0;
381	Buffer->dirty_end = 0;
382	Buffer->current = 0L;
383	Buffer->cur_size = 0; /* buffer currently empty */
384
385	Buffer->Next = Next;
386	Buffer->Class = &BufferClass;
387	Buffer->refs = 1;
388	Buffer->Buffer = 0;
389	Buffer->Next->Buffer = (Stream_t *) Buffer;
390	return Stream;
391}
392
393