2010年12月28日

vvfat、もしくはおっさんの死語的知識

qemu-img とか実行すると、最近無闇にオプションが増えてtftpやらhttpやらあるのはともかく、vvfat ってあるのが割と謎に見えるとおもうのですがあまり触れてる人をみたことがないので読んでみた。

qemu-img を実行したら最後の行にサポートするフォーマット一覧がこんな感じででてくる:
Supported formats: cow qcow vdi vmdk cloop dmg bochs vpc vvfat qcow2 parallels nbd host_cdrom host_floppy host_device raw tftp ftps ftp https http


まあここで他のはファイルフォーマットだったり通信プロトコルだったりするのですぐわかるわけですが、vvfatってなにかよくわからない。

よくわからない時の行動として反射的にapt-get source qemu とかやっちゃう。FTTHバンザイ!日本人でよかったよ!そしてdebianで生活してる幸せをかみしめ…… とか思ってるあいだに手元にソースが展開される。


snake:~/Job/20101228$ cd qemu-0.12.5+dfsg
snake:~/Job/20101228/qemu-0.12.5+dfsg$ find | grep vvfat
./block/vvfat.c


vvfat.c キタコレ。

lessで見ると不穏なコメントが最初にある。

* QEMU Block driver for virtual VFAT (shadows a local directory)

仮想的なブロックデバイスで、VFATでフォーマットされている風に、ローカルのディレクトリを見せる。と書いている。ヤバい匂いがプンプンするyo! でもちゃんと動くならホストからゲストにちょちょいとファイル送るのにつかえるかも。最近ブロックデバイスの動的attach/detachもできるようになったしね。 ということでもうちょっと読んでみる。

ソースをななめ読みするとFATのベースになる線型リスト定義をしたあと
目につくのがこれ。


typedef struct bootsector_t {


これはFATの1セクタ目ですね……。 中にあるunionでfat16とfat32の両方に対応しようとしているのがわかります。同じデータ構造が2回並んでバックアップになるんですよね。PC98の頃に1GBのHDDをneco98で復旧したトラウマが蘇ります。


typedef struct partition_t {
typedef struct mbr_t {

まんまですね。このへんはBIOSに縛られて30年ほど生き延び中のPCの代名詞的構造体です。


typedef struct direntry_t {

FATのディレクトリエントリキタ。これが大活躍するはず。みなさまはlong filenameが導入された時の衝撃を覚えているでしょうか……。

/* this structure are used to transparently access the files */

typedef struct mapping_t {
/* 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;
/* 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;

さてやっとvvfat.c に独特っぽいのがでてきました。ファイルやディレクトリに対応してサイズ情報ももって、ホスト側ファイルのパス名も持ってますね。
このデータ構造をVM内からクラックできたら1秒でprocfsつつかれてVM全滅コースです。

コメントの中の s-> ってなに? とおもったら次の構造体のStateのsのようです。

typedef struct BDRVVVFATState {
BlockDriverState* bs; /* pointer to parent */
unsigned int first_sectors_number; /* 1 for a single partition, 0x40 for a d
isk with partition table */
unsigned char first_sectors[0x40*0x200];

int fat_type; /* 16 or 32 */
array_t fat,directory,mapping;

unsigned int cluster_size;
unsigned int sectors_per_cluster;
unsigned int sectors_per_fat;
unsigned int sectors_of_root_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 */
uint32_t max_fat_value;

int current_fd;
mapping_t* current_mapping;
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 */
BlockDriverState* write_target;
char* qcow_filename;
BlockDriverState* qcow;
void* fat2;
char* used_clusters;
array_t commits;
const char* path;
int downcase_short_names;
} BDRVVVFATState;

FAT16,32に対応してて、セクタやFATのブロックサイズの他にclusterという管理のためのサイズがあること、書き込みサポートまでしようとしてること、qcowという名前から書き込みはファイルに反映するのではなくqcowファイルに差分を書き出す形で反映するのかな??、というあたりが読めます……。だいぶ感じがわかってきたかも。

array_t fat,directory,mapping; となっているのでこの構造体からディレクトリやマッピングをアクセスできる。名前的にもこいつが元締めで正解のよう。

さてデータ構造はだいたい眺めました。どうやらFAT12/16/32あたりのフォーマットを割とまじめに生成してそうです。そのあとつらつら眺めると create_short_and_long_name とかで1995年あたりに苦労させられたような気がするvfatのlong filename生成コードが。

vfatでのlong filenameは複数の8.3ファイル名のディレクトリエントリを組み合わせて1つのエントリを作るというアクロバットを行っていました。ドットの扱いや先に作ったファイルのshort filenameが後から作られるファイルによって変わってしまう悪夢については近くのおっさんに聞いてください。またこのあたりにエンコードがらみの記述がないので日本語ファイル名の利用は絶望的だということもわかります。

基本的なデータ構造とFAT用の関数群がそろったところで read_directory関数がでてきます。これが元となるディレクトリ1つを読んで、対応するVVFATのディレクトリ構造とmappingを作ります。mappingはファイル1つまたはディレクトリ1つに対応するようです。

子ディレクトリを見つけると新しいmappingを作って追加してるので、read_directoryの呼びだし元、init_directory関数の
for (i = 0, cluster = 0; i < s->mapping.next; i++) {
となっているループでうまいこと回りそうです。

なんとなくうまく動きそうな気がして落ちついてきました。

このvvfatディスクの諸元がどうやって決まるのかちょっと探索してみましょう。
ファイルの一番最後にいくと static BlockDriver bdrv_vvfat = { というような行があってバックエンド各種のオペレーションが定義されています。.brdv_open に対応するvvfat_open関数で諸元は設定されるはず。

まずはfat12が16か32か。これはコマンドライン引数から生成されています。
  fprintf(stderr, "Big fat greek warning: FAT32 has not been tested. You are welcome to do so!\n");

テストされてない宣言きました。ここでサクッとexFAT対応とかFAT32対応のテストとかやるとモテそうですね。おっさんに。

さてサイズはどうなってるでしょうか。以下のような即値で入ってました。これはFAT16BIOSのCHSアドレッシング上限にあわせたディスクですね。
bs->cyls=1024; bs->heads=16; bs->secs=63;


あとフロッピーの定義もはいっています。
bs->cyls = 80; bs->heads = 2; bs->secs = 36;


今手元のソースを見るとこの3つを掛けたものにパーティションテーブルやFATの分のオフセットを足した値でディスク容量が決まります。
特に指定がなければ504MB, フロッピーの指定があれば2.88MB。シリンダ数などの指定はできず、自動的に拡張する仕組みもないので今のところここが限界。むむ。

ちょっとしたドライバとかを渡すには便利ですが最初に期待した汎用のホスト→ゲストファイル渡し経路としてはちょっと苦しいなあ。

尻すぼみになって御免!