native/exec_pty.c (180 lines of code) (raw):
/*******************************************************************************
* Copyright (c) 2004, 2010 QNX Software Systems and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* QNX Software Systems - initial API and implementation
* Wind River Systems, Inc.
* Mikhail Zabaluev (Nokia) - bug 82744
* Mikhail Sennikovsky - bug 145737
*******************************************************************************/
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdbool.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/syscall.h>
#include <dirent.h>
#include <ctype.h>
#include "exec_pty.h"
int test();
/* from pfind.c */
extern char *pfind(const char *name, char * const envp[]);
/* from openpty.c */
extern int ptys_open(int fdm, const char *pts_name, bool acquire);
extern void set_noecho(int fd);
void restore_signal(int signum) {
struct sigaction action;
memset(&action, 0, sizeof(action));
action.sa_handler = SIG_DFL;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
if (sigaction(signum, &action, NULL) != 0) {
fprintf(stderr, "%s(%d): cannot set SIG_DFL for signal %d: %s\n", __FUNCTION__, __LINE__, signum, strerror(errno));
}
}
void restore_signals() {
restore_signal(SIGPIPE);
restore_signal(SIGINT);
restore_signal(SIGQUIT);
}
static int sys_close_range_wrapper(unsigned int from_fd_inclusive) {
// Use fast `close_range` (https://man7.org/linux/man-pages/man2/close_range.2.html) if available.
// Cannot call `close_range` from libc, as it may be unavailable in older libc.
# if defined(__linux__) && defined(SYS_close_range) && defined(CLOSE_RANGE_UNSHARE)
return syscall(SYS_close_range, from_fd_inclusive, ~0U, CLOSE_RANGE_UNSHARE);
# else
errno = ENOSYS;
return -1;
# endif
}
static int close_all_fds_using_parsing(unsigned int from_fd_inclusive) {
// If `opendir` is implemented using a file descriptor, we may close it accidentally.
// Let's close a few lowest file descriptors, in hope that `opendir` will use it.
int lowest_fds_to_close = 2;
for (int i = 0; i < lowest_fds_to_close; i++) {
close(from_fd_inclusive + i);
}
#if defined(__APPLE__)
#define FD_DIR "/dev/fd"
#else
#define FD_DIR "/proc/self/fd"
#endif
DIR *dirp = opendir(FD_DIR);
if (dirp == NULL) return -1;
struct dirent *direntp;
while ((direntp = readdir(dirp)) != NULL) {
if (isdigit(direntp->d_name[0])) {
int fd = strtol(direntp->d_name, NULL, 10);
if (fd >= from_fd_inclusive + lowest_fds_to_close && fd != dirfd(dirp)) {
close(fd);
}
}
}
closedir(dirp);
return 0;
}
static void close_all_fds_fallback(unsigned int from_fd_inclusive) {
int fdlimit = sysconf(_SC_OPEN_MAX);
if (fdlimit == -1) fdlimit = 65535; // arbitrary default, just in case
for (int fd = from_fd_inclusive; fd < fdlimit; fd++) {
close(fd);
}
}
static void close_all_fds() {
unsigned int from_fd = STDERR_FILENO + 1;
if (sys_close_range_wrapper(from_fd) == 0) return;
if (close_all_fds_using_parsing(from_fd) == 0) return;
close_all_fds_fallback(from_fd);
}
pid_t exec_pty(const char *path, char *const argv[], char *const envp[], const char *dirpath,
const char *pts_name, int fdm, const char *err_pts_name, int err_fdm, int console)
{
pid_t childpid;
char *full_path;
/*
* We use pfind() to check that the program exists and is an executable.
* If not pass the error up. Also execve() wants a full path.
*/
full_path = pfind(path, envp);
if (full_path == NULL) {
fprintf(stderr, "Unable to find full path for \"%s\"\n", (path) ? path : "");
return -1;
}
childpid = fork();
if (childpid < 0) {
fprintf(stderr, "%s(%d): returning due to error: %s\n", __FUNCTION__, __LINE__, strerror(errno));
free(full_path);
return -1;
} else if (childpid == 0) { /* child */
chdir(dirpath);
int fds;
int err_fds = -1;
if (!console && setsid() < 0) {
perror("setsid()");
return -1;
}
fds = ptys_open(fdm, pts_name, true);
if (fds < 0) {
fprintf(stderr, "%s(%d): returning due to error: %s\n", __FUNCTION__, __LINE__, strerror(errno));
return -1;
}
if (console && err_fdm >= 0) {
err_fds = ptys_open(err_fdm, err_pts_name, false);
if (err_fds < 0) {
fprintf(stderr, "%s(%d): returning due to error: %s\n", __FUNCTION__, __LINE__, strerror(errno));
return -1;
}
}
/* close masters, no need in the child */
close(fdm);
if (console && err_fdm >= 0) close(err_fdm);
if (console) {
set_noecho(fds);
if (setpgid(getpid(), getpid()) < 0) {
perror("setpgid()");
return -1;
}
}
/* redirections */
dup2(fds, STDIN_FILENO); /* dup stdin */
dup2(fds, STDOUT_FILENO); /* dup stdout */
dup2(console && err_fds >= 0 ? err_fds : fds, STDERR_FILENO); /* dup stderr */
close(fds); /* done with fds. */
if (console && err_fds >= 0) close(err_fds);
/* Close all the fd's in the child */
close_all_fds();
restore_signals();
execve(full_path, argv, envp);
_exit(127);
} else if (childpid != 0) { /* parent */
if (console) {
set_noecho(fdm);
}
free(full_path);
return childpid;
}
free(full_path);
return -1; /*NOT REACHED */
}
int wait_for_child_process_exit(pid_t child_pid) {
int status;
while (waitpid(child_pid, &status, 0) < 0) {
switch (errno) {
case ECHILD:
return 0;
case EINTR:
break;
default:
return -1;
}
}
if (WIFEXITED(status)) {
// The process exited normally; get its exit code.
return WEXITSTATUS(status);
}
if (WIFSIGNALED(status)) {
// The child exited because of a signal. Return 128 + signal number as all Unix shells do.
// https://tldp.org/LDP/abs/html/exitcodes.html
return 128 + WTERMSIG(status);
}
return status; // Unknown exit code; pass it as is.
}
int errno_non_zero() {
int last_error = errno;
return last_error > 0 ? last_error : -1; // ensure non-zero value
}
int get_window_size(int fd, struct winsize *size) {
// TIOCGWINSZ have no portable number
if (ioctl(fd, TIOCGWINSZ, size) < 0) {
return errno_non_zero();
}
return 0;
}
int set_window_size(int fd, const struct winsize *size) {
// TIOCSWINSZ have no portable number
if (ioctl(fd, TIOCSWINSZ, size) < 0) {
return errno_non_zero();
}
return 0;
}
int is_valid_fd(int fd) {
return fcntl(fd, F_GETFD) != -1 || errno != EBADF;
}