Skip to content

Instantly share code, notes, and snippets.

@StefanoBelli
Created November 10, 2025 09:56
Show Gist options
  • Select an option

  • Save StefanoBelli/5cdd5c40cce462d5968ff2a84978e0fb to your computer and use it in GitHub Desktop.

Select an option

Save StefanoBelli/5cdd5c40cce462d5968ff2a84978e0fb to your computer and use it in GitHub Desktop.
Example of pseudoterminal device usage
#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