OPEN_BY_HANDLE_AT(2)

名前
name_to_handle_at, open_by_handle_at - パス名に対するハンドルの取得とハンドルによるファイルのオープン

書式
#define _GNU_SOURCE         /* feature_test_macros(7) 参照 */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int name_to_handle_at(int dirfd, const char *pathname,
                      struct file_handle *handle,
                      int *mount_id, int flags);

int open_by_handle_at(int mount_fd, struct file_handle *handle,
                      int flags);

説明
システムコール name_to_handle_at() と open_by_handle_at() は openat(2) の機能を 2 つに分割したものである。 name_to_handle_at() は指定されたファイルに対応するハンドルを返す。 open_by_handle_at() は name_to_handle_at() が返したハンドルに対応するファイルをオープンし、 オープンされたファイルディスクリプターを返す。

  name_to_handle_at()
name_to_handle_at() システムコールは、 引数 dirfd と pathname で指定されるファイルに対応するファイルハンドルとマウント ID を返す。 ファイルハンドルは引数 handle で返される。 handle は以下の形式の構造体へのポインターである。

    struct file_handle {
        unsigned int  handle_bytes;   /* Size of f_handle [in, out] */
        int           handle_type;    /* Handle type [out] */
        unsigned char f_handle[0];    /* File identifier (sized by
                                         caller) [out] */
    };

It is the caller's responsibility to allocate the structure with a size large enough to hold the handle returned in f_handle. Before the call, the handle_bytes field should be initialized to contain the allocated size for f_handle. (The constant MAX_HANDLE_SZ, defined in <fcntl.h>, specifies the maximum expected size for a file handle. It is not a guaranteed upper limit as future filesystems may require more space.) Upon successful return, the handle_bytes field is updated to contain the number of bytes actually written to f_handle.

The caller can discover the required size for the file_handle structure by making a call in which handle->handle_bytes is zero; in this case, the call fails with the error EOVERFLOW and handle->handle_bytes is set to indicate the required size; the caller can then use this information to allocate a structure of the correct size (see EXAMPLES below). Some care is needed here as EOVERFLOW can also indicate that no file handle is available for this particular name in a filesystem which does normally support file-handle lookup. This case can be detected when the EOVERFLOW error is returned without handle_bytes being increased.

handle_bytes フィールドを使用する以外は、 呼び出し元は file_handle 構造体の内容を意識せずに扱うべきである。 フィールド handle_type と f_handle は後で open_by_handle_at() を呼び出す場合にだけ必要である。

flags 引数は、 下記の AT_EMPTY_PATH と AT_SYMLINK_FOLLOW のうち 0 個以上の論理和を取って構成されるビットマスクである。

引数 pathname と dirfd はその組み合わせでハンドルを取得するファイルを指定する。 以下の 4 つのパターンがある。

*   pathname が空でない文字列で絶対パス名を含む場合、 このパス名が参照するファイルに対するハンドルが返される。

*   pathname が相対パスが入った空でない文字列で、 dirfd が特別な値 AT_FDCWD の場合、 pathname は呼び出し元のカレントワーキングディレクトリに対する相対パスと解釈され、 そのファイルに対するハンドルが返される。

*   pathname が相対パスが入った空でない文字列で、 dirfd がディレクトリを参照するファイルディスクリプターの場合、 pathname は dirfd が参照するディレクトリに対する相対パスと解釈され、 そのファイルを参照するハンドルが返される。(なぜ「ディレクトリファイルディスクリプター」が役に立つのかについては openat(2) を参照。)

*   pathname が空の文字列で flags に AT_EMPTY_PATH が指定されている場合、 dirfd には任意の種別のファイルを参照するオープンされたファイルディスクリプターか AT_FDCWD (カレントワーキングディレクトリを意味する) を指定でき、 dirfd が参照するファイルに対するハンドルが返される。

