home *** CD-ROM | disk | FTP | other *** search
/ Chip 1995 March / CHIP3.mdf / slackwar / a / util / util-lin.10 / util-lin / util-linux-1.10 / clock.c < prev    next >
Encoding:
C/C++ Source or Header  |  1994-09-15  |  12.7 KB  |  478 lines

  1. #include <stdio.h>
  2. #include <errno.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. #include <getopt.h>
  6. #include <time.h>
  7. #include <sys/time.h>
  8. #include <string.h>
  9.  
  10. #define USE_INLINE_ASM_IO
  11.  
  12. #ifdef USE_INLINE_ASM_IO
  13. #include <asm/io.h>
  14. #endif
  15.  
  16. /* V1.0
  17.  * CMOS clock manipulation - Charles Hedrick, hedrick@cs.rutgers.edu, Apr 1992
  18.  * 
  19.  * clock [-u] -r  - read cmos clock
  20.  * clock [-u] -w  - write cmos clock from system time
  21.  * clock [-u] -s  - set system time from cmos clock
  22.  * clock [-u] -a  - set system time from cmos clock, adjust the time to
  23.  *                  correct for systematic error, and put it back to the cmos.
  24.  *  -u indicates cmos clock is kept in universal time
  25.  *
  26.  * The program is designed to run setuid, since we need to be able to
  27.  * write the CMOS port.
  28.  *
  29.  * I don't know what the CMOS clock will do in 2000, so this program
  30.  * probably won't work past the century boundary.
  31.  *
  32.  *********************
  33.  * V1.1
  34.  * Modified for clock adjustments - Rob Hooft, hooft@chem.ruu.nl, Nov 1992
  35.  * Also moved error messages to stderr. The program now uses getopt.
  36.  * Changed some exit codes. Made 'gcc 2.3 -Wall' happy.
  37.  *
  38.  * I think a small explanation of the adjustment routine should be given
  39.  * here. The problem with my machine is that its CMOS clock is 10 seconds 
  40.  * per day slow. With this version of clock.c, and my '/etc/rc.local' 
  41.  * reading '/etc/clock -au' instead of '/etc/clock -u -s', this error 
  42.  * is automatically corrected at every boot. 
  43.  *
  44.  * To do this job, the program reads and writes the file '/etc/adjtime' 
  45.  * to determine the correction, and to save its data. In this file are 
  46.  * three numbers: 
  47.  *
  48.  * 1) the correction in seconds per day (So if your clock runs 5 
  49.  *    seconds per day fast, the first number should read -5.0)
  50.  * 2) the number of seconds since 1/1/1970 the last time the program was
  51.  *    used.
  52.  * 3) the remaining part of a second which was leftover after the last 
  53.  *    adjustment
  54.  *
  55.  * Installation and use of this program:
  56.  *
  57.  * a) create a file '/etc/adjtime' containing as the first and only line:
  58.  *    '0.0 0 0.0'
  59.  * b) run 'clock -au' or 'clock -a', depending on whether your cmos is in
  60.  *    universal or local time. This updates the second number.
  61.  * c) set your system time using the 'date' command.
  62.  * d) update your cmos time using 'clock -wu' or 'clock -w'
  63.  * e) replace the first number in /etc/adjtime by your correction.
  64.  * f) put the command 'clock -au' or 'clock -a' in your '/etc/rc.local'
  65.  *
  66.  * If the adjustment doesn't work for you, try contacting me by E-mail.
  67.  *
  68.  ******
  69.  * V1.2
  70.  *
  71.  * Applied patches by Harald Koenig (koenig@nova.tat.physik.uni-tuebingen.de)
  72.  * Patched and indented by Rob Hooft (hooft@EMBL-Heidelberg.DE)
  73.  * 
  74.  * A free quote from a MAIL-message (with spelling corrections):
  75.  *
  76.  * "I found the explanation and solution for the CMOS reading 0xff problem
  77.  *  in the 0.99pl13c (ALPHA) kernel: the RTC goes offline for a small amount
  78.  *  of time for updating. Solution is included in the kernel source 
  79.  *  (linux/kernel/time.c)."
  80.  *
  81.  * "I modified clock.c to fix this problem and added an option (now default,
  82.  *  look for USE_INLINE_ASM_IO) that I/O instructions are used as inline
  83.  *  code and not via /dev/port (still possible via #undef ...)."
  84.  *
  85.  * With the new code, which is partially taken from the kernel sources, 
  86.  * the CMOS clock handling looks much more "official".
  87.  * Thanks Harald (and Torsten for the kernel code)!
  88.  *
  89.  ******
  90.  * V1.3
  91.  * Canges from alan@spri.levels.unisa.edu.au (Alan Modra):
  92.  * a) Fix a few typos in comments and remove reference to making
  93.  *    clock -u a cron job.  The kernel adjusts cmos time every 11
  94.  *    minutes - see kernel/sched.c and kernel/time.c set_rtc_mmss().
  95.  *    This means we should really have a cron job updating
  96.  *    /etc/adjtime every 11 mins (set last_time to the current time
  97.  *    and not_adjusted to ???).
  98.  * b) Swapped arguments of outb() to agree with asm/io.h macro of the
  99.  *    same name.  Use outb() from asm/io.h as it's slightly better.
  100.  * c) Changed CMOS_READ and CMOS_WRITE to inline functions.  Inserted
  101.  *    cli()..sti() pairs in appropriate places to prevent possible
  102.  *    errors, and changed ioperm() call to iopl() to allow cli.
  103.  * d) Moved some variables around to localise them a bit.
  104.  * e) Fixed bug with clock -ua or clock -us that cleared environment
  105.  *    variable TZ.  This fix also cured the annoying display of bogus
  106.  *    day of week on a number of machines. (Use mktime(), ctime()
  107.  *    rather than asctime() )
  108.  * f) Use settimeofday() rather than stime().  This one is important
  109.  *    as it sets the kernel's timezone offset, which is returned by
  110.  *    gettimeofday(), and used for display of MSDOS and OS2 file
  111.  *    times.
  112.  * g) faith@cs.unc.edu added -D flag for debugging
  113.  *
  114.  */
  115.  
  116. /* Here the information for time adjustments is kept. */
  117. #define ADJPATH "/etc/adjtime"
  118.  
  119. /* used for debugging the code. */
  120. /*#define KEEP_OFF */
  121.  
  122. /* Globals */
  123. int readit = 0;
  124. int adjustit = 0;
  125. int writeit = 0;
  126. int setit = 0;
  127. int universal = 0;
  128. int debug = 0;
  129.  
  130. volatile void 
  131. usage ()
  132. {
  133.   fprintf (stderr, 
  134.     "clock [-u] -r|w|s|a\n"
  135.     "  r: read and print CMOS clock\n"
  136.     "  w: write CMOS clock from system time\n"
  137.     "  s: set system time from CMOS clock\n"
  138.     "  a: get system time and adjust CMOS clock\n"
  139.     "  u: CMOS clock is in universal time\n"
  140.   );
  141.   exit (1);
  142. }
  143.  
  144. #ifndef USE_INLINE_ASM_IO
  145. int cmos_fd;
  146. #endif
  147.  
  148. static inline unsigned char cmos_read(unsigned char reg)
  149. {
  150.   register unsigned char ret;
  151.   __asm__ volatile ("cli");
  152.   outb (reg | 0x80, 0x70);
  153.   ret = inb (0x71);
  154.   __asm__ volatile ("sti");
  155.   return ret;
  156. }
  157.  
  158. static inline void cmos_write(unsigned char reg, unsigned char val)
  159. {
  160.   outb (reg | 0x80, 0x70);
  161.   outb (val, 0x71);
  162. }
  163.  
  164. #ifndef outb
  165. static inline void 
  166. outb (char val, unsigned short port)
  167. {
  168. #ifdef USE_INLINE_ASM_IO
  169.   __asm__ volatile ("out%B0 %0,%1"::"a" (val), "d" (port));
  170. #else
  171.   lseek (cmos_fd, port, 0);
  172.   write (cmos_fd, &val, 1);
  173. #endif
  174. }
  175. #endif
  176.  
  177. #ifndef inb
  178. static inline unsigned char 
  179. inb (unsigned short port)
  180. {
  181.   unsigned char ret;
  182. #ifdef USE_INLINE_ASM_IO
  183.   __asm__ volatile ("in%B0 %1,%0":"=a" (ret):"d" (port));
  184. #else
  185.   lseek (cmos_fd, port, 0);
  186.   read (cmos_fd, &ret, 1);
  187. #endif
  188.   return ret;
  189. }
  190. #endif
  191.  
  192. void 
  193. cmos_init ()
  194. {
  195. #ifdef USE_INLINE_ASM_IO
  196.   if (iopl (3))
  197.     {
  198.       fprintf(stderr,"clock: unable to get I/O port access\n");
  199.       exit (1);
  200.     }
  201. #else
  202.   cmos_fd = open ("/dev/port", 2);
  203.   if (cmos_fd < 0)
  204.     {
  205.       perror ("unable to open /dev/port read/write : ");
  206.       exit (1);
  207.     }
  208.   if (lseek (cmos_fd, 0x70, 0) < 0 || lseek (cmos_fd, 0x71, 0) < 0)
  209.     {
  210.       perror ("unable to seek port 0x70 in /dev/port : ");
  211.       exit (1);
  212.     }
  213. #endif
  214. }
  215.  
  216. static inline int 
  217. cmos_read_bcd (int addr)
  218. {
  219.   int b;
  220.   b = cmos_read (addr);
  221.   return (b & 15) + (b >> 4) * 10;
  222. }
  223.  
  224. static inline void 
  225. cmos_write_bcd (int addr, int value)
  226. {
  227.   cmos_write (addr, ((value / 10) << 4) + value % 10);
  228. }
  229.  
  230. int 
  231. main (int argc, char **argv, char **envp)
  232. {
  233.   struct tm tm;
  234.   time_t systime;
  235.   time_t last_time;
  236.   char arg;
  237.   double factor;
  238.   double not_adjusted;
  239.   int adjustment = 0;
  240.   unsigned char save_control, save_freq_select;
  241.  
  242.   while ((arg = getopt (argc, argv, "rwsuaD")) != -1)
  243.     {
  244.       switch (arg)
  245.     {
  246.     case 'r':
  247.       readit = 1;
  248.       break;
  249.     case 'w':
  250.       writeit = 1;
  251.       break;
  252.     case 's':
  253.       setit = 1;
  254.       break;
  255.     case 'u':
  256.       universal = 1;
  257.       break;
  258.     case 'a':
  259.       adjustit = 1;
  260.       break;
  261.         case 'D':
  262.       debug = 1;
  263.       break;
  264.     default:
  265.       usage ();
  266.     }
  267.     }
  268.  
  269.   if (readit + writeit + setit + adjustit > 1)
  270.     usage ();            /* only allow one of these */
  271.  
  272.   if (!(readit | writeit | setit | adjustit))    /* default to read */
  273.     readit = 1;
  274.  
  275.   cmos_init ();
  276.  
  277.   if (adjustit)
  278.     {                /* Read adjustment parameters first */
  279.       FILE *adj;
  280.       if ((adj = fopen (ADJPATH, "r")) == NULL)
  281.     {
  282.       perror (ADJPATH);
  283.       exit (2);
  284.     }
  285.       if (fscanf (adj, "%lf %d %lf", &factor, &last_time, ¬_adjusted) < 0)
  286.     {
  287.       perror (ADJPATH);
  288.       exit (2);
  289.     }
  290.       fclose (adj);
  291.       if (debug) printf ("Last adjustment done at %d seconds after 1/1/1970\n", last_time);
  292.     }
  293.  
  294.   if (readit || setit || adjustit)
  295.     {
  296.       int i;
  297.  
  298. /* read RTC exactly on falling edge of update flag */
  299. /* Wait for rise.... (may take upto 1 second) */
  300.  
  301.       for (i = 0; i < 10000000; i++)    
  302.     if (cmos_read (10) & 0x80)
  303.       break;
  304.  
  305. /* Wait for fall.... (must try at least 2.228 ms) */
  306.  
  307.       for (i = 0; i < 1000000; i++)    
  308.     if (!(cmos_read (10) & 0x80))
  309.       break;
  310.  
  311. /* The purpose of the "do" loop is called "low-risk programming" */
  312. /* In theory it should never run more than once */
  313.       do
  314.     { 
  315.       tm.tm_sec = cmos_read_bcd (0);
  316.       tm.tm_min = cmos_read_bcd (2);
  317.       tm.tm_hour = cmos_read_bcd (4);
  318.       tm.tm_wday = cmos_read_bcd (6);
  319.       tm.tm_mday = cmos_read_bcd (7);
  320.       tm.tm_mon = cmos_read_bcd (8);
  321.       tm.tm_year = cmos_read_bcd (9);
  322.     }
  323.       while (tm.tm_sec != cmos_read_bcd (0));
  324.       tm.tm_mon--;        /* DOS uses 1 base */
  325.       tm.tm_wday -= 3;        /* DOS uses 3 - 9 for week days */
  326.       tm.tm_isdst = -1;        /* don't know whether it's daylight */
  327.       if (debug) printf ("Cmos time : %d:%d:%d\n", tm.tm_hour, tm.tm_min, tm.tm_sec);
  328.     }
  329.  
  330.   if (readit || setit || adjustit)
  331.     {
  332. /*
  333.  * mktime() assumes we're giving it local time.  If the CMOS clock
  334.  * is in GMT, we have to set up TZ so mktime knows it.  tzset() gets
  335.  * called implicitly by the time code, but only the first time.  When
  336.  * changing the environment variable, better call tzset() explicitly.
  337.  */
  338.       if (universal)
  339.     {
  340.       char *zone;
  341.       zone = (char *) getenv ("TZ");    /* save original time zone */
  342.       (void) putenv ("TZ=");
  343.       tzset ();
  344.       systime = mktime (&tm);
  345.       /* now put back the original zone */
  346.       if (zone)
  347.         {
  348.  
  349.              char *zonebuf;
  350.              zonebuf = malloc (strlen (zone) + 4);
  351.              strcpy (zonebuf, "TZ=");
  352.              strcpy (zonebuf+3, zone);
  353.              putenv (zonebuf);
  354.              free (zonebuf);
  355.         }
  356.       else
  357.         {            /* wasn't one, so clear it */
  358.           putenv ("TZ");
  359.         }
  360.       tzset ();
  361.     }
  362.       else
  363.     {
  364.       systime = mktime (&tm);
  365.     }
  366.       if (debug) printf ("Number of seconds since 1/1/1970 is %d\n", systime);
  367.     }
  368.  
  369.   if (readit)
  370.     {
  371.       printf ("%s", ctime (&systime ));
  372.     }
  373.  
  374.   if (setit || adjustit)
  375.     {
  376.       struct timeval tv;
  377.       struct timezone tz;
  378.  
  379. /* program is designed to run setuid, be secure! */
  380.  
  381.       if (getuid () != 0)
  382.     {            
  383.       fprintf (stderr, "Sorry, must be root to set or adjust time\n");
  384.       exit (2);
  385.     }
  386.  
  387.       if (adjustit)
  388.     {            /* the actual adjustment */
  389.       double exact_adjustment;
  390.  
  391.       exact_adjustment = ((double) (systime - last_time))
  392.         * factor / (24 * 60 * 60);
  393.         + not_adjusted;
  394.       if (exact_adjustment > 0)
  395.         adjustment = (int) (exact_adjustment + 0.5);
  396.       else
  397.         adjustment = (int) (exact_adjustment - 0.5);
  398.       not_adjusted = exact_adjustment - (double) adjustment;
  399.       systime += adjustment;
  400.       if (debug) {
  401.          printf ("Time since last adjustment is %d seconds\n",
  402.              (int) (systime - last_time));
  403.          printf ("Adjusting time by %d seconds\n",
  404.              adjustment);
  405.          printf ("remaining adjustment is %.3f seconds\n",
  406.              not_adjusted);
  407.       }
  408.     }
  409. #ifndef KEEP_OFF
  410.       tv.tv_sec = systime;
  411.       tv.tv_usec = 0;
  412.       tz.tz_minuteswest = timezone / 60;
  413.       tz.tz_dsttime = daylight;
  414.  
  415.       if (settimeofday (&tv, &tz) != 0)
  416.         {
  417.       fprintf (stderr,
  418.            "Unable to set time -- probably you are not root\n");
  419.       exit (1);
  420.     }
  421.       
  422.       if (debug) {
  423.      printf( "Called settimeofday:\n" );
  424.      printf( "\ttv.tv_sec = %ld, tv.tv_usec = %ld\n",
  425.          tv.tv_sec, tv.tv_usec );
  426.      printf( "\ttz.tz_minuteswest = %d, tz.tz_dsttime = %d\n",
  427.          tz.tz_minuteswest, tz.tz_dsttime );
  428.       }
  429. #endif
  430.     }
  431.   
  432.   if (writeit || (adjustit && adjustment != 0))
  433.     {
  434.       struct tm *tmp;
  435.       systime = time (NULL);
  436.       if (universal)
  437.     tmp = gmtime (&systime);
  438.       else
  439.     tmp = localtime (&systime);
  440.  
  441. #ifndef KEEP_OFF
  442.       __asm__ volatile ("cli");
  443.       save_control = cmos_read (11);   /* tell the clock it's being set */
  444.       cmos_write (11, (save_control | 0x80));
  445.       save_freq_select = cmos_read (10);       /* stop and reset prescaler */
  446.       cmos_write (10, (save_freq_select | 0x70));
  447.  
  448.       cmos_write_bcd (0, tmp->tm_sec);
  449.       cmos_write_bcd (2, tmp->tm_min);
  450.       cmos_write_bcd (4, tmp->tm_hour);
  451.       cmos_write_bcd (6, tmp->tm_wday + 3);
  452.       cmos_write_bcd (7, tmp->tm_mday);
  453.       cmos_write_bcd (8, tmp->tm_mon + 1);
  454.       cmos_write_bcd (9, tmp->tm_year);
  455.  
  456.       cmos_write (10, save_freq_select);
  457.       cmos_write (11, save_control);
  458.       __asm__ volatile ("sti");
  459. #endif
  460.       if (debug) printf ("Set to : %d:%d:%d\n", tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
  461.     }
  462.   else
  463.     if (debug) printf ("CMOS clock unchanged.\n");
  464.   /* Save data for next 'adjustit' call */
  465.   if (adjustit)
  466.     {
  467.       FILE *adj;
  468.       if ((adj = fopen (ADJPATH, "w")) == NULL)
  469.     {
  470.       perror (ADJPATH);
  471.       exit (2);
  472.     }
  473.       fprintf (adj, "%f %d %f\n", factor, systime, not_adjusted);
  474.       fclose (adj);
  475.     }
  476.   exit (0);
  477. }
  478.