r/C_Programming 5d 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 (?_?)

5 Upvotes

6 comments sorted by

2

u/Playa_Sin_Nombre 5d ago edited 4d 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 2d 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 2d 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 12h ago

it still does not trigger execlp for some reason

1

u/HugoNikanor 5d ago

Your code looks fine. Been a while since I wrote a terminal emulator, but pasting (the relevant parts) of my own working one below.

(termios setup for child is since it's technically a multiplexer and not an emulator, but those are the same thing at the end of the day)

int main() {
    // [...]
    int pt_master, pt_slave;
    {
        pt_master = posix_openpt(O_RDWR|O_NOCTTY|O_CLOEXEC);
        // openpty (&pt_master, &pt_slave, NULL, NULL, NULL);
        if (pt_master == -1) {
            cleanup(0);
        }
        grantpt(pt_master);
        unlockpt(pt_master);
        const char *slave_name = ptsname(pt_master);

        // log_(logger, LOG_INFO, "slave pts: %s", slave_name);

        int pid;
        switch ((pid = fork())) {
            case 0: /* child */
                close(pt_master);
                {
                    struct child_data data = {
                        .tty_name = slave_name,
                        // .tty = pt_slave,
                        .rows = 2 + LINES / 2,
                        .cols = COLS,
                        .logger = logger,
                    };
                    setup_child(&data);
                }
                break;
            case -1:
                log_(logger, LOG_INFO, "Failed forking: %s",
                        strerror(errno));
                cleanup(0);
                break;
            default: /* parent */
                // close (pt_slave);
                break;
        }

        log_(logger, LOG_INFO, "Prompt size: %ix%i",
                COLS, 2 + LINES / 2);
    }
    /* [...] */
}


[[noreturn]] void setup_child(struct child_data *data) {
    int fd = open(data->tty_name, O_RDWR);
    /// int fd = data->tty;

    setsid();

#if 1
    dup2(fd, STDIN_FILENO);
    dup2(fd, STDOUT_FILENO);
    dup2(fd, STDERR_FILENO);
#else

    login_tty(fd);
#endif

    {
        struct termios termios, termios2;
        tcgetattr(fd, &termios);
        cfmakeraw(&termios);
        // termios.c_oflag &= ~OCRNL;
        // termios.c_iflag &= ~ICRNL;
        // termios.c_lflag |= IEXTEN;
        termios.c_lflag |= ECHO | IEXTEN | ECHOE | ECHOK;
        // termios.c_lflag &= ~ECHONL;
        tcsetattr(fd, TCSANOW, &termios);

        tcgetattr (fd, &termios2);

        if (memcmp(&termios, &termios2,
                    sizeof termios) != 0) {
            log_(data->logger, LOG_WARN, "Failed setting termios attributes");
        }
    }

    if (set_winsize(fd, data->rows, data->cols) == -1) {
        log_(data->logger, LOG_WARN, "Failed setting window size: %s",
                strerror(errno));
    }

    // setsid();
    ioctl(fd, TIOCSCTTY, NULL);

    // printf("Hello\n");

    const char *shell = getenv("SHELL");
    if (shell == NULL) {
        shell = "/bin/sh";
    }

    // setenv("TERM", "xterm-mono", 1);

    execl(shell, shell, NULL);
    /* Everything broken, just give up */
    exit(1);
}

0

u/StableNo80 4d ago

dont listen to ai wehn dbg code its so annoying