open_by_handle_at(2)

ИМЯ
name_to_handle_at, open_by_handle_at - получение описателя для пути и открытие файла через описатель

LIBRARY
Standard C library (libc, -lc)

СИНТАКСИС
#define _GNU_SOURCE         /* см. feature_test_macros(7) */
#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);

ОПИСАНИЕ
Функциональное назначение openat(2) было разделено на две части и добавлено в системные вызовы name_to_handle_at() и open_by_handle_at(): name_to_handle_at() возвращает описатель с произвольной формой(opaque), который соответствует указанному файлу; open_by_handle_at() открывает файл по описателю, который был возвращён предыдущим вызовом name_to_handle_at() и возвращает дескриптор открытого файла.

  name_to_handle_at()
Системный вызов name_to_handle_at() возвращает файловый описатель и идентификатор монтирования для файла, указанного в аргументах dirfd и pathname. Файловый описатель возвращается через аргумент handle, который является указателем на следующую структуру:

    struct file_handle {
        unsigned int  handle_bytes;   /* размер f_handle [in, out] */
        int           handle_type;    /* тип описателя [out] */
        unsigned char f_handle[0];    /* идентификатор файла (размер
                                         задаёт вызывающий) [out] */
    };

Вызывающий должен выделить память достаточного размера под структуру описателя, возвращаемого в f_handle. Перед вызовом поле handle_bytes должно содержать размер выделенной памяти для f_handle (константа MAX_HANDLE_SZ, определённая в <fcntl.h>, равна максимально ожидаемому размеру описателя файла. Это не гарантированное верхнее ограничение, так как файловые системы в будущем могут потребовать больше места). При успешном выполнении поле handle_bytes обновляется и содержит количество байт действительно занятых под f_handle.

Вызывающий может определить требуемый размер структуры file_handle указав при вызове значение handle->handle_bytes равное нулю; в этому случае вызов завершается с ошибкой EOVERFLOW, а в поле handle->handle_bytes записывается требуемый размер; затем вызывающий может использовать эту информацию для выделения памяти под структуру правильного размера (смотрите ПРИМЕРЫ ниже). Здесь нужно учесть, что EOVERFLOW может указывать на то, что файловый описатель недоступен для этого заданного имени в файловой системе, которая, обычно, поддерживает поиск файловых описателей. На данный случай указывает то, что ошибка EOVERFLOW возвращается без увеличившегося значения handle_bytes.

Все остальные поля, кроме handle_bytes структуры file_handle, вызывающий должен считать неизвестными: поля handle_type и f_handle требуются только в последующих вызовах open_by_handle_at().

Аргумент flags представляет собой битовую маску (OR) из комбинации нуля или более флагов AT_EMPTY_PATH и AT_SYMLINK_FOLLOW, описанных ниже.

Аргументы pathname и dirfd вместе задают файл, для которого будет получен описатель. Есть четыре различных варианта:

•   Если значение pathname — непустая строка, содержащая абсолютный путь, то описатель возвращается для файла, на который указывает путь. В этом случае dirfd игнорируется.

•   Если значение pathname — непустая строка, содержащая относительный путь и dirfd равно специальному значению AT_FDCWD, то pathname рассматривается относительно текущего рабочего каталога вызывающего и описатель возвращается для файла, на который он указывает.

•   Если значение pathname — непустая строка, содержащая относительный путь и dirfd равно файловому дескриптору, указывающему на каталог, то pathname рассматривается относительно каталога, на который указывает dirfd, и описатель возвращается для файла, на который он указывает (смотрите в openat(2) объяснение полезности «файловых дескрипторов каталогов»).

•   Если значение pathname — пустая строка и значение flags равно AT_EMPTY_PATH, то dirfd может быть открытым файловым дескриптором, указывающим на файл любого типа, или AT_FDCWD, означающим текущий рабочий каталог, и описатель возвращается для файла, на который он указывает.

