diff options
author | Karel Zak | 2006-12-07 00:25:39 +0100 |
---|---|---|
committer | Karel Zak | 2006-12-07 00:25:39 +0100 |
commit | 7eda085c41faa3445b4b168ce78ab18dab87d98a (patch) | |
tree | eb8da4baebd0af68fa84818d3d51b4a3714667fc /clock | |
parent | Imported from util-linux-2.9i tarball. (diff) | |
download | kernel-qcow2-util-linux-7eda085c41faa3445b4b168ce78ab18dab87d98a.tar.gz kernel-qcow2-util-linux-7eda085c41faa3445b4b168ce78ab18dab87d98a.tar.xz kernel-qcow2-util-linux-7eda085c41faa3445b4b168ce78ab18dab87d98a.zip |
Imported from util-linux-2.9v tarball.
Diffstat (limited to 'clock')
-rw-r--r-- | clock/Makefile | 29 | ||||
-rw-r--r-- | clock/README.aeb | 9 | ||||
-rw-r--r-- | clock/README.hwclock | 46 | ||||
-rw-r--r-- | clock/README.shhopt-1.1 | 155 | ||||
-rw-r--r-- | clock/adjtime.patch | 302 | ||||
-rw-r--r-- | clock/clock.h | 33 | ||||
-rw-r--r-- | clock/cmos.c | 598 | ||||
-rw-r--r-- | clock/hwclock.8 | 599 | ||||
-rw-r--r-- | clock/hwclock.c | 1290 | ||||
-rw-r--r-- | clock/kd.c | 149 | ||||
-rw-r--r-- | clock/rtc.c | 389 | ||||
-rw-r--r-- | clock/shhopt-1.1.lsm | 17 | ||||
-rw-r--r-- | clock/shhopt.c | 468 | ||||
-rw-r--r-- | clock/shhopt.h | 33 |
14 files changed, 4117 insertions, 0 deletions
diff --git a/clock/Makefile b/clock/Makefile new file mode 100644 index 000000000..fd0522b38 --- /dev/null +++ b/clock/Makefile @@ -0,0 +1,29 @@ +# Makefile -- Makefile for util-linux Linux utilities +# +include ../make_include +include ../MCONFIG + +# Where to put man pages? + +MAN8= hwclock.8 + +# Where to put binaries? +# See the "install" rule for the links. . . + +SBIN= hwclock + + +all: $(SBIN) + + +hwclock.o: hwclock.c shhopt.h +hwclock.o cmos.o rtc.o kd.o: clock.h +hwclock: hwclock.o shhopt.o cmos.o rtc.o kd.o + +install: all + $(INSTALLDIR) $(SBINDIR) $(BINDIR) $(USRBINDIR) + $(INSTALLBIN) $(SBIN) $(SBINDIR) + $(INSTALLMAN) $(MAN8) $(MAN8DIR) + +clean: + -rm -f *.o *~ core $(SBIN) diff --git a/clock/README.aeb b/clock/README.aeb new file mode 100644 index 000000000..3955f9050 --- /dev/null +++ b/clock/README.aeb @@ -0,0 +1,9 @@ +This directory contains the hwclock stuff as fixed by me. +It should work on all architectures. + +Bryan has backported my changes to his original source, +so the present directory should be superfluous - +however, his code fails on my Sparc. +Will look at it later. + +Andries Brouwer - aeb@cwi.nl diff --git a/clock/README.hwclock b/clock/README.hwclock new file mode 100644 index 000000000..7d2f460dd --- /dev/null +++ b/clock/README.hwclock @@ -0,0 +1,46 @@ +Hwclock is a program that runs under Linux and sets and queries the +Hardware Clock, which is often called the Real Time Clock, RTC, or +CMOS clock. + +Hwclock is shipped with an ELF executable built for ISA (Intel) +machines. So there is nothing to build for those machines. Just +install the executable file "hwclock" and the man page file +"hwclock.8" in suitable directories (like /sbin/hwclock and +/usr/man/man8/hwclock.8) and you're ready to go. + +hwclock accesses platform-dependent hardware, so if you have something +other than an ISA machine, the shipped executable probably doesn't work, +and you have to compile hwclock yourself. + +Sometimes, you need to install 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 no need for +users to use the direct ISA I/O method, so don't bother. + +To install setuid root, do something like this: + + chmod a=rx,u=s /sbin/hwclock + +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). + +You may want to preformat and/or compress the man page before installing. + +If you want to build hwclock, just cd to the source directory and invoke +make with no parameters. + +hwclock calls option processing routines in the libsshopt library, +which is part of Sverre H. Huseby's "shhopt" package. An ELF +executable of this library is included in the package, but you can use +a copy that is already on your system by altering the make file. You +can find a more authoritative copy of this library, and its source +code, on sunsite (ftp://sunsite.unc.edu/pub/Linux/libs/shhopt-X.Y). + +As shipped, the routines are linked in statically, so you only need the +libsshopt.a file to build hwclock, not to run it. + + + + diff --git a/clock/README.shhopt-1.1 b/clock/README.shhopt-1.1 new file mode 100644 index 000000000..766d6cbdc --- /dev/null +++ b/clock/README.shhopt-1.1 @@ -0,0 +1,155 @@ +shhopt - library for parsing command line options. +================================================== + +This is a set of functions for parsing command line options. Both +traditional one-character options, and GNU-style --long-options are +supported. + + +What separates this from traditional getopt? +-------------------------------------------- + +This library does more of the parsing for you. You set up a special +structure describing the names and types of the options you want your +program to support. In the structure you also give addresses of +variables to update or functions to call for the various +options. By calling optParseOptions, all options in argv are parsed +and removed from argv. What is left, are the non-optional arguments to +your program. + +The down-side of this, is that you won't be able to make a program +where the position of the options between the non-options are +significant. + + +Usage +----- + +To see how to use this library, take a look at the sample program +example.c. + +A brief explanation: + +To parse your command line, you need to create and initialize an array +of optStruct's. Each element in the array describes a long and short +version of an option and specifies what type of option it is and how +to handle it. + +The structure fields (see also shhopt.h): + + `shortName' is the short option name without the leading '-'. + + `longName' is the long option name without the leading "--". + + `type' specifies what type of option this is. (Does it expect an + argument? Is it a flag? If it takes an argument,what type should + it be?) + + `arg' is either a function to be called with the argument from + the commandline, or a pointer to a location in which to store + the value. + + `flags' indicates whether `arg' points to a function or a storage + location. + +The different argument types: + + `OPT_END' flags this as the last element in the options array. + + `OPT_FLAG' indicates an option that takes no arguments. If `arg' is + not a function pointer, the value of `arg' will be set to 1 if + this flag is found on the command line. + + `OPT_STRING' expects a string argument. + + `OPT_INT' expects an int argument. + + `OPT_UINT' expects an unsigned int argument. + + `OPT_LONG' expects a long argument. + + `OPT_ULONG' expects an unsigned long argument. + +The different flag types: + + `OPT_CALLFUNC' indicates that `arg' is a function pointer. If this + is not given, `arg' is taken as a pointer to a variable. + + +Notes +----- + +* A dash (`-') by itself is not taken as any kind of an option, as + several programs use this to indicate the special files stdin and + stdout. It is thus left as a normal argument to the program. + +* Two dashes (`--') as an argument, is taken to mean that the rest of + the arguments should not be scanned for options. This simplifies + giving names of files that start with a dash. + +* Short (one-character) options accept parameters in two ways, either + directly following the option in the same argv-entry, or in the next + argv-entry: + + -sPARAMETER + -s PARAMETER + +* Long options accept parameters in two ways: + + --long-option=PARAMETER + --long-option PARAMETER + + To follow the GNU-tradition, your program documentation should use + the first form. + +* Several one-character options may be combined after a single + dash. If any of the options requires a parameter, the rest of the + string is taken as this parameter. If there is no "rest of the + string", the next argument is taken as the parameter. + +* There is no support for floating point (double) arguments to + options. This is to avoid unnecessary linking with the math + library. See example.c for how to get around this by writing a + function converting a string argument to a double. + + +Portability +----------- + +If your libc lacks strtoul, you will need to link with GNU's -liberty, +that may be found by anonymous ftp to prep.ai.mit.edu:/pub/gnu + +The library has (more or less recently) been compiled and `tested' on +the following systems: + + IRIX Release 5.3 IP22 + Linux 1.2.9 + SunOS Release 4.1.3_U1 (-liberty needed) + ULTRIX V4.4 (Rev. 69) + +All compilations were done using GNU's gcc, and GNU's make. + + +Author +------ + +The program is written by + + Sverre H. Huseby + Maridalsvn. 122, leil. 101 + N-0461 Oslo + Norway + + sverrehu@ifi.uio.no + http://www.ifi.uio.no/~sverrehu/ + +You can use and copy this for free. If you decide to use it, please do +me three small favours: + + 1. Tell me! (E-mail, postcard, letter, whatever. If you wish + to give me something, please send a bottle of your + favourite beer (making this BeerWare)) + 2. Let your friends and favourite download site have a copy! + (with all files intact, please..) + 3. Report any bugs you find! + diff --git a/clock/adjtime.patch b/clock/adjtime.patch new file mode 100644 index 000000000..81d0430fd --- /dev/null +++ b/clock/adjtime.patch @@ -0,0 +1,302 @@ +From ao112@rgfn.epcc.edu Fri Mar 19 06:27:26 1999 +Received: from rgfn.epcc.edu (rgfn.epcc.edu [208.136.234.19]) by hera.cwi.nl with ESMTP + id GAA27711 for <Andries.Brouwer@cwi.nl>; Fri, 19 Mar 1999 06:27:23 +0100 (MET) +Received: (from ao112@localhost) + by rgfn.epcc.edu (8.8.8/8.8.8) id WAA16797; + Thu, 18 Mar 1999 22:27:19 -0700 (MST) +Date: Thu, 18 Mar 1999 22:27:19 -0700 (MST) +Message-Id: <199903190527.WAA16797@rgfn.epcc.edu> +From: ao112@rgfn.epcc.edu (James P. Rutledge) +To: Andries.Brouwer@cwi.nl +Subject: Re: hwclock patch for drift_factor calculation improvement +Reply-To: ao112@rgfn.epcc.edu +Status: R + + + +> +>Could you perhaps make your patch relative to +>util-linux-2.9n (found in ftp.cwi.nl/pub/aeb/util-linux/util-linux-2.9n.tar.gz) +>? +> +>(The hwclock stuff has changed quite a bit since 2.9g.) +> +>Andries +> + +Andries; + +Per your request, the patch has been modified for util-linux version +2.9n, from the version for 2.9g. + +The program "hwclock" (version 2.4c) could give more accurate +values for the drift factor that it places in the file "/etc/adjtime". + +A patch to improve the accuracy is included. + +I have incorporated some error sources which were not compensated +for into the drift factor calculation (performed when the "--set" +or the "--systohc" option is used) to make it more accurate. +In particular, the sync delay between the desired set time and the +start of the hardware clock second, and the expected drift since the +last hardware clock adjustment are now accounted for in the drift +factor calculation. + +With this patch, if at any time an adjust operation is attempted and +the hardware clock is found to be not valid, then the calibration +and adjustment time is set to zero to insure that if the hardware +clock should coincidentally return to validity, a calibration is not +done with bad history data (hardware clock info bad) and an adjust is +not attempted on bad (but now passing validity test) hardware clock +data. (With this patch, a previous calibration time of zero causes +the calibration time to initialize with the current time, when the +hardware clock is set, but no change is made to the drift factor, +so in effect, an initial calibration is started over while the previous +drift factor is retained.) + +Also, the behavior in the case of an initially missing "/etc/adjtime" +file or such a file produced by the predecessor "clock" program has +been slightly improved as follows: + + With this patch, if the file exists but was produced by "clock" + and, thus, is given a zero calibration time, the drift factor is + not updated upon the first calibration by "hwclock", but is left alone + and is only changed by subsequent calibrations. + + With this patch, if the file does not exist and, thus, is given + a zero calibration time, the drift factor is set to zero upon the + first calibration by "hwclock" and is then changed, as appropriate, by + subsequent calibrations. + + Also, with this patch, an "--adjust" operation against a non-existent + "/etc/adjtime" file or one which has zero as the last adjustment + time will not change the hardware clock setting. + +A context diff for a patch to the file "hwclock.c" in the directory +"util-linux-2.9n/clock" is appended. +To use the patch, "cd" to the directory "util-linux-2.9n/clock". +Run "patch < bug-report", where "bug-report" is the file name of +this mail message, to get new file "hwclock.c" which contains the proposed +new version. This patch is, of course, submitted per the GPL and the +appropriate "NO WARRANTY OF ANY KIND" and "USE AT YOUR OWN RISK" +disclaimers apply. + +Note that the patch presumptuously changes the "hwclock.c" version +number from 2.4c to 2.4c1 in "hwclock.c". + +Jim + +------------------ Patch file follows ---------------------------- +*** hwclock.c Thu Mar 18 22:04:01 1999 +--- new-hwclock.c Thu Mar 18 22:03:18 1999 +*************** +*** 76,86 **** + + #include "clock.h" + #include "../version.h" + + #define MYNAME "hwclock" +! #define VERSION "2.4c" + + char *progname = MYNAME; + + /* The struct that holds our hardware access routines */ + struct clock_ops *ur; +--- 76,86 ---- + + #include "clock.h" + #include "../version.h" + + #define MYNAME "hwclock" +! #define VERSION "2.4c1" + + char *progname = MYNAME; + + /* The struct that holds our hardware access routines */ + struct clock_ops *ur; +*************** +*** 581,601 **** + + + static void + adjust_drift_factor(struct adjtime *adjtime_p, + const time_t nowtime, +! const bool hclock_valid, const time_t hclocktime ) { + /*--------------------------------------------------------------------------- + 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 assume that the user has been doing regular drift adjustments +- using the drift factor in the adjtime file, so if <nowtime> and +- <clocktime> are different, that means the adjustment factor isn't +- quite right. +- + 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 +--- 581,598 ---- + + + static void + adjust_drift_factor(struct adjtime *adjtime_p, + const time_t nowtime, +! const bool hclock_valid, +! const time_t hclocktime, +! const float sync_delay ) { + /*--------------------------------------------------------------------------- + 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 +*************** +*** 604,629 **** + ----------------------------------------------------------------------------*/ + if (!hclock_valid) { + if (debug) + printf("Not adjusting drift factor because the Hardware Clock " + "previously contained garbage.\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 { +! const float factor_adjust = +! ((float) (nowtime - hclocktime) +! / (hclocktime - adjtime_p->last_calib_time)) +! * 24 * 60 * 60; + + if (debug) +! printf("Clock drifted %d 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", +! (int) (nowtime - hclocktime), +! (int) (hclocktime - adjtime_p->last_calib_time), + adjtime_p->drift_factor, + factor_adjust ); + + adjtime_p->drift_factor += factor_adjust; + } +--- 601,642 ---- + ----------------------------------------------------------------------------*/ + 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,\nso 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 { +! const float sec_per_day = 24.0 * 60.0 * 60.0; +! float atime_per_htime; /* adjusted time units per hardware time unit */ +! float adj_days; /* days since last adjustment (in hardware clock time) */ +! float cal_days; /* days since last calibration (in hardware clock time) */ +! float exp_drift; /* expected drift (sec) since last adjustment */ +! float unc_drift; /* uncorrected drift (sec) since last calibration */ +! float factor_adjust; /* amount to add to previous drift factor */ +! atime_per_htime = 1.0 + adjtime_p->drift_factor / sec_per_day; +! adj_days = (float)(hclocktime - adjtime_p->last_adj_time) / sec_per_day; +! exp_drift = adj_days * adjtime_p->drift_factor + adjtime_p->not_adjusted; +! unc_drift = (float)(nowtime - hclocktime) + sync_delay - exp_drift; +! cal_days = ((float)(adjtime_p->last_adj_time - adjtime_p->last_calib_time) +! + adjtime_p->not_adjusted) / (sec_per_day * atime_per_htime) +! + adj_days; +! 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; + } +*************** +*** 764,773 **** +--- 777,794 ---- + + ----------------------------------------------------------------------------*/ + if (!hclock_valid) { + fprintf(stderr, "The Hardware Clock does not contain a valid time, " + "so we cannot adjust it.\n"); ++ 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 */ + float retro; + /* Fraction of second we have to remove from clock after inserting +*************** +*** 878,888 **** + time_diff(read_time, startup_time)); + *retcode_p = 0; + } else if (set) { + set_hardware_clock_exact(set_time, startup_time, + universal, testing); +! adjust_drift_factor(&adjtime, set_time, hclock_valid, hclocktime); + *retcode_p = 0; + } else if (adjust) { + do_adjustment(&adjtime, hclock_valid, hclocktime, + read_time, universal, testing); + *retcode_p = 0; +--- 899,910 ---- + time_diff(read_time, startup_time)); + *retcode_p = 0; + } else if (set) { + set_hardware_clock_exact(set_time, startup_time, + universal, testing); +! adjust_drift_factor(&adjtime, set_time, hclock_valid, hclocktime, +! time_diff(read_time, startup_time)); + *retcode_p = 0; + } else if (adjust) { + do_adjustment(&adjtime, hclock_valid, hclocktime, + read_time, universal, testing); + *retcode_p = 0; +*************** +*** 898,908 **** + + set_hardware_clock_exact((time_t) reftime.tv_sec, reftime, + universal, testing); + *retcode_p = 0; + adjust_drift_factor(&adjtime, (time_t) reftime.tv_sec, hclock_valid, +! hclocktime); + } else if (hctosys) { + rc = set_system_clock(hclock_valid, hclocktime, testing); + if (rc != 0) { + printf("Unable to set system clock.\n"); + *retcode_p = 1; +--- 920,930 ---- + + set_hardware_clock_exact((time_t) reftime.tv_sec, reftime, + universal, testing); + *retcode_p = 0; + adjust_drift_factor(&adjtime, (time_t) reftime.tv_sec, hclock_valid, +! hclocktime, (float)(read_time.tv_usec / 1E6)); + } else if (hctosys) { + rc = set_system_clock(hclock_valid, hclocktime, testing); + if (rc != 0) { + printf("Unable to set system clock.\n"); + *retcode_p = 1; + diff --git a/clock/clock.h b/clock/clock.h new file mode 100644 index 000000000..b57b499c0 --- /dev/null +++ b/clock/clock.h @@ -0,0 +1,33 @@ +#include <stdio.h> +#include <string.h> +#include <errno.h> /* for errno, EPERM, EINVAL, ENOENT */ +#include <time.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; +#define TRUE 1 +#define FALSE 0 + +/* hwclock.c */ +extern char *progname; +extern int debug; +extern void outsyserr(char *msg); + +/* 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); diff --git a/clock/cmos.c b/clock/cmos.c new file mode 100644 index 000000000..78d5dbb94 --- /dev/null +++ b/clock/cmos.c @@ -0,0 +1,598 @@ +/* + * 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 <unistd.h> /* for geteuid() */ +#include <fcntl.h> /* for O_RDWR */ + +#include "nls.h" + +#if defined(__i386__) || defined(__alpha__) +#include <asm/io.h> /* for inb, outb */ +#else +void outb(int a, int b){} +int inb(int c){ return 0; } +#endif + +#include "clock.h" + +#define BCD_TO_BIN(val) ((val)=((val)&15) + ((val)>>4)*10) +#define BIN_TO_BCD(val) ((val)=(((val)/10)<<4) + (val)%10) + +#define TM_EPOCH 1900 +int cmos_epoch = 1900; /* 1980 for an alpha in ARC console time */ + /* One also sees 1952 (Digital Unix?) + and 1958 (ALPHA_PRE_V1_2_SRM_CONSOLE) */ + +/* Martin Ostermann writes: +The problem with the Jensen is twofold: First, it has the clock at a +different address. Secondly, it has a distinction beween "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 (ARCconsole) + cmos_epoch = 1980; + + if (ARCconsole || SRM) + return; + + + /* 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; + } + + + /* 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 UX, 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. */ + if (funky_toy || + is_in_cpuinfo("system variation", "PC164") || + is_in_cpuinfo("system variation", "LX164") || + is_in_cpuinfo("system variation", "SX164")) { + funkyTOY = 1; + if (debug) printf (_("funky TOY!\n")); + } +} +#endif + + + + +#ifdef __i386__ + +/* + * Try to do CMOS access atomically, so that no other processes + * can get a time slice while we are reading or setting the clock. + * (Also, if the kernel time is synchronized with an external source, + * the kernel itself will fiddle with the RTC every 11 minutes.) + */ + +static unsigned long +atomic(const char *name, unsigned long (*op)(unsigned long), + unsigned long arg) +{ + unsigned long v; + __asm__ volatile ("cli"); + v = (*op)(arg); + __asm__ volatile ("sti"); + return v; +} + +#elif __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; + } + } + fprintf(stderr, _("%s: atomic %s failed for 1000 iterations!"), progname, name); + exit(1); +} + +#else + +/* + * Hmmh, this isn't very atomic. Maybe we should force an error + * instead? + */ +static unsigned long +atomic(const char *name, 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); + write(dev_port_fd, &v, 1); + lseek(dev_port_fd, clock_data_addr, 0); + read(dev_port_fd, &v, 1); + 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 doesnt 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); + write(dev_port_fd, &v, 1); + v = (val & 0xff); + lseek(dev_port_fd, clock_data_addr, 0); + write(dev_port_fd, &v, 1); + } else { + outb (reg, clock_ctl_addr); + outb (val, clock_data_addr); + } + return 0; +} + +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() { + 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; +} + + + +static int +read_hardware_clock_cmos(struct tm *tm) { +/*---------------------------------------------------------------------------- + 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. + +-----------------------------------------------------------------------------*/ + 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; +} + +static int +i386_iopl(const int level) { +#if defined(__i386__) || defined(__alpha__) + extern int iopl(const int level); + return iopl(level); +#else + 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) { + int errsv = errno; + fprintf(stderr, _("Cannot open /dev/port: %s"), strerror(errsv)); + rc = 1; + } else + rc = 0; + } else { + rc = i386_iopl(3); + if (rc == -2) { + fprintf(stderr, _("I failed to get permission because I didnt try.\n")); + } else if (rc != 0) { + rc = errno; + fprintf(stderr, _("%s is unable to get I/O port access: " + "the iopl(3) call failed.\n"), progname); + if(rc == EPERM && geteuid()) + fprintf(stderr, _("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/clock/hwclock.8 b/clock/hwclock.8 new file mode 100644 index 000000000..0a216840a --- /dev/null +++ b/clock/hwclock.8 @@ -0,0 +1,599 @@ +.TH CLOCK 8 "02 March 1998" +.SH NAME +clock \- query and set the hardware clock (RTC) +.SH SYNOPSIS +.B "hwclock --show" +.br +.B "hwclock --set --date=newdate" +.br +.B "hwclock --systohc" +.br +.B "hwclock --hctosys" +.br +.B "hwclock --getepoch" +.br +.B "hwclock --setepoch --epoch=year" +.br +.B "hwclock --adjust" +.br +.B "hwclock --version" +.PP +other options: +.PP +.B "--utc --localtime --directisa --test --debug" +.PP +and arcane options for DEC Alpha: +.PP +.B "--arc --jensen --srm --funky-toy" +.PP +Minimum unique abbreviations of all options are acceptable. +.PP +Also, equivalent options -r, -w, -s, -a, -v, -u, -D, -A, -J, -S, and -F +are accepted for compatibility with the program "clock". + +.SH DESCRIPTION +.I 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 to the System Time, and set the System Time from the +Hardware Clock. +.PP +You can also run +.I hwclock +periodically to insert or remove time from the Hardware Clock to +compensate for systematic drift (where the clock consistently gains or +loses time at a certain rate if left to run). + +.SH OPTIONS +You need exactly one of the following options to tell +.I hwclock +what function to perform: +.PP +.TP +.B \-\-show +Read the Hardware Clock and print the time on Standard Output. +The time is always in local time, even if you keep your Hardware Clock +in Coordinated Universal Time. See the +.B \-\-utc +option. + +.TP +.B \-\-set +Set the Hardware Clock to the time given by the +.B \-\-date +option. +.TP +.B \-\-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/lib/zoneinfo , +as +.BR tzset (3) +would interpret them. EXCEPT: always set the Daylight Savings Time part of +the kernel's timezone value to 0 ("not Daylight Savings Time"). If DST +is indicated, just add an hour to the base part. + +See the discussion of timezones below. + +This is a good option to use in one of the system startup scripts. +.TP +.B \-\-systohc +Set the Hardware Clock to the current System 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 out standard output the kernel's Hardware Clock epoch value. +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 Counter epoch value +must be 1952. + +This epoch value is used whenever 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 +.B \-\-version +Print the version of +.I hwclock +on Standard Output. +.br +You need the following option if you specify +.B \-\-set +option. Otherwise, it is ignored. +.TP +.B \-\-date=date_string +Specifies the time to which to set the Hardware Clock. The value of this +option is an argument to the +.I date(1) +program. For example, +.sp +.I hwclock --set --date="9/22/96 16:45:05" +.sp +The argument is in local time, even if you keep your Hardware Clock in +Coordinated Universal time. See the +.I \-\-utc +option. + +.TP +.B \-\-epoch=year +Specifies the year which is the beginning of the Hardware Clock's +epoch. I.e. the number of years into AD to which a zero value in the +Hardware Clock's year counter refers. + +For example, +.sp +.I hwclock --setepoch --epoch=1952 + +.PP +The following options apply to most functions. +.TP +.B \-\-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 +.IR 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 +.B \-\-localtime +, the default is whichever was specified the last time +.I hwclock +was used to set the clock (i.e. hwclock was successfully run with the +.B \-\-set +, +.B \-\-systohc +, +or +.B \-\-adjust +options), as recorded in the adjtime file. If the adjtime file doesn't +exist, the default is local time. + +.TP +.B \-\-directisa +is meaningful only on an ISA machine or an Alpha (which implements enough +of ISA to be, roughly speaking, an ISA machine for +.IR hwclock 's +purposes). For other machines, it has no effect. This option tells +.I hwclock +to use explicit I/O instructions to access the Hardware Clock. +Without this option, +.I 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 read), it will +use the explicit I/O instructions anyway. + +The rtc device driver was new in Linux Release 2. +.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, +.I 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 +.I 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 +.I hwclock \-\-set +or +.I hwclock \-\-systohc +at least once a year! + +Though +.I 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. + +.I hwclock +warns you that you probably need +.B \-\-badyear +whenever it finds your Hardware Clock set to 1994 or 1995. + +.TP +.B \-\-srm +.TP +.B \-\-arc +.TP +.B \-\-jensen +.TP +.B \-\-funky\-toy +These options all tell +.I hwclock +what kind of Alpha machine you have. They +are invalid if you don't have an Alpha and shouldn't be necessary if you +do, because +.I hwclock +should be able to determine by itself what it's +running on. These options make it possible for +.I hwclock +to work even when +its environment does not conform to its expectations and thus it cannot +accurately determine what sort of system it is running on. If you think +hwclock is incorrectly determining the system's characteristics, try +running with the +.B \-\-debug +option to see what conclusions the program is +reaching and how. If you find you need one of these options to make +.I hwclock +work, contact the +.I hwclock +maintainer to see if the program can be improved to detect your system +automatically. + +.B \-\-jensen +means you are running on a Jensen model. + +.B \-\-arc +means your machine is running with ARC console time. + +.B \-\-srm +means your machine is running with SRM console time. + +.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 +.B \-\-debug, +in learning about +.I hwclock. +.TP +.B \-\-debug +Display a lot of information about what +.I 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 +.I hwclock +because all of the other names are inappropriate to the point of being +misleading. +.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 /usr/local/timezone +directory, as explained in the man page for 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 +.I hwclock +sets the kernel timezone to the value indicated by TZ and/or +/usr/local/timezone when you set the System Time using the +.B \-\-hctosys +option. +.PP +A complication is that the timezone value actually consists of two +parts: 1) how far from the Standard Meridian the locality is +geographically, and 2) whether or not a Daylight Savings Time (DST) +convention is in effect in the locality at the present time. In +practice, the DST part of the timezone value is almost never used, so +if the geographical part were to be set to its correct value, the +users of the timezone value would actually compute the wrong local +time. +.PP +Therefore, +.I hwclock +violates the definition of the kernel's timezone value and always sets +the DST part to zero. If DST is supposed to be in effect, +.I hwclock +simply adds an hour to the geographical part. + +.SH How hwclock Accesses the Hardware Clock +.PP +.I 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). +.PP +On older systems, the method of accessing the Hardware Clock depends on +the system hardware. +.PP +On an ISA system, +.I 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 +.I 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, +.I hwclock +can access the clock via the console driver, via the device special +file /dev/tty1. +.PP +.I 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, +.I hwclock +will fall back to another method, if available. On an ISA or Alpha +machine, you can force +.I hwclock +to use the direct manipulation of the CMOS registers without even trying +.I /dev/rtc +by specifying the \-\-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. +.IR hwclock 's +"adjust" function lets you make systematic corrections to correct the +systematic drift. +.PP +It works like this: +.I hwclock +keeps a file, +.I /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. +.I 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. +.I 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. +.I 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 +.I hwclock \-\-adjust. +.I 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 +.I \-\-systohc +), +.I 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 +.I 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 +.I 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 +.IR clock ) +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 +.I hwclock +command line. +.PP +You can use an adjtime file that was previously used with the +.I clock +program with +.I 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. +.I 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 +.I 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. + + + +.SH "ENVIRONMENT VARIABLES" +.I TZ + +.SH FILES +.I /etc/adjtime +.I /usr/lib/zoneinfo/ +.I /dev/rtc +.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. + + diff --git a/clock/hwclock.c b/clock/hwclock.c new file mode 100644 index 000000000..a496e0570 --- /dev/null +++ b/clock/hwclock.c @@ -0,0 +1,1290 @@ +/* + * 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 + */ + +/* + * clock [-u] -r - read hardware clock + * clock [-u] -w - write hardware clock from system time + * clock [-u] -s - set system time from hardware clock + * clock [-u] -a - set system time from hardware clock, adjust the time + * to correct for systematic error, and write it back to + * the hardware clock + * -u indicates cmos clock is kept in universal time + * -A indicates cmos clock is kept in Alpha ARC console time (0 == 1980) + * -J indicates we're dealing with a Jensen (early DEC Alpha PC) + */ + +/* + * 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 <string.h> +#include <stdio.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#include <errno.h> +#include <stdlib.h> +#include <unistd.h> +#include <time.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <shhopt.h> + +#include "clock.h" +#include "../version.h" +#include "nls.h" + +#define MYNAME "hwclock" +#define VERSION "2.4c" + +char *progname = MYNAME; + +/* The struct that holds our hardware access routines */ +struct clock_ops *ur; + +#define FLOOR(arg) ((arg >= 0 ? (int) arg : ((int) arg) - 1)); + +/* Here the information for time adjustments is kept. */ +#define ADJPATH "/etc/adjtime" + +/* Store the date here when "badyear" flag is set. */ +#define LASTDATE "/var/lib/lastdate" + +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; + float drift_factor; + time_t last_adj_time; + float not_adjusted; + time_t last_calib_time; +}; + +bool debug; + /* We are running in debug mode, wherein we put a lot of information about + what we're doing to standard output. */ + +bool badyear; + /* Workaround for Award 4.50g BIOS bug: keep the year in a file. */ + +/* + * 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 + */ +void +write_date_to_file (struct tm *tm) { + FILE *fp; + + if ((fp = fopen(LASTDATE,"w"))) { + fprintf(fp,"%02d.%02d.%04d\n", tm->tm_mday, tm->tm_mon+1, + tm->tm_year+1900); + fclose(fp); + } else + perror(LASTDATE); +} + +void +read_date_from_file (struct tm *tm) { + int last_mday, last_mon, last_year; + FILE *fp; + + if ((fp = fopen(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); +} + +static float +time_diff(struct timeval subtrahend, struct timeval subtractor) { +/*--------------------------------------------------------------------------- + The difference in seconds between two times in "timeval" format. +----------------------------------------------------------------------------*/ + return( (subtrahend.tv_sec - subtractor.tv_sec) + + (subtrahend.tv_usec - subtractor.tv_usec) / 1E6 ); +} + + +static struct timeval +time_inc(struct timeval addend, float increment) { +/*---------------------------------------------------------------------------- + The time, in "timeval" format, which is <increment> seconds after + the time <addend>. Of course, <increment> may be negative. +-----------------------------------------------------------------------------*/ + 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 void +read_adjtime(struct adjtime *adjtime_p, int *rc_p) { +/*---------------------------------------------------------------------------- + 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 *rc_p = 0 if all OK, !=0 otherwise. + +-----------------------------------------------------------------------------*/ + FILE *adjfile; + int rc; /* local return code */ + struct stat statbuf; /* We don't even use the contents of this. */ + + rc = stat(ADJPATH, &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; + + *rc_p = 0; + } else { + adjfile = fopen(ADJPATH, "r"); /* open file for reading */ + if (adjfile == NULL) { + outsyserr("cannot open file " ADJPATH); + *rc_p = 2; + } else { + char line1[81]; /* String: first line of adjtime file */ + char line2[81]; /* String: second line of adjtime file */ + + line1[0] = '\0'; /* In case fgets fails */ + fgets(line1, sizeof(line1), adjfile); + line2[0] = '\0'; /* In case fgets fails */ + fgets(line2, sizeof(line2), adjfile); + + 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; + + sscanf(line1, "%f %d %f", + &adjtime_p->drift_factor, + (int *) &adjtime_p->last_adj_time, + &adjtime_p->not_adjusted); + + sscanf(line2, "%d", (int *) &adjtime_p->last_calib_time); + + *rc_p = 0; + } + adjtime_p->dirty = FALSE; + + if (debug) { + printf(_("Last drift adjustment done at %d seconds after 1969\n"), + (int) adjtime_p->last_adj_time); + printf(_("Last calibration done at %d seconds after 1969\n"), + (int) adjtime_p->last_calib_time); + } + } +} + + +static void +synchronize_to_clock_tick(int *retcode_p) { +/*----------------------------------------------------------------------------- + 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 *retcode_p == 0 if it worked, nonzero if it didn't. + +-----------------------------------------------------------------------------*/ + if (debug) printf(_("Waiting for clock tick...\n")); + + *retcode_p = ur->synchronize_to_clock_tick(); + + if (debug) printf(_("...got clock tick\n")); +} + + + +static void +mktime_tz(struct tm tm, const bool universal, + bool *valid_p, time_t *systime_p) { +/*----------------------------------------------------------------------------- + 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. + +-----------------------------------------------------------------------------*/ + 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 = (char *) getenv("TZ"); /* remember original time zone */ + mktime_result = mktime(&tm); + 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: " + "%2d/%.2d/%.2d %.2d:%.2d:%.2d\n"), + tm.tm_year, 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 : %.2d:%.2d:%.2d = %d seconds since 1969\n"), + tm.tm_hour, tm.tm_min, tm.tm_sec, (int) *systime_p); + } + /* now put back the original zone. */ + if (zone) setenv("TZ", zone, TRUE); + else unsetenv("TZ"); + tzset(); +} + + +static void +read_hardware_clock(const bool universal, bool *valid_p, time_t *systime_p){ +/*---------------------------------------------------------------------------- + Read the hardware clock and return the current time via <tm> argument. + + Use the method indicated by <method> argument to access the hardware clock. +-----------------------------------------------------------------------------*/ + struct tm tm; + int err; + + err = ur->read_hardware_clock(&tm); + + if (badyear) + read_date_from_file(&tm); + + if (debug) + printf (_("Time read from Hardware Clock: %02d:%02d:%02d\n"), + tm.tm_hour, tm.tm_min, tm.tm_sec); + mktime_tz(tm, universal, valid_p, systime_p); +} + + +static void +set_hardware_clock(const time_t newtime, + const bool universal, + const bool testing) { +/*---------------------------------------------------------------------------- + Set the Hardware Clock to the time <newtime>, in local time zone or UTC, + according to <universal>. +----------------------------------------------------------------------------*/ + int err; + 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 " + "= %d seconds since 1969\n"), + new_broken_time.tm_hour, new_broken_time.tm_min, + new_broken_time.tm_sec, (int) 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); + } + err = ur->set_hardware_clock(&new_broken_time); + } +} + + + +static void +set_hardware_clock_exact(const time_t settime, + const struct timeval ref_time, + const bool universal, + const bool testing) { +/*---------------------------------------------------------------------------- + Set the Hardware Clock to the time "settime", in local time zone or UTC, + according to "universal". + + But correct "settime" and wait for a fraction of a second so that + "settime" is the value of the Hardware Clock as of system time + "ref_time", which is in the past. For example, if "settime" is + 14:03:05 and "ref_time" is 12:10:04.5 and the current system + time is 12:10:06.0: Wait .5 seconds (to make exactly 2 seconds since + "ref_time") 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. + +-----------------------------------------------------------------------------*/ + time_t newtime; /* Time to which we will set Hardware Clock */ + struct timeval now_time; /* locally used time */ + + gettimeofday(&now_time, NULL); + newtime = settime + (int) time_diff(now_time, ref_time) + 1; + if (debug) + printf(_("Time elapsed since reference time has been %.6f seconds.\n" + "Delaying further to reach the next full second.\n"), + time_diff(now_time, ref_time)); + + /* Now delay some more until Hardware Clock time newtime arrives */ + do gettimeofday(&now_time, NULL); + while (time_diff(now_time, ref_time) < newtime - settime); + + set_hardware_clock(newtime, universal, testing); +} + + + +static void +display_time(const bool hclock_valid, const time_t systime, + const float sync_duration) { +/*---------------------------------------------------------------------------- + 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". +-----------------------------------------------------------------------------*/ + if (!hclock_valid) + fprintf(stderr, _("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).\n")); + else { + char *ctime_now; /* Address of static storage containing time string */ + + /* For some strange reason, ctime() is designed to include a newline + character at the end. We have to remove that. + */ + ctime_now = ctime(&systime); /* Compute display value for time */ + *(ctime_now+strlen(ctime_now)-1) = '\0'; /* Cut off trailing newline */ + + printf(_("%s %.6f seconds\n"), ctime_now, -(sync_duration)); + } +} + + + +static int +interpret_date_string(const char *date_opt, time_t * const time_p) { +/*---------------------------------------------------------------------------- + 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. +----------------------------------------------------------------------------*/ + 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) { + fprintf(stderr, _("No --date option specified.\n")); + retcode = 14; + } else if (strchr(date_opt, '"') != NULL) { + /* Quotation marks in date_opt would ruin the date command we construct. + */ + fprintf(stderr, _("The value of the --date option is not a valid date.\n" + "In particular, it contains quotation marks.\n")); + retcode = 12; + } else { + 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) { + outsyserr(_("Unable to run 'date' program in /bin/sh shell. " + "popen() failed")); + retcode = 10; + } else { + date_resp[0] = '\0'; /* in case fgets fails */ + fgets(date_resp, sizeof(date_resp), date_child_fp); + if (debug) printf(_("response from date command = %s\n"), date_resp); + if (strncmp(date_resp, magic, sizeof(magic)-1) != 0) { + fprintf(stderr, _("The date command issued by %s returned " + "unexpected results.\n" + "The command was:\n %s\nThe response was:\n %s\n"), + MYNAME, date_command, date_resp); + retcode = 8; + } else { + int seconds_since_epoch; + rc = sscanf(date_resp + sizeof(magic)-1, "%d", &seconds_since_epoch); + if (rc < 1) { + fprintf(stderr, _("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\nThe response was:\n %s\n"), + MYNAME, date_command, date_resp); + retcode = 6; + } else { + retcode = 0; + *time_p = seconds_since_epoch; + if (debug) + printf(_("date string %s equates to %d seconds since 1969.\n"), + date_opt, (int) *time_p); + } + } + fclose(date_child_fp); + } + } + return(retcode); +} + + + +static int +set_system_clock(const bool hclock_valid, const time_t newtime, + const bool testing) { +/*---------------------------------------------------------------------------- + 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: do not consider Daylight + Savings Time to be a separate component of the time zone. Include + any effect of DST in the basic timezone value and set the kernel + DST value to 0. + + 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. +-----------------------------------------------------------------------------*/ + int retcode; /* our eventual return code */ + + if (!hclock_valid) { + fprintf(stderr,_("The Hardware Clock does not contain a valid time, so " + "we cannot set the System Time from it.\n")); + retcode = 1; + } else { + struct timeval tv; + int rc; /* local return code */ + + tv.tv_sec = newtime; + tv.tv_usec = 0; + + tzset(); /* init timezone, daylight from TZ or ...zoneinfo/localtime */ + /* An undocumented function of tzset() is to set global variabales + 'timezone' and 'daylight' + */ + + 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 = %ld\n"), timezone/60 - 60*daylight); + } + if (testing) { + printf(_("Not setting system clock because running in test mode.\n")); + retcode = 0; + } else { + /* For documentation of settimeofday(), in addition to its man page, + see kernel/time.c in the Linux source code. + */ + const struct timezone tz = { timezone/60 - 60*daylight, 0 }; + /* put daylight in minuteswest rather than dsttime, + since the latter is mostly ignored ... */ + rc = settimeofday(&tv, &tz); + if (rc != 0) { + if (errno == EPERM) + fprintf(stderr, _("Must be superuser to set system clock.\n")); + else + outsyserr(_("settimeofday() failed")); + retcode = 1; + } else retcode = 0; + } + } + return(retcode); +} + + +static void +adjust_drift_factor(struct adjtime *adjtime_p, + const time_t nowtime, + const bool hclock_valid, const time_t hclocktime ) { +/*--------------------------------------------------------------------------- + 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 assume that the user has been doing regular drift adjustments + using the drift factor in the adjtime file, so if <nowtime> and + <clocktime> are different, that means the adjustment factor isn't + quite right. + + 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. + +----------------------------------------------------------------------------*/ + if (!hclock_valid) { + if (debug) + printf(_("Not adjusting drift factor because the Hardware Clock " + "previously contained garbage.\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 { + const float factor_adjust = + ((float) (nowtime - hclocktime) + / (hclocktime - adjtime_p->last_calib_time)) + * 24 * 60 * 60; + + if (debug) + printf(_("Clock drifted %d 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"), + (int) (nowtime - hclocktime), + (int) (hclocktime - 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; +} + + + +static void +calculate_adjustment( + const float factor, + const time_t last_time, + const float not_adjusted, + const time_t systime, + int *adjustment_p, + float *retro_p, + const int debug ) { +/*---------------------------------------------------------------------------- + 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. +----------------------------------------------------------------------------*/ + float exact_adjustment; + + exact_adjustment = ((float) (systime - last_time)) * factor / (24 * 60 * 60) + + not_adjusted; + *adjustment_p = FLOOR(exact_adjustment); + + *retro_p = exact_adjustment - (float) *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); + } +} + + + +static void +save_adjtime(const struct adjtime adjtime, const bool testing) { +/*----------------------------------------------------------------------------- + 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. +-----------------------------------------------------------------------------*/ + char newfile[405]; /* 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", + adjtime.drift_factor, + (long) adjtime.last_adj_time, + adjtime.not_adjusted, + (long) adjtime.last_calib_time ); + + if (testing) { + printf(_("Not updating adjtime file because of testing mode.\n")); + printf(_("Would have written the following to %s:\n%s"), + ADJPATH, newfile); + } else { + FILE *adjfile; + int err = 0; + + adjfile = fopen(ADJPATH, "w"); + if (adjfile == NULL) { + outsyserr("Could not open file with the clock adjustment parameters " + "in it (" ADJPATH ") for writing"); + err = 1; + } else { + if (fprintf(adjfile, newfile) < 0) { + outsyserr("Could not update file with the clock adjustment " + "parameters (" ADJPATH ") in it"); + err = 1; + } + if (fclose(adjfile) < 0) { + outsyserr("Could not update file with the clock adjustment " + "parameters (" ADJPATH ") in it"); + err = 1; + } + } + if (err) + fprintf(stderr, _("Drift adjustment parameters not updated.\n")); + } + } +} + + + +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) { +/*--------------------------------------------------------------------------- + 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. + +----------------------------------------------------------------------------*/ + if (!hclock_valid) { + fprintf(stderr, _("The Hardware Clock does not contain a valid time, " + "so we cannot adjust it.\n")); + } else { + int adjustment; + /* Number of seconds we must insert in the Hardware Clock */ + float 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, + debug ); + 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(); + + if (!ur) + ur = probe_for_rtc_clock(); + + 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")); + } +} + +static void +manipulate_clock(const bool show, const bool adjust, + const bool set, const time_t set_time, + const bool hctosys, const bool systohc, + const struct timeval startup_time, + const bool universal, const bool testing, + int *retcode_p + ) { +/*--------------------------------------------------------------------------- + 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. +----------------------------------------------------------------------------*/ + struct adjtime adjtime; + /* Contents of the adjtime file, or what they should be. */ + int rc; /* local return code */ + bool no_auth; /* User lacks necessary authorization to access the clock */ + + no_auth = ur->get_permissions(); + + if (no_auth) *retcode_p = 1; + else { + if (adjust || set || systohc) + read_adjtime(&adjtime, &rc); + else { + /* A little trick to avoid reading the file if we don't have to */ + adjtime.dirty = FALSE; + rc = 0; + } + if (rc != 0) *retcode_p = 2; + else { + synchronize_to_clock_tick(retcode_p); + /* this takes up to 1 second */ + if (*retcode_p == 0) { + struct timeval read_time; + /* The time at which we read the Hardware Clock */ + + bool hclock_valid; + /* The Hardware Clock gives us a valid time, or at least something + close enough to fool mktime(). + */ + + time_t hclocktime; + /* 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. + */ + + gettimeofday(&read_time, NULL); + read_hardware_clock(universal, &hclock_valid, &hclocktime); + + if (show) { + display_time(hclock_valid, hclocktime, + time_diff(read_time, startup_time)); + *retcode_p = 0; + } else if (set) { + set_hardware_clock_exact(set_time, startup_time, + universal, testing); + adjust_drift_factor(&adjtime, set_time, hclock_valid, hclocktime); + *retcode_p = 0; + } else if (adjust) { + do_adjustment(&adjtime, hclock_valid, hclocktime, + read_time, universal, testing); + *retcode_p = 0; + } 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); + *retcode_p = 0; + adjust_drift_factor(&adjtime, (time_t) reftime.tv_sec, hclock_valid, + hclocktime); + } else if (hctosys) { + rc = set_system_clock(hclock_valid, hclocktime, testing); + if (rc != 0) { + printf(_("Unable to set system clock.\n")); + *retcode_p = 1; + } else *retcode_p = 0; + } + save_adjtime(adjtime, testing); + } + } + } +} + + +static void +manipulate_epoch(const bool getepoch, const bool setepoch, + const int epoch_opt, const bool testing) { +/*---------------------------------------------------------------------------- + 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. + +-----------------------------------------------------------------------------*/ + /* + 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__ + fprintf(stderr, _("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.\n")); +#else + if (getepoch) { + unsigned long epoch; + + if (get_epoch_rtc(&epoch, 0)) + fprintf(stderr, _("Unable to get the epoch value from the kernel.\n")); + else + printf(_("Kernel is assuming an epoch value of %lu\n"), epoch); + } else if (setepoch) { + if (epoch_opt == -1) + fprintf(stderr, _("To set the epoch value, you must use the 'epoch' " + "option to tell to what value to set it.\n")); + 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 +} + +int +main(int argc, char **argv, char **envp) { + + struct timeval startup_time; + /* The time we started up, in seconds into the epoch, including fractions. + */ + time_t set_time; /* Time to which user said to set Hardware Clock */ + + bool permitted; /* User is permitted to do the function */ + int retcode; /* Our eventual return code */ + + int rc; /* local return code */ + + /* option_def is the control table for the option parser. These other + variables are the results of parsing the options and their meanings + are given by the option_def. The only exception is <show>, which + may be modified after parsing is complete to effect an implied option. + */ + bool show, set, systohc, hctosys, adjust, getepoch, setepoch, version; + bool ARCconsole, universal, testing, directisa, Jensen, SRM, funky_toy; + char *date_opt; + int epoch_opt; + + const optStruct option_def[] = { + { 'r', (char *) "show", OPT_FLAG, &show, 0 }, + { 0, (char *) "set", OPT_FLAG, &set, 0 }, + { 'w', (char *) "systohc", OPT_FLAG, &systohc, 0 }, + { 's', (char *) "hctosys", OPT_FLAG, &hctosys, 0 }, + { 0, (char *) "getepoch", OPT_FLAG, &getepoch, 0 }, + { 0, (char *) "setepoch", OPT_FLAG, &setepoch, 0 }, + { 'a', (char *) "adjust", OPT_FLAG, &adjust, 0 }, + { 'v', (char *) "version", OPT_FLAG, &version, 0 }, + { 0, (char *) "date", OPT_STRING, &date_opt, 0 }, + { 0, (char *) "epoch", OPT_UINT, &epoch_opt, 0 }, + { 'u', (char *) "utc", OPT_FLAG, &universal, 0 }, + { 0, (char *) "badyear", OPT_FLAG, &badyear, 0 }, + { 0, (char *) "directisa", OPT_FLAG, &directisa, 0 }, + { 0, (char *) "test", OPT_FLAG, &testing, 0 }, + { 'D', (char *) "debug", OPT_FLAG, &debug, 0 }, +#ifdef __alpha__ + { 'A', (char *) "ARC", OPT_FLAG, &ARCconsole,0 }, + { 'J', (char *) "Jensen", OPT_FLAG, &Jensen, 0 }, + { 'S', (char *) "SRM", OPT_FLAG, &SRM, 0 }, + { 'F', (char *) "funky-toy", OPT_FLAG, &funky_toy, 0 }, +#endif + { 0, (char *) NULL, OPT_END, NULL, 0 } + }; + int argc_parse; /* argc, except we modify it as we parse */ + char **argv_parse; /* argv, except we modify it as we parse */ + + gettimeofday(&startup_time, NULL); /* Remember what time we were invoked */ + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + + /* set option defaults */ + show = set = systohc = hctosys = adjust = getepoch = setepoch = + version = universal = ARCconsole = SRM = funky_toy = + directisa = badyear = Jensen = testing = debug = FALSE; + date_opt = NULL; + epoch_opt = -1; + + argc_parse = argc; argv_parse = argv; + optParseOptions(&argc_parse, argv_parse, option_def, 0); + /* Uses and sets argc_parse, argv_parse. + Sets show, systohc, hctosys, adjust, universal, version, testing, + debug, set, date_opt, getepoch, setepoch, epoch_opt + */ + /* This is an ugly routine - for example, if I give an incorrect + option, it only says "unrecognized option" without telling + me what options are recognized. Rewrite with standard + getopt() and usage() and throw shhopt out. */ + + if (argc_parse - 1 > 0) { + fprintf(stderr, _("%s takes no non-option arguments. " + "You supplied %d.\n"), + MYNAME, argc_parse - 1); + exit(100); + } + + if (show + set + systohc + hctosys + adjust + + getepoch + setepoch + version > 1) { + fprintf(stderr, _("You have specified multiple function options.\n" + "You can only perform one function at a time.\n")); + exit(100); + } + +#ifdef __alpha__ + set_cmos_epoch(ARCconsole, SRM); + set_cmos_access(Jensen, funky_toy); +#endif + + if (set) { + rc = interpret_date_string(date_opt, &set_time); /* (time-consuming) */ + if (rc != 0) { + fprintf(stderr, _("No usable set-to time. Cannot set clock.\n")); + exit(100); + } + } + + if (!(show | set | systohc | hctosys | adjust | getepoch | setepoch | + version)) + show = 1; /* default to show */ + + + if (getuid() == 0) permitted = TRUE; + else { + /* program is designed to run setuid (in some situations) -- be secure! */ + if (set || hctosys || systohc || adjust) { + fprintf(stderr, + _("Sorry, only the superuser can change the Hardware Clock.\n")); + permitted = FALSE; + } else if (setepoch) { + fprintf(stderr, + _("Sorry, only the superuser can change " + "the Hardware Clock epoch in the kernel.\n")); + permitted = FALSE; + } else permitted = TRUE; + } + + if (!permitted) retcode = 2; + else { + retcode = 0; + if (version) { + printf(MYNAME " " VERSION "/%s\n",util_linux_version); + } else if (getepoch || setepoch) { + manipulate_epoch(getepoch, setepoch, epoch_opt, testing); + } else { + determine_clock_access_method(directisa); + if (!ur) + fprintf(stderr, _("Cannot access the Hardware Clock via any known " + "method. Use --debug option to see the details of our " + "search for an access method.\n")); + else + manipulate_clock(show, adjust, set, set_time, hctosys, systohc, + startup_time, universal, testing, &rc); + } + } + exit(retcode); +} + +/* A single routine for greater uniformity */ +void +outsyserr(char *msg) { + fprintf(stderr, _("%s: %s, errno=%d: %s.\n"), + progname, msg, errno, strerror(errno)); +} + +/**************************************************************************** + + 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 absense 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. + + + 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/clock/kd.c b/clock/kd.c new file mode 100644 index 000000000..9ec91f005 --- /dev/null +++ b/clock/kd.c @@ -0,0 +1,149 @@ +/* kd.c - KDGHWCLK stuff, possibly m68k only */ +#include <unistd.h> /* for close() */ +#include <fcntl.h> /* for O_RDONLY */ +#include <sys/ioctl.h> + +#include "clock.h" +#include "nls.h" + +static int con_fd = -1; /* opened by probe_for_kd_clock() */ + /* never closed */ + +/* Get defines for KDGHWCLK and KDSHWCLK (m68k) */ +#include <linux/kd.h> +#ifndef KDGHWCLK +#define KDGHWCLK 0x4B50 /* get hardware clock */ +#define KDSHWCLK 0x4B51 /* set hardware clock */ +struct hwclk_time { + unsigned sec; /* 0..59 */ + unsigned min; /* 0..59 */ + unsigned hour; /* 0..23 */ + unsigned day; /* 1..31 */ + unsigned mon; /* 0..11 */ + unsigned year; /* 70... */ + int wday; /* 0..6, 0 is Sunday, -1 means unknown/don't set */ +}; +#endif + +static int +synchronize_to_clock_tick_kd(void) { +/*---------------------------------------------------------------------------- + Wait for the top of a clock tick by calling KDGHWCLK in a busy loop until + we see it. +-----------------------------------------------------------------------------*/ + int i; + + /* The time when we were called (and started waiting) */ + struct hwclk_time start_time, nowtime; + + if (debug) + printf(_("Waiting in loop for time from KDGHWCLK to change\n")); + + if (ioctl(con_fd, KDGHWCLK, &start_time) == -1) { + outsyserr(_("KDGHWCLK ioctl to read time failed")); + return 3; + } + + i = 0; + do { + if (i++ >= 1000000) { + fprintf(stderr, _("Timed out waiting for time change.\n")); + return 2; + } + if (ioctl(con_fd, KDGHWCLK, &nowtime) == -1) { + outsyserr(_("KDGHWCLK ioctl to read time failed in loop")); + return 3; + } + } while (start_time.sec == nowtime.sec); + + return 0; +} + + +static int +read_hardware_clock_kd(struct tm *tm) { +/*---------------------------------------------------------------------------- + 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. +-----------------------------------------------------------------------------*/ + struct hwclk_time t; + + if (ioctl(con_fd, KDGHWCLK, &t) == -1) { + outsyserr(_("ioctl() failed to read time from /dev/tty1")); + exit(5); + } + + 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; +} + + +static int +set_hardware_clock_kd(const struct tm *new_broken_time) { +/*---------------------------------------------------------------------------- + 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. +----------------------------------------------------------------------------*/ + 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) { + outsyserr(_("ioctl() to open /dev/tty1 failed")); + exit(1); + } + 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) + con_fd = open("/dev/tty1", O_RDONLY); + if (con_fd >= 0) { + if (ioctl( con_fd, KDGHWCLK, &t ) == -1) { + if (errno != EINVAL) + outsyserr(_("KDGHWCLK ioctl failed")); + } else + ret = &kd; + } else { + outsyserr(_("Can't open /dev/tty1")); + } + return ret; +} diff --git a/clock/rtc.c b/clock/rtc.c new file mode 100644 index 000000000..652388430 --- /dev/null +++ b/clock/rtc.c @@ -0,0 +1,389 @@ +/* rtc.c - Use /dev/rtc for clock access */ +#include <unistd.h> /* for close() */ +#include <fcntl.h> /* for O_RDONLY */ +#include <sys/ioctl.h> + +#include "clock.h" +#include "nls.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 + + +static int +do_rtc_read_ioctl(int rtc_fd, struct tm *tm) { + int rc; + char *ioctlname; +#ifdef __sparc__ + struct sparc_rtc_time stm; + + ioctlname = "RTCGET"; + rc = ioctl(rtc_fd, RTCGET, &stm); +#else + ioctlname = "RTC_RD_TIME"; + rc = ioctl(rtc_fd, RTC_RD_TIME, tm); +#endif + if (rc == -1) { + perror(ioctlname); + fprintf(stderr, _("ioctl() to /dev/rtc to read the time failed.\n")); + exit(5); + } +#ifdef __sparc__ + 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 + tm->tm_isdst = -1; /* don't know whether it's daylight */ + return 0; +} + + +static int +busywait_for_rtc_clock_tick(const int rtc_fd) { +/*---------------------------------------------------------------------------- + Wait for the top of a clock tick by reading /dev/rtc in a busy loop until + we see it. +-----------------------------------------------------------------------------*/ + struct tm start_time; + /* The time when we were called (and started waiting) */ + struct tm nowtime; + int i; /* local loop index */ + int rc; + + if (debug) + printf(_("Waiting in loop for time from /dev/rtc to change\n")); + + 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 limit on this loop to reduce the impact + of this failure. + */ + for (i = 0; + (rc = do_rtc_read_ioctl(rtc_fd, &nowtime)) == 0 + && start_time.tm_sec == nowtime.tm_sec; + i++) + if (i >= 1000000) { + fprintf(stderr, _("Timed out waiting for time change.\n")); + return 2; + } + + if (rc) + return 3; + return 0; +} + + + +static int +synchronize_to_clock_tick_rtc(void) { +/*---------------------------------------------------------------------------- + Same as synchronize_to_clock_tick(), but just for /dev/rtc. +-----------------------------------------------------------------------------*/ +int rtc_fd; /* File descriptor of /dev/rtc */ +int ret; + + rtc_fd = open("/dev/rtc",O_RDONLY); + if (rtc_fd == -1) { + outsyserr(_("open() of /dev/rtc failed")); + 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 == 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(_("/dev/rtc does not have interrupt functions. ")); + ret = busywait_for_rtc_clock_tick(rtc_fd); + } else if (rc != -1) { + int rc; /* return code from ioctl */ + unsigned long dummy; + + /* this blocks until the next update interrupt */ + rc = read(rtc_fd, &dummy, sizeof(dummy)); + if (rc == -1) { + outsyserr(_("read() to /dev/rtc to wait for clock tick failed")); + ret = 1; + } else { + ret = 0; + } + /* Turn off update interrupts */ + rc = ioctl(rtc_fd, RTC_UIE_OFF, 0); + if (rc == -1) + outsyserr(_("ioctl() to /dev/rtc to turn off update interrupts " + "failed")); + } else { + outsyserr(_("ioctl() to /dev/rtc to turn on update interrupts " + "failed unexpectedly")); + ret = 1; + } + close(rtc_fd); + } + return ret; +} + + +static int +read_hardware_clock_rtc(struct tm *tm) { +/*---------------------------------------------------------------------------- + Read the hardware clock and return the current time via <tm> + argument. Use ioctls to "rtc" device /dev/rtc. +-----------------------------------------------------------------------------*/ + int rtc_fd; /* File descriptor of /dev/rtc */ + + rtc_fd = open("/dev/rtc",O_RDONLY); + if (rtc_fd == -1) { + outsyserr(_("open() of /dev/rtc failed")); + exit(5); + } + + /* Read the RTC time/date */ + do_rtc_read_ioctl(rtc_fd, tm); + + close(rtc_fd); + return 0; +} + + +static int +set_hardware_clock_rtc(const struct tm *new_broken_time) { +/*---------------------------------------------------------------------------- + Set the Hardware Clock to the broken down time <new_broken_time>. + Use ioctls to "rtc" device /dev/rtc. +----------------------------------------------------------------------------*/ + int rc; + int rtc_fd; + + rtc_fd = open("/dev/rtc", O_RDONLY); + if (rtc_fd < 0) { + outsyserr(_("Unable to open /dev/rtc")); + exit(5); + } else { + char *ioctlname; +#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); +#else + ioctlname = "RTC_SET_TIME"; + rc = ioctl(rtc_fd, RTC_SET_TIME, new_broken_time); +#endif + if (rc == -1) { + perror(ioctlname); + fprintf(stderr, _("ioctl() to /dev/rtc to set the time failed.\n")); + exit(5); + } else { + if (debug) + printf(_("ioctl(%s) was successful.\n"), ioctlname); + } + close(rtc_fd); + } + return 0; +} + + +static int +get_permissions_rtc(void) { + return 0; +} + +static struct clock_ops rtc = { + "/dev/rtc 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(){ + int rtc_fd = open("/dev/rtc", O_RDONLY); + if (rtc_fd > 0) { + close(rtc_fd); + return &rtc; + } + if (debug) + outsyserr(_("Open of /dev/rtc failed")); + return NULL; +} + + + +int +get_epoch_rtc(unsigned long *epoch_p, int silent) { +/*---------------------------------------------------------------------------- + Get the Hardware Clock epoch setting from the kernel. +----------------------------------------------------------------------------*/ + int rtc_fd; + + rtc_fd = open("/dev/rtc", O_RDONLY); + if (rtc_fd < 0) { + if (!silent) { + if (errno == ENOENT) + fprintf(stderr, _( + "To manipulate the epoch value in the kernel, we must " + "access the Linux 'rtc' device driver via the device special " + "file /dev/rtc. This file does not exist on this system.\n")); + else + outsyserr(_("Unable to open /dev/rtc")); + } + return 1; + } + + if (ioctl(rtc_fd, RTC_EPOCH_READ, epoch_p) == -1) { + if (!silent) + outsyserr(_("ioctl(RTC_EPOCH_READ) to /dev/rtc failed")); + close(rtc_fd); + return 1; + } + + if (debug) + printf(_("we have read epoch %ld from /dev/rtc " + "with RTC_EPOCH_READ ioctl.\n"), *epoch_p); + + close(rtc_fd); + return 0; +} + + + +int +set_epoch_rtc(unsigned long epoch) { +/*---------------------------------------------------------------------------- + Set the Hardware Clock epoch in the kernel. +----------------------------------------------------------------------------*/ + int rtc_fd; + + if (epoch < 1900) { + /* kernel would not accept this epoch value */ + /* Hmm - bad habit, deciding not to do what the user asks + just because one believes that the kernel might not like it. */ + fprintf(stderr, _("The epoch value may not be less than 1900. " + "You requested %ld\n"), epoch); + return 1; + } + + rtc_fd = open("/dev/rtc", O_RDONLY); + if (rtc_fd < 0) { + if (errno == ENOENT) + fprintf(stderr, _("To manipulate the epoch value in the kernel, we must " + "access the Linux 'rtc' device driver via the device special " + "file /dev/rtc. This file does not exist on this system.\n")); + else + outsyserr(_("Unable to open /dev/rtc")); + return 1; + } + + if (debug) + printf(_("setting epoch to %ld " + "with RTC_EPOCH_SET ioctl to /dev/rtc.\n"), epoch); + + if (ioctl(rtc_fd, RTC_EPOCH_SET, epoch) == -1) { + if (errno == EINVAL) + fprintf(stderr, _("The kernel device driver for /dev/rtc " + "does not have the RTC_EPOCH_SET ioctl.\n")); + else + outsyserr(_("ioctl(RTC_EPOCH_SET) to /dev/rtc failed")); + close(rtc_fd); + return 1; + } + + close(rtc_fd); + return 0; +} diff --git a/clock/shhopt-1.1.lsm b/clock/shhopt-1.1.lsm new file mode 100644 index 000000000..a61a26969 --- /dev/null +++ b/clock/shhopt-1.1.lsm @@ -0,0 +1,17 @@ +Begin3 +Title: shhopt - library for parsing command line options. +Version: 1.1 +Entered-date: 06JUN96 +Description: C-functions for parsing command line options, both + traditional one-character options, and GNU'ish + --long-options. +Keywords: programming, library, lib, commandline +Author: sverrehu@ifi.uio.no (Sverre H. Huseby) +Primary-site: sunsite.unc.edu /pub/Linux/libs + shhopt-1.1.tar.gz +Platforms: Requires ANSI C-compiler. +Copying-policy: BeerWare: If you have the time and money, send me a bottle + of your favourite beer. If not, just send me a mail or + something. Copy and use as you wish; just leave the + author's name where you find it. +End diff --git a/clock/shhopt.c b/clock/shhopt.c new file mode 100644 index 000000000..e55a7293a --- /dev/null +++ b/clock/shhopt.c @@ -0,0 +1,468 @@ +/* $Id: shhopt.c,v 2.2 1997/07/06 23:11:55 aebr Exp $ */ +/************************************************************************** + * + * FILE shhopt.c + * + * DESCRIPTION Functions for parsing command line arguments. Values + * of miscellaneous types may be stored in variables, + * or passed to functions as specified. + * + * REQUIREMENTS Some systems lack the ANSI C -function strtoul. If your + * system is one of those, you'll ned to write one yourself, + * or get the GNU liberty-library (from prep.ai.mit.edu). + * + * WRITTEN BY Sverre H. Huseby <sverrehu@ifi.uio.no> + * + **************************************************************************/ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <ctype.h> +#include <limits.h> +#include <errno.h> + +#include "shhopt.h" +#include "nls.h" + +/************************************************************************** + * * + * P R I V A T E D A T A * + * * + **************************************************************************/ + +static void optFatalFunc(const char *, ...); +static void (*optFatal)(const char *format, ...) = optFatalFunc; + + + +/************************************************************************** + * * + * P R I V A T E F U N C T I O N S * + * * + **************************************************************************/ + +/*------------------------------------------------------------------------- + * + * NAME optFatalFunc + * + * FUNCTION Show given message and abort the program. + * + * INPUT format, ... + * Arguments used as with printf(). + * + * RETURNS Never returns. The program is aborted. + * + */ +void optFatalFunc(const char *format, ...) +{ + va_list ap; + + fflush(stdout); + va_start(ap, format); + vfprintf(stderr, format, ap); + va_end(ap); + exit(99); +} + + + +/*------------------------------------------------------------------------- + * + * NAME optStructCount + * + * FUNCTION Get number of options in a optStruct. + * + * INPUT opt array of possible options. + * + * RETURNS Number of options in the given array. + * + * DESCRIPTION Count elements in an optStruct-array. The strcture must + * be ended using an element of type OPT_END. + * + */ +static int optStructCount(const optStruct opt[]) +{ + int ret = 0; + + while (opt[ret].type != OPT_END) + ++ret; + return ret; +} + + + +/*------------------------------------------------------------------------- + * + * NAME optMatch + * + * FUNCTION Find a matching option. + * + * INPUT opt array of possible options. + * s string to match, without `-' or `--'. + * lng match long option, otherwise short. + * + * RETURNS Index to the option if found, -1 if not found. + * + * DESCRIPTION Short options are matched from the first character in + * the given string. + * + */ +static int optMatch(const optStruct opt[], const char *s, int lng) +{ + int nopt, q, matchlen = 0; + char *p; + + nopt = optStructCount(opt); + if (lng) { + if ((p = strchr(s, '=')) != NULL) + matchlen = p - s; + else + matchlen = strlen(s); + } + for (q = 0; q < nopt; q++) { + if (lng) { + if (!opt[q].longName) + continue; + if (strncmp(s, opt[q].longName, matchlen) == 0) + return q; + } else { + if (!opt[q].shortName) + continue; + if (*s == opt[q].shortName) + return q; + } + } + return -1; +} + + + +/*------------------------------------------------------------------------- + * + * NAME optString + * + * FUNCTION Return a (static) string with the option name. + * + * INPUT opt the option to stringify. + * lng is it a long option? + * + * RETURNS Pointer to static string. + * + */ +static char *optString(const optStruct *opt, int lng) +{ + static char ret[31]; + + if (lng) { + strcpy(ret, "--"); + strncpy(ret + 2, opt->longName, 28); + } else { + ret[0] = '-'; + ret[1] = opt->shortName; + ret[2] = '\0'; + } + return ret; +} + + + +/*------------------------------------------------------------------------- + * + * NAME optNeedsArgument + * + * FUNCTION Check if an option requires an argument. + * + * INPUT opt the option to check. + * + * RETURNS Boolean value. + * + */ +static int optNeedsArgument(const optStruct *opt) +{ + return opt->type == OPT_STRING + || opt->type == OPT_INT + || opt->type == OPT_UINT + || opt->type == OPT_LONG + || opt->type == OPT_ULONG; +} + + + +/*------------------------------------------------------------------------- + * + * NAME argvRemove + * + * FUNCTION Remove an entry from an argv-array. + * + * INPUT argc pointer to number of options. + * argv array of option-/argument-strings. + * i index of option to remove. + * + * OUTPUT argc new argument count. + * argv array with given argument removed. + * + */ +static void argvRemove(int *argc, char *argv[], int i) +{ + if (i >= *argc) + return; + while (i++ < *argc) + argv[i - 1] = argv[i]; + --*argc; +} + + + +/*------------------------------------------------------------------------- + * + * NAME optExecute + * + * FUNCTION Perform the action of an option. + * + * INPUT opt array of possible options. + * arg argument to option, if it applies. + * lng was the option given as a long option? + * + * RETURNS Nothing. Aborts in case of error. + * + */ +void optExecute(const optStruct *opt, char *arg, int lng) +{ + switch (opt->type) { + case OPT_FLAG: + if (opt->flags & OPT_CALLFUNC) + ((void (*)(void)) opt->arg)(); + else + *((int *) opt->arg) = 1; + break; + + case OPT_STRING: + if (opt->flags & OPT_CALLFUNC) + ((void (*)(char *)) opt->arg)(arg); + else + *((char **) opt->arg) = arg; + break; + + case OPT_INT: + case OPT_LONG: { + long tmp; + char *e; + + tmp = strtol(arg, &e, 10); + if (*e) + optFatal(_("invalid number `%s'\n"), arg); + if (errno == ERANGE + || (opt->type == OPT_INT && (tmp > INT_MAX || tmp < INT_MIN))) + optFatal(_("number `%s' to `%s' out of range\n"), + arg, optString(opt, lng)); + if (opt->type == OPT_INT) { + if (opt->flags & OPT_CALLFUNC) + ((void (*)(int)) opt->arg)((int) tmp); + else + *((int *) opt->arg) = (int) tmp; + } else /* OPT_LONG */ { + if (opt->flags & OPT_CALLFUNC) + ((void (*)(long)) opt->arg)(tmp); + else + *((long *) opt->arg) = tmp; + } + break; + } + + case OPT_UINT: + case OPT_ULONG: { + unsigned long tmp; + char *e; + + tmp = strtoul(arg, &e, 10); + if (*e) + optFatal(_("invalid number `%s'\n"), arg); + if (errno == ERANGE + || (opt->type == OPT_UINT && tmp > UINT_MAX)) + optFatal(_("number `%s' to `%s' out of range\n"), + arg, optString(opt, lng)); + if (opt->type == OPT_UINT) { + if (opt->flags & OPT_CALLFUNC) + ((void (*)(unsigned)) opt->arg)((unsigned) tmp); + else + *((unsigned *) opt->arg) = (unsigned) tmp; + } else /* OPT_ULONG */ { + if (opt->flags & OPT_CALLFUNC) + ((void (*)(unsigned long)) opt->arg)(tmp); + else + *((unsigned long *) opt->arg) = tmp; + } + break; + } + + default: + break; + } +} + + + +/************************************************************************** + * * + * P U B L I C F U N C T I O N S * + * * + **************************************************************************/ + +/*------------------------------------------------------------------------- + * + * NAME optSetFatalFunc + * + * FUNCTION Set function used to display error message and exit. + * + * SYNOPSIS #include "shhmsg.h" + * void optSetFatalFunc(void (*f)(const char *, ...)); + * + * INPUT f function accepting printf()'like parameters, + * that _must_ abort the program. + * + */ +void optSetFatalFunc(void (*f)(const char *, ...)) +{ + optFatal = f; +} + + + +/*------------------------------------------------------------------------- + * + * NAME optParseOptions + * + * FUNCTION Parse commandline options. + * + * SYNOPSIS #include "shhopt.h" + * void optParseOptions(int *argc, char *argv[], + * const optStruct opt[], int allowNegNum); + * + * INPUT argc Pointer to number of options. + * argv Array of option-/argument-strings. + * opt Array of possible options. + * allowNegNum + * a negative number is not to be taken as + * an option. + * + * OUTPUT argc new argument count. + * argv array with arguments removed. + * + * RETURNS Nothing. Aborts in case of error. + * + * DESCRIPTION This function checks each option in the argv-array + * against strings in the opt-array, and `executes' any + * matching action. Any arguments to the options are + * extracted and stored in the variables or passed to + * functions pointed to by entries in opt. + * + * Options and arguments used are removed from the argv- + * array, and argc is decreased accordingly. + * + * Any error leads to program abortion. + * + */ +void optParseOptions(int *argc, char *argv[], + const optStruct opt[], int allowNegNum) +{ + int ai, /* argv index. */ + optarg, /* argv index of option argument, or -1 if none. */ + mi, /* Match index in opt. */ + done; + char *arg, /* Pointer to argument to an option. */ + *o, /* pointer to an option character */ + *p; + + /* + * Loop through all arguments. + */ + for (ai = 0; ai < *argc; ) { + /* + * "--" indicates that the rest of the argv-array does not + * contain options. + */ + if (strcmp(argv[ai], "--") == 0) { + argvRemove(argc, argv, ai); + break; + } + + if (allowNegNum && argv[ai][0] == '-' && isdigit(argv[ai][1])) { + ++ai; + continue; + } else if (strncmp(argv[ai], "--", 2) == 0) { + /* long option */ + /* find matching option */ + if ((mi = optMatch(opt, argv[ai] + 2, 1)) < 0) + optFatal(_("unrecognized option `%s'\n"), argv[ai]); + + /* possibly locate the argument to this option. */ + arg = NULL; + if ((p = strchr(argv[ai], '=')) != NULL) + arg = p + 1; + + /* does this option take an argument? */ + optarg = -1; + if (optNeedsArgument(&opt[mi])) { + /* option needs an argument. find it. */ + if (!arg) { + if ((optarg = ai + 1) == *argc) + optFatal(_("option `%s' requires an argument\n"), + optString(&opt[mi], 1)); + arg = argv[optarg]; + } + } else { + if (arg) + optFatal(_("option `%s' doesn't allow an argument\n"), + optString(&opt[mi], 1)); + } + /* perform the action of this option. */ + optExecute(&opt[mi], arg, 1); + /* remove option and any argument from the argv-array. */ + if (optarg >= 0) + argvRemove(argc, argv, ai); + argvRemove(argc, argv, ai); + } else if (*argv[ai] == '-') { + /* A dash by itself is not considered an option. */ + if (argv[ai][1] == '\0') { + ++ai; + continue; + } + /* Short option(s) following */ + o = argv[ai] + 1; + done = 0; + optarg = -1; + while (*o && !done) { + /* find matching option */ + if ((mi = optMatch(opt, o, 0)) < 0) + optFatal(_("unrecognized option `-%c'\n"), *o); + + /* does this option take an argument? */ + optarg = -1; + arg = NULL; + if (optNeedsArgument(&opt[mi])) { + /* option needs an argument. find it. */ + arg = o + 1; + if (!*arg) { + if ((optarg = ai + 1) == *argc) + optFatal(_("option `%s' requires an argument\n"), + optString(&opt[mi], 0)); + arg = argv[optarg]; + } + done = 1; + } + /* perform the action of this option. */ + optExecute(&opt[mi], arg, 0); + ++o; + } + /* remove option and any argument from the argv-array. */ + if (optarg >= 0) + argvRemove(argc, argv, ai); + argvRemove(argc, argv, ai); + } else { + /* a non-option argument */ + ++ai; + } + } +} diff --git a/clock/shhopt.h b/clock/shhopt.h new file mode 100644 index 000000000..ca8501ef5 --- /dev/null +++ b/clock/shhopt.h @@ -0,0 +1,33 @@ +/* $Id: shhopt.h,v 2.2 1997/07/06 23:11:58 aebr Exp $ */ +#ifndef SHHOPT_H +#define SHHOPT_H + +/* constants for recognized option types. */ +typedef enum { + OPT_END, /* nothing. used as ending element. */ + OPT_FLAG, /* no argument following. sets variable to 1. */ + OPT_STRING, /* string argument. */ + OPT_INT, /* signed integer argument. */ + OPT_UINT, /* unsigned integer argument. */ + OPT_LONG, /* signed long integer argument. */ + OPT_ULONG, /* unsigned long integer argument. */ +} optArgType; + +/* flags modifying the default way options are handeled. */ +#define OPT_CALLFUNC 1 /* pass argument to a function. */ + +typedef struct { + char shortName; /* Short option name. */ + char *longName; /* Long option name, no including '--'. */ + optArgType type; /* Option type. */ + void *arg; /* Pointer to variable to fill with argument, + * or pointer to function if Type == OPT_FUNC. */ + int flags; /* Modifier flags. */ +} optStruct; + + +void optSetFatalFunc(void (*f)(const char *, ...)); +void optParseOptions(int *argc, char *argv[], + const optStruct opt[], int allowNegNum); + +#endif |