Linux システムプログラミング (2): ファイルとディレクトリ

参考文献

1. ファイルストレージ

  • ファイルは主に dentry (ディレクトリ エントリ) と Inode の 2 つの部分で構成されます。
  • いわゆるファイルの削除は Inode を削除することを意味しますが、実際にはデータはまだハードディスク上にあり、将来上書きされます。

ここに画像の説明を挿入します

1.1 i ノード

  • その本質は、ファイルの属性情報を格納する構造体です。例: 権限、タイプ、サイズ、時間、ユーザー、ディスクの場所
  • Inode はファイル属性管理構造とも呼ばれ、ほとんどの Inode はディスク上に保存されます。
  • 少数の一般的に使用される Inode と最近使用されたInode がメモリにキャッシュされます。

1.2 ディレクトリエントリ(dentry)

  • ディレクトリ項目の本質は依然として構造体であり、2 つの重要なメンバー変数 {ファイル名、Inode、...} があり、ファイルの内容(データ) はディスク ブロックに格納されます。

2. ファイルシステム

2.1 関数 stat、fstat、fstatat、lstat

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

int stat(const char *pathname, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *pathname, struct stat *buf);
int fstatat(int fd, const char *pathname, struct stat *buf, int flag);
  • 関数の戻り値

    • 成功した場合は 0 を返します
    • エラーの場合は -1 を返します
  • パス名を指定したら

    • stat 関数は、この名前付きファイルに関連する情報構造を返します。
    • fstat 関数は、記述子 fd で開かれたファイルに関する情報を取得します。
    • lstat 関数は stat に似ていますが、指定されたファイルがシンボリック リンクの場合、lstat はシンボリック リンクによって参照されるファイルではなく、シンボリック リンクに関する情報を返します。
      • stat は、シンボリック リンクが指すファイルまたはディレクトリの属性を取得します。シンボルを貫通したくない場合は、lstat を使用します。
    • fstatat 関数は、fd パラメータが指す現在開いているディレクトリに相対的なパス名のファイル統計を返します。
      • flag パラメータは、シンボリック リンクをたどるかどうかを制御します。AT_SYMLINK_NOFOLLOW フラグが設定されている場合、fstatat はシンボリック リンクをたどらず、シンボリック リンク自体に関する情報を返します。それ以外の場合、デフォルトでは、シンボリック リンクが指す実際のファイルに関する情報を返します。
      • fd パラメータの値が AT_FDCWD で、pathname パラメータが相対パス名の場合、fstatat は現在のディレクトリを基準にして pathname パラメータを評価します。pathname が絶対パスの場合、fd パラメータは無視されます。
  • 2 番目のパラメータ buf は、ファイル属性を格納するポインタであり、Inode 構造体ポインタですその基本的な形式は次のとおりです

    struct stat {
          
          
        dev_t     st_dev;         /* ID of device containing file */
        ino_t     st_ino;         /* Inode number */
        mode_t    st_mode;        /* File type and mode */
        nlink_t   st_nlink;       /* Number of hard links */
        uid_t     st_uid;         /* User ID of owner */
        gid_t     st_gid;         /* Group ID of owner */
        dev_t     st_rdev;        /* Device ID (if special file) */
        off_t     st_size;        /* Total size, in bytes */
        blksize_t st_blksize;     /* Block size for filesystem I/O */
        blkcnt_t  st_blocks;      /* Number of 512B blocks allocated */
        struct timespec st_atim;  /* Time of last access */
        struct timespec st_mtim;  /* Time of last modification */
        struct timespec st_ctim;  /* Time of last status change */
    };
    

場合

  • ファイルサイズの取得: st_size

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/stat.h>
    
    int main(int argc, char* argv[]) {
          
          
        struct stat sbuf;
    
        int ret = stat(argv[1], &sbuf);
        if (ret == -1) {
          
          
            perror("stat error");
            exit(1);
        }
    
        printf("file size: %ld\n", sbuf.st_size);
    
        return 0;
    }
    
  • ファイル許可ビット
    ここに画像の説明を挿入します

2.2 ファイルの種類

  • UNIX システム上のほとんどのファイルは通常のファイルまたはディレクトリですが、他のファイル タイプもあります。ファイルの種類には次のようなものがあります
    • 通常のファイル
      • 何らかの形式のデータが含まれています。このデータがテキスト データであるかバイナリ データであるかは、UNIX カーネルには影響しません。
    • ディレクトリファイル
      • 他のファイルの名前と、それらのファイルに関する情報へのポインタが含まれます。
      • ディレクトリ ファイルに対する読み取り権限を持つプロセスはディレクトリの内容を読み取ることができますが、ディレクトリ ファイルに直接書き込むことができるのはカーネルだけです。
    • ブロック特殊ファイル
      • アクセスごとに固定長でデバイス (ディスクなど) にバッファリングされたアクセスを提供します
    • キャラクタースペシャルファイル
      • 各アクセスは可変長で、デバイスへのバッファーなしのアクセスを提供します。
      • システム内のすべてのデバイスは、キャラクタ型スペシャル ファイルまたはブロック型スペシャル ファイルのいずれかです
    • パイプFIFO
      • プロセス間通信に使用され、名前付きパイプと呼ばれることもあります
    • ソケット
      • プロセス間のネットワーク通信に使用されます。ホスト マシン上のプロセス間の非ネットワーク通信にも使用できます。
    • シンボリックリンク
      • このタイプのファイルは別のファイルを指します