The mount_id argument returns an identifier for the filesystem mount that corresponds to pathname. This corresponds to the first field in one of the records in /proc/self/mountinfo. Opening the pathname in the fifth field of that record yields a file descriptor for the mount point; that file descriptor can be used in a subsequent call to open_by_handle_at(). mount_id is returned both for a successful call and for a call that results in the error EOVERFLOW.

デフォルトでは、 name_to_handle_at() は pathname がシンボリックリンクの場合にその展開 (dereference) を行わず、 リンク自身に対するハンドルを返す。 AT_SYMLINK_FOLLOW が flags に指定されると、 pathname がシンボリックリンクの場合にリンクの展開が行われる (リンクが参照するファイルに対するハンドルが返される)。

name_to_handle_at() does not trigger a mount when the final component of the pathname is an automount point. When a filesystem supports both file handles and automount points, a name_to_handle_at() call on an automount point will return with error EOVERFLOW without having increased handle_bytes. This can happen since Linux 4.13 with NFS when accessing a directory which is on a separate filesystem on the server. In this case, the automount can be triggered by adding a "/" to the end of the pathname.

  open_by_handle_at()
open_by_handle_at() システムコールは handle が参照するファイルをオープンする。 handle は 前に呼び出した name_to_handle_at() が返したファイルハンドルである。

mount_fd 引数は、 handle がそのファイルシステムに関連すると解釈されるマウントされたファイルシステム内の任意のオブジェクト (ファイル、 ディレクトリなど) のファイルディスクリプターである。 特別な値 AT_FDCWD も指定できる。 この値は呼び出し元のカレントワーキングディレクトリを意味する。

引数 flags は open(2) と同じである。 handle がシンボリックリンクを参照している場合、 呼び出し元は O_PATH フラグを指定しなければならず、 そのシンボリックリンクは展開されない。 O_NOFOLLOW が指定された場合は、 O_NOFOLLOW は無視される。

open_by_handle_at() を呼び出すには、 呼び出し元が CAP_DAC_READ_SEARCH ケーパビリティーを持っていなければならない。

返り値
成功すると、 name_to_handle_at() は 0 を返し、 open_by_handle_at() はファイルディスクリプター (非負の整数) を返す。

エラーの場合、 どちらのシステムコールも -1 を返し、 errno にエラーの原因を示す値を設定する。

エラー
name_to_handle_at() と open_by_handle_at() は openat(2) と同じエラーで失敗する。 また、 これらのシステムコールは以下のエラーで失敗することもある。

name_to_handle_at() は以下のエラーで失敗することがある。

EFAULT
    pathname, mount_id, handle のどれかがアクセス可能なアドレス空間の外を指している。

EINVAL
    flags に無効なビット値が含まれている。

EINVAL
    handle->handle_bytes が MAX_HANDLE_SZ よりも大きい。

ENOENT
    pathname が空文字列だが、 flags に AT_EMPTY_PATH がされていなかった。

ENOTDIR
    dirfd で指定されたファイルディスクリプターがディレクトリを参照しておらず、 両方の flags に AT_EMPTY_PATH が指定され、 かつ pathname が空文字列である場合でもない。

EOPNOTSUPP
    ファイルシステムがパス名をファイルハンドルへの変換をサポートしていない。

EOVERFLOW
    呼び出しに渡された handle->handle_bytes の値が小さすぎた。 このエラーが発生した際、 handle->handle_bytes はハンドルに必要なサイズに更新される。

open_by_handle_at() は以下のエラーで失敗することがある。

EBADF
    mount_fd がオープンされたファイルディスクリプターでない。

EFAULT
    handle がアクセス可能なアドレス空間の外を指している。

EINVAL
    handle->handle_bytes が MAX_HANDLE_SZ より大きいか 0 に等しい。

ELOOP
    handle がシンボリックリンクを参照しているが、 flags に O_PATH がされていなかった。

EPERM
    呼び出し元が CAP_DAC_READ_SEARCH ケーパビリティを持っていない。

