shm_open not setting group write access - linux

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.

Related

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?

Why using conv=notrunc when cloning a disk with dd?

If you look up how to clone an entire disk to another one on the web, you will find something like that:
dd if=/dev/sda of=/dev/sdb conv=notrunc,noerror
While I understand the noerror, I am getting a hard time understanding why people think that notrunc is required for "data integrity" (as ArchLinux's Wiki states, for instance).
Indeed, I do agree on that if you are copying a partition to another partition on another disk, and you do not want to overwrite the entire disk, just one partition. In thise case notrunc, according to dd's manual page, is what you want.
But if you're cloning an entire disk, what does notrunc change for you? Just time optimization?
TL;DR version:
notrunc is only important to prevent truncation when writing into a file. This has no effect on a block device such as sda or sdb.
Educational version
I looked into the coreutils source code which contains dd.c to see how notrunc is processed.
Here's the segment of code that I'm looking at:
int opts = (output_flags
| (conversions_mask & C_NOCREAT ? 0 : O_CREAT)
| (conversions_mask & C_EXCL ? O_EXCL : 0)
| (seek_records || (conversions_mask & C_NOTRUNC) ? 0 : O_TRUNC));
/* Open the output file with *read* access only if we might
need to read to satisfy a `seek=' request. If we can't read
the file, go ahead with write-only access; it might work. */
if ((! seek_records
|| fd_reopen (STDOUT_FILENO, output_file, O_RDWR | opts, perms) < 0)
&& (fd_reopen (STDOUT_FILENO, output_file, O_WRONLY | opts, perms) < 0))
error (EXIT_FAILURE, errno, _("opening %s"), quote (output_file));
We can see here that if notrunc is not specified, then the output file will be opened with O_TRUNC. Looking below at how O_TRUNC is treated, we can see that a normal file will get truncated if written into.
O_TRUNC
If the file already exists and is a regular file and the open
mode allows writing (i.e., is O_RDWR or O_WRONLY) it will be truncated
to length 0. If the file is a FIFO or terminal device file, the
O_TRUNC flag is ignored. Otherwise the effect of O_TRUNC is
unspecified.
Effects of notrunc / O_TRUNC I
In the following example, we start out by creating junk.txt of size 1024 bytes. Next, we write 512 bytes to the beginning of it with conv=notrunc. We can see that the size stays the same at 1024 bytes. Finally, we try it without the notrunc option and we can see that the new file size is 512. This is because it was opened with O_TRUNC.
$ dd if=/dev/urandom of=junk.txt bs=1024 count=1
$ ls -l junk.txt
-rw-rw-r-- 1 akyserr akyserr 1024 Dec 11 17:08 junk.txt
$ dd if=/dev/urandom of=junk.txt bs=512 count=1 conv=notrunc
$ ls -l junk.txt
-rw-rw-r-- 1 akyserr akyserr 1024 Dec 11 17:10 junk.txt
$ dd if=/dev/urandom of=junk.txt bs=512 count=1
$ ls -l junk.txt
-rw-rw-r-- 1 akyserr akyserr 512 Dec 11 17:10 junk.txt
Effects of notrunc / O_TRUNC II
I still haven't answered your original question of why when doing a disk-to-disk clone, why conv=notrunc is important. According to the above definition, O_TRUNC seems to be ignored when opening certain special files, and I would expect this to be true for block device nodes too. However, I don't want to assume anything and will attempt to prove it here.
openclose.c
I've written a simple C program here which opens and closes a file given as an argument with the O_TRUNC flag.
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
int main(int argc, char * argv[])
{
if (argc < 2)
{
fprintf(stderr, "Not enough arguments...\n");
return (1);
}
int f = open(argv[1], O_RDWR | O_TRUNC);
if (f >= 0)
{
fprintf(stderr, "%s was opened\n", argv[1]);
close(f);
fprintf(stderr, "%s was closed\n", argv[1]);
} else {
perror("Opening device node");
}
return (0);
}
Normal File Test
We can see below that the simple act of opening and closing a file with O_TRUNC will cause it to lose anything that was already there.
$ dd if=/dev/urandom of=junk.txt bs=1024 count=1^C
$ ls -l junk.txt
-rw-rw-r-- 1 akyserr akyserr 1024 Dec 11 17:26 junk.txt
$ ./openclose junk.txt
junk.txt was opened
junk.txt was closed
$ ls -l junk.txt
-rw-rw-r-- 1 akyserr akyserr 0 Dec 11 17:27 junk.txt
Block Device File Test
Let's try a similar test on a USB flash drive. We can see that we start out with a single partition on the USB flash drive. If it get's 'truncated', perhaps the partition will go away (considering it's defined in the first 512 bytes of the disk)?
$ ls -l /dev/sdc*
brw-rw---- 1 root disk 8, 32 Dec 11 17:22 /dev/sdc
brw-rw---- 1 root disk 8, 33 Dec 11 17:22 /dev/sdc1
$ sudo ./openclose /dev/sdc
/dev/sdc was opened
/dev/sdc was closed
$ sudo ./openclose /dev/sdc1
/dev/sdc1 was opened
/dev/sdc1 was closed
$ ls -l /dev/sdc*
brw-rw---- 1 root disk 8, 32 Dec 11 17:31 /dev/sdc
brw-rw---- 1 root disk 8, 33 Dec 11 17:31 /dev/sdc1
It looks like it has no affect whatsoever to open either the disk or the disk's partition 1 with the O_TRUNC option. From what I can tell, the filesystem is still mountable and the files are accessible and intact.
Effects of notrunc / O_TRUNC III
Okay, for my final test I will use dd on my flash drive directly. I will start by writing 512 bytes of random data, then writing 256 bytes of zeros at the beginning. For the final test, we will verify that the last 256 bytes remained unchanged.
$ sudo dd if=/dev/urandom of=/dev/sdc bs=256 count=2
$ sudo hexdump -n 512 /dev/sdc
0000000 3fb6 d17f 8824 a24d 40a5 2db3 2319 ac5b
0000010 c659 5780 2d04 3c4e f985 053c 4b3d 3eba
0000020 0be9 8105 cec4 d6fb 5825 a8e5 ec58 a38e
0000030 d736 3d47 d8d3 9067 8db8 25fb 44da af0f
0000040 add7 c0f2 fc11 d734 8e26 00c6 cfbb b725
0000050 8ff7 3e79 af97 2676 b9af 1c0d fc34 5eb1
0000060 6ede 318c 6f9f 1fea d200 39fe 4591 2ffb
0000070 0464 9637 ccc5 dfcc 3b0f 5432 cdc3 5d3c
0000080 01a9 7408 a10a c3c4 caba 270c 60d0 d2f7
0000090 2f8d a402 f91a a261 587b 5609 1260 a2fc
00000a0 4205 0076 f08b b41b 4738 aa12 8008 053f
00000b0 26f0 2e08 865e 0e6a c87e fc1c 7ef6 94c6
00000c0 9ced 37cf b2e7 e7ef 1f26 0872 cd72 54a4
00000d0 3e56 e0e1 bd88 f85b 9002 c269 bfaa 64f7
00000e0 08b9 5957 aad6 a76c 5e37 7e8a f5fc d066
00000f0 8f51 e0a1 2d69 0a8e 08a9 0ecf cee5 880c
0000100 3835 ef79 0998 323d 3d4f d76b 8434 6f20
0000110 534c a847 e1e2 778c 776b 19d4 c5f1 28ab
0000120 a7dc 75ea 8a8b 032a c9d4 fa08 268f 95e8
0000130 7ff3 3cd7 0c12 4943 fd23 33f9 fe5a 98d9
0000140 aa6d 3d89 c8b4 abec 187f 5985 8e0f 58d1
0000150 8439 b539 9a45 1c13 68c2 a43c 48d2 3d1e
0000160 02ec 24a5 e016 4c2d 27be 23ee 8eee 958e
0000170 dd48 b5a1 10f1 bf8e 1391 9355 1b61 6ffa
0000180 fd37 7718 aa80 20ff 6634 9213 0be1 f85e
0000190 a77f 4238 e04d 9b64 d231 aee8 90b6 5c7f
00001a0 5088 2a3e 0201 7108 8623 b98a e962 0860
00001b0 c0eb 21b7 53c6 31de f042 ac80 20ee 94dd
00001c0 b86c f50d 55bc 32db 9920 fd74 a21e 911a
00001d0 f7db 82c2 4d16 3786 3e18 2c0f 47c2 ebb0
00001e0 75af 6a8c 2e80 c5b6 e4ea a9bc a494 7d47
00001f0 f493 8b58 0765 44c5 ff01 42a3 b153 d395
$ sudo dd if=/dev/zero of=/dev/sdc bs=256 count=1
$ sudo hexdump -n 512 /dev/sdc
0000000 0000 0000 0000 0000 0000 0000 0000 0000
*
0000100 3835 ef79 0998 323d 3d4f d76b 8434 6f20
0000110 534c a847 e1e2 778c 776b 19d4 c5f1 28ab
0000120 a7dc 75ea 8a8b 032a c9d4 fa08 268f 95e8
0000130 7ff3 3cd7 0c12 4943 fd23 33f9 fe5a 98d9
0000140 aa6d 3d89 c8b4 abec 187f 5985 8e0f 58d1
0000150 8439 b539 9a45 1c13 68c2 a43c 48d2 3d1e
0000160 02ec 24a5 e016 4c2d 27be 23ee 8eee 958e
0000170 dd48 b5a1 10f1 bf8e 1391 9355 1b61 6ffa
0000180 fd37 7718 aa80 20ff 6634 9213 0be1 f85e
0000190 a77f 4238 e04d 9b64 d231 aee8 90b6 5c7f
00001a0 5088 2a3e 0201 7108 8623 b98a e962 0860
00001b0 c0eb 21b7 53c6 31de f042 ac80 20ee 94dd
00001c0 b86c f50d 55bc 32db 9920 fd74 a21e 911a
00001d0 f7db 82c2 4d16 3786 3e18 2c0f 47c2 ebb0
00001e0 75af 6a8c 2e80 c5b6 e4ea a9bc a494 7d47
00001f0 f493 8b58 0765 44c5 ff01 42a3 b153 d395
Summary
Through the above experimentation, it seems that notrunc is only important for when you have a file you want to write into, but don't want to truncate it. This seems to have no effect on a block device such as sda or sdb.

linux file attributes

Use open syscall to write and create a file ,there's no attributes with the file. fedora16 gcc-4.6.3
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
char * str= "helloworld";
int fd = open("test.db",O_WRONLY|O_CREAT|O_TRUNC|O_APPEND);
write(fd,str,10);
close(fd);
return 0;
}
ll test.db
----------. 1 jiamo jiamo 14 Apr 17 11:34 test.db
While it don't create file with the default file attributes such like touch test.db
umask : 0002
if drop the O_TRUNC
int fd = open("test1.db",O_WRONLY|O_CREAT|O_APPEND)
the file attributs is :
----rwx---. 1 jiamo jiamo 14 Apr 17 12:29 test1.db
Add the required permissions to the open() syscall:
int fd = open("test.db",O_WRONLY|O_CREAT|O_TRUNC|O_APPEND, 0666);
From the documentation:
mode must be specified when O_CREAT is in the flags, and is ignored otherwise.
The argument mode specifies the permissions to use in case a new file is created.
You need to pass the mode to open. Then it will set the permissions too. open is a variable argument function and you can pass more arguments to it
int open(const char *path, int oflag, ... );
Do something like
open(LOCKFILE, O_WRONLY | O_CREAT | O_EXCL,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
Check the various permission bits here

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

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

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