意識低い系会社員

意識低い系会社員の日常

【スポンサーリンク】

ふつうのLinuxプログラミング 第2版の内容メモ(10章 前半)

【スポンサーリンク】

 

こんにちは。

 

最近、ふつうのLinuxプログラミング 第2版を読んでいるので知識の定着のために学んだ内容を要約したメモを書きます。

このエントリは完全な個人のメモです。

 

お勉強のためにこの本を読んでいるので、内容を覚えるためにSummarizing(サマライジング)を行います。

9章の内容はこちら。

 

amistad06-a.hatenablog.com

 

第10章 ファイルシステムにかかわるAPI

この章で説明すること

この章ではディレクトリ操作やファイル操作など、ファイルシステムに関するAPIを説明します。

 

Linuxのディレクトリ

ディレクトリも通常のファイルと基本的には同じで、openしてreadしてcloseすることができます。

しかし、ディレクトリは普通のファイルとは違い、特定の構造体の列になっています。

ディレクトリの中身を表す構造体のことをディレクトリエントリ(directory entry)と呼びます。

ディレクトリは中身が決まっているので、通常のopen, read, closeなどで操作するよりも専用APIを使う方が便利です。

 

opendir(3)

引数で指定されたパスのディレクトリを開きます。

戻り値はDIR型のポインタです。

 

readdir(3)

引数で渡されてディレクトリストリームからエントリを1つ読み込みます。

読み込んだエントリはstruct direntで返されます。

読み込みに失敗するとNULLが返ります。

struct direntの内容はOSによって異なります。

 

closedir(3)

引数で渡されたディレクトリストリームを閉じます。

 

lsコマンドを作る

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <dirent.h>

static void do_ls(char *path);

int main(int argc, char *argv[]){
    int i;

    // コマンドライン引数で表示するディレクトリのパスをもらう
    // コマンドライン引数がない場合、プログラムを終了
    if(argc < 2){
        fprintf(stderr, "%s: no arguments\n", argv[0]);
        exit(1);
    }
    for(i = 1; i < argc; i++){
        do_ls(argv[i]);
    }
}

static void do_ls(char *path){
    DIR *d;
    struct dirent *ent;

    // ディレクトリをopen
    d = opendir(path);
    if(!d){
        perror(path);
        exit(1);
    }
    // エントリがなくなるまで繰り返し
    // =演算子は代入された値を返す
    while(ent = readdir(d)){
        printf("%s\n", ent->d_name);
    }
    // ファイル操作と同様、openしたストリームは必ずcloseする
    closedir(d);
}

 

プログラムの説明はプログラム中のコメントを参照。

 

ディレクトリツリーのトラバース

ディレクトリを再帰的に処理することをディレクトリツリーのトラバースといいます。

opendirとreaddir, closedirで地道にディレクトリを辿れば再帰も実装できますが、実装時に「.」や「..」に注意する必要があります。

ディレクトリトラバースをしたい場合、「.」を排除しないと無限ループに、「..」を排除しないとルートディレクトリまでたどり着いてファイルシステム全体を処理してしまいます。

また、シンボリックリンクにも注意が必要です。

もしもルートディレクトリを指すシンボリックリンクがある場合、ルートディレクトリ配下すべてが処理され、その中にさらにルートディレクトリを指すシンボリックリンクが含まれるので無限ループに陥ってしまいます。

GNU libcにはディレクトリツリーをトラバースするAPIであるfts(3)がありますが、これは可搬性が低いため推奨されません。

地道にディレクトリツリーをトラバースするサンプルコードはふつうのLinuxプログラミングのサポートサイトで公開されています。

 

 

mkdir(2)

ディレクトリpathを作成します。

引数でパーミッションを指定します。

mkdirは比較的失敗しやすいシステムコールです。

以下のような原因で失敗することが多いです。

・ENOENT

親ディレクトリがない

・ENOTDIR

親ディレクトリに当たる部分がディレクトリではない

・EEXIST

既に存在する

・EPERM

親ディレクトリを変更する権限がない

 

umask

umaskはプロセスの属性の1つ。

mkdirやopenではパーミッションを指定しますが、実際にはそのパーミッションからumaskに設定された値が落とされたものが使用されます。

例えば、mkdirで0777を指定してもumaskが022なら、実際のパーミッションは0755になります。

 

umask(2)

 

umaskの値を変更するシステムコール。

このシステムコールは失敗することはありません。

 

mkdirコマンドを作る

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

