2009年03月16日

readdir_rでls -l風味ができる情報配列を構築する。

opendirとreaddirでscandir風味の配列を構築してみた。
但しデータとしてはstatで得られた値も含む。
まず第一段として一回readdirでスキャンして必要領域を計算して
2パス目にメモリーを確保してからコピーする形にしてみた。
delphiでfindfirstとかやる気が起きなくてコンポーネントだよりだったのに
FreeBSDではあっさりとできた事には感慨を覚える。
本来derentだけでファイル名とファイルタイプは取得できるので
statまで使うのは必ずしもベストではないと思うが
ls -l風味をする以前にファイルサイズは知ることができないとどうにもならないので
こういう形になった。

今回まずstatとderentに基づいて以下の構造体を設定した。


typedef struct file_info{
int prev;
int next;
char *f_uname; //usr id
char *f_gname; //グループID
int f_mode;//20
int f_link;//24
off_t f_size; //32 全体のサイズ (バイト単位)
//blksize_t st_blksize; // ファイルシステム I/O でのブロックサイズ
blkcnt_t f_blocks; //40 8割り当てられた 512B のブロック数
time_t f_birthtime;//44
time_t f_atime; //48 最終アクセス時刻
time_t f_mtime; //52 最終修正時刻
time_t f_ctime; //56 最終状態変更時刻
unsigned short f_param; //58
unsigned char f_type; //59 ファイルタイプ種別
unsigned char f_extlen; //60 拡張子の長さ
int d_fileno; //64
unsigned short d_reclen; //66 このレコードの長さ
unsigned char d_type; //67 ファイル種別。全ファイルシステム
unsigned char d_namlen;//68 名前の長さ
char d_name[256]; // ファイル名
}File_info;


まだファイル探索オブジェクトの構造体は作っていないので
グローバル変数に頼っている。

//var
char *dirname;
DIR *dirp;
Dirent *dire;
int result;
int d_cnt,d_size;
int i,i_size,d_pos;
File_info **dirinfo;
int *dirdata,f_data,dir_off,len;

char buf[1024];


dirinfoがポインター配列になる。
dirdataがデータの実体になる。
dirdataはreallocするとアドレスの変わる可能性があるので
dirinfoのデータは無効になりかねない。
対策としてリングスキャンできる連結要素を持たせてはいるが

  1. reallocをしない

  2. アドレスではなくてインデクスにする。

  3. 個別にallocする


の三通りの対処法のうち3番目は除外しているのでとりあえずは1番となる。
Cの場合、ポインタ型配列を直接キャストしようとしてもなかなか思惑どうりにはいかないので
インデクス方式にした場合一度変数に代入してやらないといけない。
その方が効率がいいのかそれともアドレス直接の方がいいのかまだ結論はでないのだが
0から始まるインデクスにしても加算するのならばもしもアドレスが変わったら
旧アドレスとの差分を演算してやれば同じなので
とりあえずは1番目の方法で進めた。

最初のreaddirのシーケンスではd_cnt,d_sizeを求める。

d_size=d_cnt=0;

while ((dire = readdir(dirp)) != NULL){
d_cnt++;
d_size+=77+dire->d_namlen-((dire->d_namlen+1) %8);
}
rewinddir(dirp);

d_sizeは定義ではdnameは256だが実際は可変長。
上の計算ではd_name以前の68バイトにnullの1バイトと8を足して
d_namlen+1(null分)の8のあまりを引くことでレコード長を8の倍数に保っている。

これで1パス目は終わり。
そしてメモリーを確保する。

dirinfo=malloc(d_cnt*4+64);
dirdata=malloc(d_size);


2パス目にデータを書き込む。まだここのコードは汚い。
肝は

f_data=dirdata;
dir_off=((int)&(dirinfo[0]->d_fileno)-(int)&(dirinfo[0]->prev));//60

まだ構造体は変えるかもしれないのでd_nameまでのオフセットを最初に計算する。
うまく配列とメンバーからキャストして値を出すことができなかった。

数が分かっているのでforループを使う。