ここに画像の説明を挿入します

場合

  • ファイルの種類/権限を取得: st_mode
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/stat.h>
    
    int main(int argc, char* argv[]) {
          
          
        struct stat sbuf;
    
        // stat 会穿透符号链接,导致无法判断符号链接
        // int ret = stat(argv[1], &sbuf);
        int ret = lstat(argv[1], &sbuf);
        if (ret == -1) {
          
          
            perror("stat error");
            exit(1);
        }
    
        if (S_ISREG(sbuf.st_mode)) {
          
          
            printf("It's a regular\n");
        } else if (S_ISDIR(sbuf.st_mode)) {
          
          
            printf("It's a dir\n");
        } else if (S_ISFIFO(sbuf.st_mode)) {
          
          
            printf("It's a pipe\n");
        } else if (S_ISLNK(sbuf.st_mode)) {
          
          
            printf("It's a sym link\n");
        }
    
        return 0;
    }
    

ls -l コマンドはシンボリック リンクを貫通しませんが、cat および vim コマンドはシンボリック リンクを貫通します。

2.3 ユーザーIDとグループIDの設定

  • プロセスに関連付けられた ID が 6 つ以上あります
    • 実際のユーザー ID と実際のグループ ID は、私たちが誰であるかを識別します。これら 2 つのフィールドは、ログイン時にパスワード ファイルのログイン エントリから取得されます。通常、これらの値はログイン セッション中に変更されませんが、スーパーユーザー プロセスには値を変更するメソッドがあります。
    • 実効ユーザー ID、実効グループ ID、および所属グループ ID によってファイルのアクセス許可が決定されます
    • 保存された設定のユーザー ID と保存された設定のグループ ID には、プログラム実行時の有効なユーザー ID と有効なグループ ID のコピーが含まれます。

ここに画像の説明を挿入します

  • 通常、実効ユーザー ID は実際のユーザー ID と等しく、実効グループ ID は実際のグループ ID と等しくなります。
  • 各ファイルには所有者とグループ所有者がいます
    • 所有者は stat 構造体の st_uid で指定されます。
    • グループ所有者は st_gid で指定されます

2.4 ファイルアクセス権限

  • st_mode 値には、ファイルへのアクセス権ビットも含まれます。すべてのファイル タイプ (ディレクトリ、キャラクター スペシャル ファイルなど) にアクセス権があります。

  • 各ファイルには 9 つのアクセス権ビットがあります

    • ユーザー (所有者) を表すには u を使用し、グループを表すには g を使用し、その他を表すには o を使用します。

ここに画像の説明を挿入します

  • ファイルアクセスルール
    • 名前付きの任意のタイプのファイルを開く場合は、その名前に含まれるすべてのディレクトリ (暗黙的な現在の作業ディレクトリを含む) に対する実行権限が必要です。
      • たとえば、ファイル /usr/include/stdio.h を開くには、ディレクトリ /、/usr、および /usr/include に対する実行権限が必要です。
    • ファイルの読み取り権限によって、既存のファイルを読み取りのために開くことができるかどうかが決まります。
      • これは、オープン関数の O_RDONLY フラグと O_RDWR フラグに関連します。
    • ファイルの書き込み権限によって、既存のファイルを開いて書き込みできるかどうかが決まります。
      • これは、オープン関数の O_WRONLY フラグと O_RDWR フラグに関連します。
    • open 関数でファイルにO_TRUNC フラグを指定するには、ファイルに対する書き込み権限が必要です。
    • ディレクトリに新しいファイルを作成するには、そのディレクトリに対する書き込み権限と実行権限が必要です。
    • 既存のファイルを削除するには、そのファイルが含まれるディレクトリに対する書き込み権限と実行権限が必要です。
      • ファイル自体に対する読み取りまたは書き込み権限を持っている必要はありません。

プロセスがファイルを開く、作成する、または削除するたびに、カーネルはファイル アクセス テストを実行します。このテストには、ファイルの所有者 (st_uid および st_gid)、プロセスの実効 ID (実効ユーザー ID および実効グループ) が含まれる場合があります。 ID)、およびプロセスの所属グループ ID (サポートされている場合)。2 つの所有者 ID はファイルのプロパティであり、2 つの実効 ID と所属グループ ID はプロセスのプロパティです。

2.5 新しいファイルとディレクトリの所有権

  • 新しいファイルのユーザー ID は、プロセスの実効ユーザー ID に設定されます。グループ ID に関しては、POSIX.1 では実装で新しいファイルのグループ ID として次のいずれかを選択できます。
    • 新しいファイルのグループ ID は、プロセスの実効グループ IDにすることができます。
    • 新しいファイルのグループ ID は、その。

2.6 関数 access と faccessat

#include <fcntl.h>
#include <unistd.h>

int access(const char *pathname, int mode);
int faccessat(int fd, const char *pathname, int mode, int flag);
  • ユーザー ID を設定することにより、プロセスが既にスーパーユーザー権限で実行されている場合でも、実際のユーザーが特定のファイルにアクセスできることを確認する必要がある場合があります。

  • 関数の戻り値

    • 成功した場合は 0 を返します

    • エラーが発生した場合は-1が返されます

    • access 関数と faccessat 関数は、実際のユーザー ID と実際のグループ ID に対してアクセスをテストします。

  • テスト ファイルが既に存在する場合、mode は F_OK です。それ以外の場合、mode は、次の図にリストされている定数のビットごとの OR になります。

