add pty_shell command.

note that c modules had to be disabled in the build settings due to #define TTYDEFCHARS.
This commit is contained in:
Kelvin Sherlock 2021-03-26 21:16:18 -04:00
parent 6ca897739a
commit 88c12a4832
3 changed files with 482 additions and 0 deletions

View File

@ -62,6 +62,8 @@
B6152B5725F4549F00605E6E /* Slot.m in Sources */ = {isa = PBXBuildFile; fileRef = B6152B5525F4549F00605E6E /* Slot.m */; };
B6152B5A25F5B57E00605E6E /* Media.m in Sources */ = {isa = PBXBuildFile; fileRef = B6152B5925F5B57E00605E6E /* Media.m */; };
B6152B5B25F5B57E00605E6E /* Media.m in Sources */ = {isa = PBXBuildFile; fileRef = B6152B5925F5B57E00605E6E /* Media.m */; };
B6374AC4260EBBCF0045CA16 /* pty_shell.c in Sources */ = {isa = PBXBuildFile; fileRef = B6374AB6260EBB970045CA16 /* pty_shell.c */; };
B6374AC5260EBC5A0045CA16 /* pty_shell in CopyFiles */ = {isa = PBXBuildFile; fileRef = B6374ABD260EBBC90045CA16 /* pty_shell */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
B63C1B8B24FF4BF700511A71 /* Ample.m in Sources */ = {isa = PBXBuildFile; fileRef = B63C1B8A24FF4BF700511A71 /* Ample.m */; };
B63C1B8C24FF4BF700511A71 /* Ample.m in Sources */ = {isa = PBXBuildFile; fileRef = B63C1B8A24FF4BF700511A71 /* Ample.m */; };
B63C1B8E25004C6D00511A71 /* mame-data.tgz in Resources */ = {isa = PBXBuildFile; fileRef = B63C1B8D25004C6D00511A71 /* mame-data.tgz */; };
@ -232,6 +234,15 @@
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
B6374ABB260EBBC90045CA16 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = /usr/share/man/man1/;
dstSubfolderSpec = 0;
files = (
);
runOnlyForDeploymentPostprocessing = 1;
};
B66236B124FDA443006CABD7 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
@ -251,6 +262,7 @@
files = (
B6841BDA251ECB1C006A5C39 /* mame64 in CopyFiles */,
B6E08076252574690075F4E1 /* vmnet_helper in CopyFiles */,
B6374AC5260EBC5A0045CA16 /* pty_shell in CopyFiles */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -335,6 +347,8 @@
B6152B5525F4549F00605E6E /* Slot.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Slot.m; sourceTree = "<group>"; };
B6152B5825F5B4F100605E6E /* Media.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Media.h; sourceTree = "<group>"; };
B6152B5925F5B57E00605E6E /* Media.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Media.m; sourceTree = "<group>"; };
B6374AB6260EBB970045CA16 /* pty_shell.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = pty_shell.c; sourceTree = "<group>"; };
B6374ABD260EBBC90045CA16 /* pty_shell */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = pty_shell; sourceTree = BUILT_PRODUCTS_DIR; };
B63C1B8924FF4B7100511A71 /* Ample.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Ample.h; sourceTree = "<group>"; };
B63C1B8A24FF4BF700511A71 /* Ample.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Ample.m; sourceTree = "<group>"; };
B63C1B8D25004C6D00511A71 /* mame-data.tgz */ = {isa = PBXFileReference; lastKnownFileType = file; name = "mame-data.tgz"; path = "embedded/mame-data.tgz"; sourceTree = "<group>"; };
@ -423,6 +437,13 @@
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
B6374ABA260EBBC90045CA16 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
B6841BCD251EC913006A5C39 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@ -450,6 +471,14 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
B6374AB2260EBB970045CA16 /* pty_shell */ = {
isa = PBXGroup;
children = (
B6374AB6260EBB970045CA16 /* pty_shell.c */,
);
path = pty_shell;
sourceTree = "<group>";
};
B649798C24EEC165008ABD20 /* Recovered References */ = {
isa = PBXGroup;
children = (
@ -564,6 +593,7 @@
B6BA257D24E99BE9005FB8FF /* Ample */,
B66236BD24FDA7EA006CABD7 /* Embedded Content */,
B6841BD1251EC913006A5C39 /* vmnet_helper */,
B6374AB2260EBB970045CA16 /* pty_shell */,
B6BA257C24E99BE9005FB8FF /* Products */,
B649798C24EEC165008ABD20 /* Recovered References */,
B66236B624FDA686006CABD7 /* Frameworks */,
@ -576,6 +606,7 @@
B6BA257B24E99BE9005FB8FF /* Ample.app */,
B6E4B5FA24FDE2670094A35C /* Ample Lite.app */,
B6841BD0251EC913006A5C39 /* vmnet_helper */,
B6374ABD260EBBC90045CA16 /* pty_shell */,
);
name = Products;
sourceTree = "<group>";
@ -683,6 +714,23 @@
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
B6374ABC260EBBC90045CA16 /* pty_shell */ = {
isa = PBXNativeTarget;
buildConfigurationList = B6374AC1260EBBC90045CA16 /* Build configuration list for PBXNativeTarget "pty_shell" */;
buildPhases = (
B6374AB9260EBBC90045CA16 /* Sources */,
B6374ABA260EBBC90045CA16 /* Frameworks */,
B6374ABB260EBBC90045CA16 /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = pty_shell;
productName = pty_shell;
productReference = B6374ABD260EBBC90045CA16 /* pty_shell */;
productType = "com.apple.product-type.tool";
};
B6841BCF251EC913006A5C39 /* vmnet_helper */ = {
isa = PBXNativeTarget;
buildConfigurationList = B6841BD4251EC913006A5C39 /* Build configuration list for PBXNativeTarget "vmnet_helper" */;
@ -747,6 +795,9 @@
LastUpgradeCheck = 1130;
ORGANIZATIONNAME = "Kelvin Sherlock";
TargetAttributes = {
B6374ABC260EBBC90045CA16 = {
CreatedOnToolsVersion = 11.3.1;
};
B6841BCF251EC913006A5C39 = {
CreatedOnToolsVersion = 11.3.1;
};
@ -771,6 +822,7 @@
B6BA257A24E99BE9005FB8FF /* Ample */,
B6E4B5AE24FDE2670094A35C /* Ample Lite */,
B6841BCF251EC913006A5C39 /* vmnet_helper */,
B6374ABC260EBBC90045CA16 /* pty_shell */,
);
};
/* End PBXProject section */
@ -990,6 +1042,14 @@
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
B6374AB9260EBBC90045CA16 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B6374AC4260EBBCF0045CA16 /* pty_shell.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
B6841BCC251EC913006A5C39 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@ -1129,6 +1189,24 @@
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
B6374AC2260EBBC90045CA16 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = NO;
CODE_SIGN_STYLE = Automatic;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
B6374AC3260EBBC90045CA16 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = NO;
CODE_SIGN_STYLE = Automatic;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
B6841BD5251EC913006A5C39 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@ -1335,6 +1413,15 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
B6374AC1260EBBC90045CA16 /* Build configuration list for PBXNativeTarget "pty_shell" */ = {
isa = XCConfigurationList;
buildConfigurations = (
B6374AC2260EBBC90045CA16 /* Debug */,
B6374AC3260EBBC90045CA16 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
B6841BD4251EC913006A5C39 /* Build configuration list for PBXNativeTarget "vmnet_helper" */ = {
isa = XCConfigurationList;
buildConfigurations = (

View File

@ -26,6 +26,11 @@
<key>orderHint</key>
<integer>0</integer>
</dict>
<key>pty_shell.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>3</integer>
</dict>
<key>vmnet_helper.xcscheme_^#shared#^_</key>
<dict>
<key>isShown</key>

390
pty_shell/pty_shell.c Normal file
View File

@ -0,0 +1,390 @@
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <paths.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sysexits.h>
#include <termios.h>
#include <unistd.h>
#include <util.h>
#define TTYDEFCHARS
#include <sys/ttydefaults.h>
void usage(int rv) {
fputs(
"Usage: pty_shell [-T term] [-w] [-r] pty [command ...]\n"
" -T term TERM (default vt100)\n"
" -w Don't wait for child to finish\n"
" -r Raw I/O\n"
, stderr);
exit(rv);
}
char *xsprintf(char *fmt, ...) {
int ok;
char *buffer = NULL;
va_list ap;
va_start(ap, fmt);
ok = vasprintf(&buffer, fmt, ap);
if (ok < 0) {
errx(EX_SOFTWARE, "vasprintf failed");
}
va_end(ap);
return buffer;
}
/* re-create execve path search so we have better control */
/* return string may or may not be allocated */
char *findexe(char *name) {
struct stat st;
char *cp;
int ok;
char *path = getenv("PATH");
if (!path) path = _PATH_DEFPATH;
if (!name || !*name) {
errno = ENOENT;
return NULL;
}
if (*name == '/') return name;
if (strchr(name, '/')) {
cp = realpath(name, NULL);
return cp;
}
char *start = path;
char *end = NULL;
size_t l;
for(;;) {
end = strchr(start, ':');
if (!end) {
/* last one */
l = strlen(start);
} else {
l = end - start;
}
if (l == 0) {
/* current directory */
cp = realpath(name, NULL);
} else {
cp = xsprintf("%.*s/%s", l, start, name);
}
// fprintf(stderr, "%s\n", cp);
ok = stat(cp, &st);
if (ok >= 0 && (st.st_mode & S_IXUSR))
return cp;
free(cp);
if (!end) break;
start = end + 1;
}
errno = ENOENT;
return NULL;
}
void dup012(int fd) {
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
if (fd > 2) close(fd);
}
void execute(int fd, char *path, char **argv, char **env) {
int ok;
int err_fd = fcntl(STDERR_FILENO, F_DUPFD_CLOEXEC, 0);
// ok = 0; dup012(fd);
ok = login_tty(fd);
if (ok < 0) {
dprintf(err_fd, "%s: login_tty: %s\n",
getprogname(), strerror(errno)
);
_exit(EX_OSERR);
}
execve(path, argv, env);
dprintf(err_fd, "%s: execve %s: %s\n",
getprogname(), path, strerror(errno)
);
_exit(EX_OSERR);
}
pid_t child_pid;
int pty_fd;
void sig_handler(int sig, siginfo_t *info, void *context) {
if (sig == SIGINFO || sig == SIGUSR1) {
int rlen, wlen;
rlen = wlen = 0;
if (pty_fd > 0) {
char buffer[128];
int n,x;
ioctl(pty_fd, TIOCOUTQ, &wlen);
ioctl(pty_fd, FIONREAD, &rlen);
if (rlen > 9999) rlen = 9999;
if (wlen > 9999) wlen = 9999;
memcpy(buffer, "child pid: read queue: write queue: \n", 58);
n = 16;
x = child_pid;
do {
buffer[n--] = '0' + (x %10);
x /= 10;
} while(x);
n = 35;
x = rlen;
do {
buffer[n--] = '0' + (x %10);
x /= 10;
} while(x);
n = 55;
x = wlen;
do {
buffer[n--] = '0' + (x %10);
x /= 10;
} while(x);
write(STDERR_FILENO, buffer, 58);
}
return;
}
if (sig == SIGHUP || sig == SIGINT || sig == SIGTERM) {
/* pass to child */
if (child_pid >= 0) {
kill(child_pid, sig);
}
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sigemptyset(&sa.sa_mask);
sa.sa_handler = SIG_DFL;
sigaction(sig, &sa, NULL);
kill(getpid(), sig);
_exit(1);
}
}
int main(int argc, char **argv) {
int c;
int fd;
pid_t pid;
int ok, i;
char *pty;
char *term = "vt100";
char *path = NULL;
struct winsize ws = { 24, 80, 0, 0 };
struct termios tios;
struct sigaction sa;
char *env[10];
int flag_w = 0;
// int flag_i = 0;
int flag_f = 0;
int flag_r = 0;
int flag_v = 0;
while ((c = getopt(argc, argv, "T:rwhv")) != -1) {
switch(c) {
// case 'f': flag_f = 1; break;
case 'r': flag_r = 1; break;
case 'w': flag_w = 1; break;
case 'v': flag_v = 1; break;
case 'h': usage(0);
case 'T':
term = optarg;
break;
default:
exit(EX_USAGE);
}
}
argc -= optind;
argv += optind;
// pty [optional command]
if (argc < 1) {
usage(EX_USAGE);
}
/* n.b. - with nonblock, fd can close before all data sent */
pty = argv[0];
fd = open(pty, O_RDWR | /* O_NONBLOCK | */ O_CLOEXEC);
if (fd < 0) {
err(EX_NOINPUT, "open %s", pty);
}
pty_fd = fd;
--argc;
++argv;
memset(&tios, 0, sizeof(tios));
memcpy(tios.c_cc, ttydefchars, sizeof(ttydefchars));
if (flag_r) {
cfmakeraw(&tios);
} else {
tios.c_oflag = TTYDEF_OFLAG;
tios.c_lflag = TTYDEF_LFLAG;
tios.c_iflag = TTYDEF_IFLAG;
tios.c_cflag = TTYDEF_CFLAG;
}
tios.c_ispeed = tios.c_ospeed = B9600;
/* verify it's pty? */
ok = tcsetattr(fd, TCSAFLUSH, &tios);
ok = ioctl(fd, TIOCSWINSZ, (void *)&ws);
/* todo - option to retain environment? */
i = 0;
env[i++] = "LANG=C";
env[i++] = xsprintf("TERM=%s", term);
env[i++] = "COLUMNS=80";
env[i++] = "LINES=24";
if (argc) {
char *cp;
cp = getenv("HOME");
if (cp) {
env[i++] = xsprintf("HOME=%s", cp);
}
}
env[i] = 0;
if (argc) {
path = findexe(argv[0]);
if (!path) {
errx(EX_OSERR, "Unable to find %s", argv[0]);
}
argv[0] = basename(argv[0]);
} else {
/* -p: don't discard environment */
static char *args[] = {
"login",
"-pf",
"",
NULL
};
char *login;
login = getlogin();
if (!login) {
errx(EX_OSERR, "getlogin() failed.");
}
path = "/usr/bin/login";
args[2] = login;
argv = args;
}
/* n.b. - login_tty will fail unless root :/ */
if (flag_f) {
/* foreground */
execute(fd, path, argv, env);
exit(0);
}
pid = fork();
if (pid < 0) {
close(fd);
err(EX_OSERR, "fork");
}
if (!pid) {
/* child */
execute(fd, path, argv, env);
}
child_pid = pid;
memset(&sa, 0, sizeof(sa));
sa.sa_flags = SA_SIGINFO | SA_RESTART;
sa.sa_sigaction = sig_handler;
sigfillset(&sa.sa_mask);
sigaction(SIGINFO, &sa, NULL);
sigaction(SIGUSR1, &sa, NULL);
sigaction(SIGHUP, &sa, NULL);
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
/* wait for the child so data isn't lost. */
if (!flag_w) {
pid_t ok;
int st;
printf("Waiting on child %d\n", (int)pid);
for(;;) {
ok = waitpid(pid, &st, 0);
if (ok < 0) {
if (errno == EINTR) {
continue;
}
warn("waitpid");
break;
}
child_pid = -1;
if (WIFEXITED(st) && WEXITSTATUS(st)) {
printf("Exit status: %d\n", WEXITSTATUS(st));
}
if (WIFSIGNALED(st)) {
printf("Exit signal: %s\n", strsignal(WTERMSIG(st)));
}
break;
}
// flush discards data.
//ok = tcflush(fd, TCIOFLUSH);
ok = tcdrain(fd);
}
close(fd);
return 0;
}