chiark / gitweb /
fd688e52d95467a6a282265d9c408310425810a5
[misc] / pause.c
1 /* -*-c-*-
2  *
3  * $Id$
4  *
5  * Pause for a while
6  *
7  * (c) 1999 Mark Wooding
8  */
9
10 /*----- Licensing notice --------------------------------------------------* 
11  *
12  * This file is part of Pause.
13  *
14  * Pause is free software; you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License as published by
16  * the Free Software Foundation; either version 2 of the License, or
17  * (at your option) any later ersion.
18  * 
19  * Pause is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  * 
24  * You should have received a copy of the GNU General Public License
25  * along with Pause; if not, write to the Free Software Foundation,
26  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27  */
28
29 /*----- Header files ------------------------------------------------------*/
30
31 #include <errno.h>
32 #include <math.h>
33 #include <signal.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37
38 #include <sys/types.h>
39 #include <sys/time.h>
40 #include <unistd.h>
41 #include <termios.h>
42
43 #include <mLib/mdwopt.h>
44 #include <mLib/quis.h>
45 #include <mLib/report.h>
46 #include <mLib/tv.h>
47
48 /*----- Static variables --------------------------------------------------*/
49
50 static struct termios ts_old, ts;
51
52 /*----- Main code ---------------------------------------------------------*/
53
54 /* --- @sig@ --- *
55  *
56  * Arguments:   @int s@ = signal number
57  *
58  * Returns:     Doesn't.
59  *
60  * Use:         Handles various fatal signals by resetting the terminal state
61  *              and re-emitting the signal.
62  */
63
64 static void sig(int s)
65 {
66   tcsetattr(STDIN_FILENO, TCSAFLUSH, &ts_old);
67   signal(s, SIG_DFL);
68   raise(s);
69 }
70
71 /* --- @suspend@ --- *
72  *
73  * Arguments:   @int s@ = signal number
74  *
75  * Returns:     ---
76  *
77  * Use:         Handles a job control signal, as recommended in APUE.  It
78  *              resets the terminal state, resets the signal disposition,
79  *              unblocks the signal and re-emits it (so that the shell
80  *              shows the right message).  When that call returns, the
81  *              signal handler is reattached, and the terminal state is set
82  *              again.
83  */
84
85 static void suspend(int s)
86 {
87   sigset_t ss;
88
89   tcsetattr(STDIN_FILENO, TCSAFLUSH, &ts_old);
90   signal(s, SIG_DFL);
91   sigemptyset(&ss);
92   sigaddset(&ss, s);
93   sigprocmask(SIG_UNBLOCK, &ss, 0);
94   raise(s);
95   signal(s, suspend);
96   tcsetattr(STDIN_FILENO, TCSAFLUSH, &ts);
97 }
98
99 /* --- Traditional GNUey help options --- */
100
101 static void usage(FILE *fp)
102 {
103   pquis(fp, "Usage: $ [time]\n");
104 }
105
106 static void version(FILE *fp)
107 {
108   pquis(fp, "$ version " VERSION "\n");
109 }
110
111 static void help(FILE *fp)
112 {
113   version(fp);
114   fputc('\n', fp);
115   usage(fp);
116   pquis(fp, "\n\
117 Pauses until a key is pressed or an optional time-limit expires.  The\n\
118 time is given as a floating point number with an option suffix of `s',\n\
119 `m', `h' or `d' (for `seconds', `minutes', `hours' or `days'\n\
120 respectively).\n\
121 \n\
122 Options available:\n\
123 \n\
124 -h, --help      Show this help message.\n\
125 -v, --version   Show program's version number.\n\
126 -u, --usage     Show terse usage summary.\n\
127 ");
128 }
129
130 /* --- @main@ --- *
131  *
132  * Arguments:   @int argc@ = number of command line arguments
133  *              @char *argv[]@ = vector of command line arguments
134  *
135  * Returns:     Nonzero if it failed, zero if everything went well.
136  *
137  * Use:         Souped-up version of `sleep'.
138  */
139
140 int main(int argc, char *argv[])
141 {
142   struct termios ts;
143   unsigned f = 0;
144   struct timeval when;
145
146   enum {
147     f_bogus = 1u,
148     f_timer = 2u,
149     f_key = 4u
150   };
151
152   /* --- Library initialization --- */
153
154   ego(argv[0]);
155
156   /* --- Parse command line options --- */
157
158   for (;;) {
159     static struct option opt[] = {
160       { "help",         0,              0,      'h' },
161       { "version",      0,              0,      'v' },
162       { "usage",        0,              0,      'u' },
163       { 0,              0,              0,      0 }
164     };
165     int i = mdwopt(argc, argv, "hvu", opt, 0, 0, 0);
166     if (i < 0)
167       break;
168     switch (i) {
169       case 'h':
170         help(stderr);
171         exit(0);
172       case 'v':
173         version(stderr);
174         exit(0);
175       case 'u':
176         usage(stderr);
177         exit(0);
178       default:
179         f |= f_bogus;
180         break;
181     }
182   }
183
184   /* --- Parse a timer spec --- */
185
186   if (!(f & f_bogus) && optind < argc) {
187     const char *p =argv[optind++];
188     char *q;
189     double t = strtod(p, &q);
190     switch (*q) {
191       case 'd': t *= 24;
192       case 'h': t *= 60;
193       case 'm': t *= 60;
194       case 's': if (q[1] != 0)
195       default:    t = 0;
196       case 0:   break;
197     }
198     if (t <= 0)
199       die(1, "bad time specification `%s'", p);
200     gettimeofday(&when, 0);
201     TV_ADDL(&when, &when, t, (t - floor(t)) * MILLION);
202     f |= f_timer;
203   }
204
205   /* --- Report a syntax error --- */
206
207   if (optind < argc)
208     f |= f_bogus;
209   if (f & f_bogus) {
210     usage(stderr);
211     exit(1);
212   }
213
214   /* --- Set up terminal for single keypresses --- */
215
216   if (tcgetattr(STDIN_FILENO, &ts_old) == 0) {
217     static struct { int sig; void (*func)(int); } sigs[] = {
218       { SIGINT, sig },
219       { SIGQUIT, sig },
220       { SIGTERM, sig },
221       { SIGTSTP, suspend },
222       { 0, 0 }
223     };
224     int i;
225
226     /* --- Set up the new terminal attributes --- */
227
228     ts = ts_old;
229     ts.c_lflag &= ~(ECHO | ICANON | TOSTOP);
230
231     /* --- Set up signal handlers to reset attributes --- */
232
233     for (i = 0; sigs[i].sig; i++) {
234       struct sigaction sa;
235       sigaction(sigs[i].sig, 0, &sa);
236       if (sa.sa_handler != SIG_IGN)
237         signal(sigs[i].sig, sigs[i].func);
238     }
239     signal(SIGTTIN, suspend);
240
241     /* --- Commit the terminal attribute changes --- */
242
243     tcsetattr(STDIN_FILENO, TCSAFLUSH, &ts);
244     f |= f_key;
245   }
246
247   /* --- Main loop --- */
248
249   for (;;) {
250     fd_set fd, *pfd;
251     int maxfd;
252     struct timeval tv, *ptv;
253
254     /* --- Wait for a keypress --- */
255
256     if (f & f_key) {
257       FD_ZERO(&fd);
258       FD_SET(STDIN_FILENO, &fd);
259       maxfd = STDIN_FILENO + 1;
260       pfd = &fd;
261     } else {
262       maxfd = 0;
263       pfd = 0;
264     }
265
266     /* --- Wait for the timer to expire --- */
267
268     if (f & f_timer) {
269       gettimeofday(&tv, 0);
270       TV_SUB(&tv, &when, &tv);
271       ptv = &tv;
272     } else
273       ptv = 0;
274
275     /* --- See what interesting things have happened --- */
276
277     if (select(maxfd, pfd, 0, 0, ptv) < 0) {
278       if (errno == EINTR || errno == EAGAIN)
279         continue;
280       die(1, "error in select: %s", strerror(errno));
281     }
282
283     /* --- Check the timer --- */
284
285     if (f & f_timer) {
286       gettimeofday(&tv, 0);
287       if (TV_CMP(&tv, >=, &when))
288         break;
289     }
290
291     /* --- Check the key state --- */
292
293     if (f & f_key && FD_ISSET(STDIN_FILENO, &fd))
294       break;
295   }
296
297   /* --- See what gives --- */
298
299   if (f & f_key)
300     tcsetattr(STDIN_FILENO, TCSAFLUSH, &ts_old);
301   return (0);
302 }
303
304 /*----- That's all, folks -------------------------------------------------*/