diff options
Diffstat (limited to 'sys-utils/clock.c')
-rw-r--r-- | sys-utils/clock.c | 490 |
1 files changed, 490 insertions, 0 deletions
diff --git a/sys-utils/clock.c b/sys-utils/clock.c new file mode 100644 index 000000000..15020c260 --- /dev/null +++ b/sys-utils/clock.c @@ -0,0 +1,490 @@ +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> +#include <unistd.h> +#include <getopt.h> +#include <time.h> +#include <sys/time.h> +#include <string.h> + +#define USE_INLINE_ASM_IO + +#ifdef USE_INLINE_ASM_IO +#include <asm/io.h> +#endif + +/* 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 the CMOS port. + * + * I don't know what the CMOS clock will do in 2000, so this program + * probably won't work past the century boundary. + * + ********************* + * 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. + * + */ + +#define VERSION "1.4" + +/* Here the information for time adjustments is kept. */ +#define ADJPATH "/etc/adjtime" + + +/* 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; + +volatile void +usage () +{ + 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 (1); +} + +#ifndef USE_INLINE_ASM_IO +int cmos_fd; +#endif + +static inline unsigned char cmos_read(unsigned char reg) +{ + register unsigned char ret; + __asm__ volatile ("cli"); + outb (reg | 0x80, 0x70); + ret = inb (0x71); + __asm__ volatile ("sti"); + return ret; +} + +static inline void cmos_write(unsigned char reg, unsigned char val) +{ + outb (reg | 0x80, 0x70); + outb (val, 0x71); +} + +#ifndef outb +static inline void +outb (char val, unsigned short port) +{ +#ifdef USE_INLINE_ASM_IO + __asm__ volatile ("out%B0 %0,%1"::"a" (val), "d" (port)); +#else + lseek (cmos_fd, port, 0); + write (cmos_fd, &val, 1); +#endif +} +#endif + +#ifndef inb +static inline unsigned char +inb (unsigned short port) +{ + unsigned char ret; +#ifdef USE_INLINE_ASM_IO + __asm__ volatile ("in%B0 %1,%0":"=a" (ret):"d" (port)); +#else + lseek (cmos_fd, port, 0); + read (cmos_fd, &ret, 1); +#endif + return ret; +} +#endif + +void +cmos_init () +{ +#ifdef USE_INLINE_ASM_IO + if (iopl (3)) + { + fprintf(stderr,"clock: unable to get I/O port access\n"); + exit (1); + } +#else + cmos_fd = open ("/dev/port", 2); + if (cmos_fd < 0) + { + perror ("unable to open /dev/port read/write : "); + exit (1); + } + if (lseek (cmos_fd, 0x70, 0) < 0 || lseek (cmos_fd, 0x71, 0) < 0) + { + perror ("unable to seek port 0x70 in /dev/port : "); + exit (1); + } +#endif +} + +static inline int +cmos_read_bcd (int addr) +{ + int b; + b = cmos_read (addr); + return (b & 15) + (b >> 4) * 10; +} + +static inline void +cmos_write_bcd (int addr, int value) +{ + cmos_write (addr, ((value / 10) << 4) + value % 10); +} + +int +main (int argc, char **argv, char **envp) +{ + struct tm tm; + time_t systime; + time_t last_time; + char arg; + double factor; + double not_adjusted; + int adjustment = 0; + unsigned char save_control, save_freq_select; + + 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': + fprintf( stderr, "clock " VERSION "\n" ); + exit(0); + default: + usage (); + } + } + + if (readit + writeit + setit + adjustit > 1) + usage (); /* only allow one of these */ + + if (!(readit | writeit | setit | adjustit)) /* default to read */ + readit = 1; + + cmos_init (); + + if (adjustit) + { /* Read adjustment parameters first */ + FILE *adj; + if ((adj = fopen (ADJPATH, "r")) == NULL) + { + perror (ADJPATH); + exit (2); + } + if (fscanf (adj, "%lf %d %lf", &factor, &last_time, ¬_adjusted) < 0) + { + perror (ADJPATH); + exit (2); + } + fclose (adj); + if (debug) printf ("Last adjustment done at %d seconds after 1/1/1970\n", last_time); + } + + if (readit || setit || adjustit) + { + int i; + +/* read RTC exactly on falling edge of update flag */ +/* Wait for rise.... (may take upto 1 second) */ + + for (i = 0; i < 10000000; i++) + if (cmos_read (10) & 0x80) + break; + +/* Wait for fall.... (must try at least 2.228 ms) */ + + for (i = 0; i < 1000000; i++) + if (!(cmos_read (10) & 0x80)) + break; + +/* The purpose of the "do" loop is called "low-risk programming" */ +/* In theory it should never run more than once */ + do + { + tm.tm_sec = cmos_read_bcd (0); + tm.tm_min = cmos_read_bcd (2); + tm.tm_hour = cmos_read_bcd (4); + tm.tm_wday = cmos_read_bcd (6); + tm.tm_mday = cmos_read_bcd (7); + tm.tm_mon = cmos_read_bcd (8); + tm.tm_year = cmos_read_bcd (9); + } + while (tm.tm_sec != cmos_read_bcd (0)); + if (tm.tm_year < 70) + tm.tm_year += 100; /* 70..99 => 1970..1999, 0..69 => 2000..2069 */ + tm.tm_mon--; /* DOS uses 1 base */ + tm.tm_wday -= 3; /* DOS uses 3 - 9 for week days */ + tm.tm_isdst = -1; /* don't know whether it's daylight */ + if (debug) printf ("Cmos time : %d:%d:%d\n", tm.tm_hour, tm.tm_min, tm.tm_sec); + } + + if (readit || setit || adjustit) + { +/* + * mktime() assumes we're giving it local time. If the CMOS clock + * is in GMT, we have to set up TZ so mktime knows it. tzset() gets + * called implicitly by the time code, but only the first time. When + * changing the environment variable, better call tzset() explicitly. + */ + if (universal) + { + char *zone; + zone = (char *) getenv ("TZ"); /* save original time zone */ + (void) putenv ("TZ="); + tzset (); + systime = mktime (&tm); + /* now put back the original zone */ + if (zone) + { + + char *zonebuf; + zonebuf = malloc (strlen (zone) + 4); + strcpy (zonebuf, "TZ="); + strcpy (zonebuf+3, zone); + putenv (zonebuf); + free (zonebuf); + } + else + { /* wasn't one, so clear it */ + putenv ("TZ"); + } + tzset (); + } + else + { + systime = mktime (&tm); + } + if (debug) printf ("Number of seconds since 1/1/1970 is %d\n", systime); + } + + if (readit) + { + printf ("%s", ctime (&systime )); + } + + if (setit || adjustit) + { + struct timeval tv; + struct timezone tz; + +/* program is designed to run setuid, be secure! */ + + if (getuid () != 0) + { + fprintf (stderr, "Sorry, must be root to set or adjust time\n"); + exit (2); + } + + 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) { + printf ("Time since last adjustment is %d seconds\n", + (int) (systime - last_time)); + printf ("Adjusting time by %d seconds\n", + adjustment); + 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) + { + fprintf (stderr, + "Unable to set time -- probably you are not root\n"); + exit (1); + } + + if (debug) { + printf( "Called settimeofday:\n" ); + printf( "\ttv.tv_sec = %ld, tv.tv_usec = %ld\n", + tv.tv_sec, tv.tv_usec ); + printf( "\ttz.tz_minuteswest = %d, tz.tz_dsttime = %d\n", + tz.tz_minuteswest, tz.tz_dsttime ); + } +#endif + } + + if (writeit || (adjustit && adjustment != 0)) + { + struct tm *tmp; + systime = time (NULL); + if (universal) + tmp = gmtime (&systime); + else + tmp = localtime (&systime); + +#ifndef KEEP_OFF + __asm__ volatile ("cli"); + 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)); + + cmos_write_bcd (0, tmp->tm_sec); + cmos_write_bcd (2, tmp->tm_min); + cmos_write_bcd (4, tmp->tm_hour); + cmos_write_bcd (6, tmp->tm_wday + 3); + cmos_write_bcd (7, tmp->tm_mday); + cmos_write_bcd (8, tmp->tm_mon + 1); + cmos_write_bcd (9, tmp->tm_year); + + cmos_write (10, save_freq_select); + cmos_write (11, save_control); + __asm__ volatile ("sti"); +#endif + if (debug) printf ("Set to : %d:%d:%d\n", tmp->tm_hour, tmp->tm_min, tmp->tm_sec); + } + else + if (debug) printf ("CMOS clock unchanged.\n"); + /* Save data for next 'adjustit' call */ + if (adjustit) + { + FILE *adj; + if ((adj = fopen (ADJPATH, "w")) == NULL) + { + perror (ADJPATH); + exit (2); + } + fprintf (adj, "%f %d %f\n", factor, systime, not_adjusted); + fclose (adj); + } + exit (0); +} |