Granting user read/write permissions to a USB device in Ubuntu Linux on boot
Sep 22, 2020
One of my recent projects involved having a cash register print receipts to a
thermal printer. In order to do this, we had a Sinatra
server running on boot on the device that would, upon receiving a POST
request, print out the ESC/POS data.
The way I did this was by creating a custom service on Ubuntu that automatically starts on boot that runs the following script:
$ su - <NON_ROOT_USER> -l -c \
"cd /path/to/code && \
bundle exec ruby app.rb /dev/usb/lp0"
In this script, we’re passing the file path /dev/usb/lp0
to the server. This
is the file descriptor for the thermal printer I wish to print to.
With all this set, what happens when I power on the device and send in a POST
request?
Sadly, I get an error saying that I don’t have permission to write to the file
/dev/usb/lp0
. I can run the following command to fix this, though:
$ sudo chown <NON_ROOT_USER> /dev/usb/lp0
Once I enter it, I can print to my heart’s (and paper capicity’s) content! However, once the device gets rebooted, the file will get re-created and not have the permissions I assigned. Well, darn.
I did find a solution that could help me, though!
Custom udev rules
This was when I learned about udev
, the system for managing devices in Debian systems. With it, I can write specific rules for recognising devices:
udev allows for rules that specify what name is given to a device, regardless of which port it is plugged into. For example, a rule to always mount a hard drive with manufacturer “iRiver” and device code “ABC” as /dev/iriver is possible. This consistent naming of devices guarantees that scripts dependent on a specific device’s existence will not be broken.
This is exactly what I need! I can assign all usb devices plugged into the Linux machine to be owned by NON_ROOT_USER
.
By following the instructions, I created a file in the directory /etc/udev/rules.d
called 99-perm.rules
with the following line:
SUBSYSTEM=="usb", OWNER="<NON_ROOT_USER>"
With this rule in place, rebooting the machine immediately made it work!
Making it more secure
You might’ve been asking yourself:
Does this mean all USB devices will be owned by
NON_ROOT_USER
?
And you would be absolutely correct. Maybe we want to only grant permissions for that one device, which we can totally do! We can base it on the serial number of the device. For example, for my thermal printer, I can find this out with the following command:
$ udevadm -a /dev/usb/lp0
This’ll print out some results, including this line:
ATTRS{serial}=="L29955839962630040"
Let’s open up /etc/udev/rules.d/99-perm.rules
again, and replace the line we added with the following:
SUBSYSTEM=="usb", ATTRS{serial}=="L29955839962630040", OWNER="<NON_ROOT_USER>"
See with this we’re specifically telling udev
that we wanna own this specific
thermal printer. A quick reboot and score, it works!
What’s next?
A solid question! There is certainly more we can do to make sure this system works.
For example, if I were to have multiple thermal printers on a device, then it
wouldn’t be guaranteed to have the name lp0
. For this case we can use the
SYMLINK+=
attribute to specify a filename it’ll always definitely have. So
instead of lp0
or lp1
I could point my script to thermal-printer
or something:
SUBSYSTEM=="usb", ATTRS{serial}=="L72010011070626380", OWNER="<NON_ROOT_USER>", SYMLINK+="usb/thermal-printer"
This will then create a symbolic
link called
/dev/usb/thermal-printer
that I can point my Ruby server to!
Big thank you to Daniel Drake
for this helpful guide on how to write rules for udev
!
I’m eager to hear your thoughts or ideas for improvements, so hit me up on Twitter!
Buy me a coffee @hola_soy_milk