diff options
author | Karel Zak | 2012-05-31 11:15:07 +0200 |
---|---|---|
committer | Karel Zak | 2012-06-26 20:48:23 +0200 |
commit | c7f753901f45bffdf39506e4f19bba4c37659ccc (patch) | |
tree | 88fc1be2f3f104144b4a45737a070ebf76801106 /sys-utils | |
parent | build-sys: convert sys-utils/ to module (diff) | |
download | kernel-qcow2-util-linux-c7f753901f45bffdf39506e4f19bba4c37659ccc.tar.gz kernel-qcow2-util-linux-c7f753901f45bffdf39506e4f19bba4c37659ccc.tar.xz kernel-qcow2-util-linux-c7f753901f45bffdf39506e4f19bba4c37659ccc.zip |
build-sys: move hwclock to sys-utils/
Signed-off-by: Karel Zak <kzak@redhat.com>
Diffstat (limited to 'sys-utils')
-rw-r--r-- | sys-utils/Makemodule.am | 20 | ||||
-rw-r--r-- | sys-utils/hwclock-cmos.c | 677 | ||||
-rw-r--r-- | sys-utils/hwclock-kd.c | 183 | ||||
-rw-r--r-- | sys-utils/hwclock-rtc.c | 509 | ||||
-rw-r--r-- | sys-utils/hwclock.8 | 661 | ||||
-rw-r--r-- | sys-utils/hwclock.c | 1895 | ||||
-rw-r--r-- | sys-utils/hwclock.h | 47 |
7 files changed, 3992 insertions, 0 deletions
diff --git a/sys-utils/Makemodule.am b/sys-utils/Makemodule.am index b4adf634e..cc1a3f63a 100644 --- a/sys-utils/Makemodule.am +++ b/sys-utils/Makemodule.am @@ -314,3 +314,23 @@ bin_PROGRAMS += arch dist_man_MANS += sys-utils/arch.1 arch_SOURCES = sys-utils/arch.c endif + +if BUILD_HWCLOCK +sbin_PROGRAMS += hwclock +dist_man_MANS += sys-utils/hwclock.8 +hwclock_SOURCES = \ + sys-utils/hwclock.c \ + sys-utils/hwclock.h \ + sys-utils/hwclock-cmos.c \ + sys-utils/hwclock-kd.c \ + lib/strutils.c + +if LINUX +hwclock_SOURCES += sys-utils/hwclock-rtc.c +endif + +hwclock_LDADD = +if HAVE_AUDIT +hwclock_LDADD += -laudit +endif +endif # BUILD_HWCLOCK diff --git a/sys-utils/hwclock-cmos.c b/sys-utils/hwclock-cmos.c new file mode 100644 index 000000000..366d934fb --- /dev/null +++ b/sys-utils/hwclock-cmos.c @@ -0,0 +1,677 @@ +/* + * i386 CMOS starts out with 14 bytes clock data alpha has something + * similar, but with details depending on the machine type. + * + * byte 0: seconds 0-59 + * byte 2: minutes 0-59 + * byte 4: hours 0-23 in 24hr mode, + * 1-12 in 12hr mode, with high bit unset/set + * if am/pm. + * byte 6: weekday 1-7, Sunday=1 + * byte 7: day of the month 1-31 + * byte 8: month 1-12 + * byte 9: year 0-99 + * + * Numbers are stored in BCD/binary if bit 2 of byte 11 is unset/set The + * clock is in 12hr/24hr mode if bit 1 of byte 11 is unset/set The clock is + * undefined (being updated) if bit 7 of byte 10 is set. The clock is frozen + * (to be updated) by setting bit 7 of byte 11 Bit 7 of byte 14 indicates + * whether the CMOS clock is reliable: it is 1 if RTC power has been good + * since this bit was last read; it is 0 when the battery is dead and system + * power has been off. + * + * Avoid setting the RTC clock within 2 seconds of the day rollover that + * starts a new month or enters daylight saving time. + * + * The century situation is messy: + * + * Usually byte 50 (0x32) gives the century (in BCD, so 19 or 20 hex), but + * IBM PS/2 has (part of) a checksum there and uses byte 55 (0x37). + * Sometimes byte 127 (0x7f) or Bank 1, byte 0x48 gives the century. The + * original RTC will not access any century byte; some modern versions will. + * If a modern RTC or BIOS increments the century byte it may go from 0x19 + * to 0x20, but in some buggy cases 0x1a is produced. + */ +/* + * A struct tm has int fields + * tm_sec 0-59, 60 or 61 only for leap seconds + * tm_min 0-59 + * tm_hour 0-23 + * tm_mday 1-31 + * tm_mon 0-11 + * tm_year number of years since 1900 + * tm_wday 0-6, 0=Sunday + * tm_yday 0-365 + * tm_isdst >0: yes, 0: no, <0: unknown + */ + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "c.h" +#include "nls.h" + +#if defined(__i386__) +# ifdef HAVE_SYS_IO_H +# include <sys/io.h> +# elif defined(HAVE_ASM_IO_H) +# include <asm/io.h> /* for inb, outb */ +# else +/* + * Disable cmos access; we can no longer use asm/io.h, since the kernel does + * not export that header. + */ +#undef __i386__ +void outb(int a __attribute__ ((__unused__)), + int b __attribute__ ((__unused__))) +{ +} + +int inb(int c __attribute__ ((__unused__))) +{ + return 0; +} +#endif /* __i386__ */ + +#elif defined(__alpha__) +/* <asm/io.h> fails to compile, probably because of u8 etc */ +extern unsigned int inb(unsigned long port); +extern void outb(unsigned char b, unsigned long port); +#else +void outb(int a __attribute__ ((__unused__)), + int b __attribute__ ((__unused__))) +{ +} + +int inb(int c __attribute__ ((__unused__))) +{ + return 0; +} +#endif /* __alpha__ */ + +#include "hwclock.h" + +#define BCD_TO_BIN(val) ((val)=((val)&15) + ((val)>>4)*10) +#define BIN_TO_BCD(val) ((val)=(((val)/10)<<4) + (val)%10) + +/* + * The epoch. + * + * Unix uses 1900 as epoch for a struct tm, and 1970 for a time_t. But what + * was written to CMOS? + * + * Digital DECstations use 1928 - this is on a mips or alpha Digital Unix + * uses 1952, e.g. on AXPpxi33. Windows NT uses 1980. The ARC console + * expects to boot Windows NT and uses 1980. (But a Ruffian uses 1900, just + * like SRM.) It is reported that ALPHA_PRE_V1_2_SRM_CONSOLE uses 1958. + */ +#define TM_EPOCH 1900 +int cmos_epoch = 1900; + +/* + * Martin Ostermann writes: + * + * The problem with the Jensen is twofold: First, it has the clock at a + * different address. Secondly, it has a distinction between "local" and + * normal bus addresses. The local ones pertain to the hardware integrated + * into the chipset, like serial/parallel ports and of course, the RTC. + * Those need to be addressed differently. This is handled fine in the + * kernel, and it's not a problem, since this usually gets totally optimized + * by the compile. But the i/o routines of (g)libc lack this support so far. + * The result of this is, that the old clock program worked only on the + * Jensen when USE_DEV_PORT was defined, but not with the normal inb/outb + * functions. + */ +int use_dev_port = 0; /* 1 for Jensen */ +int dev_port_fd; +unsigned short clock_ctl_addr = 0x70; /* 0x170 for Jensen */ +unsigned short clock_data_addr = 0x71; /* 0x171 for Jensen */ + +int century_byte = 0; /* 0: don't access a century byte + * 50 (0x32): usual PC value + * 55 (0x37): PS/2 + */ + +#ifdef __alpha__ +int funkyTOY = 0; /* 1 for PC164/LX164/SX164 type alpha */ +#endif + +#ifdef __alpha + +static int is_in_cpuinfo(char *fmt, char *str) +{ + FILE *cpuinfo; + char field[256]; + char format[256]; + int found = 0; + + sprintf(format, "%s : %s", fmt, "%255s"); + + if ((cpuinfo = fopen("/proc/cpuinfo", "r")) != NULL) { + while (!feof(cpuinfo)) { + if (fscanf(cpuinfo, format, field) == 1) { + if (strncmp(field, str, strlen(str)) == 0) + found = 1; + break; + } + fgets(field, 256, cpuinfo); + } + fclose(cpuinfo); + } + return found; +} + +/* + * Set cmos_epoch, either from user options, or by asking the kernel, or by + * looking at /proc/cpu_info + */ +void set_cmos_epoch(int ARCconsole, int SRM) +{ + unsigned long epoch; + + /* Believe the user */ + if (epoch_option != -1) { + cmos_epoch = epoch_option; + return; + } + + if (ARCconsole) + cmos_epoch = 1980; + + if (ARCconsole || SRM) + return; + +#ifdef __linux__ + /* + * If we can ask the kernel, we don't need guessing from + * /proc/cpuinfo + */ + if (get_epoch_rtc(&epoch, 1) == 0) { + cmos_epoch = epoch; + return; + } +#endif + + /* + * The kernel source today says: read the year. + * + * If it is in 0-19 then the epoch is 2000. + * If it is in 20-47 then the epoch is 1980. + * If it is in 48-69 then the epoch is 1952. + * If it is in 70-99 then the epoch is 1928. + * + * Otherwise the epoch is 1900. + * TODO: Clearly, this must be changed before 2019. + */ + /* + * See whether we are dealing with SRM or MILO, as they have + * different "epoch" ideas. + */ + if (is_in_cpuinfo("system serial number", "MILO")) { + ARCconsole = 1; + if (debug) + printf(_("booted from MILO\n")); + } + + /* + * See whether we are dealing with a RUFFIAN aka Alpha PC-164 UX (or + * BX), as they have REALLY different TOY (TimeOfYear) format: BCD, + * and not an ARC-style epoch. BCD is detected dynamically, but we + * must NOT adjust like ARC. + */ + if (ARCconsole && is_in_cpuinfo("system type", "Ruffian")) { + ARCconsole = 0; + if (debug) + printf(_("Ruffian BCD clock\n")); + } + + if (ARCconsole) + cmos_epoch = 1980; +} + +void set_cmos_access(int Jensen, int funky_toy) +{ + + /* + * See whether we're dealing with a Jensen---it has a weird I/O + * system. DEC was just learning how to build Alpha PCs. + */ + if (Jensen || is_in_cpuinfo("system type", "Jensen")) { + use_dev_port = 1; + clock_ctl_addr = 0x170; + clock_data_addr = 0x171; + if (debug) + printf(_("clockport adjusted to 0x%x\n"), + clock_ctl_addr); + } + + /* + * See whether we are dealing with PC164/LX164/SX164, as they have a + * TOY that must be accessed differently to work correctly. + */ + /* Nautilus stuff reported by Neoklis Kyriazis */ + if (funky_toy || + is_in_cpuinfo("system variation", "PC164") || + is_in_cpuinfo("system variation", "LX164") || + is_in_cpuinfo("system variation", "SX164") || + is_in_cpuinfo("system type", "Nautilus")) { + funkyTOY = 1; + if (debug) + printf(_("funky TOY!\n")); + } +} +#endif /* __alpha */ + +#if __alpha__ +/* + * The Alpha doesn't allow user-level code to disable interrupts (for good + * reasons). Instead, we ensure atomic operation by performing the operation + * and checking whether the high 32 bits of the cycle counter changed. If + * they did, a context switch must have occurred and we redo the operation. + * As long as the operation is reasonably short, it will complete + * atomically, eventually. + */ +static unsigned long +atomic(const char *name, unsigned long (*op) (unsigned long), unsigned long arg) +{ + unsigned long ts1, ts2, n, v; + + for (n = 0; n < 1000; ++n) { + asm volatile ("rpcc %0":"r=" (ts1)); + v = (*op) (arg); + asm volatile ("rpcc %0":"r=" (ts2)); + + if ((ts1 ^ ts2) >> 32 == 0) { + return v; + } + } + errx(EXIT_FAILURE, _("atomic %s failed for 1000 iterations!"), + name); +} +#else + +/* + * Hmmh, this isn't very atomic. Maybe we should force an error instead? + * + * TODO: optimize the access to CMOS by mlockall(MCL_CURRENT) and SCHED_FIFO + */ +static unsigned long +atomic(const char *name __attribute__ ((__unused__)), + unsigned long (*op) (unsigned long), + unsigned long arg) +{ + return (*op) (arg); +} + +#endif + +static inline unsigned long cmos_read(unsigned long reg) +{ + if (use_dev_port) { + unsigned char v = reg | 0x80; + lseek(dev_port_fd, clock_ctl_addr, 0); + if (write(dev_port_fd, &v, 1) == -1 && debug) + printf(_ + ("cmos_read(): write to control address %X failed: %m\n"), + clock_ctl_addr); + lseek(dev_port_fd, clock_data_addr, 0); + if (read(dev_port_fd, &v, 1) == -1 && debug) + printf(_ + ("cmos_read(): read data address %X failed: %m\n"), + clock_data_addr); + return v; + } else { + /* + * We only want to read CMOS data, but unfortunately writing + * to bit 7 disables (1) or enables (0) NMI; since this bit + * is read-only we have to guess the old status. Various + * docs suggest that one should disable NMI while + * reading/writing CMOS data, and enable it again + * afterwards. This would yield the sequence + * + * outb (reg | 0x80, 0x70); + * val = inb(0x71); + * outb (0x0d, 0x70); // 0x0d: random read-only location + * + * Other docs state that "any write to 0x70 should be + * followed by an action to 0x71 or the RTC wil be left in + * an unknown state". Most docs say that it doesn't matter at + * all what one does. + */ + /* + * bit 0x80: disable NMI while reading - should we? Let us + * follow the kernel and not disable. Called only with 0 <= + * reg < 128 + */ + outb(reg, clock_ctl_addr); + return inb(clock_data_addr); + } +} + +static inline unsigned long cmos_write(unsigned long reg, unsigned long val) +{ + if (use_dev_port) { + unsigned char v = reg | 0x80; + lseek(dev_port_fd, clock_ctl_addr, 0); + if (write(dev_port_fd, &v, 1) == -1 && debug) + printf(_ + ("cmos_write(): write to control address %X failed: %m\n"), + clock_ctl_addr); + v = (val & 0xff); + lseek(dev_port_fd, clock_data_addr, 0); + if (write(dev_port_fd, &v, 1) == -1 && debug) + printf(_ + ("cmos_write(): write to data address %X failed: %m\n"), + clock_data_addr); + } else { + outb(reg, clock_ctl_addr); + outb(val, clock_data_addr); + } + return 0; +} + +static unsigned long cmos_set_time(unsigned long arg) +{ + unsigned char save_control, save_freq_select, pmbit = 0; + struct tm tm = *(struct tm *)arg; + unsigned int century; + +/* + * CMOS byte 10 (clock status register A) has 3 bitfields: + * bit 7: 1 if data invalid, update in progress (read-only bit) + * (this is raised 224 us before the actual update starts) + * 6-4 select base frequency + * 010: 32768 Hz time base (default) + * 111: reset + * all other combinations are manufacturer-dependent + * (e.g.: DS1287: 010 = start oscillator, anything else = stop) + * 3-0 rate selection bits for interrupt + * 0000 none (may stop RTC) + * 0001, 0010 give same frequency as 1000, 1001 + * 0011 122 microseconds (minimum, 8192 Hz) + * .... each increase by 1 halves the frequency, doubles the period + * 1111 500 milliseconds (maximum, 2 Hz) + * 0110 976.562 microseconds (default 1024 Hz) + */ + save_control = cmos_read(11); /* tell the clock it's being set */ + cmos_write(11, (save_control | 0x80)); + save_freq_select = cmos_read(10); /* stop and reset prescaler */ + cmos_write(10, (save_freq_select | 0x70)); + + tm.tm_year += TM_EPOCH; + century = tm.tm_year / 100; + tm.tm_year -= cmos_epoch; + tm.tm_year %= 100; + tm.tm_mon += 1; + tm.tm_wday += 1; + + if (!(save_control & 0x02)) { /* 12hr mode; the default is 24hr mode */ + if (tm.tm_hour == 0) + tm.tm_hour = 24; + if (tm.tm_hour > 12) { + tm.tm_hour -= 12; + pmbit = 0x80; + } + } + + if (!(save_control & 0x04)) { /* BCD mode - the default */ + BIN_TO_BCD(tm.tm_sec); + BIN_TO_BCD(tm.tm_min); + BIN_TO_BCD(tm.tm_hour); + BIN_TO_BCD(tm.tm_wday); + BIN_TO_BCD(tm.tm_mday); + BIN_TO_BCD(tm.tm_mon); + BIN_TO_BCD(tm.tm_year); + BIN_TO_BCD(century); + } + + cmos_write(0, tm.tm_sec); + cmos_write(2, tm.tm_min); + cmos_write(4, tm.tm_hour | pmbit); + cmos_write(6, tm.tm_wday); + cmos_write(7, tm.tm_mday); + cmos_write(8, tm.tm_mon); + cmos_write(9, tm.tm_year); + if (century_byte) + cmos_write(century_byte, century); + + /* + * The kernel sources, linux/arch/i386/kernel/time.c, have the + * following comment: + * + * The following flags have to be released exactly in this order, + * otherwise the DS12887 (popular MC146818A clone with integrated + * battery and quartz) will not reset the oscillator and will not + * update precisely 500 ms later. You won't find this mentioned in + * the Dallas Semiconductor data sheets, but who believes data + * sheets anyway ... -- Markus Kuhn + */ + cmos_write(11, save_control); + cmos_write(10, save_freq_select); + return 0; +} + +static int hclock_read(unsigned long reg) +{ + return atomic("clock read", cmos_read, (reg)); +} + +static void hclock_set_time(const struct tm *tm) +{ + atomic("set time", cmos_set_time, (unsigned long)(tm)); +} + +static inline int cmos_clock_busy(void) +{ + return +#ifdef __alpha__ + /* poll bit 4 (UF) of Control Register C */ + funkyTOY ? (hclock_read(12) & 0x10) : +#endif + /* poll bit 7 (UIP) of Control Register A */ + (hclock_read(10) & 0x80); +} + +static int synchronize_to_clock_tick_cmos(void) +{ + int i; + + /* + * Wait for rise. Should be within a second, but in case something + * weird happens, we have a limit on this loop to reduce the impact + * of this failure. + */ + for (i = 0; !cmos_clock_busy(); i++) + if (i >= 10000000) + return 1; + + /* Wait for fall. Should be within 2.228 ms. */ + for (i = 0; cmos_clock_busy(); i++) + if (i >= 1000000) + return 1; + return 0; +} + +/* + * Read the hardware clock and return the current time via <tm> argument. + * Assume we have an ISA machine and read the clock directly with CPU I/O + * instructions. + * + * This function is not totally reliable. It takes a finite and + * unpredictable amount of time to execute the code below. During that time, + * the clock may change and we may even read an invalid value in the middle + * of an update. We do a few checks to minimize this possibility, but only + * the kernel can actually read the clock properly, since it can execute + * code in a short and predictable amount of time (by turning of + * interrupts). + * + * In practice, the chance of this function returning the wrong time is + * extremely remote. + */ +static int read_hardware_clock_cmos(struct tm *tm) +{ + bool got_time = FALSE; + unsigned char status, pmbit; + + status = pmbit = 0; /* just for gcc */ + + while (!got_time) { + /* + * Bit 7 of Byte 10 of the Hardware Clock value is the + * Update In Progress (UIP) bit, which is on while and 244 + * uS before the Hardware Clock updates itself. It updates + * the counters individually, so reading them during an + * update would produce garbage. The update takes 2mS, so we + * could be spinning here that long waiting for this bit to + * turn off. + * + * Furthermore, it is pathologically possible for us to be + * in this code so long that even if the UIP bit is not on + * at first, the clock has changed while we were running. We + * check for that too, and if it happens, we start over. + */ + if (!cmos_clock_busy()) { + /* No clock update in progress, go ahead and read */ + tm->tm_sec = hclock_read(0); + tm->tm_min = hclock_read(2); + tm->tm_hour = hclock_read(4); + tm->tm_wday = hclock_read(6); + tm->tm_mday = hclock_read(7); + tm->tm_mon = hclock_read(8); + tm->tm_year = hclock_read(9); + status = hclock_read(11); +#if 0 + if (century_byte) + century = hclock_read(century_byte); +#endif + /* + * Unless the clock changed while we were reading, + * consider this a good clock read . + */ + if (tm->tm_sec == hclock_read(0)) + got_time = TRUE; + } + /* + * Yes, in theory we could have been running for 60 seconds + * and the above test wouldn't work! + */ + } + + if (!(status & 0x04)) { /* BCD mode - the default */ + BCD_TO_BIN(tm->tm_sec); + BCD_TO_BIN(tm->tm_min); + pmbit = (tm->tm_hour & 0x80); + tm->tm_hour &= 0x7f; + BCD_TO_BIN(tm->tm_hour); + BCD_TO_BIN(tm->tm_wday); + BCD_TO_BIN(tm->tm_mday); + BCD_TO_BIN(tm->tm_mon); + BCD_TO_BIN(tm->tm_year); +#if 0 + BCD_TO_BIN(century); +#endif + } + + /* + * We don't use the century byte of the Hardware Clock since we + * don't know its address (usually 50 or 55). Here, we follow the + * advice of the X/Open Base Working Group: "if century is not + * specified, then values in the range [69-99] refer to years in the + * twentieth century (1969 to 1999 inclusive), and values in the + * range [00-68] refer to years in the twenty-first century (2000 to + * 2068 inclusive)." + */ + tm->tm_wday -= 1; + tm->tm_mon -= 1; + tm->tm_year += (cmos_epoch - TM_EPOCH); + if (tm->tm_year < 69) + tm->tm_year += 100; + if (pmbit) { + tm->tm_hour += 12; + if (tm->tm_hour == 24) + tm->tm_hour = 0; + } + + tm->tm_isdst = -1; /* don't know whether it's daylight */ + return 0; +} + +static int set_hardware_clock_cmos(const struct tm *new_broken_time) +{ + + hclock_set_time(new_broken_time); + return 0; +} + +#if defined(__i386__) || defined(__alpha__) +# if defined(HAVE_IOPL) +static int i386_iopl(const int level) +{ + extern int iopl(const int lvl); + return iopl(level); +} +# else +static int i386_iopl(const int level __attribute__ ((__unused__))) +{ + extern int ioperm(unsigned long from, unsigned long num, int turn_on); + return ioperm(clock_ctl_addr, 2, 1); +} +# endif +#else +static int i386_iopl(const int level __attribute__ ((__unused__))) +{ + return -2; +} +#endif + +static int get_permissions_cmos(void) +{ + int rc; + + if (use_dev_port) { + if ((dev_port_fd = open("/dev/port", O_RDWR)) < 0) { + warn(_("Cannot open /dev/port")); + rc = 1; + } else + rc = 0; + } else { + rc = i386_iopl(3); + if (rc == -2) { + warnx(_("I failed to get permission because I didn't try.")); + } else if (rc != 0) { + rc = errno; + warn(_("unable to get I/O port access: " + "the iopl(3) call failed.")); + if (rc == EPERM && geteuid()) + warnx(_("Probably you need root privileges.\n")); + } + } + return rc ? 1 : 0; +} + +static struct clock_ops cmos = { + "direct I/O instructions to ISA clock", + get_permissions_cmos, + read_hardware_clock_cmos, + set_hardware_clock_cmos, + synchronize_to_clock_tick_cmos, +}; + +/* + * return &cmos if cmos clock present, NULL otherwise choose this + * construction to avoid gcc messages about unused variables + */ +struct clock_ops *probe_for_cmos_clock(void) +{ + int have_cmos = +#if defined(__i386__) || defined(__alpha__) + TRUE; +#else + FALSE; +#endif + return have_cmos ? &cmos : NULL; +} diff --git a/sys-utils/hwclock-kd.c b/sys-utils/hwclock-kd.c new file mode 100644 index 000000000..ec98f45ba --- /dev/null +++ b/sys-utils/hwclock-kd.c @@ -0,0 +1,183 @@ +/* + * kd.c - KDGHWCLK stuff, possibly m68k only, likely to be deprecated + */ + + +#ifdef __m68k__ + +# include <fcntl.h> +# include <sysexits.h> +# include <sys/ioctl.h> +# include <unistd.h> + +# include "nls.h" +# include "usleep.h" + +# include "hwclock.h" + +/* Get defines for KDGHWCLK and KDSHWCLK (m68k) */ +# include <linux/kd.h> + +/* Even on m68k, if KDGHWCLK (antique) is not defined, don't build this */ + +#endif + +#if !defined(__m68k__) || !defined(KDGHWCLK) + +#include <stddef.h> +struct clock_ops *probe_for_kd_clock(void) +{ + return NULL; +} + +#else /* __m68k__ && KDGHWCLK */ + +/* Opened by probe_for_kd_clock(), and never closed. */ +static int con_fd = -1; +static char *con_fd_filename; /* usually "/dev/tty1" */ + +/* + * Wait for the top of a clock tick by calling KDGHWCLK in a busy loop until + * we see it. + */ +static int synchronize_to_clock_tick_kd(void) +{ + /* The time when we were called (and started waiting) */ + struct hwclk_time start_time, nowtime; + struct timeval begin, now; + + if (debug) + printf(_("Waiting in loop for time from KDGHWCLK to change\n")); + + if (ioctl(con_fd, KDGHWCLK, &start_time) == -1) { + warn(_("KDGHWCLK ioctl to read time failed")); + return 3; + } + + /* + * Wait for change. Should be within a second, but in case something + * weird happens, we have a time limit (1.5s) on this loop to reduce + * the impact of this failure. + */ + gettimeofday(&begin, NULL); + do { + /* + * Added by Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de> + * + * "The culprit is the fast loop with KDGHWCLK ioctls. It + * seems the kernel gets confused by those on Amigas with + * A2000 RTCs and simply hangs after some time. Inserting a + * sleep helps." + */ + usleep(1); + + if (ioctl(con_fd, KDGHWCLK, &nowtime) == -1) { + warn(_("KDGHWCLK ioctl to read time failed in loop")); + return 3; + } + if (start_time.tm_sec != nowtime.tm_sec) + break; + gettimeofday(&now, NULL); + if (time_diff(now, begin) > 1.5) { + warnx(_("Timed out waiting for time change.")); + return 2; + } + } while (1); + + return 0; +} + +/* + * Read the hardware clock and return the current time via <tm> argument. + * Use ioctls to /dev/tty1 on what we assume is an m68k machine. + * + * Note that we don't use /dev/console here. That might be a serial console. + */ +static int read_hardware_clock_kd(struct tm *tm) +{ + struct hwclk_time t; + + if (ioctl(con_fd, KDGHWCLK, &t) == -1) { + warn(_("ioctl() failed to read time from %s"), + con_fd_filename); + hwclock_exit(EX_IOERR); + } + + tm->tm_sec = t.sec; + tm->tm_min = t.min; + tm->tm_hour = t.hour; + tm->tm_mday = t.day; + tm->tm_mon = t.mon; + tm->tm_year = t.year; + tm->tm_wday = t.wday; + tm->tm_isdst = -1; /* Don't know if it's Daylight Savings Time */ + + return 0; +} + +/* + * Set the Hardware Clock to the time <new_broken_time>. Use ioctls to + * /dev/tty1 on what we assume is an m68k machine. + * + * Note that we don't use /dev/console here. That might be a serial console. + */ +static int set_hardware_clock_kd(const struct tm *new_broken_time) +{ + struct hwclk_time t; + + t.sec = new_broken_time->tm_sec; + t.min = new_broken_time->tm_min; + t.hour = new_broken_time->tm_hour; + t.day = new_broken_time->tm_mday; + t.mon = new_broken_time->tm_mon; + t.year = new_broken_time->tm_year; + t.wday = new_broken_time->tm_wday; + + if (ioctl(con_fd, KDSHWCLK, &t) == -1) { + warn(_("ioctl KDSHWCLK failed")); + hwclock_exit(EX_IOERR); + } + return 0; +} + +static int get_permissions_kd(void) +{ + return 0; +} + +static struct clock_ops kd = { + "KDGHWCLK interface to m68k clock", + get_permissions_kd, + read_hardware_clock_kd, + set_hardware_clock_kd, + synchronize_to_clock_tick_kd, +}; + +/* return &kd if KDGHWCLK works, NULL otherwise */ +struct clock_ops *probe_for_kd_clock() +{ + struct clock_ops *ret = NULL; + struct hwclk_time t; + + if (con_fd < 0) { /* first time here */ + con_fd_filename = "/dev/tty1"; + con_fd = open(con_fd_filename, O_RDONLY); + } + if (con_fd < 0) { + /* perhaps they are using devfs? */ + con_fd_filename = "/dev/vc/1"; + con_fd = open(con_fd_filename, O_RDONLY); + } + if (con_fd < 0) { + /* probably KDGHWCLK exists on m68k only */ + warn(_("Can't open /dev/tty1 or /dev/vc/1")); + } else { + if (ioctl(con_fd, KDGHWCLK, &t) == -1) { + if (errno != EINVAL) + warn(_("KDGHWCLK ioctl failed")); + } else + ret = &kd; + } + return ret; +} +#endif /* __m68k__ && KDGHWCLK */ diff --git a/sys-utils/hwclock-rtc.c b/sys-utils/hwclock-rtc.c new file mode 100644 index 000000000..62adc49d7 --- /dev/null +++ b/sys-utils/hwclock-rtc.c @@ -0,0 +1,509 @@ +/* + * rtc.c - Use /dev/rtc for clock access + */ +#include <asm/ioctl.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <sysexits.h> +#include <sys/ioctl.h> +#include <sys/select.h> +#include <sys/time.h> +#include <time.h> +#include <unistd.h> + +#include "nls.h" + +#include "hwclock.h" + +/* + * Get defines for rtc stuff. + * + * Getting the rtc defines is nontrivial. The obvious way is by including + * <linux/mc146818rtc.h> but that again includes <asm/io.h> which again + * includes ... and on sparc and alpha this gives compilation errors for + * many kernel versions. So, we give the defines ourselves here. Moreover, + * some Sparc person decided to be incompatible, and used a struct rtc_time + * different from that used in mc146818rtc.h. + */ + +/* + * On Sparcs, there is a <asm/rtc.h> that defines different ioctls (that are + * required on my machine). However, this include file does not exist on + * other architectures. + */ +/* One might do: +#ifdef __sparc__ +# include <asm/rtc.h> +#endif + */ +/* The following is roughly equivalent */ +struct sparc_rtc_time +{ + int sec; /* Seconds 0-59 */ + int min; /* Minutes 0-59 */ + int hour; /* Hour 0-23 */ + int dow; /* Day of the week 1-7 */ + int dom; /* Day of the month 1-31 */ + int month; /* Month of year 1-12 */ + int year; /* Year 0-99 */ +}; + +#define RTCGET _IOR('p', 20, struct sparc_rtc_time) +#define RTCSET _IOW('p', 21, struct sparc_rtc_time) + +/* non-sparc stuff */ +#if 0 +# include <linux/version.h> +/* + * Check if the /dev/rtc interface is available in this version of the + * system headers. 131072 is linux 2.0.0. + */ +# if LINUX_VERSION_CODE >= 131072 +# include <linux/mc146818rtc.h> +# endif +#endif + +/* + * struct rtc_time is present since 1.3.99. + * Earlier (since 1.3.89), a struct tm was used. + */ +struct linux_rtc_time { + int tm_sec; + int tm_min; + int tm_hour; + int tm_mday; + int tm_mon; + int tm_year; + int tm_wday; + int tm_yday; + int tm_isdst; +}; + +/* RTC_RD_TIME etc have this definition since 1.99.9 (pre2.0-9) */ +#ifndef RTC_RD_TIME +# define RTC_RD_TIME _IOR('p', 0x09, struct linux_rtc_time) +# define RTC_SET_TIME _IOW('p', 0x0a, struct linux_rtc_time) +# define RTC_UIE_ON _IO('p', 0x03) /* Update int. enable on */ +# define RTC_UIE_OFF _IO('p', 0x04) /* Update int. enable off */ +#endif + +/* RTC_EPOCH_READ and RTC_EPOCH_SET are present since 2.0.34 and 2.1.89 */ +#ifndef RTC_EPOCH_READ +# define RTC_EPOCH_READ _IOR('p', 0x0d, unsigned long) /* Read epoch */ +# define RTC_EPOCH_SET _IOW('p', 0x0e, unsigned long) /* Set epoch */ +#endif + +/* + * /dev/rtc is conventionally chardev 10/135 + * ia64 uses /dev/efirtc, chardev 10/136 + * devfs (obsolete) used /dev/misc/... for miscdev + * new RTC framework + udev uses dynamic major and /dev/rtc0.../dev/rtcN + * ... so we need an overridable default + */ + +/* default or user defined dev (by hwclock --rtc=<path>) */ +char *rtc_dev_name; + +static int rtc_dev_fd = -1; + +static void close_rtc(void) +{ + if (rtc_dev_fd != -1) + close(rtc_dev_fd); + rtc_dev_fd = -1; +} + +static int open_rtc(void) +{ + char *fls[] = { +#ifdef __ia64__ + "/dev/efirtc", + "/dev/misc/efirtc", +#endif + "/dev/rtc", + "/dev/rtc0", + "/dev/misc/rtc", + NULL + }; + char **p; + + if (rtc_dev_fd != -1) + return rtc_dev_fd; + + /* --rtc option has been given */ + if (rtc_dev_name) + rtc_dev_fd = open(rtc_dev_name, O_RDONLY); + else { + for (p = fls; *p; ++p) { + rtc_dev_fd = open(*p, O_RDONLY); + + if (rtc_dev_fd < 0 + && (errno == ENOENT || errno == ENODEV)) + continue; + rtc_dev_name = *p; + break; + } + if (rtc_dev_fd < 0) + rtc_dev_name = *fls; /* default for error messages */ + } + + if (rtc_dev_fd != 1) + atexit(close_rtc); + return rtc_dev_fd; +} + +static int open_rtc_or_exit(void) +{ + int rtc_fd = open_rtc(); + + if (rtc_fd < 0) { + warn(_("open() of %s failed"), rtc_dev_name); + hwclock_exit(EX_OSFILE); + } + return rtc_fd; +} + +static int do_rtc_read_ioctl(int rtc_fd, struct tm *tm) +{ + int rc = -1; + char *ioctlname; + +#ifdef __sparc__ + /* some but not all sparcs use a different ioctl and struct */ + struct sparc_rtc_time stm; + + ioctlname = "RTCGET"; + rc = ioctl(rtc_fd, RTCGET, &stm); + if (rc == 0) { + tm->tm_sec = stm.sec; + tm->tm_min = stm.min; + tm->tm_hour = stm.hour; + tm->tm_mday = stm.dom; + tm->tm_mon = stm.month - 1; + tm->tm_year = stm.year - 1900; + tm->tm_wday = stm.dow - 1; + tm->tm_yday = -1; /* day in the year */ + } +#endif + if (rc == -1) { /* no sparc, or RTCGET failed */ + ioctlname = "RTC_RD_TIME"; + rc = ioctl(rtc_fd, RTC_RD_TIME, tm); + } + if (rc == -1) { + warn(_("ioctl(%s) to %s to read the time failed"), + ioctlname, rtc_dev_name); + return -1; + } + + tm->tm_isdst = -1; /* don't know whether it's dst */ + return 0; +} + +/* + * Wait for the top of a clock tick by reading /dev/rtc in a busy loop until + * we see it. + */ +static int busywait_for_rtc_clock_tick(const int rtc_fd) +{ + struct tm start_time; + /* The time when we were called (and started waiting) */ + struct tm nowtime; + int rc; + struct timeval begin, now; + + if (debug) + printf(_("Waiting in loop for time from %s to change\n"), + rtc_dev_name); + + rc = do_rtc_read_ioctl(rtc_fd, &start_time); + if (rc) + return 1; + + /* + * Wait for change. Should be within a second, but in case + * something weird happens, we have a time limit (1.5s) on this loop + * to reduce the impact of this failure. + */ + gettimeofday(&begin, NULL); + do { + rc = do_rtc_read_ioctl(rtc_fd, &nowtime); + if (rc || start_time.tm_sec != nowtime.tm_sec) + break; + gettimeofday(&now, NULL); + if (time_diff(now, begin) > 1.5) { + warnx(_("Timed out waiting for time change.")); + return 2; + } + } while (1); + + if (rc) + return 3; + return 0; +} + +/* + * Same as synchronize_to_clock_tick(), but just for /dev/rtc. + */ +static int synchronize_to_clock_tick_rtc(void) +{ + int rtc_fd; /* File descriptor of /dev/rtc */ + int ret; + + rtc_fd = open_rtc(); + if (rtc_fd == -1) { + warn(_("open() of %s failed"), rtc_dev_name); + ret = 1; + } else { + int rc; /* Return code from ioctl */ + /* Turn on update interrupts (one per second) */ +#if defined(__alpha__) || defined(__sparc__) + /* + * Not all alpha kernels reject RTC_UIE_ON, but probably + * they should. + */ + rc = -1; + errno = EINVAL; +#else + rc = ioctl(rtc_fd, RTC_UIE_ON, 0); +#endif + if (rc == -1 && (errno == ENOTTY || errno == EINVAL)) { + /* + * This rtc device doesn't have interrupt functions. + * This is typical on an Alpha, where the Hardware + * Clock interrupts are used by the kernel for the + * system clock, so aren't at the user's disposal. + */ + if (debug) + printf(_ + ("%s does not have interrupt functions. "), + rtc_dev_name); + ret = busywait_for_rtc_clock_tick(rtc_fd); + } else if (rc == 0) { +#ifdef Wait_until_update_interrupt + unsigned long dummy; + + /* this blocks until the next update interrupt */ + rc = read(rtc_fd, &dummy, sizeof(dummy)); + ret = 1; + if (rc == -1) + warn(_ + ("read() to %s to wait for clock tick failed"), + rtc_dev_name); + else + ret = 0; +#else + /* + * Just reading rtc_fd fails on broken hardware: no + * update interrupt comes and a bootscript with a + * hwclock call hangs + */ + fd_set rfds; + struct timeval tv; + + /* + * Wait up to five seconds for the next update + * interrupt + */ + FD_ZERO(&rfds); + FD_SET(rtc_fd, &rfds); + tv.tv_sec = 5; + tv.tv_usec = 0; + rc = select(rtc_fd + 1, &rfds, NULL, NULL, &tv); + ret = 1; + if (rc == -1) + warn(_ + ("select() to %s to wait for clock tick failed"), + rtc_dev_name); + else if (rc == 0) + warn(_ + ("select() to %s to wait for clock tick timed out"), + rtc_dev_name); + else + ret = 0; +#endif + + /* Turn off update interrupts */ + rc = ioctl(rtc_fd, RTC_UIE_OFF, 0); + if (rc == -1) + warn(_ + ("ioctl() to %s to turn off update interrupts failed"), + rtc_dev_name); + } else { + warn(_ + ("ioctl() to %s to turn on update interrupts " + "failed unexpectedly"), rtc_dev_name); + ret = 1; + } + } + return ret; +} + +static int read_hardware_clock_rtc(struct tm *tm) +{ + int rtc_fd, rc; + + rtc_fd = open_rtc_or_exit(); + + /* Read the RTC time/date, return answer via tm */ + rc = do_rtc_read_ioctl(rtc_fd, tm); + + return rc; +} + +/* + * Set the Hardware Clock to the broken down time <new_broken_time>. Use + * ioctls to "rtc" device /dev/rtc. + */ +static int set_hardware_clock_rtc(const struct tm *new_broken_time) +{ + int rc = -1; + int rtc_fd; + char *ioctlname; + + rtc_fd = open_rtc_or_exit(); + +#ifdef __sparc__ + { + struct sparc_rtc_time stm; + + stm.sec = new_broken_time->tm_sec; + stm.min = new_broken_time->tm_min; + stm.hour = new_broken_time->tm_hour; + stm.dom = new_broken_time->tm_mday; + stm.month = new_broken_time->tm_mon + 1; + stm.year = new_broken_time->tm_year + 1900; + stm.dow = new_broken_time->tm_wday + 1; + + ioctlname = "RTCSET"; + rc = ioctl(rtc_fd, RTCSET, &stm); + } +#endif + if (rc == -1) { /* no sparc, or RTCSET failed */ + ioctlname = "RTC_SET_TIME"; + rc = ioctl(rtc_fd, RTC_SET_TIME, new_broken_time); + } + + if (rc == -1) { + warn(_("ioctl(%s) to %s to set the time failed."), + ioctlname, rtc_dev_name); + hwclock_exit(EX_IOERR); + } + + if (debug) + printf(_("ioctl(%s) was successful.\n"), ioctlname); + + return 0; +} + +static int get_permissions_rtc(void) +{ + return 0; +} + +static struct clock_ops rtc = { + "/dev interface to clock", + get_permissions_rtc, + read_hardware_clock_rtc, + set_hardware_clock_rtc, + synchronize_to_clock_tick_rtc, +}; + +/* return &rtc if /dev/rtc can be opened, NULL otherwise */ +struct clock_ops *probe_for_rtc_clock(void) +{ + int rtc_fd = open_rtc(); + if (rtc_fd >= 0) + return &rtc; + if (debug) + warn(_("Open of %s failed"), rtc_dev_name); + return NULL; +} + +/* + * Get the Hardware Clock epoch setting from the kernel. + */ +int get_epoch_rtc(unsigned long *epoch_p, int silent) +{ + int rtc_fd; + + rtc_fd = open_rtc(); + if (rtc_fd < 0) { + if (!silent) { + if (errno == ENOENT) + warnx(_ + ("To manipulate the epoch value in the kernel, we must " + "access the Linux 'rtc' device driver via the device special " + "file %s. This file does not exist on this system."), + rtc_dev_name); + else + warn(_("Unable to open %s"), rtc_dev_name); + } + return 1; + } + + if (ioctl(rtc_fd, RTC_EPOCH_READ, epoch_p) == -1) { + if (!silent) + warn(_("ioctl(RTC_EPOCH_READ) to %s failed"), + rtc_dev_name); + return 1; + } + + if (debug) + printf(_("we have read epoch %ld from %s " + "with RTC_EPOCH_READ ioctl.\n"), *epoch_p, + rtc_dev_name); + + return 0; +} + +/* + * Set the Hardware Clock epoch in the kernel. + */ +int set_epoch_rtc(unsigned long epoch) +{ + int rtc_fd; + + if (epoch < 1900) { + /* kernel would not accept this epoch value + * + * Bad habit, deciding not to do what the user asks just + * because one believes that the kernel might not like it. + */ + warnx(_("The epoch value may not be less than 1900. " + "You requested %ld"), epoch); + return 1; + } + + rtc_fd = open_rtc(); + if (rtc_fd < 0) { + if (errno == ENOENT) + warnx(_ + ("To manipulate the epoch value in the kernel, we must " + "access the Linux 'rtc' device driver via the device special " + "file %s. This file does not exist on this system."), + rtc_dev_name); + else + warn(_("Unable to open %s"), rtc_dev_name); + return 1; + } + + if (debug) + printf(_("setting epoch to %ld " + "with RTC_EPOCH_SET ioctl to %s.\n"), epoch, + rtc_dev_name); + + if (ioctl(rtc_fd, RTC_EPOCH_SET, epoch) == -1) { + if (errno == EINVAL) + warnx(_("The kernel device driver for %s " + "does not have the RTC_EPOCH_SET ioctl."), + rtc_dev_name); + else + warn(_("ioctl(RTC_EPOCH_SET) to %s failed"), + rtc_dev_name); + return 1; + } + + return 0; +} diff --git a/sys-utils/hwclock.8 b/sys-utils/hwclock.8 new file mode 100644 index 000000000..07d9fc024 --- /dev/null +++ b/sys-utils/hwclock.8 @@ -0,0 +1,661 @@ +.TH HWCLOCK 8 "August 2011" "util-linux" "System Administration" +.SH NAME +hwclock \- query or set the hardware clock (RTC) +.SH SYNOPSIS +.B hwclock +.RI [ function ] +.RI [ option ...] + +.SH DESCRIPTION +.B hwclock +is a tool for accessing the Hardware Clock. You can display the +current time, set the Hardware Clock to a specified time, set the +Hardware Clock from the System Time, or set the System Time from the +Hardware Clock. +.PP +You can also run +.B hwclock +periodically to add or subtract time from the Hardware Clock to +compensate for systematic drift (where the clock consistently loses or +gains time at a certain rate when left to run). + +.SH FUNCTIONS +You need exactly one of the following options to tell +.B hwclock +what function to perform: +.PP +.TP +.BR \-r , \ \-\-show +Read the Hardware Clock and print the time on standard output. +The time shown is always in local time, even if you keep your Hardware Clock +in Coordinated Universal Time. See the +.B \-\-utc +option. +Showing the Hardware Clock time is the default when no function is specified. + +.TP +.B \-\-set +Set the Hardware Clock to the time given by the +.B \-\-date +option. +.TP +.BR \-s , \ \-\-hctosys +Set the System Time from the Hardware Clock. + +Also set the kernel's timezone value to the local timezone +as indicated by the TZ environment variable and/or +.IR /usr/share/zoneinfo , +as +.BR tzset (3) +would interpret them. +The obsolete tz_dsttime field of the kernel's timezone value is set +to DST_NONE. (For details on what this field used to mean, see +.BR settimeofday (2).) + +This is a good option to use in one of the system startup scripts. +.TP +.BR \-w , \ \-\-systohc +Set the Hardware Clock to the current System Time. +.TP +.B \-\-systz +Reset the System Time based on the current timezone. + +Also set the kernel's timezone value to the local timezone +as indicated by the TZ environment variable and/or +.IR /usr/share/zoneinfo , +as +.BR tzset (3) +would interpret them. +The obsolete tz_dsttime field of the kernel's timezone value is set +to DST_NONE. (For details on what this field used to mean, see +.BR settimeofday (2).) + +This is an alternate option to +.B \-\-hctosys +that does not read the hardware clock, and may be used in system startup +scripts for recent 2.6 kernels where you know the System Time contains +the Hardware Clock time. +.TP +.B \-\-adjust +Add or subtract time from the Hardware Clock to account for systematic +drift since the last time the clock was set or adjusted. See discussion +below. +.TP +.B \-\-getepoch +Print the kernel's Hardware Clock epoch value to standard output. +This is the number of years into AD to which a zero year value in the +Hardware Clock refers. For example, if you are using the convention +that the year counter in your Hardware Clock contains the number of +full years since 1952, then the kernel's Hardware Clock epoch value +must be 1952. + +This epoch value is used whenever +.B hwclock +reads or sets the Hardware Clock. +.TP +.B \-\-setepoch +Set the kernel's Hardware Clock epoch value to the value specified by the +.B \-\-epoch +option. See the +.B \-\-getepoch +option for details. + +.TP +.BI \-\-predict +Predict what the RTC will read at time given by the +.B \-\-date +option based on the adjtime file. This is useful for example if you +need to set an RTC wakeup time to distant future and want to account +for the RTC drift. +.TP +.BR \-h , \ \-\-help +Display a help text and exit. +.TP +.BR \-V , \ \-\-version +Display the version of +.B hwclock +and exit. + +.SH OPTIONS +.PP +The first two options apply to just a few specific functions, +the others apply to most functions. +.TP +.BI \-\-date= date_string +You need this option if you specify the +.B \-\-set +or +.B \-\-predict +functions, otherwise it is ignored. +It specifies the time to which to set the Hardware Clock, or the +time for which to predict the Hardware Clock reading. +The value of this option is an argument to the +.BR date (1) +program. +For example: +.sp +.B " hwclock" --set --date="2011-08-14 16:45:05" +.sp +The argument must be in local time, even if you keep your Hardware Clock in +Coordinated Universal time. See the +.B \-\-utc +option. + +.TP +.BI \-\-epoch= year +Specifies the year which is the beginning of the Hardware Clock's +epoch, that is the number of years into AD to which a zero value in the +Hardware Clock's year counter refers. It is used together with +the \fB\-\-setepoch\fR option to set the kernel's idea of the epoch of the +Hardware Clock, or otherwise to specify the epoch for use with +direct ISA access. + +For example, on a Digital Unix machine: +.sp +.B " hwclock" --setepoch --epoch=1952 + +.TP +.BR \-u , \ \-\-utc +.TP +.B \-\-localtime +Indicates that the Hardware Clock is kept in Coordinated Universal +Time or local time, respectively. It is your choice whether to keep +your clock in UTC or local time, but nothing in the clock tells which +you've chosen. So this option is how you give that information to +.BR hwclock . + +If you specify the wrong one of these options (or specify neither and +take a wrong default), both setting and querying of the Hardware Clock +will be messed up. + +If you specify neither +.B \-\-utc +nor +.BR \-\-localtime , +the default is whichever was specified the last time +.B hwclock +was used to set the clock (i.e. +.B hwclock +was successfully run with the +.BR \-\-set , +.BR \-\-systohc , +or +.B \-\-adjust +options), as recorded in the adjtime file. If the adjtime file doesn't +exist, the default is UTC time. + +.TP +.B \-\-noadjfile +Disables the facilities provided by +.IR /etc/adjtime . +.B hwclock +will not read nor write to that file with this option. Either +.B \-\-utc +or +.B \-\-localtime +must be specified when using this option. + +.TP +.BI \-\-adjfile= filename +Overrides the default /etc/adjtime. + +.TP +.BR \-f , \ \-\-rtc=\fIfilename\fB +Overrides the default /dev file name, which is +.IR /dev/rtc +on many platforms but may be +.IR /dev/rtc0 , +.IR /dev/rtc1 , +and so on. + +.TP +.B \-\-directisa +This option is meaningful only on an ISA machine or an Alpha (which implements +enough of ISA to be, roughly speaking, an ISA machine for +.BR hwclock 's +purposes). For other machines, it has no effect. This option tells +.B hwclock +to use explicit I/O instructions to access the Hardware Clock. +Without this option, +.B hwclock +will try to use the /dev/rtc device (which it assumes to be driven by the +RTC device driver). If it is unable to open the device (for reading), it will +use the explicit I/O instructions anyway. + +.TP +.B \-\-badyear +Indicates that the Hardware Clock is incapable of storing years outside +the range 1994-1999. There is a problem in some BIOSes (almost all +Award BIOSes made between 4/26/94 and 5/31/95) wherein they are unable +to deal with years after 1999. If one attempts to set the year-of-century +value to something less than 94 (or 95 in some cases), the value that +actually gets set is 94 (or 95). Thus, if you have one of these machines, +.B hwclock +cannot set the year after 1999 and cannot use the value of the clock as +the true time in the normal way. + +To compensate for this (without your getting a BIOS update, which would +definitely be preferable), always use +.B \-\-badyear +if you have one of these machines. When +.B hwclock +knows it's working with a brain-damaged clock, it ignores the year part of +the Hardware Clock value and instead tries to guess the year based on the +last calibrated date in the adjtime file, by assuming that that date is +within the past year. For this to work, you had better do a +.B hwclock \-\-set +or +.B hwclock \-\-systohc +at least once a year! + +Though +.B hwclock +ignores the year value when it reads the Hardware Clock, it sets the +year value when it sets the clock. It sets it to 1995, 1996, 1997, or +1998, whichever one has the same position in the leap year cycle as +the true year. That way, the Hardware Clock inserts leap days where +they belong. Again, if you let the Hardware Clock run for more than a +year without setting it, this scheme could be defeated and you could +end up losing a day. + +.B hwclock +warns you that you probably need +.B \-\-badyear +whenever it finds your Hardware Clock set to 1994 or 1995. + +.TP +.B \-\-srm +This option is equivalent to +.B \-\-epoch=1900 +and is used to specify the most common epoch on Alphas +with SRM console. +.TP +.B \-\-arc +This option is equivalent to +.B \-\-epoch=1980 +and is used to specify the most common epoch on Alphas +with ARC console (but Ruffians have epoch 1900). +.TP +.B \-\-jensen +.TP +.B \-\-funky\-toy +These two options specify what kind of Alpha machine you have. They +are invalid if you don't have an Alpha and are usually unnecessary +if you do, because +.B hwclock +should be able to determine by itself what it's +running on, at least when +.I /proc +is mounted. +(If you find you need one of these options to make +.B hwclock +work, contact the maintainer to see if the program can be improved +to detect your system automatically. Output of `hwclock --debug' +and `cat /proc/cpuinfo' may be of interest.) + +Option +.B \-\-jensen +means you are running on a Jensen model. And +.B \-\-funky\-toy +means that on your machine one has to use the UF bit instead +of the UIP bit in the Hardware Clock to detect a time transition. "Toy" +in the option name refers to the Time Of Year facility of the machine. + + +.TP +.B \-\-test +Do everything except actually updating the Hardware Clock or anything +else. This is useful, especially in conjunction with +.BR \-\-debug , +in learning about +.BR hwclock . +.TP +.B \-\-debug +Display a lot of information about what +.B hwclock +is doing internally. Some of its function is complex and this output +can help you understand how the program works. + + +.SH NOTES + + +.SH Clocks in a Linux System +.PP +There are two main clocks in a Linux system: +.PP +.B The Hardware Clock: +This is a clock that runs independently of any control program running +in the CPU and even when the machine is powered off. + +On an ISA system, this clock is specified as part of the ISA standard. +The control program can read or set this clock to a whole second, but +the control program can also detect the edges of the 1 second clock +ticks, so the clock actually has virtually infinite precision. +.PP +This clock is commonly called the hardware clock, the real time clock, +the RTC, the BIOS clock, and the CMOS clock. Hardware Clock, in its +capitalized form, was coined for use by +.B hwclock +because all of the other names are inappropriate to the point of being +misleading. +.PP +So for example, some non-ISA systems have a few real time clocks with +only one of them having its own power domain. +A very low power external I2C or SPI clock chip might be used with a +backup battery as the hardware clock to initialize a more functional +integrated real-time clock which is used for most other purposes. +.PP +.B The System Time: +This is the time kept by a clock inside the Linux kernel and driven by +a timer interrupt. (On an ISA machine, the timer interrupt is part of +the ISA standard). It has meaning only while Linux is running on the +machine. The System Time is the number of seconds since 00:00:00 +January 1, 1970 UTC (or more succinctly, the number of seconds since +1969). The System Time is not an integer, though. It has virtually +infinite precision. +.PP +The System Time is the time that matters. The Hardware Clock's basic +purpose in a Linux system is to keep time when Linux is not running. You +initialize the System Time to the time from the Hardware Clock when Linux +starts up, and then never use the Hardware Clock again. Note that in DOS, +for which ISA was designed, the Hardware Clock is the only real time clock. +.PP +It is important that the System Time not have any discontinuities such as +would happen if you used the +.BR date (1L) +program to set it while the system is running. You can, however, do whatever +you want to the Hardware Clock while the system is running, and the next +time Linux starts up, it will do so with the adjusted time from the Hardware +Clock. You can also use the program +.BR adjtimex (8) +to smoothly adjust the System Time while the system runs. +.PP +A Linux kernel maintains a concept of a local timezone for the system. +But don't be misled -- almost nobody cares what timezone the kernel +thinks it is in. Instead, programs that care about the timezone +(perhaps because they want to display a local time for you) almost +always use a more traditional method of determining the timezone: They +use the TZ environment variable and/or the +.I /usr/share/zoneinfo +directory, as explained in the man page for +.BR tzset (3). +However, some +programs and fringe parts of the Linux kernel such as filesystems use +the kernel timezone value. An example is the vfat filesystem. If the +kernel timezone value is wrong, the vfat filesystem will report and +set the wrong timestamps on files. +.PP +.B hwclock +sets the kernel timezone to the value indicated by TZ and/or +.I /usr/share/zoneinfo +when you set the System Time using the +.B \-\-hctosys +option. +.PP +The timezone value actually consists of two parts: 1) a field +tz_minuteswest indicating how many minutes local time (not adjusted +for DST) lags behind UTC, and 2) a field tz_dsttime indicating +the type of Daylight Savings Time (DST) convention that is in effect +in the locality at the present time. +This second field is not used under Linux and is always zero. +(See also +.BR settimeofday (2).) + +.SH Users access and setuid +.PP +Sometimes, you need to install +.B hwclock +setuid root. If you want users other than the superuser to be able to +display the clock value using the direct ISA I/O method, install it setuid +root. If you have the /dev/rtc interface on your system or are on a non-ISA +system, there's probably no need for users to use the direct ISA I/O method, +so don't bother. + +In any case, hwclock will not allow you to set anything unless you have the +superuser real uid. (This is restriction is not necessary if you haven't +installed setuid root, but it's there for now). + +.SH How hwclock Accesses the Hardware Clock +.PP +.B hwclock +uses many different ways to get and set Hardware Clock values. +The most normal way is to do I/O to the device special file /dev/rtc, +which is presumed to be driven by the rtc device driver. However, +this method is not always available. For one thing, the rtc driver is +a relatively recent addition to Linux. Older systems don't have it. +Also, though there are versions of the rtc driver that work on DEC +Alphas, there appear to be plenty of Alphas on which the rtc driver +does not work (a common symptom is hwclock hanging). +Moreover, recent Linux systems have more generic support for RTCs, +even systems that have more than one, so you might need to override +the default by specifying +.I /dev/rtc0 +or +.I /dev/rtc1 +instead. +.PP +On older systems, the method of accessing the Hardware Clock depends on +the system hardware. +.PP +On an ISA system, +.B hwclock +can directly access the "CMOS memory" registers that +constitute the clock, by doing I/O to Ports 0x70 and 0x71. It does +this with actual I/O instructions and consequently can only do it if +running with superuser effective userid. (In the case of a Jensen +Alpha, there is no way for +.B hwclock +to execute those I/O instructions, and so it uses instead the +/dev/port device special file, which provides almost as low-level an +interface to the I/O subsystem). + +This is a really poor method of accessing the clock, for all the +reasons that user space programs are generally not supposed to do +direct I/O and disable interrupts. Hwclock provides it because it is +the only method available on ISA and Alpha systems which don't have +working rtc device drivers available. + +.PP +On an m68k system, +.B hwclock +can access the clock via the console driver, via the device special +file /dev/tty1. +.PP +.B hwclock +tries to use /dev/rtc. If it is compiled for a kernel that doesn't have +that function or it is unable to open /dev/rtc +(or the alternative special file you've defined on the command line) +.B hwclock +will fall back to another method, if available. On an ISA or Alpha +machine, you can force +.B hwclock +to use the direct manipulation of the CMOS registers without even trying +.I /dev/rtc +by specifying the +.B \-\-directisa +option. + + +.SH The Adjust Function +.PP +The Hardware Clock is usually not very accurate. However, much of its +inaccuracy is completely predictable - it gains or loses the same amount +of time every day. This is called systematic drift. +.BR hwclock 's +"adjust" function lets you make systematic corrections to correct the +systematic drift. +.PP +It works like this: +.B hwclock +keeps a file, +.IR /etc/adjtime , +that keeps some historical information. This is called the adjtime file. +.PP +Suppose you start with no adjtime file. You issue a +.I hwclock \-\-set +command to set the Hardware Clock to the true current time. +.B Hwclock +creates the adjtime file and records in it the current time as the +last time the clock was calibrated. +5 days later, the clock has gained 10 seconds, so you issue another +.I hwclock \-\-set +command to set it back 10 seconds. +.B Hwclock +updates the adjtime file to show the current time as the last time the +clock was calibrated, and records 2 seconds per day as the systematic +drift rate. 24 hours go by, and then you issue a +.I hwclock \-\-adjust +command. +.B Hwclock +consults the adjtime file and sees that the clock gains 2 seconds per +day when left alone and that it has been left alone for exactly one +day. So it subtracts 2 seconds from the Hardware Clock. It then +records the current time as the last time the clock was adjusted. +Another 24 hours goes by and you issue another +.IR "hwclock \-\-adjust" . +.B Hwclock +does the same thing: subtracts 2 seconds and updates the adjtime file +with the current time as the last time the clock was adjusted. +.PP +Every time you calibrate (set) the clock (using +.I \-\-set +or +.IR \-\-systohc ), +.B hwclock +recalculates the systematic drift rate based on how long it has been +since the last calibration, how long it has been since the last +adjustment, what drift rate was assumed in any intervening +adjustments, and the amount by which the clock is presently off. +.PP +A small amount of error creeps in any time +.B hwclock +sets the clock, so it refrains from making an adjustment that would be +less than 1 second. Later on, when you request an adjustment again, +the accumulated drift will be more than a second and +.B hwclock +will do the adjustment then. +.PP +It is good to do a +.I hwclock \-\-adjust +just before the +.I hwclock \-\-hctosys +at system startup time, and maybe periodically while the system is +running via cron. +.PP +The adjtime file, while named for its historical purpose of controlling +adjustments only, actually contains other information for use by hwclock +in remembering information from one invocation to the next. +.PP +The format of the adjtime file is, in ASCII: +.PP +Line 1: 3 numbers, separated by blanks: 1) systematic drift rate in +seconds per day, floating point decimal; 2) Resulting number of +seconds since 1969 UTC of most recent adjustment or calibration, +decimal integer; 3) zero (for compatibility with +.BR clock (8)) +as a decimal integer. +.PP +Line 2: 1 number: Resulting number of seconds since 1969 UTC of most +recent calibration. Zero if there has been no calibration yet or it +is known that any previous calibration is moot (for example, because +the Hardware Clock has been found, since that calibration, not to +contain a valid time). This is a decimal integer. +.PP +Line 3: "UTC" or "LOCAL". Tells whether the Hardware Clock is set to +Coordinated Universal Time or local time. You can always override this +value with options on the +.B hwclock +command line. +.PP +You can use an adjtime file that was previously used with the +.BR clock (8) +program with +.BR hwclock . + + +.SH "Automatic Hardware Clock Synchronization By the Kernel" + +You should be aware of another way that the Hardware Clock is kept +synchronized in some systems. The Linux kernel has a mode wherein it +copies the System Time to the Hardware Clock every 11 minutes. +This is a good mode to use when you are using something sophisticated +like ntp to keep your System Time synchronized. (ntp is a way to keep +your System Time synchronized either to a time server somewhere on the +network or to a radio clock hooked up to your system. See RFC 1305). + +This mode (we'll call it "11 minute mode") is off until something +turns it on. The ntp daemon xntpd is one thing that turns it on. You +can turn it off by running anything, including +.IR "hwclock \-\-hctosys" , +that sets the System Time the old fashioned way. + +To see if it is on or +off, use the command +.I adjtimex \-\-print +and look at the value of "status". If the "64" bit of this number +(expressed in binary) equal to 0, 11 minute mode is on. Otherwise, it +is off. + +If your system runs with 11 minute mode on, don't use +.I hwclock \-\-adjust +or +.IR "hwclock \-\-hctosys" . +You'll just make a mess. It is acceptable to use a +.I hwclock \-\-hctosys +at startup time to get a reasonable System Time until your system is +able to set the System Time from the external source and start 11 +minute mode. + + +.SH ISA Hardware Clock Century value + +There is some sort of standard that defines CMOS memory Byte 50 on an ISA +machine as an indicator of what century it is. +.B hwclock +does not use or set that byte because there are some machines that +don't define the byte that way, and it really isn't necessary anyway, +since the year-of-century does a good job of implying which century it +is. + +If you have a bona fide use for a CMOS century byte, contact the +.B hwclock +maintainer; an option may be appropriate. + +Note that this section is only relevant when you are using the "direct +ISA" method of accessing the Hardware Clock. +ACPI provides a standard way to access century values, when they +are supported by the hardware. + +.SH "ENVIRONMENT VARIABLES" +.I TZ + +.SH FILES +.I /etc/adjtime +.I /usr/share/zoneinfo/ +.RI ( /usr/lib/zoneinfo +on old systems) +.I /dev/rtc +.I /dev/rtc0 +.I /dev/port +.I /dev/tty1 +.I /proc/cpuinfo + +.SH "SEE ALSO" +.BR adjtimex (8), +.BR date (1), +.BR gettimeofday (2), +.BR settimeofday (2), +.BR crontab (1), +.BR tzset (3) + +.SH AUTHORS +Written by Bryan Henderson, September 1996 (bryanh@giraffe-data.com), +based on work done on the +.I clock +program by Charles Hedrick, Rob Hooft, and Harald Koenig. +See the source code for complete history and credits. + +.SH AVAILABILITY +The hwclock command is part of the util-linux package and is available from +ftp://ftp.kernel.org/pub/linux/utils/util-linux/. diff --git a/sys-utils/hwclock.c b/sys-utils/hwclock.c new file mode 100644 index 000000000..c0ac67826 --- /dev/null +++ b/sys-utils/hwclock.c @@ -0,0 +1,1895 @@ +/* + * hwclock.c + * + * clock.c was written by Charles Hedrick, hedrick@cs.rutgers.edu, Apr 1992 + * Modified for clock adjustments - Rob Hooft <hooft@chem.ruu.nl>, Nov 1992 + * Improvements by Harald Koenig <koenig@nova.tat.physik.uni-tuebingen.de> + * and Alan Modra <alan@spri.levels.unisa.edu.au>. + * + * Major rewrite by Bryan Henderson <bryanh@giraffe-data.com>, 96.09.19. + * The new program is called hwclock. New features: + * + * - You can set the hardware clock without also modifying the system + * clock. + * - You can read and set the clock with finer than 1 second precision. + * - When you set the clock, hwclock automatically refigures the drift + * rate, based on how far off the clock was before you set it. + * + * Reshuffled things, added sparc code, and re-added alpha stuff + * by David Mosberger <davidm@azstarnet.com> + * and Jay Estabrook <jestabro@amt.tay1.dec.com> + * and Martin Ostermann <ost@coments.rwth-aachen.de>, aeb@cwi.nl, 990212. + * + * Fix for Award 2094 bug, Dave Coffin (dcoffin@shore.net) 11/12/98 + * Change of local time handling, Stefan Ring <e9725446@stud3.tuwien.ac.at> + * Change of adjtime handling, James P. Rutledge <ao112@rgfn.epcc.edu>. + * + * Distributed under GPL + */ +/* + * Explanation of `adjusting' (Rob Hooft): + * + * The problem with my machine is that its CMOS clock is 10 seconds + * per day slow. With this version of clock.c, and my '/etc/rc.local' + * reading '/etc/clock -au' instead of '/etc/clock -u -s', this error + * is automatically corrected at every boot. + * + * To do this job, the program reads and writes the file '/etc/adjtime' + * to determine the correction, and to save its data. In this file are + * three numbers: + * + * 1) the correction in seconds per day. (So if your clock runs 5 + * seconds per day fast, the first number should read -5.0) + * 2) the number of seconds since 1/1/1970 the last time the program + * was used + * 3) the remaining part of a second which was leftover after the last + * adjustment + * + * Installation and use of this program: + * + * a) create a file '/etc/adjtime' containing as the first and only + * line: '0.0 0 0.0' + * b) run 'clock -au' or 'clock -a', depending on whether your cmos is + * in universal or local time. This updates the second number. + * c) set your system time using the 'date' command. + * d) update your cmos time using 'clock -wu' or 'clock -w' + * e) replace the first number in /etc/adjtime by your correction. + * f) put the command 'clock -au' or 'clock -a' in your '/etc/rc.local' + */ + +#include <errno.h> +#include <getopt.h> +#include <limits.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <time.h> +#include <unistd.h> + +#define OPTUTILS_EXIT_CODE EX_USAGE + +#include "c.h" +#include "closestream.h" +#include "nls.h" +#include "optutils.h" +#include "pathnames.h" +#include "strutils.h" +#include "hwclock.h" + +#ifdef HAVE_LIBAUDIT +#include <libaudit.h> +static int hwaudit_fd = -1; +static int hwaudit_on; +#endif + +#define EXCL_ERROR "--{adjust,getepoch,hctosys,predict,set,setepoch,show,systohc,systz}" + +/* The struct that holds our hardware access routines */ +struct clock_ops *ur; + +#define FLOOR(arg) ((arg >= 0 ? (int) arg : ((int) arg) - 1)); + +const char *adj_file_name = NULL; + +struct adjtime { + /* + * This is information we keep in the adjtime file that tells us how + * to do drift corrections. Elements are all straight from the + * adjtime file, so see documentation of that file for details. + * Exception is <dirty>, which is an indication that what's in this + * structure is not what's in the disk file (because it has been + * updated since read from the disk file). + */ + bool dirty; + /* line 1 */ + double drift_factor; + time_t last_adj_time; + double not_adjusted; + /* line 2 */ + time_t last_calib_time; + /* + * The most recent time that we set the clock from an external + * authority (as opposed to just doing a drift adjustment) + */ + /* line 3 */ + enum a_local_utc { LOCAL, UTC, UNKNOWN } local_utc; + /* + * To which time zone, local or UTC, we most recently set the + * hardware clock. + */ +}; + +/* Long only options. */ +enum { + OPT_SET = CHAR_MAX + 1, + OPT_GETEPOCH, + OPT_SETEPOCH, + OPT_NOADJFILE, + OPT_LOCALTIME, + OPT_BADYEAR, + OPT_DIRECTISA, + OPT_TEST, + OPT_DATE, + OPT_EPOCH, + OPT_ADJFILE, + OPT_SYSTZ, + OPT_PREDICT_HC +}; + +/* + * We are running in debug mode, wherein we put a lot of information about + * what we're doing to standard output. + */ +bool debug; + +/* Workaround for Award 4.50g BIOS bug: keep the year in a file. */ +bool badyear; + +/* User-specified epoch, used when rtc fails to return epoch. */ +unsigned long epoch_option = -1; + +/* + * Almost all Award BIOS's made between 04/26/94 and 05/31/95 have a nasty + * bug limiting the RTC year byte to the range 94-99. Any year between 2000 + * and 2093 gets changed to 2094, every time you start the system. + * + * With the --badyear option, we write the date to file and hope that the + * file is updated at least once a year. I recommend putting this command + * "hwclock --badyear" in the monthly crontab, just to be safe. + * + * -- Dave Coffin 11/12/98 + */ +static void write_date_to_file(struct tm *tm) +{ + FILE *fp; + + if ((fp = fopen(_PATH_LASTDATE, "w"))) { + fprintf(fp, "%02d.%02d.%04d\n", tm->tm_mday, tm->tm_mon + 1, + tm->tm_year + 1900); + if (close_stream(fp) != 0) + warn(_("cannot write %s"), _PATH_LASTDATE); + } else + warn(_("cannot write %s"), _PATH_LASTDATE); +} + +static void read_date_from_file(struct tm *tm) +{ + int last_mday, last_mon, last_year; + FILE *fp; + + if ((fp = fopen(_PATH_LASTDATE, "r"))) { + if (fscanf(fp, "%d.%d.%d\n", &last_mday, &last_mon, &last_year) + == 3) { + tm->tm_year = last_year - 1900; + if ((tm->tm_mon << 5) + tm->tm_mday < + ((last_mon - 1) << 5) + last_mday) + tm->tm_year++; + } + fclose(fp); + } + write_date_to_file(tm); +} + +/* + * The difference in seconds between two times in "timeval" format. + */ +double time_diff(struct timeval subtrahend, struct timeval subtractor) +{ + return (subtrahend.tv_sec - subtractor.tv_sec) + + (subtrahend.tv_usec - subtractor.tv_usec) / 1E6; +} + +/* + * The time, in "timeval" format, which is <increment> seconds after the + * time <addend>. Of course, <increment> may be negative. + */ +static struct timeval time_inc(struct timeval addend, double increment) +{ + struct timeval newtime; + + newtime.tv_sec = addend.tv_sec + (int)increment; + newtime.tv_usec = addend.tv_usec + (increment - (int)increment) * 1E6; + + /* + * Now adjust it so that the microsecond value is between 0 and 1 + * million. + */ + if (newtime.tv_usec < 0) { + newtime.tv_usec += 1E6; + newtime.tv_sec -= 1; + } else if (newtime.tv_usec >= 1E6) { + newtime.tv_usec -= 1E6; + newtime.tv_sec += 1; + } + return newtime; +} + +static bool +hw_clock_is_utc(const bool utc, const bool local_opt, + const struct adjtime adjtime) +{ + bool ret; + + if (utc) + ret = TRUE; /* --utc explicitly given on command line */ + else if (local_opt) + ret = FALSE; /* --localtime explicitly given */ + else + /* get info from adjtime file - default is UTC */ + ret = (adjtime.local_utc != LOCAL); + if (debug) + printf(_("Assuming hardware clock is kept in %s time.\n"), + ret ? _("UTC") : _("local")); + return ret; +} + +/* + * Read the adjustment parameters out of the /etc/adjtime file. + * + * Return them as the adjtime structure <*adjtime_p>. If there is no + * /etc/adjtime file, return defaults. If values are missing from the file, + * return defaults for them. + * + * return value 0 if all OK, !=0 otherwise. + */ +static int read_adjtime(struct adjtime *adjtime_p) +{ + FILE *adjfile; + int rc; /* local return code */ + struct stat statbuf; /* We don't even use the contents of this. */ + char line1[81]; /* String: first line of adjtime file */ + char line2[81]; /* String: second line of adjtime file */ + char line3[81]; /* String: third line of adjtime file */ + long timeval; + + rc = stat(adj_file_name, &statbuf); + if (rc < 0 && errno == ENOENT) { + /* He doesn't have a adjtime file, so we'll use defaults. */ + adjtime_p->drift_factor = 0; + adjtime_p->last_adj_time = 0; + adjtime_p->not_adjusted = 0; + adjtime_p->last_calib_time = 0; + adjtime_p->local_utc = UNKNOWN; + adjtime_p->dirty = FALSE; /* don't create a zero adjfile */ + + return 0; + } + + adjfile = fopen(adj_file_name, "r"); /* open file for reading */ + if (adjfile == NULL) { + warn("cannot open file %s", adj_file_name); + return EX_OSFILE; + } + + + if (!fgets(line1, sizeof(line1), adjfile)) + line1[0] = '\0'; /* In case fgets fails */ + if (!fgets(line2, sizeof(line2), adjfile)) + line2[0] = '\0'; /* In case fgets fails */ + if (!fgets(line3, sizeof(line3), adjfile)) + line3[0] = '\0'; /* In case fgets fails */ + + fclose(adjfile); + + /* Set defaults in case values are missing from file */ + adjtime_p->drift_factor = 0; + adjtime_p->last_adj_time = 0; + adjtime_p->not_adjusted = 0; + adjtime_p->last_calib_time = 0; + timeval = 0; + + sscanf(line1, "%lf %ld %lf", + &adjtime_p->drift_factor, + &timeval, &adjtime_p->not_adjusted); + adjtime_p->last_adj_time = timeval; + + sscanf(line2, "%ld", &timeval); + adjtime_p->last_calib_time = timeval; + + if (!strcmp(line3, "UTC\n")) { + adjtime_p->local_utc = UTC; + } else if (!strcmp(line3, "LOCAL\n")) { + adjtime_p->local_utc = LOCAL; + } else { + adjtime_p->local_utc = UNKNOWN; + if (line3[0]) { + warnx(_("Warning: unrecognized third line in adjtime file\n" + "(Expected: `UTC' or `LOCAL' or nothing.)")); + } + } + + adjtime_p->dirty = FALSE; + + if (debug) { + printf(_ + ("Last drift adjustment done at %ld seconds after 1969\n"), + (long)adjtime_p->last_adj_time); + printf(_("Last calibration done at %ld seconds after 1969\n"), + (long)adjtime_p->last_calib_time); + printf(_("Hardware clock is on %s time\n"), + (adjtime_p->local_utc == + LOCAL) ? _("local") : (adjtime_p->local_utc == + UTC) ? _("UTC") : _("unknown")); + } + + return 0; +} + +/* + * Wait until the falling edge of the Hardware Clock's update flag so that + * any time that is read from the clock immediately after we return will be + * exact. + * + * The clock only has 1 second precision, so it gives the exact time only + * once per second, right on the falling edge of the update flag. + * + * We wait (up to one second) either blocked waiting for an rtc device or in + * a CPU spin loop. The former is probably not very accurate. + * + * Return 0 if it worked, nonzero if it didn't. + */ +static int synchronize_to_clock_tick(void) +{ + int rc; + + if (debug) + printf(_("Waiting for clock tick...\n")); + + rc = ur->synchronize_to_clock_tick(); + + if (debug) { + if (rc) + printf(_("...synchronization failed\n")); + else + printf(_("...got clock tick\n")); + } + + return rc; +} + +/* + * Convert a time in broken down format (hours, minutes, etc.) into standard + * unix time (seconds into epoch). Return it as *systime_p. + * + * The broken down time is argument <tm>. This broken down time is either + * in local time zone or UTC, depending on value of logical argument + * "universal". True means it is in UTC. + * + * If the argument contains values that do not constitute a valid time, and + * mktime() recognizes this, return *valid_p == false and *systime_p + * undefined. However, mktime() sometimes goes ahead and computes a + * fictional time "as if" the input values were valid, e.g. if they indicate + * the 31st day of April, mktime() may compute the time of May 1. In such a + * case, we return the same fictional value mktime() does as *systime_p and + * return *valid_p == true. + */ +static void +mktime_tz(struct tm tm, const bool universal, + bool * valid_p, time_t * systime_p) +{ + time_t mktime_result; /* The value returned by our mktime() call */ + char *zone; /* Local time zone name */ + + /* + * We use the C library function mktime(), but since it only works + * on local time zone input, we may have to fake it out by + * temporarily changing the local time zone to UTC. + */ + zone = getenv("TZ"); /* remember original time zone */ + if (universal) { + /* Set timezone to UTC */ + setenv("TZ", "", TRUE); + /* + * Note: tzset() gets called implicitly by the time code, + * but only the first time. When changing the environment + * variable, better call tzset() explicitly. + */ + tzset(); + } + mktime_result = mktime(&tm); + if (mktime_result == -1) { + /* + * This apparently (not specified in mktime() documentation) + * means the 'tm' structure does not contain valid values + * (however, not containing valid values does _not_ imply + * mktime() returns -1). + */ + *valid_p = FALSE; + *systime_p = 0; + if (debug) + printf(_("Invalid values in hardware clock: " + "%4d/%.2d/%.2d %.2d:%.2d:%.2d\n"), + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec); + } else { + *valid_p = TRUE; + *systime_p = mktime_result; + if (debug) + printf(_ + ("Hw clock time : %4d/%.2d/%.2d %.2d:%.2d:%.2d = " + "%ld seconds since 1969\n"), tm.tm_year + 1900, + tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, + tm.tm_sec, (long)*systime_p); + } + /* now put back the original zone. */ + if (zone) + setenv("TZ", zone, TRUE); + else + unsetenv("TZ"); + tzset(); +} + +/* + * Read the hardware clock and return the current time via <tm> argument. + * + * Use the method indicated by <method> argument to access the hardware + * clock. + */ +static int +read_hardware_clock(const bool universal, bool * valid_p, time_t * systime_p) +{ + struct tm tm; + int err; + + err = ur->read_hardware_clock(&tm); + if (err) + return err; + + if (badyear) + read_date_from_file(&tm); + + if (debug) + printf(_ + ("Time read from Hardware Clock: %4d/%.2d/%.2d %02d:%02d:%02d\n"), + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, + tm.tm_min, tm.tm_sec); + mktime_tz(tm, universal, valid_p, systime_p); + + return 0; +} + +/* + * Set the Hardware Clock to the time <newtime>, in local time zone or UTC, + * according to <universal>. + */ +static void +set_hardware_clock(const time_t newtime, + const bool universal, const bool testing) +{ + struct tm new_broken_time; + /* + * Time to which we will set Hardware Clock, in broken down format, + * in the time zone of caller's choice + */ + + if (universal) + new_broken_time = *gmtime(&newtime); + else + new_broken_time = *localtime(&newtime); + + if (debug) + printf(_("Setting Hardware Clock to %.2d:%.2d:%.2d " + "= %ld seconds since 1969\n"), + new_broken_time.tm_hour, new_broken_time.tm_min, + new_broken_time.tm_sec, (long)newtime); + + if (testing) + printf(_("Clock not changed - testing only.\n")); + else { + if (badyear) { + /* + * Write the real year to a file, then write a fake + * year between 1995 and 1998 to the RTC. This way, + * Award BIOS boots on 29 Feb 2000 thinking that + * it's 29 Feb 1996. + */ + write_date_to_file(&new_broken_time); + new_broken_time.tm_year = + 95 + ((new_broken_time.tm_year + 1) & 3); + } + ur->set_hardware_clock(&new_broken_time); + } +} + +/* + * Set the Hardware Clock to the time "sethwtime", in local time zone or + * UTC, according to "universal". + * + * Wait for a fraction of a second so that "sethwtime" is the value of the + * Hardware Clock as of system time "refsystime", which is in the past. For + * example, if "sethwtime" is 14:03:05 and "refsystime" is 12:10:04.5 and + * the current system time is 12:10:06.0: Wait .5 seconds (to make exactly 2 + * seconds since "refsystime") and then set the Hardware Clock to 14:03:07, + * thus getting a precise and retroactive setting of the clock. + * + * (Don't be confused by the fact that the system clock and the Hardware + * Clock differ by two hours in the above example. That's just to remind you + * that there are two independent time scales here). + * + * This function ought to be able to accept set times as fractional times. + * Idea for future enhancement. + */ +static void +set_hardware_clock_exact(const time_t sethwtime, + const struct timeval refsystime, + const bool universal, const bool testing) +{ + time_t newhwtime = sethwtime; + struct timeval beginsystime, nowsystime; + double tdiff; + int time_resync = 1; + + /* + * Now delay some more until Hardware Clock time newhwtime arrives. + * The 0.5 s is because the Hardware Clock always sets to your set + * time plus 500 ms (because it is designed to update to the next + * second precisely 500 ms after you finish the setting). + */ + do { + if (time_resync) { + gettimeofday(&beginsystime, NULL); + tdiff = time_diff(beginsystime, refsystime); + newhwtime = sethwtime + (int)(tdiff + 0.5); + if (debug) + printf(_ + ("Time elapsed since reference time has been %.6f seconds.\n" + "Delaying further to reach the new time.\n"), + tdiff); + time_resync = 0; + } + + gettimeofday(&nowsystime, NULL); + tdiff = time_diff(nowsystime, beginsystime); + if (tdiff < 0) { + time_resync = 1; /* probably backward time reset */ + continue; + } + if (tdiff > 0.1) { + time_resync = 1; /* probably forward time reset */ + continue; + } + beginsystime = nowsystime; + tdiff = time_diff(nowsystime, refsystime); + } while (newhwtime == sethwtime + (int)(tdiff + 0.5)); + + set_hardware_clock(newhwtime, universal, testing); +} + +/* + * Put the time "systime" on standard output in display format. Except if + * hclock_valid == false, just tell standard output that we don't know what + * time it is. + * + * Include in the output the adjustment "sync_duration". + */ +static void +display_time(const bool hclock_valid, const time_t systime, + const double sync_duration) +{ + if (!hclock_valid) + warnx(_ + ("The Hardware Clock registers contain values that are " + "either invalid (e.g. 50th day of month) or beyond the range " + "we can handle (e.g. Year 2095).")); + else { + struct tm *lt; + char *format = "%c"; + char ctime_now[200]; + + lt = localtime(&systime); + strftime(ctime_now, sizeof(ctime_now), format, lt); + printf(_("%s %.6f seconds\n"), ctime_now, -(sync_duration)); + } +} + +/* + * Interpret the value of the --date option, which is something like + * "13:05:01". In fact, it can be any of the myriad ASCII strings that + * specify a time which the "date" program can understand. The date option + * value in question is our "dateopt" argument. + * + * The specified time is in the local time zone. + * + * Our output, "*time_p", is a seconds-into-epoch time. + * + * We use the "date" program to interpret the date string. "date" must be + * runnable by issuing the command "date" to the /bin/sh shell. That means + * in must be in the current PATH. + * + * If anything goes wrong (and many things can), we return return code 10 + * and arbitrary *time_p. Otherwise, return code is 0 and *time_p is valid. + */ +static int interpret_date_string(const char *date_opt, time_t * const time_p) +{ + FILE *date_child_fp; + char date_resp[100]; + const char magic[] = "seconds-into-epoch="; + char date_command[100]; + int retcode; /* our eventual return code */ + int rc; /* local return code */ + + if (date_opt == NULL) { + warnx(_("No --date option specified.")); + return 14; + } + + /* prevent overflow - a security risk */ + if (strlen(date_opt) > sizeof(date_command) - 50) { + warnx(_("--date argument too long")); + return 13; + } + + /* Quotes in date_opt would ruin the date command we construct. */ + if (strchr(date_opt, '"') != NULL) { + warnx(_ + ("The value of the --date option is not a valid date.\n" + "In particular, it contains quotation marks.")); + return 12; + } + + sprintf(date_command, "date --date=\"%s\" +seconds-into-epoch=%%s", + date_opt); + if (debug) + printf(_("Issuing date command: %s\n"), date_command); + + date_child_fp = popen(date_command, "r"); + if (date_child_fp == NULL) { + warn(_("Unable to run 'date' program in /bin/sh shell. " + "popen() failed")); + return 10; + } + + if (!fgets(date_resp, sizeof(date_resp), date_child_fp)) + date_resp[0] = '\0'; /* in case fgets fails */ + if (debug) + printf(_("response from date command = %s\n"), date_resp); + if (strncmp(date_resp, magic, sizeof(magic) - 1) != 0) { + warnx(_("The date command issued by %s returned " + "unexpected results.\n" + "The command was:\n %s\n" + "The response was:\n %s"), + program_invocation_short_name, date_command, date_resp); + retcode = 8; + } else { + long seconds_since_epoch; + rc = sscanf(date_resp + sizeof(magic) - 1, "%ld", + &seconds_since_epoch); + if (rc < 1) { + warnx(_("The date command issued by %s returned " + "something other than an integer where the " + "converted time value was expected.\n" + "The command was:\n %s\n" + "The response was:\n %s\n"), + program_invocation_short_name, date_command, + date_resp); + retcode = 6; + } else { + retcode = 0; + *time_p = seconds_since_epoch; + if (debug) + printf(_("date string %s equates to " + "%ld seconds since 1969.\n"), + date_opt, (long)*time_p); + } + } + pclose(date_child_fp); + + return retcode; +} + +/* + * Set the System Clock to time 'newtime'. + * + * Also set the kernel time zone value to the value indicated by the TZ + * environment variable and/or /usr/lib/zoneinfo/, interpreted as tzset() + * would interpret them. + * + * EXCEPT: if hclock_valid is false, just issue an error message saying + * there is no valid time in the Hardware Clock to which to set the system + * time. + * + * If 'testing' is true, don't actually update anything -- just say we would + * have. + */ +static int +set_system_clock(const bool hclock_valid, const time_t newtime, + const bool testing) +{ + int retcode; + + if (!hclock_valid) { + warnx(_ + ("The Hardware Clock does not contain a valid time, so " + "we cannot set the System Time from it.")); + retcode = 1; + } else { + struct timeval tv; + struct tm *broken; + int minuteswest; + int rc; + + tv.tv_sec = newtime; + tv.tv_usec = 0; + + broken = localtime(&newtime); +#ifdef HAVE_TM_GMTOFF + minuteswest = -broken->tm_gmtoff / 60; /* GNU extension */ +#else + minuteswest = timezone / 60; + if (broken->tm_isdst) + minuteswest -= 60; +#endif + + if (debug) { + printf(_("Calling settimeofday:\n")); + printf(_("\ttv.tv_sec = %ld, tv.tv_usec = %ld\n"), + (long)tv.tv_sec, (long)tv.tv_usec); + printf(_("\ttz.tz_minuteswest = %d\n"), minuteswest); + } + if (testing) { + printf(_ + ("Not setting system clock because running in test mode.\n")); + retcode = 0; + } else { + const struct timezone tz = { minuteswest, 0 }; + + rc = settimeofday(&tv, &tz); + if (rc) { + if (errno == EPERM) { + warnx(_ + ("Must be superuser to set system clock.")); + retcode = EX_NOPERM; + } else { + warn(_("settimeofday() failed")); + retcode = 1; + } + } else + retcode = 0; + } + } + return retcode; +} + +/* + * Reset the System Clock from local time to UTC, based on its current value + * and the timezone unless universal is TRUE. + * + * Also set the kernel time zone value to the value indicated by the TZ + * environment variable and/or /usr/lib/zoneinfo/, interpreted as tzset() + * would interpret them. + * + * If 'testing' is true, don't actually update anything -- just say we would + * have. + */ +static int set_system_clock_timezone(const bool universal, const bool testing) +{ + int retcode; + struct timeval tv; + struct tm *broken; + int minuteswest; + int rc; + + gettimeofday(&tv, NULL); + if (debug) { + struct tm broken_time; + char ctime_now[200]; + + broken_time = *gmtime(&tv.tv_sec); + strftime(ctime_now, sizeof(ctime_now), "%Y/%m/%d %H:%M:%S", + &broken_time); + printf(_("Current system time: %ld = %s\n"), (long)tv.tv_sec, + ctime_now); + } + + broken = localtime(&tv.tv_sec); +#ifdef HAVE_TM_GMTOFF + minuteswest = -broken->tm_gmtoff / 60; /* GNU extension */ +#else + minuteswest = timezone / 60; + if (broken->tm_isdst) + minuteswest -= 60; +#endif + + gettimeofday(&tv, NULL); + if (!universal) + tv.tv_sec += minuteswest * 60; + + if (debug) { + struct tm broken_time; + char ctime_now[200]; + + broken_time = *gmtime(&tv.tv_sec); + strftime(ctime_now, sizeof(ctime_now), "%Y/%m/%d %H:%M:%S", + &broken_time); + + printf(_("Calling settimeofday:\n")); + printf(_("\tUTC: %s\n"), ctime_now); + printf(_("\ttv.tv_sec = %ld, tv.tv_usec = %ld\n"), + (long)tv.tv_sec, (long)tv.tv_usec); + printf(_("\ttz.tz_minuteswest = %d\n"), minuteswest); + } + if (testing) { + printf(_ + ("Not setting system clock because running in test mode.\n")); + retcode = 0; + } else { + const struct timezone tz = { minuteswest, 0 }; + + rc = settimeofday(&tv, &tz); + if (rc) { + if (errno == EPERM) { + warnx(_ + ("Must be superuser to set system clock.")); + retcode = EX_NOPERM; + } else { + warn(_("settimeofday() failed")); + retcode = 1; + } + } else + retcode = 0; + } + return retcode; +} + +/* + * Update the drift factor in <*adjtime_p> to reflect the fact that the + * Hardware Clock was calibrated to <nowtime> and before that was set to + * <hclocktime>. + * + * We record in the adjtime file the time at which we last calibrated the + * clock so we can compute the drift rate each time we calibrate. + * + * EXCEPT: if <hclock_valid> is false, assume Hardware Clock was not set + * before to anything meaningful and regular adjustments have not been done, + * so don't adjust the drift factor. + */ +static void +adjust_drift_factor(struct adjtime *adjtime_p, + const time_t nowtime, + const bool hclock_valid, + const time_t hclocktime, const double sync_delay) +{ + if (!hclock_valid) { + if (debug) + printf(_("Not adjusting drift factor because the " + "Hardware Clock previously contained " + "garbage.\n")); + } else if (adjtime_p->last_calib_time == 0) { + if (debug) + printf(_("Not adjusting drift factor because last " + "calibration time is zero,\n" + "so history is bad and calibration startover " + "is necessary.\n")); + } else if ((hclocktime - adjtime_p->last_calib_time) < 23 * 60 * 60) { + if (debug) + printf(_("Not adjusting drift factor because it has " + "been less than a day since the last " + "calibration.\n")); + } else if (adjtime_p->last_calib_time != 0) { + /* + * At adjustment time we adjust the hardware clock according + * to the contents of /etc/adjtime. + * + * At calibration time we set the hardware clock and update + * /etc/adjtime, that is, for each calibration (except the + * first) we also do an adjustment. + * + * We are now at calibration time. + * + * Let us do computation in doubles. (Floats almost suffice, + * but 195 days + 1 second equals 195 days in floats.) + */ + const double sec_per_day = 24.0 * 60.0 * 60.0; + double atime_per_htime; + double adj_days, cal_days; + double exp_drift, unc_drift; + double factor_adjust; + + /* Adjusted time units per hardware time unit */ + atime_per_htime = 1.0 + adjtime_p->drift_factor / sec_per_day; + + /* Days since last adjustment (in hardware clock time) */ + adj_days = (double)(hclocktime - adjtime_p->last_adj_time) + / sec_per_day; + + /* Expected drift (sec) since last adjustment */ + exp_drift = adj_days * adjtime_p->drift_factor + + adjtime_p->not_adjusted; + + /* Uncorrected drift (sec) since last calibration */ + unc_drift = (double)(nowtime - hclocktime) + + sync_delay - exp_drift; + + /* Days since last calibration (in hardware clock time) */ + cal_days = ((double)(adjtime_p->last_adj_time + - adjtime_p->last_calib_time) + + adjtime_p->not_adjusted) + / (sec_per_day * atime_per_htime) + adj_days; + + /* Amount to add to previous drift factor */ + factor_adjust = unc_drift / cal_days; + + if (debug) + printf(_("Clock drifted %.1f seconds in the past " + "%d seconds in spite of a drift factor of " + "%f seconds/day.\n" + "Adjusting drift factor by %f seconds/day\n"), + unc_drift, + (int)(nowtime - adjtime_p->last_calib_time), + adjtime_p->drift_factor, factor_adjust); + + adjtime_p->drift_factor += factor_adjust; + } + adjtime_p->last_calib_time = nowtime; + + adjtime_p->last_adj_time = nowtime; + + adjtime_p->not_adjusted = 0; + + adjtime_p->dirty = TRUE; +} + +/* + * Do the drift adjustment calculation. + * + * The way we have to set the clock, we need the adjustment in two parts: + * + * 1) an integer number of seconds (return as *adjustment_p) + * 2) a positive fraction of a second (less than 1) (return as *retro_p) + * + * The sum of these two values is the adjustment needed. Positive means to + * advance the clock or insert seconds. Negative means to retard the clock + * or remove seconds. + */ +static void +calculate_adjustment(const double factor, + const time_t last_time, + const double not_adjusted, + const time_t systime, int *adjustment_p, double *retro_p) +{ + double exact_adjustment; + + exact_adjustment = + ((double)(systime - last_time)) * factor / (24 * 60 * 60) + + not_adjusted; + *adjustment_p = FLOOR(exact_adjustment); + + *retro_p = exact_adjustment - (double)*adjustment_p; + if (debug) { + printf(_("Time since last adjustment is %d seconds\n"), + (int)(systime - last_time)); + printf(_("Need to insert %d seconds and refer time back " + "%.6f seconds ago\n"), *adjustment_p, *retro_p); + } +} + +/* + * Write the contents of the <adjtime> structure to its disk file. + * + * But if the contents are clean (unchanged since read from disk), don't + * bother. + */ +static void save_adjtime(const struct adjtime adjtime, const bool testing) +{ + char newfile[412]; /* Stuff to write to disk file */ + + if (adjtime.dirty) { + /* + * snprintf is not always available, but this is safe as + * long as libc does not use more than 100 positions for %ld + * or %f + */ + sprintf(newfile, "%f %ld %f\n%ld\n%s\n", + adjtime.drift_factor, + (long)adjtime.last_adj_time, + adjtime.not_adjusted, + (long)adjtime.last_calib_time, + (adjtime.local_utc == UTC) ? "UTC" : "LOCAL"); + + if (testing) { + printf(_ + ("Not updating adjtime file because of testing mode.\n")); + printf(_("Would have written the following to %s:\n%s"), + adj_file_name, newfile); + } else { + FILE *adjfile; + int err = 0; + + adjfile = fopen(adj_file_name, "w"); + if (adjfile == NULL) { + warn(_ + ("Could not open file with the clock adjustment parameters " + "in it (%s) for writing"), adj_file_name); + err = 1; + } else { + if (fputs(newfile, adjfile) < 0) { + warn(_ + ("Could not update file with the clock adjustment " + "parameters (%s) in it"), + adj_file_name); + err = 1; + } + if (close_stream(adjfile) != 0) { + warn(_ + ("Could not update file with the clock adjustment " + "parameters (%s) in it"), + adj_file_name); + err = 1; + } + } + if (err) + warnx(_ + ("Drift adjustment parameters not updated.")); + } + } +} + +/* + * Do the adjustment requested, by 1) setting the Hardware Clock (if + * necessary), and 2) updating the last-adjusted time in the adjtime + * structure. + * + * Do not update anything if the Hardware Clock does not currently present a + * valid time. + * + * Arguments <factor> and <last_time> are current values from the adjtime + * file. + * + * <hclock_valid> means the Hardware Clock contains a valid time, and that + * time is <hclocktime>. + * + * <read_time> is the current system time (to be precise, it is the system + * time at the time <hclocktime> was read, which due to computational delay + * could be a short time ago). + * + * <universal>: the Hardware Clock is kept in UTC. + * + * <testing>: We are running in test mode (no updating of clock). + * + * We do not bother to update the clock if the adjustment would be less than + * one second. This is to avoid cumulative error and needless CPU hogging + * (remember we use an infinite loop for some timing) if the user runs us + * frequently. + */ +static void +do_adjustment(struct adjtime *adjtime_p, + const bool hclock_valid, const time_t hclocktime, + const struct timeval read_time, + const bool universal, const bool testing) +{ + if (!hclock_valid) { + warnx(_("The Hardware Clock does not contain a valid time, " + "so we cannot adjust it.")); + adjtime_p->last_calib_time = 0; /* calibration startover is required */ + adjtime_p->last_adj_time = 0; + adjtime_p->not_adjusted = 0; + adjtime_p->dirty = TRUE; + } else if (adjtime_p->last_adj_time == 0) { + if (debug) + printf(_ + ("Not setting clock because last adjustment time is zero, " + "so history is bad.")); + } else { + int adjustment; + /* Number of seconds we must insert in the Hardware Clock */ + double retro; + /* + * Fraction of second we have to remove from clock after + * inserting <adjustment> whole seconds. + */ + calculate_adjustment(adjtime_p->drift_factor, + adjtime_p->last_adj_time, + adjtime_p->not_adjusted, + hclocktime, &adjustment, &retro); + if (adjustment > 0 || adjustment < -1) { + set_hardware_clock_exact(hclocktime + adjustment, + time_inc(read_time, -retro), + universal, testing); + adjtime_p->last_adj_time = hclocktime + adjustment; + adjtime_p->not_adjusted = 0; + adjtime_p->dirty = TRUE; + } else if (debug) + printf(_("Needed adjustment is less than one second, " + "so not setting clock.\n")); + } +} + +static void determine_clock_access_method(const bool user_requests_ISA) +{ + ur = NULL; + + if (user_requests_ISA) + ur = probe_for_cmos_clock(); + +#ifdef __linux__ + if (!ur) + ur = probe_for_rtc_clock(); +#endif + + if (!ur) + ur = probe_for_kd_clock(); + + if (!ur && !user_requests_ISA) + ur = probe_for_cmos_clock(); + + if (debug) { + if (ur) + printf(_("Using %s.\n"), ur->interface_name); + else + printf(_("No usable clock interface found.\n")); + } +} + +/* + * Do all the normal work of hwclock - read, set clock, etc. + * + * Issue output to stdout and error message to stderr where appropriate. + * + * Return rc == 0 if everything went OK, rc != 0 if not. + */ +static int +manipulate_clock(const bool show, const bool adjust, const bool noadjfile, + const bool set, const time_t set_time, + const bool hctosys, const bool systohc, const bool systz, + const struct timeval startup_time, + const bool utc, const bool local_opt, + const bool testing, const bool predict) +{ + /* Contents of the adjtime file, or what they should be. */ + struct adjtime adjtime; + bool universal; + /* Set if user lacks necessary authorization to access the clock */ + bool no_auth; + /* The time at which we read the Hardware Clock */ + struct timeval read_time; + /* + * The Hardware Clock gives us a valid time, or at + * least something close enough to fool mktime(). + */ + bool hclock_valid = FALSE; + /* + * The time the hardware clock had just after we + * synchronized to its next clock tick when we + * started up. Defined only if hclock_valid is true. + */ + time_t hclocktime = 0; + /* local return code */ + int rc = 0; + + if (!systz && !predict) { + no_auth = ur->get_permissions(); + if (no_auth) + return EX_NOPERM; + } + + if (!noadjfile + && (adjust || set || systohc || (!utc && !local_opt) || predict)) { + rc = read_adjtime(&adjtime); + if (rc) + return rc; + } else { + /* A little trick to avoid reading the file if we don't have to */ + adjtime.dirty = FALSE; + } + + universal = hw_clock_is_utc(utc, local_opt, adjtime); + + if ((set || systohc || adjust) && + (adjtime.local_utc == UTC) != universal) { + adjtime.local_utc = universal ? UTC : LOCAL; + adjtime.dirty = TRUE; + } + + if (show || adjust || hctosys || (!noadjfile && !systz && !predict)) { + /* data from HW-clock are required */ + rc = synchronize_to_clock_tick(); + + /* + * 2 = synchronization timeout. We don't + * error out if the user is attempting to + * set the RTC - the RTC could be + * functioning but contain invalid time data + * so we still want to allow a user to set + * the RTC time. + */ + if (rc && rc != 2 && !set && !systohc) + return EX_IOERR; + gettimeofday(&read_time, NULL); + + /* + * If we can't synchronize to a clock tick, + * we likely can't read from the RTC so + * don't bother reading it again. + */ + if (!rc) { + rc = read_hardware_clock(universal, + &hclock_valid, &hclocktime); + if (rc && !set && !systohc) + return EX_IOERR; + } + } + + if (show) { + display_time(hclock_valid, hclocktime, + time_diff(read_time, startup_time)); + } else if (set) { + set_hardware_clock_exact(set_time, startup_time, + universal, testing); + if (!noadjfile) + adjust_drift_factor(&adjtime, set_time, + hclock_valid, + hclocktime, + time_diff(read_time, startup_time)); + } else if (adjust) { + do_adjustment(&adjtime, hclock_valid, + hclocktime, read_time, universal, testing); + } else if (systohc) { + struct timeval nowtime, reftime; + /* + * We can only set_hardware_clock_exact to a + * whole seconds time, so we set it with + * reference to the most recent whole + * seconds time. + */ + gettimeofday(&nowtime, NULL); + reftime.tv_sec = nowtime.tv_sec; + reftime.tv_usec = 0; + set_hardware_clock_exact((time_t) + reftime.tv_sec, + reftime, universal, testing); + if (!noadjfile) + adjust_drift_factor(&adjtime, (time_t) + reftime.tv_sec, + hclock_valid, hclocktime, (double) + read_time.tv_usec / 1E6); + } else if (hctosys) { + rc = set_system_clock(hclock_valid, hclocktime, testing); + if (rc) { + printf(_("Unable to set system clock.\n")); + return rc; + } + } else if (systz) { + rc = set_system_clock_timezone(universal, testing); + if (rc) { + printf(_("Unable to set system clock.\n")); + return rc; + } + } else if (predict) { + int adjustment; + double retro; + + calculate_adjustment(adjtime.drift_factor, + adjtime.last_adj_time, + adjtime.not_adjusted, + set_time, &adjustment, &retro); + if (debug) { + printf(_ + ("At %ld seconds after 1969, RTC is predicted to read %ld seconds after 1969.\n"), + set_time, set_time + adjustment); + } + display_time(TRUE, set_time + adjustment, -retro); + } + if (!noadjfile) + save_adjtime(adjtime, testing); + return 0; +} + +/* + * Get or set the Hardware Clock epoch value in the kernel, as appropriate. + * <getepoch>, <setepoch>, and <epoch> are hwclock invocation options. + * + * <epoch> == -1 if the user did not specify an "epoch" option. + */ +#ifdef __linux__ +/* + * Maintenance note: This should work on non-Alpha machines, but the + * evidence today (98.03.04) indicates that the kernel only keeps the epoch + * value on Alphas. If that is ever fixed, this function should be changed. + */ +# ifndef __alpha__ +static void +manipulate_epoch(const bool getepoch __attribute__ ((__unused__)), + const bool setepoch __attribute__ ((__unused__)), + const unsigned long epoch_opt __attribute__ ((__unused__)), + const bool testing __attribute__ ((__unused__))) +{ + warnx(_("The kernel keeps an epoch value for the Hardware Clock " + "only on an Alpha machine.\nThis copy of hwclock was built for " + "a machine other than Alpha\n(and thus is presumably not running " + "on an Alpha now). No action taken.")); +} +# else +static void +manipulate_epoch(const bool getepoch, + const bool setepoch, + const unsigned long epoch_opt, + const bool testing) +{ + if (getepoch) { + unsigned long epoch; + + if (get_epoch_rtc(&epoch, 0)) + warnx(_ + ("Unable to get the epoch value from the kernel.")); + else + printf(_("Kernel is assuming an epoch value of %lu\n"), + epoch); + } else if (setepoch) { + if (epoch_opt == -1) + warnx(_ + ("To set the epoch value, you must use the 'epoch' " + "option to tell to what value to set it.")); + else if (testing) + printf(_ + ("Not setting the epoch to %d - testing only.\n"), + epoch_opt); + else if (set_epoch_rtc(epoch_opt)) + printf(_ + ("Unable to set the epoch value in the kernel.\n")); + } +} +# endif /* __alpha__ */ +#endif /* __linux__ */ + +static void out_version(void) +{ + printf(_("%s from %s\n"), program_invocation_short_name, PACKAGE_STRING); +} + +/* + * usage - Output (error and) usage information + * + * This function is called both directly from main to show usage information + * and as fatal function from shhopt if some argument is not understood. In + * case of normal usage info FMT should be NULL. In that case the info is + * printed to stdout. If FMT is given usage will act like fprintf( stderr, + * fmt, ... ), show a usage information and terminate the program + * afterwards. + */ +static void usage(const char *fmt, ...) +{ + FILE *usageto; + va_list ap; + + usageto = fmt ? stderr : stdout; + + fputs(_("\nUsage:\n"), usageto); + fputs(_(" hwclock [function] [option...]\n"), usageto); + + fputs(_("\nFunctions:\n"), usageto); + fputs(_(" -h, --help show this help text and exit\n" + " -r, --show read hardware clock and print result\n" + " --set set the RTC to the time given with --date\n"), usageto); + fputs(_(" -s, --hctosys set the system time from the hardware clock\n" + " -w, --systohc set the hardware clock from the current system time\n" + " --systz set the system time based on the current timezone\n" + " --adjust adjust the RTC to account for systematic drift since\n" + " the clock was last set or adjusted\n"), usageto); +#ifdef __linux__ + fputs(_(" --getepoch print out the kernel's hardware clock epoch value\n" + " --setepoch set the kernel's hardware clock epoch value to the \n" + " value given with --epoch\n"), usageto); +#endif + fputs(_(" --predict predict RTC reading at time given with --date\n" + " -V, --version display version information and exit\n"), usageto); + + fputs(_("\nOptions:\n"), usageto); + fputs(_(" -u, --utc the hardware clock is kept in UTC\n" + " --localtime the hardware clock is kept in local time\n"), usageto); +#ifdef __linux__ + fputs(_(" -f, --rtc <file> special /dev/... file to use instead of default\n"), usageto); +#endif + fprintf(usageto, _( + " --directisa access the ISA bus directly instead of %s\n" + " --badyear ignore RTC's year because the BIOS is broken\n" + " --date <time> specifies the time to which to set the hardware clock\n" + " --epoch <year> specifies the year which is the beginning of the\n" + " hardware clock's epoch value\n"), _PATH_RTC_DEV); + fprintf(usageto, _( + " --noadjfile do not access %s; this requires the use of\n" + " either --utc or --localtime\n" + " --adjfile <file> specifies the path to the adjust file;\n" + " the default is %s\n"), _PATH_ADJPATH, _PATH_ADJPATH); + fputs(_(" --test do not update anything, just show what would happen\n" + " -D, --debug debugging mode\n" "\n"), usageto); +#ifdef __alpha__ + fputs(_(" -J|--jensen, -A|--arc, -S|--srm, -F|--funky-toy\n" + " tell hwclock the type of Alpha you have (see hwclock(8))\n" + "\n"), usageto); +#endif + + if (fmt) { + va_start(ap, fmt); + vfprintf(usageto, fmt, ap); + va_end(ap); + } + + fflush(usageto); + hwclock_exit(fmt ? EX_USAGE : EX_OK); +} + +/* + * Returns: + * EX_USAGE: bad invocation + * EX_NOPERM: no permission + * EX_OSFILE: cannot open /dev/rtc or /etc/adjtime + * EX_IOERR: ioctl error getting or setting the time + * 0: OK (or not) + * 1: failure + */ +int main(int argc, char **argv) +{ + struct timeval startup_time; + /* + * The time we started up, in seconds into the epoch, including + * fractions. + */ + time_t set_time = 0; /* Time to which user said to set Hardware Clock */ + + bool permitted; /* User is permitted to do the function */ + int rc, c; + + enum { + EXCL_NONE, + + EXCL_ADJFILE, + EXCL_NO_AJDFILE, + + EXCL_LOCALTIME, + EXCL_UTC, + + EXCL_ADJUST, + EXCL_GETEPOCH, + EXCL_HCTOSYS, + EXCL_PREDICT, + EXCL_SET, + EXCL_SETEPOCH, + EXCL_SHOW, + EXCL_SYSTOHC, + EXCL_SYSTZ + }; + int excl_adj = EXCL_NONE; + int excl_utc_local = EXCL_NONE; + int excl_action = EXCL_NONE; + + /* Variables set by various options; show may also be set later */ + /* The options debug, badyear and epoch_option are global */ + bool show, set, systohc, hctosys, systz, adjust, getepoch, setepoch, + predict; + bool utc, testing, local_opt, noadjfile, directisa; + char *date_opt; +#ifdef __alpha__ + bool ARCconsole, Jensen, SRM, funky_toy; +#endif + + static const struct option longopts[] = { + {"adjust", 0, 0, 'a'}, + {"help", 0, 0, 'h'}, + {"show", 0, 0, 'r'}, + {"hctosys", 0, 0, 's'}, + {"utc", 0, 0, 'u'}, + {"version", 0, 0, 'v'}, + {"systohc", 0, 0, 'w'}, + {"debug", 0, 0, 'D'}, +#ifdef __alpha__ + {"ARC", 0, 0, 'A'}, + {"arc", 0, 0, 'A'}, + {"Jensen", 0, 0, 'J'}, + {"jensen", 0, 0, 'J'}, + {"SRM", 0, 0, 'S'}, + {"srm", 0, 0, 'S'}, + {"funky-toy", 0, 0, 'F'}, +#endif + {"set", 0, 0, OPT_SET}, +#ifdef __linux__ + {"getepoch", 0, 0, OPT_GETEPOCH}, + {"setepoch", 0, 0, OPT_SETEPOCH}, +#endif + {"noadjfile", 0, 0, OPT_NOADJFILE}, + {"localtime", 0, 0, OPT_LOCALTIME}, + {"badyear", 0, 0, OPT_BADYEAR}, + {"directisa", 0, 0, OPT_DIRECTISA}, + {"test", 0, 0, OPT_TEST}, + {"date", 1, 0, OPT_DATE}, + {"epoch", 1, 0, OPT_EPOCH}, +#ifdef __linux__ + {"rtc", 1, 0, 'f'}, +#endif + {"adjfile", 1, 0, OPT_ADJFILE}, + {"systz", 0, 0, OPT_SYSTZ}, + {"predict-hc", 0, 0, OPT_PREDICT_HC}, + {NULL, 0, NULL, 0} + }; + + /* Remember what time we were invoked */ + gettimeofday(&startup_time, NULL); + +#ifdef HAVE_LIBAUDIT + hwaudit_fd = audit_open(); + if (hwaudit_fd < 0 && !(errno == EINVAL || errno == EPROTONOSUPPORT || + errno == EAFNOSUPPORT)) { + /* + * You get these error codes only when the kernel doesn't + * have audit compiled in. + */ + warnx(_("Unable to connect to audit system")); + return EX_NOPERM; + } +#endif + setlocale(LC_ALL, ""); +#ifdef LC_NUMERIC + /* + * We need LC_CTYPE and LC_TIME and LC_MESSAGES, but must avoid + * LC_NUMERIC since it gives problems when we write to /etc/adjtime. + * - gqueri@mail.dotcom.fr + */ + setlocale(LC_NUMERIC, "C"); +#endif + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + /* Set option defaults */ + show = set = systohc = hctosys = systz = adjust = noadjfile = predict = + FALSE; + getepoch = setepoch = utc = local_opt = directisa = testing = debug = FALSE; +#ifdef __alpha__ + ARCconsole = Jensen = SRM = funky_toy = badyear = FALSE; +#endif + date_opt = NULL; + + while ((c = getopt_long(argc, argv, "?hvVDarsuwAJSFf:", longopts, NULL)) + != -1) { + switch (c) { + case 'D': + debug = TRUE; + break; + case 'a': + adjust = TRUE; + exclusive_option(&excl_action, EXCL_ADJUST, EXCL_ERROR); + break; + case 'r': + show = TRUE; + exclusive_option(&excl_action, EXCL_SHOW, EXCL_ERROR); + break; + case 's': + hctosys = TRUE; + exclusive_option(&excl_action, EXCL_HCTOSYS, EXCL_ERROR); + break; + case 'u': + utc = TRUE; + exclusive_option(&excl_utc_local, EXCL_UTC, "--{utc,localtime}"); + break; + case 'w': + systohc = TRUE; + exclusive_option(&excl_action, EXCL_SYSTOHC, EXCL_ERROR); + break; +#ifdef __alpha__ + case 'A': + ARCconsole = TRUE; + break; + case 'J': + Jensen = TRUE; + break; + case 'S': + SRM = TRUE; + break; + case 'F': + funky_toy = TRUE; + break; +#endif + case OPT_SET: + set = TRUE; + exclusive_option(&excl_action, EXCL_SET, EXCL_ERROR); + break; +#ifdef __linux__ + case OPT_GETEPOCH: + getepoch = TRUE; + exclusive_option(&excl_action, EXCL_GETEPOCH, EXCL_ERROR); + break; + case OPT_SETEPOCH: + setepoch = TRUE; + exclusive_option(&excl_action, EXCL_SETEPOCH, EXCL_ERROR); + break; +#endif + case OPT_NOADJFILE: + noadjfile = TRUE; + exclusive_option(&excl_adj, EXCL_NO_AJDFILE, "--{adjfile,noadjfile}"); + break; + case OPT_LOCALTIME: + local_opt = TRUE; /* --localtime */ + exclusive_option(&excl_utc_local, EXCL_LOCALTIME, "--{utc,localtime}"); + break; + case OPT_BADYEAR: + badyear = TRUE; + break; + case OPT_DIRECTISA: + directisa = TRUE; + break; + case OPT_TEST: + testing = TRUE; /* --test */ + break; + case OPT_DATE: + date_opt = optarg; /* --date */ + break; + case OPT_EPOCH: + epoch_option = /* --epoch */ + strtoul_or_err(optarg, _("invalid epoch argument")); + break; + case OPT_ADJFILE: + adj_file_name = optarg; /* --adjfile */ + exclusive_option(&excl_adj, EXCL_ADJFILE, "--{adjfile,noadjfile}"); + break; + case OPT_SYSTZ: + systz = TRUE; /* --systz */ + exclusive_option(&excl_action, EXCL_SYSTZ, EXCL_ERROR); + break; + case OPT_PREDICT_HC: + predict = TRUE; /* --predict-hc */ + exclusive_option(&excl_action, EXCL_PREDICT, EXCL_ERROR); + break; +#ifdef __linux__ + case 'f': + rtc_dev_name = optarg; /* --rtc */ + break; +#endif + case 'v': /* --version */ + case 'V': + out_version(); + return 0; + case 'h': /* --help */ + case '?': + default: + usage(NULL); + } + } + + argc -= optind; + argv += optind; + +#ifdef HAVE_LIBAUDIT + if (testing != TRUE) { + if (adjust == TRUE || hctosys == TRUE || systohc == TRUE || + set == TRUE || setepoch == TRUE) { + hwaudit_on = TRUE; + } + } +#endif + if (argc > 0) { + usage(_("%s takes no non-option arguments. " + "You supplied %d.\n"), program_invocation_short_name, + argc); + } + + if (!adj_file_name) + adj_file_name = _PATH_ADJPATH; + + if (noadjfile && !excl_utc_local) { + warnx(_("With --noadjfile, you must specify " + "either --utc or --localtime")); + hwclock_exit(EX_USAGE); + } +#ifdef __alpha__ + set_cmos_epoch(ARCconsole, SRM); + set_cmos_access(Jensen, funky_toy); +#endif + + if (set || predict) { + rc = interpret_date_string(date_opt, &set_time); + /* (time-consuming) */ + if (rc != 0) { + warnx(_("No usable set-to time. " + "Cannot set clock.")); + hwclock_exit(EX_USAGE); + } + } + + if (!(show | set | systohc | hctosys | systz | adjust | getepoch + | setepoch | predict)) + show = 1; /* default to show */ + + if (getuid() == 0) + permitted = TRUE; + else { + /* program is designed to run setuid (in some situations) */ + if (set || systohc || adjust) { + warnx(_("Sorry, only the superuser can change " + "the Hardware Clock.")); + permitted = FALSE; + } else if (systz || hctosys) { + warnx(_("Sorry, only the superuser can change " + "the System Clock.")); + permitted = FALSE; + } else if (setepoch) { + warnx(_("Sorry, only the superuser can change the " + "Hardware Clock epoch in the kernel.")); + permitted = FALSE; + } else + permitted = TRUE; + } + + if (!permitted) + hwclock_exit(EX_NOPERM); + +#ifdef __linux__ + if (getepoch || setepoch) { + manipulate_epoch(getepoch, setepoch, epoch_option, testing); + hwclock_exit(EX_OK); + } +#endif + + if (debug) + out_version(); + + if (!systz && !predict) { + determine_clock_access_method(directisa); + if (!ur) { + warnx(_("Cannot access the Hardware Clock via " + "any known method.")); + if (!debug) + warnx(_("Use the --debug option to see the " + "details of our search for an access " + "method.")); + hwclock_exit(EX_SOFTWARE); + } + } + + rc = manipulate_clock(show, adjust, noadjfile, set, set_time, + hctosys, systohc, systz, startup_time, utc, + local_opt, testing, predict); + hwclock_exit(rc); + return rc; /* Not reached */ +} + +#ifdef HAVE_LIBAUDIT +/* + * hwclock_exit calls either this function or plain exit depending + * HAVE_LIBAUDIT see also clock.h + */ +void __attribute__((__noreturn__)) hwaudit_exit(int status) +{ + if (hwaudit_on) { + audit_log_user_message(hwaudit_fd, AUDIT_USYS_CONFIG, + "changing system time", NULL, NULL, NULL, + status ? 0 : 1); + close(hwaudit_fd); + } + exit(status); +} +#endif + +/* + * History of this program: + * + * 98.08.12 BJH Version 2.4 + * + * Don't use century byte from Hardware Clock. Add comments telling why. + * + * 98.06.20 BJH Version 2.3. + * + * Make --hctosys set the kernel timezone from TZ environment variable + * and/or /usr/lib/zoneinfo. From Klaus Ripke (klaus@ripke.com). + * + * 98.03.05 BJH. Version 2.2. + * + * Add --getepoch and --setepoch. + * + * Fix some word length things so it works on Alpha. + * + * Make it work when /dev/rtc doesn't have the interrupt functions. In this + * case, busywait for the top of a second instead of blocking and waiting + * for the update complete interrupt. + * + * Fix a bunch of bugs too numerous to mention. + * + * 97.06.01: BJH. Version 2.1. Read and write the century byte (Byte 50) of + * the ISA Hardware Clock when using direct ISA I/O. Problem discovered by + * job (jei@iclnl.icl.nl). + * + * Use the rtc clock access method in preference to the KDGHWCLK method. + * Problem discovered by Andreas Schwab <schwab@LS5.informatik.uni-dortmund.de>. + * + * November 1996: Version 2.0.1. Modifications by Nicolai Langfeldt + * (janl@math.uio.no) to make it compile on linux 1.2 machines as well as + * more recent versions of the kernel. Introduced the NO_CLOCK access method + * and wrote feature test code to detect absence of rtc headers. + * + *************************************************************************** + * Maintenance notes + * + * To compile this, you must use GNU compiler optimization (-O option) in + * order to make the "extern inline" functions from asm/io.h (inb(), etc.) + * compile. If you don't optimize, which means the compiler will generate no + * inline functions, the references to these functions in this program will + * be compiled as external references. Since you probably won't be linking + * with any functions by these names, you will have unresolved external + * references when you link. + * + * The program is designed to run setuid superuser, since we need to be able + * to do direct I/O. (More to the point: we need permission to execute the + * iopl() system call). (However, if you use one of the methods other than + * direct ISA I/O to access the clock, no setuid is required). + * + * Here's some info on how we must deal with the time that elapses while + * this program runs: There are two major delays as we run: + * + * 1) Waiting up to 1 second for a transition of the Hardware Clock so + * we are synchronized to the Hardware Clock. + * 2) Running the "date" program to interpret the value of our --date + * option. + * + * Reading the /etc/adjtime file is the next biggest source of delay and + * uncertainty. + * + * The user wants to know what time it was at the moment he invoked us, not + * some arbitrary time later. And in setting the clock, he is giving us the + * time at the moment we are invoked, so if we set the clock some time + * later, we have to add some time to that. + * + * So we check the system time as soon as we start up, then run "date" and + * do file I/O if necessary, then wait to synchronize with a Hardware Clock + * edge, then check the system time again to see how much time we spent. We + * immediately read the clock then and (if appropriate) report that time, + * and additionally, the delay we measured. + * + * If we're setting the clock to a time given by the user, we wait some more + * so that the total delay is an integral number of seconds, then set the + * Hardware Clock to the time the user requested plus that integral number + * of seconds. N.B. The Hardware Clock can only be set in integral seconds. + * + * If we're setting the clock to the system clock value, we wait for the + * system clock to reach the top of a second, and then set the Hardware + * Clock to the system clock's value. + * + * Here's an interesting point about setting the Hardware Clock: On my + * machine, when you set it, it sets to that precise time. But one can + * imagine another clock whose update oscillator marches on a steady one + * second period, so updating the clock between any two oscillator ticks is + * the same as updating it right at the earlier tick. To avoid any + * complications that might cause, we set the clock as soon as possible + * after an oscillator tick. + * + * About synchronizing to the Hardware Clock when reading the time: The + * precision of the Hardware Clock counters themselves is one second. You + * can't read the counters and find out that is 12:01:02.5. But if you + * consider the location in time of the counter's ticks as part of its + * value, then its precision is as infinite as time is continuous! What I'm + * saying is this: To find out the _exact_ time in the hardware clock, we + * wait until the next clock tick (the next time the second counter changes) + * and measure how long we had to wait. We then read the value of the clock + * counters and subtract the wait time and we know precisely what time it + * was when we set out to query the time. + * + * hwclock uses this method, and considers the Hardware Clock to have + * infinite precision. + * + * TODO: Enhancements needed: + * + * - When waiting for whole second boundary in set_hardware_clock_exact, + * fail if we miss the goal by more than .1 second, as could happen if we + * get pre-empted (by the kernel dispatcher). + */ diff --git a/sys-utils/hwclock.h b/sys-utils/hwclock.h new file mode 100644 index 000000000..175a6d1ae --- /dev/null +++ b/sys-utils/hwclock.h @@ -0,0 +1,47 @@ +#ifndef HWCLOCK_CLOCK_H +#define HWCLOCK_CLOCK_H + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include "c.h" + +struct clock_ops { + char *interface_name; + int (*get_permissions) (void); + int (*read_hardware_clock) (struct tm * tm); + int (*set_hardware_clock) (const struct tm * tm); + int (*synchronize_to_clock_tick) (void); +}; + +extern struct clock_ops *probe_for_cmos_clock(void); +extern struct clock_ops *probe_for_rtc_clock(void); +extern struct clock_ops *probe_for_kd_clock(void); + +typedef int bool; + +/* hwclock.c */ +extern char *progname; +extern int debug; +extern unsigned long epoch_option; +extern double time_diff(struct timeval subtrahend, struct timeval subtractor); +/* cmos.c */ +extern void set_cmos_epoch(int ARCconsole, int SRM); +extern void set_cmos_access(int Jensen, int funky_toy); + +/* rtc.c */ +extern int get_epoch_rtc(unsigned long *epoch, int silent); +extern int set_epoch_rtc(unsigned long epoch); +extern char *rtc_dev_name; + +#ifdef HAVE_LIBAUDIT +extern void hwaudit_exit(int status); +# define hwclock_exit(_status) hwaudit_exit(_status) +#else +# define hwclock_exit(_status) exit(_status) +#endif + +#endif /* HWCLOCK_CLOCK_H */ |