RealUID, Saved UID, Effective UID. What's going on? - linux

This is a set-root-uid program
$ls -l
-rwsr-sr-x 1 root root 7406 2011-12-13 22:37 ./x*
The source code:
int main(void) {
printf(
" UID GID \n"
"Real %d Real %d \n"
"Effective %d Effective %d \n",
getuid (), getgid (),
geteuid(), getegid()
);
seteuid(600);
printf(
" UID GID \n"
"Real %d Real %d \n"
"Effective %d Effective %d \n",
getuid (), getgid (),
geteuid(), getegid()
);
setuid(1000);
printf(
" UID GID \n"
"Real %d Real %d \n"
"Effective %d Effective %d \n",
getuid (), getgid (),
geteuid(), getegid()
);
setuid(0); // HOW DOES THIS SUCCEED IN SETTING THE EUID BACK TO 0
printf(
" UID GID \n"
"Real %d Real %d \n"
"Effective %d Effective %d \n",
getuid (), getgid (),
geteuid(), getegid()
);
return 0 ;
}
OUTPUT
UID GID
Real 1000 Real 1000
Effective 0 Effective 0
UID GID
Real 1000 Real 1000
Effective 600 Effective 0
UID GID
Real 1000 Real 1000
Effective 1000 Effective 1000
UID GID
Real 1000 Real 1000
Effective 0 Effective 1000
My question
The man page states that setuid will change the real,saved and effective uid.
So after the calling setuid(1000), all three change to 1000.
How is that setuid(0) let's me change euid to 0?

There are two cases,
You want to temporarily drop root privilege while executing setuid program
You want to permanently drop root privilege while executing setuid program...
You can temporarily do it by setting the euid to the real user id and then changing the uid to anything you want.And later when you need the root privilege back you can setuid to root and the effective userid will change back to root. This is because the saved user id is not changed.
You can drop privilege permanently by changing the uid straight away to a lesser privileged user id. After this no matter what you cannot get back the root privilege.
Case 1:
After a setuid program starts executing
1.seteuid(600);
2.setuid(1000);
3.setuid(0);
For this case the root privilege can be gained back again.
+----+------+------------+
| uid|euid |saved-uid |
|----|------|------------|
1.|1000| 0 | 0 |
2.|1000| 600 | 0 |
3.|1000| 1000 | 0 |
4.|1000| 0 | 0 |
| | | |
+------------------------+
Case 2:
After a setuid program starts executing,
1.setuid(1000);
2.setuid(0);
+----+------+------------+
| uid|euid |saved-uid |
|----|------|------------|
1.|1000|0 | 0 |
2.|1000|1000 | 1000 |
| | | |
+------------------------+
In this case you cannot get back the root privilege.
This can be verified by the following command,
cat /proc/PROCID/task/PROCID/status | less
Uid: 1000 0 0 0
Gid: 1000 0 0 0
This command will display a Uid and Gid and it will have 4 fields( the first three fields are the one we are concerned with). Something like the above
The three fields represent uid,euid and saved-user-id. You can introduce a pause (an input from user) in your setuid program and check for each step the cat /proc/PROCID/task/PROCID/status | less command. During each step you can check the saved uid getting changed as mentioned.
If you're euid is root and you change the uid, the privileges gets dropped permanently.If effective user id is not root then saved user id is never touched and you can regain the root privilege back anytime you want in your program.

