📄 hwclock.c
字号:
/* * hwclock.c * * clock.c was written by Charles Hedrick, hedrick@cs.rutgers.edu, Apr 1992 * Modified for clock adjustments - Rob Hooft <hooft@chem.ruu.nl>, Nov 1992 * Improvements by Harald Koenig <koenig@nova.tat.physik.uni-tuebingen.de> * and Alan Modra <alan@spri.levels.unisa.edu.au>. * * Major rewrite by Bryan Henderson <bryanh@giraffe-data.com>, 96.09.19. * The new program is called hwclock. New features: * - You can set the hardware clock without also modifying the system clock. * - You can read and set the clock with finer than 1 second precision. * - When you set the clock, hwclock automatically refigures the drift * rate, based on how far off the clock was before you set it. * * Reshuffled things, added sparc code, and re-added alpha stuff * by David Mosberger <davidm@azstarnet.com> * and Jay Estabrook <jestabro@amt.tay1.dec.com> * and Martin Ostermann <ost@coments.rwth-aachen.de>, aeb@cwi.nl, 990212. * * Fix for Award 2094 bug, Dave Coffin (dcoffin@shore.net) 11/12/98 * Change of local time handling, Stefan Ring <e9725446@stud3.tuwien.ac.at> * Change of adjtime handling, James P. Rutledge <ao112@rgfn.epcc.edu>. * * Distributed under GPL *//* * 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 <getopt.h>#include <sysexits.h>#include "clock.h"#include "nls.h"#define MYNAME "hwclock"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 */ double drift_factor; time_t last_adj_time; double not_adjusted; /* line 2 */ time_t last_calib_time; /* The most recent time that we set the clock from an external authority (as opposed to just doing a drift adjustment) */ /* line 3 */ enum a_local_utc {LOCAL, UTC, UNKNOWN} local_utc; /* To which time zone, local or UTC, we most recently set the hardware clock. */};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 voidwrite_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 voidread_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 double 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 timevaltime_inc(struct timeval addend, double 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 boolhw_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 intread_adjtime(struct adjtime *adjtime_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 value 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; return 0; } adjfile = fopen(ADJPATH, "r"); /* open file for reading */ if (adjfile == NULL) { outsyserr("cannot open file " ADJPATH); return EX_OSFILE; } { char line1[81]; /* String: first line of adjtime file */ char line2[81]; /* String: second line of adjtime file */ char line3[81]; /* String: third line of adjtime file */ long timeval; 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; timeval = 0; sscanf(line1, "%lf %ld %lf", &adjtime_p->drift_factor, &timeval, &adjtime_p->not_adjusted); adjtime_p->last_adj_time = timeval; sscanf(line2, "%ld", &timeval); adjtime_p->last_calib_time = timeval; if (!strcmp(line3, "UTC\n")) adjtime_p->local_utc = UTC; else if (!strcmp(line3, "LOCAL\n")) adjtime_p->local_utc = LOCAL; else { adjtime_p->local_utc = UNKNOWN; if (line3[0]) { fprintf(stderr, _("%s: Warning: unrecognized third line in adjtime file\n"), MYNAME); fprintf(stderr, _("(Expected: `UTC' or `LOCAL' or nothing.)\n")); } } } adjtime_p->dirty = FALSE; if (debug) { printf(_("Last drift adjustment done at %ld seconds after 1969\n"), (long) adjtime_p->last_adj_time); printf(_("Last calibration done at %ld seconds after 1969\n"), (long) adjtime_p->last_calib_time); printf(_("Hardware clock is on %s time\n"), (adjtime_p->local_utc == LOCAL) ? _("local") : (adjtime_p->local_utc == UTC) ? _("UTC") : _("unknown")); } return 0;}static intsynchronize_to_clock_tick(void) {/*----------------------------------------------------------------------------- Wait until the falling edge of the Hardware Clock's update flag so that any time that is read from the clock immediately after we return will be exact. The clock only has 1 second precision, so it gives the exact time only once per second, right on the falling edge of the update flag. We wait (up to one second) either blocked waiting for an rtc device or in a CPU spin loop. The former is probably not very accurate. Return 0 if it worked, nonzero if it didn't.-----------------------------------------------------------------------------*/ int rc; if (debug) printf(_("Waiting for clock tick...\n")); rc = ur->synchronize_to_clock_tick(); if (debug) printf(_("...got clock tick\n")); return rc;}static voidmktime_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 = 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,
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -