Commit a046433a authored by bellard's avatar bellard
Browse files

Major overhaul of the virtual FAT driver for read/write support (Johannes Schindelin)


git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@1717 c046a42c-6fe2-441c-8c8c-71466251a162
parent 95389c86
/* vim:set shiftwidth=4 ts=8: */
/*
* QEMU Block driver for virtual VFAT (shadows a local directory)
*
* Copyright (c) 2004 Johannes E. Schindelin
* Copyright (c) 2004,2005 Johannes E. Schindelin
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
......@@ -27,19 +28,47 @@
#include "vl.h"
#include "block_int.h"
// TODO: new file
// TODO: delete file
// TODO: make root directory larger
// TODO: make directory clusters connected, so they are reserved anyway... add a member which tells how many clusters are reserved after a directory
// TODO: introduce another member in mapping_t which says where the directory resides in s->directory (for mkdir and rmdir)
// in _read and _write, before treating direntries or file contents, get_mapping to know what it is.
// TODO: mkdir
// TODO: rmdir
#ifndef S_IWGRP
#define S_IWGRP 0
#endif
#ifndef S_IWOTH
#define S_IWOTH 0
#endif
/* TODO: add ":bootsector=blabla.img:" */
/* LATER TODO: add automatic boot sector generation from
BOOTEASY.ASM and Ranish Partition Manager
Note that DOS assumes the system files to be the first files in the
file system (test if the boot sector still relies on that fact)! */
/* MAYBE TODO: write block-visofs.c */
/* TODO: call try_commit() only after a timeout */
/* #define DEBUG */
#ifdef DEBUG
#define DLOG(a) a
#undef stderr
#define stderr STDERR
FILE* stderr = NULL;
// TODO: when commit_data'ing a direntry and is_consistent, commit_remove
// TODO: reset MODE_MODIFIED when commit_remove'ing
static void checkpoint();
#define DEBUG
#ifdef __MINGW32__
void nonono(const char* file, int line, const char* msg) {
fprintf(stderr, "Nonono! %s:%d %s\n", file, line, msg);
exit(-5);
}
#undef assert
#define assert(a) if (!(a)) nonono(__FILE__, __LINE__, #a)
#endif
#else
#define DLOG(a)
#endif
/* dynamic array functions */
typedef struct array_t {
......@@ -62,23 +91,37 @@ static inline void array_free(array_t* array)
array->size=array->next=0;
}
/* make sure that memory is reserved at pointer[index*item_size] */
/* does not automatically grow */
static inline void* array_get(array_t* array,unsigned int index) {
if((index+1)*array->item_size>array->size) {
int new_size=(index+32)*array->item_size;
array->pointer=realloc(array->pointer,new_size);
if(!array->pointer)
return 0;
array->size=new_size;
array->next=index+1;
assert(index >= 0);
assert(index < array->next);
return array->pointer + index * array->item_size;
}
static inline int array_ensure_allocated(array_t* array, int index)
{
if((index + 1) * array->item_size > array->size) {
int new_size = (index + 32) * array->item_size;
array->pointer = realloc(array->pointer, new_size);
if (!array->pointer)
return -1;
array->size = new_size;
array->next = index + 1;
}
return array->pointer+index*array->item_size;
return 0;
}
static inline void* array_get_next(array_t* array) {
unsigned int next=array->next;
void* result=array_get(array,next);
array->next=next+1;
unsigned int next = array->next;
void* result;
if (array_ensure_allocated(array, next) < 0)
return NULL;
array->next = next + 1;
result = array_get(array, next);
return result;
}
......@@ -132,14 +175,32 @@ static inline int array_roll(array_t* array,int index_to,int index_from,int coun
return 0;
}
int array_remove(array_t* array,int index)
inline int array_remove_slice(array_t* array,int index, int count)
{
if(array_roll(array,array->next-1,index,1))
assert(index >=0);
assert(count > 0);
assert(index + count <= array->next);
if(array_roll(array,array->next-1,index,count))
return -1;
array->next--;
array->next -= count;
return 0;
}
int array_remove(array_t* array,int index)
{
return array_remove_slice(array, index, 1);
}
/* return the index for a given member */
int array_index(array_t* array, void* pointer)
{
size_t offset = (char*)pointer - array->pointer;
assert(offset >= 0);
assert((offset % array->item_size) == 0);
assert(offset/array->item_size < array->next);
return offset/array->item_size;
}
/* These structures are used to fake a disk and the VFAT filesystem.
* For this reason we need to use __attribute__((packed)). */
......@@ -151,7 +212,7 @@ typedef struct bootsector_t {
uint16_t reserved_sectors;
uint8_t number_of_fats;
uint16_t root_entries;
uint16_t zero;
uint16_t total_sectors16;
uint8_t media_type;
uint16_t sectors_per_fat;
uint16_t sectors_per_track;
......@@ -186,7 +247,7 @@ typedef struct partition_t {
uint8_t start_head;
uint8_t start_sector;
uint8_t start_cylinder;
uint8_t fs_type; /* 0x6 = FAT16, 0xb = FAT32 */
uint8_t fs_type; /* 0x1 = FAT12, 0x6 = FAT16, 0xb = FAT32 */
uint8_t end_head;
uint8_t end_sector;
uint8_t end_cylinder;
......@@ -218,32 +279,44 @@ typedef struct direntry_t {
/* this structure are used to transparently access the files */
typedef struct mapping_t {
/* begin is the first cluster, end is the last+1,
* offset is the offset in the file in clusters of this slice */
off_t begin,end,offset;
char* filename;
/* begin is the first cluster, end is the last+1 */
uint32_t begin,end;
/* as s->directory is growable, no pointer may be used here */
unsigned int dir_index;
enum { MODE_NORMAL,MODE_UNDEFINED,MODE_MODIFIED,MODE_DELETED,MODE_DIRECTORY } mode;
/* the clusters of a file may be in any order; this points to the first */
int first_mapping_index;
union {
/* offset is
* - the offset in the file (in clusters) for a file, or
* - the next cluster of the directory for a directory, and
* - the address of the buffer for a faked entry
*/
struct {
uint32_t offset;
} file;
struct {
int parent_mapping_index;
int first_dir_index;
} dir;
} info;
/* path contains the full path, i.e. it always starts with s->path */
char* path;
enum { MODE_UNDEFINED = 0, MODE_NORMAL = 1, MODE_MODIFIED = 2,
MODE_DIRECTORY = 4, MODE_FAKED = 8,
MODE_DELETED = 16, MODE_RENAMED = 32 } mode;
int read_only;
} mapping_t;
/* this structure is used to hold sectors which need to be written, but it's
* not known yet where to write them. */
typedef struct commit_t {
uint32_t cluster_num;
uint8_t* buf;
} commit_t;
/* write support exists for fat, direntry and file contents */
typedef enum {
WRITE_UNDEFINED,WRITE_FAT,WRITE_DIRENTRY,WRITE_DATA
} write_action_t;
#ifdef DEBUG
static void print_direntry(const struct direntry_t*);
static void print_mapping(const struct mapping_t* mapping);
#endif
/* here begins the real VVFAT driver */
typedef struct BDRVVVFATState {
BlockDriverState* bs; /* pointer to parent */
unsigned int first_sectors_number; /* 1 for a single partition, 0x40 for a disk with partition table */
unsigned char first_sectors[0x40*0x200];
......@@ -254,31 +327,33 @@ typedef struct BDRVVVFATState {
unsigned int sectors_per_cluster;
unsigned int sectors_per_fat;
unsigned int sectors_of_root_directory;
unsigned int sectors_for_directory;
uint32_t last_cluster_of_root_directory;
unsigned int faked_sectors; /* how many sectors are faked before file data */
uint32_t sector_count; /* total number of sectors of the partition */
uint32_t cluster_count; /* total number of clusters of this partition */
unsigned int first_file_mapping; /* index of the first mapping which is not a directory, but a file */
uint32_t max_fat_value;
int current_fd;
char current_fd_is_writable; /* =0 if read only, !=0 if read/writable */
mapping_t* current_mapping;
unsigned char* cluster;
unsigned char* cluster; /* points to current cluster */
unsigned char* cluster_buffer; /* points to a buffer to hold temp data */
unsigned int current_cluster;
/* write support */
array_t commit;
/* for each file, the file contents, the direntry, and the fat entries are
* written, but not necessarily in that order */
write_action_t action[3];
BlockDriverState* write_target;
char* qcow_filename;
BlockDriverState* qcow;
void* fat2;
char* used_clusters;
array_t commits;
const char* path;
int downcase_short_names;
} BDRVVVFATState;
static int vvfat_probe(const uint8_t *buf, int buf_size, const char *filename)
{
if (strstart(filename, "fat:", NULL) ||
strstart(filename, "fatrw:", NULL))
if (strstart(filename, "fat:", NULL))
return 100;
return 0;
}
......@@ -295,16 +370,19 @@ static void init_mbr(BDRVVVFATState* s)
partition->start_head=1;
partition->start_sector=1;
partition->start_cylinder=0;
partition->fs_type=(s->fat_type==16?0x6:0xb); /* FAT16/FAT32 */
partition->end_head=0xf;
/* FAT12/FAT16/FAT32 */
partition->fs_type=(s->fat_type==12?0x1:s->fat_type==16?0x6:0xb);
partition->end_head=s->bs->heads-1;
partition->end_sector=0xff; /* end sector & upper 2 bits of cylinder */;
partition->end_cylinder=0xff; /* lower 8 bits of end cylinder */;
partition->start_sector_long=cpu_to_le32(0x3f);
partition->start_sector_long=cpu_to_le32(s->bs->secs);
partition->end_sector_long=cpu_to_le32(s->sector_count);
real_mbr->magic[0]=0x55; real_mbr->magic[1]=0xaa;
}
/* direntry functions */
/* dest is assumed to hold 258 bytes, and pads with 0xffff up to next multiple of 26 */
static inline int short2long_name(unsigned char* dest,const char* src)
{
......@@ -344,9 +422,62 @@ static inline direntry_t* create_long_filename(BDRVVVFATState* s,const char* fil
return array_get(&(s->directory),s->directory.next-number_of_entries);
}
static char is_free(const direntry_t* direntry)
{
/* return direntry->name[0]==0 ; */
return direntry->attributes == 0 || direntry->name[0]==0xe5;
}
static char is_volume_label(const direntry_t* direntry)
{
return direntry->attributes == 0x28;
}
static char is_long_name(const direntry_t* direntry)
{
return direntry->attributes == 0xf;
}
static char is_short_name(const direntry_t* direntry)
{
return !is_volume_label(direntry) && !is_long_name(direntry)
&& !is_free(direntry);
}
static char is_directory(const direntry_t* direntry)
{
return direntry->attributes & 0x10 && direntry->name[0] != 0xe5;
}
static inline char is_dot(const direntry_t* direntry)
{
return is_short_name(direntry) && direntry->name[0] == '.';
}
static char is_file(const direntry_t* direntry)
{
return is_short_name(direntry) && !is_directory(direntry);
}
static inline uint32_t begin_of_direntry(const direntry_t* direntry)
{
return le16_to_cpu(direntry->begin)|(le16_to_cpu(direntry->begin_hi)<<16);
}
static inline uint32_t filesize_of_direntry(const direntry_t* direntry)
{
return le32_to_cpu(direntry->size);
}
static void set_begin_of_direntry(direntry_t* direntry, uint32_t begin)
{
direntry->begin = cpu_to_le16(begin & 0xffff);
direntry->begin_hi = cpu_to_le16((begin >> 16) & 0xffff);
}
/* fat functions */
static inline uint8_t fat_chksum(direntry_t* entry)
static inline uint8_t fat_chksum(const direntry_t* entry)
{
uint8_t chksum=0;
int i;
......@@ -375,29 +506,39 @@ static uint16_t fat_datetime(time_t time,int return_time) {
static inline void fat_set(BDRVVVFATState* s,unsigned int cluster,uint32_t value)
{
if(s->fat_type==12) {
assert(0); /* TODO */
if(s->fat_type==32) {
uint32_t* entry=array_get(&(s->fat),cluster);
*entry=cpu_to_le32(value);
} else if(s->fat_type==16) {
uint16_t* entry=array_get(&(s->fat),cluster);
*entry=cpu_to_le16(value&0xffff);
} else {
uint32_t* entry=array_get(&(s->fat),cluster);
*entry=cpu_to_le32(value);
int offset = (cluster*3/2);
unsigned char* p = array_get(&(s->fat), offset);
switch (cluster&1) {
case 0:
p[0] = value&0xff;
p[1] = (p[1]&0xf0) | ((value>>8)&0xf);
break;
case 1:
p[0] = (p[0]&0xf) | ((value&0xf)<<4);
p[1] = (value>>4);
break;
}
}
}
static inline uint32_t fat_get(BDRVVVFATState* s,unsigned int cluster)
{
//fprintf(stderr,"want to get fat for cluster %d\n",cluster);
if(s->fat_type==12) {
const uint8_t* x=s->fat.pointer+cluster*3/2;
return ((x[0]|(x[1]<<8))>>(cluster&1?4:0))&0x0fff;
if(s->fat_type==32) {
uint32_t* entry=array_get(&(s->fat),cluster);
return le32_to_cpu(*entry);
} else if(s->fat_type==16) {
uint16_t* entry=array_get(&(s->fat),cluster);
return le16_to_cpu(*entry);
} else {
uint32_t* entry=array_get(&(s->fat),cluster);
return le32_to_cpu(*entry);
const uint8_t* x=s->fat.pointer+cluster*3/2;
return ((x[0]|(x[1]<<8))>>(cluster&1?4:0))&0x0fff;
}
}
......@@ -410,69 +551,32 @@ static inline int fat_eof(BDRVVVFATState* s,uint32_t fat_entry)
static inline void init_fat(BDRVVVFATState* s)
{
int i;
array_init(&(s->fat),(s->fat_type==32?4:2));
array_get(&(s->fat),s->sectors_per_fat*0x200/s->fat.item_size-1);
if (s->fat_type == 12) {
array_init(&(s->fat),1);
array_ensure_allocated(&(s->fat),
s->sectors_per_fat * 0x200 * 3 / 2 - 1);
} else {
array_init(&(s->fat),(s->fat_type==32?4:2));
array_ensure_allocated(&(s->fat),
s->sectors_per_fat * 0x200 / s->fat.item_size - 1);
}
memset(s->fat.pointer,0,s->fat.size);
fat_set(s,0,0x7ffffff8);
for(i=1;i<s->sectors_for_directory/s->sectors_per_cluster-1;i++)
fat_set(s,i,i+1);
fat_set(s,i,0x7fffffff);
switch(s->fat_type) {
case 12: s->max_fat_value=0xfff; break;
case 16: s->max_fat_value=0xffff; break;
case 32: s->max_fat_value=0xfffffff; break;
case 32: s->max_fat_value=0x0fffffff; break;
default: s->max_fat_value=0; /* error... */
}
}
static inline int long2unix_name(unsigned char* dest,int dest_size,direntry_t* direntry_short) {
int i=-1,j;
int chksum=fat_chksum(direntry_short);
while(1) {
char* buf=(char*)(direntry_short+i);
if((buf[0]&0x3f)!=-i || direntry_short[i].reserved[1]!=chksum ||
direntry_short[i].attributes!=0xf) {
if(i<-1)
return -3;
/* take short name */
for(j=7;j>0 && direntry_short->name[j]==' ';j--);
if(j+1>dest_size)
return -1;
strncpy(dest,direntry_short->name,j+1);
dest+=j+1; dest_size-=j+1;
for(j=2;j>=0 && direntry_short->extension[j]==' ';j--);
if(j>=0) {
if(j+2>dest_size)
return -1;
dest[0]='.';
strncpy(dest+1,direntry_short->extension,j+1);
}
return 0;
}
for(j=0;j<13;j++) {
dest_size--;
if(dest_size<0)
return -2;
dest[0]=buf[2*j+((j<5)?1:(j<11)?4:6)];
if(dest[0]==0 && (buf[0]&0x40)!=0)
return 0;
dest++;
}
/* last entry, but no trailing \0? */
if(buf[0]&0x40)
return -3;
i--;
}
}
static inline direntry_t* create_short_filename(BDRVVVFATState* s,unsigned int directory_start,const char* filename,int is_dot)
/* TODO: in create_short_filename, 0xe5->0x05 is not yet handled! */
/* TODO: in parse_short_filename, 0x05->0xe5 is not yet handled! */
static inline direntry_t* create_short_and_long_name(BDRVVVFATState* s,
unsigned int directory_start, const char* filename, int is_dot)
{
int i,long_index=s->directory.next;
int i,j,long_index=s->directory.next;
direntry_t* entry=0;
direntry_t* entry_long=0;
......@@ -483,26 +587,28 @@ static inline direntry_t* create_short_filename(BDRVVVFATState* s,unsigned int d
return entry;
}
for(i=1;i<8 && filename[i] && filename[i]!='.';i++);
entry_long=create_long_filename(s,filename);
i = strlen(filename);
for(j = i - 1; j>0 && filename[j]!='.';j--);
if (j > 0)
i = (j > 8 ? 8 : j);
else if (i > 8)
i = 8;
entry=array_get_next(&(s->directory));
memset(entry->name,0x20,11);
strncpy(entry->name,filename,i);
if(filename[i]) {
int len=strlen(filename);
for(i=len;i>0 && filename[i-1]!='.';i--);
if(i>0)
memcpy(entry->extension,filename+i,(len-i>3?3:len-i));
}
if(j > 0)
for (i = 0; i < 3 && filename[j+1+i]; i++)
entry->extension[i] = filename[j+1+i];
/* upcase & remove unwanted characters */
for(i=10;i>=0;i--) {
if(i==10 || i==7) for(;i>1 && entry->name[i]==' ';i--);
if(i==10 || i==7) for(;i>0 && entry->name[i]==' ';i--);
if(entry->name[i]<=' ' || entry->name[i]>0x7f
|| strchr("*?<>|\":/\\[];,+='",entry->name[i]))
|| strchr(".*?<>|\":/\\[];,+='",entry->name[i]))
entry->name[i]='_';
else if(entry->name[i]>='a' && entry->name[i]<='z')
entry->name[i]+='A'-'a';
......@@ -514,7 +620,7 @@ static inline direntry_t* create_short_filename(BDRVVVFATState* s,unsigned int d
int j;
for(;entry1<entry;entry1++)
if(!(entry1->attributes&0xf) && !memcmp(entry1->name,entry->name,11))
if(!is_long_name(entry1) && !memcmp(entry1->name,entry->name,11))
break; /* found dupe */
if(entry1==entry) /* no dupe found */
break;
......@@ -543,8 +649,7 @@ static inline direntry_t* create_short_filename(BDRVVVFATState* s,unsigned int d
/* calculate anew, because realloc could have taken place */
entry_long=array_get(&(s->directory),long_index);
while(entry_long<entry
&& entry_long->attributes==0xf) {
while(entry_long<entry && is_long_name(entry_long)) {
entry_long->reserved[1]=chksum;
entry_long++;
}
......@@ -553,33 +658,48 @@ static inline direntry_t* create_short_filename(BDRVVVFATState* s,unsigned int d
return entry;
}
static int read_directory(BDRVVVFATState* s,const char* dirname,
int first_cluster_of_parent)
/*
* Read a directory. (the index of the corresponding mapping must be passed).
*/
static int read_directory(BDRVVVFATState* s, int mapping_index)
{
mapping_t* mapping = array_get(&(s->mapping), mapping_index);
direntry_t* direntry;
const char* dirname = mapping->path;
int first_cluster = mapping->begin;
int parent_index = mapping->info.dir.parent_mapping_index;
mapping_t* parent_mapping = (mapping_t*)
(parent_index >= 0 ? array_get(&(s->mapping), parent_index) : 0);
int first_cluster_of_parent = parent_mapping ? parent_mapping->begin : -1;
DIR* dir=opendir(dirname);
struct dirent* entry;
struct stat st;
unsigned int start_of_directory=s->directory.next;
/* mappings before first_file_mapping are directories */
unsigned int first_directory_mapping=s->first_file_mapping;
unsigned int first_cluster=(start_of_directory/0x10/s->sectors_per_cluster);
int i;
if(!dir)
assert(mapping->mode & MODE_DIRECTORY);
if(!dir) {
mapping->end = mapping->begin;
return -1;
}
i = mapping->info.dir.first_dir_index =
first_cluster == 0 ? 0 : s->directory.next;
/* actually read the directory, and allocate the mappings */
while((entry=readdir(dir))) {
unsigned int length=strlen(dirname)+2+strlen(entry->d_name);
char* buffer;
direntry_t* direntry;
struct stat st;
int is_dot=!strcmp(entry->d_name,".");
int is_dotdot=!strcmp(entry->d_name,"..");
if(start_of_directory==1 && (is_dotdot || is_dot))
if(first_cluster == 0 && (is_dotdot || is_dot))
continue;
buffer=(char*)malloc(length);
assert(buffer);
snprintf(buffer,length,"%s/%s",dirname,entry->d_name);
if(stat(buffer,&st)<0) {
......@@ -588,8 +708,8 @@ static int read_directory(BDRVVVFATState* s,const char* dirname,
}
/* create directory entry for this file */
//fprintf(stderr,"create direntry at %d (cluster %d) for %s\n",s->directory.next,s->directory.next/0x10/s->sectors_per_cluster,entry->d_name);
direntry=create_short_filename(s,start_of_directory,entry->d_name,is_dot||is_dotdot);
direntry=create_short_and_long_name(s, i, entry->d_name,
is_dot || is_dotdot);