ここに画像の説明を挿入します

  • faccessat 関数は、次の 2 つの場合は access 関数と同じですが、それ以外の場合は、faccessat は、fd パラメータが指すオープン ディレクトリに対する相対パス名を計算します。

    • 1 つは、pathname パラメータが絶対パスであることです。
    • もう 1 つは、fd パラメータの値が AT_FDCWD であり、pathname パラメータが相対パスであることです。
  • flag パラメータを使用して、faccessat の動作を変更できます。

    • フラグが AT_EACCESS に設定されている場合、アクセス チェックでは、実際のユーザー ID と実際のグループ ID の代わりに、呼び出しプロセスの実効ユーザー ID と実効グループ ID が使用されます。

場合

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

int main(int argc, char* argv[]) {
    
    
    if (argc != 2) {
    
    
        perror("usage: access <pathname>");
        exit(1);
    }

    if (access(argv[1], R_OK) < 0) {
    
    
        perror("access error");
        exit(1);
    } else {
    
    
        printf("read access OK\n");
    }

    if (open(argv[1], O_RDONLY) < 0) {
    
    
        perror("open error");
        exit(1);
    } else {
    
    
        printf("open for reading OK\n");
    }

    return 0;
}
$ gcc access.c -o access
$ ./access fcntl.c
read access OK
open for reading OK

2.7 関数umask

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

// mask 取值见 2.4 节图
mode_t umask(mode_t mask);
  • umask 関数は、プロセスがファイル モードを設定するためのマスク ワードを作成し、前の値を返します。これは、エラーを引き起こさない数少ない戻り関数の 1 つです。

  • 関数の戻り値

    • 以前のファイル モードではマスクされた単語が作成されます
  • プロセスが新しいファイルまたはディレクトリを作成するときは、必ずテキスト モードを使用して画面文字を作成します。

    • open 関数と create 関数には両方ともパラメータ モードがあり、新しいファイルのアクセス許可ビットを指定します。

場合

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

#define RWRWRW (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)

