Created
November 10, 2025 09:56
-
-
Save StefanoBelli/5cdd5c40cce462d5968ff2a84978e0fb to your computer and use it in GitHub Desktop.
Example of pseudoterminal device usage
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #define _XOPEN_SOURCE 600 | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <errno.h> | |
| #include <termios.h> | |
| #include <pthread.h> | |
| #include <unistd.h> | |
| #include <wait.h> | |
| #include <fcntl.h> | |
| #include <linux/limits.h> | |
| #include <sys/ioctl.h> | |
| /* globals */ | |
| static pthread_t g_pthr_print; | |
| static pthread_t g_pthr_write; | |
| static struct termios g_orig_tos_stdin; | |
| static struct termios g_my_tos_stdin; | |
| /* atexit cbs for threads */ | |
| static void cancel_print_thread_cb() { | |
| if(pthread_cancel(g_pthr_print) != 0) { | |
| printf("[ pthread_cancel(g_thr_print) failed: %s ]\n", strerror(errno)); | |
| } else { | |
| puts("[ print thread canceled ]"); | |
| } | |
| } | |
| static void cancel_write_thread_cb() { | |
| if(pthread_cancel(g_pthr_write) != 0) { | |
| printf("[ pthread_cancel(g_thr_write) failed: %s ]\n", strerror(errno)); | |
| } else { | |
| puts("[ write thread canceled ]"); | |
| } | |
| } | |
| /* utils for termios (incl. atexit cb for termios restore) */ | |
| static void get_termios_stdin(struct termios *tos) { | |
| if(tcgetattr(STDIN_FILENO, tos)) { | |
| perror("tcgetattr"); | |
| exit(EXIT_FAILURE); | |
| } | |
| } | |
| static void set_termios_stdin(const struct termios *tos) { | |
| if(tcsetattr(STDIN_FILENO, TCSANOW, tos) != 0) { | |
| perror("tcsetattr"); | |
| exit(EXIT_FAILURE); | |
| } | |
| } | |
| static void restore_termios_stdin_cb() { | |
| set_termios_stdin(&g_orig_tos_stdin); | |
| puts("[ restored termios settings for stdin ]"); | |
| } | |
| static void apply_termios_settings() { | |
| get_termios_stdin(&g_orig_tos_stdin); | |
| memcpy(&g_my_tos_stdin, &g_orig_tos_stdin, sizeof(struct termios)); | |
| g_my_tos_stdin.c_lflag = ~(ICANON | ISIG | ECHO | ECHOE | ECHOK | ECHONL); | |
| set_termios_stdin(&g_my_tos_stdin); | |
| atexit(restore_termios_stdin_cb); | |
| puts("[ applied termios settings for stdin ]"); | |
| } | |
| /* obtain master/slave pseudoterminal fds */ | |
| static void getmypt(int *master_fd, int *slave_fd, char* slave_path, size_t pathsize) { | |
| *master_fd = posix_openpt(O_RDWR); | |
| if(*master_fd < 0) { | |
| perror("posix_openpt"); | |
| exit(EXIT_FAILURE); | |
| } | |
| if(unlockpt(*master_fd) != 0) { | |
| close(*master_fd); | |
| perror("unlockpt"); | |
| exit(EXIT_FAILURE); | |
| } | |
| if(grantpt(*master_fd) != 0) { | |
| close(*master_fd); | |
| perror("grantpt"); | |
| exit(EXIT_FAILURE); | |
| } | |
| char *_tmp_slave_path = ptsname(*master_fd); | |
| if(_tmp_slave_path == NULL) { | |
| close(*master_fd); | |
| perror("ptsname"); | |
| exit(EXIT_FAILURE); | |
| } | |
| *slave_fd = open(_tmp_slave_path, O_RDWR); | |
| if(*slave_fd < 0) { | |
| close(*master_fd); | |
| perror("open"); | |
| exit(EXIT_FAILURE); | |
| } | |
| if(slave_path != NULL) { | |
| snprintf(slave_path, pathsize, "%s", _tmp_slave_path); | |
| } | |
| } | |
| /* threads fns */ | |
| struct term_threads_params { | |
| int mfd; | |
| }; | |
| static void *print_prog_stdout(void* args) { | |
| int mfd = ((struct term_threads_params*)args)->mfd; | |
| char chr; | |
| while(read(mfd, &chr, 1) == 1) { | |
| putc(chr, stdout); | |
| fflush(stdout); | |
| } | |
| return NULL; | |
| } | |
| static void *write_prog_stdin(void* args) { | |
| int mfd = ((struct term_threads_params*)args)->mfd; | |
| int ch; | |
| while((ch = getchar()) != EOF) { | |
| char realch = (char) ch; | |
| if(write(mfd, &realch, 1) != 1) { | |
| perror("write"); | |
| } | |
| } | |
| return NULL; | |
| } | |
| /* parent mgmt */ | |
| static void handle_waitpid(int status, pid_t pid) { | |
| if(WIFEXITED(status)) { | |
| printf("[ process %d exited with code %d ]\n", pid, WEXITSTATUS(status)); | |
| exit(EXIT_SUCCESS); | |
| } else if(WIFSTOPPED(status)) { | |
| printf("[ process %d stopped by signal %d ]\n", pid, WSTOPSIG(status)); | |
| } else if(WIFSIGNALED(status)) { | |
| printf("[ process %d exited, being hit by signal %d ]\n", pid, WTERMSIG(status)); | |
| exit(EXIT_SUCCESS); | |
| } else if(WIFCONTINUED(status)) { | |
| printf("[ process %d resumed ]\n", pid); | |
| } | |
| } | |
| static void do_term_things(int master_fd, pid_t child_pid) { | |
| struct term_threads_params p = { .mfd = master_fd }; | |
| if(pthread_create(&g_pthr_print, NULL, print_prog_stdout, &p) != 0) { | |
| perror("pthread_create"); | |
| exit(EXIT_FAILURE); | |
| } | |
| atexit(cancel_print_thread_cb); | |
| puts("[ print thread spawned ]"); | |
| apply_termios_settings(); | |
| if(pthread_create(&g_pthr_write, NULL, write_prog_stdin, &p) != 0) { | |
| perror("pthread_create"); | |
| exit(EXIT_FAILURE); | |
| } | |
| atexit(cancel_write_thread_cb); | |
| puts("[ write thread spawned ]"); | |
| while(1) { | |
| int wstatus; | |
| if(waitpid(child_pid, &wstatus, WUNTRACED | WCONTINUED) == -1) { | |
| perror("waitpid"); | |
| exit(EXIT_FAILURE); | |
| } else { | |
| handle_waitpid(wstatus, child_pid); | |
| } | |
| } | |
| } | |
| /* | |
| * do fork, setup std{in,out,err}, setsid for child and run bash, | |
| * should not be needed, but ensure slave is a controlling terminal, | |
| * this is an important point in order to let the kernel send SIGHUP | |
| * when terminal emulator is killed by the user | |
| * (the user didn't "exit" from the running shell) | |
| */ | |
| static void just_fork(int master_fd, int slave_fd, char** envp) { | |
| pid_t child_pid = fork(); | |
| if(child_pid == 0) { | |
| close(master_fd); | |
| close(STDIN_FILENO); | |
| close(STDOUT_FILENO); | |
| close(STDERR_FILENO); | |
| int new_stdin = dup(slave_fd); | |
| int new_stdout = dup(slave_fd); | |
| int new_stderr = dup(slave_fd); | |
| close(slave_fd); | |
| if( | |
| new_stdin != STDIN_FILENO || | |
| new_stdout != STDOUT_FILENO || | |
| new_stderr != STDERR_FILENO || | |
| setsid() == -1 | |
| ) { | |
| goto __child_failure; | |
| } | |
| printf("[ process %d (child) setup stdin, stdout, stderr and setsid ok ]\n", getpid()); | |
| errno = 0; | |
| int cttyerr = ioctl(new_stdin, TIOCSCTTY, 0); | |
| printf("[ process %d (child) ensure slave controlling terminal, ioctl(TIOCSCTTY): %s, rv = %d ]\n", | |
| getpid(), strerror(errno), cttyerr); | |
| printf("[ process %d (child) is ready ]\n", getpid()); | |
| printf("[ process %d (child) will run bash... ]\n\n", getpid()); | |
| execve("/usr/bin/bash", NULL, envp); | |
| perror("execve"); | |
| __child_failure: | |
| close(new_stdin); | |
| close(new_stdout); | |
| close(new_stderr); | |
| exit(EXIT_FAILURE); | |
| } else if(child_pid > 0) { | |
| do_term_things(master_fd, child_pid); | |
| } else { | |
| perror("fork"); | |
| exit(EXIT_FAILURE); | |
| } | |
| } | |
| int main(int, char**, char** envp) { | |
| int master_fd; | |
| int slave_fd; | |
| char slave_path[PATH_MAX]; | |
| getmypt(&master_fd, &slave_fd, slave_path, PATH_MAX); | |
| printf("[ slave device opened success: %s ]\n", slave_path); | |
| just_fork(master_fd, slave_fd, envp); | |
| //unreachable code | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment