Why the tcsetpgrp() call does not work as expected? - c

Looking for a snippet of code showing the use of a tcsetpgrp() call, I came across https://www.ibm.com/support/knowledgecenter/SSLTBW_2.3.0/com.ibm.zos.v2r3.bpxbd00/rttcsp.htm, where the code of CELEBT10.c is shown.
When executing the code I get
original foreground process group id of stdout was 59741
now setting to 59742
then the program stops.
With ps -aj I see the group change (setpgid()) works correctly.
In fact, when I send the child a SIGCONT signal, the child executes the remaining part and exit (together with the waiting parent).
Adding a sleep() after tcsetpgrp(), ps -aj also shows the parent’s group is still the foreground one. That is, the tcsetpgrp() call fails.
Can somebody explain why the child stops in the tcsetpgrp() call and why it fails?

This is because of SIGTTOU generated by attempting tcsetpgrp from a background process, as noted in the manual:
If tcsetpgrp() is called from a background process group against the caller's controlling terminal, a SIGTTOU signal may be generated depending how the process is handling SIGTTOUs:
You can see this by running `strace -f ./a.out` and observing the output of the child process (`-f` means follow forks):
[pid 4062] setpgid(4062, 0) = 0
[pid 4062] write(1, "now setting to 4062\n", 20now setting to 4062
) = 20
[pid 4062] ioctl(1, TIOCSPGRP, [4062]) = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
[pid 4062] --- SIGTTOU {si_signo=SIGTTOU, si_code=SI_KERNEL} ---
The tcsetpgrp() is translated by the library to an ioctl, and we can see what's up.
Copying the pointed-to code here:
/* CELEBT10
*
* This example changes the PGID.
*
* */
#define _POSIX_SOURCE
#include <termios.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <stdio.h>
#include <signal.h>
int main() {
pid_t pid;
int status;
if (fork() == 0)
{
// signal(SIGTTOU, SIG_IGN); // UNCOMMENT ME
if ((pid = tcgetpgrp(STDOUT_FILENO)) < 0)
perror("tcgetpgrp() error");
else {
printf("original foreground process group id of stdout was %d\n",
(int) pid);
if (setpgid(getpid(), 0) != 0)
perror("setpgid() error");
else {
printf("now setting to %d\n", (int) getpid());
if (tcsetpgrp(STDOUT_FILENO, getpid()) != 0)
perror("tcsetpgrp() error");
else if ((pid = tcgetpgrp(STDOUT_FILENO)) < 0)
perror("tcgetpgrp() error");
else
printf("new foreground process group id of stdout was %d\n", (int) pid);
fflush(stdout);
}
}
}
else wait(&status);
}
See the note "UNCOMMENT ME" and it will allow the function to continue:
$ ./a.out
original foreground process group id of stdout was 4070
now setting to 4071
new foreground process group id of stdout was 4071
It's been ages since I had to do this so I'm fuzzy on the rationale, but I believe the idea is that background process ought not write to the terminal and mess up whatever the foreground process is doing. Many times code that puts itself in the background redirects its input/output to detach itself from the foreground terminal.
By catching (or ignoring) the signal, the code is making its intentions explicit, but I'm not sure a casual "just ignore the signal" is automatically the right answer; we'd need to understand how this code fit into the bigger picture.

Related

Signals not working as intended (C language, linux)