int main(int argc, char* argv[]) {
    
    
    umask(0);
    if (creat("foo", RWRWRW) < 0) {
    
    
        perror("creat error for foo");
    }
    umask(S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
    if (creat("bar", RWRWRW) < 0) {
    
    
        perror("creat error for bar");
    }

    return 0;
}
$ umask      # 先打印当前文件模式创建屏蔽字
0002
$ gcc umask.c -o umask
$ ./umask
$ ls -l foo bar
-rw------- 1 yxd yxd 0 9月  14 08:53 bar
-rw-rw-rw- 1 yxd yxd 0 9月  14 08:53 foo
$ umask
0002
$ umask -S   # 观察文件模式创建屏蔽字是否更改
u=rwx,g=rwx,o=rx
$ umask 027  # 更改文件模式创建屏蔽字
$ umask -S
u=rwx,g=rx,o=
  • 新しいファイルを作成するプログラムを作成するときに、指定したアクセス許可ビットを確実にアクティブにするには、プロセスの実行中に umask 値を変更する必要があります。たとえば、すべてのユーザーがファイルを読み取れるようにしたい場合は、 umask を 0 に設定する必要がありますそうしないと、プロセスの実行中に有効な umask 値によって許可ビットがオフになる可能性があります。
  • プロセスのファイル モードを変更すると、親プロセス (通常はシェル) のマスクに影響を与えることなくマスクが作成されます。
  • ユーザーは、umask 値を設定して、作成されたファイルのデフォルトの権限を制御できます。値は 8 進数で表され、1 ビットがブロックする許可を表し、下図のように、対応するビットが設定されると、対応する許可が拒否されます。
    • 一般的に使用される umask 値は 002、022、027 です。
      • 002 他のユーザーがあなたのファイルに書き込めないようにします
      • 022 グループメンバーや他のユーザーによるファイルへの書き込みを禁止する
      • 027 同じグループのメンバーがあなたのファイルに書き込めないようにし、他のユーザーがあなたのファイルを読み書きまたは実行できないようにする

ここに画像の説明を挿入します

2.8 関数 chmod、fchmod、fchmodat (既存のファイルアクセス権限の変更)

#include <fcntl.h>
#include <sys/stat.h>

int chmod(const char *pathname, mode_t mode);
int fchmod(int fd, mode_t mode);
int fchmodat(int fd, const char *pathname, mode_t mode, int flag);
  • 関数の戻り値

    • 成功した場合は 0 を返します
    • エラーが発生した場合は-1が返されます
  • chmod 関数は指定されたファイルに対して動作し、fchmod 関数は開かれたファイルに対して動作します。

  • ファイルの許可ビットを変更するには

    • プロセスの実効ユーザー ID は、ファイルの所有者 ID と等しくなければなりません
    • または、プロセスにはスーパーユーザー権限が必要です
  • パラメータ モードは、次の図に示す定数のビット単位の OR です。

ここに画像の説明を挿入します

場合

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

#define RWRWRW (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)

int main(int argc, char* argv[]) {
    
    
    struct stat statbuf;

    // 对于其当前状态设置权限:先调用 stat 获得其当前权限,然后修改它
    // 显式地打开设置组 ID 位、关闭了组执行位
    if (stat("foo", &statbuf) < 0) {
    
    
        perror("stat error for foo");
        exit(1);
    }
    if (chmod("foo", (statbuf.st_mode & ~S_IXGRP) | S_ISGID) < 0) {
    
    
        perror("chmod error for foo");
        exit(1);
    }

    // 不管文件 bar 的当前权限位如何,都将其权限设置为一个绝对值 rw-r--r--
    if (chmod("bar", S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) < 0) {
    
    
        perror("chmod error for bar");
    }

    return 0;
}
$ gcc chmod.c -o chmod
$ ./chmod
$ ls -l foo bar
-rw-r--r-- 1 yxd yxd 0 9月  14 08:53 bar
-rw-rwSrw- 1 yxd yxd 0 9月  14 08:53 foo

2.9 接着ビット

  • S_ISVTX はスティッキー ビットと呼ばれます
    • このビットが実行可能プログラム ファイルに設定されている場合、プログラムが最初に実行されるとき、プログラム テキストのコピーは終了時にもスワップ領域に保存されます (プログラム テキストは機械語命令です)。次回実行時により速くメモリにロードされるようにする
    • ディレクトリ /tmp および /var/tmp は、スティッキー ビットを設定するための一般的な候補です。どのユーザーもこれら 2 つのディレクトリにファイルを作成できます。これら 2 つのディレクトリに対するユーザー (ユーザー、グループなど) の権限は、通常、読み取り、書き込み、および実行です。ただし、両方のディレクトリのファイル モードでスティッキー ビットが設定されているため、ユーザーは他人に属するファイルを削除したり名前を変更したりすることはできません。

2.10 関数 chown、fchown、fchownat、lchown

  • 次の chown 関数を使用して、ファイルのユーザー ID とグループ ID を変更できます。

    #include <fcntl.h>
    #include <unistd.h>
    
    // 如果两个参数 owner 或 group 中的任意一个是 -1,则对应的 ID 不变
    int chown(const char *pathname, uid_t owner, gid_t group);
    int fchown(int fd, uid_t owner, gid_t group);
    int lchown(const char *pathname, uid_t owner, gid_t group);
    
    int fchownat(int fd, const char *pathname, uid_t owner, gid_t group, int flag);
    
  • 関数の戻り値

    • 成功した場合は 0 を返します
    • エラーが発生した場合は-1が返されます
  • これら 4 つの関数の操作は、参照されるファイルがシンボリック リンクであることを除いて似ています。

    • シンボリック リンクの場合、lchown および fchownat (AT_SYMLINK NOFOLLOW フラグが設定されている) は、シンボリック リンクが指すファイルの所有者ではなく、シンボリック リンク自体の所有者を変更します。
    • fchown 関数は、fd パラメータで指定された開いているファイルの所有者を変更しますが、すでに開いているファイルを操作するため、シンボリック リンクの所有者の変更には使用できません。

2.11 ファイルの長さ

  • stat 構造体のメンバー st_size は、ファイルの長さをバイト単位で表します。

    • このフィールドは、通常のファイル、ディレクトリ ファイル、およびシンボリック リンクに対してのみ意味があります。
  • 通常のファイルの場合、ファイル長は 0 にすることができます。そのようなファイルの読み取りを開始すると、ファイルの終わりを示すメッセージが表示されます。

  • ディレクトリの場合、ファイルの長さは通常、数値の整数倍です (16 や 512 など)。

  • シンボリック リンクの場合、ファイルの長さはファイル名の実際のバイト数です。

    • シンボリック リンク ファイルの長さは常に st_size で示されるため、C 言語で名前の終了に通常使用される null バイトは含まれません。
  • ファイル穴

    • ホールは、ファイルの終わりを超えてオフセットを設定し、データを書き込むことによって発生します。

2.12 ファイルの切り捨て

  • ファイルを短くするために、ファイルの末尾の一部のデータを切り詰める必要がある場合があります。
    • ファイルの長さを 0 に切り詰めるのは特殊なケースであり、ファイルを開くときに O_TRUNC フラグを使用して実行できます。
    • ファイルを切り詰めるには、関数 truncate および ftruncate を呼び出すことができます。
#include <unistd.h>
#include <sys/types.h>

int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);
  • 戻り値

    • 成功した場合は 0 を返します
    • エラーが発生した場合は-1が返されます
  • これら 2 つの関数は、既存のファイルの長さを length に切り詰めます。

    • ファイルの前の長さが length より大きい場合、length を超えるデータにはアクセスできません。
    • 以前の長さが length より小さかった場合、ファイルの長さは増加し、ファイルの前の端と新しいファイルの端の間のデータは 0 として読み取られます (つまり、ファイルに穴が作成される可能性があります)

2.13 ファイルシステム

  • ディスクは 1 つ以上のパーティションに分割できます。各パーティションにはファイル システムを含めることができ、inode はファイルに関するほとんどの情報を含む固定長のレコード エントリです。

