diff options
author | Karel Zak <kzak@redhat.com> | 2006-12-07 00:25:37 +0100 |
---|---|---|
committer | Karel Zak <kzak@redhat.com> | 2006-12-07 00:25:37 +0100 |
commit | 5c36a0eb7cdb0360f9afd5d747c321f423b35984 (patch) | |
tree | 147599a77eaff2b5fbc0d389e89d2b51602326c0 /sys-utils/hwclock.c | |
parent | 2b6fc908bc368b540845a313c3b8a867c5ad9a42 (diff) | |
download | util-linux-5c36a0eb7cdb0360f9afd5d747c321f423b35984.tar.gz |
Imported from util-linux-2.9i tarball.
Diffstat (limited to 'sys-utils/hwclock.c')
-rw-r--r-- | sys-utils/hwclock.c | 362 |
1 files changed, 231 insertions, 131 deletions
diff --git a/sys-utils/hwclock.c b/sys-utils/hwclock.c index 643ccda5..02302da2 100644 --- a/sys-utils/hwclock.c +++ b/sys-utils/hwclock.c @@ -106,12 +106,14 @@ #include <time.h> #include <sys/time.h> #include <sys/stat.h> -#include <asm/io.h> +#ifdef __i386__ +#include <asm/io.h> /* for inb, outb */ +#endif #include <shhopt.h> #include "../version.h" #define MYNAME "hwclock" -#define VERSION "2.2" +#define VERSION "2.4" #define FLOOR(arg) ((arg >= 0 ? (int) arg : ((int) arg) - 1)); @@ -190,7 +192,6 @@ bool interrupts_enabled; need to be turned back on. */ - #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. Might need to make @@ -658,17 +659,27 @@ synchronize_to_clock_tick(enum clock_access_method clock_access, -static time_t -mktime_tz(struct tm tm, const bool universal) { +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). + 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 systime; /* our eventual return value */ + 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 @@ -686,23 +697,31 @@ mktime_tz(struct tm tm, const bool universal) { */ tzset(); } - systime = mktime(&tm); - if (systime == -1) { - /* We don't expect this to happen. Consider this a crash */ - fprintf(stderr, "mktime() failed unexpectedly (rc -1). Aborting.\n"); - exit(2); + mktime_result = mktime(&tm); + if (mktime_result == -1) { + /* This apparently (not specified in mktime() documentation) means + the 'tm' structure does not contain valid values (however, not + containing valid values does _not_ imply mktime() returns -1). + */ + *valid_p = FALSE; + *systime_p = 0; + if (debug) + printf("Invalid values in hardware clock: " + "%2d/%.2d/%.2d %.2d:%.2d:%.2d\n", + tm.tm_year, tm.tm_mon+1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec + ); + } else { + *valid_p = TRUE; + *systime_p = mktime_result; + if (debug) + printf("Hw clock time : %.2d:%.2d:%.2d = %d seconds since 1969\n", + tm.tm_hour, tm.tm_min, tm.tm_sec, (int) *systime_p); } - /* now put back the original zone. */ if (zone) setenv("TZ", zone, TRUE); else unsetenv("TZ"); tzset(); - - if (debug) - printf("Hw clock time : %.2d:%.2d:%.2d = %d seconds since 1969\n", - tm.tm_hour, tm.tm_min, tm.tm_sec, (int) systime); - - return(systime); } @@ -837,32 +856,28 @@ read_hardware_clock_isa(struct tm *tm) { tm->tm_mday = hclock_read_bcd(7); tm->tm_mon = hclock_read_bcd(8) - 1; tm->tm_year = hclock_read_bcd(9); - if (hclock_read_bcd(50) == 0) { - /* I suppose Linux could run on an old machine that doesn't implement - the Byte 50 century value, and that if it does, that machine puts - zero in Byte 50. If so, this could could be useful, in that it - makes values 70-99 -> 1970-1999 and 00-69 -> 2000-2069. - */ - if (hclock_read_bcd(9) >= 70) tm->tm_year = hclock_read_bcd(9); - else tm->tm_year = hclock_read_bcd(9) + 100; - } else { - tm->tm_year = hclock_read_bcd(50) * 100 + hclock_read_bcd(9) - 1900; - /* Note: Byte 50 contains centuries since A.D. Byte 9 contains - years since beginning of century. tm_year contains years - since 1900. At least we _assume_ that's what tm_year - contains. It is documented only as "year", and it could - conceivably be years since the beginning of the current - century. If so, this code won't work after 1999. - */ - } - /* Unless the clock changed while we were reading, consider this - a good clock read . + /* We don't use the century byte (Byte 50) of the Hardware Clock. + Here's why: On older machines, it isn't defined. In at least + one reported case, a machine puts some arbitrary value in that + byte. Furthermore, the Linux standard time data structure doesn't + allow for times beyond about 2037 and no Linux systems were + running before 1937. Therefore, all the century byte could tell + us is that the clock is wrong or this whole program is obsolete! + + So we just say if the year of century is less than 37, it's the + 21st century, otherwise it's the 20th. */ - if (tm->tm_sec == hclock_read_bcd (0)) got_time = TRUE; - /* Yes, in theory we could have been running for 60 seconds and - the above test wouldn't work! - */ + + if (hclock_read_bcd(9) >= 37) tm->tm_year = hclock_read_bcd(9); + else tm->tm_year = hclock_read_bcd(9) + 100; } + /* Unless the clock changed while we were reading, consider this + a good clock read . + */ + if (tm->tm_sec == hclock_read_bcd (0)) got_time = TRUE; + /* Yes, in theory we could have been running for 60 seconds and + the above test wouldn't work! + */ } tm->tm_isdst = -1; /* don't know whether it's daylight */ } @@ -870,21 +885,25 @@ read_hardware_clock_isa(struct tm *tm) { static void -read_hardware_clock(const enum clock_access_method method, struct tm *tm){ +read_hardware_clock(const enum clock_access_method method, + 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; + + switch (method) { case ISA: - read_hardware_clock_isa(tm); + read_hardware_clock_isa(&tm); break; case RTC_IOCTL: - read_hardware_clock_rtc_ioctl(tm); + read_hardware_clock_rtc_ioctl(&tm); break; case KD: - read_hardware_clock_kd(tm); + read_hardware_clock_kd(&tm); break; default: fprintf(stderr, @@ -893,7 +912,8 @@ read_hardware_clock(const enum clock_access_method method, struct tm *tm){ } if (debug) printf ("Time read from Hardware Clock: %02d:%02d:%02d\n", - tm->tm_hour, tm->tm_min, tm->tm_sec); + tm.tm_hour, tm.tm_min, tm.tm_sec); + mktime_tz(tm, universal, valid_p, systime_p); } @@ -994,14 +1014,13 @@ set_hardware_clock_isa(const struct tm new_broken_time, an ISA Hardware Clock. ----------------------------------------------------------------------------*/ unsigned char save_control, save_freq_select; -#ifdef __i386__ - const bool interrupts_were_enabled = interrupts_enabled; -#endif if (testing) printf("Not setting Hardware Clock because running in test mode.\n"); else { #ifdef __i386__ + const bool interrupts_were_enabled = interrupts_enabled; + __asm__ volatile ("cli"); interrupts_enabled = FALSE; #endif @@ -1220,21 +1239,30 @@ set_epoch(unsigned long epoch, const bool testing, int *retcode_p) { static void -display_time(const time_t systime, const float sync_duration) { +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". -----------------------------------------------------------------------------*/ - char *ctime_now; /* Address of static storage containing time string */ - - /* For some strange reason, ctime() is designed to include a newline - character at the end. We have to remove that. - */ - ctime_now = ctime(&systime); /* Compute display value for time */ - *(ctime_now+strlen(ctime_now)-1) = '\0'; /* Cut off trailing newline */ + if (!hclock_valid) + fprintf(stderr, "The Hardware Clock registers contain values that are " + "either invalid (e.g. 50th day of month) or beyond the range " + "we can handle (e.g. Year 2095).\n"); + else { + char *ctime_now; /* Address of static storage containing time string */ - printf("%s %.6f seconds\n", ctime_now, -(sync_duration)); + /* For some strange reason, ctime() is designed to include a newline + character at the end. We have to remove that. + */ + ctime_now = ctime(&systime); /* Compute display value for time */ + *(ctime_now+strlen(ctime_now)-1) = '\0'; /* Cut off trailing newline */ + + printf("%s %.6f seconds\n", ctime_now, -(sync_duration)); + } } @@ -1322,34 +1350,69 @@ interpret_date_string(const char *date_opt, time_t * const time_p) { static int -set_system_clock(const time_t newtime, const bool testing) { +set_system_clock(const bool hclock_valid, const time_t newtime, + const bool testing) { +/*---------------------------------------------------------------------------- + Set the System Clock to time 'newtime'. + + Also set the kernel time zone value to the value indicated by the + TZ environment variable and/or /usr/lib/zoneinfo/, interpreted as + tzset() would interpret them. Except: do not consider Daylight + Savings Time to be a separate component of the time zone. Include + any effect of DST in the basic timezone value and set the kernel + DST value to 0. - struct timeval tv; + EXCEPT: if hclock_valid is false, just issue an error message + saying there is no valid time in the Hardware Clock to which to set + the system time. + + If 'testing' is true, don't actually update anything -- just say we + would have. +-----------------------------------------------------------------------------*/ int retcode; /* our eventual return code */ - int rc; /* local return code */ - tv.tv_sec = newtime; - tv.tv_usec = 0; - - if (debug) { - printf( "Calling settimeofday:\n" ); - printf( "\ttv.tv_sec = %ld, tv.tv_usec = %ld\n", - (long) tv.tv_sec, (long) tv.tv_usec ); - } - if (testing) { - printf("Not setting system clock because running in test mode.\n"); - retcode = 0; + 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 { - rc = settimeofday(&tv, NULL); - if (rc != 0) { - if (errno == EPERM) - fprintf(stderr, "Must be superuser to set system clock.\n"); - else - fprintf(stderr, - "settimeofday() failed, errno=%d:%s\n", - errno, strerror(errno)); - retcode = 1; - } else retcode = 0; + struct timeval tv; + int rc; /* local return code */ + + tv.tv_sec = newtime; + tv.tv_usec = 0; + + tzset(); /* init timezone, daylight from TZ or ...zoneinfo/localtime */ + /* An undocumented function of tzset() is to set global variabales + 'timezone' and 'daylight' + */ + + if (debug) { + printf( "Calling settimeofday:\n" ); + printf( "\ttv.tv_sec = %ld, tv.tv_usec = %ld\n", + (long) tv.tv_sec, (long) tv.tv_usec ); + } + if (testing) { + printf("Not setting system clock because running in test mode.\n"); + retcode = 0; + } else { + /* For documentation of settimeofday(), in addition to its man page, + see kernel/time.c in the Linux source code. + */ + const struct timezone tz = { timezone/60 - 60*daylight, 0 }; + /* put daylight in minuteswest rather than dsttime, + since the latter is mostly ignored ... */ + rc = settimeofday(&tv, &tz); + if (rc != 0) { + if (errno == EPERM) + fprintf(stderr, "Must be superuser to set system clock.\n"); + else + fprintf(stderr, + "settimeofday() failed, errno=%d:%s\n", + errno, strerror(errno)); + retcode = 1; + } else retcode = 0; + } } return(retcode); } @@ -1358,7 +1421,7 @@ set_system_clock(const time_t newtime, const bool testing) { static void adjust_drift_factor(struct adjtime *adjtime_p, const time_t nowtime, - const time_t hclocktime ) { + 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 @@ -1372,8 +1435,20 @@ adjust_drift_factor(struct adjtime *adjtime_p, 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 ((hclocktime - adjtime_p->last_calib_time) >= 24 * 60 * 60) { + 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)) @@ -1387,18 +1462,15 @@ adjust_drift_factor(struct adjtime *adjtime_p, (int) (hclocktime - adjtime_p->last_calib_time), adjtime_p->drift_factor, factor_adjust ); - + adjtime_p->drift_factor += factor_adjust; - } else if (debug) - printf("Not adjusting drift factor because it has been less than a " - "day since the last calibration.\n"); - + } adjtime_p->last_calib_time = nowtime; adjtime_p->last_adj_time = nowtime; - + adjtime_p->not_adjusted = 0; - + adjtime_p->dirty = TRUE; } @@ -1507,7 +1579,8 @@ save_adjtime(const struct adjtime adjtime, const bool testing) { static void do_adjustment(struct adjtime *adjtime_p, - const time_t hclocktime, const struct timeval read_time, + const bool hclock_valid, const time_t hclocktime, + const struct timeval read_time, const enum clock_access_method clock_access, const bool universal, const bool testing) { /*--------------------------------------------------------------------------- @@ -1515,10 +1588,14 @@ do_adjustment(struct adjtime *adjtime_p, 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. - <hclocktime> is the current time set in the Hardware Clock. + <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 @@ -1534,29 +1611,34 @@ do_adjustment(struct adjtime *adjtime_p, frequently. ----------------------------------------------------------------------------*/ - int adjustment; + 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; + 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), - clock_access, 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"); + 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), + clock_access, 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"); + } } @@ -1659,14 +1741,6 @@ manipulate_clock(const bool show, const bool adjust, ----------------------------------------------------------------------------*/ struct adjtime adjtime; /* Contents of the adjtime file, or what they should be. */ - struct tm tm; - time_t hclocktime; - /* The time the hardware clock had just after we synchronized to its - next clock tick when we started up. - */ - struct timeval read_time; - /* The time at which we read the Hardware Clock */ - int rc; /* local return code */ bool no_auth; /* User lacks necessary authorization to access the clock */ @@ -1674,7 +1748,7 @@ manipulate_clock(const bool show, const bool adjust, rc = i386_iopl(3); if (rc != 0) { fprintf(stderr, MYNAME " is unable to get I/O port access. " - "I.e. iopl(2) returned nonzero return code %d.\n" + "I.e. iopl(3) returned nonzero return code %d.\n" "This is often because the program isn't running " "with superuser privilege, which it needs.\n", rc); @@ -1696,21 +1770,36 @@ manipulate_clock(const bool show, const bool adjust, synchronize_to_clock_tick(clock_access, retcode_p); /* this takes up to 1 second */ if (*retcode_p == 0) { - /* Get current time from Hardware Clock, in case we need it */ + 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(clock_access, &tm); - hclocktime = mktime_tz(tm, universal); + read_hardware_clock(clock_access, universal, &hclock_valid, + &hclocktime); if (show) { - display_time(hclocktime, time_diff(read_time, startup_time)); + 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, clock_access, universal, testing); - adjust_drift_factor(&adjtime, set_time, hclocktime); + adjust_drift_factor(&adjtime, set_time, hclock_valid, hclocktime); *retcode_p = 0; } else if (adjust) { - do_adjustment(&adjtime, hclocktime, read_time, clock_access, + do_adjustment(&adjtime, hclock_valid, hclocktime, + read_time, clock_access, universal, testing); *retcode_p = 0; } else if (systohc) { @@ -1726,9 +1815,10 @@ manipulate_clock(const bool show, const bool adjust, set_hardware_clock_exact((time_t) reftime.tv_sec, reftime, clock_access, universal, testing); *retcode_p = 0; - adjust_drift_factor(&adjtime, (time_t) reftime.tv_sec, hclocktime); + adjust_drift_factor(&adjtime, (time_t) reftime.tv_sec, hclock_valid, + hclocktime); } else if (hctosys) { - rc = set_system_clock(hclocktime, testing); + rc = set_system_clock(hclock_valid, hclocktime, testing); if (rc != 0) { printf("Unable to set system clock.\n"); *retcode_p = 1; @@ -1934,6 +2024,16 @@ main(int argc, char **argv, char **envp) { 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. |