Provided by: manpages-ru-dev_4.27.0-1_all bug

НАИМЕНОВАНИЕ

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

БИБЛИОТЕКА

       Стандартная библиотека языка C (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.

       Other  than  the  use  of the handle_bytes field, the caller should treat the file_handle structure as an
       opaque  data  type:  the  handle_type  and  f_handle  fields  can  be  used  in  a  subsequent  call   to
       open_by_handle_at().   The  caller  can  also  use  the  opaque  file_handle  to  compare the identity of
       filesystem objects that were queried at different times and possibly at different paths.  The fanotify(7)
       subsystem can report events  with  an  information  record  containing  a  file_handle  to  identify  the
       filesystem object.

       The  flags  argument  is  a  bit  mask  constructed  by  ORing  together  zero  or more of AT_HANDLE_FID,
       AT_EMPTY_PATH, and AT_SYMLINK_FOLLOW, described below.

       When flags contain the AT_HANDLE_FID (since Linux 6.5)  flag, the  caller  indicates  that  the  returned
       file_handle is needed to identify the filesystem object, and not for opening the file later, so it should
       be expected that a subsequent call to open_by_handle_at()  with the returned file_handle may fail.

       Аргументы  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 The  specified handle is not valid for opening a file.  This error will occur if, for example, the
              file has been deleted.   This  error  can  also  occur  if  the  handle  was  acquired  using  the
              AT_HANDLE_FID flag and the filesystem does not support open_by_handle_at().

ВЕРСИИ

       FreeBSD has a broadly similar pair of system calls in the form of getfh() and fhopen().

СТАНДАРТЫ

       Linux.

ИСТОРИЯ

       Linux 2.6.39, glibc 2.14.

ПРИМЕЧАНИЯ

       Файловый описатель может быть сгенерирован с помощью name_to_handle_at() в одном процессе и использован в
       вызовах open_by_handle_at() в другом.

       Some filesystem don't support the translation of pathnames to file handles, for example, /proc, /sys, and
       various  network filesystems.  Some filesystems support the translation of pathnames to file handles, but
       do not support using those file handles in open_by_handle_at().

       Файловый описатель может стать некорректным («просроченным»), если файл удалён, или по  другим  причинам,
       относящимся к файловой системе. Для некорректных описателей 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 <sys/types.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> и Kirill Rekhov <krekhov.dev@gmail.com>

       Этот перевод является свободной программной документацией; он распространяется на условиях  общедоступной
       лицензии  GNU  (GNU  General Public License - GPL, https://www.gnu.org/licenses/gpl-3.0.html версии 3 или
       более поздней) в отношении авторского права, но БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ.

       Если вы обнаружите какие-либо ошибки в переводе этой страницы руководства, пожалуйста, сообщите  об  этом
       разработчику(ам)   по   его(их)  адресу(ам)  электронной  почты  или  по  адресу  списка рассылки русских
       переводчиков.

Справочные страницы Linux 6.9.1                  15 июня 2024 г.                            open_by_handle_at(2)