Provided by: manpages-ru-dev_4.21.0-2_all bug

ИМЯ

       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 некорректно. Эта ошибка возникает, например, из-за удаления файла.

ВЕРСИИ

       These system calls first appeared in Linux 2.6.39.   Library  support  is  provided  since
       glibc 2.14.

СТАНДАРТЫ

       Данные системные вызовы являются нестандартными расширениями Linux.

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

ЗАМЕЧАНИЯ

       Файловый  описатель может быть сгенерирован с помощью 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, "Использование: %s путь\n", argv[0]);
               exit(EXIT_FAILURE);
           }

           pathname = argv[1];

           /* выделяем место под структуру file_handle */

           fhsize = sizeof(*fhp);
           fhp = malloc(fhsize);
           if (fhp == NULL)
               err(EXIT_FAILURE, "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)
               err(EXIT_FAILURE, "realloc");

           /* получаем файловый описатель из пути, который
              указан в командной строке */

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

           /* пишем идентификатор монтирования, размер файлового описателя
              и файловый описатель в stdout
              для повторного использования в 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, "Использование: %s [путь-монт.]\n", argv[0]);
               exit(EXIT_FAILURE);
           }

           /* Стандартный ввод содержит идентификатор монтирования и
              информацию о файловом описателе:

                Строка 1: <идентификатор монтирования>
                Строка 2: <handle_bytes> <handle_type>
                           <байты описателя в шестнад. системе счисления>
           */

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

           /* открываем файл, используя описатель и точку монтирования */

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

           /* пытаемся прочитать несколько байт из файла */

           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⟩.