[SOLVED] Issue: USB passthrough: Using "Network UPS Tools" (NUT) within a container for an APC UPS

Hi,

I’m trying to run NUT (“Network UPS Tools”) within an unprivileged LXD container, as described here:

https://alioth-lists.debian.net/pipermail/nut-upsuser/2022-July/012884.html

So the passthrough itself seems to have worked via $ lxc config device add nut apcusbhid usb vendorid=051d productid=0002, lsusb and “nut-scanner -U” both find the USB interface to and the APC UPS itself.

However trying to start the NUT drivers fails:

root@nut:~# upsdrvctl -DDD start
Network UPS Tools - UPS driver controller 2.7.4
   0.000000
If you're not a NUT core developer, chances are that you're told to enable debugging
to see why a driver isn't working for you. We're sorry for the confusion, but this is
the 'upsdrvctl' wrapper, not the driver you're interested in.

Below you'll find one or more lines starting with 'exec:' followed by an absolute
path to the driver binary and some command line option. This is what the driver
starts and you need to copy and paste that line and append the debug flags to that
line (less the 'exec:' prefix).

   0.000373     Starting UPS: apc-back-ups-rs-900g
   0.000480     1 remaining attempts
   0.000547     exec:  /lib/nut/usbhid-ups -a apc-back-ups-rs-900g
Network UPS Tools - Generic HID driver 0.41 (2.7.4)
USB communication driver 0.33
device->Product is NULL so it is not possible to determine whether to activate max_report_size workaround
Can't claim USB device [051d:0002]: could not detach kernel driver from interface 0: Operation not permitted
   0.012423     Driver failed to start (exit status=1)
root@nut:~# /lib/nut/usbhid-ups -DDD -a apc-back-ups-rs-900g
Network UPS Tools - Generic HID driver 0.41 (2.7.4)
USB communication driver 0.33
   0.000000     debug level is '3'
   0.004204     upsdrv_initups...
   0.005032     Checking device (051D/0002) (001/004)
   0.005239     - VendorID: 051d
   0.005306     - ProductID: 0002
   0.005365     - Manufacturer: unknown
   0.005424     - Product: unknown
   0.005477     - Serial Number: unknown
   0.005526     - Bus: 001
   0.005574     - Device release number: 0090
   0.005623     Trying to match device
   0.005679     device->Product is NULL so it is not possible to determine whether to activate max_report_size workaround
   0.005852     Device matches
   0.005928     failed to claim USB device: could not claim interface 0: Operation not permitted
   0.005992     failed to detach kernel driver from USB device: could not detach kernel driver from interface 0: Operation not permitted
   0.006052     failed to claim USB device: could not claim interface 0: Operation not permitted
   0.006112     failed to detach kernel driver from USB device: could not detach kernel driver from interface 0: Operation not permitted
   0.006175     failed to claim USB device: could not claim interface 0: Operation not permitted
   0.006241     failed to detach kernel driver from USB device: could not detach kernel driver from interface 0: Operation not permitted
   0.006301     failed to claim USB device: could not claim interface 0: Operation not permitted
   0.006363     failed to detach kernel driver from USB device: could not detach kernel driver from interface 0: Operation not permitted
   0.006426     Can't claim USB device [051d:0002]: could not detach kernel driver from interface 0: Operation not permitted
root@nut:~#

Which is a call to usb_claim_interface() in libusb-0.1 from here in NUT: https://github.com/networkupstools/nut/blob/v2.7.4/drivers/libusb.c#L267

The usb_claim_interface() call in libusb-0.1 in turn calls ioctl(dev->fd, IOCTL_USB_CLAIMINTF, &interface), which is the ioctl call USBDEVFS_CLAIMINTERFACE in the Linux kernel: usb.rst - Documentation/driver-api/usb/usb.rst - Linux source code (v5.18.12) - Bootlin. Which unfortunately returns an -EPERM. All syscalls via strace:

