diff options
author | Karel Zak | 2006-12-07 00:25:44 +0100 |
---|---|---|
committer | Karel Zak | 2006-12-07 00:25:44 +0100 |
commit | 66ee8158b69525e12060ef558cb5d77feadab1dc (patch) | |
tree | 08b30f2d07df9213f5647bc6f60b5090a263ef43 /hwclock | |
parent | Imported from util-linux-2.10m tarball. (diff) | |
download | kernel-qcow2-util-linux-66ee8158b69525e12060ef558cb5d77feadab1dc.tar.gz kernel-qcow2-util-linux-66ee8158b69525e12060ef558cb5d77feadab1dc.tar.xz kernel-qcow2-util-linux-66ee8158b69525e12060ef558cb5d77feadab1dc.zip |
Imported from util-linux-2.10s tarball.
Diffstat (limited to 'hwclock')
-rw-r--r-- | hwclock/Makefile | 35 | ||||
-rw-r--r-- | hwclock/README.aeb | 9 | ||||
-rw-r--r-- | hwclock/README.hwclock | 27 | ||||
-rw-r--r-- | hwclock/README.shhopt-1.1 | 155 | ||||
-rw-r--r-- | hwclock/adjtime.patch | 302 | ||||
-rw-r--r-- | hwclock/clock-ppc.c | 459 | ||||
-rw-r--r-- | hwclock/clock.h | 35 | ||||
-rw-r--r-- | hwclock/cmos.c | 612 | ||||
-rw-r--r-- | hwclock/hwclock.8 | 595 | ||||
-rw-r--r-- | hwclock/hwclock.c | 1448 | ||||
-rw-r--r-- | hwclock/kd.c | 167 | ||||
-rw-r--r-- | hwclock/rtc.c | 396 | ||||
-rw-r--r-- | hwclock/shhopt-1.1.lsm | 17 | ||||
-rw-r--r-- | hwclock/shhopt.c | 468 | ||||
-rw-r--r-- | hwclock/shhopt.h | 33 |
15 files changed, 4758 insertions, 0 deletions
diff --git a/hwclock/Makefile b/hwclock/Makefile new file mode 100644 index 000000000..70ff6436c --- /dev/null +++ b/hwclock/Makefile @@ -0,0 +1,35 @@ +# 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 + +CWFLAGS := $(subst -Wmissing-prototypes,,$(CFLAGS)) + +cmos.o: cmos.c + $(CC) $(CWFLAGS) -c cmos.c -o $@ +# $< expands to clock.h on some systems + +install: all + $(INSTALLDIR) $(SBINDIR) $(BINDIR) $(USRBINDIR) + $(INSTALLBIN) $(SBIN) $(SBINDIR) + $(INSTALLMAN) $(MAN8) $(MAN8DIR) + +clean: + -rm -f *.o *~ core $(SBIN) diff --git a/hwclock/README.aeb b/hwclock/README.aeb new file mode 100644 index 000000000..3955f9050 --- /dev/null +++ b/hwclock/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/hwclock/README.hwclock b/hwclock/README.hwclock new file mode 100644 index 000000000..ba8bc44c3 --- /dev/null +++ b/hwclock/README.hwclock @@ -0,0 +1,27 @@ +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. + +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 probably +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. You +can find a more authoritative copy of this package on metalab +(ftp://metalab.unc.edu/pub/Linux/libs/shhopt-X.Y). diff --git a/hwclock/README.shhopt-1.1 b/hwclock/README.shhopt-1.1 new file mode 100644 index 000000000..766d6cbdc --- /dev/null +++ b/hwclock/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/hwclock/adjtime.patch b/hwclock/adjtime.patch new file mode 100644 index 000000000..81d0430fd --- /dev/null +++ b/hwclock/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/hwclock/clock-ppc.c b/hwclock/clock-ppc.c new file mode 100644 index 000000000..6d8969adb --- /dev/null +++ b/hwclock/clock-ppc.c @@ -0,0 +1,459 @@ +/* +From t-matsuu@protein.osaka-u.ac.jp Sat Jan 22 13:43:20 2000 +Date: Sat, 22 Jan 2000 21:42:54 +0900 (JST) +To: Andries.Brouwer@cwi.nl +Subject: Please merge the source for PPC +From: MATSUURA Takanori <t-matsuu@protein.osaka-u.ac.jp> + +Even now, it is used clock-1.1 based source on Linux for PowerPC +architecture, attached on this mail. + +Please merge this source in main util-linux source. + +But I'm not an author of this source, but Paul Mackerras. +http://linuxcare.com.au/paulus/ +shows details of him. + +MATSUURA Takanori @ Division of Protein Chemistry, + Institute for Protein Research, Osaka University, Japan +E-Mail: t-matsuu@protein.osaka-u.ac.jp +Web Page: http://www.protein.osaka-u.ac.jp/chemistry/matsuura/ +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> + +#include <time.h> +#include <fcntl.h> +#include <getopt.h> +#include <sys/time.h> + +#include <asm/cuda.h> + +/* + * Adapted for Power Macintosh by Paul Mackerras. + */ + +/* V1.0 + * CMOS clock manipulation - Charles Hedrick, hedrick@cs.rutgers.edu, Apr 1992 + * + * clock [-u] -r - read cmos clock + * clock [-u] -w - write cmos clock from system time + * clock [-u] -s - set system time from cmos clock + * clock [-u] -a - set system time from cmos clock, adjust the time to + * correct for systematic error, and put it back to the cmos. + * -u indicates cmos clock is kept in universal time + * + * The program is designed to run setuid, since we need to be able to + * write to the CUDA. + * + ********************* + * V1.1 + * Modified for clock adjustments - Rob Hooft, hooft@chem.ruu.nl, Nov 1992 + * Also moved error messages to stderr. The program now uses getopt. + * Changed some exit codes. Made 'gcc 2.3 -Wall' happy. + * + * I think a small explanation of the adjustment routine should be given + * here. 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' + * + * If the adjustment doesn't work for you, try contacting me by E-mail. + * + ****** + * V1.2 + * + * Applied patches by Harald Koenig (koenig@nova.tat.physik.uni-tuebingen.de) + * Patched and indented by Rob Hooft (hooft@EMBL-Heidelberg.DE) + * + * A free quote from a MAIL-message (with spelling corrections): + * + * "I found the explanation and solution for the CMOS reading 0xff problem + * in the 0.99pl13c (ALPHA) kernel: the RTC goes offline for a small amount + * of time for updating. Solution is included in the kernel source + * (linux/kernel/time.c)." + * + * "I modified clock.c to fix this problem and added an option (now default, + * look for USE_INLINE_ASM_IO) that I/O instructions are used as inline + * code and not via /dev/port (still possible via #undef ...)." + * + * With the new code, which is partially taken from the kernel sources, + * the CMOS clock handling looks much more "official". + * Thanks Harald (and Torsten for the kernel code)! + * + ****** + * V1.3 + * Canges from alan@spri.levels.unisa.edu.au (Alan Modra): + * a) Fix a few typos in comments and remove reference to making + * clock -u a cron job. The kernel adjusts cmos time every 11 + * minutes - see kernel/sched.c and kernel/time.c set_rtc_mmss(). + * This means we should really have a cron job updating + * /etc/adjtime every 11 mins (set last_time to the current time + * and not_adjusted to ???). + * b) Swapped arguments of outb() to agree with asm/io.h macro of the + * same name. Use outb() from asm/io.h as it's slightly better. + * c) Changed CMOS_READ and CMOS_WRITE to inline functions. Inserted + * cli()..sti() pairs in appropriate places to prevent possible + * errors, and changed ioperm() call to iopl() to allow cli. + * d) Moved some variables around to localise them a bit. + * e) Fixed bug with clock -ua or clock -us that cleared environment + * variable TZ. This fix also cured the annoying display of bogus + * day of week on a number of machines. (Use mktime(), ctime() + * rather than asctime() ) + * f) Use settimeofday() rather than stime(). This one is important + * as it sets the kernel's timezone offset, which is returned by + * gettimeofday(), and used for display of MSDOS and OS2 file + * times. + * g) faith@cs.unc.edu added -D flag for debugging + * + * V1.4: alan@SPRI.Levels.UniSA.Edu.Au (Alan Modra) + * Wed Feb 8 12:29:08 1995, fix for years > 2000. + * faith@cs.unc.edu added -v option to print version. + * + * August 1996 Tom Dyas (tdyas@eden.rutgers.edu) + * Converted to be compatible with the SPARC /dev/rtc driver. + * + */ + +#define VERSION "1.4" + +/* Here the information for time adjustments is kept. */ +#define ADJPATH "/etc/adjtime" + +/* Apparently the RTC on PowerMacs stores seconds since 1 Jan 1904 */ +#define RTC_OFFSET 2082844800 + +/* used for debugging the code. */ +/*#define KEEP_OFF */ + +/* Globals */ +int readit = 0; +int adjustit = 0; +int writeit = 0; +int setit = 0; +int universal = 0; +int debug = 0; + +time_t mkgmtime(struct tm *); + +volatile void +usage ( void ) +{ + (void) fprintf (stderr, + "clock [-u] -r|w|s|a|v\n" + " r: read and print CMOS clock\n" + " w: write CMOS clock from system time\n" + " s: set system time from CMOS clock\n" + " a: get system time and adjust CMOS clock\n" + " u: CMOS clock is in universal time\n" + " v: print version (" VERSION ") and exit\n" + ); + exit(EXIT_FAILURE); +} + +int adb_fd; + +void +adb_init ( void ) +{ + adb_fd = open ("/dev/adb", 2); + if (adb_fd < 0) + { + perror ("unable to open /dev/adb read/write : "); + exit(EXIT_FAILURE); + } +} + +unsigned char get_packet[2] = { (unsigned char) CUDA_PACKET, + (unsigned char) CUDA_GET_TIME }; +unsigned char set_packet[6] = { (unsigned char) CUDA_PACKET, + (unsigned char) CUDA_SET_TIME }; + +int +main (int argc, char **argv ) +{ + struct tm tm, *tmp; + time_t systime; + time_t last_time; + time_t clock_time; + int i, arg; + double factor; + double not_adjusted; + int adjustment = 0; + /* unsigned char save_control, save_freq_select; */ + unsigned char reply[16]; + + while ((arg = getopt (argc, argv, "rwsuaDv")) != -1) + { + switch (arg) + { + case 'r': + readit = 1; + break; + case 'w': + writeit = 1; + break; + case 's': + setit = 1; + break; + case 'u': + universal = 1; + break; + case 'a': + adjustit = 1; + break; + case 'D': + debug = 1; + break; + case 'v': + (void) fprintf( stderr, "clock " VERSION "\n" ); + exit(EXIT_SUCCESS); + default: + usage (); + } + } + + /* If we are in MkLinux do not even bother trying to set the clock */ + if(!access("/proc/osfmach3/version", R_OK)) + { // We're running MkLinux + if ( readit | writeit | setit | adjustit ) + printf("You must change the clock setting in MacOS.\n"); + exit(0); + } + + if (readit + writeit + setit + adjustit > 1) + usage (); /* only allow one of these */ + + if (!(readit | writeit | setit | adjustit)) /* default to read */ + readit = 1; + + adb_init (); + + if (adjustit) + { /* Read adjustment parameters first */ + FILE *adj; + if ((adj = fopen (ADJPATH, "r")) == NULL) + { + perror (ADJPATH); + exit(EXIT_FAILURE); + } + if (fscanf (adj, "%lf %d %lf", &factor, (int *) (&last_time), + ¬_adjusted) < 0) + { + perror (ADJPATH); + exit(EXIT_FAILURE); + } + (void) fclose (adj); + if (debug) (void) printf( + "Last adjustment done at %d seconds after 1/1/1970\n", + (int) last_time); + } + + if (readit || setit || adjustit) + { + int ii; + + if (write(adb_fd, get_packet, sizeof(get_packet)) < 0) { + perror("write adb"); + exit(EXIT_FAILURE); + } + ii = (int) read(adb_fd, reply, sizeof(reply)); + if (ii < 0) { + perror("read adb"); + exit(EXIT_FAILURE); + } + if (ii != 7) + (void) fprintf(stderr, + "Warning: bad reply length from CUDA (%d)\n", ii); + clock_time = (time_t) ((reply[3] << 24) + (reply[4] << 16) + + (reply[5] << 8)) + (time_t) reply[6]; + clock_time -= RTC_OFFSET; + + if (universal) { + systime = clock_time; + } else { + tm = *gmtime(&clock_time); + (void) printf("time in rtc is %s", asctime(&tm)); + tm.tm_isdst = -1; /* don't know whether it's DST */ + systime = mktime(&tm); + } + } + + if (readit) + { + (void) printf ("%s", ctime (&systime )); + } + + if (setit || adjustit) + { + struct timeval tv; + struct timezone tz; + +/* program is designed to run setuid, be secure! */ + + if (getuid () != 0) + { + (void) fprintf (stderr, + "Sorry, must be root to set or adjust time\n"); + exit(EXIT_FAILURE); + } + + if (adjustit) + { /* the actual adjustment */ + double exact_adjustment; + + exact_adjustment = ((double) (systime - last_time)) + * factor / (24 * 60 * 60) + + not_adjusted; + if (exact_adjustment > 0.) + adjustment = (int) (exact_adjustment + 0.5); + else + adjustment = (int) (exact_adjustment - 0.5); + not_adjusted = exact_adjustment - (double) adjustment; + systime += adjustment; + if (debug) { + (void) printf ("Time since last adjustment is %d seconds\n", + (int) (systime - last_time)); + (void) printf ("Adjusting time by %d seconds\n", + adjustment); + (void) printf ("remaining adjustment is %.3f seconds\n", + not_adjusted); + } + } +#ifndef KEEP_OFF + tv.tv_sec = systime; + tv.tv_usec = 0; + tz.tz_minuteswest = timezone / 60; + tz.tz_dsttime = daylight; + + if (settimeofday (&tv, &tz) != 0) + { + (void) fprintf (stderr, + "Unable to set time -- probably you are not root\n"); + exit(EXIT_FAILURE); + } + + if (debug) { + (void) printf( "Called settimeofday:\n" ); + (void) printf( "\ttv.tv_sec = %ld, tv.tv_usec = %ld\n", + tv.tv_sec, tv.tv_usec ); + (void) printf( "\ttz.tz_minuteswest = %d, tz.tz_dsttime = %d\n", + tz.tz_minuteswest, tz.tz_dsttime ); + } +#endif + } + + if (writeit || (adjustit && adjustment != 0)) + { + systime = time (NULL); + + if (universal) { + clock_time = systime; + + } else { + tmp = localtime(&systime); + clock_time = mkgmtime(tmp); + } + + clock_time += RTC_OFFSET; + set_packet[2] = clock_time >> 24; + set_packet[3] = clock_time >> 16; + set_packet[4] = clock_time >> 8; + set_packet[5] = (unsigned char) clock_time; + + if (write(adb_fd, set_packet, sizeof(set_packet)) < 0) { + perror("write adb (set)"); + exit(EXIT_FAILURE); + } + i = (int) read(adb_fd, reply, sizeof(reply)); + if (debug) { + int j; + (void) printf("set reply %d bytes:", i); + for (j = 0; j < i; ++j) + (void) printf(" %.2x", (unsigned int) reply[j]); + (void) printf("\n"); + } + if (i != 3 || reply[1] != (unsigned char) 0) + (void) fprintf(stderr, "Warning: error %d setting RTC\n", + (int) reply[1]); + + if (debug) { + clock_time -= RTC_OFFSET; + (void) printf("set RTC to %s", asctime(gmtime(&clock_time))); + } + } + else + if (debug) (void) printf ("CMOS clock unchanged.\n"); + /* Save data for next 'adjustit' call */ + if (adjustit) + { + FILE *adj; + if ((adj = fopen (ADJPATH, "w")) == NULL) + { + perror (ADJPATH); + exit(EXIT_FAILURE); + } + (void) fprintf (adj, "%f %d %f\n", factor, (int) systime, not_adjusted); + (void) fclose (adj); + } + exit(EXIT_SUCCESS); +} + +/* Stolen from linux/arch/i386/kernel/time.c. */ +/* Converts Gregorian date to seconds since 1970-01-01 00:00:00. + * Assumes input in normal date format, i.e. 1980-12-31 23:59:59 + * => year=1980, mon=12, day=31, hour=23, min=59, sec=59. + * + * [For the Julian calendar (which was used in Russia before 1917, + * Britain & colonies before 1752, anywhere else before 1582, + * and is still in use by some communities) leave out the + * -year/100+year/400 terms, and add 10.] + * + * This algorithm was first published by Gauss (I think). + * + * WARNING: this function will overflow on 2106-02-07 06:28:16 on + * machines were long is 32-bit! (However, as time_t is signed, we + * will already get problems at other places on 2038-01-19 03:14:08) + */ +time_t mkgmtime(struct tm *tm) +{ + int mon = tm->tm_mon + 1; + int year = tm->tm_year + 1900; + + if (0 >= (int) (mon -= 2)) { /* 1..12 -> 11,12,1..10 */ + mon += 12; /* Puts Feb last since it has leap day */ + year -= 1; + } + return ((( + (unsigned long)(year/4 - year/100 + year/400 + 367*mon/12) + + tm->tm_mday + year*365 - 719499 + )*24 + tm->tm_hour /* now have hours */ + )*60 + tm->tm_min /* now have minutes */ + )*60 + tm->tm_sec; /* finally seconds */ +} diff --git a/hwclock/clock.h b/hwclock/clock.h new file mode 100644 index 000000000..91d3df801 --- /dev/null +++ b/hwclock/clock.h @@ -0,0 +1,35 @@ +#include <stdio.h> +#include <stdlib.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 int epoch_option; +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/hwclock/cmos.c b/hwclock/cmos.c new file mode 100644 index 000000000..4fc82d798 --- /dev/null +++ b/hwclock/cmos.c @@ -0,0 +1,612 @@ +/* + * 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 (epoch_option != -1) { + cmos_epoch = epoch_option; + return; + } + + 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; + } + + /* The kernel source today says: read the year. If it is + in 11-43 then the epoch is 1980 (this covers 1991-2023). + Otherwise, if it is less than 96 then the epoch is 1952 + (this covers 1952-1962 and 1996-2047). Otherwise, the epoch + is 1900 (this covers 1996-1999, or rather 1996-2155). */ + + + /* See whether we are dealing with SRM or MILO, as they have + different "epoch" ideas. */ + if (is_in_cpuinfo("system serial number", "MILO")) { + ARCconsole = 1; + if (debug) printf (_("booted from MILO\n")); + } + + /* See whether we are dealing with a RUFFIAN aka Alpha PC-164 UX (or BX), + as they have REALLY different TOY (TimeOfYear) format: BCD, and not + an ARC-style epoch. + BCD is detected dynamically, but we must NOT adjust like ARC. */ + if (ARCconsole && is_in_cpuinfo("system type", "Ruffian")) { + ARCconsole = 0; + if (debug) printf (_("Ruffian BCD clock\n")); + } + + if (ARCconsole) + cmos_epoch = 1980; +} + +void +set_cmos_access(int Jensen, int funky_toy) { + + /* See whether we're dealing with a Jensen---it has a weird I/O + system. DEC was just learning how to build Alpha PCs. */ + if (Jensen || is_in_cpuinfo("system type", "Jensen")) { + use_dev_port = 1; + clock_ctl_addr = 0x170; + clock_data_addr = 0x171; + if (debug) printf (_("clockport adjusted to 0x%x\n"), clock_ctl_addr); + } + + /* see whether we are dealing with PC164/LX164/SX164, as they have a TOY + that must be accessed differently to work correctly. */ + /* Nautilus stuff reported by Neoklis Kyriazis */ + if (funky_toy || + is_in_cpuinfo("system variation", "PC164") || + is_in_cpuinfo("system variation", "LX164") || + is_in_cpuinfo("system variation", "SX164") || + is_in_cpuinfo("system type", "Nautilus")) { + funkyTOY = 1; + if (debug) printf (_("funky TOY!\n")); + } +} +#endif + + + + +#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(void) { + return +#ifdef __alpha__ + /* poll bit 4 (UF) of Control Register C */ + funkyTOY ? (hclock_read(12) & 0x10) : +#endif + /* poll bit 7 (UIP) of Control Register A */ + (hclock_read(10) & 0x80); +} + + +static int +synchronize_to_clock_tick_cmos(void) { + int i; + + /* Wait for rise. Should be within a second, but in case something + weird happens, we have a limit on this loop to reduce the impact + of this failure. + */ + for (i = 0; !cmos_clock_busy(); i++) + if (i >= 10000000) + return 1; + + /* Wait for fall. Should be within 2.228 ms. */ + for (i = 0; cmos_clock_busy(); i++) + if (i >= 1000000) + return 1; + return 0; +} + + + +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 lvl); + 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/hwclock/hwclock.8 b/hwclock/hwclock.8 new file mode 100644 index 000000000..7e4106d45 --- /dev/null +++ b/hwclock/hwclock.8 @@ -0,0 +1,595 @@ +.TH HWCLOCK 8 "02 March 1998" +.SH NAME +hwclock \- query and set the hardware clock (RTC) +.SH SYNOPSIS +.BR "hwclock \-r" " or " "hwclock \-\-show" +.br +.BR "hwclock \-w" " or " "hwclock \-\-systohc" +.br +.BR "hwclock \-s" " or " "hwclock \-\-hctosys" +.br +.BR "hwclock \-a" " or " "hwclock \-\-adjust" +.br +.BR "hwclock \-v" " or " "hwclock \-\-version" +.br +.B "hwclock \-\-set \-\-date=newdate" +.br +.B "hwclock \-\-getepoch" +.br +.B "hwclock \-\-setepoch \-\-epoch=year" +.PP +other options: +.PP +.B "[\-u|\-\-utc] \-\-localtime \-\-directisa \-\-test \-\-debug" +.PP +and arcane options for DEC Alpha: +.PP +.B "[\-A|\-\-arc] [\-J|\-\-jensen] [\-S|\-\-srm] [\-F|\-\-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", while \-h asks for a help message. + +.SH DESCRIPTION +.B hwclock +is a tool for accessing the Hardware Clock. You can display the +current time, set the Hardware Clock to a specified time, set the +Hardware Clock to the System Time, and set the System Time from the +Hardware Clock. +.PP +You can also run +.B 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 +.B 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. +The obsolete tz_dsttime field of the kernel's timezone value is set +to DST_NONE. (For details on what this field used to mean, see +.BR settimeofday (2).) + +This is a good option to use in one of the system startup scripts. +.TP +.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 +.B 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 +.BR 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 +.B \-\-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 +.BR hwclock . + +If you specify the wrong one of these options (or specify neither and +take a wrong default), both setting and querying of the Hardware Clock +will be messed up. + +If you specify neither +.B \-\-utc +nor +.B \-\-localtime +, the default is whichever was specified the last time +.B 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 +.BR hwclock 's +purposes). For other machines, it has no effect. This option tells +.B hwclock +to use explicit I/O instructions to access the Hardware Clock. +Without this option, +.B hwclock +will try to use the /dev/rtc device (which it assumes to be driven by the +rtc device driver). If it is unable to open the device (for 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, +.B hwclock +cannot set the year after 1999 and cannot use the value of the clock as +the true time in the normal way. + +To compensate for this (without your getting a BIOS update, which would +definitely be preferable), always use +.B \-\-badyear +if you have one of these machines. When +.B hwclock +knows it's working with a brain-damaged clock, it ignores the year part of +the Hardware Clock value and instead tries to guess the year based on the +last calibrated date in the adjtime file, by assuming that that date is +within the past year. For this to work, you had better do a +.I hwclock \-\-set +or +.I hwclock \-\-systohc +at least once a year! + +Though +.B hwclock +ignores the year value when it reads the Hardware Clock, it sets the +year value when it sets the clock. It sets it to 1995, 1996, 1997, or +1998, whichever one has the same position in the leap year cycle as +the true year. That way, the Hardware Clock inserts leap days where +they belong. Again, if you let the Hardware Clock run for more than a +year without setting it, this scheme could be defeated and you could +end up losing a day. + +.B hwclock +warns you that you probably need +.B \-\-badyear +whenever it finds your Hardware Clock set to 1994 or 1995. + +.TP +.B \-\-srm +.TP +.B \-\-arc +.TP +.B \-\-jensen +.TP +.B \-\-funky\-toy +These options all tell +.B 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 +.B hwclock +should be able to determine by itself what it's +running on, at least when +.I /proc +is mounted. These options make it possible for +.B 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 +.B hwclock +work, contact the +.B 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 uses epoch 1980 in its hardware clock, as is commonly +the case for machines on ARC console (but Ruffians have epoch 1900). + +.B \-\-srm +means your machine uses epoch 1900 in its hardware clock, as is commonly +the case for machines on SRM console. + +.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 +.B hwclock. +.TP +.B \-\-debug +Display a lot of information about what +.B hwclock +is doing internally. Some of its function is complex and this output +can help you understand how the program works. + + +.SH NOTES + + +.SH Clocks in a Linux System +.PP +There are two main clocks in a Linux system: +.PP +.B The Hardware Clock: +This is a clock that runs independently of any control program running +in the CPU and even when the machine is powered off. + +On an ISA system, this clock is specified as part of the ISA standard. +The control program can read or set this clock to a whole second, but +the control program can also detect the edges of the 1 second clock +ticks, so the clock actually has virtually infinite precision. +.PP +This clock is commonly called the hardware clock, the real time clock, +the RTC, the BIOS clock, and the CMOS clock. Hardware Clock, in its +capitalized form, was coined for use by +.B hwclock +because all of the other names are inappropriate to the point of being +misleading. +.PP +.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 +.B 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 +The timezone value actually consists of two parts: 1) a field +tz_minuteswest indicating how many minutes local time (not adjusted +for DST) lags behind UTC, and 2) a field tz_dsttime indicating +the type of Daylight Savings Time (DST) convention that is in effect +in the locality at the present time. +This second field is not used under Linux and is always zero. +(See also +.BR settimeofday (2).) + +.SH How hwclock Accesses the Hardware Clock +.PP +.B hwclock +Uses many different ways to get and set Hardware Clock values. +The most normal way is to do I/O to the device special file /dev/rtc, +which is presumed to be driven by the rtc device driver. However, +this method is not always available. For one thing, the rtc driver is +a relatively recent addition to Linux. Older systems don't have it. +Also, though there are versions of the rtc driver that work on DEC +Alphas, there appear to be plenty of Alphas on which the rtc driver +does not work (a common symptom is hwclock hanging). +.PP +On older systems, the method of accessing the Hardware Clock depends on +the system hardware. +.PP +On an ISA system, +.B hwclock +can directly access the "CMOS memory" registers that +constitute the clock, by doing I/O to Ports 0x70 and 0x71. It does +this with actual I/O instructions and consequently can only do it if +running with superuser effective userid. (In the case of a Jensen +Alpha, there is no way for +.B hwclock +to execute those I/O instructions, and so it uses instead the +/dev/port device special file, which provides almost as low-level an +interface to the I/O subsystem). + +This is a really poor method of accessing the clock, for all the +reasons that user space programs are generally not supposed to do +direct I/O and disable interrupts. Hwclock provides it because it is +the only method available on ISA and Alpha systems which don't have +working rtc device drivers available. + +.PP +On an m68k system, +.B hwclock +can access the clock via the console driver, via the device special +file /dev/tty1. +.PP +.B hwclock +tries to use /dev/rtc. If it is compiled for a kernel that doesn't have +that function or it is unable to open /dev/rtc, +.B hwclock +will fall back to another method, if available. On an ISA or Alpha +machine, you can force +.B hwclock +to use the direct manipulation of the CMOS registers without even trying +.I /dev/rtc +by specifying the \-\-directisa option. + + +.SH The Adjust Function +.PP +The Hardware Clock is usually not very accurate. However, much of its +inaccuracy is completely predictable - it gains or loses the same amount +of time every day. This is called systematic drift. +.BR hwclock 's +"adjust" function lets you make systematic corrections to correct the +systematic drift. +.PP +It works like this: +.B hwclock +keeps a file, +.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. +.B Hwclock +creates the adjtime file and records in it the current time as the +last time the clock was calibrated. +5 days later, the clock has gained 10 seconds, so you issue another +.I hwclock \-\-set +command to set it back 10 seconds. +.B Hwclock +updates the adjtime file to show the current time as the last time the +clock was calibrated, and records 2 seconds per day as the systematic +drift rate. 24 hours go by, and then you issue a +.I hwclock \-\-adjust +command. +.B Hwclock +consults the adjtime file and sees that the clock gains 2 seconds per +day when left alone and that it has been left alone for exactly one +day. So it subtracts 2 seconds from the Hardware Clock. It then +records the current time as the last time the clock was adjusted. +Another 24 hours goes by and you issue another +.I hwclock \-\-adjust. +.B Hwclock +does the same thing: subtracts 2 seconds and updates the adjtime file +with the current time as the last time the clock was adjusted. +.PP +Every time you calibrate (set) the clock (using +.I \-\-set +or +.I \-\-systohc +), +.B hwclock +recalculates the systematic drift rate based on how long it has been +since the last calibration, how long it has been since the last +adjustment, what drift rate was assumed in any intervening +adjustments, and the amount by which the clock is presently off. +.PP +A small amount of error creeps in any time +.B hwclock +sets the clock, so it refrains from making an adjustment that would be +less than 1 second. Later on, when you request an adjustment again, +the accumulated drift will be more than a second and +.B hwclock +will do the adjustment then. +.PP +It is good to do a +.I hwclock \-\-adjust +just before the +.I hwclock \-\-hctosys +at system startup time, and maybe periodically while the system is +running via cron. +.PP +The adjtime file, while named for its historical purpose of controlling +adjustments only, actually contains other information for use by hwclock +in remembering information from one invocation to the next. +.PP +The format of the adjtime file is, in ASCII: +.PP +Line 1: 3 numbers, separated by blanks: 1) systematic drift rate in +seconds per day, floating point decimal; 2) Resulting number of +seconds since 1969 UTC of most recent adjustment or calibration, +decimal integer; 3) zero (for compatibility with +.BR clock (8)) +as a decimal integer. +.PP +Line 2: 1 number: Resulting number of seconds since 1969 UTC of most +recent calibration. Zero if there has been no calibration yet or it +is known that any previous calibration is moot (for example, because +the Hardware Clock has been found, since that calibration, not to +contain a valid time). This is a decimal integer. +.PP +Line 3: "UTC" or "LOCAL". Tells whether the Hardware Clock is set to +Coordinated Universal Time or local time. You can always override this +value with options on the +.B hwclock +command line. +.PP +You can use an adjtime file that was previously used with the +.BR clock (8) +program with +.B hwclock. + + +.SH "Automatic Hardware Clock Synchronization By the Kernel" + +You should be aware of another way that the Hardware Clock is kept +synchronized in some systems. The Linux kernel has a mode wherein it +copies the System Time to the Hardware Clock every 11 minutes. +This is a good mode to use when you are using something sophisticated +like ntp to keep your System Time synchronized. (ntp is a way to keep +your System Time synchronized either to a time server somewhere on the +network or to a radio clock hooked up to your system. See RFC 1305). + +This mode (we'll call it "11 minute mode") is off until something +turns it on. The ntp daemon xntpd is one thing that turns it on. You +can turn it off by running anything, including +.IR "hwclock \-\-hctosys" , +that sets the System Time the old fashioned way. + +To see if it is on or +off, use the command +.I adjtimex \-\-print +and look at the value of "status". If the "64" bit of this number +(expressed in binary) equal to 0, 11 minute mode is on. Otherwise, it +is off. + +If your system runs with 11 minute mode on, don't use +.I hwclock \-\-adjust +or +.IR "hwclock \-\-hctosys" . +You'll just make a mess. It is acceptable to use a +.I hwclock \-\-hctosys +at startup time to get a reasonable System Time until your system is +able to set the System Time from the external source and start 11 +minute mode. + + +.SH ISA Hardware Clock Century value + +There is some sort of standard that defines CMOS memory Byte 50 on an ISA +machine as an indicator of what century it is. +.B hwclock +does not use or set that byte because there are some machines that +don't define the byte that way, and it really isn't necessary anyway, +since the year-of-century does a good job of implying which century it +is. + +If you have a bona fide use for a CMOS century byte, contact the +.B hwclock +maintainer; an option may be appropriate. + +Note that this section is only relevant when you are using the "direct +ISA" method of accessing the Hardware Clock. + + + +.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/hwclock/hwclock.c b/hwclock/hwclock.c new file mode 100644 index 000000000..3a3a94e1a --- /dev/null +++ b/hwclock/hwclock.c @@ -0,0 +1,1448 @@ +/* + * hwclock.c + * + * clock.c was written by Charles Hedrick, hedrick@cs.rutgers.edu, Apr 1992 + * Modified for clock adjustments - Rob Hooft <hooft@chem.ruu.nl>, Nov 1992 + * Improvements by Harald Koenig <koenig@nova.tat.physik.uni-tuebingen.de> + * and Alan Modra <alan@spri.levels.unisa.edu.au>. + * + * Major rewrite by Bryan Henderson <bryanh@giraffe-data.com>, 96.09.19. + * The new program is called hwclock. New features: + * - You can set the hardware clock without also modifying the system clock. + * - You can read and set the clock with finer than 1 second precision. + * - When you set the clock, hwclock automatically refigures the drift + * rate, based on how far off the clock was before you set it. + * + * Reshuffled things, added sparc code, and re-added alpha stuff + * by David Mosberger <davidm@azstarnet.com> + * and Jay Estabrook <jestabro@amt.tay1.dec.com> + * and Martin Ostermann <ost@coments.rwth-aachen.de>, aeb@cwi.nl, 990212. + * + * Fix for Award 2094 bug, Dave Coffin (dcoffin@shore.net) 11/12/98 + * Change of local time handling, Stefan Ring <e9725446@stud3.tuwien.ac.at> + * + * Distributed under GPL + */ + +/* + * 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 <stdarg.h> + +#include "shhopt.h" +#include "clock.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; + /* line 1 */ + float drift_factor; + time_t last_adj_time; + float not_adjusted; + /* line 2 */ + time_t last_calib_time; + /* The most recent time that we set the clock from an external + authority (as opposed to just doing a drift adjustment) */ + /* line 3 */ + enum a_local_utc {LOCAL, UTC, UNKNOWN} local_utc; + /* To which time zone, local or UTC, we most recently set the + hardware clock. */ +}; + +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. */ + +int epoch_option = -1; + /* User-specified epoch, used when rtc fails to return epoch. */ + +/* + * Almost all Award BIOS's made between 04/26/94 and 05/31/95 + * have a nasty bug limiting the RTC year byte to the range 94-99. + * Any year between 2000 and 2093 gets changed to 2094, every time + * you start the system. + * With the --badyear option, we write the date to file and hope + * that the file is updated at least once a year. + * I recommend putting this command "hwclock --badyear" in the monthly + * crontab, just to be safe. -- Dave Coffin 11/12/98 + */ +static void +write_date_to_file (struct tm *tm) { + FILE *fp; + + if ((fp = fopen(LASTDATE,"w"))) { + fprintf(fp,"%02d.%02d.%04d\n", tm->tm_mday, tm->tm_mon+1, + tm->tm_year+1900); + fclose(fp); + } else + perror(LASTDATE); +} + +static 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 bool +hw_clock_is_utc(const bool utc, const bool local_opt, + const struct adjtime adjtime) { + bool ret; + + if (utc) + ret = TRUE; /* --utc explicitly given on command line */ + else if (local_opt) + ret = FALSE; /* --localtime explicitly given */ + else + /* get info from adjtime file - default is local */ + ret = (adjtime.local_utc == UTC); + if (debug) + printf(_("Assuming hardware clock is kept in %s time.\n"), + ret ? _("UTC") : _("local")); + return ret; +} + + + +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; + adjtime_p->local_utc = UNKNOWN; + + *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 */ + char line3[81]; /* String: third 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); + line3[0] = '\0'; /* In case fgets fails */ + fgets(line3, sizeof(line3), 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); + + if (!strcmp(line3, "UTC\n")) + adjtime_p->local_utc = UTC; + else if (!strcmp(line3, "LOCAL\n")) + adjtime_p->local_utc = LOCAL; + else { + adjtime_p->local_utc = UNKNOWN; + if (line3[0]) { + fprintf(stderr, _("%s: Warning: unrecognized third line in adjtime file\n"), + MYNAME); + fprintf(stderr, _("(Expected: `UTC' or `LOCAL' or nothing.)\n")); + } + } + + *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); + printf(_("Hardware clock is on %s time\n"), + (adjtime_p->local_utc == LOCAL) ? _("local") : + (adjtime_p->local_utc == UTC) ? _("UTC") : _("unknown")); + } + } +} + + +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 */ + if (universal) { + /* Set timezone to UTC */ + setenv("TZ", "", TRUE); + /* Note: tzset() gets called implicitly by the time code, but only the + first time. When changing the environment variable, better call + tzset() explicitly. + */ + tzset(); + } + mktime_result = mktime(&tm); + if (mktime_result == -1) { + /* This apparently (not specified in mktime() documentation) means + the 'tm' structure does not contain valid values (however, not + containing valid values does _not_ imply mktime() returns -1). + */ + *valid_p = FALSE; + *systime_p = 0; + if (debug) + printf(_("Invalid values in hardware clock: " + "%4d/%.2d/%.2d %.2d:%.2d:%.2d\n"), + tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec); + } else { + *valid_p = TRUE; + *systime_p = mktime_result; + if (debug) + printf(_("Hw clock time : %4d/%.2d/%.2d %.2d:%.2d:%.2d = " + "%ld seconds since 1969\n"), + tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec, (long) *systime_p); + } + /* now put back the original zone. */ + if (zone) setenv("TZ", zone, TRUE); + else unsetenv("TZ"); + tzset(); +} + + +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: %4d/%.2d/%.2d %02d:%02d:%02d\n"), + tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec); + mktime_tz(tm, universal, valid_p, systime_p); +} + + +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 { + struct tm *lt; + char *format = "%c"; + char ctime_now[200]; + + lt = localtime(&systime); + strftime(ctime_now, sizeof(ctime_now), format, lt); + printf(_("%s %.6f seconds\n"), ctime_now, -(sync_duration)); + } +} + + + +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: 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; + + 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; + struct tm *broken; + int minuteswest; + int rc; + + tv.tv_sec = newtime; + tv.tv_usec = 0; + + broken = localtime(&newtime); +#ifdef HAVE_tm_gmtoff + minuteswest = -broken->tm_gmtoff/60; /* GNU extension */ +#else + minuteswest = timezone/60; + if (broken->tm_isdst) + minuteswest -= 60; +#endif + + if (debug) { + printf( _("Calling settimeofday:\n") ); + printf( _("\ttv.tv_sec = %ld, tv.tv_usec = %ld\n"), + (long) tv.tv_sec, (long) tv.tv_usec ); + printf( _("\ttz.tz_minuteswest = %d\n"), minuteswest); + } + if (testing) { + printf(_("Not setting system clock because running in test mode.\n")); + retcode = 0; + } else { + const struct timezone tz = { minuteswest, 0 }; + + rc = settimeofday(&tv, &tz); + if (rc != 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[412]; /* Stuff to write to disk file */ + + if (adjtime.dirty) { + /* snprintf is not always available, but this is safe + as long as libc does not use more than 100 positions for %ld or %f */ + sprintf(newfile, "%f %ld %f\n%ld\n%s\n", + adjtime.drift_factor, + (long) adjtime.last_adj_time, + adjtime.not_adjusted, + (long) adjtime.last_calib_time, + (adjtime.local_utc == UTC) ? "UTC" : "LOCAL"); + + if (testing) { + printf(_("Not updating adjtime file because of testing mode.\n")); + printf(_("Would have written the following to %s:\n%s"), + 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 (fputs(newfile, adjfile) < 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 utc, const bool local_opt, + 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 || (!utc && !local_opt)) + 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 { + const bool universal = hw_clock_is_utc(utc, local_opt, adjtime); + + if ((set || systohc || adjust) && + (adjtime.local_utc == UTC) != universal) { + adjtime.local_utc = universal ? UTC : LOCAL; + adjtime.dirty = TRUE; + } + + 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 +} + +#if __ia64__ +#define RTC_DEV "/dev/efirtc" +#else +#define RTC_DEV "/dev/rtc" +#endif + +/* + usage - Output (error and) usage information + + This function is called both directly from main to show usage + information and as fatal function from shhopt if some argument is + not understood. In case of normal usage info FMT should be NULL. + In that case the info is printed to stdout. If FMT is given + usage will act like fprintf( stderr, fmt, ... ), show a usage + information and terminate the program afterwards. +*/ +static void +usage( const char *fmt, ... ) { + FILE *usageto; + va_list ap; + + usageto = fmt ? stderr : stdout; + + fprintf( usageto, _( + "hwclock - query and set the hardware clock (RTC)\n\n" + "Usage: hwclock [function] [options...]\n\n" + "Functions:\n" + " --help show this help\n" + " --show read hardware clock and print result\n" + " --set set the rtc to the time given with --date\n" + " --hctosys set the system time from the hardware clock\n" + " --systohc set the hardware clock to the current system time\n" + " --adjust adjust the rtc to account for systematic drift since \n" + " the clock was last set or adjusted\n" + " --getepoch print out the kernel's hardware clock epoch value\n" + " --setepoch set the kernel's hardware clock epoch value to the \n" + " value given with --epoch\n" + " --version print out the version of hwclock to stdout\n" + "\nOptions: \n" + " --utc the hardware clock is kept in coordinated universal time\n" + " --localtime the hardware clock is kept in local time\n" + " --directisa access the ISA bus directly instead of %s\n" + " --badyear ignore rtc's year because the bios is broken\n" + " --date specifies the time to which to set the hardware clock\n" + " --epoch=year specifies the year which is the beginning of the \n" + " hardware clock's epoch value\n" + ),RTC_DEV); +#ifdef __alpha__ + fprintf( usageto, _( + " --jensen, --arc, --srm, --funky-toy\n" + " tell hwclock the type of alpha you have (see hwclock(8))\n" + ) ); +#endif + + + fflush(stdout); + if( fmt ) { + usageto = stderr; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } + + exit( fmt ? 99 : 0 ); +} + +int +main(int argc, char **argv) { + + struct timeval startup_time; + /* The time we started up, in seconds into the epoch, including fractions. + */ + time_t set_time; /* 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 help, show, set, systohc, hctosys, adjust, getepoch, setepoch, version; + bool ARCconsole, utc, testing, directisa, Jensen, SRM, funky_toy; + bool local_opt; + char *date_opt; + + const optStruct option_def[] = { + { 'h', (char *) "help", OPT_FLAG, &help, 0 }, + { '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 }, + { 'V', (char *) "version", OPT_FLAG, &version, 0 }, + { 0, (char *) "date", OPT_STRING, &date_opt, 0 }, + { 0, (char *) "epoch", OPT_UINT, &epoch_option,0 }, + { 'u', (char *) "utc", OPT_FLAG, &utc, 0 }, + { 0, (char *) "localtime", OPT_FLAG, &local_opt, 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, ""); +#ifdef LC_NUMERIC + /* We need LC_CTYPE and LC_TIME and LC_MESSAGES, but must avoid + LC_NUMERIC since it gives problems when we write to /etc/adjtime. + - gqueri@mail.dotcom.fr */ + setlocale(LC_NUMERIC, "C"); +#endif + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + + /* set option defaults */ + help = show = set = systohc = hctosys = adjust = getepoch = setepoch = + version = utc = local_opt = ARCconsole = SRM = funky_toy = + directisa = badyear = Jensen = testing = debug = FALSE; + date_opt = NULL; + + 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, utc, local_opt, version, + testing, debug, set, date_opt, getepoch, setepoch, epoch_option + */ + /* 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) { + usage( _("%s takes no non-option arguments. " + "You supplied %d.\n"), + MYNAME, argc_parse - 1); + } + + if (help) + usage( NULL ); + + 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); + } + + if (utc && local_opt) { + fprintf(stderr, _("%s: The --utc and --localtime options are mutually " + "exclusive. You specified both.\n"), MYNAME); + 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 (hctosys) { + fprintf(stderr, + _("Sorry, only the superuser can change the System 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_option, testing); + } else { + if (debug) + printf(MYNAME " " VERSION "/%s\n",util_linux_version); + determine_clock_access_method(directisa); + if (!ur) { + fprintf(stderr, + _("Cannot access the Hardware Clock via any known method.\n")); + if (!debug) + fprintf(stderr, + _("Use the --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, utc, local_opt, testing, &rc); + } + } + exit(retcode); +} + +/* A single routine for greater uniformity */ +void +outsyserr(char *msg, ...) { + va_list args; + int errsv = errno; + + fprintf(stderr, "%s: ", progname); + va_start(args, msg); + vfprintf(stderr, msg, args); + va_end(args); + fprintf(stderr, ", errno=%d: %s.\n", + errsv, strerror(errsv)); +} + +/**************************************************************************** + + 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/hwclock/kd.c b/hwclock/kd.c new file mode 100644 index 000000000..fd4fdd5a1 --- /dev/null +++ b/hwclock/kd.c @@ -0,0 +1,167 @@ +/* kd.c - KDGHWCLK stuff, possibly m68k only */ +#include <unistd.h> /* for close() */ +#include <fcntl.h> /* for O_RDONLY */ +#include <sys/ioctl.h> + +#include "../defines.h" /* for HAVE_nanosleep */ +#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 { + /* Added by Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de> */ + /* "The culprit is the fast loop with KDGHWCLK ioctls. It seems + the kernel gets confused by those on Amigas with A2000 RTCs + and simply hangs after some time. Inserting a nanosleep helps." */ + /* Christian T. Steigies: 1 instead of 1000000 is still sufficient + to keep the machine from freezing. */ + +#ifdef HAVE_nanosleep + struct timespec xsleep = { 0, 1 }; + nanosleep( &xsleep, NULL ); +#else + usleep(1); +#endif + + 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 { + /* probably KDGHWCLK exists on m68k only */ +#ifdef __m68k__ + outsyserr(_("Can't open /dev/tty1")); +#endif + } + return ret; +} diff --git a/hwclock/rtc.c b/hwclock/rtc.c new file mode 100644 index 000000000..2874bfa14 --- /dev/null +++ b/hwclock/rtc.c @@ -0,0 +1,396 @@ +/* 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. + */ + +/* ia64 uses /dev/efirtc (char 10,136) */ +#if __ia64__ +#define RTC_DEV "/dev/efirtc" +#else +#define RTC_DEV "/dev/rtc" +#endif + +/* 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 %s to read the time failed.\n"),RTC_DEV); + 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 %s to change\n"),RTC_DEV); + + 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(RTC_DEV,O_RDONLY); + if (rtc_fd == -1) { + outsyserr(_("open() of %s failed"),RTC_DEV); + 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(_("%s does not have interrupt functions. "),RTC_DEV); + ret = busywait_for_rtc_clock_tick(rtc_fd); + } else if (rc == 0) { + unsigned long dummy; + + /* this blocks until the next update interrupt */ + rc = read(rtc_fd, &dummy, sizeof(dummy)); + if (rc == -1) { + outsyserr(_("read() to %s to wait for clock tick failed"),RTC_DEV); + ret = 1; + } else { + ret = 0; + } + /* Turn off update interrupts */ + rc = ioctl(rtc_fd, RTC_UIE_OFF, 0); + if (rc == -1) + outsyserr(_("ioctl() to %s to turn off update interrupts " + "failed"),RTC_DEV); + } else { + outsyserr(_("ioctl() to %s to turn on update interrupts " + "failed unexpectedly"),RTC_DEV); + 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(RTC_DEV,O_RDONLY); + if (rtc_fd == -1) { + outsyserr(_("open() of %s failed"),RTC_DEV); + 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(RTC_DEV, O_RDONLY); + if (rtc_fd < 0) { + outsyserr(_("Unable to open %s"),RTC_DEV); + 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 %s to set the time failed.\n"),RTC_DEV); + 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(RTC_DEV, O_RDONLY); + if (rtc_fd > 0) { + close(rtc_fd); + return &rtc; + } + if (debug) + outsyserr(_("Open of %s failed"),RTC_DEV); + 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(RTC_DEV, 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 %s. This file does not exist on this system.\n"),RTC_DEV); + else + outsyserr(_("Unable to open %s"),RTC_DEV); + } + return 1; + } + + if (ioctl(rtc_fd, RTC_EPOCH_READ, epoch_p) == -1) { + if (!silent) + outsyserr(_("ioctl(RTC_EPOCH_READ) to %s failed"),RTC_DEV); + close(rtc_fd); + return 1; + } + + if (debug) + printf(_("we have read epoch %ld from %s " + "with RTC_EPOCH_READ ioctl.\n"), *epoch_p,RTC_DEV); + + 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(RTC_DEV, 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 %s. This file does not exist on this system.\n"),RTC_DEV); + else + outsyserr(_("Unable to open %s"),RTC_DEV); + return 1; + } + + if (debug) + printf(_("setting epoch to %ld " + "with RTC_EPOCH_SET ioctl to %s.\n"), epoch, RTC_DEV); + + if (ioctl(rtc_fd, RTC_EPOCH_SET, epoch) == -1) { + if (errno == EINVAL) + fprintf(stderr, _("The kernel device driver for %s " + "does not have the RTC_EPOCH_SET ioctl.\n"),RTC_DEV); + else + outsyserr(_("ioctl(RTC_EPOCH_SET) to %s failed"),RTC_DEV); + close(rtc_fd); + return 1; + } + + close(rtc_fd); + return 0; +} diff --git a/hwclock/shhopt-1.1.lsm b/hwclock/shhopt-1.1.lsm new file mode 100644 index 000000000..a61a26969 --- /dev/null +++ b/hwclock/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/hwclock/shhopt.c b/hwclock/shhopt.c new file mode 100644 index 000000000..ac3412829 --- /dev/null +++ b/hwclock/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. + * + */ +static 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/hwclock/shhopt.h b/hwclock/shhopt.h new file mode 100644 index 000000000..ca8501ef5 --- /dev/null +++ b/hwclock/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 |