How do I uniquely identify an USB-device? - linux

I was wondering how to get the unique id of a USB storage device.
I already know how to fetch the SCSI serial id from this post : USB-drive serial number under linux C++
The post mentions using the Device Descriptor to get the ID. Can someone post some code to determine the Device Descriptor information under Linux?

ls -l /dev/disk/by-id

I suggest to use libusb. You can find the documentation here.

With USB, the "device name" of a device can change, depending on the order of which, the device was connected.
Surprisingly few devices have a real serial number.
If you can't get a unique identification from the device itself, the only solution is to depend on the physical address of connection. The drawback on this, is that the address changes, if you plug the device into another USB connector.
Programmatically you can use sysfs to get the information the kernel has, about the device. Sysfs is a file-system-like representation of devices as the kernel sees them. (Its not real files on the disk)
With it, you can:
- identify the device type with product and vendor ID
- read the serial number of the device, if it has one.
- read the physical connection number on the USB hub
You could start by finding your type of devices in /sys/class. In this example I use an USB→LPT port. But the principle is the same.
$ ls -l /sys/class/usbmisc
lp1 -> ../../devices/pci0000:00/0000:00:1d.0/usb4/4-1/4-1.5/4-1.5:1.0/usbmisc/lp1
lp2 -> ../../devices/pci0000:00/0000:00:1d.0/usb4/4-1/4-1.6/4-1.6:1.0/usbmisc/lp2
Grap the device name from the uevent file:
cat /sys/class/usbmisc/lp1/uevent
MAJOR=180
MINOR=1
DEVNAME=__usb/lp1__
add /dev so you get the device name to open: /dev/usb/lp1
Use the real path:
$ cd -P /sys/class/usbmisc/lp1
Step back 3 branches:
$ cd ../../../
/sys/devices/pci0000:00/0000:00:1d.0/usb4/4-1/4-1.5
This directory contains a lot of the information on the device:
idProduct and idVendor can be used to uniquely identify the device type.
If there is a serial file and it contains a unique serial number, you are done.
Otherwise your option is to use the physical connection as identification, wich is this directory name “4-1.5” It is unique for the physical connection, and will as you already mentioned change if you plug the device to another port.

Generalizing Simon Rigét's answer, I came up with this bash function that, given optional vendor id and product id, returns a list of device node names, related to that vendor id and that product id if given.
getDevNodes() {
if [ -n "$1" ] && [ "$1" != "no_class" ]; then
2>/dev/null find -L /sys/class/$1 -maxdepth 2 -mindepth 2 -name uevent -exec realpath "{}" +
else
find /sys/devices -name uevent
fi | {
if [ -z "$1" ]; then
readarray -t lines < <(find /sys/class -maxdepth 2 -mindepth 2 -type l -print -exec realpath "{}" +)
local -i count=${#lines[#]} sys_dev=count/2 sys_class=0
local -A classes
while [ $sys_dev -lt $count ]; do
class="${lines[$sys_class]#/*/*/}"
class="${class%/*}"
classes["${lines[$sys_dev]}"]="$class"
sys_dev+=1
sys_class+=1
done
fi
readarray -t uevents
for u in "${uevents[#]}"; do
DEVNAME=; DEVTYPE=no_type; while IFS="=" read key value; do {
[ "$key" = "DEVNAME" ] && DEVNAME=/dev/"$value"
} || {
[ "$key" = "DEVTYPE" ] && DEVTYPE="$value"
}; done < "$u"
if [ -n "$DEVNAME" ]; then
path="${u%/uevent}"
while [ "$path" != "/sys/devices" ] && ! [ -f "$path"/idVendor ]; do
path="${path%/*}"
done
[ "$path" != "/sys/devices" ] && {
read readIdVendor < "$path"/idVendor
read readIdProduct < "$path"/idProduct
} || {
readIdVendor=----
readIdProduct=----
}
echo "${1:-${classes[${u%/uevent}]:-no_class}}" "$DEVTYPE" "$readIdVendor" "$readIdProduct" "$DEVNAME"
fi
done
} | grep "^${1:-[[:graph:]]\+} ${2:-[[:graph:]]\+} ${3:-....} ${4:-....}" | cat
}
For instance, this is what my lsusb tells me:
$ lsusb
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 008: ID 0bda:b719 Realtek Semiconductor Corp.
Bus 001 Device 006: ID 0bda:57b5 Realtek Semiconductor Corp.
Bus 001 Device 004: ID 0bda:0129 Realtek Semiconductor Corp. RTS5129 Card Reader Controller
Bus 001 Device 097: ID 1004:6344 LG Electronics, Inc. G2 Android Phone [tethering mode]
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
So, if I wanted to see which device nodes are associated with the vendor id 0x1004 and the product id 0x6344 I'd do the following:
$ getDevNodes "" "" 1004 6344
no_class usb_device 1004 6344 /dev/bus/usb/001/097
tty no_type 1004 6344 /dev/ttyACM0
So we've got two device nodes, one of which is of class tty with no devtype and the other is of unknown class but with a devtype usb_device.
One can also only give the vendor id, like this:
$ getDevNodes "" "" 0bda
no_class usb_device 0bda 0129 /dev/bus/usb/001/004
no_class usb_device 0bda b719 /dev/bus/usb/001/008
no_class no_type 0bda 57b5 /dev/media0
video4linux no_type 0bda 57b5 /dev/video0
input no_type 0bda 57b5 /dev/input/event14
no_class usb_device 0bda 57b5 /dev/bus/usb/001/006
If I only wanted video4linux class devices whose vendor id is 0bda, then I'd do the following:
$ getDevNodes video4linux "" "" 0bda
video4linux no_type 0bda 57b5 /dev/video0
Arguments are basically filters over the complete list of device nodes and their associated info. Omitting one of those arguments, or using the empty string "" as an argument, disables the filter for that specific argument.
Arguments are given in this order: 1: the class, 2: the type, 3: the vendor id, 4: the product id.
Here follows a lite version of the above function that runs faster at the expense of some functionalities: the device nodes are printed without the additional info and there's no filter for the device type.
getDevNodesLite() {
if [ -n "$1" ]; then
2>/dev/null find -L /sys/class/$1 -maxdepth 2 -mindepth 2 -name uevent -exec realpath "{}" +
else
find /sys/devices -name uevent
fi | {
if [ -n "$2" ]; then
readarray -t uevents
for u in "${uevents[#]}"; do
path="${u%/uevent}"
while [ "$path" != "/sys/devices" ] && ! [ -f "$path"/idVendor ]; do
path="${path%/*}"
done
[ "$path" != "/sys/devices" ] && read readValue < "$path"/idVendor && [ "$readValue" = "$2" ] && {
if [ -n "$idProduct" ]; then
read readValue < "$path"/idProduct && [ "$readValue" = "$3" ]
fi
} && echo "$u"
done
else
cat
fi
} | {
readarray -t uevents
[ ${#uevents[#]} -gt 0 ] && sed -n 's,DEVNAME=\(.*\),/dev/\1,p' "${uevents[#]}"
}
}

To add to what everyone else has said:
USB devices do not always have a serial number; even when one is present, it is not guaranteed to be globally unique. (For instance, my Apple USB keyboard has no serial number, and GoPro cameras all have the same bogus serial number of 123456789ABC.) As such, it is not always possible to uniquely identify a device.

do sudo blkid and it will list the id of all mounted devices with a filesystem

Related

Check duplicate disk LABEL before mounting it to the system in bash script

is there a way to check if there is duplicate disk LABEL before mounting it to the system?
i need to make sure that if the user have two external drives, if the two of them have the same label, prompt to the user a warning and asking it to remove the duplicated disk.
my code is in the early stages:
if mountpoint -q "${JOB_MOUNT_DIR}"; then
echo " ${JOB_MOUNT_HD_LABEL} já está montado e está pronto para uso"
else
echo "O dispositivo ""${JOB_MOUNT_HD_LABEL}"" não está montado no diretório ""${JOB_MOUNT_DIR}"""
echo "Deseja montar o diretório?"
echo -n "Qual sua opção? [s/n]: "
read -r "opcao"
if [ "$opcao" == "s" ]; then
mkdir -p "${JOB_MOUNT_DIR}/${JOB_MOUNT_HD_LABEL}"
mount -L "${JOB_MOUNT_HD_LABEL}" "${JOB_MOUNT_DIR}/${JOB_MOUNT_HD_LABEL}"
exit 0
else
echo "Disco não irá ser montado"
exit 0
fi
fi
exit 0
some parts are in pt-br i think that will not be a problem
first it checks if that the disk is already mounted, if not it asks to mount, then there is the problem to know which of the two disk with the same LABEL is to mount
You can use:
lsblk -o name,label
NAME LABEL
mmcblk0
└─mmcblk0p1 eMMC
Or you can use:
blkid
/dev/nvme0n1p1: UUID="36D7-B890" TYPE="vfat" PARTUUID="8614534f-01"
/dev/nvme0n1p5: UUID="65885781-bd9b-4c62-afb0-4a82a0e5759e" TYPE="ext4" PARTUUID="8614534f-05"
/dev/mmcblk0p1: LABEL="eMMC" UUID="79ff33b4-2add-4f2f-844e-d7d242c18578" TYPE="ext4" PARTUUID="d4b36674-ab5f-4f15-bb83-313cce242fe4"
You may need to prefix above commands with sudo
If the above commands get your labels correctly, you can pipe the output roughly as follows to list duplicates:
lsblk -o label | sort | uniq -d

How to delete the data in Character device

I have written some data into my character device in /dev/my_char.
What should I do to delete the data without removing the device from the kernel ? .
The method which I follow to delete the contents is to
1) rm /dev/my_char and
2) rmmod My_Char.
But by using this method, I have to insert the module again into the kernel and create the device in dev folder which is a lengthy process.
Using only rm /dev/my_char doesn't delete its contents.
I would like to know if there is any other method other than this.
You could implement an ioctl to reset the input buffer.
Add an ioctl handler to the driver.
Add the entry point to the file_operations structure. .unlocked_ioctl =(your function name)
For the case of the correct ioctl command, reset the buffer pointer(s), clear the count, or whatever is needed to make the device look empty.
Or you could write a script to remove the driver and reload it. Here is what I use (I call it reload):
#!/bin/bash
if [ -d /device/my_device ]; then
sudo rmmod my_device.ko
fi
VERBOSE=0
MESSAGES=0
VENDOR=
DEVICEID=
while (( $# > 0 ))
do
arg="$1"
shift
case $arg in
v=* | ve=* | ver=* | verb=* | verbo=* | verbos=* | verbose=*)
VERBOSE=${arg#*=}
;;
v | ve | ver | verb | verbo | verbos | verbose)
VERBOSE=1
;;
t | tt | tty)
MESSAGES=1
;;
ven=* | vend=* | vendo=* | vendor=*)
VENDOR="opt_vendor_id=${arg#*=}"
;;
ven | vend | vendo | vendor)
VENDOR="opt_vendor_id=$1"
shift
;;
d=* | de=* | dev=* | devi=* | devic=* | device=*)
DEVICEID="opt_device_id=${arg#*=}"
;;
d | de | dev | devi | devic | device)
DEVICEID="opt_device_id=$1"
shift
;;
*)
echo "Invalid option '$arg':"
echo "Options are 'verbose', 'tty', 'vendor='<vendor number>, and 'deviceid='<device id>"
exit 1
;;
esac
done
echo "insmod my_device.ko opt_verbose=$VERBOSE opt_tty_msgs=$MESSAGES $VENDOR $DEVICEID"
sudo insmod my_device.ko opt_verbose=$VERBOSE opt_tty_msgs=$MESSAGES $VENDOR $DEVICEID
This has a lot of extra complexity to handle parameters which are passed to the module when it is loaded. If you don't have any module parameters, the above can be simplified to:
#!/bin/bash
if [ -d /device/my_device ]; then
sudo rmmod my_device.ko
fi
sudo insmod my_device.ko
You can work with your character device as if it is a generic file
cat /dev/null > /dev/my_char
Its possible to delete the data in the device by just removing the module from the kernel and then loading the module again to the kernel.ie "rmmod My_Char" and again "insmod My_Char".By this method we need not create the device again in the /dev/my_char as it will be automatically loaded with no data.

