はじめに
この章は、Linux のファイルとディレクトリインターフェイスを基にしています。このプロジェクトは、ファイルシステムの性質を中心に、lstat関数とディレクトリ操作を使って、ファイルタイプを再帰的に数えるプログラムを実装します。これは、Linux ファイルシステムにおけるファイルタイプの構成を深く理解するための便利な方法を提供します。また、このプロジェクトで開発されたファイルタイプ数えプログラムは、実際の学習や作業環境で使用できます。
👀 予想結果
$./file_type.
通常ファイル = 2, 66.67 %
ディレクトリ = 1, 33.33 %
ブロック特殊ファイル = 0, 0.00 %
キャラクタ特殊ファイル = 0, 0.00 %
FIFO = 0, 0.00 %
シンボリックリンク = 0, 0.00 %
ソケット = 0, 0.00 %
🎯 タスク
このプロジェクトでは、以下のことを学びます。
- Linux のファイルとディレクトリインターフェイスを使って、ディレクトリ内のファイルタイプを再帰的に数える C 言語のプログラムを実装する方法。
🏆 成果
このプロジェクトを完了すると、以下のことができるようになります。
- Linux で
lstat関数を使ってファイル情報を取得する。 - ディレクトリを開いてディレクトリエントリを読むなどのディレクトリ操作を行う。
- 通常ファイル、ディレクトリ、ブロック特殊ファイル、キャラクタ特殊ファイル、名前付きパイプ、シンボリックリンク、ソケットを含む、さまざまなファイルタイプを再帰的に数えるプログラムを作成する。
- ディレクトリ内の各ファイルタイプの割合を計算して表示する。
基本知識とプロジェクトファイルの作成
次に、コンセプトから実装までの手順を紹介します。主に以下の C 言語の知識ポイントを適用します。
stat構造体とlstat関数、opendir、readdir関数、dirent構造体、再帰、関数呼び出しなど。
プログラム全体では、main、myftw、dopath、myfunc、path_allocなどの関数を構築します。
myfunc関数は主に条件に合致するファイルタイプをトラバースして数えます。dopath関数は主に再帰的にパスを取得し、それがディレクトリかファイルかを判断します。myftw関数はpath_allocから完全パスを格納するためのメモリ空間の開始アドレスとサイズを取得してから始まります。path_alloc関数は主にパス(完全パス)用のメモリ空間を割り当てます。
~/projectディレクトリに新しいファイルfile_type.cを作成し、好きなコードエディタで開きます。
cd ~/project
touch file_type.c
main 関数を設計する
main 関数の主な機能は、まずコマンドライン引数を受け取り、その妥当性をチェックすることです。次に、myftw関数を呼び出して、さまざまな種類のファイルの数を計算します。最後に、ファイルタイプの割合を計算して出力します。
#include <dirent.h>
#include <limits.h>
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#define FTW_F 1 /* ディレクトリでないファイル用のフラグ */
#define FTW_D 2 /* ディレクトリファイル用のフラグ */
#define FTW_DNR 3 /* 読み取り不可なディレクトリファイル用のフラグ */
#define FTW_NS 4 /* stat によってアクセスできないファイル用のフラグ */
static char *fullpath;
static size_t pathlen;
/* ファイルを処理する関数を定義 */
typedef int Myfunc(const char *, const struct stat *, int);
static Myfunc myfunc;
static int myftw(char *, Myfunc *);
static int dopath(Myfunc *);
char *path_alloc(size_t *size);
static long nreg, ndir, nblk, nchr, nfifo, nslink, nsock, ntot;
int main(int argc, char *argv[])
{
int ret;
// 入力の妥当性チェック
if (argc!= 2)
{
printf("無効なコマンド入力! \n");
exit(1);
}
/* さまざまな種類のファイルの数を計算 */
ret = myftw(argv[1], myfunc);
/* ファイルの総数を計算 */
ntot = nreg + ndir + nblk + nchr + nfifo + nslink + nsock;
/* ゼロ割り算を回避してプログラムの安定性を向上させる */
if (ntot == 0)
ntot = 1;
/* さまざまな種類のファイルの割合を表示 */
printf("通常ファイル = %7ld, %5.2f %%\n", nreg,
nreg*100.0 / ntot);
printf("ディレクトリ = %7ld, %5.2f %%\n", ndir,
ndir*100.0 / ntot);
printf("ブロック特殊 = %7ld, %5.2f %%\n", nblk,
nblk*100.0 / ntot);
printf("キャラクタ特殊 = %7ld, %5.2f %%\n", nchr,
nchr*100.0 / ntot);
printf("FIFO = %7ld, %5.2f %%\n", nfifo,
nfifo*100.0 / ntot);
printf("シンボリックリンク = %7ld, %5.2f %%\n", nslink,
nslink*100.0 / ntot);
printf("ソケット = %7ld, %5.2f %%\n", nsock,
nsock*100.0 / ntot);
exit(ret);
}
*fullpath:ファイルの完全パスを格納するために使用。pathlen:ファイルパスの長さを格納するために使用。nreg:通常ファイルの数。ndir:ディレクトリファイルの数。nblk:ブロック特殊ファイルの数。nchr:キャラクタ特殊ファイルの数。nfifo:名前付きパイプの数。nslink:シンボリックリンクファイルの数。nsock:ソケットファイルの数。ntot:ファイルの総数。
myftw 関数を設計する
この関数は、pathnameを処理してグローバルな文字配列に保存し、その後dopathを呼び出します。関数path_allocは空間を割り当てるために使用されます。重要なことは、fullpathがグローバル変数であるため、異なる関数が便利に使用できることです。その後、dopath関数を呼び出して、パス名(ディレクトリかどうか)をさらに処理します。
static int myftw(char *pathname, Myfunc *func)
{
/* パスを保存するための文字列配列の空間を割り当てる */
fullpath = path_alloc(&pathlen);
/* 割り当てられた空間がパスを保存するのに十分でない場合、realloc を使って再割り当てする */
if (pathlen <= strlen(pathname)) {
pathlen = strlen(pathname) * 2;
if ((fullpath = realloc(fullpath, pathlen)) == NULL);
printf("realloc failed!\n");
}
/* パス名パラメータを完全パスに保存する。注意:fullpath はグローバル変数であり、dopath によって呼び出される */
strcpy(fullpath, pathname);
/* dopath 関数を呼び出す */
return(dopath(func));
}
/* パス配列の割り当て */
char *path_alloc(size_t* size)
{
char *p = NULL;
if (!size)
return NULL;
p = malloc(256);
if (p)
*size = 256;
else
*size = 0;
return p;
}
「dopath」関数を設計する
int lstat(const char *path, struct stat *buf)ファイルがシンボリックリンクの場合、lstat はシンボリックリンク自体に関する情報を返しますが、stat はリンクが指すファイルに関する情報を返します。ここで使用される 1 つのデータ構造は、stat構造です。この構造の定義は以下の通りです。
struct stat {
dev_t st_dev;
ino_t st_ino;
mode_t st_mode;
nlink_t st_nlink;
uid_t st_uid;
gid_t st_gid;
dev_t st_rdev;
off_t st_size;
timestruc_t st_atim;
timestruc_t st_mtim;
timestruc_t st_ctim;
blksize_t st_blksize;
blkcnt_t st_blocks;
char st_fstype[_ST_FSTYPSZ];
};
DIR* opendir (const char * path )機能:ディレクトリを開き、失敗した場合は空のポインタを返します。struct dirent *readdir(DIR *dir)機能:readdir() はディレクトリストリーム dir 内の次のディレクトリエントリを返します。戻り値:成功時、readdir() は次のディレクトリエントリへのポインタを返します。ディレクトリストリームの末尾に達すると、readdir() は NULL を返します。
/* dopath は、それがディレクトリかどうかを判断し、その後、直接 myfunc 関数に入ってカウントを行うか、再帰的に dopath 関数を呼び出すかを選択します。 */
static int dopath(Myfunc* func)
{
struct stat statbuf;
struct dirent *dirp;
DIR *dp;
int ret, n;
/* lstat 関数を呼び出して、pathname の stat 情報を取得します。失敗した場合は、func 関数を呼び出して FTW_NS を渡します。 */
if (lstat(fullpath, &statbuf) < 0)
return(func(fullpath, &statbuf, FTW_NS));
/* ファイル stat 構造の st_mode をチェックします。ディレクトリでない場合は、func 関数を呼び出して FTW_F を渡し、その後、myfunc によってファイルタイプを判断します。 */
if (S_ISDIR(statbuf.st_mode) == 0)
return(func(fullpath, &statbuf, FTW_F));
/* 最後のケースは、pathname がディレクトリを表す場合です。func の通常の戻り値は 0 です。したがって、func を実行した後は戻らず、再帰的に func を呼び続けます。 */
if ((ret = func(fullpath, &statbuf, FTW_D))!= 0)
return(ret);
/* パス処理、パス空間の長さを拡張します。 */
n = strlen(fullpath);
if (n + NAME_MAX + 2 > pathlen) {
pathlen *= 2;
if ((fullpath = realloc(fullpath, pathlen)) == NULL)
printf("realloc failed!\n");
}
fullpath[n++] = '/';
fullpath[n] = 0;
/* ディレクトリ内の各エントリを処理します。 */
if ((dp = opendir(fullpath)) == NULL)
return(func(fullpath, &statbuf, FTW_DNR));
while ((dirp = readdir(dp))!= NULL) {
if (strcmp(dirp->d_name, ".") == 0 ||
strcmp(dirp->d_name, "..") == 0)
continue; /* 現在のディレクトリ (.) と親ディレクトリ (..) を無視して、無限ループを回避します。 */
strcpy(&fullpath[n], dirp->d_name); /* "/"の後に現在のディレクトリエントリの名前を追加します。 */
if ((ret = dopath(func))!= 0) /* その後、新しいパス名で再帰的に dopath を呼び出します。 */
break;
}
fullpath[n-1] = 0;
/* ディレクトリを閉じます。 */
if (closedir(dp) < 0)
printf("can't close directory %s", fullpath);
return(ret);
}
myfunc 関数を設計する
myfunc関数の主な目的は、statに基づいてファイルタイプを判断し、それらをカウントすることです。S_IFMTは、st_mode フラグを解釈するために使用されるマスクです。
ファイルタイプを判断するのに役立ついくつかのマクロ定義があります。これらは、関数呼び出しに似たパラメータ化マクロです。
S_ISBLK:特殊ブロックデバイスファイルかどうかをテストします。S_ISCHR:特殊文字デバイスファイルかどうかをテストします。S_ISDIR:ディレクトリかどうかをテストします。S_ISFIFO:FIFO デバイスかどうかをテストします。S_ISREG:通常ファイルかどうかをテストします。S_ISLNK:シンボリックリンクかどうかをテストします。S_ISSOCK:ソケットかどうかをテストします。
static int myfunc(const char *pathname, const struct stat *statptr, int type)
{
switch (type) {
/* ディレクトリでないファイルの処理 */
case FTW_F:
switch (statptr->st_mode & S_IFMT) {
case S_IFREG: nreg++; break;
case S_IFBLK: nblk++; break;
case S_IFCHR: nchr++; break;
case S_IFIFO: nfifo++; break;
case S_IFLNK: nslink++; break;
case S_IFSOCK: nsock++; break;
case S_IFDIR:
printf("for S_IFDIR for %s", pathname);
}
break;
/* ディレクトリファイルの処理 */
case FTW_D:
ndir++;
break;
/* 読み取り不可なディレクトリの処理 */
case FTW_DNR:
printf("%s directory is unreadable", pathname);
break;
case FTW_NS:
printf("%s error in stat", pathname);
break;
default:
printf("Type %d is unrecognized, pathname is %s", type, pathname);
}
return(0);
}
コンパイルとテスト
端末で以下のコマンドを入力してコンパイルと実行を行います。
cd ~/project
gcc -o file_type file_type.c
./file_type.
labex:project/ $ ls
file_type file_type.c
labex:project/ $ gcc -o file_type file_type.c
labex:project/ $./file_type.
通常ファイル = 2, 66.67 %
ディレクトリ = 1, 33.33 %
ブロック特殊 = 0, 0.00 %
キャラクタ特殊 = 0, 0.00 %
FIFO = 0, 0.00 %
シンボリックリンク = 0, 0.00 %
ソケット = 0, 0.00 %
結果から、現在のディレクトリでは、通常ファイルが 66.67%、ディレクトリが 33.33% を占めていることがわかります。
システムディレクトリ内のパーミッションをカウントする必要がある場合は、コマンドを実行する前に sudo を使用できます。ただし、それでもカウントできないファイルもいくつかあります。
まとめ
このプロジェクトのトレーニングを通じて、Linux ファイルシステムに対する理解を深めることができます。ディレクトリ操作を行う方法を学び、ファイル情報を格納するstat構造についてより深く理解することができます。このプロジェクトを完了することで、実用的な Linux ツールを開発することができます。