int main(int argc, char *argv[]){
    int i;

    // コマンドライン引数で作りたいディレクトリのパスを受け取る
    // コマンドライン引数がない場合、プログラムを終了
    if(argc < 2){
        fprintf(stderr, "%s: no arguments\n", argv[0]);
        exit(1);
    }
    for(i = 1; i < argc; i++){
        // mkdir(2)でディレクトリを作成
        // 成功したら0、失敗したら-1が変える
        if(mkdir(argv[i], 0777) < 0){
            perror(argv[i]);
            exit(1);
        }
    }
    exit(0);
}

 

プログラムの説明はプログラム中のコメントを参照。

 

ディレクトリツリーを作る

mkdirコマンドのpオプションのような、親ディレクトリがない場合親ディレクトリも一緒に作るといったことはシステムコールレベルでは一発でできません。

地道にディレクトリをチェックして、なかったら作るという処理が必要になります。

 

rmdir(2)

引数で指定されたpathのディレクトリを削除します。

ディレクトリは空でなくてはいけません。

 

rmdirコマンドを作る

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[]){
    int i; 

    // コマンドライン引数で削除したいディレクトリのパスを受け取る
    // コマンドライン引数がない場合、プログラムを終了
    if(argc < 2){
        fprintf(stderr, "%s: no arguments\n", argv[0]);
        exit(1);
    }
    for(i = 1; i < argc; i++){
        // rmdir(2)でディレクトリを削除
        // 成功したら0、失敗したら-1が変える
        if(rmdir(argv[i]) < 0){
            perror(argv[0]);
            exit(1);
        }
    }
    exit(0);
}

 

プログラムの説明はプログラム中のコメントを参照。

 

ディレクトリを中身ごと消す

システムコールではrm -r相当の操作はできません。

これも地道にディレクトリをトラバースして中身を一つずつ消していくしかありません。

 

 

ハードリンク

Linuxでは1つのファイルに2つ以上の名前を付けることができます。

この仕組みがリンクです。

シンボリックリンクと区別するためにハードリンクということもあります。

1つのファイルにaとbという名前を付けた場合、

aとbは同じものを指しているので、aを開いて中身を変更したらbも変更されます。

逆も同じです。

ファイルにいくつリンクがあるか(名前がつけられているか)はls -lコマンドでリンクカウントが表示されるので、そこで確認することができます。

ファイルに2つ以上のリンク(ファイル名)があるとき、rmコマンドで片方を消してもファイルは残ります。

実は、rmコマンドが消すのはファイルではなくリンク(ファイル名)です。

ファイルの実体はリンクカウントが0になったときにはじめて削除されます。

 

link(2)

ハードリンクを作成するシステムコール。

成功したら0を返し、失敗したら-1を返す。

ディレクトリに別名を付けることはできない。

 

lnコマンドを作る

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[]){
    // 引数でファイルと別名が与えられていない場合、プログラムを終了する
    if(argc != 3){
        fprintf(stderr, "%s: wrong arguments\n", argv[0]);
        exit(1);
    }
    // ハードリンクを生成する
    // 失敗した場合、プログラムを終了する
    if(link(argv[1], argv[2]) < 0){
        perror(argv[1]);
        exit(1);
    }
    exit(0);
}

 

プログラムの説明はプログラム中のコメントを参照。

 

シンボリックリンク

ハードリンクがファイルの実体に別名をつけるのに対し、シンボリックリンクは名前に名前を結び付けます。

イメージ図は以下のような感じ。

 

f:id:amistad06-k:20190313152417p:plain

ハードリンクとシンボリックリンクのイメージ

※画像はふつうのLinuxプログラミング第2版から拝借しています。

 

シンボリックリンクには以下の特徴があります。

・シンボリックリンクには対応する実体がなくてもいい

・ファイルシステムをまたげる

・ディレクトリにも別名を付けられる

 

symlink(2)

シンボリックリンクを作成するシステムコール。

成功したら0、失敗したら-1を返します。

 

readlink(2)

シンボリックリンクが指している名前を取得するシステムコール。

取得した文字列の最後に終端文字(\0)が書き込まれないので注意してください。

成功したらバイト数を、失敗したら-1を返します。

 

symlinkコマンドを作る

ln -sと同じ動きをするものを作ります。

 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[]){
    // 引数でリンク名とリンク先が与えられていない場合、プログラムを終了する
    if(argc !=3){
        fprintf(stderr, "%s: wrong number of arguments\n", argv[0]);
        exit(1);
    }
    // シンボリックリンクを作成する
    // 失敗した場合、プログラムを終了する
    if(symlink(argv[1], argv[2]) < 0){
        perror(argv[1]);
        exit(1);
    }
    exit(0);
}

 

プログラムの説明はプログラム中のコメントを参照。

 

まとめ

10章はかなりボリュームが多いので、ここで一旦区切ります。

第10章の続きはこちら。

amistad06-a.hatenablog.com

 

おわり。

 

 

 

【スポンサーリンク】