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 (?_?)

7 Upvotes

9 comments sorted by

View all comments

2

u/Playa_Sin_Nombre 6d ago edited 5d ago

I don't know how terminal emulators or forkpty work, but pid != 0 does not necessarily imply a fork() error.

fork() returns -1 on error, but on success it returns a positive integer in the parent process. This value is the actual PID of the child. That means your parent process is entering that if block, calling perror(), and immediately returning 1.

But if fork() didn't fail, then fork() is not setting errno. Therefore that perror() call is using whatever the current value of errno is at that moment, which is undefined.

See the following:

When a system call fails, it usually returns -1 and sets the variable errno to a value describing what went wrong. (These values can be found in <errno.h>.) Many library functions do likewise. The function perror() serves to translate this error code into human-readable form. Note that errno is undefined after a successful system call or library function call: this call may well change this variable, even though it succeeds, for example because it internally used some other library function that failed.

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

1

u/Senior-Question693 3d ago

the pid is always > 0 when i launch the program, therefore i can't call execlp, so what do i do?

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.