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] */
};

f_handle で返されるハンドルを保持するのに十分な大きさの構造体を確保するのは、 呼び出し元が責任をもって行う必要がある。 呼び出し前に、 handle_bytes フィールドは f_handle 用に格納されたサイズで初期化すべきである (<fcntl.h> で定義されている定数 MAX_HANDLE_SZ でファイルハンドルの最大サイズが規定されている)。 呼び出しが成功でリターンする際、 handle_bytes フィールドは f_handle に実際に書き込まれたバイト数に更新される。 呼び出し元では、 handle->handle_bytes を 0 に設定して呼び出しを行うことで、 file_handle 構造体に必要なサイズを知ることができる。 この場合、 この呼び出しはエラー EOVERFLOW で失敗し、 handle->handle_bytes に必要なサイズが設定される。 呼び出し元はこの情報を使って、正しいサイズの構造体を割り当てることができる (下記の「例」を参照)。 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 が参照するファイルに対するハンドルが返される。

mount_id 引き数は、 pathname に対応するファイルシステムのマウントの識別子を返す。 この識別子は /proc/self/mountinfo のいずれかのレコードの最初のフィールドに対応する。 対応するレコードの 5 番目のフィールドのパス名をオープンすると、 このマウントポイントのファイルディスクリプターが得られる。 このファイルディスクリプターはこの後の open_by_handle_at() の呼び出しで使用できる。 デフォルトでは、 name_to_handle_at() は pathname がシンボリックリンクの場合にその展開 (dereference) を行わず、 リンク自身に対するハンドルを返す。 AT_SYMLINK_FOLLOW が flags に指定されると、 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, j;
    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(struct file_handle) + 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("%d %d   ", fhp->handle_bytes, fhp->handle_type);
    for (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, j;
    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(struct file_handle) + handle_bytes);
    if (fhp == NULL)
        errExit("malloc");
    fhp->handle_bytes = handle_bytes;
    fhp->handle_type = strtoul(nextp, &nextp, 0);
    for (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 プロジェクトのリリース 3.79 の一部 である。プロジェクトの説明とバグ報告に関する情報は http://www.kernel.org/doc/man-pages/ に書かれている。