В аргументе mount_id возвращается идентификатор точки монтирования в файловой системе, соответствующий pathname. Это значение соответствует первому полю одной из записей в /proc/self/mountinfo. Открытие пути из пятого поля этой записи возвращает файловый дескриптор этой точки монтирования; этот файловый дескриптор можно использовать в последующем вызове open_by_handle_at(). Аргумент mount_id возвращается при успешном выполнении, а также при ошибке EOVERFLOW().

По умолчанию, name_to_handle_at() не разыменовывает pathname, если это символическая ссылка, и поэтому возвращается описатель самой ссылки. Если в flags указан AT_SYMLINK_FOLLOW, то pathname разыменовывается, если это символическая ссылка (то есть вызов возвращает описатель файла, на который указывает ссылка).

Системный вызов name_to_handle_at() не вызывает монтирования, если конечная часть пути является автоматической точкой монтирования. Если файловая система поддерживает файловые описатели и автоматические точки монтирования, то вызов name_to_handle_at() для автоматической точки монтирования завершится ошибкой EOVERFLOW без увеличения значения handle_bytes. Это может происходить с NFS начиная с версии Linux 4.13, когда задействованный каталог находится в отдельной файловой системе на сервере. В этом случае автомонтирование можно получить добавлением «/» в конец пути.

  open_by_handle_at()
Системный вызов open_by_handle_at() открывает файл, на который указывает handle, файловый описатель, полученный от предшествующего вызова name_to_handle_at().

Аргумент mount_fd — это файловый дескриптор любого объекта (файла, каталога и т. д.) в смонтированной файловой системе, в которой должен находиться handle. Может быть равен специальному значению AT_FDCWD, которое обозначает текущий рабочий каталог вызывающего.

Значение аргумента flags как у open(2). Если handle указывает на символическую ссылку, то вызывающий должен указать флаг O_PATH, и символическая ссылка не разыменовывается; флаг O_NOFOLLOW игнорируется.

Для вызова open_by_handle_at() вызывающий должен иметь мандат CAP_DAC_READ_SEARCH.

ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ
On success, name_to_handle_at() returns 0, and open_by_handle_at() returns a file descriptor (a nonnegative integer).

In the event of an error, both system calls return -1 and set errno to indicate the error.

ОШИБКИ
Вызовы 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 не является открытым файловым дескриптором.

EBADF
    В pathname содержится относительный путь, но значение dirfd не равно AT_FDCWD и не является правильным файловым дескриптором.

EFAULT
    Значение handle указывает за пределы доступного адресного пространства.

EINVAL
    Значение handle->handle_bytes больше MAX_HANDLE_SZ или равно нулю.

ELOOP
    Значение handle указывает на символическую ссылку, но в flags не указан O_PATH.

EPERM
    Вызывающий не имеет мандата CAP_DAC_READ_SEARCH.

ESTALE
    Значение handle некорректно. Эта ошибка возникает, например, из-за удаления файла.

ВЕРСИИ
В FreeBSD есть относительно похожая пара системных вызовов getfh() и openfh().

СТАНДАРТЫ
Linux.

ИСТОРИЯ
Linux 2.6.39, glibc 2.14.

ЗАМЕЧАНИЯ
Файловый описатель может быть сгенерирован с помощью name_to_handle_at() в одном процессе и использован в вызовах open_by_handle_at() в другом.

Некоторые файловые системы не поддерживают трансляцию путей в файловые описатели, например: /proc, /sys и различные сетевые файловые системы.

Файловый описатель может стать некорректным («просроченным»), если файл удалён, или по другим причинам, относящимся к файловой системе. Для некорректных описателей open_by_handle_at() возвращает ошибку ESTALE.

Данные системные вызовы предназначены для использования в файловых серверах пространства пользователя. Например, сервер пользовательского пространства NFS может генерировать файловый описатель и передавать его клиенту NFS. Позднее, когда клиент захочет открыть файл, он может передать описатель обратно серверу. Такого рода возможность позволяет файловому серверу пространства пользователя работать без формирования состояния (stateless fashion) для файлов, которые они обслуживают.

