/*
* This file is part of the uHex project.
* Copyright (C) Mateusz Viste 2015
*
* Provides a file-handling API with an integrated cache. This avoids to
* access the disk too frequently on cacheless systems, significantly speeding
* up the application when running from a diskette (or any other slow medium).
*/
#include <stdio.h>
#include <errno.h>
#include <stdlib.h> /* malloc() */
#include <string.h> /* memcpy() */
#include "file.h" /* include self for control */
#define CACHEPAGES 4 /* number of pages that the cache has to keep track of */
#define PAGESIZE 512 /* size of a single cache page (512 is the size of one
floppy sector, so it seemed like a good value). This
MUST be a power of 2! */
#define PAGESZBIT 9 /* the bit length of PAGESIZE-1 (512=9, 1024=10, etc) */
static char *cache[CACHEPAGES];
static long cacheidx[CACHEPAGES];
static int cacheseq[CACHEPAGES];
static FILE *fd = NULL;
static long fsize = 0;
static unsigned int curseq = 0;
/* clears the cache */
static void file_clearcache(void) {
int x;
for (x = 0; x < CACHEPAGES; x++) {
cacheidx[x] = -1;
cacheseq[x] = -1;
}
}
/* computes the page number of the given offset, and return it, filling
* *offset with the offset of our position within the cache page */
static long offset2page(long off, int *offset) {
long res;
res = off >> PAGESZBIT;
*offset = off - (res << PAGESZBIT);
return(res);
}
/* opens a file in given mode, returns the file's size on success, and a
* negative value on error. if error occurs, errvar is filled with errno. */
long file_open(char *fname, char *mode, int *errvar) {
int x;
*errvar = 0;
fd = fopen(fname, mode);
if (fd == NULL) {
*errvar = errno;
return(-1);
}
fseek(fd, 0, SEEK_END);
fsize = ftell(fd);
/* allocate the cache pages */
for (x = 0; x < CACHEPAGES; x++) {
cache[x] = malloc(PAGESIZE);
if (cache[x] == NULL) *errvar = errno;
}
/* if an error occured, deallocate memory and return */
if (*errvar != 0) {
for (x = 0; x < CACHEPAGES; x++) {
free(cache[x]);
}
return(-2);
}
file_clearcache();
curseq = 0;
return(fsize);
}
/* reads len bytes from file since position offset and writes them to
* bytebuff. returns the amount of bytes read (can be less than len if EOF
* reached). */
int file_read(unsigned char *bytebuff, int len, long offset) {
long page;
int pageoffs;
int i;
int oldestpage, oldestseq;
int pageid;
/* if offset above filesize, return -1 immediately */
if (offset >= fsize) return(-1);
/* compute the page for this offset */
page = offset2page(offset, &pageoffs);
/* if the read is across pages, bypass cache and read directly from file */
if (offset2page(offset + len - 1, &i) != page) {
fseek(fd, offset, SEEK_SET);
return(fread(bytebuff, 1, len, fd));
}
/* if such page in cache already? also fetch the highest and lowest seq */
oldestpage = 0;
oldestseq = cacheseq[0];
pageid = -1;
for (i = 0; i < CACHEPAGES; i++) {
if (cacheidx[i] == page) {
pageid = i;
}
/* find oldest page */
if (cacheseq[i] < oldestseq) {
oldestpage = i;
oldestseq = cacheseq[i];
}
}
/* if not, load it instead of the oldest page */
if (pageid < 0) {
pageid = oldestpage;
cacheidx[pageid] = page;
fseek(fd, offset - pageoffs, SEEK_SET);
fread(cache[pageid], 1, PAGESIZE, fd);
}
/* read the byte from cache, update the page seq and return it */
if (offset + len > fsize) len = fsize - offset; /* take care of EOF */
cacheseq[pageid] = curseq++; /* mark it as last used */
memcpy(bytebuff, cache[pageid] + pageoffs, len);
/* normalize seqs to avoid them ever wraping */
if ((oldestseq & 16384) != 0) {
for (i = 0; i < CACHEPAGES; i++) {
cacheseq[i] -= oldestseq;
}
}
return(len);
}
/* writes a byte 'b' to file at position offset */
int file_writebyte(long offset, int b) {
/* clear cache pages */
file_clearcache();
fseek(fd, offset, SEEK_SET);
return(fwrite(&b, 1, 1, fd));
}
/* close the file and frees the cache memory */
void file_close(void) {
int x;
fclose(fd);
for (x = 0; x < CACHEPAGES; x++) {
free(cache[x]);
}
}