I tried to answer this question:
Write a program C that creates two children. The second child process
is blocked until the reception of the signal SIGUSR1 sent from the
parent process. While the first child process is blocked until the
reception of the signal SIGUSR2 (that will kill him) sent from the
second child process. The parent is terminated after the termination
of his children.
However the execution is not working as intended with my code below, and only the parent printfs are displayed. Can you tell me what's wrong with my code?
My code:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <signal.h>
void this(int sig) {
printf("this is this");
}
int main() {
int pid = fork();
int pid2;
if (pid < 0) {
exit(-1);
} else if (pid == 0) {
printf("FIrst child is paused");
pause();
printf("ERror");
} else {
pid2 = fork();
if (pid2 < 0) {
exit(-2);
} else if (pid2 == 0) {
signal(SIGUSR1, &this);
printf("Second child is paused");
pause();
kill(pid,SIGUSR2);
printf("signal sent to first child");
} else {
printf("this is the parent");
kill(pid2, SIGUSR1);
printf("signal sent to second child");
wait(NULL);
exit(-3);
}
}
}
You make no provision to ensure that the parent's signal is delivered to the second child only when that child is ready for it. Because process startup takes some time, chances are good that the signal is indeed delivered sooner. In that case, the second child will be terminated (default disposition of SIGUSR1) or it will block indefinitely in pause() (if the signal is received after the handler is installed but before pauseing). In neither case will the second child signal the first.
Signal masks and signal dispositions are inherited across a fork, so you can address that by blocking SIGUSR1 in the parent before forking, and then using sigsuspend() in the child instead of pause(), which will enable you to atomically unblock the signal and start waiting for it.
The same is not an issue for the first child because you're looking for it to exercise the default disposition for SIGUSR2 (termination), and it does not matter for the specified behavior whether that happens before that child reaches or blocks in pause().
Additionally,
the parent waits only for one child, but the prompt seems to say that it must wait for both. Perhaps you dropped the second wait() because the parent was not terminating, but if so, that was a missed clue that one of the children was not terminating.
printf is not async-signal-safe, so calling it from a signal handler invokes undefined behavior.
you should put a newline at the end of your printf formats. This will make your output much more readable, and it will also ensure that the output is delivered to the screen promptly. That could end up being useful as you debug. Alternatively, use puts() instead of printf() since you are outputting only fixed strings. puts() will add a newline automatically.
The absence of newlines probably explains why the first child's output from before it pauses is never printed. If the second child were reaching the indefinite pause state then it would also explain why that child's pre-pause output was not being printed.

Why I don't call "read" after setting other group id?

In this program I change group id of child process.
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void) {
int status;
char b[4];
pid_t pid, ch_pid;
switch(pid=fork()) {
case -1:
perror("Fork failed");
exit(1);
case 0:
printf("\nCHILD: This is child process!\n");
printf("CHILD: My PID is-- %d\n", getpid());
printf("CHILD: My parent PID -- %d\n", getppid());
printf("CHILD: My GID is -- %d\n", getpgid(getpid()));
printf("CHILD: My SID is -- %d\n", getsid(getpid()));
int k = setpgid(getpid(),getpid()); /*Modifies group id. Therefore, when user press
Cn+C, ChPr can't die*/
printf("BEFORE SETPGRP CHILD: My GID is -- %d\n", getpgid(getpid()));
printf("BEFORE SETPGRP CHILD: My SID is -- %d\n", getsid(getpid()));
//read(0,b,4);
//printf("b: %s\n",b);
pause();
exit(0);
default:
printf("PARENT: This is parent process!\n");
printf("PARENT: My PID -- %d\n", getpid());
printf("PARENT: My child PID %d\n",pid);
printf("PARENT: My parent PID %d\n",getppid());
printf("PARENT: My GID %d\n",getpgid(getpid()));
printf("PARENT: My SID %d\n",getsid(getpid()));
pause();
exit(0);
}
return 0;
}
But when I attempt to call "read" (commented strings), bash terminal doesn't read and doesn't output. However, parent process are reading successful. Why? Parent and child process have similar session ID. It's mean that they are controlled from common tty. I noted, if I change GID for child process and press Cntrl+C, parent process interrupted only and child process becomes an orphan. So, if I uncomment "read" in my program and press Cntrl+C it kills both process . May be unsuccessful read-call sends some signal to bash? Thank you!
The terminal has a foreground process group setting. When the shell runs commands, it runs foreground jobs in the terminal's foreground process group, but background jobs are put in their own process group. Only the foreground process group is allowed to read from the terminal. If a background process tries to read, it's suspended; when the user moves it to the foreground, the terminal process group is changed to that group, the process is resumed, and then it will be able to read. There's a stty mode tostop that can be used to control whether a background process can write to the terminal, but there's no similar option for reading, it's always prohibited.
If you want the process to be able to read from the terminal after you change its process group, you need to change the terminal's foreground process group. This is done using the tcsetpgrp() function.
tcsetpgrp(0, getpgid(getpid()));
read(2) is only allowed to processes in terminal foreground process group
(this is the process group associated to the terminal in the terminal driver). Other processes get stopped, signal SIGSTOP is sent to them by the tty driver. Some control characters are directed to the terminal control process group also.
See termios(4) or tty(4) for a description on tty control.

Does tcsetpgrp() succeds when the caller belong to a background process?

