1*f1b790a5Sclaudio /* $OpenBSD: control.c,v 1.27 2024/11/21 13:38:14 claudio Exp $ */ 264c82965Sphessler 364c82965Sphessler /* 464c82965Sphessler * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org> 564c82965Sphessler * Copyright (c) 2012 Mike Miller <mmiller@mgm51.com> 664c82965Sphessler * 764c82965Sphessler * Permission to use, copy, modify, and distribute this software for any 864c82965Sphessler * purpose with or without fee is hereby granted, provided that the above 964c82965Sphessler * copyright notice and this permission notice appear in all copies. 1064c82965Sphessler * 1164c82965Sphessler * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 1264c82965Sphessler * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 1364c82965Sphessler * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 1464c82965Sphessler * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 1564c82965Sphessler * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 1664c82965Sphessler * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 1764c82965Sphessler * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 1864c82965Sphessler */ 1964c82965Sphessler 2064c82965Sphessler #include <sys/types.h> 2164c82965Sphessler #include <sys/stat.h> 2264c82965Sphessler #include <sys/socket.h> 2364c82965Sphessler #include <sys/un.h> 2464c82965Sphessler #include <errno.h> 255fbd837dStedu #include <math.h> 2664c82965Sphessler #include <stdio.h> 2764c82965Sphessler #include <stdlib.h> 2864c82965Sphessler #include <string.h> 2964c82965Sphessler #include <unistd.h> 3064c82965Sphessler #include <fcntl.h> 3164c82965Sphessler #include <err.h> 3264c82965Sphessler 3364c82965Sphessler #include "ntpd.h" 3464c82965Sphessler 3564c82965Sphessler #define CONTROL_BACKLOG 5 3664c82965Sphessler 375fbd837dStedu #define square(x) ((x) * (x)) 385fbd837dStedu 3964c82965Sphessler int 40febce360Sflorian control_check(char *path) 41febce360Sflorian { 42febce360Sflorian struct sockaddr_un sun; 43febce360Sflorian int fd; 44febce360Sflorian 45febce360Sflorian bzero(&sun, sizeof(sun)); 46febce360Sflorian sun.sun_family = AF_UNIX; 47febce360Sflorian strlcpy(sun.sun_path, path, sizeof(sun.sun_path)); 48febce360Sflorian 49febce360Sflorian if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { 50e6c01febSotto log_debug("control_check: socket check"); 51febce360Sflorian return (-1); 52febce360Sflorian } 53febce360Sflorian 54febce360Sflorian if (connect(fd, (struct sockaddr *)&sun, sizeof(sun)) == 0) { 55e6c01febSotto log_debug("control_check: socket in use"); 56febce360Sflorian close(fd); 57febce360Sflorian return (-1); 58febce360Sflorian } 59febce360Sflorian 60febce360Sflorian close(fd); 61febce360Sflorian 62febce360Sflorian return (0); 63febce360Sflorian } 64febce360Sflorian 65febce360Sflorian int 6664c82965Sphessler control_init(char *path) 6764c82965Sphessler { 68accd9e67Sbcook struct sockaddr_un sa; 6964c82965Sphessler int fd; 7064c82965Sphessler mode_t old_umask; 7164c82965Sphessler 724e840e7aSrzalamena if ((fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0)) == -1) { 7364c82965Sphessler log_warn("control_init: socket"); 7464c82965Sphessler return (-1); 7564c82965Sphessler } 7664c82965Sphessler 77842d7e97Sbcook memset(&sa, 0, sizeof(sa)); 78accd9e67Sbcook sa.sun_family = AF_UNIX; 79accd9e67Sbcook if (strlcpy(sa.sun_path, path, sizeof(sa.sun_path)) >= 80accd9e67Sbcook sizeof(sa.sun_path)) 8164c82965Sphessler errx(1, "ctl socket name too long"); 8264c82965Sphessler 8364c82965Sphessler if (unlink(path) == -1) 8464c82965Sphessler if (errno != ENOENT) { 8564c82965Sphessler log_warn("control_init: unlink %s", path); 8664c82965Sphessler close(fd); 8764c82965Sphessler return (-1); 8864c82965Sphessler } 8964c82965Sphessler 9064c82965Sphessler old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH); 91accd9e67Sbcook if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) { 9264c82965Sphessler log_warn("control_init: bind: %s", path); 9364c82965Sphessler close(fd); 9464c82965Sphessler umask(old_umask); 9564c82965Sphessler return (-1); 9664c82965Sphessler } 9764c82965Sphessler umask(old_umask); 9864c82965Sphessler 9964c82965Sphessler if (chmod(path, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) == -1) { 10064c82965Sphessler log_warn("control_init: chmod"); 10164c82965Sphessler close(fd); 10264c82965Sphessler (void)unlink(path); 10364c82965Sphessler return (-1); 10464c82965Sphessler } 10564c82965Sphessler 1068bf26dd9Skrw session_socket_nonblockmode(fd); 10764c82965Sphessler 10864c82965Sphessler return (fd); 10964c82965Sphessler } 11064c82965Sphessler 11164c82965Sphessler int 11264c82965Sphessler control_listen(int fd) 11364c82965Sphessler { 11464c82965Sphessler if (fd != -1 && listen(fd, CONTROL_BACKLOG) == -1) { 11564c82965Sphessler log_warn("control_listen: listen"); 11664c82965Sphessler return (-1); 11764c82965Sphessler } 11864c82965Sphessler 11964c82965Sphessler return (0); 12064c82965Sphessler } 12164c82965Sphessler 12264c82965Sphessler void 12364c82965Sphessler control_shutdown(int fd) 12464c82965Sphessler { 12564c82965Sphessler close(fd); 12664c82965Sphessler } 12764c82965Sphessler 12864c82965Sphessler int 12964c82965Sphessler control_accept(int listenfd) 13064c82965Sphessler { 13164c82965Sphessler int connfd; 13264c82965Sphessler socklen_t len; 133accd9e67Sbcook struct sockaddr_un sa; 13464c82965Sphessler struct ctl_conn *ctl_conn; 13564c82965Sphessler 136accd9e67Sbcook len = sizeof(sa); 13764c82965Sphessler if ((connfd = accept(listenfd, 138accd9e67Sbcook (struct sockaddr *)&sa, &len)) == -1) { 13964c82965Sphessler if (errno != EWOULDBLOCK && errno != EINTR) 14064c82965Sphessler log_warn("control_accept: accept"); 14164c82965Sphessler return (0); 14264c82965Sphessler } 14364c82965Sphessler 1448bf26dd9Skrw session_socket_nonblockmode(connfd); 14564c82965Sphessler 14664c82965Sphessler if ((ctl_conn = calloc(1, sizeof(struct ctl_conn))) == NULL) { 14764c82965Sphessler log_warn("control_accept"); 14864c82965Sphessler close(connfd); 14964c82965Sphessler return (0); 15064c82965Sphessler } 15164c82965Sphessler 152*f1b790a5Sclaudio if (imsgbuf_init(&ctl_conn->ibuf, connfd) == -1) { 153*f1b790a5Sclaudio log_warn("control_accept"); 154*f1b790a5Sclaudio close(connfd); 155*f1b790a5Sclaudio free(ctl_conn); 156*f1b790a5Sclaudio return (0); 157*f1b790a5Sclaudio } 15864c82965Sphessler 15964c82965Sphessler TAILQ_INSERT_TAIL(&ctl_conns, ctl_conn, entry); 16064c82965Sphessler 16164c82965Sphessler return (1); 16264c82965Sphessler } 16364c82965Sphessler 16464c82965Sphessler struct ctl_conn * 16564c82965Sphessler control_connbyfd(int fd) 16664c82965Sphessler { 16764c82965Sphessler struct ctl_conn *c; 16864c82965Sphessler 1694ff7cad5Skrw TAILQ_FOREACH(c, &ctl_conns, entry) { 1704ff7cad5Skrw if (c->ibuf.fd == fd) 1714ff7cad5Skrw break; 1724ff7cad5Skrw } 17364c82965Sphessler 17464c82965Sphessler return (c); 17564c82965Sphessler } 17664c82965Sphessler 17764c82965Sphessler int 17864c82965Sphessler control_close(int fd) 17964c82965Sphessler { 18064c82965Sphessler struct ctl_conn *c; 18164c82965Sphessler 18264c82965Sphessler if ((c = control_connbyfd(fd)) == NULL) { 18364c82965Sphessler log_warn("control_close: fd %d: not found", fd); 18464c82965Sphessler return (0); 18564c82965Sphessler } 18664c82965Sphessler 1879cbf9e90Sclaudio imsgbuf_clear(&c->ibuf); 18864c82965Sphessler TAILQ_REMOVE(&ctl_conns, c, entry); 18964c82965Sphessler 19064c82965Sphessler close(c->ibuf.fd); 19164c82965Sphessler free(c); 19264c82965Sphessler 19364c82965Sphessler return (1); 19464c82965Sphessler } 19564c82965Sphessler 19664c82965Sphessler int 19764c82965Sphessler control_dispatch_msg(struct pollfd *pfd, u_int *ctl_cnt) 19864c82965Sphessler { 19964c82965Sphessler struct imsg imsg; 20064c82965Sphessler struct ctl_conn *c; 20164c82965Sphessler struct ntp_peer *p; 20264c82965Sphessler struct ntp_sensor *s; 20364c82965Sphessler struct ctl_show_status c_status; 20464c82965Sphessler struct ctl_show_peer c_peer; 20564c82965Sphessler struct ctl_show_sensor c_sensor; 20664c82965Sphessler int cnt; 20764c82965Sphessler ssize_t n; 20864c82965Sphessler 20964c82965Sphessler if ((c = control_connbyfd(pfd->fd)) == NULL) { 21064c82965Sphessler log_warn("control_dispatch_msg: fd %d: not found", pfd->fd); 21164c82965Sphessler return (0); 21264c82965Sphessler } 21364c82965Sphessler 21464c82965Sphessler if (pfd->revents & POLLOUT) 215dd7efffeSclaudio if (imsgbuf_write(&c->ibuf) == -1) { 21664c82965Sphessler *ctl_cnt -= control_close(pfd->fd); 21764c82965Sphessler return (1); 21864c82965Sphessler } 21964c82965Sphessler 22064c82965Sphessler if (!(pfd->revents & POLLIN)) 22164c82965Sphessler return (0); 22264c82965Sphessler 223f6bd242eSclaudio if (imsgbuf_read(&c->ibuf) != 1) { 22464c82965Sphessler *ctl_cnt -= control_close(pfd->fd); 22564c82965Sphessler return (1); 22664c82965Sphessler } 22764c82965Sphessler 22864c82965Sphessler for (;;) { 22964c82965Sphessler if ((n = imsg_get(&c->ibuf, &imsg)) == -1) { 23064c82965Sphessler *ctl_cnt -= control_close(pfd->fd); 23164c82965Sphessler return (1); 23264c82965Sphessler } 23364c82965Sphessler if (n == 0) 23464c82965Sphessler break; 23564c82965Sphessler 23664c82965Sphessler switch (imsg.hdr.type) { 23764c82965Sphessler case IMSG_CTL_SHOW_STATUS: 23864c82965Sphessler build_show_status(&c_status); 23964c82965Sphessler imsg_compose(&c->ibuf, IMSG_CTL_SHOW_STATUS, 0, 0, -1, 24064c82965Sphessler &c_status, sizeof (c_status)); 24164c82965Sphessler break; 24264c82965Sphessler case IMSG_CTL_SHOW_PEERS: 24364c82965Sphessler cnt = 0; 24464c82965Sphessler TAILQ_FOREACH(p, &conf->ntp_peers, entry) { 24564c82965Sphessler build_show_peer(&c_peer, p); 24664c82965Sphessler imsg_compose(&c->ibuf, IMSG_CTL_SHOW_PEERS, 24764c82965Sphessler 0, 0, -1, &c_peer, sizeof(c_peer)); 24864c82965Sphessler cnt++; 24964c82965Sphessler } 25064c82965Sphessler imsg_compose(&c->ibuf, IMSG_CTL_SHOW_PEERS_END, 25164c82965Sphessler 0, 0, -1, &cnt, sizeof(cnt)); 25264c82965Sphessler break; 25364c82965Sphessler case IMSG_CTL_SHOW_SENSORS: 25464c82965Sphessler cnt = 0; 25564c82965Sphessler TAILQ_FOREACH(s, &conf->ntp_sensors, entry) { 25664c82965Sphessler build_show_sensor(&c_sensor, s); 25764c82965Sphessler imsg_compose(&c->ibuf, IMSG_CTL_SHOW_SENSORS, 25864c82965Sphessler 0, 0, -1, &c_sensor, sizeof(c_sensor)); 25964c82965Sphessler cnt++; 26064c82965Sphessler } 26164c82965Sphessler imsg_compose(&c->ibuf, IMSG_CTL_SHOW_SENSORS_END, 26264c82965Sphessler 0, 0, -1, &cnt, sizeof(cnt)); 26364c82965Sphessler break; 26464c82965Sphessler case IMSG_CTL_SHOW_ALL: 26564c82965Sphessler build_show_status(&c_status); 26664c82965Sphessler imsg_compose(&c->ibuf, IMSG_CTL_SHOW_STATUS, 0, 0, -1, 26764c82965Sphessler &c_status, sizeof (c_status)); 26864c82965Sphessler 26964c82965Sphessler cnt = 0; 27064c82965Sphessler TAILQ_FOREACH(p, &conf->ntp_peers, entry) { 27164c82965Sphessler build_show_peer(&c_peer, p); 27264c82965Sphessler imsg_compose(&c->ibuf, IMSG_CTL_SHOW_PEERS, 27364c82965Sphessler 0, 0, -1, &c_peer, sizeof(c_peer)); 27464c82965Sphessler cnt++; 27564c82965Sphessler } 27664c82965Sphessler imsg_compose(&c->ibuf, IMSG_CTL_SHOW_PEERS_END, 27764c82965Sphessler 0, 0, -1, &cnt, sizeof(cnt)); 27864c82965Sphessler 27964c82965Sphessler cnt = 0; 28064c82965Sphessler TAILQ_FOREACH(s, &conf->ntp_sensors, entry) { 28164c82965Sphessler build_show_sensor(&c_sensor, s); 28264c82965Sphessler imsg_compose(&c->ibuf, IMSG_CTL_SHOW_SENSORS, 28364c82965Sphessler 0, 0, -1, &c_sensor, sizeof(c_sensor)); 28464c82965Sphessler cnt++; 28564c82965Sphessler } 28664c82965Sphessler imsg_compose(&c->ibuf, IMSG_CTL_SHOW_SENSORS_END, 28764c82965Sphessler 0, 0, -1, &cnt, sizeof(cnt)); 28864c82965Sphessler 28964c82965Sphessler imsg_compose(&c->ibuf, IMSG_CTL_SHOW_ALL_END, 29064c82965Sphessler 0, 0, -1, NULL, 0); 29164c82965Sphessler break; 29264c82965Sphessler default: 29364c82965Sphessler break; 29464c82965Sphessler } 29564c82965Sphessler imsg_free(&imsg); 29664c82965Sphessler } 29764c82965Sphessler return (0); 29864c82965Sphessler } 29964c82965Sphessler 30064c82965Sphessler void 3018bf26dd9Skrw session_socket_nonblockmode(int fd) 30264c82965Sphessler { 30364c82965Sphessler int flags; 30464c82965Sphessler 3058bf26dd9Skrw if ((flags = fcntl(fd, F_GETFL)) == -1) 30664c82965Sphessler fatal("fcntl F_GETFL"); 30764c82965Sphessler 30864c82965Sphessler flags |= O_NONBLOCK; 30964c82965Sphessler 31064c82965Sphessler if ((flags = fcntl(fd, F_SETFL, flags)) == -1) 31164c82965Sphessler fatal("fcntl F_SETFL"); 31264c82965Sphessler } 31364c82965Sphessler 31464c82965Sphessler void 31564c82965Sphessler build_show_status(struct ctl_show_status *cs) 31664c82965Sphessler { 31764c82965Sphessler struct ntp_peer *p; 31864c82965Sphessler struct ntp_sensor *s; 31964c82965Sphessler 32064c82965Sphessler cs->peercnt = cs->valid_peers = 0; 32164c82965Sphessler cs->sensorcnt = cs->valid_sensors = 0; 32264c82965Sphessler 32364c82965Sphessler TAILQ_FOREACH(p, &conf->ntp_peers, entry) { 32464c82965Sphessler cs->peercnt++; 32564c82965Sphessler if (p->trustlevel >= TRUSTLEVEL_BADPEER) 32664c82965Sphessler cs->valid_peers++; 32764c82965Sphessler } 32864c82965Sphessler TAILQ_FOREACH(s, &conf->ntp_sensors, entry) { 32964c82965Sphessler cs->sensorcnt++; 33064c82965Sphessler if (s->update.good) 33164c82965Sphessler cs->valid_sensors++; 33264c82965Sphessler } 33364c82965Sphessler 33464c82965Sphessler cs->synced = conf->status.synced; 33564c82965Sphessler cs->stratum = conf->status.stratum; 33664c82965Sphessler cs->clock_offset = getoffset() * 1000.0; 3371908d877Sotto cs->constraints = !TAILQ_EMPTY(&conf->constraints); 338bc58a738Sreyk cs->constraint_median = conf->constraint_median; 339bc58a738Sreyk cs->constraint_last = conf->constraint_last; 340bc58a738Sreyk cs->constraint_errors = conf->constraint_errors; 34164c82965Sphessler } 34264c82965Sphessler 34364c82965Sphessler void 34464c82965Sphessler build_show_peer(struct ctl_show_peer *cp, struct ntp_peer *p) 34564c82965Sphessler { 34664c82965Sphessler const char *a = "not resolved"; 34764c82965Sphessler const char *pool = "", *addr_head_name = ""; 348b98b0a5cSotto const char *auth = ""; 3494c6157faSbluhm int shift, best = -1, validdelaycnt = 0, jittercnt = 0; 35064c82965Sphessler time_t now; 35164c82965Sphessler 35264c82965Sphessler now = getmonotime(); 35364c82965Sphessler 354b98b0a5cSotto if (p->addr) { 3558745f5cfSotto a = log_ntp_addr(p->addr); 356b98b0a5cSotto if (p->addr->notauth) 357b98b0a5cSotto auth = " (non-dnssec lookup)"; 358b98b0a5cSotto } 35964c82965Sphessler if (p->addr_head.pool) 36064c82965Sphessler pool = "from pool "; 36164c82965Sphessler 362f64e4370Sotto if (0 != strcmp(a, p->addr_head.name) || p->addr_head.pool) 36364c82965Sphessler addr_head_name = p->addr_head.name; 36464c82965Sphessler 36564c82965Sphessler snprintf(cp->peer_desc, sizeof(cp->peer_desc), 366b98b0a5cSotto "%s %s%s%s", a, pool, addr_head_name, auth); 36764c82965Sphessler 36864c82965Sphessler cp->offset = cp->delay = 0.0; 36964c82965Sphessler for (shift = 0; shift < OFFSET_ARRAY_SIZE; shift++) { 37064c82965Sphessler if (p->reply[shift].delay > 0.0) { 37164c82965Sphessler cp->offset += p->reply[shift].offset; 37264c82965Sphessler cp->delay += p->reply[shift].delay; 37364c82965Sphessler 3744c6157faSbluhm if (best == -1 || 3754c6157faSbluhm p->reply[shift].delay < p->reply[best].delay) 37664c82965Sphessler best = shift; 37764c82965Sphessler 37864c82965Sphessler validdelaycnt++; 37964c82965Sphessler } 38064c82965Sphessler } 38164c82965Sphessler 38264c82965Sphessler if (validdelaycnt > 1) { 38364c82965Sphessler cp->offset /= validdelaycnt; 38464c82965Sphessler cp->delay /= validdelaycnt; 38564c82965Sphessler } 38664c82965Sphessler 38764c82965Sphessler cp->jitter = 0.0; 3884c6157faSbluhm if (best != -1) { 38964c82965Sphessler for (shift = 0; shift < OFFSET_ARRAY_SIZE; shift++) { 39064c82965Sphessler if (p->reply[shift].delay > 0.0 && shift != best) { 3915fbd837dStedu cp->jitter += square(p->reply[shift].delay - 3925fbd837dStedu p->reply[best].delay); 39364c82965Sphessler jittercnt++; 39464c82965Sphessler } 39564c82965Sphessler } 39664c82965Sphessler if (jittercnt > 1) 39764c82965Sphessler cp->jitter /= jittercnt; 3985fbd837dStedu cp->jitter = sqrt(cp->jitter); 3994c6157faSbluhm } 40064c82965Sphessler 40164c82965Sphessler if (p->shift == 0) 40264c82965Sphessler shift = OFFSET_ARRAY_SIZE - 1; 40364c82965Sphessler else 40464c82965Sphessler shift = p->shift - 1; 40564c82965Sphessler 40664c82965Sphessler if (conf->status.synced == 1 && 40764c82965Sphessler p->reply[shift].status.send_refid == conf->status.refid) 40864c82965Sphessler cp->syncedto = 1; 40964c82965Sphessler else 41064c82965Sphessler cp->syncedto = 0; 41164c82965Sphessler 41264c82965Sphessler /* milliseconds to reduce number of leading zeroes */ 41364c82965Sphessler cp->offset *= 1000.0; 41464c82965Sphessler cp->delay *= 1000.0; 41564c82965Sphessler cp->jitter *= 1000.0; 41664c82965Sphessler 41764c82965Sphessler cp->weight = p->weight; 41864c82965Sphessler cp->trustlevel = p->trustlevel; 41964c82965Sphessler cp->stratum = p->reply[shift].status.stratum; 42064c82965Sphessler cp->next = p->next - now < 0 ? 0 : p->next - now; 42164c82965Sphessler cp->poll = p->poll; 42264c82965Sphessler } 42364c82965Sphessler 42464c82965Sphessler void 42564c82965Sphessler build_show_sensor(struct ctl_show_sensor *cs, struct ntp_sensor *s) 42664c82965Sphessler { 42764c82965Sphessler time_t now; 42864c82965Sphessler u_int8_t shift; 42964c82965Sphessler u_int32_t refid; 43064c82965Sphessler 43164c82965Sphessler now = getmonotime(); 43264c82965Sphessler 43364c82965Sphessler memcpy(&refid, SENSOR_DEFAULT_REFID, sizeof(refid)); 43464c82965Sphessler refid = refid == s->refid ? 0 : s->refid; 43564c82965Sphessler 43664c82965Sphessler snprintf(cs->sensor_desc, sizeof(cs->sensor_desc), 43764c82965Sphessler "%s %.4s", s->device, (char *)&refid); 43864c82965Sphessler 43964c82965Sphessler if (s->shift == 0) 44064c82965Sphessler shift = SENSOR_OFFSETS - 1; 44164c82965Sphessler else 44264c82965Sphessler shift = s->shift - 1; 44364c82965Sphessler 44464c82965Sphessler if (conf->status.synced == 1 && 44564c82965Sphessler s->offsets[shift].status.send_refid == conf->status.refid) 44664c82965Sphessler cs->syncedto = 1; 44764c82965Sphessler else 44864c82965Sphessler cs->syncedto = 0; 44964c82965Sphessler 45064c82965Sphessler cs->weight = s->weight; 45164c82965Sphessler cs->good = s->update.good; 45264c82965Sphessler cs->stratum = s->offsets[shift].status.stratum; 45364c82965Sphessler cs->next = s->next - now < 0 ? 0 : s->next - now; 45464c82965Sphessler cs->poll = SENSOR_QUERY_INTERVAL; 45564c82965Sphessler cs->offset = s->offsets[shift].offset * 1000.0; 45664c82965Sphessler cs->correction = (double)s->correction / 1000.0; 45764c82965Sphessler } 458