2009年03月17日

ftsでファイル階層を取得する。

危うくreaddirで本実装を開始してしまいそうだったが
めげずにライブラリ探索してfts系の関数にいきついた。lsやrmも普通にこれを使っているようだ。
ftsは内部でデータ構造を構築しsortを指定できるがsortは階層ごとに行われるようだ。
工夫しないと単一レベルだけで止めることはできない。
一方ftwという関数はsortはできないものの階層の深さを指定できselectをコールバックで呼び出せる。
どちらもscandirやreaddirと比べて一長一短だが最初からstatをもっているので扱いやすい。
今回は取り合えずftsを扱った。
FreeBSDについてmanのftsの記述は微妙に間違っているように感じ取れた。

まず宣言として

#include <sys/types.h>
#include <stdio.h>
//#include <stdlib.h>
#include <string.h>
#include <fts.h>
#include <sys/stat.h>

が必要になる。
stdlibをコメントアウトしたが実際はstdio.hもstring.hもなくても動く
逆にsys/types.hはないとfts.h内でエラーがでる。

構造体もLinuxのものを引っ張ったような感じだがfts.hをみると

typedef struct _ftsent {
struct _ftsent *fts_cycle; /* cycle node */
struct _ftsent *fts_parent; /* parent directory */
struct _ftsent *fts_link; /* next file in directory */
union {
struct {
long __fts_number; /* local numeric value */
void *__fts_pointer; /* local address value */
} __struct_ftsent;
int64_t __fts_bignum;
} __union_ftsent;
#define fts_number __union_ftsent.__struct_ftsent.__fts_number
#define fts_pointer __union_ftsent.__struct_ftsent.__fts_pointer
#define fts_bignum __union_ftsent.__fts_bignum

char *fts_accpath; /* access path */
char *fts_path; /* root path */
int fts_errno; /* errno for this node */
int fts_symfd; /* fd for symlink */
u_short fts_pathlen; /* strlen(fts_path) */
u_short fts_namelen; /* strlen(fts_name) */

ino_t fts_ino; /* inode */
dev_t fts_dev; /* device */
nlink_t fts_nlink; /* link count */

short fts_level; /* depth (-1 to N) */
u_short fts_info; /* user flags for FTSENT structure */

u_short fts_flags; /* private flags for FTSENT structure */

u_short fts_instr; /* fts_set() instructions */

struct stat *fts_statp; /* stat(2) information */
char *fts_name; /* file name */
FTS *fts_fts; /* back pointer to main FTS */
} FTSENT;

と微妙に違っていた。

使い方の基本としてはreaddirと似た感じで
fts_openでFTSのインスタンスを取得して
fts_readで読み込みながら処理して
fts_closeで終了

readdirと違って巻き戻し用関数は提供されていない。
fts_openで指定する比較関数はそのディレクトリー内だけのsortに使われる。

sortのためのcompare関数もqsortのようでFTSENTが渡される。
「const FTSENT *a」としているところが多いが「**a」じゃないと動かない。

今回は以下のcompareを書いてみた。無論変数にパラメータとしていれてもいい。

//public
int cmpare(const FTSENT **a, const FTSENT **b)
{
//var
int t;
//begen
if (S_ISDIR((*a)->fts_statp->st_mode)){
if (!(S_ISDIR((*b)->fts_statp->st_mode))) return(-1);
} else
if (S_ISDIR((*b)->fts_statp->st_mode))return(1);

t =(*a)->fts_statp->st_ctime - (*b)->fts_statp->st_ctime;
if (t)return(t);
return (strcasecmp((*a)->fts_name, (*b)->fts_name));
}

aがディレクトリーの時bがディレクトリーでなければ-1を返し
aがディレクトリーでない時bがディレクトリーならば1を返す
こうするとディレクトリーが上にくる。
ディレクトリーをスキップしたい場合は-1と1を逆にするといい。

次にfts_statp つまりstatのctimeを比較してゼロでなければ引いた結果をそのまま返す。
最後は文字で比較
ソートはreadでファイルディスクリプタを取得した時に行うみたいなので無駄が少ない。

mainでは変数として

FTS *ftsp;
FTSENT *p;
char *path[] = { ".", NULL };
struct tm f_tm1;
struct tm *f_tm;

などを使った。
tmのポインタ変数に実体を割り当てて置く

f_tm=&f_tm1;

無論時間を文字化したりしないなら不要

fts_openへの配列の渡し型は結構面倒くさく型キャスト用のstructが無い場合は
今回のpath配列のような書き方が必要になる。
main関数の場合もう一つの方法としてはargv[0]に"."を代入する手が使える。

fps_openは失敗すると0を返す。

if ((ftsp = fts_open( (argc == 1)? path :&argv[1]
,FTS_PHYSICAL
, cmpare)) == NULL)
{puts("error");return(1);}


第一要素に三項式を使ってみた。関数の引数に使えるところがdelphiとは違う。
Cの場合case文は存在しないのでif文を羅列するなら三項式を使うのも一考ではある。
実行ファイルの引数がある時はargv[1]の値、そうでないときはpath

argv[0]=".";
fts_open(argv[argc-1]

とできなくてもない。
あとポインタ型配列は

argv[0]=".";
if (argc > 1)argv++;
fts_open(argv

などともできる。
第二引数は選択肢が少なく動作も不確かだが
シンボリックの先を示すFTS_LOGICALかファイル実体を示すFTS_PHYSICALかはよく使いそうだ。

compareをNULLにすると比較をしない。ファイル容量計算や一括削除の時はしなくてもいいかもしれない。


while((p = fts_read(ftsp)) != NULL) {

でFTSENTが取得できる。
パーミッションの関係でエラーがでることがあるから
p->fts_infoで処理を絞る必要はあるかもしれない。

この次に処理がくるのだがここはコールバックの方が良いだろう。


if ((int)p->fts_level>0)
fts_set(ftsp,p,FTS_SKIP);

とレベルを絞ることで探索デプスを狭めることはできた。

ソート済みなので配列を保持する必要は無く一発で処理が終わる。
但しディレクトリーにまたがるファイルのソートはできない。

巻き戻し関数などは無いがftspのパラメータを直接操作するなど
工夫の余地はありそうだ。

終了したら

fts_close(ftsp);

を実行する。

scandirより明かに使いやすい。単一層の名前の一覧を保持する場合はreaddirよりコスト高
メモリ管理は悪そうだが先日の「プロトタイプ」よりも現実的ではないだろうか


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

メールアドレス:

ホームページアドレス:

コメント:

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


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

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