How to load LUKS passphrase from USB, falling back to keyboard?

I want to set up a headless Linux (Debian Wheezy) PC with whole disk encryption, with the ability to unlock the disk either with a USB drive, or by entering a passphrase by keyboard. My starting point is a fresh install using the basic whole disk encryption option in the Debian Installer, which manages everything besides /boot as a LUKS-encrypted logical volume group and gives me the keyboard option. I will describe my current solution in an answer, in hopes that it will be useful and that others can improve on it.
Here are some of the issues I had:
Setting up a passphrase and putting it on the USB drive.
Loading the USB modules in time.
Waiting for the USB drive to recognized by Linux before trying to read from it.
Identifying the correct USB drive (not some other drive that happens to be inserted).
Writing a "keyscript" to pull a passphrase off the USB drive.
Ensuring that the fall-back to keyboard kicks in in all USB failure cases.
I will accept an answer with significant improvements and upvote answers that offer contributions.
A lot of my solution is derived from the post, Using A USB Key For The LUKS Passphrase.
Create a random passphrase:
dd if=/dev/urandom bs=1 count=256 > passphrase
Insert a USB drive. dmesg output will show the device name; assume /dev/sdd. Figure out its size:
blockdev --getsize64 /dev/sdd
I decided to install the passphrase at the end of the raw device, figuring it might survive any accidental use of the USB drive.
dd if=passphrase of=/dev/sdd bs=1 seek=<size-256>
Add the passphrase to the LUKS volume:
cryptsetup luksAddKey /dev/sda5 passphrase
This does not affect the existing hand-entered passphrase from the installer. The passphrase file can be deleted:
rm passphrase
Find a unique name for the USB stick, so we can identify it when present:
ls -l /dev/disk/by-id | grep -w sdd
You should see one symlink. I will call it /dev/disk/by-id/<ID>.
Edit /etc/crypttab. You should see a line like:
sdc5_crypt UUID=b9570e0f-3bd3-40b0-801f-ee20ac460207 none luks
Modify it to:
sdc5_crypt UUID=b9570e0f-3bd3-40b0-801f-ee20ac460207 /dev/disk/by-id/<ID> luks,keyscript=/bin/passphrase-from-usb
The keyscript referred to above will need to read the passphrase from the USB device. However, it needs to do more than that. To understand how it is used, check /usr/share/initramfs-tools/scripts/local-top/cryptroot, the script that runs at boot time to unlock the root device. Note when a keyscript is set, it is simply run and the output piped to luksOpen with no other checking. There is no way to signal an error (USB drive not present) or fall back to keyboard input. If the passphrase fails, the keyscript is run again in a loop, up to some number of times; however we are not told which iteration we are on. Also, we have no control over when the keyscript is run, so we can't be sure Linux has recognized the USB drive.
I addressed this with some hacks:
Poll on the USB drive and wait 3 seconds for it to appear. This works for me, but I would love to know a better way.
Create a dummy file /passphrase-from-usb-tried on first run to indicate that we have been run at least once.
If we have been run at least once, or the USB drive cannot be found, run the askpass program used by cryptroot for keyboard input.
The final script:
#!/bin/sh
set -e
if ! [ -e /passphrase-from-usb-tried ]; then
touch /passphrase-from-usb-tried
if ! [ -e "$CRYPTTAB_KEY" ]; then
echo "Waiting for USB stick to be recognized..." >&2
sleep 3
fi
if [ -e "$CRYPTTAB_KEY" ]; then
echo "Unlocking the disk $CRYPTTAB_SOURCE ($CRYPTTAB_NAME) from USB key" >&2
dd if="$CRYPTTAB_KEY" bs=1 skip=129498880 count=256 2>/dev/null
exit
else
echo "Can't find $CRYPTTAB_KEY; USB stick not present?" >&2
fi
fi
/lib/cryptsetup/askpass "Unlocking the disk $CRYPTTAB_SOURCE ($CRYPTTAB_NAME)\nEnter passphrase: "
Finally, we need to ensure that this script is available in the initramfs. Create /etc/initramfs-tools/hooks/passphrase-from-usb containing:
#!/bin/sh
PREREQ=""
prereqs() {
echo "$PREREQ"
}
case "$1" in
prereqs)
prereqs
exit 0
;;
esac
. "${CONFDIR}/initramfs.conf"
. /usr/share/initramfs-tools/hook-functions
copy_exec /bin/passphrase-from-usb /bin
The USB drivers were not present in my initramfs. (It appears they are by default in later versions of Debian.) I had to add them by adding to /etc/initramfs-tools/modules:
uhci_hcd
ehci_hcd
usb_storage
When all is done, update the initramfs:
update-initramfs -u
It would be ideal to me if I could simply have a small USB stick containing a passphrase that will
unlock the disk. Not only would that be handy for servers (where you could leave the USB stick in the
server - the goal is to be able to return broken harddisks without having to worry about confidential data), it would also be great for my laptop: Insert the USB stick when booting and remove it after
unlocking the cryptodisk.
I have now written a patch that will search the root dir of all devices for the file 'cryptkey.txt' and try decrypting
with each line as a key. If that fails: Revert to typing in the pass phrase.
It does mean the key cannot contain \n, but that would apply to any typed in key, too. The good part is that you can use the same USB disk to store the key for multiple machines: You do not need a separate USB disk for each. So if you have a USB drive in your physical key ring, you can use the same drive for all the machines you boot when being physically close.
You add the key with:
cryptsetup luksAddKey /dev/sda5
And then put the same key as a line in a file on the USB/MMC disk called 'cryptkey.txt'. The patch is here:
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=864647
If the USB drivers, MMC drivers or the filesystems are not present in your initramfs, you need to add them by adding to /etc/initramfs-tools/modules:
uhci_hcd
ehci_hcd
usb_storage
nls_utf8
nls_cp437
vfat
fat
sd_mod
mmc_block
tifm_sd
tifm_core
mmc_core
tifm_7xx1
sdhci
sdhci_pci
When all is done, update the initramfs:
update-initramfs -u
It can be found as patch and file at: https://gitlab.com/ole.tange/tangetools/tree/master/decrypt-root-with-usb
despite the great answer from #Andrew which works in previous versions. The solution actually is outdated and needs lots of tuning for ubuntu 18.04 and 19.10. So I want to share my research on this.
There are several catches about crypttab. The sepcs actually changed a lot from 14.04 to 18.04 and to 19.10. It starts to support more parameters for cryptsetup. For example keyfile-offset, keyfile-size, etc. Some of the options e.g. nobootwait are gone. Some parameters supported in other distro already but is not supported in ubuntu yet (for example very nice parameter keyfile-timeout. This can eliminate the entire keyscript since it will automatically fallback to keyboard input after the keyfile-timeout.)
The major pit-fall for crypttab on ubuntu is that it actually processed by 2 different processes. One is the traditionally initramfs and another is the modern systemd. Systemd is supposed to be more advanced and flexiable in many aspect. However, systemd has poor support for crypptab, there are many options such as keyscript just silently ignored. so I have no idea what is going on, until I spotted this post. Almost all the posts online about crypttab settings is for initramfs not for systemd. So we need to add initramfs to all the entries in crypttab to avoid problems.
I also discovered a nice way to debug our keyscript and crypttab without VM or repeatedly rebooting. It is cryptdisks_start. Before we actually propagate our changes to initramfs, we should always test it with this nice command. Otherwise, you have to end-up locked out from your system and can only recover it through chroot environment.
#andrew posted a nice way to use data hide in the raw area of a file system. However, I found it is very annoying when we want to automatically create partitions and dd the raw data to lots of usbkeys, we have to calculate the offset for all different file systems and different partition sizes. Moreover, if a user accidentally write onto the FS, there is some risk that the key got overritten. A raw partition without any FS on it makes more sense in this case. However raw partition does not have UUID which is not very useful for automatic unlocking. Thus, I would like introduce a way just use normal passphrase files on the usbkey filesystem. The major issue of passdev is it does not seek/stop during reading the file. Thus we cannot use the keyfile-offset and keyfile-size option when we want to fallback to keyboard input. Because cryptsetup will actually try to skip in the input content and if the content is shorter than keyfile-size, it raises an error. This also means if there is large offset, passdev can be very slow since it always read from beginning. However, there is no point to implement offset and keyfile size for a actual file on file system. I believe those are created for raw device.
The crypttab
luks-part UUID="<uuid>" /dev/disk/by-uuid/<keyfile FS uuid>:/<keyfile path relative to usbkey root>:<timeout in sec> luks,keyfile-offset=<seek to the key>,keyfile-size=<>,keyscript=/bin/passphrase-from-usbfs.sh,tries=<number of times to try>,initramfs
the keyscript passphrase-from-usbfs.sh utilized the /lib/cryptsetup/scripts/passdev which will wait the usb device and mount the fs then pipe out the file content. It supports the CRYPTTAB_KEY in format of /device-path/<keyfile FS uuid>:/<keyfile path relative to usbkey root>:<timeout in sec>.
#!/bin/sh
#all message need to echo to stderr, the stdout is used for passphrase
# TODO: we may need to do something about the plymouth
echo "CRYPTTAB_KEY=$CRYPTTAB_KEY" >&2
echo "CRYPTTAB_OPTION_keyfile_offset=$CRYPTTAB_OPTION_keyfile_offset" >&2
#set your offset and file size here if your system does not support those paramters
#CRYPTTAB_OPTION_keyfile_offset=
#CRYPTTAB_OPTION_keyfile_size=
echo "timeout=$CRYPTTAB_OPTION_keyfile_timeout" >&2
CRYPTTAB_OPTION_keyfile_timeout=10 # keyfile-timeout is not supported yet
pass=$(/lib/cryptsetup/scripts/passdev $CRYPTTAB_KEY)
rc=$?
if ! [ $rc -eq 0 ]; then
echo "Can't find $CRYPTTAB_KEY; USB stick not present?" >&2
/lib/cryptsetup/askpass "Unlocking the disk $CRYPTTAB_SOURCE ($CRYPTTAB_NAME) Enter passphrase: "
else
echo "successfully load passphrase." >&2
echo -n $pass
fi
The hook tell update-initramfs to copy our scripts.
#!/bin/sh
PREREQ=""
prereqs() {
echo "$PREREQ"
}
case "$1" in
prereqs)
prereqs
exit 0
;;
esac
. "${CONFDIR}/initramfs.conf"
. /usr/share/initramfs-tools/hook-functions
copy_exec /bin/passphrase-from-usbfs.sh
copy_exec /bin/passphrase-from-usb.sh
#when using passdev we need to hook additionaly FS and binary
copy_exec /lib/cryptsetup/scripts/passdev
manual_add_modules ext4 ext3 ext2 vfat btrfs reiserfs xfs jfs ntfs iso9660 udf
Finally I posted the updated version of passphrase-from-usb.sh which can use the new parameters in crypttab:
To accompany excellent answers above please see C routines you could use to write/generate and read raw block device key. The "readkey.c" extracts key of given size from block device and "writekey.c" can generate or write existing key to raw device. The "readkey.c" once compiled can be used in custom script to extract key of known size from raw block device like so:
readkey </path/to/device> <keysize>
To see usage for "writekey", after compiled run it with no flags.
To compile just use:
gcc readkey.c -o readkey
gcc writekey.c -o writekey
I tested both on Verbatim 16GB USB 2.0 USB flash drive with custom "keyscript=" in crypttab also published below. The idea for "crypto-usb.sh" is from "debian etch" cryptsetup guide.
crypto-usb.sh:
#!/bin/sh
echo ">>> Trying to get the key from agreed space <<<" >&2
modprobe usb-storage >/dev/null 2>&1
sleep 4
OPENED=0
disk="/sys/block/sdb"
boot_dir="/boot"
readkey="/boot/key/readkey"
echo ">>> Trying device: $disk <<<" >&2
F=$disk/dev
if [ 0`cat $disk/removable` -eq 1 -a -f $F ]; then
mkdir -p $boot_dir
mount /dev/sda1 $boot_dir -t ext2 >&2
echo ">>> Attempting key extraction <<<" >&2
if [ -f $readkey ]; then
# prints key array to the caller
$readkey /dev/sdb 4096
OPENED=1
fi
umount $boot_dir >&2
fi
if [ $OPENED -eq 0 ]; then
echo "!!! FAILED to find suitable key !!!" >&2
echo -n ">>> Try to enter your password: " >&2
read -s -r A
echo -n "$A"
else
echo ">>> Success loading key <<<" >&2
fi
When generating the key size of the key has to be provided, generated key is saved to ".tmpckey" file with file permissions 0600 for later use. When writing existing key, size is determined by measuring the existing key size. This looks like complex approach however once compiled with simple "gcc" it can provide easy way of manipulating the raw key content.
readkey.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void main(int argc, char *argv[])
{
int blockSize = 512;
int keySize = 2048;
FILE *device;
if ( argc == 3
&& (sizeof(argv[1]) / sizeof(char)) > 1
&& (sizeof(argv[2]) / sizeof(char)) > 1
&& (atoi(argv[2]) % 512) == 0
) {
device = fopen(argv[1], "r");
if(device == NULL) {
printf("\nI got trouble opening the device %s\n", argv[1]);
exit(EXIT_FAILURE);
}
keySize = atoi(argv[2]);
}
else if ( argc == 2
&& (sizeof(argv[1]) / sizeof(char)) > 1
) {
device = fopen(argv[1], "r");
if(device == NULL) {
printf("\nI got trouble opening the device %s\n", argv[1]);
exit(EXIT_FAILURE);
}
}
else {
printf("\nUsage: \n");
printf("\nKey Size Provided: \n");
printf("\n\t\treadkey </path/to/device> <keysize> \n");
printf("\nDefault key size: %d\n", keySize);
printf("\n\t\treadkey </path/to/device>\n");
exit(1);
}
int count;
char *block;
/* Verify if key is multiple of blocks */
int numBlocks = 0;
if (keySize % 512 != 0) {
printf("\nSory but key size is not multiple of block size, try again. TA.\n");
exit(1);
}
/* Seek till the end to get disk size and position to start */
fseek(device, 0, SEEK_END);
/* Determine where is the end */
long endOfDisk = ftell(device);
/* Make sure we start again */
rewind(device); // Do I need it ???
/* Get the required amount minus block size */
long startFrom = endOfDisk - blockSize - keySize;
/* Allocate space for bloc */
block = calloc(keySize, sizeof(char));
/* Start reading from specified block */
fseek(device, startFrom, SEEK_SET);
fread(block, 1, keySize, device);
/* Do something with the data */
for(count = 0; count < keySize/*sizeof(block)*/; count++){
printf("%c", block[count]);
}
/* Close file */
fclose(device);
/* Make sure freed array is zeroed */
memset(block, 0, keySize);
free(block);
}
writekey.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[])
{
int blockSize = 512;
int keySize = 2048;
int count;
unsigned char *block;
/*
Thing to always remember that argv starts from 0 - the name of the program, and argc starts from 1 i.e. 1 is the name of the program.
*/
if ( argc == 3
&& strcmp(argv[1], "genwrite") != 0
&& (sizeof(argv[2]) / sizeof(char)) > 2
) {
char ch;
FILE *keyF;
keyF = fopen(argv[1], "r");
if (keyF == NULL) exit(EXIT_FAILURE);
/* Tell key Size */
fseek(keyF, 0, SEEK_END);
keySize = ftell(keyF);
rewind(keyF);
printf("\nKey Size: %d\n", keySize);
block = calloc(keySize, sizeof(char));
printf("\n-- Start Key --:\n");
for(count = 0; count < keySize/*sizeof(block)*/; count++){
char ch = fgetc(keyF);
block[count] = ch;
/*
Uncomment below to see your key on screen
*/
// printf("%c",ch);
}
printf("\n-- End Key --:\n");
fclose(keyF);
}
else if ( argc == 3
&& strcmp(argv[1], "genwrite") == 0
&& (sizeof(argv[2]) / sizeof(char)) > 2
)
{
printf("\n-- Attempting to create random key(ish --) of size: %d\n", keySize);
block = calloc(keySize, sizeof(char));
int count;
for(count = 0; count < keySize/*sizeof(block)*/; count++){
block[count] = (char) rand();
}
FILE *tmpfile;
tmpfile = fopen(".tmpckey", "w");
if(tmpfile == NULL) exit(EXIT_FAILURE);
fwrite(block, 1, keySize, tmpfile);
fclose(tmpfile);
chmod(".tmpckey", 0600);
}
else if ( argc == 4
&& strcmp(argv[1], "genwrite") == 0
&& (sizeof(argv[2]) / sizeof(char)) > 2
&& ((atoi(argv[3]) % 512) == 0)
)
{
keySize = atoi(argv[3]);
printf("\n-- Attempting to create random key(ish --) of size: %d\n", keySize);
block = calloc(keySize, sizeof(char));
int count;
for(count = 0; count < keySize/*sizeof(block)*/; count++){
block[count] = (char) rand();
}
FILE *tmpfile;
tmpfile = fopen(".tmpckey", "w");
if(tmpfile == NULL) exit(EXIT_FAILURE);
fwrite(block, 1, keySize, tmpfile);
fclose(tmpfile);
chmod(".tmpckey", 0600);
}
else {
printf("\n");
printf("################################################################################\n");
printf("# #\n");
printf("# Usage: #\n");
printf("# #\n");
printf("################################################################################\n");
printf("#> To write existing key to device: #\n");
printf("# #\n");
printf("# writekey </path/to/keyfile> </path/to/removable/sd*> #\n");
printf("# #\n");
printf("#> To generate and write pseudo random key, #\n");
printf("#> key will be saved to temporary file .tmpckey #\n");
printf("# #\n");
printf("# writekey genwrite </path/to/removable/sd*> <keysize in multiples of 512> #\n");
printf("# #\n");
printf("#> When keysize is not provided default size is set to %d. #\n", keySize);
printf("# #\n");
printf("################################################################################\n");
exit(1);
}
/*
Some printf debugging below, uncomment when needed to see what is going on.
*/
/*
printf("\nNumber of Args: %d\n", argc);
printf("\nCurrently block array contains: \n");
for(count = 0; count < keySize; count++){
printf("%c", block[count]);
}
printf("\n-- End block -- \n");
*/
/* Open Device itp... */
FILE *device = fopen(argv[2], "a");
if(device == NULL) exit(EXIT_FAILURE);
printf("\nDevice to write: %s\n", argv[2]);
fseek(device, 0, SEEK_END);
/* Determine where is the end */
long endOfDisk = ftell(device);
printf("\nDevice Size: %ld\n", endOfDisk);
/* Verify if key is multiple of blocks */
int numBlocks = 0;
if (keySize % 512 != 0 || endOfDisk < (blockSize + keySize) ) {
printf("\nSorry but key size is not multiple of block size or device you trying to write to is too small, try again. TA.\n");
fclose(device);
exit(1);
}
/* Make sure we start again */
rewind(device);
/* Get the required amount sunbstracting block size */
long startFrom = endOfDisk - blockSize - keySize;
/* Write some data to the disk */
printf("\nWriting data starting from: %ld\n", startFrom);
fseek(device, startFrom, SEEK_SET);
fwrite(block, 1, keySize, device);
printf("\nBlock Position after data write procedure : %ld\n", ftell(device));
/*
Below is just for convenience, to read what was written,
can aid in debugging hence left commented for later.
*/
/*
printf("\nAmount of Data written : %ld\n", ftell(device) - startFrom);
// Start reading from specified block
printf("\n>>>>>>>> DEBUGGING SECTION <<<<<<<<<\n");
rewind(device); //
fseek(device, startFrom, SEEK_SET);
printf("\nBlock Position before read attempted: %d\n", ftell(device));
printf("\nKey size: %d\n", keySize);
fread(block, 1, keySize, device);
// Do something with the data
printf("\nBlock Position startFrom: %ld\n", startFrom);
printf("\nBlock Position after read: %d\n", ftell(device));
printf("\n-- Buffer Read: --\n");
for(count = 0; count < keySize; count++){
printf("%c", block[count]);
}
printf("\n-- End block -- \n");
printf("\n-- -- \n");
printf("\n-- -- \n");
*/
/* Close file */
fclose(device);
/* Make sure freed array is zeroed */
memset(block, 0, keySize);
free(block);
/* Return success, might change it to be useful return not place holder */
return 0;
}
To verify key written to raw device is the same as the one in file(below will output nothing if keys are identical):
diff -B <(./readkey </path/to/device> 4096) <(cat .tmpckey)
Or for existing key generated using own means:
diff -B <(./readkey </path/to/device> <generated elsewhere key size>) <(cat </path/to/keyfile>)
Thank You
Here is a solution similar to the one by Andrew, but
using CRYPTTAB_TRIED described in the Debian crypttab man page to distinguish tries, and
calling the existing standard keyscript /lib/cryptsetup/scripts/passdev on the first try.
Create your keyfile or keypartition as usual for the passdev script.
Create the following file /usr/local/bin/key-from-usb and make it executable.
#!/bin/sh
set -e
if [ $CRYPTTAB_TRIED -ge 1 ]; then
/lib/cryptsetup/askpass "Second try to unlock $CRYPTTAB_SOURCE ($CRYPTTAB_NAME). Please enter passphrase: "
else
/lib/cryptsetup/scripts/passdev $CRYPTTAB_KEY
fi
In /etc/crypttab use the parameter keyscript=/usr/local/bin/key-from-usb.
Create /etc/initramfs-tools/hooks/key-from-usb with this content:
#!/bin/sh
PREREQ=""
prereqs() {
echo "$PREREQ"
}
case "$1" in
prereqs)
prereqs
exit 0
;;
esac
. "${CONFDIR}/initramfs.conf"
. /usr/share/initramfs-tools/hook-functions
manual_add_modules vfat
copy_exec /usr/lib/cryptsetup/scripts/passdev /usr/lib/cryptsetup/scripts/passdev
copy_exec /usr/local/bin/key-from-usb /usr/local/bin/key-from-usb
The first copy_exec line here is needed because passdev is not copied if it is not mentioned in crypttab. Similarly, manual_add_modules vfat will ensure that a vfat usb disk can still be used.
Hint: Use lsinitramfs /boot/initrd.img-... and diff/compare the results to check that the script and all its dependencies are included.