Если pathname указывает на символическую ссылку и в flags отсутствует AT_SYMLINK_FOLLOW, то name_to_handle_at() возвращает описатель ссылки (а не файла, на который она ссылается). Процесс, получивший описатель, может позднее выполнить операции над символической ссылкой, преобразовав описатель в файловый дескриптор, используя open_by_handle_at() с флагом O_PATH, и затем передав файловый дескриптор через аргумент dirfd в системные вызовы readlinkat(2) и fchownat(2).

  Получение постоянного идентификатора файловой системы
Идентификаторы монтирования в /proc/self/mountinfo могут быть использованы повторно после размонтирования и монтирования файловой системы. Поэтому, идентификатор монтирования, возвращаемый name_to_handle_at() (в *mount_id), не должен считаться постоянным идентификатором соответствующей файловой системы. Однако, приложение может использовать информацию в записи mountinfo, которая соответствует идентификатору монтирования, для получения постоянного идентификатора.

Например, можно использовать имя устройства в пятом поле записи mountinfo для поиска соответствующего устройству UUID через символические ссылки в /dev/disks/by-uuid (более удобный способ получения UUID — использовать библиотеку libblkid(3)). Этот процесс может быть и обратным — используя UUID найти имя устройства, и затем получить соответствующую точку монтирования, чтобы создать аргумент mount_fd, используемый для open_by_handle_at().

ПРИМЕРЫ
Две представленные далее программы демонстрируют использование name_to_handle_at() и open_by_handle_at(). Первая программа (t_name_to_handle_at.c) использует name_to_handle_at() для получения файлового описателя и идентификатора монтирования для файла, указанного в аргументе командной строки; описатель и идентификатор монтирования записываются в стандартный вывод.

Вторая программа (t_open_by_handle_at.c) читает идентификатор монтирования и файловый описатель из стандартного ввода. Затем программа, используя описатель, применяет open_by_handle_at() для открытия файла. Если указан необязательный параметр командной строки, то аргумент mount_fd для open_by_handle_at() создаётся из открытия каталога, указанного в аргументе. В противном случае mount_fd заполняется результатом сканированием /proc/self/mountinfo в целях найти запись, чей идентификатор монтирования совпадает с идентификатором монтирования из стандартного ввода, и открывается каталог монтирования, указанный в этой записи (эти программы не учитывают, что идентификатор монтирования не постоянен).

Следующий сеанс работы в оболочке показывает использование этих программ:

    $ 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