According to the POSIX specification, tcsetpgrp can cause SIGTTOU to be sent to the group of the calling process if it is a background process.
However I can't understand if in such case the foreground group is changed.
Also, if the foreground group is actually changed despite the signal generation, I wonder what happens to the session and to the terminal if the new foreground group is the one that is going to receive the SIGTTOU.
TL:DR:
No the foreground group does not change. This makes sense since the signal is supposed to be sent when the process is changing a setting on the terminal -- an output operation. The signal would also not be delivered to the process (now the foreground group) if the change succeeded, because then it could get stuck without someone to send SIGCONT.
Longer answer:
A simple example:
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
void sig(int signo) {
const char* msg = strsignal(signo); // XXX: Not async-signal-safe.
write(STDOUT_FILENO, msg, strlen(msg));
write(STDOUT_FILENO, "\n", 1);
}
int main() {
char cntl_tty[L_ctermid];
ctermid(cntl_tty);
signal(SIGTTOU, sig);
signal(SIGCONT, sig);
int fd = open(cntl_tty, O_RDONLY);
if (fd == -1) {
perror("open");
exit(1);
}
if (tcsetpgrp(fd, getpgrp()) == -1) {
perror("tcsetpgrp");
} else {
puts("foregrounded");
}
return 0;
}
When this code is started as a background process and SIGTTOU is handled, this loops forever printing that the signal is received. The perror is never called, which implies that the kernel restarts the system call. Sending SIGCONT does not matter. The foregrounding never succeeds. However when foregrounding the code through the shell, "foregrounded" is printed as expected.
When the signal disposition for SIGTTOU is changed to SIG_IGN, "foregrounded" is printed immediately.

How do I get tcsetpgrp() to work in C?

I'm trying to give a child process (via fork()) foreground access to the terminal.
After I fork(), I run the following code in the child process:
setpgid(0, 0);
And:
setpgid(child, child);
In the parent process.
This gives the child its own process group. The call to setpgid() works correctly.
Now I want to give the child access to the terminal.
I added the following to the child after the setpgid() call:
if (!tcsetpgrp(STDIN_FILENO, getpid())) {
perror("tcsetpgrp failed");
}
After that, there is an execv() command to spawn /usr/bin/nano.
However, instead of having nano come up, nothing happens, and the terminal looks as if it's expecting user input.
Further, no code seems to execute after the tcsetpgrp() call.
I read somewhere that I need to send a SIGCONT signal to the child process to get it to work. If the process is stopped, how can I do that? Does the parent have to send the signal?
How do I go about sending the SIGCONT signal if that is the solution?
raise(SIGCONT);
Also, I'm not sure if this helps, but the code works fine and spawns nano if I run my program with:
exec ./program
Instead of:
./program
Any ideas? Thanks so much!
Figured it out. I have to ignore any SIGTTOU signals.
I did that by adding:
signal(SIGTTOU, SIG_IGN);
Before the tcsetpgrp() call.
man 3 tcsetpgrp states:
If tcsetpgrp() is called by a member of a background process group in its session, and the calling process is not blocking or ignoring SIGTTOU, a SIGTTOU signal is sent to all members of this background process group.
You need to call tcsetpgrp() in your parent process not in child. However if your parent process started and moved into background it will receive SIGTTOU and will be stopped.
It's the parent rather than child who should invoke tcsetpgrp(). After setpgid() call, the child becomes a background process. A valid case is the foreground group gives up its permission, let another background group become foreground and itself background. A process in background group can't grab controlling terminal. Example code maybe look like:
/* perror_act.h */
#ifndef PERROR_ACT_H
#define PERROR_ACT_H
#define PERROR_ACT(rtn, act) do { \
perror(rtn);\
act; \
} while (0)
#define PERROR_EXIT1(rtn) PERROR_ACT(rtn, exit(1))
#define PERROR_RETN1(rtn) PERROR_ACT(rtn, return -1)
#endif
/* invnano.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include "perror_act.h"
void sig_chld(int chld)
{
exit(0);
}
int main(void)
{
pid_t child;
int p2c[2];
struct sigaction sa = {.sa_handler = sig_chld};
if (sigaction(SIGCHLD, &sa, NULL))
PERROR_EXIT1("sigaction");
if (pipe(p2c))
PERROR_EXIT1("pipe");
if ((child = fork()) < 0)
PERROR_EXIT1("fork");
if (child == 0) {
char buff;
size_t nread;
if (close(p2c[1])) /* We must make sure this fd is closed. The reason is explained in following comments. */
PERROR_EXIT1("close");
if ((nread = read(p2c[0], &buff, 1)) < 0) /* Just to receive a message from parent indicating its work is done. Content is not important. */
PERROR_EXIT1("read");
if (nread == 0) /* When all the write ends of a pipe are closed, a read() to the read end of this pipe will get a return value of 0. We've closed the child's write end so if 0 as returned, we can sure the parent have exited because of error. */
exit(1);
close(p2c[0]);
execlp("nano", "nano", (char *) 0);
PERROR_EXIT1("execlp");
} else {
if (close(p2c[0]))
PERROR_EXIT1("close");
if (setpgid(child, child))
PERROR_EXIT1("setpgid");
if (tcsetpgrp(STDIN_FILENO, child))
PERROR_EXIT1("tcsetpgrp");
if (write(p2c[1], &child, 1) != 1) /* If all the read ends of a pipe are close, a write() to the write end of this pipe will let the calling process receive a SIGPIPE whose default deposition is to terminate. */
PERROR_EXIT1("write");
while (1) /* If parent exit here, login shell will see the news and grab the controlling terminal */
pause();
}
return 0;
}