Integrity Measurement Architecture(IMA) & Linux Extended Verification Module (EVM)

I am trying to activate IMA appraisal & EVM modules.
After compiling linux kernel 3.10.2 on my bt5R3 and setting kernel boot option in a first time like this:
GRUB_CMDLINE_LINUX="rootflags=i_version ima_tcb ima_appraise=fix ima_appraise_tcb evm=fix"
and after running this command to generate xattr security.ima and security.evm
find / \( -fstype rootfs -o -fstype ext4 \) -type f -uid 0 -exec head -c 1 '{}' \;
like this:
GRUB_CMDLINE_LINUX="rootflags=i_version ima_tcb ima_appraise=enforce ima_appraise_tcb evm=enforce"
I try to create digital signature of xattr like it's recommended on this tutorial
Tutorial to IMA & EVM
Every steps have been followed, creating RSA keys, loading them early at boot in initramfs with keyctl.
Session Keyring
-3 --alswrv 0 65534 keyring: _uid_ses.0
977514165 --alswrv 0 65534 \_ keyring: _uid.0
572301790 --alswrv 0 0 \_ user: kmk-user
126316032 --alswrv 0 0 \_ encrypted: evm-key
570886575 --alswrv 0 0 \_ keyring: _ima
304346597 --alswrv 0 0 \_ keyring: _evm
However as soon as I reboot my OS when I try to read a signed and hashed file I get the error "Permission Denied"
Running dmesg tells me :
[ 5461.175996] type=1800 audit(1375262160.913:57): pid=1756 uid=0 auid=4294967295 ses=4294967295 op="appraise_data" cause="**invalid-HMAC**" comm="sh" name="/root/Desktop/new.sh" dev="sda1" ino=546526 res=0
Have you any idea why i get invalid HMAC ?
They keys are loaded like the tutorial says...
#!/bin/sh -e
PREREQ=""
# Output pre-requisites
prereqs()
{
echo "$PREREQ"
}
case "$1" in
prereqs)
prereqs
exit 0
;;
esac
grep -q "ima=off" /proc/cmdline && exit 1
mount -n -t securityfs securityfs /sys/kernel/security
IMA_POLICY=/sys/kernel/security/ima/policy
LSM_POLICY=/etc/ima_policy
grep -v "^#" $LSM_POLICY >$IMA_POLICY
# import EVM HMAC key
keyctl show |grep -q kmk || keyctl add user kmk "testing123" #u
keyctl add encrypted evm-key "load `cat /etc/keys/evm-key`" #u
#keyctl revoke kmk
# import Module public key
mod_id=`keyctl newring _module #u`
evmctl import /etc/keys/pubkey_evm.pem $mod_id
# import IMA public key
ima_id=`keyctl newring _ima #u`
evmctl import /etc/keys/pubkey_evm.pem $ima_id
# import EVM public key
evm_id=`keyctl newring _evm #u`
evmctl import /etc/keys/pubkey_evm.pem $evm_id
# enable EVM
echo "1" > /sys/kernel/security/evm
# enable module checking
#echo "1" > /sys/kernel/security/module_check
Thanks for your help
Solved, new kernel use HMAC v2 and you have to activate asymmetric key when you compile kernel.
cat .config should have this entries:
CONFIG_EVM_HMAC_VERSION=2
CONFIG_ASYMMETRIC_KEY_TYPE=y
Then when you hash or sign a file use
evmctl -u - -x --imasig/--imahash $file
As well you should have create the asymetric keys and load them in _evm and _ima keyring with keyctl with initramfs.