$ strace /lib/nut/usbhid-ups -DDD -a apc-back-ups-rs-900g 2> /tmp/strace-usbhid-ups > /tmp/strace-usbhid-ups
Network UPS Tools - Generic HID driver 0.41 (2.7.4)
USB communication driver 0.33
rs-900g"], 0x7fd4c67278 /* 9 vars */) = 0
brk(NULL)                               = 0x558ceaa000
faccessat(AT_FDCWD, "/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=10217, ...}) = 0
mmap(NULL, 10217, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fadb94000
close(3)                                = 0
openat(AT_FDCWD, "/lib/aarch64-linux-gnu/libusb-0.1.so.4", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0\267\0\1\0\0\0\220\32\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0644, st_size=30872, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fadb92000
mmap(NULL, 106920, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fadb4e000
mprotect(0x7fadb54000, 65536, PROT_NONE) = 0
mmap(0x7fadb64000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x6000) = 0x7fadb64000
mmap(0x7fadb66000, 8616, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fadb66000
close(3)                                = 0
openat(AT_FDCWD, "/lib/aarch64-linux-gnu/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0\267\0\1\0\0\0\0q\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=160200, ...}) = 0
mmap(NULL, 197632, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fadb1d000
mprotect(0x7fadb39000, 61440, PROT_NONE) = 0
mmap(0x7fadb48000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1b000) = 0x7fadb48000
mmap(0x7fadb4a000, 13312, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fadb4a000
close(3)                                = 0
openat(AT_FDCWD, "/lib/aarch64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0\267\0\1\0\0\0`C\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1458480, ...}) = 0
mmap(NULL, 1531032, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fad9a7000
mprotect(0x7fadb04000, 65536, PROT_NONE) = 0
mmap(0x7fadb14000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x15d000) = 0x7fadb14000
mmap(0x7fadb1a000, 11416, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fadb1a000
close(3)                                = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fadb90000
mprotect(0x7fadb14000, 12288, PROT_READ) = 0
mprotect(0x7fadb48000, 4096, PROT_READ) = 0
mprotect(0x7fadb64000, 4096, PROT_READ) = 0
mprotect(0x55645a3000, 4096, PROT_READ) = 0
mprotect(0x7fadb9a000, 4096, PROT_READ) = 0
munmap(0x7fadb94000, 10217)             = 0
set_tid_address(0x7fadb900e0)           = 317
set_robust_list(0x7fadb900f0, 24)       = 0
rt_sigaction(SIGRTMIN, {sa_handler=0x7fadb23b74, sa_mask=[], sa_flags=SA_SIGINFO}, NULL, 8) = 0
rt_sigaction(SIGRT_1, {sa_handler=0x7fadb23c30, sa_mask=[], sa_flags=SA_RESTART|SA_SIGINFO}, NULL, 8) = 0
rt_sigprocmask(SIG_UNBLOCK, [RTMIN RT_1], NULL, 8) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
brk(NULL)                               = 0x558ceaa000
brk(0x558cecb000)                       = 0x558cecb000
socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0) = 3
connect(3, {sa_family=AF_UNIX, sun_path="/dev/log"}, 110) = 0
fstat(1, {st_mode=S_IFREG|0644, st_size=3470, ...}) = 0
openat(AT_FDCWD, "/etc/nut/ups.conf", O_RDONLY) = 4
fstat(4, {st_mode=S_IFREG|0640, st_size=169, ...}) = 0
read(4, "#maxretry = 3\n\n[apc-back-ups-rs-"..., 4096) = 169
read(4, "", 4096)                       = 0
close(4)                                = 0
write(2, "   0.000000\t", 12   0.000000 )           = 12
write(2, "debug level is '3'\n", 19debug level is '3'
)    = 19
socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 4
connect(4, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
close(4)                                = 0
socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 4
connect(4, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
close(4)                                = 0
openat(AT_FDCWD, "/etc/nsswitch.conf", O_RDONLY|O_CLOEXEC) = 4
fstat(4, {st_mode=S_IFREG|0644, st_size=510, ...}) = 0
read(4, "# /etc/nsswitch.conf\n#\n# Example"..., 4096) = 510
read(4, "", 4096)                       = 0
close(4)                                = 0
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 4
fstat(4, {st_mode=S_IFREG|0644, st_size=10217, ...}) = 0
mmap(NULL, 10217, PROT_READ, MAP_PRIVATE, 4, 0) = 0x7fadb94000
close(4)                                = 0
openat(AT_FDCWD, "/lib/aarch64-linux-gnu/libnss_files.so.2", O_RDONLY|O_CLOEXEC) = 4
read(4, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0\267\0\1\0\0\0\0*\0\0\0\0\0\0"..., 832) = 832
fstat(4, {st_mode=S_IFREG|0644, st_size=51640, ...}) = 0
mmap(NULL, 140616, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 4, 0) = 0x7fad984000
mprotect(0x7fad98f000, 65536, PROT_NONE) = 0
mmap(0x7fad99f000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 4, 0xb000) = 0x7fad99f000
mmap(0x7fad9a1000, 21832, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fad9a1000
close(4)                                = 0
mprotect(0x7fad99f000, 4096, PROT_READ) = 0
munmap(0x7fadb94000, 10217)             = 0
openat(AT_FDCWD, "/etc/passwd", O_RDONLY|O_CLOEXEC) = 4
lseek(4, 0, SEEK_CUR)                   = 0
fstat(4, {st_mode=S_IFREG|0644, st_size=1388, ...}) = 0
read(4, "root:x:0:0:root:/root:/bin/bash\n"..., 4096) = 1388
close(4)                                = 0
geteuid()                               = 0
getuid()                                = 0
setresuid(-1, 0, -1)                    = 0
openat(AT_FDCWD, "/proc/sys/kernel/ngroups_max", O_RDONLY) = 4
read(4, "65536\n", 31)                  = 6
close(4)                                = 0
socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 4
connect(4, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
close(4)                                = 0
socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 4
connect(4, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
close(4)                                = 0
openat(AT_FDCWD, "/etc/group", O_RDONLY|O_CLOEXEC) = 4
lseek(4, 0, SEEK_CUR)                   = 0
fstat(4, {st_mode=S_IFREG|0644, st_size=641, ...}) = 0
read(4, "root:x:0:\ndaemon:x:1:\nbin:x:2:\ns"..., 4096) = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
lseek(4, 0, SEEK_CUR)                   = 641
read(4, "", 4096)                       = 0
close(4)                                = 0
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 4
fstat(4, {st_mode=S_IFREG|0644, st_size=10217, ...}) = 0
mmap(NULL, 10217, PROT_READ, MAP_PRIVATE, 4, 0) = 0x7fadb94000
close(4)                                = 0
openat(AT_FDCWD, "/lib/aarch64-linux-gnu/libnss_systemd.so.2", O_RDONLY|O_CLOEXEC) = 4
read(4, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0\267\0\1\0\0\0\200^\0\0\0\0\0\0"..., 832) = 832
fstat(4, {st_mode=S_IFREG|0644, st_size=240072, ...}) = 0
mmap(NULL, 305920, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 4, 0) = 0x7fad939000
mprotect(0x7fad970000, 65536, PROT_NONE) = 0
mmap(0x7fad980000, 16384, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 4, 0x37000) = 0x7fad980000
close(4)                                = 0
openat(AT_FDCWD, "/lib/aarch64-linux-gnu/libm.so.6", O_RDONLY|O_CLOEXEC) = 4
read(4, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0\267\0\1\0\0\0\20\334\0\0\0\0\0\0"..., 832) = 832
fstat(4, {st_mode=S_IFREG|0644, st_size=633000, ...}) = 0
mmap(NULL, 696448, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 4, 0) = 0x7fad88e000
mprotect(0x7fad927000, 65536, PROT_NONE) = 0
mmap(0x7fad937000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 4, 0x99000) = 0x7fad937000
close(4)                                = 0
openat(AT_FDCWD, "/lib/aarch64-linux-gnu/libdl.so.2", O_RDONLY|O_CLOEXEC) = 4
read(4, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0\267\0\1\0\0\0\0\21\0\0\0\0\0\0"..., 832) = 832
fstat(4, {st_mode=S_IFREG|0644, st_size=14560, ...}) = 0
mmap(NULL, 78080, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 4, 0) = 0x7fad87a000
mprotect(0x7fad87d000, 61440, PROT_NONE) = 0
mmap(0x7fad88c000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 4, 0x2000) = 0x7fad88c000
close(4)                                = 0
mprotect(0x7fad88c000, 4096, PROT_READ) = 0
mprotect(0x7fad937000, 4096, PROT_READ) = 0
mprotect(0x7fad980000, 12288, PROT_READ) = 0
munmap(0x7fadb94000, 10217)             = 0
rt_sigprocmask(SIG_BLOCK, [HUP USR1 USR2 PIPE ALRM CHLD TSTP URG VTALRM PROF WINCH IO], [], 8) = 0
openat(AT_FDCWD, "/run/systemd/userdb/", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 4
fstat(4, {st_mode=S_IFDIR|0755, st_size=60, ...}) = 0
getdents64(4, 0x558ceaeae0 /* 3 entries */, 32768) = 96
socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 5
connect(5, {sa_family=AF_UNIX, sun_path="/run/systemd/userdb/io.systemd.DynamicUser"}, 45) = 0
getpid()                                = 317
epoll_create1(EPOLL_CLOEXEC)            = 6
timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC|TFD_NONBLOCK) = 7
epoll_ctl(6, EPOLL_CTL_ADD, 7, {EPOLLIN, {u32=2364239344, u64=367436459504}}) = 0
epoll_ctl(6, EPOLL_CTL_ADD, 5, {0, {u32=2364241488, u64=367436461648}}) = 0
gettid()                                = 317
getrandom("\xb1\x7e\x7a\x9d\xce\xe8\x1a\x62\x5e\x07\x66\xb6\x8e\x4d\xbe\x59", 16, GRND_NONBLOCK|GRND_INSECURE) = 16
futex(0x7fad983ac8, FUTEX_WAKE_PRIVATE, 2147483647) = 0
getdents64(4, 0x558ceaeae0 /* 0 entries */, 32768) = 0
close(4)                                = 0
epoll_ctl(6, EPOLL_CTL_MOD, 5, {EPOLLIN|EPOLLOUT, {u32=2364241488, u64=367436461648}}) = 0
openat(AT_FDCWD, "/proc/sys/kernel/random/boot_id", O_RDONLY|O_NOCTTY|O_CLOEXEC) = 4
read(4, "b5817194-760e-49cc-8610-aec40e45"..., 38) = 37
read(4, "", 1)                          = 0
close(4)                                = 0
timerfd_settime(7, TFD_TIMER_ABSTIME, {it_interval={tv_sec=0, tv_nsec=0}, it_value={tv_sec=36065, tv_nsec=862531000}}, NULL) = 0
epoll_pwait(6, [{EPOLLOUT, {u32=2364241488, u64=367436461648}}], 4, 0, NULL, 8) = 1
timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC|TFD_NONBLOCK) = 4
close(4)                                = 0
sendto(5, "{\"method\":\"io.systemd.UserDataba"..., 131, MSG_DONTWAIT|MSG_NOSIGNAL, NULL, 0) = 131
epoll_ctl(6, EPOLL_CTL_MOD, 5, {EPOLLIN, {u32=2364241488, u64=367436461648}}) = 0
epoll_pwait(6, [{EPOLLIN, {u32=2364241488, u64=367436461648}}], 4, 0, NULL, 8) = 1
mmap(NULL, 135168, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fad859000
mremap(0x7fad859000, 135168, 139264, MREMAP_MAYMOVE) = 0x7fad837000
recvfrom(5, "{\"error\":\"io.systemd.UserDatabas"..., 135152, MSG_DONTWAIT, NULL, NULL) = 66
epoll_ctl(6, EPOLL_CTL_MOD, 5, {0, {u32=2364241488, u64=367436461648}}) = 0
epoll_pwait(6, [], 4, 0, NULL, 8)       = 0
epoll_pwait(6, [], 4, 0, NULL, 8)       = 0
epoll_ctl(6, EPOLL_CTL_DEL, 5, NULL)    = 0
close(5)                                = 0
munmap(0x7fad837000, 139264)            = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
close(6)                                = 0
close(7)                                = 0
setgroups(1, [110])                     = 0
setgid(110)                             = 0
setuid(106)                             = 0
chdir("/run/nut")                       = 0
write(2, "   0.054668\t", 12   0.054668 )           = 12
write(2, "upsdrv_initups...\n", 18upsdrv_initups...
)     = 18
openat(AT_FDCWD, "/dev/bus/usb", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 4
fstat(4, {st_mode=S_IFDIR|0755, st_size=60, ...}) = 0
getdents64(4, 0x558ceb80d0 /* 3 entries */, 32768) = 72
close(4)                                = 0
openat(AT_FDCWD, "/dev/bus/usb", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 4
fstat(4, {st_mode=S_IFDIR|0755, st_size=60, ...}) = 0
getdents64(4, 0x558ceb80d0 /* 3 entries */, 32768) = 72
getdents64(4, 0x558ceb80d0 /* 0 entries */, 32768) = 0
close(4)                                = 0
openat(AT_FDCWD, "/dev/bus/usb/001", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 4
fstat(4, {st_mode=S_IFDIR|0755, st_size=60, ...}) = 0
getdents64(4, 0x558ceb80d0 /* 3 entries */, 32768) = 72
openat(AT_FDCWD, "/dev/bus/usb/001/004", O_RDWR) = -1 EACCES (Permission denied)
openat(AT_FDCWD, "/dev/bus/usb/001/004", O_RDONLY) = 5
ioctl(5, USBDEVFS_CONNECTINFO, 0x7fe46c3228) = -1 EPERM (Operation not permitted)
read(5, "\22\1\0\2\0\0\0@\35\5\2\0\220\0\1\2\3\1", 18) = 18
read(5, "\t\2\"\0\1\1\0\340", 8)        = 8
read(5, "\1\t\4\0\0\1\3\0\0\0\t!\0\1!\1\"n\4\7\5\201\3\10\0d", 26) = 26
close(5)                                = 0
getdents64(4, 0x558ceb80d0 /* 0 entries */, 32768) = 0
close(4)                                = 0
openat(AT_FDCWD, "/dev/bus/usb/001/004", O_RDWR) = -1 EACCES (Permission denied)
openat(AT_FDCWD, "/dev/bus/usb/001/004", O_RDONLY) = 4
ioctl(4, USBDEVFS_IOCTL, 0x7fe46c49e8)  = -1 EPERM (Operation not permitted)
close(4)                                = 0
write(2, "   0.063107\t", 12   0.063107 )           = 12
write(2, "Checking device (051D/0002) (001"..., 38Checking device (051D/0002) (001/004)
) = 38
openat(AT_FDCWD, "/dev/bus/usb/001/004", O_RDWR) = -1 EACCES (Permission denied)
openat(AT_FDCWD, "/dev/bus/usb/001/004", O_RDONLY) = 4
ioctl(4, USBDEVFS_CONTROL, 0x7fe46c5180) = -1 EPERM (Operation not permitted)
ioctl(4, USBDEVFS_CONTROL, 0x7fe46c5180) = -1 EPERM (Operation not permitted)
ioctl(4, USBDEVFS_CONTROL, 0x7fe46c5180) = -1 EPERM (Operation not permitted)
write(2, "   0.065652\t", 12   0.065652 )           = 12
write(2, "- VendorID: 051d\n", 17- VendorID: 051d
)      = 17
write(2, "   0.066273\t", 12   0.066273 )           = 12
write(2, "- ProductID: 0002\n", 18- ProductID: 0002
)     = 18
write(2, "   0.066931\t", 12   0.066931 )           = 12
write(2, "- Manufacturer: unknown\n", 24- Manufacturer: unknown
) = 24
write(2, "   0.067596\t", 12   0.067596 )           = 12
write(2, "- Product: unknown\n", 19- Product: unknown
)    = 19
write(2, "   0.068202\t", 12   0.068202 )           = 12
write(2, "- Serial Number: unknown\n", 25- Serial Number: unknown
) = 25
write(2, "   0.068805\t", 12   0.068805 )           = 12
write(2, "- Bus: 001\n", 11- Bus: 001
)            = 11
write(2, "   0.069409\t", 12   0.069409 )           = 12
write(2, "- Device release number: 0090\n", 30- Device release number: 0090
) = 30
write(2, "   0.070012\t", 12   0.070012 )           = 12
write(2, "Trying to match device\n", 23Trying to match device
) = 23
write(2, "   0.070623\t", 12   0.070623 )           = 12
write(2, "device->Product is NULL so it is"..., 106device->Product is NULL so it is not possible to determine whether to activate max_report_size workaround
) = 106
write(2, "   0.071406\t", 12   0.071406 )           = 12
write(2, "Device matches\n", 15Device matches
)        = 15
ioctl(4, USBDEVFS_CLAIMINTERFACE, 0x7fe46c52ec) = -1 EPERM (Operation not permitted)
write(2, "   0.072330\t", 12   0.072330 )           = 12
write(2, "failed to claim USB device: coul"..., 81failed to claim USB device: could not claim interface 0: Operation not permitted
) = 81
ioctl(4, USBDEVFS_IOCTL, 0x7fe46c52d8)  = -1 EPERM (Operation not permitted)
write(2, "   0.073248\t", 12   0.073248 )           = 12
write(2, "failed to detach kernel driver f"..., 121failed to detach kernel driver from USB device: could not detach kernel driver from interface 0: Operation not permitted
) = 121
ioctl(4, USBDEVFS_CLAIMINTERFACE, 0x7fe46c52ec) = -1 EPERM (Operation not permitted)
write(2, "   0.074162\t", 12   0.074162 )           = 12
write(2, "failed to claim USB device: coul"..., 81failed to claim USB device: could not claim interface 0: Operation not permitted
) = 81
ioctl(4, USBDEVFS_IOCTL, 0x7fe46c52d8)  = -1 EPERM (Operation not permitted)
write(2, "   0.075155\t", 12   0.075155 )           = 12
write(2, "failed to detach kernel driver f"..., 121failed to detach kernel driver from USB device: could not detach kernel driver from interface 0: Operation not permitted
) = 121
ioctl(4, USBDEVFS_CLAIMINTERFACE, 0x7fe46c52ec) = -1 EPERM (Operation not permitted)
write(2, "   0.076069\t", 12   0.076069 )           = 12
write(2, "failed to claim USB device: coul"..., 81failed to claim USB device: could not claim interface 0: Operation not permitted
) = 81
ioctl(4, USBDEVFS_IOCTL, 0x7fe46c52d8)  = -1 EPERM (Operation not permitted)
write(2, "   0.076979\t", 12   0.076979 )           = 12
write(2, "failed to detach kernel driver f"..., 121failed to detach kernel driver from USB device: could not detach kernel driver from interface 0: Operation not permitted
) = 121
ioctl(4, USBDEVFS_CLAIMINTERFACE, 0x7fe46c52ec) = -1 EPERM (Operation not permitted)
write(2, "   0.077894\t", 12   0.077894 )           = 12
write(2, "failed to claim USB device: coul"..., 81failed to claim USB device: could not claim interface 0: Operation not permitted
) = 81
ioctl(4, USBDEVFS_IOCTL, 0x7fe46c52d8)  = -1 EPERM (Operation not permitted)
write(2, "   0.078891\t", 12   0.078891 )           = 12
write(2, "failed to detach kernel driver f"..., 121failed to detach kernel driver from USB device: could not detach kernel driver from interface 0: Operation not permitted
) = 121
write(2, "   0.079548\t", 12   0.079548 )           = 12
write(2, "Can't claim USB device [051d:000"..., 109Can't claim USB device [051d:0002]: could not detach kernel driver from interface 0: Operation not permitted
) = 109
write(1, "Network UPS Tools - Generic HID "..., 82) = 82
exit_group(1)                           = ?
+++ exited with 1 +++

Is this USBDEVFS_CLAIMINTERFACE ioctl supposed to be usable in an unprivileged container? And if yes, are there additional steps I would need to take to make it work?

Regards, T_X

Found my issue: I wasn’t aware that when /lib/nut/usbhid-ups is started as root that it drops its user privileges from root to the “nut” user:

root@nut:~# ps -Af | grep usb
nut           91       1  0 22:10 ?        00:00:02 /lib/nut/usbhid-ups -a apc-back-ups-rs-900g

So I needed to add the “uid” and “gid” attributes here:

$ lxc start nut
[ nut needs to be installed in the container before, so that the user+group "nut"
  are available ]
$ lxc config device add nut apcusbhid usb vendorid=051d productid=0002 uid="$(lxc exec nut -- /bin/id -u nut)" gid="$(lxc exec nut -- /bin/id -g nut)"
$ lxc exec nut -- /usr/bin/systemctl enable nut-server
$ lxc restart nut

upsc now returns just fine, with valid values:

$ lxc exec nut -- /usr/bin/upsc apc-back-ups-rs-900g@localhost battery.charge
Init SSL without certificate database
100
1 Like