Test cases in C for WIFSIGNALED, WIFSTOPPED, WIFCONTINUED

I'm playing with waitpid() and signal() and I'm looking for reliable test cases for returning WIFSIGNALED(status) = WIFSTOPPED(status) = WIFCONTINUED (status) = true but can't find any...
Care to tell me how can I make sure those return true so I can debug my code?
Also, a few hints about what signals should I catch with signal() to test those macros would be helpful...
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#define NELEMS(x) (sizeof (x) / sizeof (x)[0])
static void testsignaled(void) {
kill(getpid(), SIGINT);
}
static void teststopped(void) {
kill(getpid(), SIGSTOP);
}
static void testcontinued(void) {
kill(getpid(), SIGSTOP);
/* Busy-work to keep us from exiting before the parent waits.
* This is a race.
*/
alarm(1);
while(1) {}
}
int main(void) {
void (*test[])(void) = {testsignaled, teststopped, testcontinued};
pid_t pid[NELEMS(test)];
int i, status;
for(i = 0; i < sizeof test / sizeof test[0]; ++i) {
pid[i] = fork();
if(0 == pid[i]) {
test[i]();
return 0;
}
}
/* Pause to let the child processes to do their thing.
* This is a race.
*/
sleep(1);
/* Observe the stoppage of the third process and continue it. */
wait4(pid[2], &status, WUNTRACED, 0);
kill(pid[2], SIGCONT);
/* Wait for the child processes. */
for(i = 0; i < NELEMS(test); ++i) {
wait4(pid[i], &status, WCONTINUED | WUNTRACED, 0);
printf("%d%s%s%s\n", i, WIFCONTINUED(status) ? " CONTINUED" : "", WIFSIGNALED(status) ? " SIGNALED" : "", WIFSTOPPED(status) ? " STOPPED" : "");
}
return 0;
}
Handling WIFSIGNALED is easy. The child process can commit suicide with the kill() system call. You can also check for core dumps - some signals create them (SIGQUIT, IIRC); some signals do not (SIGINT).
Handling WIFSTOPPED may be harder. The simple step to try is for the child to send itself SIGSTOP with the kill() system call again. Actually, I think that should work. Note that you may want to check on SIGTTIN and SIGTTOU and SIGTSTOP - I believe they count for WIFSTOPPED. (There's also a chance that SIGSTOP only works sanely when sent by a debugger to a process it is running via the non-POSIX system call, ptrace().)
Handling WIFCONTINUED is something that I think the parent has to do; after you detect a process has been stopped, your calling code should make it continue by sending it a SIGCONT signal (kill() again). The child can't deliver this itself; it has been stopped. Again, I'm not sure whether there are extra wrinkles to worry about - probably.
A framework something like the below will allow you check the results of the wait() and waitpid() calls.
pid_t pid = fork();
if (pid == 0) {
/* child */
sleep(200);
}
else {
/* parent */
kill(pid, SIGSTOP);
/* do wait(), waitpid() stuff */
}
You do not actually have to catch the signals (using signal() or related function) that are sent. signal() installs a handler that overrides the default behavior for the specific signal - so if you want to check for a signal terminating your process, pick one that has that default behavior - "man -s7 signal" will give you details a signal's default behavior.
For the macros you have mentioned use SIGSTOP for WIFSTOPPED(status), SIGCONT for WIFCONTINUED (status) and SIGINT for WIFSIGNALED(status)
If you want more flexibility for testing, you could use kill (see "man kill") to send signals to your process. kill -l will list all the signals that can be sent.
in your tests you can fork() and send specific signal to your child processes? In this scenario your child processes are test cases?
EDIT
my answer is about coding a C test. you fork, get the pid of your child process (the process
with signal handlers installed), then you can send signal to it by using kill(2).
In this way you can test the exit status

Resources