I am operating in a Fedora environment, using c code to open a USB port to talk with a serial device.
For the most part, my program works fine. However, as part of testing, I regularly open a terminal window and run the screen command and then manually send commands to the serial device. That works fine as well, but afterwards, the port is no longer accessible to the c program. I've closed the screen instance either with ctrl-a k, OR with ctrl-a d followed by the appropriate sudo kill -9 <ID>. Afterwards there seems to be no evidence of the screen instance (sudo lsof /dev/tty* shows no "screen"), however, running my c program fails. As far as I can tell, the open(...) command just hangs. The only way to restore connectivity is to remove and reinsert the USB cable to the device.
So,
Is there a better way to close the "screen" instance than the two I've been using?
Why would "open" not return?
1) Is there a better way to close the "screen" instance than the two I've been using?
That should be irrelevant if your program is made to be independent of any prior configuration and performs its own full initialization.
2) Why would "open" not return?
A printf after the open() never fires when the port is locked up in this mode
An open() syscall for a serial terminal could block if the DCD (Data Carrier Detect) line from a modem is not asserted.
Your program can ignore the state of the DCD line during an open() by specifying the O_NONBLOCK option, e.g:
fd = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY | O_NONBLOCK);
However that option will also put the serial terminal in nonblocking mode, which will force your application to (inefficiently) poll the system for reading data instead of using the preferred event-driven capability.
Your program can revert back to blocking mode by issuing an fcntl() call to clear the non-blocking option, e.g.:
fcntl(fd, F_SETFL, 0);
The above actually clears the five modifiable file-status flags, i.e. O_APPEND, O_ASYNC, O_DIRECT, O_NOATIME, and O_NONBLOCK flags.
The Linux kernel code that blocks the open() of the serial terminal from continuing is the while (1) loop in tty_port_block_til_ready().
Note that if the previous open of the serial terminal had set the CLOCAL termios flag, then no modem is presumed to be connected, and the check for DCD is abandoned.
Related
I am writing a TCP server application that interacts with terminals. It accepts incoming TCP connections directly and then handles I/O all within the process. One requirement is to be able to disable echo when necessary, nothing too out of the ordinary.
I was initially trying to call tcgetattr directly on the TCP socket fd, which failed since you can't call tcgetattr and tcsetattr on sockets, you have to call them on TTY/PTY fds. So I created a pseudoterminal pair and inserted them in the middle. The master fd relays to the TCP socket fd.
Now, I am able to disable canonical mode, but for some reason I can't disable the echo.
I have the following architecture:
[Client] <--- TCP socket ---> [Server] socket fd <---> PTY master <---> PTY slave
This is all in a single process. I'm not doing forkpty type stuff like most PTY examples do. I'm simply launching another thread to handle the PTY master, which relays between the PTY master FD and the socket FD. The actual application thread reads and writes only using the slave FD.
All the examples I see of putting the PTY into "raw" mode (to disable echo, etc.) do so on STDIN_FILENO, which in my case would be where the socket fd is (for example, The Linux Programming Interface, ch. 62-4):
if (ttySetRaw(STDIN_FILENO, &userTermios) == -1)
errExit("ttySetRaw");
These program examples all assume a program is launched from a terminal, but in my case, since my program is the network login service, it's creating a pseudoterminal from scratch and relaying directly to the socket: there's no "STDIN" in the middle anywhere, and this is a multithreaded program so using STDIN would make no sense.
tcsetattr succeeds for term.c_lflag &= ~ECHO; on the slave FD, but clearly this doesn't seem to be sufficient. Since you can't do terminal operations directly on socket FDs, how would I properly disable echo here? If I debug data relayed in the PTY master thread, I'm not seeing input characters relayed back to the socket, but yet I still see them on the terminal. If I try to call tcsetattr on the master FD, this not only doesn't work but also has the effect of re-enabling input buffering, making the problem worse. But these are the only fds I can touch, so I'm confused what else to try here.
Ended up finding that this was due to Telnet's local echo, not anything pseudoterminal-related after all.
This can't be disabled using termios, you have to send Telnet escape sequences to do so:
#include <arpa/telnet.h>
unsigned char echo_ctl[] = {IAC, WILL, TELOPT_ECHO};
write(fd, echo_ctl, 3);
Source: http://www.verycomputer.com/174_d636f401932e1db5_1.htm
Similarly, if using a tool like netcat to test, you'd want to do something like stty -echo && nc 127.0.0.1 23
fd = open("/dev/ttyUSB0",O_RDWR | O_NOCTTY);
/* O_NOCTTY - No terminal will control the process */
Why should we specify or not specify this option? What does it do and doesn't do?
That's answered in the documentation for GNU C library:
If the named file is a terminal device, don’t make it the controlling terminal for the process. See Job Control, for information about what it means to be the controlling terminal.
On GNU/Hurd systems and 4.4 BSD, opening a file never makes it the controlling terminal and O_NOCTTY is zero. However, GNU/Linux systems and some other systems use a nonzero value for O_NOCTTY and set the controlling terminal when you open a file that is a terminal device; so to be portable, use O_NOCTTY when it is important to avoid this.
That is, since the USB serial device is treated like a terminal, use the flag is you don't intend for it to be like a terminal and allow the device to start/stop jobs (e.g., processes) which happen to be connected to it with a given user.
Further reading:
What is the purpose of the controlling terminal?
How does one check if a serial port is already open in Linux using Posix/C functions? I want to check the status of the serial port to check if the serial port is open or not.
I would like to know what methods work for:
Checking the file descriptor to see if the serial port is open and
Checking the serial port file name to see if the the serial port is open which in the example below is "/dev/ttyUSB0"
--
// This code is for example purposes only
int open_port()
{
int fd;
fd = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY | O_NDELAY);
if (fd < 0)
{
perror("open_port: Unable to open /dev/ttyf1 - ");
}
return fd;
}
I imagine there is a "standard" way of doing this, and that is what I am trying to get to.
There are two aspects to the stated question, which really require two different solutions:
An application or service daemon using a serial port should exclude other processes from using the port concurrently.
There are two ways of doing this: exclusive mode, and an advisory lock. An application can choose to do one, or both.
After opening the device, use ioctl(fd, TIOCEXCL) to put the serial port into exclusive mode. Until the descriptor is closed, or the process issues ioctl(fd, TIOCNXCL), any attempt to open the device will fail with EBUSY error code.
After opening the device, use flock(fd, LOCK_EX | LOCK_NB) to try and place an exclusive advisory lock on the open device. Other processes can still open the device normally, and even read and write to it, but trying to place an advisory flock lock on it will fail with EWOULDBLOCK (or block until unlocked or closed if placed without LOCK_NB).
The difference between the above two approaches is that the latter is co-operative, allowing other processes to open the device; while the former disallows further opens.
The reason to use both, is to detect if another process has already opened the device without putting it into exclusive mode, but has hopefully set the advisory lock. In that case, the open() and ioctl() both succeed, but the flock() fails.
(We can use the second aspect of this question discussed below — after having opened the device, set it to exclusive mode, and even obtained the exclusive lock on it —, to detect if other processes have the device open but not exclusive or locked. Personally, I wouldn't bother; I do expect users to only use sane applications on their systems. If they really did that, I'd expect them to have a reason for it, so I'd prefer to allow that oddball scenario. It definitely should not occur with standard utilities.)
An application or daemon can use lsof (from the lsof package) to examine if any process has the specified file or device open.
The idea is to run the equivalent of LANG=C LC_ALL=C lsof -F -p DEVICE with root privileges. The output will contain zero or more lines. The lines beginning with p (and immediately followed with the PID and a newline \n) specify the processes that have DEVICE open. (Each such line is followed by one or more lines starting with f, describing which descriptor in that process refer to the device.)
If the application or daemon does not have root privileges, a helper program that is installed setuid root is needed. It is supplied with one or more device names. The helper program verifies each one is a character device using stat() and S_IFCHR(st_mode) (to avoid the helper being used in security exploits); and if they are, executes the above-mentioned command. In Linux, such helpers are usually installed in /usr/lib/APPLICATION/, where APPLICATION is the name of the application or daemon.
The application or daemon itself can execute the helper via popen("/path/to/helper 2>/dev/null", "r"), and read the input using for example fscanf(). (Remember to use pclose() to obtain the status, and use e.g. (WIFEXITED(status) && !WEXITSTATUS(status)) to verify the command was successful.
Note that the helper program approach allows easier portability to other POSIXy systems from Linux, by simply replacing the helper program with one appropriate to the new system. Even if it is not installed as setuid root, it gives system maintainers an easy hook to modify the behaviour of the application or service, if such need arises. I personally warmly recommend the helper approach.
Though not completely related, in that this answer is specifically about lsof, this is relevant as it provides clues how to use a similar sequence of kernel calls as lsof.
The answer containing LANG=C LC_ALL=C lsof -F -p DEVICE did not work for me as -p means process id, and when lsof queries a char special (i.e. tty) device it seems to block for a long time or hang indefinately.
After a read of the lsof man page:
-O
This option directs lsof to bypass the strategy it uses to avoid being blocked by some kernel operations - i.e., doing them in forked child processes. See the BLOCKS AND TIMEOUTS and AVOIDING KERNEL BLOCKS sections for more information on kernel operations that may block lsof.
While use of this option will reduce
lsof startup overhead, it may also cause lsof to hang when the kernel doesn't respond to a function. Use this option cautiously.
I tried -O and it immediately worked.
So, despite my reading of the man page indicating otherswise, I had success with:
lsof -t -S 2 -O /dev/ttyS0
-t = Terse output (no minimal detail, no headers)
-S 2 = Reduce 15s timeout on blocking kernel calls to 2s
-O = Ask lsof to avoid certain kernel calls that might block
I am using serial port, and while communicating with this, i have to change the configuration using tcsetattr() with TCSDRAIN mode.
TCSADRAIN
The change should take place after all output written to fd has been read by the master pseudoterminal. Use this value when changing terminal attributes that affect output.
And while calling tcsetattr() with TCSDRAIN, if there still remain output data in buffer, Linux seems blocked and check the buffer again after a some interrupt time to change a configuration.
I tested for this like below
First Normall Case
write data to serial
change configuration using tcsetattr()
there is a remaining data in output buffer
the process blocked during regular interval, for example 20ms
wake up.
Second Case
write data to serial
take sleep() manually for 5ms, it means give enough time to clear output to Linux
there is no remaining data
change configuration using tcsetattr()
there is no block
And for me the interrupt time is too long to do what i want
How can i change this interrupt timer interval?
(in Ubuntu and raspberrypi)
I do not think that the .cc[VTIME] field of the termios structure affects the flush timeout, if that's what you are asking. As far as I know, it only affects the read() behaviour.
However, there are several ways you can control the interval the kernel tries to drain/flush, before giving up.
One option is to use fcntl(fd, F_SETFL, O_NONBLOCK) to (temporarily) set the descriptor to non-blocking state, and use clock_gettime(CLOCK_MONOTONIC, &now) and nanosleep() to retry tcsetattr(fd, TCSADRAIN, &attrs) a few times over a suitable time interval, until it succeeds. Then revert the descriptor to normal mode using fcntl(fd, F_SETFL, 0). If the tcsetattr() calls failed with errno == EWOULDBLOCK || errno == EAGAIN, use tcsetattr(fd, TCSANOW, &attrs) to discard all unread/unsent data.
Note that I have not tested the above on RPi! It might not work on some specific architectures/serial port drivers, as it is possible to hardcode the flush/drain interval into the driver and disregard the fact that the descriptor is in non-blocking mode. That would be a bug, however, and is fixable with a kernel patch. (In other words, this should work, but some serial port drivers may be crappy, and ignore the nonblocking nature above, and will block until some unspecified interval.)
Another option is to use a timer to raise a signal. If you install a function to handle that signal without the SA_RESTART flag, the delivery of the signal will interrupt the blocking tcdrain()/tcflush()/tcsetattr() call. (If the process uses multiple threads, the signal should be blocked in all other threads, as otherwise the kernel will just pick one of the process' threads to deliver the signal. If it is not the thread in a blocking call, the blocking call will not be interrupted.)
Because signals are a very reliable method to interrupt most syscalls and library functions (see section Interruption of system calls and library functions by signal handlers in man 7 signal -- termios functions are equivalent to ioctl() calls in Linux), I personally prefer to have a separate thread do nothing but maintain these timeouts. Such a thread is very lightweight, and does not consume CPU time unless a timeout elapses or is added. I can also have multiple concurrent timeouts, with a simple interface to check, cancel, and add timeouts, which makes it easier to design the rest of the application to be robust and efficient.
I want to use named fifo channel and I want to implement a timeout when I write in this fifo.
fd = open(pipe, O_WRONLY);
write(fd, msg, len);
Program is blocked by function open, so using the function select will not work.
Thanks.
use select() and its timeout argument.
Read pipe(7), fifo(7), poll(2)
You might setup a timer or or alarm with a signal handler (see time(7) & signal(7)) before your call to open(2) - but I won't do that - or you could use the O_NONBLOCK flag, since fifo(7) says:
A process can open a FIFO in nonblocking mode. In this case, opening
for read-only will succeed even if no-one has opened on the write
side yet, opening for write-only will fail with ENXIO (no such device
or address) unless the other end has already been opened.
However, you need something (some other process reading) on the other side of the FIFO or pipe.
Perhaps you should consider using unix(7) sockets, i.e. the AF_UNIX address family. It looks more relevant to your case: change your code above (trying to open for writing a FIFO) to a AF_UNIX socket on the client side (with a connect), and change the other process to become an AF_UNIX socket server.
As 5gon12eder commented, you might also look into inotify(7). Or even perhaps D-bus !
I'm guessing that FIFOs or pipes are not the right solution in your situation. You should explain more and give a broader picture of your concerns and goals.