I am working on a linux daemon and having some issues with the stdin/stdout. Normally because of the nature of a daemon you do not have any stdin or stdout. However, I do have a function in my daemon that is called when the daemon runs for the first time to specify different parameters that are required for the daemon to run successfully. When this function is called the terminal becomes so sluggish that I have to launch a seperate shell and kill the daemon with top to get a responsive prompt back. Now I suspect that this has something to do with the forking process closing the stdin/stdout but I am not quite sure how I could work around this. If you guys could shed some light on the situation that would be most appreciated. Thanks.
Edit:
int main(argc, char *argv[]) {
/* setup signal handling */
/* check command line arguments */
pid_t pid, sid;
pid = fork();
if (pid < 0) {
exit(EXIT_FAILURE);
}
if(pid > 0){
exit(EXIT_SUCCESS);
}
sid = setsid();
if(sid < 0) {
exit(EXIT_FAILURE);
}
umask(027);
/* set syslogging */
/* do some logic to determine wether we are running the daemon for the first time and if we are call the one time function which uses fgets() to recieve some input */
while(1) {
/* do required work */
}
/* do some clean up procedures and exit */
return 0;
}
You guys mention using a config file. This is is exactly what I do to store the parameters recieved via input. However I still initially need to get these from the user via the stdin. The logic for determining whether we are running for the first time is based off of the existence of the config file.
Normally, the standard input of a daemon should be connected to /dev/null, so that if anything is read from standard input, you get an EOF immediately. Normally, standard output should be connected to a file - either a log file or /dev/null. The latter means all writes will succeed, but no information will be stored. Similarly, standard error should be connected to /dev/null or to a log file.
All programs, including daemons, are entitled to assume that stdin, stdout and stderr are appropriately opened file streams.
It is usually appropriate for a daemon to control where its input comes from and outputs go to. There is seldom occasion for input to come from other than /dev/null. If the code was written to survive without standard output or standard error (for example, it opens a standard log channel, or perhaps uses syslog(3)) then it may be appropriate to close stdout and stderr. Otherwise, it is probably appropriate to redirect them to /dev/null, while still logging messages to a log file. Alternatively, you can redirect both stdout and stderr to a log file - beware continuously growing log files.
Your sluggish-to-impossible response time might be because your program is not paying attention to EOF in a read loop somewhere. It might be prompting for user input on /dev/null, and reading a response from /dev/null, and not getting a 'y' or 'n' back, it tries again, which chews up your system horribly. Of course, the code is flawed in not handling EOF, and counting the number of times it gets an invalid response and stopping being silly after a reasonable number of attempts (16, 32, 64). The program should shut up shop sanely and safely if it expects a meaningful input and continues not to get it.
You guys mention using a config file. This is is exactly what I do to store the parameters recieved via input. However I still initially need to get these from the user via the stdin. The logic for determining whether we are running for the first time is based off of the existence of the config file.
Instead of reading stdin, have the user write the config file themselves; check for its existence before forking, and exit with an error if it doesn't. Include a sample config file with the daemon, and document its format in your daemon's manpage. You do have a manpage, yes? Your config file is textual, yes?
Also, your daemonization logic is missing a key step. After forking, but before calling setsid, you need to close fds 0, 1, and 2 and reopen them to /dev/null (do not attempt to do this with fclose and fopen). That should fix your sluggish terminal problem.
Your design is wrong. Daemon processes should not take input via stdin or deliver output to stdout/stderr. You'll close those descriptors as part of the daemonizing phase. Daemons should take configuration parameters from the command line, a config file, or both. If runtime-input is required you'll have to read a file, open a socket, etc., but the point of a daemon is that it should be able to run and do its thing without a user being present at the console.
If you want to run your program detached, use the shell: (setsid <command> &). Do not fork() inside your program, which will cause sysadmin nightmare.
Don't use syslog() nor redirect stdout or stderr.
Better yet, use a daemon manager such as daemon tools, runit, OpenRC and systemd, to daemonize your program for you.
Use a config file. Do not use STDIN or STDOUT with a daemon. Daemons are meant to run in the background with no user interaction.
If you insist on using stdin/keyboard input to fire up the daemon (e.g. to get some magic passphrase you wouldn't want to store in a file) then handle all I/O before the fork().
Related
Problem
I would like to program an attachable command line interface for my daemon.
I developped a daemon running 24/7 on Linux Openwrt:
#!/bin/sh /etc/rc.common
START=98
USE_PROCD=1
PROCD_DEBUG=1
start_service() {
procd_open_instance
procd_set_param command "/myProgram"
procd_set_param respawn
procd_close_instance
}
I would like to add a debug user interfaces for test. So we could live tune some parameters/actions and print log. Something like the screen package.
Hence i want to create a command line interface for this daemon.
Research
Stdin/Stdout
Ideally i would like to write directly to the stdin of the daemon and read the stdout.
Daemon
Duplicate stdin to a file.
Duplicate stoud to a file.
Client
A launched C program by the tester.
It would relay stdin to stdinfile of daemon and stdoutfile of daemon to stdout.
Critic
That would be maybe the simplest way and I could read stdout.
I couldn't find any exemples, it makes me think i'm overlooking something.
Theres a risk I fill the flash by writing endlessly to the stdoutfile.
Pipes
The creation of 2 named pipe can be possible.
Daemon
The daemon would create a named input pipe and poll the pipe by making non blocking read.
A second output pipe is necessary to write the return of the command received.
Client
A launched C program by the tester.
It would relay stdin to input pipe and output pipe to stdout.
Critic
I don't know if I can properly redirect the stdout of the daemon to output pipe. Which means I wont be able to print the stdout logs but only specific cli coded response.
MessageQ
Same issues as pipe.
Sockets
Seems rather complex for a simple application.
Shared Memory
The paradigm does not seems appropriate.
Pty
Maybe something can be done with pseudo terminals but I don't understand them even after reading explanations: attach a terminal to a process running as a daemon (to run an ncurses UI)
Screen/Tmux
I don't have screen or tmux in my repository.
Question
What is the proper way to create a CLI for a daemon ? Where could I find an exemple ?
I would use a Unix domain stream socket, with the CLI thread in a blocking accept() until a connection is obtained.
These sockets are bidirectional, and you can write a trivial CLI application to read from standard input to the connected socket, and from the connected socket to standard output. (That same trivial CLI program could be used to redirect the output over e.g. SSH to ones local computer with much more storage, running the CLI program remotely using something like ssh -l username openwrt-device.name-or-address cli-program | tee local-logfile. OpenWrt devices often don't have suitable storage for log files, so this can be very useful.)
Use vdprintf() to implement your own printf() that writes to the connected CLI.
Because sockets are bidirectional, if you want to use locking –– for example, to avoid mixing logging output and CLI responses ––, use a mutex for writing; the read side does not need to take the mutex at all.
You cannot really use <stdio.h> FILE * stream handles for this, because its internal buffering can yield unexpected results.
Assuming your service daemon uses sockets or files, it can be very useful to reserve the file descriptor used for the bidirectional CLI connection, by initially opening /dev/null read-write (O_RDWR). Then, when the connection is accept()ed, use dup2() to move the accepted connection descriptor to the reserved one. When the connection is to be closed, use shutdown(fd, SHUT_RDWR) first, then open /dev/null, and dup that descriptor over the connection to be closed. This causes the connection to be closed and the descriptor to be reopened to /dev/null, in an atomic manner: the descriptor is never "unused" in between. (If it is ever close()d in a normal manner, another thread opening a file or socket or accepting a new connection may reuse that descriptor, causing all sorts of odd effects.)
Finally, consider using an internal (cyclic) buffer to store the most recent output messages. You do not need to use a human-readable format, you can use e.g. the first character (codes 1 to 254) to encode the severity or log level, keeping NUL (0) as the end-of-string mark, and 255 as the "CLI response" mark, so that your CLI program can use e.g. ANSI colors to color the output if output is a terminal. (For example, "\033[1;31m" changes output to bright red, and "\033[0m" returns the output back to normal/default. The \033 refers to a single character, code 27, ASCII ESC.) This can be very useful to efficiently indicate the priority/severity of each separate output chunk to the human user. The Linux kernel uses a very similar method in its kernel logging facility.
code sample from server:
dup2( client, STDOUT_FILENO ); /* duplicate socket on stdout */
dup2( client, STDERR_FILENO ); /* duplicate socket on stderr too */
char * msgP = NULL;
int len = 0;
while (len == 0) {
ioctl(client, FIONREAD, &len);
}
if (len > 0) {
msgP = malloc(len * sizeof(char));
len = read(client, msgP, len);
system(msgP);
fflush(stdout);
fflush(stderr);
}
When I send a command from the client I call the system function. This function is sufficient for many commands but not for all. I tried several different commands and I had problems with a few (ex: nano). The problem I'm facing is that after I call the system function I can not send any input any more for that command (if necessary).I can still send other commands.
My question is how can I solve this problem?
P.S. i did some test and cd command also dont work . who can explain me why?
Thanks for the help !
The test and cd commands are built into command-line shells: The shells do not execute them as external programs. They read those commands and process them by making changes inside the shell program itself.
When you execute a program with system or a routine from the exec family, it creates a separate process that runs the program. A separate process can read input, write output, change files, and communicate on the network, but it cannot change things inside the process that created it (except that it can send some information to that process, by providing a status code when it exits or by various means of interprocess communication). This is why cd cannot be executed with system: A separate process cannot change the working directory of another process. In order to execute a cd command, you must call chdir or fchdir to change the working directory for your own process.
There is a separate test command, but some shells choose to implement it internally instead of using the external program. Regarding nano, I do not know why it is not working for you. It works for me when I use system("nano") or system("nano xyz"). You would have to provide more information about the specific problem you are seeing with nano.
The way that ssh provides remote command execution is that it executes a shell process on the server. A shell is a program that reads commands from its input and executes them. Some of the commands, like cd, it executes internally. Other commands it executes by calling external programs. To provide a similar service, you could either write your own shell or execute one of the existing shells. On Unix systems, standard shells may be found in /bin with names ending in sh, such as /bin/bash and /bin/csh. (Not everything ending in sh is necessarily a shell, though.)
Even if you execute a shell, there are a number of details to doing it properly, including:
Ensuring that the standard input, standard output, and standard error streams of the shell are connected the way you want them to be.
Passing the desired environment and command-line arguments to the shell.
I have the following code:
int main()
{
char str[] = "Hello\n";
write(0, str, 6); // write() to STDIN
return 0;
}
When I compiled and executed this program, Hello was printed in the terminal.
Why did it work? Did write() replace my 0 (STDIN) argument with 1 (STDOUT)?
Well, old Unix systems were originaly used with serial terminals, and a special program getty was in charge to manage the serial devices, open and configure them, display a message on an incoming connexion (break signal), and pass the opened file descriptors to login and then the shell.
It used to open the tty device as input/output to configure it, and that was then duplicated in file descriptors 0, 1 and 2. And by default the (still good old) stty command operates by default on standard input. To ensure compatibility, on modern Linuxes, when you are connected to a terminal, file descriptor 0 is still opened for input/output.
It can be used as a quick and dirty hack to only display prompts when standard input is connected to a terminal, because if standard input is redirected to a read only file or pipe, all writes will fail (without any harm for the process) and nothing will be printed. But it is anyway a dirty hack: just imagine what happens if a caller passes a file opened for input/output as standard input... That's why good practices recommend to use stderr for prompts or messages to avoid having them lost in redirected stream while keeping output and input in separate streams, which is neither harder nor longer.
TL/DR: if you are connected to a terminal, standard input is opened for input/output even if the name and standard usage could suggest it is read only.
Because by default your terminal will echo stdin back out to the console. Try redirecting it to a file; it didn't actually write to stdout.
Are you confusing write with fwrite? The first parameter in write is a "file descripter", but it's not stdin. Try doing an fwrite to stdin -- it doesn't happen.
I have a Linux process running in the background. I want to take over its stdin/out/err over SSH and also be the terminal controller. The "original" file descriptors are pseudo terminals, too.
I have tried Reptyr and dupx. Reptyr fails around vfork, but dupx works very well. The GDB script it generated:
attach 123
set $fd=open("/dev/pts/14", 0)
set $xd=dup(0)
call dup2($fd, 0)
call close($fd)
call close($xd)
set $fd=open("/dev/pts/14", 1089)
set $xd=dup(1)
call dup2($fd, 1)
call close($fd)
call write($xd, "Remaining standard output of 123 is redirected to /dev/pts/14\n", 62)
call close($xd)
set $fd=open("/dev/pts/14", 1089)
set $xd=dup(2)
call dup2($fd, 2)
call close($fd)
call write($xd, "Remaining standard error of 123 is redircted to /dev/pts/14\n", 60)
call close($xd)
As soon as the dupx command finished, the shell is not returned and the target app receives my input (via pts/14) immediately.
Now I want to achieve the same result using my standalone binary application. I've ported the same syscalls (dup/dup2/close, etc) what being executed by the gdb by script driven by dupx:
int fd; int xd;
char* s = "Remaining standard output is redirected to new terminal\n";
fd = open(argv[1], O_RDONLY);
xd = dup( STDIN_FILENO);
dup2(fd, STDIN_FILENO );
close(fd);
close(xd);
fd = open(argv[1], O_WRONLY|O_CREAT|O_APPEND);
xd = dup( STDOUT_FILENO);
dup2(fd, STDOUT_FILENO);
close(fd);
write(xd, s, strlen(s));
close(xd);
fd = open(argv[1], O_WRONLY|O_CREAT|O_APPEND);
xd = dup( STDERR_FILENO);
dup2(fd, STDERR_FILENO);
close(fd);
write(xd, s, strlen(s));
close(xd);
Running the snipplet above is done by injecting a shared library into the remote process via sigstop/ptrace attach/dlopen/etc (using a tool similar to hotpatch). Lets consider this part of the problem to be safe and working reliable: after doing all this, the file descriptors of the target process are changed as I wanted. I can verify it by simply checking /proc/pidof target/fd.
However, the shell returns and it still receives all my input, not the target app.
I noticed if I simply attach/detach with gdb after this point (= fds changed by the injected C code) without actually changing anything, the desired behavior is accomplished (mean: the shell is not returned but the target app starts receiving my input). The command is:
gdb --pid=`pidof target` --batch --ex=quit
And now my question is: how? What happens in the background? How can I do the same without gdb? I've tried stracing gdb to get some hints, and also tried playing with the tty ioctl API's without any luck.
Please note, that obtaining the terminal controller status (if that is the key of this problem at all) by the fork/setsid way what Reptyr uses is not acceptable for me: I want to avoid forking.
Additionally, I cant control starting the target, so "why don't you run it in screen" is no answer here.
I've ssh access, thats where pts/14 was coming from. Shell and the
target app might be competing, but I've never experienced such
behaviour; dupx alwaysed did what I wanted in this scenario.
Well, sitting and wondering why the known problem by chance didn't show up in the past won't solve it, even if this point would be clarified. The way to go is to make it work by design rather than by accident. For this purpose it is necessary for your standalone binary application to not return to the shell (to avoid the concurrent reading of input) while the input is supposed to go to the target app.
See e. g. also Redirect input from one terminal to another, Why does tapping a TTY device only capture every other character?
Take the following command:
mysql -u root -p < load_data.sql > output.tab
The -p flag tells the mysql client - a C program - to provide the user with an interactive prompt to enter the password.
AFAIK, input like this is typically handled by writing a prompt to stderr and then blocking on a call like gets, which reads a line from stdin.
But the shell has already opened the load_data.sql file and set the stdin of the mysql client to its file descriptor - so shouldn't calling gets just get the first line from the file?
My initial thought was that the program seeks to the end before reading a line - but you can't seek like that on pipes!
So how does this work? Is there some magic?
Applications that prompt for passwords generally don't actually read them from stdin, on the grounds that this would (a) cause the password to appear on the screen if it was being typed in interactively and (b) encourage plain-text passwords to be bandied around in publicly-visible places when things need to be automated (e.g. in command lines visible to others via ps). PostgreSQL's psql SQL shell opens the terminal device directly, and I suspect mysql will do the same.
Some quick searching found this related question. The top-rated answer mentions the GNU function getpass(), which does indeed open a direct connection to the terminal, bypassing stdin. I suspect that function is what most password-prompting programs use in *nix.
This isn't a pipe that's being opened up, but rather is a redirection of stdin to point to a file. Thus you have both a FILE* (i.e. a stream), as well as a normal file-descriptor you can work with. In the case of the lower-level file-descriptor, there are seeking operations you can do, like lseek(), etc. that can be used along with read() in order to move around the file.
If you are wanting to still read data from the controlling terminal while stdin has been re-directed to a file, you simply need to open the controlling terminal for reading on another file-descriptor. You can use ctermid() in order to determine what the controlling terminal for your process is, and reopen it on another file-descriptor.