re-mark 1.29b-2 as not yet uploaded (merge madness!)
[debian/tar] / tests / ttyemu.c
1 /* Run program with its first three file descriptors attached to a tty.
2
3    Copyright 2014 Free Software Foundation, Inc.
4
5    This file is part of GNU tar.
6
7    GNU tar is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 3 of the License, or
10    (at your option) any later version.
11
12    GNU tar is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 */
20 #define _XOPEN_SOURCE 600
21 #include <config.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <sys/time.h>
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <sys/wait.h>
28 #include <unistd.h>
29 #include <fcntl.h>
30 #include <errno.h>
31 #include <signal.h>
32 #include <string.h>
33 #include <sys/socket.h>
34 #include <sys/select.h>
35 #include <termios.h>
36 #include <sys/ioctl.h>
37
38 #ifndef TCSASOFT
39 # define TCSASOFT 0
40 #endif
41
42 #define C_EOT 4
43
44 #define EX_OK 0
45 #define EX_USAGE 125
46 #define EX_ERR   126
47 #define EX_EXEC  127
48
49 #define BUF_SIZE 1024
50
51 #if 0
52 # define DEBUG(c) fprintf (stderr, "%s\n", c)
53 #else
54 # define DEBUG(c)
55 #endif
56
57 struct buffer
58 {
59   char buf[BUF_SIZE];
60   int avail;
61   int written;
62   int cr;
63   time_t ts;
64 };
65
66 #define shut(fildes)                                            \
67   do                                                            \
68     {                                                           \
69       DEBUG (("closing " #fildes));                             \
70       close(fildes);                                            \
71       fildes = -1;                                              \
72     }                                                           \
73   while(0)
74
75 #define bufinit(buffer,all)                             \
76   do                                                    \
77     {                                                   \
78       (buffer).avail = (buffer).written = 0;            \
79       (buffer).ts = time (NULL);                        \
80       if (all)                                          \
81         (buffer).cr = 0;                                \
82     }                                                   \
83   while(0)
84
85 #define bufisempty(buffer) ((buffer).avail == (buffer).written)
86 #define bufavail(buffer) (BUF_SIZE - (buffer).avail)
87
88 #define bufread(buffer,fildes,tty)                                      \
89   do                                                                    \
90     {                                                                   \
91       int r = read (fildes, (buffer).buf + (buffer).avail,              \
92                     BUF_SIZE - (buffer).avail);                         \
93       (buffer).ts = time (NULL);                                        \
94       if (r < 0)                                                        \
95         {                                                               \
96           if (errno == EINTR)                                           \
97             continue;                                                   \
98           if (tty && errno == EIO)                                      \
99             shut (fildes);                                              \
100           else                                                          \
101             {                                                           \
102               fprintf (stderr, "%s:%d: reading from %s: %s",            \
103                        __FILE__,__LINE__,#fildes, strerror (errno));    \
104               exit (EX_ERR);                                            \
105             }                                                           \
106         }                                                               \
107       else if (r == 0)                                                  \
108         shut (fildes);                                                  \
109       else                                                              \
110         (buffer).avail += r;                                            \
111     }                                                                   \
112   while(0)
113
114 #define bufwrite(buffer,fildes)                                         \
115   do                                                                    \
116     {                                                                   \
117       int r = write (fildes, (buffer).buf + (buffer).written,           \
118                      (buffer).avail - (buffer).written);                \
119       (buffer).ts = time (NULL);                                        \
120       if (r < 0)                                                        \
121         {                                                               \
122           if (errno == EINTR)                                           \
123             continue;                                                   \
124           if (stop)                                                     \
125             shut (fildes);                                              \
126           else                                                          \
127             {                                                           \
128               perror ("writing");                                       \
129               exit (EX_ERR);                                            \
130             }                                                           \
131         }                                                               \
132       else if (r == 0)                                                  \
133         /*shut (fildes)*/;                                              \
134       else                                                              \
135         (buffer).written += r;                                          \
136     }                                                                   \
137   while(0)
138
139 void
140 tr (struct buffer *bp)
141 {
142   int i, j;
143
144   for (i = j = bp->written; i < bp->avail;)
145     {
146       if (bp->buf[i] == '\r')
147         {
148           bp->cr = 1;
149           i++;
150         }
151       else
152         {
153           if (bp->cr)
154             {
155               bp->cr = 0;
156               if (bp->buf[i] != '\n')
157                 bp->buf[j++] = '\r';
158             }
159           bp->buf[j++] = bp->buf[i++];
160         }
161     }
162   bp->avail = j;
163 }
164
165 int stop;
166 int status;
167
168 void
169 sigchld (int sig)
170 {
171   DEBUG (("child exited"));
172   wait (&status);
173   stop = 1;
174 }
175
176 void
177 noecho (int fd)
178 {
179   struct termios to;
180
181   if (tcgetattr (fd, &to))
182     {
183       perror ("tcgetattr");
184       exit (EX_ERR);
185     }
186   to.c_lflag |= ICANON;
187   to.c_lflag &= ~(ECHO | ISIG);
188   to.c_cc[VEOF] = C_EOT;
189   if (tcsetattr (fd, TCSAFLUSH | TCSASOFT, &to))
190     {
191       perror ("tcsetattr");
192       exit (EX_ERR);
193     }
194 }
195
196 char *usage_text[] = {
197   "usage: ttyemu [-ah] [-i INFILE] [-o OUTFILE] [-t TIMEOUT] PROGRAM [ARGS...]",
198   "ttyemu runs PROGRAM with its first three file descriptors connected to a"
199   " terminal",
200   "",
201   "Options are:",
202   "",
203   "   -a            append output to OUTFILE, instead of overwriting it",
204   "   -i INFILE     read input from INFILE",
205   "   -o OUTFILE    write output to OUTFILE",
206   "   -t TIMEOUT    set I/O timeout",
207   "   -h            print this help summary",
208   "",
209   "Report bugs and suggestions to <bug-tar@gnu.org>.",
210   NULL
211 };
212
213 static void
214 usage (void)
215 {
216   int i;
217   
218   for (i = 0; usage_text[i]; i++)
219     {
220       fputs (usage_text[i], stderr);
221       fputc ('\n', stderr);
222     }
223 }
224
225 int
226 main (int argc, char **argv)
227 {
228   int i;
229   int master, slave;
230   pid_t pid;
231   fd_set rdset, wrset;
232   struct buffer ibuf, obuf;
233   int in = 0, out = 1;
234   char *infile = NULL, *outfile = NULL;
235   int outflags = O_TRUNC;
236   int maxfd;
237   int eot = C_EOT;
238   int timeout = 0;
239   
240   while ((i = getopt (argc, argv, "ai:o:t:h")) != EOF)
241     {
242       switch (i)
243         {
244         case 'a':
245           outflags &= ~O_TRUNC;
246           break;
247         
248         case 'i':
249           infile = optarg;
250           break;
251           
252         case 'o':
253           outfile = optarg;
254           break;
255
256         case 't':
257           timeout = atoi (optarg);
258           break;
259           
260         case 'h':
261           usage ();
262           return EX_OK;
263           
264         default:
265           return EX_USAGE;
266         }
267     }
268   
269   argc -= optind;
270   argv += optind;
271
272   if (argc == 0)
273     {
274       usage ();
275       return EX_USAGE;
276     }
277
278   if (infile)
279     {
280       in = open (infile, O_RDONLY);
281       if (in == -1)
282         {
283           perror (infile);
284           return EX_ERR;
285         }
286     }
287
288   if (outfile)
289     {
290       out = open (outfile, O_RDWR|O_CREAT|outflags, 0666);
291       if (out == -1)
292         {
293           perror (outfile);
294           return EX_ERR;
295         }
296     }
297   
298   master = posix_openpt (O_RDWR);
299   if (master == -1)
300     {
301       perror ("posix_openpty");
302       return EX_ERR;
303     }
304
305   if (grantpt (master))
306     {
307       perror ("grantpt");
308       return EX_ERR;
309     }
310
311   if (unlockpt (master))
312     {
313       perror ("unlockpt");
314       return EX_ERR;
315     }
316   
317   signal (SIGCHLD, sigchld);
318
319   pid = fork ();
320   if (pid == -1)
321     {
322       perror ("fork");
323       return EX_ERR;
324     }
325
326   if (pid == 0)
327     {
328       slave = open (ptsname (master), O_RDWR);
329       if (slave < 0)
330         {
331           perror ("open");
332           return EX_ERR;
333         }
334
335       noecho (slave);
336       for (i = 0; i < 3; i++)
337         {
338           if (slave != i)
339             {
340               close (i);
341               if (dup (slave) != i)
342                 {
343                   perror ("dup");
344                   _exit (EX_EXEC);
345                 }
346             }
347         }
348       for (i = sysconf (_SC_OPEN_MAX) - 1; i > 2; --i)
349         close (i);
350
351       setsid ();
352 #ifdef TIOCSCTTY
353       ioctl (0, TIOCSCTTY, 1);
354 #endif      
355       execvp (argv[0], argv);
356       perror (argv[0]);
357       _exit (EX_EXEC);
358     }
359   sleep (1);
360
361   bufinit (ibuf, 1);
362   bufinit (obuf, 1);
363   while (1)
364     {
365       FD_ZERO (&rdset);
366       FD_ZERO (&wrset);
367       
368       maxfd = 0;
369
370       if (in != -1)
371         {
372           FD_SET (in, &rdset);
373           if (in > maxfd)
374             maxfd = in;
375         }
376
377       if (master != -1)
378         {
379           FD_SET (master, &rdset);
380           if (!stop)
381             FD_SET (master, &wrset);
382           if (master > maxfd)
383             maxfd = master;
384         }
385
386       if (maxfd == 0)
387         {
388           if (stop)
389             break;
390           pause ();
391           continue;
392         }
393       
394       if (select (maxfd + 1, &rdset, &wrset, NULL, NULL) < 0)
395         {
396           if (errno == EINTR)
397             continue;
398           perror ("select");
399           return EX_ERR;
400         }
401
402       if (timeout)
403         {
404           time_t now = time (NULL);
405           if (now - ibuf.ts > timeout || now - obuf.ts > timeout)
406             {
407               fprintf (stderr, "ttyemu: I/O timeout\n");
408               return EX_ERR;
409             }
410         }
411       
412       if (in >= 0)
413         {
414           if (bufavail (ibuf) && FD_ISSET (in, &rdset))
415             bufread (ibuf, in, 0);
416         }
417       else if (master == -1)
418         break;
419
420       if (master >= 0 && FD_ISSET (master, &wrset))
421         {
422           if (!bufisempty (ibuf))
423             bufwrite (ibuf, master);
424           else if (in == -1 && eot)
425             {
426               DEBUG (("sent EOT"));
427               if (write (master, &eot, 1) <= 0)
428                 {
429                   perror ("write");
430                   return EX_ERR;
431                 }
432               eot = 0;
433             }
434         }
435
436       if (master >= 0 && bufavail (obuf) && FD_ISSET (master, &rdset))
437         bufread (obuf, master, 1);
438
439       if (bufisempty (obuf))
440         bufinit (obuf, 0);
441       else
442         {
443           tr (&obuf);
444           bufwrite (obuf, out);
445         }
446       
447       if (bufisempty (ibuf))
448         bufinit (ibuf, 0);
449     }
450
451   if (WIFEXITED (status))
452     return WEXITSTATUS (status);
453
454   if (WIFSIGNALED (status))
455     fprintf (stderr, "ttyemu: child process %s failed on signal %d\n",
456              argv[0], WTERMSIG (status));
457   else
458     fprintf (stderr, "ttyemu: child process %s failed\n", argv[0]);
459   return EX_EXEC;
460 }