ここに画像の説明を挿入します

  • シリンダ グループの i ノードとデータ ブロック部分
    • 各 i ノードにはリンク数があり、その値はその i ノードを指すディレクトリ エントリの数です。リンク数が 0 になった場合にのみ、ファイルを削除できます (つまり、ファイルが占有しているデータ ブロックを解放できます)。
      • これが、「ファイルのリンクを解除する」ことが必ずしも「ファイルが占有しているディスク ブロックを解放する」ことを意味するとは限らない理由です。
      • このため、ディレクトリ エントリを削除する関数は、削除ではなくリンク解除と呼ばれます。
    • 別の種類のリンクはシンボリック リンクと呼ばれます。シンボリック リンク ファイル (データ ブロック内) の実際の内容には、シンボリック リンクが指すファイルの名前が含まれます。
    • i ノードには、ファイル タイプ、ファイル アクセス許可ビット、ファイル長、ファイル データ ブロックへのポインタなど、ファイルに関するすべての情報が含まれています。stat 構造内の情報のほとんどは、i 個のノードから取得されます。ディレクトリ エントリには、ファイル名と i ノード番号の 2 つの重要なデータのみが保存されます。
    • ディレクトリ エントリの i ノード番号は同じファイル システム内の対応する i ノードを指すため、ディレクトリ エントリは別のファイル システムの i ノードを指すことはできません。
    • ファイル システムを変更せずにファイルの名前を変更すると、ファイルの実際の内容は移動されず、既存の i ノードを指す新しいディレクトリ エントリが構築されるだけで、古いディレクトリ エントリは削除されますリンク数は変わりません
      • 例: ファイル /usr/lib/foo の名前を /usr/foo に変更します。/usr/lib と /usr が同じファイル システム内にある場合、ファイル foo の内容を移動する必要はありません。

ここに画像の説明を挿入します

2.14 関数 link、linkat、unlink、unlinkat、remove

なぜディレクトリエントリを i ノードの外に保持し、ファイル名を別個に保存する必要があるのでしょうか? この保管方法の利点は何ですか?

  • その目的は、ファイル共有を実現することです。Linux では、複数のディレクトリ エントリが i ノード、つまり共有ディスク ブロック (データ) を共有できます。
  • 異なるファイル名は人間の目には 2 つのファイルとして解釈されますが、カーネルの目には同じファイルです。

2.14.1 関数リンク、linkat

  • 既存のファイルへのリンクを作成するには、link 関数または linkat 関数を使用します。
    #include <fcntl.h>
    #include <unistd.h>
    
    int link(const char *oldpath, const char *newpath);
    int linkat(int oldfd, const char *oldpath, int newfd, const char *newpath, int flag);
    
  • これら 2 つの関数は、既存のファイル oldpath を参照する新しいディレクトリ エントリ newpath を作成します。
    • newpath がすでに存在する場合は、エラーが返されます。newpath の最後のコンポーネントのみを作成します。残りのパスはすでに存在している必要があります。
  • 戻り値
    • 成功した場合は 0 を返します
    • エラーが発生した場合は-1が返されます
  • 既存のファイルがシンボリック リンクの場合、flag パラメーターは、linkat 関数が既存のシンボリック リンクへのリンクを作成するか、既存のシンボリック リンクが指すファイルへのリンクを作成するかを制御します。

場合

  • mvコマンドを実装する
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <sys/stat.h>
    
    int main(int argc, char* argv[]) {
          
          
        link (argv[1], argv[2]);
        unlink(argv[1]);    
    
        return 0;
    }
    

2.14.2 関数 unlink および unlinkat

  • 既存のディレクトリ エントリを削除するには、unlink 関数を呼び出します。
#include <fcntl.h>
#include <unistd.h>

int unlink(const char *pathname);
int unlinkat(int fd, const char *pathname, int flag);
  • これら 2 つの関数は、ディレクトリ エントリを削除し、パス名で参照されるファイルのリンク数を 1 減らします。
    • ファイルへの他のリンクがある場合でも、他のリンクを通じてファイルのデータにアクセスできます。
    • エラーが発生した場合、ファイルは変更されません。
  • 戻り値
    • 成功した場合は 0 を返します
    • エラーが発生した場合は-1が返されます
  • flag パラメータは、呼び出しプロセスが unlinkat 関数のデフォルトの動作を変更する方法を提供します。
    • AT_REMOVEDIR フラグが設定されている場合、unlinkat 関数は rmdir と同様のディレクトリを削除できます。
    • このフラグがクリアされると、unlinkat は unlink と同じ操作を実行します。

リンク数が 0 になった場合のみ、ファイルの内容を削除できますファイルの内容の削除を防ぐ別の条件もあります。つまり、プロセスがファイルを開いている限り、その内容は削除できませんファイルを閉じるとき、カーネルは最初にファイルを開いているプロセスの数を確認します。この数が 0 に達すると、カーネルはリンク数を確認します。数も 0 の場合、ファイルの内容は削除されます。

  • このリンク解除の機能は、プログラムがクラッシュしても作成される一時ファイルが残らないようにするために、プログラムでよく使用されます。
    • このプロセスでは、open または create を使用してファイルを作成し、すぐに unlink を呼び出します。ファイルはまだ開いているため、その内容は削除されません。ファイルの内容は、プロセスがファイルを閉じるか終了する場合にのみ削除されます (この場合、カーネルはプロセスによって開かれたすべてのファイルを閉じます)。
    • ファイルを削除すると、ある意味、ファイルをリリースできる状態になるだけです。
    • アンリンク機能の特徴: ファイルをクリアするとき、ファイルのハードリンク数が 0 になると、対応する dentry はなくなりますが、ファイルはすぐには解放されません。システムは、ファイルを開いているすべてのプロセスがファイルを閉じるまで待機し、その後ファイルを解放します。

暗黙的なリサイクル: プロセスが終了すると、プロセスによって開かれていたすべてのファイルが閉じられ、要求されたメモリ空間が解放されます。システムのこの機能は、システム リソースの暗黙的なリサイクルと呼ばれます。

