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

16,424

Solution 1

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.

Solution 2

Figured it out. I have to ignore any SIGTTOU signals.

I did that by adding:

signal(SIGTTOU, SIG_IGN);

Before the tcsetpgrp() call.

Solution 3

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;
}
Share:
16,424
SubParProgrammer
Author by

SubParProgrammer

Updated on June 16, 2022

Comments

  • SubParProgrammer
    SubParProgrammer almost 2 years

    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!

  • tyree731
    tyree731 about 13 years
    Why do you need to ignore SIGTTOU? Is this related to the loss of or sharing of terminal control?
  • SubParProgrammer
    SubParProgrammer about 13 years
    Not sure... better than ignoring it, I've decided, is to block it, call tcsetpgrp(), and then unblock it.
  • ajfbiw.s
    ajfbiw.s about 8 years
    Can you expalin why tcsetpgrp() needs to be called in the parent, not the child?
  • ajfbiw.s
    ajfbiw.s about 8 years
    I read that "When you type CTRL-C, your terminal sends a signal to every process inside the foreground process group. You can change which process group is in the foreground of a terminal with “tcsetpgrp(int fd, pid_t pgrp)". Now, do we do this in the child (fork() ==0) or the parent?
  • Ashfaqur Rahaman
    Ashfaqur Rahaman almost 5 years
    How to add the parent process back into foreground process group? After adding the child in foregroung process group parent will become background process and execlp() will never return. Then from where should I call tcsetpgrp() to add parent in foreground process group again?
  • Ashfaqur Rahaman
    Ashfaqur Rahaman almost 5 years
    I have got my answer. I case of parent process after finishing child process I can ignore SIGTTOU signal and then call tcsetpgrp() to add parent process in foreground process group. This is already answered in @John Kurlak solution.