ESTALE
    指定された handle が有効ではない。 このエラーは、 例えばファイルが削除された場合などに発生する。

バージョン
これらのシステムコールは Linux 2.6.39 で初めて登場した。ライブラリによるサポートはバージョン 2.14 以降の glibc で提供されている。

準拠
これらのシステムコールは非標準の Linux の拡張である。

FreeBSD には getfh() と openfh() というほとんど同じ機能のシステムコールのペアが存在する。

注意
あるプロセスで name_to_handle_at() を使ってファイルハンドルを生成して、 そのハンドルを別のプロセスの open_by_handle_at() で使用することができる。

いくつかのファイルシステムでは、 パス名からファイルハンドルへの変換がサポートされていない。 例えば、 /proc, /sys や種々のネットワークファイルシステムなどである。

ファイルハンドルは、 ファイルが削除されたり、 その他のファイルシステム固有の理由で、 無効 ("stale") になる場合がある。 無効なハンドルであることは、 open_by_handle_at() からエラー ESTALE が返ることで通知される。

これらのシステムコールは、 ユーザー空間のファイルサーバーでの使用を意図して設計されている。 例えば、 ユーザー空間 NFS サーバーがファイルハンドルを生成して、 そのハンドルを NFS クライアントに渡すことができる。 その後、 クライアントがファイルをオープンしようとした際に、 このハンドルをサーバーに送り返すことができる。 このような機能により、 ユーザー空間ファイルサーバーは、 そのサーバーが提供するファイルに関してステートレスで (状態を保持せずに) 動作することができる。

pathname がシンボリックリンクを参照していて、 flags に AT_SYMLINK_FOLLOW が指定されていない場合、 name_to_handle_at() は (シンボリックが参照するファイルではなく) リンクに対するハンドルを返す。 ハンドルを受け取ったプロセスは、 open_by_handle_at() の O_PATH フラグを使ってハンドルをファイルディスクリプターに変換し、 そのファイルディスクリプターを readlinkat(2) や fchownat(2) などのシステムコールの dirfd 引数として渡すことで、 そのシンボリックリンクに対して操作を行うことができる。

  永続的なファイルシステム ID の取得
/proc/self/mountinfo のマウント ID は、 ファイルシステムのアンマウント、マウントが行われるに連れて再利用されることがある。 したがって、 name_to_handle_at() (の *mount_id) で返されたマウント ID は対応するマウントされたファイルシステムを表す永続的な ID と考えるべきではない。 ただし、 アプリケーションは、 マウント ID に対応する mountinfo レコードの情報を使うことで、 永続的な ID を得ることができる。

例えば、 mountinfo レコードの 5 番目のフィールドのデバイス名を使って、 /dev/disks/by-uuid のシンボリックリンク経由で対応するデバイス UUID を検索できる。 (UUID を取得するもっと便利な方法は libblkid(3) ライブラリを使用することである。) そのプロセスは、逆に、 この UUID を使ってデバイス名を検索し、 対応するマウントポイントを取得することで、 open_by_handle_at() で使用する mount_fd 引数を生成することができる。

例
以下の 2 つのプログラムは name_to_handle_at() と open_by_handle_at() の使用例を示したものである。 最初のプログラム (t_name_to_handle_at.c) は name_to_handle_at() を使用して、 コマンドライン引数で指定されたファイルに対応するファイルハンドルとマウント ID を取得する。 ハンドルとマウント ID は標準出力に出力される。

2 つ目のプログラム (t_open_by_handle_at.c) は、 標準入力からマウント ID とファイルハンドルを読み込む。 それから、 open_by_handle_at() を利用して、 そのハンドルを使ってファイルをオープンする。 追加のコマンドライン引数が指定された場合は、 open_by_handle_at() の mount_fd 引数は、 この引数で渡された名前のディレクトリをオープンして取得する。 それ以外の場合、 /proc/self/mountinfo からスキャンして標準入力から読み込んだマウント ID に一致するマウント ID を検索し、 そのレコードで指定されているマウントディレクトリをオープンして、 mount_fd を入手する。 (これらのプログラムではマウント ID が永続的ではない点についての対処は行わない。)