場合

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

int main(int argc, char* argv[]) {
    
    
    int fd, ret;
    char* p = "test of unlink\n";
    char* p2 = "after write something.\n";

    fd = open("lseek.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);
    if (fd < 0) {
    
    
        perror("open temp error");
        exit(1);
    }

    ret = unlink("lseek.txt");
    if (ret < 0) {
    
    
        perror("unlink error");
        exit(1);
    }

    // 此处的 write 实际是把内容写到了缓冲区而非磁盘区
    ret = write(fd, p, strlen(p));
    if (ret == -1) {
    
    
        perror("-----write error");
    }
    printf("hi! I'm printf\n");

    ret = write(fd, p2, strlen(p2));
    if (ret == -1) {
    
    
        perror("-----write error");
    }
    printf("Enter anykey continue\n");
    getchar();

    p[3] = 'H';

    close(fd);

    return 0;
}

2.14.3 機能の削除

#include <stdio.h>

int remove(const char* pathname);
  • 削除関数を使用すると、ファイルまたはディレクトリのリンクを解除できます。
    • ファイルの場合、削除にはリンク解除と同じ機能があります。
    • ディレクトリの場合、remove は rmdir と同じ機能を持ちます。

2.15 関数 rename と renameat

  • ファイルまたはディレクトリの名前は、rename 関数または renameat 関数を使用して変更できます。
#include <fcntl.h> 
#include <stdio.h>

int rename(const char *oldpath, const char *newpath);
int renameat(int oldfd, const char *oldpath, int newfd, const char *newpath);
  • oldname がファイルを参照している場合は、ファイルまたはシンボリック リンクの名前を変更します。

    • newname はディレクトリがすでに存在する場合は参照できません
    • newname がすでに存在し、それがディレクトリではない場合は、ディレクトリ エントリを削除し、oldname の名前を newname に変更します。
    • 両方のディレクトリに変更が加えられるため、呼び出しプロセスには、oldname を含むディレクトリと newname を含むディレクトリに対する書き込み権限が必要です。
  • oldname がディレクトリを参照している場合は、ディレクトリの名前を変更します

    • newname がすでに存在する場合は、ディレクトリを参照する必要があり、そのディレクトリは空のディレクトリである必要があります (空のディレクトリとは、ディレクトリ内に . と ... のエントリのみがあることを意味します)
    • newname が存在する (かつ空のディレクトリである) 場合は、まずそれを削除してから、oldname の名前を newname に変更します。
    • ディレクトリの名前を変更する場合、新しい名前にパスのプレフィックスとして古い名前を含めることはできません
      • 例: 古い名前 (/usr/foo) は新しい名前のパス接頭辞であり、削除できないため、/usr/foo の名前を /usr/foo/testdir に変更することはできません。
  • . および ... の名前は変更できません。より正確には、oldname と newname の最後の部分に . も ... も使用できません。

  • 特殊なケースとして、oldname と newname が同じファイルを参照している場合、関数は何も変更せずに正常に戻ります。

2.16 シンボリックリンク

  • シンボリック リンクは、ファイルへの間接的なポインタです。前のセクションで説明したハード リンクとは異なり、ファイルの i ノードを直接指します。

  • シンボリック リンクを導入する理由は、ハード リンクのいくつかの制限を回避するためです。

    • ハード リンクでは通常、リンクとファイルが同じファイル システム上にある必要があります。
    • スーパーユーザーのみがディレクトリへのハード リンクを作成できます (基礎となるファイル システムがサポートしている場合)。
  • シンボリック リンクおよびシンボリック リンクが指すオブジェクトには、ファイル システムの制限はありません。どのユーザーでもディレクトリへのシンボリック リンクを作成できます。シンボリック リンクは通常、ファイルまたはディレクトリ構造全体をシステム上の別の場所に移動するために使用されます。

場合

  • シンボリック リンクを使用すると、ファイル システムにサイクルが発生する可能性があります。これが発生すると、パス名を検索するほとんどの関数は、errno 値が ELOOP のエラーを返します。次の一連のコマンドを考えてみましょう。
    $ mkdir foo                 # 创建一个新目录
    $ touch foo/a               # 创建一个 0 长度的文件
    $ ln -s ../foo foo/testdir  # 创建一个符号链接
    $ ls -l foo
    total 0
    -rw-rw-r-- 1 yxd yxd 0 9月  14 15:28 a
    lrwxrwxrwx 1 yxd yxd 6 9月  14 15:28 testdir -> ../foo
    
  • 上記のコマンドは、ディレクトリ foo を作成します。このディレクトリには、a という名前のファイルと foo を指すシンボリック リンクが含まれています。
    • ループを形成するシンボリック リンク testdir
  • このようなサイクルは簡単に解消されます
    • unlink はシンボリック リンクをたどらないため、ファイル foo/testdir のリンクを解除できます。
  • しかし、このようなループを形成するハードリンクが作成されると、それを除去するのは困難になります。
    • これが、リンク関数がディレクトリへのハード リンクの構築を許可しない理由です(プロセスにスーパーユーザー権限がない限り)。

ここに画像の説明を挿入します

  • open でファイルを開くとき、open 関数に渡されたパス名がシンボリック リンクを指定している場合、open は指定されたファイルへのリンクをたどります。このシンボリック リンクが指すファイルは存在しないため、open はファイルを開けないことを示すエラーを返します。
    $ ln -s /no/such/file myfile   # 创建一个符号链接
    $ ls myfile
    myfile
    $ cat myfile                   # 试图查看文件
    cat: myfile: No such file or directory
    $ ls -l myfile
    lrwxrwxrwx 1 yxd yxd 13 9月  14 15:37 myfile -> /no/such/file
    
    • ファイル myfile は存在しますが、cat は存在​​しないと言っています。これは、myfile がシンボリック リンクであり、シンボリック リンクが指すファイルが存在しないためです。
    • ls コマンドの -l オプションには 2 つのプロンプトがあります
      • 最初の文字は l で、これがシンボリック リンクであることを示し、-> もこれがシンボリック リンクであることを示します
    • ls コマンドには別のオプション -F があります
      • シンボリック リンクのファイル名の末尾に @ 記号が追加されます。これは、-l オプションが使用されていない場合にシンボリック リンクを識別するのに役立ちます。

2.17 シンボリックリンクの作成と読み取り

  • シンボリック リンクは、symlink または symlinkat 関数を使用して作成できます。
    #include <fcntl.h>
    #include <unistd.h>
    
    int symlink(const char *target, const char *linkpath);
    int symlinkat(const char *target, int newdirfd, const char *linkpath);
    
  • 戻り値
    • 成功した場合は 0 を返します
    • エラーが発生した場合は-1が返されます
  • open 関数はシンボリック リンクをたどるため、リンク自体を開いてリンク内の名前を読み取る方法が必要です。readlink 関数と readlinkat 関数がこの機能を提供します。
    #include <fcntl.h>
    #include <unistd.h>
    
    ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);
    ssize_t readlinkat(int fd, const char *pathname, char *buf, size_t bufsiz);
    