DESCRIPTION
setuid() sets the effective user ID of the calling process. If the effective UID of the caller is
root, the real UID and saved set-user-ID are also set.
Under Linux, setuid() is implemented like the POSIX version with the _POSIX_SAVED_IDS feature. This
allows a set-user-ID (other than root) program to drop all of its user privileges, do some un-privileged work, and then reengage the original effective user ID in a secure manner.
If the user is root or the program is set-user-ID-root, special care must be taken. The setuid() function checks the effective user ID of the caller and if it is the superuser, all process-related user
ID's are set to uid. After this has occurred, it is impossible for the program to regain root privileges.
Thus, a set-user-ID-root program wishing to temporarily drop root privileges, assume the identity of an
unprivileged user, and then regain root privileges afterward cannot use setuid(). You can accomplish
this with seteuid(2).
(from the Linux Programmers' Manual, 2014-09-21, page setuid.2)

O! These functions are difficult to use correctly.
The man page states that setuid will change the real,saved and effective uid. So after the calling setuid(1000), all three change to 1000.
That is the case if and only if you are euid 0. At the time you call setuid(0), however, you are euid 1000 and saved uid 0 (check getresuid(2), for example). That's why you're able to regain privileges.

Code:
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
void print_uid(char *str, int ret)
{
uid_t ruid;
uid_t euid;
uid_t suid;
getresuid(&ruid, &euid, &suid);
printf("%s ret:%d\n"
"Real:%4d Effective:%4d Saved:%4d\n",
str, ret, ruid, euid, suid);
}
int main(void)
{
int ret = 0;
print_uid("init", ret); /* Real:1000 Effective: 0 Saved: 0 */
ret = seteuid(600);
print_uid("seteuid(600)", ret); /* Real:1000 Effective: 600 Saved: 0 */
ret = setuid(1000);
print_uid("setuid(1000)", ret); /* Real:1000 Effective:1000 Saved: 0 */
ret = setuid(0);
print_uid("setuid(0)", ret); /* Real:1000 Effective: 0 Saved: 0 */
ret = setuid(1000);
print_uid("setuid(1000)", ret); /* Real:1000 Effective:1000 Saved:1000 */
ret = setuid(0);
print_uid("setuid(0)", ret); /* Real:1000 Effective:1000 Saved:1000 */
return 0 ;
}
sudo chown root setuid_feature
sudo chmod +s setuid_feature
There are three uids for a process in Linux: REAL uid, EFFECTIVE uid, SAVED uid.
Cond 1. When euid is root, setuid or seteuid can be set to any uid, but there is a side effect, when using setuid(not seteuid), all of the three can be set to the same uid which is not ROOT, and then the process can't regain ROOT privilege.
Cond 2. When euid is not root, setuid or seteuid can be set to ruid or suid, and only euid is changed.
| seteuid | setuid
Cond 1. (euid == root) | set euid to any uid | set all three uids to any uid
Cond 2. (euid != root) | set euid to ruid or suid | set euid to ruid or suid
so, there are 5 setuid or seteuid process there in code, let me classify them:
1. seteuid(600): Cond 1, set euid to 600
2. setuid(1000): Cond 2, set euid to 1000
3. setuid(0) : Cond 2, set euid to 0(suid)
4. setuid(1000): Cond 1, set all three uids to 1000
5. setuid(0) : Cond 2, all three uids is not equal 0, so can't set to 0, failed with ret = -1

Related

Who runs first in fork, with contradicting results

I have this simple test:
int main() {
int res = fork();
if (res == 0) { // child
printf("Son running now, pid = %d\n", getpid());
}
else { // parent
printf("Parent running now, pid = %d\n", getpid());
wait(NULL);
}
return 0;
}
When I run it a hundred times, i.e. run this command,
for ((i=0;i<100;i++)); do echo ${i}:; ./test; done
I get:
0:
Parent running now, pid = 1775
Son running now, pid = 1776
1:
Parent running now, pid = 1777
Son running now, pid = 1778
2:
Parent running now, pid = 1779
Son running now, pid = 1780
and so on; whereas when I first write to a file and then read the file, i.e. run this command,
for ((i=0;i<100;i++)); do echo ${i}:; ./test; done > forout
cat forout
I get it flipped! That is,
0:
Son running now, pid = 1776
Parent running now, pid = 1775
1:
Son running now, pid = 1778
Parent running now, pid = 1777
2:
Son running now, pid = 1780
Parent running now, pid = 1779
I know about the scheduler. What does this result not mean, in terms of who runs first after forking?
The forking function, do_fork() (at kernel/fork.c) ends with setting the need_resched flag to 1, with the comment by kernel developers saying, "let the child process run first."
I guessed that this has something to do with the buffers that the printf writes to.
Also, is it true to say that the input redirection (>) writes everything to a buffer first and only then copies to the file? And even so, why would this change the order of the prints?
Note: I am running the test on a single-core virtual machine with a Linux kernel v2.4.14.
Thank you for your time.
When you redirect, glibc detects that stdout is not tty turns on output buffering for efficiency. The buffer is therefore not written until the process exits. You can see this with e.g.:
int main() {
printf("hello world\n");
sleep(60);
}
When you run it interactively, it prints "hello world" and waits. When you redirect to a file, you will see that nothing is written for 60 seconds:
$ ./foo > file & tail -f file
(no output for 60 seconds)
Since your parent process waits for the child, it will necessarily always exit last, and therefore flush its output last.

shm_open not setting group write access

I am creating a new shared memory object like so
int fd = shm_open("somekey", O_CREAT | O_RDWR, S_IRWXU | S_IRWXG);
The return value is fine and I expect to find something like the following in /dev/shm
-rwxrwx--- 1 root group 4096 Jun 27 19:08 somekey
but instead the write access in the files are missing and I don't know why.
-rwxr-x--- 1 root group 4096 Jun 27 19:08 somekey
Your issue is duplicate of below thread
POSIX shared memory and semaphores permissions set incorrectly by open calls
mode_t old_umask = umask(0);
int fd = shm_open("somekey", O_CREAT | O_RDWR, S_IRWXU | S_IRWXG);
// restore old
umask(old_umask);
http://man7.org/linux/man-pages/man3/shm_open.3.html
the object's permission bits are set according to the low-order 9 bits of mode, except that those bits set in the process file mode creation mask (see umask(2)) are cleared for the new object.
It's just umask doing its job, the same way it does for a regular open.

linux file permission and different user privilege

root user creates one file which has 664 permission.
[root]# ls -trl
total 4
-rw-rw-r-- 1 root root 0 Dec 9 08:40 mnode-log-ipc-shm_5500
I am going to use the admin level to open the file with opening mode WRITE/READ , here is my test code.
int main(int argc, char **argv)
{
char filename[] = "./mnode-log-ipc-shm_5500";
if ( argc < 2 )
{
printf("./mytest id\n");
return 0;
}
int gid = atoi(argv[1]);
printf("%d gid\n", gid);
if ( -1 == setgid(gid))
printf("set gid to error:%s\n", strerror(errno));
if ( -1 == setuid(gid))
printf("set uid to error:%s\n", strerror(errno));
int fds = open(filename, O_RDWR);
if(fds < 0)
{
printf("failed with uid:%d gid:%d\n", getuid(), getgid());
fprintf(stderr, "open error %s\n", strerror(errno));
}
else
{
printf("sucess with uid:%d gid:%d\n", getuid(), getgid());
close(fds);
}
while (1)
sleep(1);
return 0;
}
my expectation is open API will return error, because the file for other user permission just is readable, but the real result is admin user can open it. I am confusing about this, could someone explain to me why?
[root#0-3 ~]# ./mytest 501
501 gid
sucess with uid:501 gid:501
[root#0-3 ~]# ps -ef | grep mytest
admin 1337 13006 0 05:04 pts/1 00:00:00 ./mytest 501
root 1454 1344 0 05:04 pts/0 00:00:00 grep mytest
You need to understand linux file permission.
For file mnode-log-ipc-shm_5500, it has read, write permission to user root and group root, and only have read permission to others. Therefore, admin does not have enough permission to write content to the file.
There are many approaches to enable write access:
Gain higher permission (write permission) to other users, execute this command chmod o+w mnode-log-ipc-shm_5500
Change the owner to admin, execute this command chown admin mnode-log-ipc-shm_5500
Use access control list, which is more advance approach in this case
See more: Execute vs Read bit. How do directory permissions in Linux work?

Pipeline management in linux shell

i'm currently looking how pipelining is managed into shells.
for example, in my shell, if i enter "ls | wc | less". The result of this operation will be the creation of three process, ls wc and less.
Ouput of ls will be piped to the enter input of wc, and the ouput of wc will be piped to the enter intput of less.
For me, it means that during the execution of "ls | wc | less". The standard input of less will not be the keyboard, but the ouput of wc. But, less will still be responsive to my keyboard. Why ? I don't understand, because for me, less should not be sensitive to the keyboard since it have been piped.
Do somebody have an idea ?
Thanks
The code from less
#if HAVE_DUP
/*
* Force standard input to be the user's terminal
* (the normal standard input), even if less's standard input
* is coming from a pipe.
*/
inp = dup(0);
close(0);
#if OS2
/* The __open() system call translates "/dev/tty" to "con". */
if (__open("/dev/tty", OPEN_READ) < 0)
#else
if (open("/dev/tty", OPEN_READ) < 0)
#endif
dup(inp);
#endif
It opens a direct stream from /dev/tty as well as whatever your stdin is.
Just a guess - less is opening /dev/console for the interactive session, I used that trick once. I was wrong - strace is your friend :-):
echo | strace less
) = 16
read(0, "\n", 8192) = 1
write(1, "\n", 1
) = 1
read(0, "", 8191) = 0
write(1, "\33[7m(END)\33[27m\33[K", 17(END)) = 17
read(3,
As you can see, less is reading from FD 3.
/* Standard file descriptors. */
#define STDIN_FILENO 0 /* Standard input. */
#define STDOUT_FILENO 1 /* Standard output. */
#define STDERR_FILENO 2 /* Standard error output. */
And a closer look (after 'q') shows:
open("/dev/tty", O_RDONLY) = 3
Which confirms #123's source code inspection - it opens /dev/tty.

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