How to find all serial devices (ttyS, ttyUSB, ..) on Linux without opening them?

What is the proper way to get a list of all available serial ports/devices on a Linux system?
In other words, when I iterate over all devices in /dev/, how do I tell which ones are serial ports in the classic way, that is, those usually supporting baud rates and RTS/CTS flow control?
The solution would be coded in C.
I ask because I am using a third-party library that does this clearly wrong: It appears to only iterate over /dev/ttyS*. The problem is that there are, for instance, serial ports over USB (provided by USB-RS232 adapters), and those are listed under /dev/ttyUSB*. And reading the Serial-HOWTO at Linux.org, I get the idea that there'll be other name spaces as well, as time comes.
So I need to find the official way to detect serial devices. The problem is that none appears to be documented, or I can't find it.
I imagine one way would be to open all files from /dev/tty* and call a specific ioctl() on them that is only available on serial devices. Would that be a good solution, though?
Update
hrickards suggested to look at the source for "setserial".
Its code does exactly what I had in mind:
First, it opens a device with:
fd = open (path, O_RDWR | O_NONBLOCK)
Then it invokes:
ioctl (fd, TIOCGSERIAL, &serinfo)
If that call returns no error, then it's a serial device, apparently.
I found similar code in Serial Programming/termios, which suggested to also add the O_NOCTTY option.
There is one problem with this approach, though:
When I tested this code on BSD Unix (that is, Mac OS X), it worked as well. However, serial devices that are provided through Bluetooth cause the system (driver) to try to connect to the Bluetooth device, which takes a while before it'll return with a timeout error. This is caused by just opening the device. And I can imagine that similar things can happen on Linux as well - ideally, I should not need to open the device to figure out its type. I wonder if there's also a way to invoke ioctl functions without an open, or open a device in a way that it does not cause connections to be made?
What should I do?
The /sys filesystem should contain plenty information for your quest. My system (2.6.32-40-generic #87-Ubuntu) suggests:
/sys/class/tty
Which gives you descriptions of all TTY devices known to the system. A trimmed down example:
# ll /sys/class/tty/ttyUSB*
lrwxrwxrwx 1 root root 0 2012-03-28 20:43 /sys/class/tty/ttyUSB0 -> ../../devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.4/2-1.4:1.0/ttyUSB0/tty/ttyUSB0/
lrwxrwxrwx 1 root root 0 2012-03-28 20:44 /sys/class/tty/ttyUSB1 -> ../../devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.3/2-1.3:1.0/ttyUSB1/tty/ttyUSB1/
Following one of these links:
# ll /sys/class/tty/ttyUSB0/
insgesamt 0
drwxr-xr-x 3 root root 0 2012-03-28 20:43 ./
drwxr-xr-x 3 root root 0 2012-03-28 20:43 ../
-r--r--r-- 1 root root 4096 2012-03-28 20:49 dev
lrwxrwxrwx 1 root root 0 2012-03-28 20:43 device -> ../../../ttyUSB0/
drwxr-xr-x 2 root root 0 2012-03-28 20:49 power/
lrwxrwxrwx 1 root root 0 2012-03-28 20:43 subsystem -> ../../../../../../../../../../class/tty/
-rw-r--r-- 1 root root 4096 2012-03-28 20:43 uevent
Here the dev file contains this information:
# cat /sys/class/tty/ttyUSB0/dev
188:0
This is the major/minor node. These can be searched in the /dev directory to get user-friendly names:
# ll -R /dev |grep "188, *0"
crw-rw---- 1 root dialout 188, 0 2012-03-28 20:44 ttyUSB0
The /sys/class/tty dir contains all TTY devices but you might want to exclude those pesky virtual terminals and pseudo terminals. I suggest you examine only those which have a device/driver entry:
# ll /sys/class/tty/*/device/driver
lrwxrwxrwx 1 root root 0 2012-03-28 19:07 /sys/class/tty/ttyS0/device/driver -> ../../../bus/pnp/drivers/serial/
lrwxrwxrwx 1 root root 0 2012-03-28 19:07 /sys/class/tty/ttyS1/device/driver -> ../../../bus/pnp/drivers/serial/
lrwxrwxrwx 1 root root 0 2012-03-28 19:07 /sys/class/tty/ttyS2/device/driver -> ../../../bus/platform/drivers/serial8250/
lrwxrwxrwx 1 root root 0 2012-03-28 19:07 /sys/class/tty/ttyS3/device/driver -> ../../../bus/platform/drivers/serial8250/
lrwxrwxrwx 1 root root 0 2012-03-28 20:43 /sys/class/tty/ttyUSB0/device/driver -> ../../../../../../../../bus/usb-serial/drivers/ftdi_sio/
lrwxrwxrwx 1 root root 0 2012-03-28 21:15 /sys/class/tty/ttyUSB1/device/driver -> ../../../../../../../../bus/usb-serial/drivers/ftdi_sio/
In recent kernels (not sure since when) you can list the contents of /dev/serial to get a list of the serial ports on your system. They are actually symlinks pointing to the correct /dev/ node:
flu0#laptop:~$ ls /dev/serial/
total 0
drwxr-xr-x 2 root root 60 2011-07-20 17:12 by-id/
drwxr-xr-x 2 root root 60 2011-07-20 17:12 by-path/
flu0#laptop:~$ ls /dev/serial/by-id/
total 0
lrwxrwxrwx 1 root root 13 2011-07-20 17:12 usb-Prolific_Technology_Inc._USB-Serial_Controller-if00-port0 -> ../../ttyUSB0
flu0#laptop:~$ ls /dev/serial/by-path/
total 0
lrwxrwxrwx 1 root root 13 2011-07-20 17:12 pci-0000:00:0b.0-usb-0:3:1.0-port0 -> ../../ttyUSB0
This is a USB-Serial adapter, as you can see. Note that when there are no serial ports on the system, the /dev/serial/ directory does not exists. Hope this helps :).
I found
dmesg | grep tty
doing the job.
I'm doing something like the following code. It works for USB-devices and also the stupid serial8250-devuices that we all have 30 of - but only a couple of them realy works.
Basically I use concept from previous answers. First enumerate all tty-devices in /sys/class/tty/. Devices that does not contain a /device subdir is filtered away. /sys/class/tty/console is such a device. Then the devices actually containing a devices in then accepted as valid serial-port depending on the target of the driver-symlink fx.
$ ls -al /sys/class/tty/ttyUSB0//device/driver
lrwxrwxrwx 1 root root 0 sep 6 21:28 /sys/class/tty/ttyUSB0//device/driver -> ../../../bus/platform/drivers/usbserial
and for ttyS0
$ ls -al /sys/class/tty/ttyS0//device/driver
lrwxrwxrwx 1 root root 0 sep 6 21:28 /sys/class/tty/ttyS0//device/driver -> ../../../bus/platform/drivers/serial8250
All drivers driven by serial8250 must be probes using the previously mentioned ioctl.
if (ioctl(fd, TIOCGSERIAL, &serinfo)==0) {
// If device type is no PORT_UNKNOWN we accept the port
if (serinfo.type != PORT_UNKNOWN)
the_port_is_valid
Only port reporting a valid device-type is valid.
The complete source for enumerating the serialports looks like this. Additions are welcome.
#include <stdlib.h>
#include <dirent.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <linux/serial.h>
#include <iostream>
#include <list>
using namespace std;
static string get_driver(const string& tty) {
struct stat st;
string devicedir = tty;
// Append '/device' to the tty-path
devicedir += "/device";
// Stat the devicedir and handle it if it is a symlink
if (lstat(devicedir.c_str(), &st)==0 && S_ISLNK(st.st_mode)) {
char buffer[1024];
memset(buffer, 0, sizeof(buffer));
// Append '/driver' and return basename of the target
devicedir += "/driver";
if (readlink(devicedir.c_str(), buffer, sizeof(buffer)) > 0)
return basename(buffer);
}
return "";
}
static void register_comport( list<string>& comList, list<string>& comList8250, const string& dir) {
// Get the driver the device is using
string driver = get_driver(dir);
// Skip devices without a driver
if (driver.size() > 0) {
string devfile = string("/dev/") + basename(dir.c_str());
// Put serial8250-devices in a seperate list
if (driver == "serial8250") {
comList8250.push_back(devfile);
} else
comList.push_back(devfile);
}
}
static void probe_serial8250_comports(list<string>& comList, list<string> comList8250) {
struct serial_struct serinfo;
list<string>::iterator it = comList8250.begin();
// Iterate over all serial8250-devices
while (it != comList8250.end()) {
// Try to open the device
int fd = open((*it).c_str(), O_RDWR | O_NONBLOCK | O_NOCTTY);
if (fd >= 0) {
// Get serial_info
if (ioctl(fd, TIOCGSERIAL, &serinfo)==0) {
// If device type is no PORT_UNKNOWN we accept the port
if (serinfo.type != PORT_UNKNOWN)
comList.push_back(*it);
}
close(fd);
}
it ++;
}
}
list<string> getComList() {
int n;
struct dirent **namelist;
list<string> comList;
list<string> comList8250;
const char* sysdir = "/sys/class/tty/";
// Scan through /sys/class/tty - it contains all tty-devices in the system
n = scandir(sysdir, &namelist, NULL, NULL);
if (n < 0)
perror("scandir");
else {
while (n--) {
if (strcmp(namelist[n]->d_name,"..") && strcmp(namelist[n]->d_name,".")) {
// Construct full absolute file path
string devicedir = sysdir;
devicedir += namelist[n]->d_name;
// Register the device
register_comport(comList, comList8250, devicedir);
}
free(namelist[n]);
}
free(namelist);
}
// Only non-serial8250 has been added to comList without any further testing
// serial8250-devices must be probe to check for validity
probe_serial8250_comports(comList, comList8250);
// Return the lsit of detected comports
return comList;
}
int main() {
list<string> l = getComList();
list<string>::iterator it = l.begin();
while (it != l.end()) {
cout << *it << endl;
it++;
}
return 0;
}
I think I found the answer in my kernel source documentation:
/usr/src/linux-2.6.37-rc3/Documentation/filesystems/proc.txt
1.7 TTY info in /proc/tty
-------------------------
Information about the available and actually used tty's can be found in the
directory /proc/tty.You'll find entries for drivers and line disciplines in
this directory, as shown in Table 1-11.
Table 1-11: Files in /proc/tty
..............................................................................
File Content
drivers list of drivers and their usage
ldiscs registered line disciplines
driver/serial usage statistic and status of single tty lines
..............................................................................
To see which tty's are currently in use, you can simply look into the file
/proc/tty/drivers:
> cat /proc/tty/drivers
pty_slave /dev/pts 136 0-255 pty:slave
pty_master /dev/ptm 128 0-255 pty:master
pty_slave /dev/ttyp 3 0-255 pty:slave
pty_master /dev/pty 2 0-255 pty:master
serial /dev/cua 5 64-67 serial:callout
serial /dev/ttyS 4 64-67 serial
/dev/tty0 /dev/tty0 4 0 system:vtmaster
/dev/ptmx /dev/ptmx 5 2 system
/dev/console /dev/console 5 1 system:console
/dev/tty /dev/tty 5 0 system:/dev/tty
unknown /dev/tty 4 1-63 console
Here is a link to this file:
http://git.kernel.org/?p=linux/kernel/git/next/linux-next.git;a=blob_plain;f=Documentation/filesystems/proc.txt;hb=e8883f8057c0f7c9950fa9f20568f37bfa62f34a
setserial with the -g option appears to do what you want and the C source is available at http://www.koders.com/c/fid39344DABD14604E70DF1B8FEA7D920A94AF78BF8.aspx.
I have no serial device here to test it, but if you have python and dbus you can try it yourself.
import dbus
bus = dbus.SystemBus()
hwmanager = bus.get_object('org.freedesktop.Hal', '/org/freedesktop/Hal/Manager')
hwmanager_i = dbus.Interface(hwmanager, 'org.freedesktop.Hal.Manager')
print hwmanager_i.FindDeviceByCapability("serial")
If it fails you can search inside hwmanager_i.GetAllDevicesWithProperties() to see if the capability name "serial" that I just guessed has a different name.
HTH
Using /proc/tty/drivers only indicates which tty drivers are loaded. If you're looking for a list of the serial ports check out /dev/serial, it will have two subdirectories: by-id and by-path.
EX:
# find . -type l
./by-path/usb-0:1.1:1.0-port0
./by-id/usb-Prolific_Technology_Inc._USB-Serial_Controller-if00-port0
Thanks to this post: https://superuser.com/questions/131044/how-do-i-know-which-dev-ttys-is-my-serial-port
My approach via group dialout to get every tty with user 'dialout'
ls -l /dev/tty* | grep 'dialout'
to only get its folder
ls -l /dev/tty* | grep 'dialout' | rev | cut -d " " -f1 | rev
easy listen to the tty output e.g. when arduino serial out:
head --lines 1 < /dev/ttyUSB0
listen to every tty out for one line only:
for i in $(ls -l /dev/tty* | grep 'dialout' | rev | cut -d " " -f1 | rev); do head --lines 1 < $i; done
I really like the approach via looking for drivers:
ll /sys/class/tty/*/device/driver
You can pick the tty-Name now:
ls /sys/class/tty/*/device/driver | grep 'driver' | cut -d "/" -f 5
I do not have a USB serial device, but there must be a way to find the real ports using the HAL libraries directly:
====================================================================
#! /usr/bin/env bash
#
# Uses HAL to find existing serial hardware
#
for sport in $(hal-find-by-capability --capability serial) ; do
hal-get-property --udi "${sport}" --key serial.device
done
====================================================================
The posted python-dbus code nor this sh script lists the bluetooth /dev/rfcomm* devices, so it is not the best solution.
Note that on other unix platforms, the serial ports are not named ttyS? and even in linux, some serial cards allow you to name the devices. Assuming a pattern in the serial devices names is wrong.
My solution is based on udev library and below code is based on the example2:
#include <string.h>
#include <libudev.h>
bool enumerate_serial_ports(void)
{
struct udev* udev;
struct udev_enumerate* enumerate;
struct udev_list_entry* devices, *dev_list_entry;
/* create udev object */
udev = udev_new();
if (!udev)
{
SPDLOG_ERROR("Cannot create udev context.");
return false;
}
/* create enumerate object */
enumerate = udev_enumerate_new(udev);
if (!enumerate)
{
SPDLOG_ERROR("Cannot create enumerate context.");
udev_unref(udev);
return false;
}
udev_enumerate_add_match_subsystem(enumerate, "tty");
udev_enumerate_scan_devices(enumerate);
/* fillup device list */
devices = udev_enumerate_get_list_entry(enumerate);
if (!devices)
{
SPDLOG_ERROR("Failed to get device list.");
udev_enumerate_unref(enumerate);
udev_unref(udev);
return false;
}
udev_list_entry_foreach(dev_list_entry, devices)
{
struct udev_device* dev = udev_device_new_from_syspath(udev, udev_list_entry_get_name(dev_list_entry));
// filter out virtual ports
if((udev_device_get_sysnum(dev) != NULL) && (strstr(udev_device_get_devpath(dev), "/devices/virtual/") == NULL))
{
SPDLOG_DEBUG("subsystem={}", udev_device_get_subsystem(dev));
SPDLOG_DEBUG("syspath={}", udev_device_get_syspath(dev));
SPDLOG_DEBUG("sysname={}", udev_device_get_sysname(dev));
SPDLOG_DEBUG("sysnum={}", udev_device_get_sysnum(dev));
SPDLOG_DEBUG("devnode={}", udev_device_get_devnode(dev));
SPDLOG_DEBUG("-----------------------------------------");
}
/* free dev */
udev_device_unref(dev);
}
/* free enumerate */
udev_enumerate_unref(enumerate);
/* free udev */
udev_unref(udev);
return true;
}
And the output on a RPI4 with an USB serial adaptor:
[ debug ][11:50:47.645] - subsystem=tty
[ debug ][11:50:47.645] - syspath=/sys/devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.3/1-1.3:1.0/ttyUSB0/tty/ttyUSB0
[ debug ][11:50:47.645] - sysname=ttyUSB0
[ debug ][11:50:47.645] - sysnum=0
[ debug ][11:50:47.645] - devnode=/dev/ttyUSB0
[ debug ][11:50:47.645] - -----------------------------------------
[ debug ][11:50:47.645] - subsystem=tty
[ debug ][11:50:47.645] - syspath=/sys/devices/platform/soc/fe201000.serial/tty/ttyAMA0
[ debug ][11:50:47.645] - sysname=ttyAMA0
[ debug ][11:50:47.645] - sysnum=0
[ debug ][11:50:47.645] - devnode=/dev/ttyAMA0
[ debug ][11:50:47.645] - -----------------------------------------
[ debug ][11:50:47.646] - subsystem=tty
[ debug ][11:50:47.646] - syspath=/sys/devices/platform/soc/fe215040.serial/tty/ttyS0
[ debug ][11:50:47.646] - sysname=ttyS0
[ debug ][11:50:47.646] - sysnum=0
[ debug ][11:50:47.646] - devnode=/dev/ttyS0
[ debug ][11:50:47.646] - -----------------------------------------
The serial communication manager library has many API and features targeted for the task you want. If the device is a USB-UART its VID/PID can be used. If the device is BT-SPP than platform specific APIs can be used. Take a look at this project for serial port programming: https://github.com/RishiGupta12/serial-communication-manager
#dmesg | grep tty
This command show you every port
yes, I know, I'm too late (as always). Here is my piece of code (based on the reply of mk2). Maybe this helps someone:
std::vector<std::string> find_serial_ports()
{
std::vector<std::string> ports;
std::filesystem::path kdr_path{"/proc/tty/drivers"};
if (std::filesystem::exists(kdr_path))
{
std::ifstream ifile(kdr_path.generic_string());
std::string line;
std::vector<std::string> prefixes;
while (std::getline(ifile, line))
{
std::vector<std::string> items;
auto it = line.find_first_not_of(' ');
while (it != std::string::npos)
{
auto it2 = line.substr(it).find_first_of(' ');
if (it2 == std::string::npos)
{
items.push_back(line.substr(it));
break;
}
it2 += it;
items.push_back(line.substr(it, it2 - it));
it = it2 + line.substr(it2).find_first_not_of(' ');
}
if (items.size() >= 5)
{
if (items[4] == "serial" && items[0].find("serial") != std::string::npos)
{
prefixes.emplace_back(items[1]);
}
}
}
ifile.close();
for (auto& p: std::filesystem::directory_iterator("/dev"))
{
for (const auto& pf : prefixes)
{
auto dev_path = p.path().generic_string();
if (dev_path.size() >= pf.size() && std::equal(dev_path.begin(), dev_path.begin() + pf.size(), pf.begin()))
{
ports.emplace_back(dev_path);
}
}
}
}
return ports;
}
Using setserial tool:
setserial -gG /dev/{ttyUSB,ttyS,ttyACM}* 2>/dev/null | grep -Ev "ttyS[0-9]+.*irq\s0\s*"
And if you want only the port device path on the output:
setserial -gG /dev/{ttyUSB,ttyS,ttyACM}* 2>/dev/null | grep -Ev "ttyS[0-9]+.*irq\s0\s*" | cut -d' ' -f1
Possibly this solutions are not applicable to all needs, since some USB devices can be named in another way by UDEV, so more generic but less optimal (NOT RECOMMENDED):
setserial -gG /dev/* 2>/dev/null | grep -Ev "ttyS[0-9]+.*irq\s0\s*" | cut -d' ' -f1

Resources