Вопрос или проблема
У меня есть пять устройств ttyACM, которые меняют порядок при загрузке. Я также запускаю (плохо написанную) программу, работающую на mono, которая принимает только устройства формата ttyACM# (где # — это число в порядке возрастания, начиная с 0, на основе устройств ttyACM в системе).
Я уже создал правило, подобное этому:
SUBSYSTEM=="usb", ATTRS{idProduct}=="0200", ATTRS{idVendor}=="0658", SYMLINK+="ttyACMradio"
которое создает символическую ссылку с именем, не принимаемым приложением (из-за несоответствия шаблона имени). Я также пробовал называть символическую ссылку ttyACM9, но приложение ее не приняло (из-за того, что номера ttyACM не последовательны).
Можно ли принудительно задать ИМЯ устройства (не символическую ссылку), чтобы обеспечить последовательный порядок устройств ttyACM? Как это сделать? Единственные упоминания об ИМЯ, которые я могу найти в документации udev, относятся к сетевым устройствам.
Я уверен, что кто-то скажет исправить приложение, но оно не мое, и у поставщика нет интереса помогать пользователям Linux. Я также могу добавлять/удалять устройства ACM в будущем, поэтому не хочу жестко задать номера ACM, начиная сразу после последнего реального. Между прочим, использую AlmaLinux 9
Вы не можете изменить имена устройств /dev/ttyACM0
. Одно из решений, если ваш бинарный файл это позволяет, — это использовать вашу собственную Си-функцию, которая будет вызвана вместо настоящей open()
в библиотеке C. Ваша функция может проверить, является ли открываемый файл “/dev/ttyACM0”, и, если да, вызвать реальную функцию open с именем символической ссылки. Вот такой интерпозер, называемый shim_open.c
:
/*
* https://unix.stackexchange.com/q/789103/119298
* перехват вызовов функции и замена их на ваш код
* gcc -Wall -O2 -fpic -shared -ldl -o shim_open.so shim_open.c
* LD_PRELOAD=/.../shim_open.so ./test
*/
#define _FCNTL_H 1 /* хак для прототипа open() */
#define _GNU_SOURCE /* нужно для определения RTLD_NEXT в dlfcn.h */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <dlfcn.h>
/* заставить open(f1,) выполнить open(f2,0) используя имена из env $OLDNAME $NEWNAME */
int open(const char *pathname, int flags, mode_t mode){
static int (*real_open)(const char *pathname, int flags, mode_t mode) = NULL;
static char *oldname, *newname;
if (!real_open) {
real_open = dlsym(RTLD_NEXT, "open");
char *error = dlerror();
if (error != NULL) {
fprintf(stderr, "%s\n", error);
exit(1);
}
oldname = getenv("OLDNAME");
newname = getenv("NEWNAME");
if(oldname==0 || newname == 0){
fprintf(stderr, "нужно задать в env OLDNAME NEWNAME\n");
exit(2);
}
}
if (strcmp(pathname,oldname)==0) pathname = newname;
fprintf(stderr, "открытие: %s\n", pathname);
return real_open(pathname, flags, mode);
}
Скомпилируйте ее с gcc -Wall -O2 -fpic -shared -ldl -o shim_open.so shim_open.c
и используйте с
LD_PRELOAD=/.../shim_open.so OLDNAME=/dev/ttyACM0 NEWNAME=/dev/ttyACMradio ./myprogram
Обратите внимание, однако, что в наши дни существует несколько версий open(2)
,
поэтому, если это не сработает, возможно, вам придется запустить strace ./myprogram
, чтобы узнать,
какой именно вариант вызывается. Также, программа может fstat()
файл
дескриптор, чтобы убедиться, что он является устройством узлом и так далее, так что это может быть
лишь первый шаг.
На странице руководства udev(7)
о присвоении значений ключу NAME=
говорится следующее (выделено мной):
NAME
Имя, которое нужно использовать для сетевого интерфейса. См. systemd.link(5) для более высокого уровня механизма настройки имени интерфейса. Имя узла устройства не может быть изменено udev, можно только создавать дополнительные символические ссылки.
Старые версии udev
позволяли изменять фактическое имя любого узла устройства при его создании с использованием ключа NAME=
, но оказалось, что это была эксплуатируемая проблема безопасности. (Как правило, поэтому у нас не может быть хороших вещей.)
В текущих реализациях установка ключа NAME=
ограничена только именами сетевых интерфейсов, чтобы все еще разрешить наиболее широко используемый необозримый случай использования, который имела старая реализация.
Вы не можете изменить имена устройств, но вы можете создать свои собственные устройства в /dev
и продублировать основные/второстепенные номера, чтобы фактически создать клон. Вы можете сделать это с помощью mknod
или просто с помощью жесткой ссылки ln
. Если вы хотите поменять местами ttyACM0
и ttyACM1
, например, вы можете сделать
cd /dev
ln ttyACM0 ttyACMx0
ln ttyACM1 ttyACMx1
rm ttyACM0 ttyACM1
ln ttyACMx1 ttyACM0
ln ttyACMx0 ttyACM1
rm ttyACMx0 ttyACMx1
или даже просто переименовать их:
cd /dev
mv ttyACM0 ttyACMx
mv ttyACM1 ttyACM0
mv ttyACMx ttyACM1
Это может привести к некоторой путанице, если устройства отключаются не группой, конечно.
Ответ или решение
Чтобы решить проблему с назначением фиксированных имен для устройств ttyACM
в Linux, важно сначала понять ограничения и возможности, предоставляемые системой udev
. Согласно документации udev
, невозможно изменить имя существующего устройства, однако можно создавать дополнительные символические ссылки. В вашей ситуации, где у вас есть пять устройств ttyACM
, которые меняют порядок при загрузке, и вам необходим фиксированный порядок, это может быть сложной задачей.
Основные тезисы:
-
Ограничения
udev
: Согласно официальной документации,udev
не позволяет изменять имя устройства, когда речь идет не о сетевых интерфейсах. Это значит, что для устройства, созданного в/dev
, невозможно изменить его имя посредством правилаudev
, хотя можно создать символьные ссылки. -
Альтернативные стратегии:
-
Патч для программы: Одним из наиболее надежных решений будет модификация или патчинг самой программы, чтобы она принимала символическую ссылку, например
ttyACMradio
. Понимая, что это может быть неосуществимо из-за отсутствия поддержки со стороны разработчиков, можно рассмотреть изменение низкоуровневых вызовов библиотек, как описано в стратегии с использованиемLD_PRELOAD
. -
Использование
LD_PRELOAD
: Вы можете написать междупоточную библиотеку, которая перехватывает системные вызовыopen()
, заменяя путьttyACM0
на символическую ссылку. Это можно сделать с использованием C иdlfcn.h
для динамической подмены функций. Хотя для некоторых может показаться, что это сложный и неустойчивый подход, он тем не менее позволяет обойти проблему без изменения исходного кода приложения.
-
-
Создание дубликатов узлов: Если необходимо иметь непосредственные устройства с фиксированными именами, можно создать собственные устройства, дублируя основные и минорные номера. Это можно сделать с помощью
mknod
или создания твердых ссылок, однако это требует ручного вмешательства и управления этими устройствами в случае их отключения/подключения. -
Управление вручную: В крайних случаях можно вручную переименовывать устройства в
/dev
, чтобы поддерживать необходимый порядок, но это скорее временное решение, и оно может вызвать путаницу в случае отключения устройств.
Практические советы:
-
Мониторинг изменений: Используйте
udevadm monitor
или аналогичный инструмент для отслеживания, какudev
назначает устройства при загрузке, и затем настройте ваши правилаudev
для создания нужных символических ссылок. -
Создание правил
udev
: Настройте правилаudev
так, чтобы они создавали символьные ссылки для всех ваших устройств, что позволит быстрее изменить междупоточную библиотеку в будущем.
Резюмируя, наиболее рациональной стратегией кажется использование междупоточной библиотеки для обхода ограничений текущей программы, поскольку непосредственно изменить имена ttyACM
устройств через udev
невозможно.