summaryrefslogtreecommitdiffstats
path: root/sys-utils/clock.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys-utils/clock.c')
-rw-r--r--sys-utils/clock.c490
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, &not_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);
+}