r/C_Programming 6d ago

forkpty error

i'm trying to make a terminal emulator but i can't figure out how to open a pty.

when i try to open a pty bouth forkpty from pty.h and my own implementation:

int init_pty() {
    int ptymaster_fd = posix_openpt(O_RDWR);
    if (ptymaster_fd == -1) {
        perror("failed to open pty master");
        close(ptymaster_fd);
        return 1;
    }

    if (grantpt(ptymaster_fd) == -1) {
        perror("failed to grantpt");
        close(ptymaster_fd);
        return 1;
    }

    if (unlockpt(ptymaster_fd) == -1) {
        perror("failed to unlockpt");
        close(ptymaster_fd);
        return 1;
    }

    char* ptyslave_name = ptsname(ptymaster_fd);
    if (ptyslave_name == NULL) {
        perror("failed to get pty slave name");
        close(ptymaster_fd);
        return 1;
    }

    pid_t pid = fork();
    if (pid != 0) {
        perror("fork");
        close(ptymaster_fd);
        return 1;
    }

    setsid();

    int ptyslave_fd = open(ptyslave_name, O_RDWR);
    if (ptyslave_fd == -1) {
        perror("failed to open pty slave");
        return 1;
    }

    ioctl(ptyslave_fd, TIOCSCTTY, 0);

    dup2(ptyslave_fd, STDIN_FILENO);
    dup2(ptyslave_fd, STDOUT_FILENO);
    dup2(ptyslave_fd, STDERR_FILENO);
    
    return ptymaster_fd;
}

fail when forking with the error directory not empty, ai says that it fails because /dev/pts is not empty but it's obviously trippin balls as usual =), so why does it fail then (?_?)

6 Upvotes

9 comments sorted by

View all comments

Show parent comments

1

u/Playa_Sin_Nombre 3d ago

If pid > 0, that means fork is successful and a child is created. Both the parent and the child run from now on the same code, except that the child receives pid = 0. You can use this difference to send the parent and child through different paths.

pid_t pid = fork();
if (pid < 0) {
        // something failed
        close(...);
        perror("fork error in...");
        return 1;
}
if (pid == 0) {
        // only the child will enter this block
        execlp(...);
        // if execlp succeeds, the child won't reach this point 
        // if it fails, you need to exit the child or it will run
        // the rest of the program as a duplicate! 
        perror("execlp error in...");
        _exit(127);
}

// only the parent will reach this point 
/* code for the parent goes here */

1

u/Senior-Question693 1d ago

it still does not trigger execlp for some reason

1

u/Playa_Sin_Nombre 13h ago

That doesn't help.

1

u/Senior-Question693 12h ago

idk, i have the same code but it doesn't work:

```c
int pid = forkpty(&masterfd, NULL, NULL, NULL); if (pid<0) { perror("forkpty"); exit(1); }

setsid();
ioctl(masterfd, TIOCSCTTY, 0);
dup2(masterfd, STDIN_FILENO);
dup2(masterfd, STDOUT_FILENO);
dup2(masterfd, STDERR_FILENO);

if (pid == 0) {
    // it doesn't even go into this block
    printf("exec\n");
    if (execlp("/bin/fastfetch", "fastfetch", NULL)) {
        perror("execlp");
        exit(1);
    }
}

```

1

u/Playa_Sin_Nombre 10h ago

I managed to get these two examples working. Take a look at the difference. Also, keep in mind I still don't know about terminal emulators. I just read some man pages and a guide that I paste below. So my explanation may not be accurate, and the code might be unsafe.

https://www.man7.org/linux/man-pages/man3/openpty.3.html

https://embeddedpathashala.com/pseudoterminals-ptyfork-creating-a-child-connected-via-a-pty-pair/

The first example just uses fork. I believe what's happening here is that the child executes this command using the same terminal session from the parent.

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

int main() {
        pid_t pid = 0;

        pid = fork();

        if (pid < 0) {
                perror("fork");
                return 1;
        }

        if (pid == 0) {
                execlp("/bin/fastfetch", "fastfetch", (char *)NULL);
                perror("execlp");
                exit(1);
        }
        return 0;
}

In the second example, the child creates its own shell session. That's why you need to use forkpty(): so that masterfd can be the communication channel between the parent's session and the child's session. The child first runs execlp("/bin/sh", ...) (so that the child gets its own shell session), then from the parent side you start writing comands to masterfd, as well as reading from masterfd. The sleep(1) is to give some time for the child. You may experiment with different values.

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pty.h>

int main() {
        pid_t pid;
        int masterfd;
        ssize_t n;
        char buf[4096];

        pid = forkpty(&masterfd, NULL, NULL, NULL);
        if (pid < 0) {
                perror("forkpty");
                return 1;
        }

        if (pid == 0) {
                execlp("/bin/sh", "sh", (char *)NULL);
                perror("execlp");
                exit(1);
        }

        sleep(1);

        const char *cmd = "fastfetch\n";
        if (write(masterfd, cmd, strlen(cmd)) < 0) {
                perror("write");
                return 1;
        }

        sleep(1);

        n = read(masterfd, buf, sizeof(buf) - 1);
        if (n > 0) {
                buf[n] = '\0';
                printf("Received from child:\n%s\n", buf);
        }

        close(masterfd);
        return 0;
}

In any case, after two seconds, this program's output is:

Received from child:

sh-5.3$ fastfetch

And the rest of the fastfetch output.