305d71ea0a
using frama-c
348 lines
11 KiB
C
348 lines
11 KiB
C
/*
|
|
|
|
File: hdcache.c
|
|
|
|
Copyright (C) 2005-2008 Christophe GRENIER <grenier@cgsecurity.org>
|
|
|
|
This software 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 the Free Software Foundation, Inc., 51
|
|
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
*/
|
|
#if !defined(DISABLED_FOR_FRAMAC)
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
#include <stdio.h>
|
|
#ifdef HAVE_STDLIB_H
|
|
#include <stdlib.h>
|
|
#endif
|
|
#ifdef HAVE_STRING_H
|
|
#include <string.h>
|
|
#endif
|
|
#include "types.h"
|
|
#include "common.h"
|
|
#include "hdcache.h"
|
|
#include "log.h"
|
|
|
|
#define CACHE_BUFFER_NBR 16
|
|
#define CACHE_DEFAULT_SIZE 64*512
|
|
//#define DEBUG_CACHE 1
|
|
|
|
struct cache_buffer_struct
|
|
{
|
|
unsigned char *buffer;
|
|
unsigned int buffer_size;
|
|
unsigned int cache_size;
|
|
uint64_t cache_offset;
|
|
int cache_status;
|
|
};
|
|
|
|
struct cache_struct
|
|
{
|
|
disk_t *disk_car;
|
|
struct cache_buffer_struct cache[CACHE_BUFFER_NBR];
|
|
#ifdef DEBUG_CACHE
|
|
uint64_t nbr_fnct_sect;
|
|
uint64_t nbr_pread_sect;
|
|
unsigned int nbr_fnct_call;
|
|
unsigned int nbr_pread_call;
|
|
#endif
|
|
unsigned int cache_buffer_nbr;
|
|
unsigned int cache_size_min;
|
|
unsigned int last_io_error_nbr;
|
|
};
|
|
|
|
/*@
|
|
@ requires \valid(disk_car);
|
|
@ requires valid_disk(disk_car);
|
|
@ requires \valid((char *)buffer + (0 .. count-1));
|
|
@ requires separation: \separated(disk_car, (char *)buffer + (0 .. count-1));
|
|
@*/
|
|
static int cache_pread_aux(disk_t *disk_car, void *buffer, const unsigned int count, const uint64_t offset, const unsigned int read_ahead)
|
|
{
|
|
struct cache_struct *data=(struct cache_struct *)disk_car->data;
|
|
#ifdef DEBUG_CACHE
|
|
log_info("cache_pread(buffer, count=%u, offset=%llu, read_ahead=%u)\n", count,(long long unsigned)offset, read_ahead);
|
|
data->nbr_fnct_call++;
|
|
#endif
|
|
{
|
|
unsigned int i;
|
|
unsigned int cache_buffer_nbr;
|
|
/* Data is probably in the last buffers */
|
|
for(i=0, cache_buffer_nbr=data->cache_buffer_nbr;
|
|
i<CACHE_BUFFER_NBR;
|
|
i++, cache_buffer_nbr=(cache_buffer_nbr+CACHE_BUFFER_NBR-1)%CACHE_BUFFER_NBR)
|
|
{
|
|
const struct cache_buffer_struct *cache=&data->cache[cache_buffer_nbr];
|
|
if(cache->cache_offset <= offset &&
|
|
offset < cache->cache_offset +cache->cache_size &&
|
|
cache->buffer!=NULL && cache->cache_size>0)
|
|
{
|
|
const unsigned int data_available= cache->cache_size + cache->cache_offset - offset;
|
|
const int res=cache->cache_status + cache->cache_offset - offset;
|
|
if(count<=data_available)
|
|
{
|
|
#ifdef DEBUG_CACHE
|
|
log_info("cache use %5u count=%u, coffset=%llu, cstatus=%d\n",
|
|
cache_buffer_nbr, cache->cache_size, (long long unsigned)cache->cache_offset,
|
|
cache->cache_status);
|
|
data->nbr_fnct_sect+=count;
|
|
#endif
|
|
memcpy(buffer, cache->buffer + offset - cache->cache_offset, count);
|
|
return (res < (signed)count ? res : (signed)count );
|
|
}
|
|
else
|
|
{
|
|
#ifdef DEBUG_CACHE
|
|
log_info("cache USE %5u count=%u, coffset=%llu, ctstatus=%d, call again cache_pread_aux\n",
|
|
cache_buffer_nbr, cache->cache_size, (long long unsigned)cache->cache_offset,
|
|
cache->cache_status);
|
|
data->nbr_fnct_sect+=data_available;
|
|
#endif
|
|
memcpy(buffer, cache->buffer + offset - cache->cache_offset, data_available);
|
|
return res + cache_pread_aux(disk_car, (unsigned char*)buffer+data_available,
|
|
count-data_available, offset+data_available, read_ahead);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
{
|
|
struct cache_buffer_struct *cache;
|
|
const unsigned int count_new=(read_ahead!=0 && count<data->cache_size_min && (offset+data->cache_size_min<data->disk_car->disk_real_size)?data->cache_size_min:count);
|
|
data->cache_buffer_nbr=(data->cache_buffer_nbr+1)%CACHE_BUFFER_NBR;
|
|
cache=&data->cache[data->cache_buffer_nbr];
|
|
if(cache->buffer_size < count_new)
|
|
{ /* Buffer is too small, drop it */
|
|
free(cache->buffer);
|
|
cache->buffer=NULL;
|
|
}
|
|
if(cache->buffer==NULL)
|
|
{ /* Allocate buffer */
|
|
cache->buffer_size=(count_new<CACHE_DEFAULT_SIZE?CACHE_DEFAULT_SIZE:count_new);
|
|
cache->buffer=(unsigned char *)MALLOC(cache->buffer_size);
|
|
}
|
|
cache->cache_size=count_new;
|
|
cache->cache_offset=offset;
|
|
cache->cache_status=data->disk_car->pread(data->disk_car, cache->buffer, count_new, offset);
|
|
#ifdef DEBUG_CACHE
|
|
data->nbr_fnct_sect+=count;
|
|
data->nbr_pread_call++;
|
|
data->nbr_pread_sect+=count_new;
|
|
log_info("cache PREAD(buffer[%u], count=%u, count_new=%u, offset=%llu, cstatus=%d)\n",
|
|
data->cache_buffer_nbr, count, count_new, (long long unsigned)offset,
|
|
cache->cache_status);
|
|
#endif
|
|
if(cache->cache_status >= (signed)count)
|
|
{
|
|
data->last_io_error_nbr=0;
|
|
memcpy(buffer, cache->buffer, count);
|
|
return count;
|
|
}
|
|
/* Read failure */
|
|
data->last_io_error_nbr++;
|
|
if(count_new<=disk_car->sector_size || disk_car->sector_size<=0 || data->last_io_error_nbr>1)
|
|
{
|
|
memcpy(buffer, cache->buffer, count);
|
|
return cache->cache_status;
|
|
}
|
|
/* Free the existing cache */
|
|
cache->cache_size=0;
|
|
/* split the read sector by sector */
|
|
{
|
|
unsigned int off;
|
|
memset(buffer, 0, count);
|
|
for(off=0; off<count; off+=disk_car->sector_size)
|
|
{
|
|
if(cache_pread_aux(disk_car,
|
|
(unsigned char*)buffer+off,
|
|
(disk_car->sector_size < count - off ? disk_car->sector_size : count - off),
|
|
offset+off, 0) <= 0)
|
|
{
|
|
return off;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*@
|
|
@ requires \valid(disk_car);
|
|
@ requires valid_disk(disk_car);
|
|
@ requires \valid((char *)buffer + (0 .. count-1));
|
|
@ requires separation: \separated(disk_car, (char *)buffer + (0 .. count-1));
|
|
@*/
|
|
static int cache_pread(disk_t *disk_car, void *buffer, const unsigned int count, const uint64_t offset)
|
|
{
|
|
const struct cache_struct *data=(const struct cache_struct *)disk_car->data;
|
|
return cache_pread_aux(disk_car, buffer, count, offset, (data->last_io_error_nbr==0));
|
|
}
|
|
|
|
/*@
|
|
@ requires \valid(disk_car);
|
|
@ requires valid_disk(disk_car);
|
|
@ requires \valid_read((char *)buffer + (0 .. count-1));
|
|
@ requires separation: \separated(disk_car, (const char *)buffer + (0 .. count-1));
|
|
@ decreases 0;
|
|
@*/
|
|
static int cache_pwrite(disk_t *disk_car, const void *buffer, const unsigned int count, const uint64_t offset)
|
|
{
|
|
struct cache_struct *data=(struct cache_struct *)disk_car->data;
|
|
unsigned int i;
|
|
for(i=0;i<CACHE_BUFFER_NBR;i++)
|
|
{
|
|
struct cache_buffer_struct *cache=&data->cache[i];
|
|
if(!(cache->cache_offset+cache->cache_size-1 < offset || offset+count-1 < cache->cache_offset))
|
|
{
|
|
/* Discard the cache */
|
|
cache->cache_size=0;
|
|
}
|
|
}
|
|
disk_car->write_used=1;
|
|
return data->disk_car->pwrite(data->disk_car, buffer, count, offset);
|
|
}
|
|
|
|
/*@
|
|
@ requires \valid(disk_car);
|
|
@ requires valid_disk(disk_car);
|
|
@ decreases 0;
|
|
@*/
|
|
static void cache_clean(disk_t *disk_car)
|
|
{
|
|
if(disk_car->data)
|
|
{
|
|
struct cache_struct *data=(struct cache_struct *)disk_car->data;
|
|
unsigned int i;
|
|
#ifdef DEBUG_CACHE
|
|
log_info("%s\ncache_pread total_call=%u, total_count=%llu\n read total_call=%u, total_count=%llu\n",
|
|
data->disk_car->description(data->disk_car),
|
|
data->nbr_fnct_call, (long long unsigned)data->nbr_fnct_sect,
|
|
data->nbr_pread_call, (long long unsigned)data->nbr_pread_sect);
|
|
#endif
|
|
data->disk_car->clean(data->disk_car);
|
|
for(i=0;i<CACHE_BUFFER_NBR;i++)
|
|
{
|
|
struct cache_buffer_struct *cache=&data->cache[i];
|
|
free(cache->buffer);
|
|
}
|
|
free(disk_car->data);
|
|
disk_car->data=NULL;
|
|
}
|
|
free(disk_car);
|
|
}
|
|
|
|
/*@
|
|
@ requires \valid(disk_car);
|
|
@ decreases 0;
|
|
@*/
|
|
static int cache_sync(disk_t *disk_car)
|
|
{
|
|
struct cache_struct *data=(struct cache_struct *)disk_car->data;
|
|
return data->disk_car->sync(data->disk_car);
|
|
}
|
|
|
|
/*@
|
|
@ requires \valid_read(CHS_source);
|
|
@ requires \valid(CHS_dst);
|
|
@ requires separation: \separated(CHS_dst, CHS_source);
|
|
@ assigns CHS_dst->cylinders, CHS_dst->heads_per_cylinder, CHS_dst->sectors_per_head;
|
|
@ ensures CHS_dst->cylinders==CHS_source->cylinders;
|
|
@ ensures CHS_dst->heads_per_cylinder==CHS_source->heads_per_cylinder;
|
|
@ ensures CHS_dst->sectors_per_head==CHS_source->sectors_per_head;
|
|
@*/
|
|
static void dup_geometry(CHSgeometry_t * CHS_dst, const CHSgeometry_t * CHS_source)
|
|
{
|
|
CHS_dst->cylinders=CHS_source->cylinders;
|
|
CHS_dst->heads_per_cylinder=CHS_source->heads_per_cylinder;
|
|
CHS_dst->sectors_per_head=CHS_source->sectors_per_head;
|
|
}
|
|
|
|
/*@
|
|
@ requires \valid(disk_car);
|
|
@ requires valid_disk(disk_car);
|
|
@ decreases 0;
|
|
@ ensures valid_read_string(\result);
|
|
@*/
|
|
static const char *cache_description(disk_t *disk_car)
|
|
{
|
|
const char *tmp;
|
|
struct cache_struct *data=(struct cache_struct *)disk_car->data;
|
|
dup_geometry(&data->disk_car->geom,&disk_car->geom);
|
|
data->disk_car->disk_size=disk_car->disk_size;
|
|
tmp=data->disk_car->description(data->disk_car);
|
|
/*@ assert valid_read_string(tmp); */
|
|
return tmp;
|
|
}
|
|
|
|
/*@
|
|
@ requires \valid(disk_car);
|
|
@ requires valid_disk(disk_car);
|
|
@ decreases 0;
|
|
@ ensures valid_read_string(\result);
|
|
@*/
|
|
static const char *cache_description_short(disk_t *disk_car)
|
|
{
|
|
const char *tmp;
|
|
struct cache_struct *data=(struct cache_struct *)disk_car->data;
|
|
dup_geometry(&data->disk_car->geom,&disk_car->geom);
|
|
data->disk_car->disk_size=disk_car->disk_size;
|
|
tmp=data->disk_car->description_short(data->disk_car);
|
|
/*@ assert valid_read_string(tmp); */
|
|
return tmp;
|
|
}
|
|
|
|
disk_t *new_diskcache(disk_t *disk_car, const unsigned int testdisk_mode)
|
|
{
|
|
unsigned int i;
|
|
struct cache_struct*data=(struct cache_struct*)MALLOC(sizeof(*data));
|
|
disk_t *new_disk_car=(disk_t *)MALLOC(sizeof(*new_disk_car));
|
|
memcpy(new_disk_car,disk_car,sizeof(*new_disk_car));
|
|
data->disk_car=disk_car;
|
|
#ifdef DEBUG_CACHE
|
|
data->nbr_fnct_sect=0;
|
|
data->nbr_pread_sect=0;
|
|
data->nbr_fnct_call=0;
|
|
data->nbr_pread_call=0;
|
|
#endif
|
|
data->cache_buffer_nbr=0;
|
|
data->last_io_error_nbr=0;
|
|
if(testdisk_mode&TESTDISK_O_READAHEAD_8K)
|
|
data->cache_size_min=16*512;
|
|
else if(testdisk_mode&TESTDISK_O_READAHEAD_32K)
|
|
data->cache_size_min=64*512;
|
|
else
|
|
data->cache_size_min=0;
|
|
dup_geometry(&new_disk_car->geom,&disk_car->geom);
|
|
new_disk_car->disk_size=disk_car->disk_size;
|
|
new_disk_car->disk_real_size=disk_car->disk_real_size;
|
|
new_disk_car->write_used=0;
|
|
new_disk_car->data=data;
|
|
new_disk_car->pread=&cache_pread;
|
|
new_disk_car->pwrite=&cache_pwrite;
|
|
new_disk_car->sync=&cache_sync;
|
|
new_disk_car->clean=&cache_clean;
|
|
new_disk_car->description=&cache_description;
|
|
new_disk_car->description_short=&cache_description_short;
|
|
new_disk_car->rbuffer=NULL;
|
|
new_disk_car->wbuffer=NULL;
|
|
new_disk_car->rbuffer_size=0;
|
|
new_disk_car->wbuffer_size=0;
|
|
for(i=0;i<CACHE_BUFFER_NBR;i++)
|
|
{
|
|
data->cache[i].buffer=NULL;
|
|
data->cache[i].buffer_size=0;
|
|
}
|
|
return new_disk_car;
|
|
}
|
|
#endif
|