for (i=0;i<d_cnt;i++){

if( readdir_r(dirp, (f_data+dir_off), &dire )!= NULL){
puts(msg[2]);
return(1);
}
dirinfo[i]=f_data;
len=77+dire->d_namlen-((dire->d_namlen+1) %8);

f_data+=len;
dire->d_reclen=(short int)len;
dirinfo[i]->next=(int)f_data;
result=lstat(dire->d_name,&f_info);

readdir_rを使っている。この方法で直接構造体の中にデータを書き込ませている。
ようするに互換性を持たせたのだ。
readdir_rは成功するとNULLを返す。readdirをコピペしてしたので
最初ここではまってしまった。

レコード長計算はループ時と同じdirentのd_reclenをそのまま活用する。
lstatを使いレコードの取得をしている。各項目は現状直接代入した。
ファイルのタイプはf_modeとd_typeと重複するがd_typeの方が使いやすい。
シンボリックリンクについてはd_namlenがあるので後ろにリンク先を入れるといいと思うが
まだやっていない。それも含めて拡張子長さとかのパラメータも持たせた。
リンク先はreadlinkで求められる。


}
closedir(dirp);

ループが終わったらもうDIRはいらない。
ここで開放しないとファイルディスクリプタを枯渇させかねないので
ここで開放するのがいいだろう。

ループが終わるともう配列とデータの塊ができている。
ソートしてみる。

int file_cmp ( const File_info **d0 , const File_info **d1 ){
//begen
if ((*d0)->d_type ==4){//dir
if ((*d1)->d_type !=4) return(-1);
} else
if ((*d1)->d_type ==4) return(1);
return (strcasecmp((*d0)->d_name,(*d1)->d_name));
}

プログラムの字面はderent単独時とまったく同じ、キャストが面倒なので宣言でFile_info型を使っている。


qsort((int)(&(dirinfo[0]))+8, d_cnt-2, 4, file_cmp);

&dirinfo[2]とかするとエラーになるのでみの表記
ファイルリストは必ず「.」と「..」を含む。
ここでおもしろいのが「f_link」でこれはstatの「st_nlink」なのだが
ufs2の場合ディレクトリーの数になる。
つまり「.」と「..」の2が必ずあるから
「.」の「f_link」はそのディレクトリーのなかにあるサブディレクトリーの数を表しているわけだ。
ツリー構造を構築する場合活用できそうだが
自分自身ユーザーディレクトリーにUSBのカードリーダーを手動マウントする事もあるので微妙だ。

そしてreaddirのサンプルではわざわざ「.」をとりわけているものもあるが
sortで3番めから総数-2のループをしてやればいいだけなので削っていない。

この後まぁ適当にfprintでls風味を楽しんだのだが権限をls -l風に表記するのに

void pri_mod(int p_mod, char *buf){
//const
const long c_mod[8]={0x002D2D2D
,0x00782D2D
,0x002D772D
,0x0078772D
,0x002D2D72
,0x00782D72
,0x002D7772
,0x00787772};

//var
int a;
//begen
a=p_mod &0x0fff;
*(long*)(&buf[1])=c_mod[((a & 0x1c0)>>6)];
*(long*)(&buf[4])=c_mod[((a & 0x38)>>3)];
*(long*)(&buf[7])=c_mod[(a & 7)];
}

として
本体の方でも

const char d_mod[16]={'-','f','c','-','d','-','b','-','-'
,'-','l','-','s','-','w','-'};

としておき

表示ループ中で

buf[0]=d_mod[dirinfo[i]->d_type];
pri_mod(dirinfo[i]->f_mode,buf);

とすることでbufに「drwxrw----」
と表示を出したりした。

当然配列は使い終わってから開放する。

free(dirdata);
free(dirinfo);


finalizationみたいな形でmainを組むかincludeにして
毎回かかないようにしたいところだ。

今すぐ本格的にファイラーにとりかかる分けでもないけれど
結局mtimeとか必要なので今回作ったものをもう少しオブジェクト化したいが
目下はselectの切り出しと深い階層をどうするかという点を先にやりたい。

実践としてのメモリーアロケータとして予測がつかない場合は2パスにするというのは
いかがなものかとも思うが場当たり的なプログラミングサンプルを目指しているのではないので
とりあえず一つの形として何かできたとは思えた。
あと今回はオーソドックスにmallocを使ったが処理中固定長ということであれば
変数渡しで配列宣言はできるようだ。


posted by Xo_ox at 19:54| Comment(0) | FreeBSDアプリ | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
×

この広告は180日以上新しい記事の投稿がないブログに表示されております。