2.18 関数 mkdir、mkdirat、および rmdir

2.18.1 関数 mkdir、mkdirat (ディレクトリの作成)

#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>

int mkdir(const char *pathname, mode_t mode);
int mkdirat(int fd, const char *pathname, mode_t mode);
  • 戻り値
    • 成功した場合は 0 を返します
    • エラーが発生した場合は-1が返されます
  • これら 2 つの関数は、新しい空のディレクトリを作成します。
    • このうち、. および ... ディレクトリ エントリは自動的に作成されます。指定されたファイル アクセス許可モードは、プロセスのファイル モード作成マスクによって変更されます。
    • よくある間違いは、ファイルと同じモードを指定することです (読み取りおよび書き込み権限のみが指定されます)。ただし、ディレクトリには通常、ディレクトリ内のファイル名へのアクセスを許可するために、少なくとも 1 つの実行許可ビットが設定されています。

2.18.2 関数 rmdir (ディレクトリの削除)

#include <unistd.h>

int rmdir(const char *pathname);
  • 戻り値

    • 成功した場合は 0 を返します
    • エラーが発生した場合は-1が返されます
  • rmdir関数を使って空のディレクトリを削除する

    • 空のディレクトリとは、. と... のみが含まれるディレクトリです。
  • この関数が呼び出されてディレクトリのリンク数が 0 になり、他のプロセスがこのディレクトリを開いていない場合、このディレクトリによって占有されていたスペースは解放されます。

  • リンク数が 0 に達したときに 1 つ以上のプロセスがこのディレクトリを開いている場合、この関数が戻る前に最後のリンクと . および... エントリが削除されます。

  • また、このディレクトリには新しいファイルを作成できません。ただし、このディレクトリは最後のプロセスが閉じるまで解放されません。

2.18.3 ファイルとディレクトリのアクセス許可

  • ディレクトリ ファイルも「ファイル」であり、そのファイルの内容は、ディレクトリ内のすべてのサブファイルのディレクトリ エントリ dentry ですvim でディレクトリを開いてみることができます

ここに画像の説明を挿入します

2.19 ディレクトリの読み込み

  • ディレクトリにアクセスできるすべてのユーザーがディレクトリを読み取ることができますが、ファイル システムの混乱を防ぐために、カーネルのみがディレクトリに書き込むことができます。
    #include <sys/types.h>
    #include <dirent.h>
    
    // 1、打开目录
    // 若成功,返回目录结构体指针;若出错,返回 NULL
    // DIR* 类似于 FILE*
    DIR *opendir(const char *name);
    DIR *fdopendir(int fd);
    
    // 2、读目录
    // 若成功,返回目录项结构体指针;若在目录尾或出错,返回 NULL,设置 errno 为相应值
    struct dirent *readdir(DIR *dirp);
    
    // 3、关闭目录
    // 若成功,返回 0;若出错,返回 -1,设置 errno 为相应值
    int closedir(DIR *dirp);
    
  • ヘッダー ファイル <dirent.h> で定義されている dirent 構造は実装に依存します。この構造体の実装定義には、少なくとも次の 2 つのメンバーが含まれています
    ino_t d_ino;       // inode 编号
    char d_name[256]   // 文件名
    

場合

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <dirent.h>

int main(int argc, char* argv[]) {
    
    
    DIR* dp;
    struct dirent* sdp;

    dp = opendir(argv[1]);
    if (dp == NULL) {
    
    
        perror("opendir error");
        exit(1);
    }

    while ((sdp = readdir(dp)) != NULL) {
    
    
        if ((strcmp(sdp->d_name, ".") == 0)) {
    
    
            continue;
        }
        printf("%s\t", sdp->d_name);
    }
    printf("\n");

    closedir(dp);

    return 0;
}
$ gcc myls.c -o myls
$ ./myls ..   # 与 ls .. 命令等价