以下のシェルセッションは、これら 2 つのプログラムの使用例である。

    $ echo 'Can you please think about it?' > cecilia.txt
    $ ./t_name_to_handle_at cecilia.txt > fh
    $ ./t_open_by_handle_at < fh
    open_by_handle_at: Operation not permitted
    $ sudo ./t_open_by_handle_at < fh      # Need CAP_SYS_ADMIN
    Read 31 bytes
    $ rm cecilia.txt

ここで、 ファイルを削除し (すぐに) 再作成する。 同じ内容で (運がよければ) 同じ inode になる。 この場合でも、 open_by_handle_at() はこのファイルハンドルが参照する元のファイルがすでに存在しないことを認識する。

    $ stat --printf="%i\n" cecilia.txt     # Display inode number
    4072121
    $ rm cecilia.txt
    $ echo 'Can you please think about it?' > cecilia.txt
    $ stat --printf="%i\n" cecilia.txt     # Check inode number
    4072121
    $ sudo ./t_open_by_handle_at < fh
    open_by_handle_at: Stale NFS file handle

  プログラムのソース: t_name_to_handle_at.c
    #define _GNU_SOURCE
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <errno.h>
    #include <string.h>
    #define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
                            } while (0)
    int
    main(int argc, char *argv[])
    {
        struct file_handle *fhp;
        int mount_id, fhsize, flags, dirfd;
        char *pathname;
        if (argc != 2) {
            fprintf(stderr, "Usage: %s pathname\n", argv[0]);
            exit(EXIT_FAILURE);
        }
        pathname = argv[1];
        /* file_handle 構造体を確保する */
        fhsize = sizeof(*fhp);
        fhp = malloc(fhsize);
        if (fhp == NULL)
            errExit("malloc");
        /* name_to_handle_at() を最初に呼び出して
           ファイルハンドルに必要なサイズを入手する */
        dirfd = AT_FDCWD;           /* For name_to_handle_at() calls */
        flags = 0;                  /* For name_to_handle_at() calls */
        fhp->handle_bytes = 0;
        if (name_to_handle_at(dirfd, pathname, fhp,
                    &mount_id, flags) != -1 || errno != EOVERFLOW) {
            fprintf(stderr, "Unexpected result from name_to_handle_at()\n");
            exit(EXIT_FAILURE);
        }
        /* file_handle 構造体を正しいサイズに確保し直す */
        fhsize = sizeof(*fhp) + fhp->handle_bytes;
        fhp = realloc(fhp, fhsize);         /* Copies fhp->handle_bytes */
        if (fhp == NULL)
            errExit("realloc");
        /* コマンドラインで指定されたパス名からファイルハンドルを取得 */
        if (name_to_handle_at(dirfd, pathname, fhp, &mount_id, flags) == -1)
            errExit("name_to_handle_at");
        /* t_open_by_handle_at.c で後で再利用できるように、マウント ID、
           ファイルハンドルのサイズ、ファイルハンドルを標準出力に書き出す */
        printf("%d\n", mount_id);
        printf("%u %d   ", fhp->handle_bytes, fhp->handle_type);
        for (int j = 0; j < fhp->handle_bytes; j++)
            printf(" %02x", fhp->f_handle[j]);
        printf("\n");
        exit(EXIT_SUCCESS);
    }

  プログラムのソース: t_open_by_handle_at.c
    #define _GNU_SOURCE
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <limits.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
                            } while (0)
    /* /proc/self/mountinfo をスキャンして、マウント ID が 'mount_id' に
       一致する行を探す。 (もっと簡単な方法は 'util-linux' プロジェクト
       が提供する 'libmount' ライブラリをインストールして使うことである)
       対応するマウントパスをオープンし、得られたファイルディスクリプターを返す。 */
    static int
    open_mount_path_by_id(int mount_id)
    {
        char *linep;
        size_t lsize;
        char mount_path[PATH_MAX];
        int mi_mount_id, found;
        ssize_t nread;
        FILE *fp;
        fp = fopen("/proc/self/mountinfo", "r");
        if (fp == NULL)
            errExit("fopen");
        found = 0;
        linep = NULL;
        while (!found) {
            nread = getline(&linep, &lsize, fp);
            if (nread == -1)
                break;
            nread = sscanf(linep, "%d %*d %*s %*s %s",
                           &mi_mount_id, mount_path);
            if (nread != 2) {
                fprintf(stderr, "Bad sscanf()\n");
                exit(EXIT_FAILURE);
            }
            if (mi_mount_id == mount_id)
                found = 1;
        }
        free(linep);
        fclose(fp);
        if (!found) {
            fprintf(stderr, "Could not find mount point\n");
            exit(EXIT_FAILURE);
        }
        return open(mount_path, O_RDONLY);
    }
    int
    main(int argc, char *argv[])
    {
        struct file_handle *fhp;
        int mount_id, fd, mount_fd, handle_bytes;
        ssize_t nread;
        char buf[1000];
    #define LINE_SIZE 100
        char line1[LINE_SIZE], line2[LINE_SIZE];
        char *nextp;
        if ((argc > 1 && strcmp(argv[1], "--help") == 0) || argc > 2) {
            fprintf(stderr, "Usage: %s [mount-path]\n", argv[0]);
            exit(EXIT_FAILURE);
        }
        /* マウント ID とファイルハンドル情報が入った標準入力:
             Line 1: <mount_id>
             Line 2: <handle_bytes> <handle_type>   <bytes of handle in hex>
        */
        if ((fgets(line1, sizeof(line1), stdin) == NULL) ||
               (fgets(line2, sizeof(line2), stdin) == NULL)) {
            fprintf(stderr, "Missing mount_id / file handle\n");
            exit(EXIT_FAILURE);
        }
        mount_id = atoi(line1);
        handle_bytes = strtoul(line2, &nextp, 0);
        /* handle_bytes があれば、
           file_handle 構造体をここで割り当てできる */
        fhp = malloc(sizeof(*fhp) + handle_bytes);
        if (fhp == NULL)
            errExit("malloc");
        fhp->handle_bytes = handle_bytes;
        fhp->handle_type = strtoul(nextp, &nextp, 0);
        for (int j = 0; j < fhp->handle_bytes; j++)
            fhp->f_handle[j] = strtoul(nextp, &nextp, 16);
        /* マウントポイントのファイルディスクリプターを取得する。
           取得は、コマンドラインで指定されたパス名をオープンするか、
           /proc/self/mounts をスキャンして標準入力から受け取った
           'mount_id' に一致するマウントを探すことで行う。 */
        if (argc > 1)
            mount_fd = open(argv[1], O_RDONLY);
        else
            mount_fd = open_mount_path_by_id(mount_id);
        if (mount_fd == -1)
            errExit("opening mount fd");
        /* ハンドルとマウントポイントを使ってファイルをオープンする */
        fd = open_by_handle_at(mount_fd, fhp, O_RDONLY);
        if (fd == -1)
            errExit("open_by_handle_at");
        /* そのファイルからバイトを読み出す */
        nread = read(fd, buf, sizeof(buf));
        if (nread == -1)
            errExit("read");
        printf("Read %zd bytes\n", nread);
        exit(EXIT_SUCCESS);
    }

関連項目
open(2), libblkid(3), blkid(8), findfs(8), mount(8)

https://www.kernel.org/pub/linux/utils/util-linux/ で入手できる最新の util-linux リリースの libblkid と libmount のドキュメント。

この文書について
この man ページは Linux man-pages プロジェクトのリリース 5.10 の一部である。プロジェクトの説明とバグ報告に関する情報は https://www.kernel.org/doc/man-pages/ に書かれている。