summaryrefslogtreecommitdiff
path: root/term-utils/script.c
diff options
context:
space:
mode:
authorIgor Pashev <pashev.igor@gmail.com>2012-11-02 20:15:39 +0400
committerIgor Pashev <pashev.igor@gmail.com>2012-11-02 20:15:39 +0400
commitb13154de3eca5ba28fbb4854d916cd0be5febeed (patch)
tree30f2e9e89ab71a2df837076ac68c3ba770230294 /term-utils/script.c
downloadutil-linux-upstream.tar.gz
Imported Upstream version 2.22upstream/2.22upstream
Diffstat (limited to 'term-utils/script.c')
-rw-r--r--term-utils/script.c568
1 files changed, 568 insertions, 0 deletions
diff --git a/term-utils/script.c b/term-utils/script.c
new file mode 100644
index 0000000..ccd8873
--- /dev/null
+++ b/term-utils/script.c
@@ -0,0 +1,568 @@
+/*
+ * Copyright (c) 1980 Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * 1999-02-22 Arkadiusz Mi¶kiewicz <misiek@pld.ORG.PL>
+ * - added Native Language Support
+ *
+ * 2000-07-30 Per Andreas Buer <per@linpro.no> - added "q"-option
+ */
+
+/*
+ * script
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <paths.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <termios.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <signal.h>
+#include <errno.h>
+#include <string.h>
+#include <getopt.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <locale.h>
+#include <stddef.h>
+
+#include "closestream.h"
+#include "nls.h"
+#include "c.h"
+
+#if defined(HAVE_LIBUTIL) && defined(HAVE_PTY_H)
+# include <pty.h>
+#endif
+
+#ifdef HAVE_LIBUTEMPTER
+# include <utempter.h>
+#endif
+
+#define DEFAULT_OUTPUT "typescript"
+
+void finish(int);
+void done(void);
+void fail(void);
+void resize(int);
+void fixtty(void);
+void getmaster(void);
+void getslave(void);
+void doinput(void);
+void dooutput(FILE *timingfd);
+void doshell(void);
+
+char *shell;
+FILE *fscript;
+int master = -1;
+int slave;
+pid_t child;
+pid_t subchild;
+int childstatus;
+char *fname;
+
+struct termios tt;
+struct winsize win;
+int lb;
+int l;
+#if !HAVE_LIBUTIL || !HAVE_PTY_H
+char line[] = "/dev/ptyXX";
+#endif
+int aflg = 0;
+char *cflg = NULL;
+int eflg = 0;
+int fflg = 0;
+int qflg = 0;
+int tflg = 0;
+int forceflg = 0;
+
+int die;
+int resized;
+
+static void
+die_if_link(char *fn) {
+ struct stat s;
+
+ if (forceflg)
+ return;
+ if (lstat(fn, &s) == 0 && (S_ISLNK(s.st_mode) || s.st_nlink > 1))
+ errx(EXIT_FAILURE,
+ _("output file `%s' is a link\n"
+ "Use --force if you really want to use it.\n"
+ "Program not started."), fn);
+}
+
+static void __attribute__((__noreturn__))
+usage(FILE *out)
+{
+ fputs(_("\nUsage:\n"), out);
+ fprintf(out,
+ _(" %s [options] [file]\n"), program_invocation_short_name);
+
+ fputs(_("\nOptions:\n"), out);
+ fputs(_(" -a, --append append the output\n"
+ " -c, --command <command> run command rather than interactive shell\n"
+ " -e, --return return exit code of the child process\n"
+ " -f, --flush run flush after each write\n"
+ " --force use output file even when it is a link\n"
+ " -q, --quiet be quiet\n"
+ " -t, --timing[=<file>] output timing data to stderr (or to FILE)\n"
+ " -V, --version output version information and exit\n"
+ " -h, --help display this help and exit\n\n"), out);
+
+ exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
+}
+
+/*
+ * script -t prints time delays as floating point numbers
+ * The example program (scriptreplay) that we provide to handle this
+ * timing output is a perl script, and does not handle numbers in
+ * locale format (not even when "use locale;" is added).
+ * So, since these numbers are not for human consumption, it seems
+ * easiest to set LC_NUMERIC here.
+ */
+
+int
+main(int argc, char **argv) {
+ sigset_t block_mask, unblock_mask;
+ struct sigaction sa;
+ int ch;
+ FILE *timingfd = stderr;
+
+ enum { FORCE_OPTION = CHAR_MAX + 1 };
+
+ static const struct option longopts[] = {
+ { "append", no_argument, NULL, 'a' },
+ { "command", required_argument, NULL, 'c' },
+ { "return", no_argument, NULL, 'e' },
+ { "flush", no_argument, NULL, 'f' },
+ { "force", no_argument, NULL, FORCE_OPTION, },
+ { "quiet", no_argument, NULL, 'q' },
+ { "timing", optional_argument, NULL, 't' },
+ { "version", no_argument, NULL, 'V' },
+ { "help", no_argument, NULL, 'h' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ setlocale(LC_ALL, "");
+ setlocale(LC_NUMERIC, "C"); /* see comment above */
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ atexit(close_stdout);
+
+ while ((ch = getopt_long(argc, argv, "ac:efqt::Vh", longopts, NULL)) != -1)
+ switch(ch) {
+ case 'a':
+ aflg = 1;
+ break;
+ case 'c':
+ cflg = optarg;
+ break;
+ case 'e':
+ eflg = 1;
+ break;
+ case 'f':
+ fflg = 1;
+ break;
+ case FORCE_OPTION:
+ forceflg = 1;
+ break;
+ case 'q':
+ qflg = 1;
+ break;
+ case 't':
+ if (optarg)
+ if ((timingfd = fopen(optarg, "w")) == NULL)
+ err(EXIT_FAILURE, _("cannot open %s"), optarg);
+ tflg = 1;
+ break;
+ case 'V':
+ printf(_("%s from %s\n"), program_invocation_short_name,
+ PACKAGE_STRING);
+ exit(EXIT_SUCCESS);
+ break;
+ case 'h':
+ usage(stdout);
+ break;
+ case '?':
+ default:
+ usage(stderr);
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc > 0)
+ fname = argv[0];
+ else {
+ fname = DEFAULT_OUTPUT;
+ die_if_link(fname);
+ }
+ if ((fscript = fopen(fname, aflg ? "a" : "w")) == NULL) {
+ warn(_("cannot open %s"), fname);
+ fail();
+ }
+
+ shell = getenv("SHELL");
+ if (shell == NULL)
+ shell = _PATH_BSHELL;
+
+ getmaster();
+ if (!qflg)
+ printf(_("Script started, file is %s\n"), fname);
+ fixtty();
+
+#ifdef HAVE_LIBUTEMPTER
+ utempter_add_record(master, NULL);
+#endif
+ /* setup SIGCHLD handler */
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sa.sa_handler = finish;
+ sigaction(SIGCHLD, &sa, NULL);
+
+ /* init mask for SIGCHLD */
+ sigprocmask(SIG_SETMASK, NULL, &block_mask);
+ sigaddset(&block_mask, SIGCHLD);
+
+ sigprocmask(SIG_SETMASK, &block_mask, &unblock_mask);
+ child = fork();
+ sigprocmask(SIG_SETMASK, &unblock_mask, NULL);
+
+ if (child < 0) {
+ warn(_("fork failed"));
+ fail();
+ }
+ if (child == 0) {
+
+ sigprocmask(SIG_SETMASK, &block_mask, NULL);
+ subchild = child = fork();
+ sigprocmask(SIG_SETMASK, &unblock_mask, NULL);
+
+ if (child < 0) {
+ warn(_("fork failed"));
+ fail();
+ }
+ if (child)
+ dooutput(timingfd);
+ else
+ doshell();
+ } else {
+ sa.sa_handler = resize;
+ sigaction(SIGWINCH, &sa, NULL);
+ }
+ doinput();
+
+ if (close_stream(timingfd) != 0)
+ errx(EXIT_FAILURE, _("write error"));
+ return EXIT_SUCCESS;
+}
+
+void __attribute__((__noreturn__))
+doinput(void) {
+ ssize_t cc;
+ char ibuf[BUFSIZ];
+
+ if (close_stream(fscript) != 0)
+ errx(EXIT_FAILURE, _("write error"));
+
+ while (die == 0) {
+ if ((cc = read(STDIN_FILENO, ibuf, BUFSIZ)) > 0) {
+ ssize_t wrt = write(master, ibuf, cc);
+ if (wrt < 0) {
+ warn (_("write failed"));
+ fail();
+ }
+ }
+ else if (cc < 0 && errno == EINTR && resized)
+ resized = 0;
+ else
+ break;
+ }
+
+ done();
+}
+
+#include <sys/wait.h>
+
+void
+finish(int dummy __attribute__ ((__unused__))) {
+ int status;
+ pid_t pid;
+
+ while ((pid = wait3(&status, WNOHANG, 0)) > 0)
+ if (pid == child) {
+ childstatus = status;
+ die = 1;
+ }
+}
+
+void
+resize(int dummy __attribute__ ((__unused__))) {
+ resized = 1;
+ /* transmit window change information to the child */
+ ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&win);
+ ioctl(slave, TIOCSWINSZ, (char *)&win);
+}
+
+/*
+ * Stop extremely silly gcc complaint on %c:
+ * warning: `%c' yields only last 2 digits of year in some locales
+ */
+static void
+my_strftime(char *buf, size_t len, const char *fmt, const struct tm *tm) {
+ strftime(buf, len, fmt, tm);
+}
+
+void __attribute__((__noreturn__))
+dooutput(FILE *timingfd) {
+ ssize_t cc;
+ time_t tvec;
+ char obuf[BUFSIZ];
+ struct timeval tv;
+ double oldtime=time(NULL), newtime;
+ int flgs = 0;
+ ssize_t wrt;
+ ssize_t fwrt;
+
+ close(STDIN_FILENO);
+#ifdef HAVE_LIBUTIL
+ close(slave);
+#endif
+ tvec = time((time_t *)NULL);
+ my_strftime(obuf, sizeof obuf, "%c\n", localtime(&tvec));
+ fprintf(fscript, _("Script started on %s"), obuf);
+
+ do {
+ if (die && flgs == 0) {
+ /* ..child is dead, but it doesn't mean that there is
+ * nothing in buffers.
+ */
+ flgs = fcntl(master, F_GETFL, 0);
+ if (fcntl(master, F_SETFL, (flgs | O_NONBLOCK)) == -1)
+ break;
+ }
+ if (tflg)
+ gettimeofday(&tv, NULL);
+
+ errno = 0;
+ cc = read(master, obuf, sizeof (obuf));
+
+ if (die && errno == EINTR && cc <= 0)
+ /* read() has been interrupted by SIGCHLD, try it again
+ * with O_NONBLOCK
+ */
+ continue;
+ if (cc <= 0)
+ break;
+ if (tflg) {
+ newtime = tv.tv_sec + (double) tv.tv_usec / 1000000;
+ fprintf(timingfd, "%f %zd\n", newtime - oldtime, cc);
+ oldtime = newtime;
+ }
+ wrt = write(STDOUT_FILENO, obuf, cc);
+ if (wrt < 0) {
+ warn (_("write failed"));
+ fail();
+ }
+ fwrt = fwrite(obuf, 1, cc, fscript);
+ if (fwrt < cc) {
+ warn (_("cannot write script file"));
+ fail();
+ }
+ if (fflg)
+ fflush(fscript);
+ } while(1);
+
+ if (flgs)
+ fcntl(master, F_SETFL, flgs);
+ if (close_stream(timingfd) != 0)
+ errx(EXIT_FAILURE, _("write error"));
+ done();
+}
+
+void __attribute__((__noreturn__))
+doshell(void) {
+ char *shname;
+
+ getslave();
+ close(master);
+ if (close_stream(fscript) != 0)
+ errx(EXIT_FAILURE, _("write error"));
+ dup2(slave, STDIN_FILENO);
+ dup2(slave, STDOUT_FILENO);
+ dup2(slave, STDERR_FILENO);
+ close(slave);
+
+ master = -1;
+
+ shname = strrchr(shell, '/');
+ if (shname)
+ shname++;
+ else
+ shname = shell;
+
+ /*
+ * When invoked from within /etc/csh.login, script spawns a csh shell
+ * that spawns programs that cannot be killed with a SIGTERM. This is
+ * because csh has a documented behaviour wherein it disables all
+ * signals when processing the /etc/csh.* files.
+ *
+ * Let's restore the default behavior.
+ */
+ signal(SIGTERM, SIG_DFL);
+
+ if (cflg)
+ execl(shell, shname, "-c", cflg, NULL);
+ else
+ execl(shell, shname, "-i", NULL);
+
+ warn(_("failed to execute %s"), shell);
+ fail();
+}
+
+void
+fixtty(void) {
+ struct termios rtt;
+
+ rtt = tt;
+ cfmakeraw(&rtt);
+ rtt.c_lflag &= ~ECHO;
+ tcsetattr(STDIN_FILENO, TCSANOW, &rtt);
+}
+
+void __attribute__((__noreturn__))
+fail(void) {
+
+ kill(0, SIGTERM);
+ done();
+}
+
+void __attribute__((__noreturn__))
+done(void) {
+ time_t tvec;
+
+ if (subchild) {
+ if (!qflg) {
+ char buf[BUFSIZ];
+ tvec = time((time_t *)NULL);
+ my_strftime(buf, sizeof buf, "%c\n", localtime(&tvec));
+ fprintf(fscript, _("\nScript done on %s"), buf);
+ }
+ if (close_stream(fscript) != 0)
+ errx(EXIT_FAILURE, _("write error"));
+ close(master);
+
+ master = -1;
+ } else {
+ tcsetattr(STDIN_FILENO, TCSADRAIN, &tt);
+ if (!qflg)
+ printf(_("Script done, file is %s\n"), fname);
+#ifdef HAVE_LIBUTEMPTER
+ if (master >= 0)
+ utempter_remove_record(master);
+#endif
+ }
+
+ if(eflg) {
+ if (WIFSIGNALED(childstatus))
+ exit(WTERMSIG(childstatus) + 0x80);
+ else
+ exit(WEXITSTATUS(childstatus));
+ }
+ exit(EXIT_SUCCESS);
+}
+
+void
+getmaster(void) {
+#if defined(HAVE_LIBUTIL) && defined(HAVE_PTY_H)
+ tcgetattr(STDIN_FILENO, &tt);
+ ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&win);
+ if (openpty(&master, &slave, NULL, &tt, &win) < 0) {
+ warn(_("openpty failed"));
+ fail();
+ }
+#else
+ char *pty, *bank, *cp;
+ struct stat stb;
+
+ pty = &line[strlen("/dev/ptyp")];
+ for (bank = "pqrs"; *bank; bank++) {
+ line[strlen("/dev/pty")] = *bank;
+ *pty = '0';
+ if (stat(line, &stb) < 0)
+ break;
+ for (cp = "0123456789abcdef"; *cp; cp++) {
+ *pty = *cp;
+ master = open(line, O_RDWR);
+ if (master >= 0) {
+ char *tp = &line[strlen("/dev/")];
+ int ok;
+
+ /* verify slave side is usable */
+ *tp = 't';
+ ok = access(line, R_OK|W_OK) == 0;
+ *tp = 'p';
+ if (ok) {
+ tcgetattr(STDIN_FILENO, &tt);
+ ioctl(STDIN_FILENO, TIOCGWINSZ,
+ (char *)&win);
+ return;
+ }
+ close(master);
+ master = -1;
+ }
+ }
+ }
+ master = -1;
+ warn(_("out of pty's"));
+ fail();
+#endif /* not HAVE_LIBUTIL */
+}
+
+void
+getslave(void) {
+#ifndef HAVE_LIBUTIL
+ line[strlen("/dev/")] = 't';
+ slave = open(line, O_RDWR);
+ if (slave < 0) {
+ warn(_("cannot open %s"), line);
+ fail();
+ }
+ tcsetattr(slave, TCSANOW, &tt);
+ ioctl(slave, TIOCSWINSZ, (char *)&win);
+#endif
+ setsid();
+ ioctl(slave, TIOCSCTTY, 0);
+}