2.20 関数 chdir、fchdir、getcwd

2.20.1 関数 chdir、fchdir

  • 各プロセスには現在の作業ディレクトリがあり、これがすべての相対パス名を検索するための開始点になります (スラッシュで始まらないパス名は相対パス名です)。現在の作業ディレクトリはプロセスの属性であり、開始ディレクトリはログイン名の属性です。

  • プロセスは chdir または fchdir 関数を呼び出して、現在の作業ディレクトリを変更します。

    #include <unistd.h>
    
    int chdir(const char *path);
    int fchdir(int fd);
    
  • 戻り値

    • 成功した場合は 0 を返します
    • エラーが発生した場合は-1が返されます
  • 現在の作業ディレクトリはプロセスの属性であるため、chdir を呼び出すプロセス自体にのみ影響し、他のプロセスには影響しません。

2.20.2 関数 getcwd

  • getcwd関数

    • 現在の作業ディレクトリ (.) から開始して、... を使用してその上位レベルのディレクトリを検索し、ディレクトリ エントリの i ノード番号が作業ディレクトリの i ノード番号と同じになるまでそのディレクトリ エントリを読み取ります。ディレクトリにあるため、対応するファイル名が見つかります。
    • この方法では、ルートに到達するまで階層ごとに移動し、現在の作業ディレクトリの完全な絶対パス名を取得します。
    #include <unistd.h>
    
    char* getcwd(char* buf, size_t size);
    
  • 戻り値

    • 成功した場合は buf を返します
    • エラーが発生した場合はNULLが返されます
  • この関数には 2 つのパラメータを渡す必要があります。1 つはバッファ アドレス buf で、もう 1 つはバッファ サイズの長さ (バイト単位) です。バッファーは絶対パス名と終端の null バイトを保持するのに十分な長さである必要があります。それ以外の場合はエラーが返されます。

  • getcwd 関数は、アプリケーションがファイル システム内の作業の開始点に戻る必要がある場合に役立ちます。

    • 作業ディレクトリを変更する前に、getcwd 関数を呼び出して最初に作業ディレクトリを保存できます。処理が完了すると、保存された元の作業ディレクトリのパス名を呼び出しパラメータとして chdir に渡すことができ、ファイル システムの開始点に戻ります。

2.21 デバイス特殊ファイル

  • st_dev と st_rdev は、しばしば混乱を引き起こす 2 つのフィールドです
    • 各ファイル システムが存在するストレージ デバイスは、メジャー デバイス番号とマイナー デバイス番号で表されます
      • デバイス番号に使用されるデータ型は、基本システム データ型 dev_t です。
      • メジャーデバイス番号はデバイスドライバーを識別します
      • マイナーデバイス番号は特定のサブデバイスを識別します
    • 通常、メジャーおよびマイナーの 2 つのマクロを使用して、メジャーおよびマイナーのデバイス番号にアクセスします。
    • システム内の各ファイル名に関連付けられた st_dev 値は、ファイル名とそれに対応する i ノードを含むファイル システムのデバイス番号です。
    • キャラクタ型スペシャル ファイルとブロック型スペシャル ファイルのみが、実際のデバイスのデバイス番号を含むst_rdev 値を持ちます。

再帰的なディレクトリトラバーサルの場合

1. アイデア分析

ここに画像の説明を挿入します

2. コードの実装

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/stat.h>
#include <dirent.h>

void isFile(char* name);

// 打开目录读取,处理目录
void read_dir(char* dir, void (*func)(char*)) {
    
    
    char path[256];
    DIR* dp;
    struct dirent *sdp;

    dp = opendir(dir);
    if (dp == NULL) {
    
    
        perror("opendir error");
        return;
    }

    // 读取目录项
    while ((sdp = readdir(dp)) != NULL) {
    
    
        if (strcmp(sdp->d_name, ".") == 0 || strcmp(sdp->d_name, "..") == 0) {
    
    
            continue;
        }

        // 目录项本身不可访问, 拼接 目录/目录项
        sprintf(path, "%s/%s", dir, sdp->d_name);

        // 判断文件类型,目录递归进入,文件显示名字/大小
        (*func)(path);
    }
    closedir(dp);

    return;
}

void isFile(char* name) {
    
    
    int ret = 0;
    struct stat sub;

    // 获取文件属性, 判断文件类型
    ret = stat(name, &sub);
    if (ret == -1) {
    
    
        perror("stat error");
        return;
    }

    // 是目录文件
    if (S_ISDIR(sub.st_mode)) {
    
    
        read_dir(name, isFile);
    }

    // 是普通文件, 直接打印名字/大小
    printf("%10s\t\t%ld\n", name, sub.st_size);

    return;
}

int main(int argc, char* argv[]) {
    
    
    // 命令行参数个数 argc = ---→ ./ls-R
    // 命令行参数列表 argv[1]---→ ./ls-R /home/test
    if (argc == 1) {
    
      // 判断命令行参数
        isFile(".");
    } else {
    
    
        isFile(argv[1]);
    }

    return 0;
}
$ gcc ls-R.c -o ls-R
$ ./ls-R 
   ./fcntl		8384
 ./mycat.c		262
  ./ls-R.c		943
  ./fcntl2		8432
    ./ls-R		8768
         .		4096

おすすめ

転載: blog.csdn.net/qq_42994487/article/details/132910626