From 88c12a48329b16349e2e78f4c42fc05afaca7d48 Mon Sep 17 00:00:00 2001 From: Kelvin Sherlock Date: Fri, 26 Mar 2021 21:16:18 -0400 Subject: [PATCH] add pty_shell command. note that c modules had to be disabled in the build settings due to #define TTYDEFCHARS. --- Ample.xcodeproj/project.pbxproj | 87 ++++ .../xcschemes/xcschememanagement.plist | 5 + pty_shell/pty_shell.c | 390 ++++++++++++++++++ 3 files changed, 482 insertions(+) create mode 100644 pty_shell/pty_shell.c diff --git a/Ample.xcodeproj/project.pbxproj b/Ample.xcodeproj/project.pbxproj index afa459d..97bf0eb 100644 --- a/Ample.xcodeproj/project.pbxproj +++ b/Ample.xcodeproj/project.pbxproj @@ -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 = ""; }; B6152B5825F5B4F100605E6E /* Media.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Media.h; sourceTree = ""; }; B6152B5925F5B57E00605E6E /* Media.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Media.m; sourceTree = ""; }; + B6374AB6260EBB970045CA16 /* pty_shell.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = pty_shell.c; sourceTree = ""; }; + 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 = ""; }; B63C1B8A24FF4BF700511A71 /* Ample.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Ample.m; sourceTree = ""; }; B63C1B8D25004C6D00511A71 /* mame-data.tgz */ = {isa = PBXFileReference; lastKnownFileType = file; name = "mame-data.tgz"; path = "embedded/mame-data.tgz"; sourceTree = ""; }; @@ -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 = ""; + }; 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 = ""; @@ -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 = ( diff --git a/Ample.xcodeproj/xcuserdata/kelvin.xcuserdatad/xcschemes/xcschememanagement.plist b/Ample.xcodeproj/xcuserdata/kelvin.xcuserdatad/xcschemes/xcschememanagement.plist index c0958d8..006e090 100644 --- a/Ample.xcodeproj/xcuserdata/kelvin.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Ample.xcodeproj/xcuserdata/kelvin.xcuserdatad/xcschemes/xcschememanagement.plist @@ -26,6 +26,11 @@ orderHint 0 + pty_shell.xcscheme_^#shared#^_ + + orderHint + 3 + vmnet_helper.xcscheme_^#shared#^_ isShown diff --git a/pty_shell/pty_shell.c b/pty_shell/pty_shell.c new file mode 100644 index 0000000..acaa849 --- /dev/null +++ b/pty_shell/pty_shell.c @@ -0,0 +1,390 @@ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TTYDEFCHARS +#include + + +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; +}