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