script: new applet by Pascal Bellard <pascal.bellard AT ads-lu.com>
[platform/upstream/busybox.git] / util-linux / script.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * script implementation for busybox
4  *
5  * pascal.bellard@ads-lu.com
6  *
7  * Based on code from util-linux v 2.12r
8  * Copyright (c) 1980
9  *      The Regents of the University of California.  All rights reserved.
10  *
11  * Licensed under GPLv2 or later, see file License in this tarball for details.
12  */
13
14 #include "libbb.h"
15
16 struct globals {
17         int child_pid;
18         int attr_ok; /* NB: 0: ok */
19         struct termios tt;
20         const char *fname;
21 };
22 #define G (*ptr_to_globals)
23 #define child_pid (G.child_pid)
24 #define attr_ok   (G.attr_ok  )
25 #define tt        (G.tt       )
26 #define fname     (G.fname    )
27 #define INIT_G() do { \
28         PTR_TO_GLOBALS = xzalloc(sizeof(G)); \
29         fname = "typescript"; \
30 } while (0)
31
32 static void done(void)
33 {
34         if (child_pid) { /* we are parent */
35                 if (attr_ok == 0)
36                         tcsetattr(0, TCSAFLUSH, &tt);
37                 if (!(option_mask32 & 8)) /* not -q */
38                         printf("Script done, file is %s\n", fname);
39         }
40         exit(0);
41 }
42
43 #ifdef UNUSED
44 static void handle_sigchld(int sig)
45 {
46         /* wait for the exited child and exit */
47         while (wait_any_nohang(&sig) > 0)
48                 continue;
49         done();
50 }
51 #endif
52
53 #if ENABLE_GETOPT_LONG
54 static const char getopt_longopts[] ALIGN1 =
55         "append\0"  No_argument       "a"
56         "command\0" Required_argument "c"
57         "flush\0"   No_argument       "f"
58         "quiet\0"   No_argument       "q"
59         ;
60 #endif
61
62 int script_main(int argc, char *argv[]) MAIN_EXTERNALLY_VISIBLE;
63 int script_main(int argc, char *argv[])
64 {
65         int opt, pty;
66         int winsz_ok;
67         int mode;
68         struct termios rtt;
69         struct winsize win;
70         char line[32];
71         const char *shell;
72         char shell_opt[] = "-i";
73         char *shell_arg = NULL;
74
75         INIT_G();
76 #if ENABLE_GETOPT_LONG
77         applet_long_options = getopt_longopts;
78 #endif
79         opt_complementary = "?1"; /* max one arg */
80         opt = getopt32(argv, "ac:fq", &shell_arg);
81         //argc -= optind;
82         argv += optind;
83         if (argv[0]) {
84                 fname = argv[0];
85         }
86         mode = O_CREAT|O_TRUNC|O_WRONLY;
87         if (opt & 1) {
88                 mode = O_CREAT|O_APPEND|O_WRONLY;
89         }
90         if (opt & 2) {
91                 shell_opt[1] = 'c';
92         }
93         if (!(opt & 8)) { /* not -q */
94                 printf("Script started, file is %s\n", fname);
95         }
96         shell = getenv("SHELL");
97         if (shell == NULL) {
98                 shell = _PATH_BSHELL;
99         }
100
101         pty = getpty(line, sizeof(line));
102         if (pty < 0) {
103                 bb_perror_msg_and_die("can't get pty");
104         }
105
106         /* get current stdin's tty params */
107         attr_ok = tcgetattr(0, &tt);
108         winsz_ok = ioctl(0, TIOCGWINSZ, (char *)&win);
109
110         rtt = tt;
111         cfmakeraw(&rtt);
112         rtt.c_lflag &= ~ECHO;
113         tcsetattr(0, TCSAFLUSH, &rtt);
114
115         /* We exit as soon as child exits */
116         //signal(SIGCHLD, handle_sigchld);
117         signal(SIGCHLD, (void (*)(int)) done);
118
119         child_pid = vfork();
120         if (child_pid < 0) {
121                 bb_perror_msg_and_die("vfork");
122         }
123
124         if (child_pid) {
125                 /* parent */
126                 char buf[256];
127                 struct pollfd pfd[2];
128                 int outfd;
129                 int fd_count = 2;
130                 struct pollfd *ppfd = pfd;
131
132                 outfd = xopen(fname, mode);
133                 pfd[0].fd = 0;
134                 pfd[0].events = POLLIN;
135                 pfd[1].fd = pty;
136                 pfd[1].events = POLLIN;
137                 ndelay_on(pty); /* this descriptor is not shared, can do this */
138                 /* ndelay_on(0); - NO, stdin can be shared! */
139
140                 /* copy stdin to pty master input,
141                  * copy pty master output to stdout and file */
142                 /* TODO: don't use full_write's, use proper write buffering */
143                 while (fd_count && safe_poll(ppfd, fd_count, -1) > 0) {
144                         if (pfd[0].revents) {
145                                 int count = safe_read(0, buf, sizeof(buf));
146                                 if (count <= 0) {
147                                         /* err/eof: don't read anymore */
148                                         pfd[0].revents = 0;
149                                         ppfd++;
150                                         fd_count--;
151                                 } else {
152                                         full_write(pty, buf, count);
153                                 }
154                         }
155                         if (pfd[1].revents) {
156                                 int count;
157                                 errno = 0;
158                                 count = safe_read(pty, buf, sizeof(buf));
159                                 if (count <= 0 && errno != EAGAIN) {
160                                         /* err/eof: don't read anymore */
161                                         pfd[1].revents = 0;
162                                         fd_count--;
163                                 }
164                                 if (count > 0) {
165                                         full_write(1, buf, count);
166                                         full_write(outfd, buf, count);
167                                         if (opt & 4) { /* -f */
168                                                 fsync(outfd);
169                                         }
170                                 }
171                         }
172                 }
173                 done(); /* does not return */
174         }
175
176         /* child: make pty slave to be input, output, error; run shell */
177         close(pty); /* close pty master */
178         /* open pty slave to fd 0,1,2 */
179         close(0);               
180         xopen(line, O_RDWR); /* uses fd 0 */
181         xdup2(0, 1);
182         xdup2(0, 2);
183         /* copy our original stdin tty's parameters to pty */
184         if (attr_ok == 0)
185                 tcsetattr(0, TCSAFLUSH, &tt);
186         if (winsz_ok == 0)
187                 ioctl(0, TIOCSWINSZ, (char *)&win);
188         /* set pty as a controlling tty */
189         setsid();
190         ioctl(0, TIOCSCTTY, 0 /* 0: don't forcibly steal */);
191
192         /* signal(SIGCHLD, SIG_DFL); - exec does this for us */
193         execl(shell, shell, shell_opt, shell_arg, NULL);
194         bb_simple_perror_msg_and_die(shell);
195 }