#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);
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 つのパターンがある。
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.
mount_fd 引数は、 handle がそのファイルシステムに関連すると解釈されるマウントされたファイルシステム内の任意のオブジェクト (ファイル、 ディレクトリなど) のファイルディスクリプターである。 特別な値 AT_FDCWD も指定できる。 この値は呼び出し元のカレントワーキングディレクトリを意味する。
引数 flags は open(2) と同じである。 handle がシンボリックリンクを参照している場合、 呼び出し元は O_PATH フラグを指定しなければならず、 そのシンボリックリンクは展開されない。 O_NOFOLLOW が指定された場合は、 O_NOFOLLOW は無視される。
open_by_handle_at() を呼び出すには、 呼び出し元が CAP_DAC_READ_SEARCH ケーパビリティーを持っていなければならない。
エラーの場合、 どちらのシステムコールも -1 を返し、 errno にエラーの原因を示す値を設定する。
name_to_handle_at() は以下のエラーで失敗することがある。
open_by_handle_at() は以下のエラーで失敗することがある。
FreeBSD には getfh() と openfh() というほとんど同じ機能のシステムコールのペアが存在する。
いくつかのファイルシステムでは、 パス名からファイルハンドルへの変換がサポートされていない。 例えば、 /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 引数として渡すことで、 そのシンボリックリンクに対して操作を行うことができる。
例えば、 mountinfo レコードの 5 番目のフィールドのデバイス名を使って、 /dev/disks/by-uuid のシンボリックリンク経由で対応するデバイス UUID を検索できる。 (UUID を取得するもっと便利な方法は libblkid(3) ライブラリを使用することである。) そのプロセスは、逆に、 この UUID を使ってデバイス名を検索し、 対応するマウントポイントを取得することで、 open_by_handle_at() で使用する mount_fd 引数を生成することができる。
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
#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");
#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);
で入手できる最新の util-linux リリースの libblkid と libmount のドキュメント。