Add -n (not for real) option. The program goes through motions, but
[dragonfly.git] / usr.sbin / dntpd / main.c
1 /*
2  * Copyright (c) 2005 The DragonFly Project.  All rights reserved.
3  * 
4  * This code is derived from software contributed to The DragonFly Project
5  * by Matthew Dillon <dillon@backplane.com>
6  * 
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in
15  *    the documentation and/or other materials provided with the
16  *    distribution.
17  * 3. Neither the name of The DragonFly Project nor the names of its
18  *    contributors may be used to endorse or promote products derived
19  *    from this software without specific, prior written permission.
20  * 
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
25  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  * 
34  * $DragonFly: src/usr.sbin/dntpd/main.c,v 1.6 2005/04/26 00:56:54 dillon Exp $
35  */
36
37 #include "defs.h"
38
39 static void usage(const char *av0);
40 static void dotest(const char *target);
41 static void add_server(const char *target);
42 static void process_config_file(const char *path);
43 static pid_t check_pid(void);
44 static void set_pid(const char *av0);
45 static void sigint_handler(int signo);
46
47 static struct server_info **servers;
48 static int nservers;
49 static int maxservers;
50
51 int daemon_opt = 1;
52 int debug_opt = 0;
53 int debug_level = -1;           /* (set to default later) */
54 int quickset_opt = 0;           /* immediate set time of day on startup */
55 int no_update_opt = 0;          /* do not make any actual updates */
56 int min_sleep_opt = 5;          /* 5 seconds minimum poll interval */
57 int nom_sleep_opt = 300;        /* 5 minutes nominal poll interval */
58 int max_sleep_opt = 1800;       /* 30 minutes maximum poll interval */
59 const char *config_opt;         /* config file */
60 const char *pid_opt = "/var/run/dntpd.pid";
61
62 int
63 main(int ac, char **av)
64 {
65     int test_opt = 0;
66     pid_t pid;
67     int rc;
68     int ch;
69     int i;
70
71     /*
72      * Really randomize
73      */
74     srandomdev();
75     rc = 0;
76
77     /*
78      * Process Options
79      */
80     while ((ch = getopt(ac, av, "df:l:np:qstFL:QST:")) != -1) {
81         switch(ch) {
82         case 'd':
83             debug_opt = 1;
84             daemon_opt = 0;
85             if (debug_level < 0)
86                 debug_level = 99;
87             if (config_opt == NULL)
88                 config_opt = "/dev/null";
89             break;
90         case 'p':
91             pid_opt = optarg;
92             break;
93         case 'f':
94             config_opt = optarg;
95             break;
96         case 'l':
97             debug_level = strtol(optarg, NULL, 0);
98             break;
99         case 'n':
100             no_update_opt = 1;
101             break;
102         case 'q':
103             debug_level = 0;
104             break;
105         case 's':
106             quickset_opt = 1;
107             fprintf(stderr, "%s: warning, -s not currently implemented\n", 
108                     av[0]);
109             break;
110         case 'S':
111             quickset_opt = 0;
112             break;
113         case 't':
114             test_opt = 1;
115             debug_opt = 1;
116             daemon_opt = 0;
117             if (debug_level < 0)
118                 debug_level = 99;
119             if (config_opt == NULL)
120                 config_opt = "/dev/null";
121             break;
122         case 'F':
123             daemon_opt = 0;
124             break;
125         case 'L':
126             max_sleep_opt = strtol(optarg, NULL, 0);
127             break;
128         case 'T':
129             nom_sleep_opt = strtol(optarg, NULL, 0);
130             if (nom_sleep_opt < 1) {
131                 fprintf(stderr, "Warning: nominal poll interval too small, "
132                                 "limiting to 1 second\n");
133                 nom_sleep_opt = 1;
134             }
135             if (nom_sleep_opt > 24 * 60 * 60) {
136                 fprintf(stderr, "Warning: nominal poll interval too large, "
137                                 "limiting to 24 hours\n");
138                 nom_sleep_opt = 24 * 60 * 60;
139             }
140             if (min_sleep_opt > nom_sleep_opt)
141                 min_sleep_opt = nom_sleep_opt;
142             if (max_sleep_opt < nom_sleep_opt * 5)
143                 max_sleep_opt = nom_sleep_opt * 5;
144             break;
145         case 'Q':
146             if ((pid = check_pid()) != 0) {
147                 fprintf(stderr, "%s: killing old daemon\n", av[0]);
148                 kill(pid, SIGINT);
149                 usleep(100000);
150                 if (check_pid())
151                     sleep(1);
152                 if (check_pid())
153                     sleep(9);
154                 if (check_pid()) {
155                     fprintf(stderr, "%s: Unable to kill running daemon.\n", av[0]);
156                 } else {
157                     fprintf(stderr, "%s: Running daemon has been terminated.\n", av[0]);
158                 }
159             } else {
160                 fprintf(stderr, "%s: There is no daemon running to kill.\n", av[0]);
161             }
162             exit(0);
163             break;
164         case 'h':
165         default:
166             usage(av[0]);
167             /* not reached */
168         }
169     }
170     if (config_opt == NULL) {
171         if (optind != ac)
172             config_opt = "/dev/null";
173         else
174             config_opt = "/etc/dntpd.conf";
175     }
176
177     if (debug_level < 0)
178         debug_level = 1;
179
180     process_config_file(config_opt);
181
182     if (debug_opt == 0)
183         openlog("dntpd", LOG_CONS|LOG_PID, LOG_DAEMON);
184
185     if (test_opt) {
186         if (optind != ac - 1)
187             usage(av[0]);
188         dotest(av[optind]);
189         /* not reached */
190     }
191
192     /*
193      * Add additional hosts.
194      */
195     for (i = optind; i < ac; ++i) {
196         add_server(av[i]);
197     }
198     if (nservers == 0) {
199         usage(av[0]);
200         /* not reached */
201     }
202
203     /*
204      * Do an initial course time setting if requested using the first
205      * host successfully polled.
206      */
207     /* XXX */
208
209     /*
210      * Daemonize, stop logging to stderr.
211      */
212     if (daemon_opt) {
213         if ((pid = check_pid()) != 0) {
214             logerrstr("%s: NOTE: killing old daemon and starting a new one", 
215                         av[0]);
216             kill(pid, SIGINT);
217             usleep(100000);
218             if (check_pid())
219                 sleep(1);
220             if (check_pid())
221                 sleep(9);
222             if (check_pid()) {
223                 logerrstr("%s: Unable to kill running daemon, exiting", av[0]);
224                 exit(1);
225             }
226         }
227         daemon(0, 0);
228     } else if (check_pid() != 0) {
229         logerrstr("%s: A background dntpd is running, you must kill it first",
230                 av[0]);
231         exit(1);
232     }
233     if (debug_opt == 0) {
234         log_stderr = 0;
235         set_pid(av[0]);
236         signal(SIGINT, sigint_handler);
237         logdebug(0, "dntpd version %s started\n", DNTPD_VERSION);
238     }
239
240     /*
241      * And go.
242      */
243     sysntp_clear_alternative_corrections();
244     client_init();
245     rc = client_main(servers, nservers);
246     return(rc);
247 }
248
249 static
250 void
251 usage(const char *av0)
252 {
253     fprintf(stderr, "%s [-dqstFS] [-l log_level] [-T poll_interval] [-L poll_limit] [additional_targets]\n", av0);
254     fprintf(stderr, 
255         "\t-d\tDebugging mode, implies -F, -l 99, and logs to stderr\n"
256         "\t-f file\tSpecify the config file (/etc/dntpd.conf)\n"
257         "\t-l int\tSet log level (0-4), default 1\n"
258         "\t-n\tNo-update mode.  No offset or frequency corrections are made\n"
259         "\t-q\tQuiet-mode, same as -L 0\n"
260         "\t-s\tSet the time immediately on startup\n"
261         "\t-t\tTest mode, implies -F, -l 99, -n, logs to stderr\n"
262         "\t-F\tRun in foreground (log still goes to syslog)\n"
263         "\t-L int\tMaximum polling interval\n"
264         "\t-S\tDo not set the time immediately on startup\n"
265         "\t-T int\tNominal polling interval\n"
266         "\t-Q\tTerminate any running background daemon\n"
267         "\n"
268         "\t\tNOTE: in debug and test modes -f must be specified if\n"
269         "\t\tyou want to use a config file.\n"
270     );
271     exit(1);
272 }
273
274 static
275 void
276 dotest(const char *target)
277 {
278     struct server_info info;
279
280     bzero(&info, sizeof(info));
281     info.fd = udp_socket(target, 123);
282     if (info.fd < 0) {
283         logerrstr("unable to create UDP socket for %s", target);
284         return;
285     }
286     info.target = strdup(target);
287     client_init();
288
289     fprintf(stderr, 
290             "Will run %d-second polls until interrupted.\n", nom_sleep_opt);
291
292     for (;;) {
293         client_poll(&info, nom_sleep_opt);
294         sleep(nom_sleep_opt);
295     }
296     /* not reached */
297 }
298
299 static void
300 add_server(const char *target)
301 {
302     server_info_t info;
303
304     if (nservers == maxservers) {
305         maxservers += 16;
306         servers = realloc(servers, maxservers * sizeof(server_info_t));
307         assert(servers != NULL);
308     }
309     info = malloc(sizeof(struct server_info));
310     servers[nservers] = info;
311     bzero(info, sizeof(struct server_info));
312     info->fd = udp_socket(target, 123);
313     if (info->fd < 0) {
314         logerrstr("Unable to add server %s", target);
315     } else {
316         info->target = strdup(target);
317         ++nservers;
318     }
319 }
320
321 static void
322 process_config_file(const char *path)
323 {
324     const char *ws = " \t\r\n";
325     char buf[1024];
326     char *keyword;
327     char *data;
328     int line;
329     FILE *fi;
330
331     if ((fi = fopen(path, "r")) != NULL) {
332         line = 1;
333         while (fgets(buf, sizeof(buf), fi) != NULL) {
334             if (strchr(buf, '#'))
335                 *strchr(buf, '#') = 0;
336             if ((keyword = strtok(buf, ws)) != NULL) {
337                 data = strtok(NULL, ws);
338                 if (strcmp(keyword, "server") == 0) {
339                     if (data == NULL) {
340                         logerr("%s:%d server missing host specification",
341                                 path, line);
342                     } else {
343                         add_server(data);
344                     }
345                 } else {
346                     logerr("%s:%d unknown keyword %s", path, line, keyword);
347                 }
348             }
349             ++line;
350         }
351         fclose(fi);
352     } else {
353         logerr("Unable to open %s", path);
354         exit(1);
355     }
356 }
357
358 static 
359 pid_t
360 check_pid(void)
361 {
362     char buf[32];
363     pid_t pid;
364     FILE *fi;
365
366     pid = 0;
367     if ((fi = fopen(pid_opt, "r")) != NULL) {
368         if (fgets(buf, sizeof(buf), fi) != NULL) {
369             pid = strtol(buf, NULL, 0);
370             if (kill(pid, 0) != 0)
371                 pid = 0;
372         }
373         fclose(fi);
374     }
375     return(pid);
376 }
377
378 static 
379 void
380 set_pid(const char *av0)
381 {
382     pid_t pid;
383     FILE *fo;
384
385     pid = getpid();
386     if ((fo = fopen(pid_opt, "w")) != NULL) {
387         fprintf(fo, "%d\n", (int)pid);
388         fclose(fo);
389     } else {
390         logerr("%s: Unable to create %s, continuing anyway.", av0, pid_opt);
391     }
392 }
393
394 static
395 void
396 sigint_handler(int signo)
397 {
398     remove(pid_opt);
399     /* dangerous, but we are exiting anyway so pray... */
400     logdebug(0, "dntpd version %s stopped\n", DNTPD_VERSION);
401     exit(0);
402 }
403