Теперь мы удаляем и (быстро) пересоздаём файл с тем же содержимым и (если повезёт) с той же инодой. Не смотря на это, 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 <err.h>
    #include <errno.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <stdlib.h>

    int
    main(int argc, char *argv[])
    {
        int                 mount_id, fhsize, flags, dirfd;
        char                *pathname;
        struct file_handle  *fhp;

        if (argc != 2) {
            fprintf(stderr, "Usage: %s pathname\n", argv[0]);
            exit(EXIT_FAILURE);
        }

        pathname = argv[1];

        /* Allocate file_handle structure. */

        fhsize = sizeof(*fhp);
        fhp = malloc(fhsize);
        if (fhp == NULL)
            err(EXIT_FAILURE, "malloc");

        /* Make an initial call to name_to_handle_at() to discover
           the size required for file handle. */

        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);
        }

        /* Reallocate file_handle structure with correct size. */

        fhsize = sizeof(*fhp) + fhp->handle_bytes;
        fhp = realloc(fhp, fhsize);         /* Copies fhp->handle_bytes */
        if (fhp == NULL)
            err(EXIT_FAILURE, "realloc");

        /* Get file handle from pathname supplied on command line. */

        if (name_to_handle_at(dirfd, pathname, fhp, &mount_id, flags) == -1)
            err(EXIT_FAILURE, "name_to_handle_at");

        /* Write mount ID, file handle size, and file handle to stdout,
           for later reuse by t_open_by_handle_at.c. */

        printf("%d\n", mount_id);
        printf("%u %d   ", fhp->handle_bytes, fhp->handle_type);
        for (size_t 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 <err.h>
    #include <fcntl.h>
    #include <limits.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>

    /* Scan /proc/self/mountinfo to find the line whose mount ID matches
       'mount_id'. (An easier way to do this is to install and use the
       'libmount' library provided by the 'util-linux' project.)
       Open the corresponding mount path and return the resulting file
       descriptor. */

    static int
    open_mount_path_by_id(int mount_id)
    {
        int      mi_mount_id, found;
        char     mount_path[PATH_MAX];
        char     *linep;
        FILE     *fp;
        size_t   lsize;
        ssize_t  nread;

        fp = fopen("/proc/self/mountinfo", "r");
        if (fp == NULL)
            err(EXIT_FAILURE, "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[])
    {
        int                 mount_id, fd, mount_fd, handle_bytes;
        char                buf[1000];
    #define LINE_SIZE 100
        char                line1[LINE_SIZE], line2[LINE_SIZE];
        char                *nextp;
        ssize_t             nread;
        struct file_handle  *fhp;

        if ((argc > 1 && strcmp(argv[1], "--help") == 0) || argc > 2) {
            fprintf(stderr, "Usage: %s [mount-path]\n", argv[0]);
            exit(EXIT_FAILURE);
        }

        /* Standard input contains mount ID and file handle information:

             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);

        /* Given handle_bytes, we can now allocate file_handle structure. */

        fhp = malloc(sizeof(*fhp) + handle_bytes);
        if (fhp == NULL)
            err(EXIT_FAILURE, "malloc");

        fhp->handle_bytes = handle_bytes;

        fhp->handle_type = strtoul(nextp, &nextp, 0);

        for (size_t j = 0; j < fhp->handle_bytes; j++)
            fhp->f_handle[j] = strtoul(nextp, &nextp, 16);

        /* Obtain file descriptor for mount point, either by opening
           the pathname specified on the command line, or by scanning
           /proc/self/mounts to find a mount that matches the 'mount_id'
           that we received from stdin. */

        if (argc > 1)
            mount_fd = open(argv[1], O_RDONLY);
        else
            mount_fd = open_mount_path_by_id(mount_id);

        if (mount_fd == -1)
            err(EXIT_FAILURE, "opening mount fd");

        /* Open file using handle and mount point. */

        fd = open_by_handle_at(mount_fd, fhp, O_RDONLY);
        if (fd == -1)
            err(EXIT_FAILURE, "open_by_handle_at");

        /* Try reading a few bytes from the file. */

        nread = read(fd, buf, sizeof(buf));
        if (nread == -1)
            err(EXIT_FAILURE, "read");

        printf("Read %zd bytes\n", nread);

        exit(EXIT_SUCCESS);
    }

СМ. ТАКЖЕ
open(2), libblkid(3), blkid(8), findfs(8), mount(8)

Документация libblkid и libmount в последнем выпуске util-linux https://www.kernel.org/pub/linux/utils/util-linux/

ПЕРЕВОД
Русский перевод этой страницы руководства был сделан Azamat Hackimov <azamat.hackimov@gmail.com>, Konstantin Shvaykovskiy <kot.shv@gmail.com>, Yuri Kozlov <yuray@komyakino.ru> и Иван Павлов <pavia00@gmail.com>

Этот перевод является бесплатной документацией; прочитайте Стандартную общественную лицензию GNU версии 3 (https://www.gnu.org/licenses/gpl-3.0.html) или более позднюю, чтобы узнать об условиях авторского права. Мы не несем НИКАКОЙ ОТВЕТСТВЕННОСТИ.

Если вы обнаружите ошибки в переводе этой страницы руководства, пожалуйста, отправьте электронное письмо на man-pages-ru-talks@lists.sourceforge.net.