home *** CD-ROM | disk | FTP | other *** search
/ Chip 1995 March / CHIP3.mdf / slackwar / a / util / util-lin.2 / util-lin / util-linux-2.2 / sys-utils / clock.c < prev    next >
Encoding:
C/C++ Source or Header  |  1995-02-22  |  13.1 KB  |  491 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.  * V1.4: alan@SPRI.Levels.UniSA.Edu.Au (Alan Modra)
  115.  *       Wed Feb  8 12:29:08 1995, fix for years > 2000.
  116.  *       faith@cs.unc.edu added -v option to print version.
  117.  *
  118.  */
  119.  
  120. #define VERSION "1.4"
  121.  
  122. /* Here the information for time adjustments is kept. */
  123. #define ADJPATH "/etc/adjtime"
  124.  
  125.  
  126. /* used for debugging the code. */
  127. /*#define KEEP_OFF */
  128.  
  129. /* Globals */
  130. int readit = 0;
  131. int adjustit = 0;
  132. int writeit = 0;
  133. int setit = 0;
  134. int universal = 0;
  135. int debug = 0;
  136.  
  137. volatile void 
  138. usage ()
  139. {
  140.   fprintf (stderr, 
  141.     "clock [-u] -r|w|s|a|v\n"
  142.     "  r: read and print CMOS clock\n"
  143.     "  w: write CMOS clock from system time\n"
  144.     "  s: set system time from CMOS clock\n"
  145.     "  a: get system time and adjust CMOS clock\n"
  146.     "  u: CMOS clock is in universal time\n"
  147.     "  v: print version (" VERSION ") and exit\n"
  148.   );
  149.   exit (1);
  150. }
  151.  
  152. #ifndef USE_INLINE_ASM_IO
  153. int cmos_fd;
  154. #endif
  155.  
  156. static inline unsigned char cmos_read(unsigned char reg)
  157. {
  158.   register unsigned char ret;
  159.   __asm__ volatile ("cli");
  160.   outb (reg | 0x80, 0x70);
  161.   ret = inb (0x71);
  162.   __asm__ volatile ("sti");
  163.   return ret;
  164. }
  165.  
  166. static inline void cmos_write(unsigned char reg, unsigned char val)
  167. {
  168.   outb (reg | 0x80, 0x70);
  169.   outb (val, 0x71);
  170. }
  171.  
  172. #ifndef outb
  173. static inline void 
  174. outb (char val, unsigned short port)
  175. {
  176. #ifdef USE_INLINE_ASM_IO
  177.   __asm__ volatile ("out%B0 %0,%1"::"a" (val), "d" (port));
  178. #else
  179.   lseek (cmos_fd, port, 0);
  180.   write (cmos_fd, &val, 1);
  181. #endif
  182. }
  183. #endif
  184.  
  185. #ifndef inb
  186. static inline unsigned char 
  187. inb (unsigned short port)
  188. {
  189.   unsigned char ret;
  190. #ifdef USE_INLINE_ASM_IO
  191.   __asm__ volatile ("in%B0 %1,%0":"=a" (ret):"d" (port));
  192. #else
  193.   lseek (cmos_fd, port, 0);
  194.   read (cmos_fd, &ret, 1);
  195. #endif
  196.   return ret;
  197. }
  198. #endif
  199.  
  200. void 
  201. cmos_init ()
  202. {
  203. #ifdef USE_INLINE_ASM_IO
  204.   if (iopl (3))
  205.     {
  206.       fprintf(stderr,"clock: unable to get I/O port access\n");
  207.       exit (1);
  208.     }
  209. #else
  210.   cmos_fd = open ("/dev/port", 2);
  211.   if (cmos_fd < 0)
  212.     {
  213.       perror ("unable to open /dev/port read/write : ");
  214.       exit (1);
  215.     }
  216.   if (lseek (cmos_fd, 0x70, 0) < 0 || lseek (cmos_fd, 0x71, 0) < 0)
  217.     {
  218.       perror ("unable to seek port 0x70 in /dev/port : ");
  219.       exit (1);
  220.     }
  221. #endif
  222. }
  223.  
  224. static inline int 
  225. cmos_read_bcd (int addr)
  226. {
  227.   int b;
  228.   b = cmos_read (addr);
  229.   return (b & 15) + (b >> 4) * 10;
  230. }
  231.  
  232. static inline void 
  233. cmos_write_bcd (int addr, int value)
  234. {
  235.   cmos_write (addr, ((value / 10) << 4) + value % 10);
  236. }
  237.  
  238. int 
  239. main (int argc, char **argv, char **envp)
  240. {
  241.   struct tm tm;
  242.   time_t systime;
  243.   time_t last_time;
  244.   char arg;
  245.   double factor;
  246.   double not_adjusted;
  247.   int adjustment = 0;
  248.   unsigned char save_control, save_freq_select;
  249.  
  250.   while ((arg = getopt (argc, argv, "rwsuaDv")) != -1)
  251.     {
  252.       switch (arg)
  253.     {
  254.     case 'r':
  255.       readit = 1;
  256.       break;
  257.     case 'w':
  258.       writeit = 1;
  259.       break;
  260.     case 's':
  261.       setit = 1;
  262.       break;
  263.     case 'u':
  264.       universal = 1;
  265.       break;
  266.     case 'a':
  267.       adjustit = 1;
  268.       break;
  269.         case 'D':
  270.       debug = 1;
  271.       break;
  272.     case 'v':
  273.       fprintf( stderr, "clock " VERSION "\n" );
  274.       exit(0);
  275.     default:
  276.       usage ();
  277.     }
  278.     }
  279.  
  280.   if (readit + writeit + setit + adjustit > 1)
  281.     usage ();            /* only allow one of these */
  282.  
  283.   if (!(readit | writeit | setit | adjustit))    /* default to read */
  284.     readit = 1;
  285.  
  286.   cmos_init ();
  287.  
  288.   if (adjustit)
  289.     {                /* Read adjustment parameters first */
  290.       FILE *adj;
  291.       if ((adj = fopen (ADJPATH, "r")) == NULL)
  292.     {
  293.       perror (ADJPATH);
  294.       exit (2);
  295.     }
  296.       if (fscanf (adj, "%lf %d %lf", &factor, &last_time, ¬_adjusted) < 0)
  297.     {
  298.       perror (ADJPATH);
  299.       exit (2);
  300.     }
  301.       fclose (adj);
  302.       if (debug) printf ("Last adjustment done at %d seconds after 1/1/1970\n", last_time);
  303.     }
  304.  
  305.   if (readit || setit || adjustit)
  306.     {
  307.       int i;
  308.  
  309. /* read RTC exactly on falling edge of update flag */
  310. /* Wait for rise.... (may take upto 1 second) */
  311.  
  312.       for (i = 0; i < 10000000; i++)    
  313.     if (cmos_read (10) & 0x80)
  314.       break;
  315.  
  316. /* Wait for fall.... (must try at least 2.228 ms) */
  317.  
  318.       for (i = 0; i < 1000000; i++)    
  319.     if (!(cmos_read (10) & 0x80))
  320.       break;
  321.  
  322. /* The purpose of the "do" loop is called "low-risk programming" */
  323. /* In theory it should never run more than once */
  324.       do
  325.     { 
  326.       tm.tm_sec = cmos_read_bcd (0);
  327.       tm.tm_min = cmos_read_bcd (2);
  328.       tm.tm_hour = cmos_read_bcd (4);
  329.       tm.tm_wday = cmos_read_bcd (6);
  330.       tm.tm_mday = cmos_read_bcd (7);
  331.       tm.tm_mon = cmos_read_bcd (8);
  332.       tm.tm_year = cmos_read_bcd (9);
  333.     }
  334.       while (tm.tm_sec != cmos_read_bcd (0));
  335.       if (tm.tm_year < 70)
  336.         tm.tm_year += 100;  /* 70..99 => 1970..1999, 0..69 => 2000..2069 */
  337.       tm.tm_mon--;        /* DOS uses 1 base */
  338.       tm.tm_wday -= 3;        /* DOS uses 3 - 9 for week days */
  339.       tm.tm_isdst = -1;        /* don't know whether it's daylight */
  340.       if (debug) printf ("Cmos time : %d:%d:%d\n", tm.tm_hour, tm.tm_min, tm.tm_sec);
  341.     }
  342.  
  343.   if (readit || setit || adjustit)
  344.     {
  345. /*
  346.  * mktime() assumes we're giving it local time.  If the CMOS clock
  347.  * is in GMT, we have to set up TZ so mktime knows it.  tzset() gets
  348.  * called implicitly by the time code, but only the first time.  When
  349.  * changing the environment variable, better call tzset() explicitly.
  350.  */
  351.       if (universal)
  352.     {
  353.       char *zone;
  354.       zone = (char *) getenv ("TZ");    /* save original time zone */
  355.       (void) putenv ("TZ=");
  356.       tzset ();
  357.       systime = mktime (&tm);
  358.       /* now put back the original zone */
  359.       if (zone)
  360.         {
  361.  
  362.              char *zonebuf;
  363.              zonebuf = malloc (strlen (zone) + 4);
  364.              strcpy (zonebuf, "TZ=");
  365.              strcpy (zonebuf+3, zone);
  366.              putenv (zonebuf);
  367.              free (zonebuf);
  368.         }
  369.       else
  370.         {            /* wasn't one, so clear it */
  371.           putenv ("TZ");
  372.         }
  373.       tzset ();
  374.     }
  375.       else
  376.     {
  377.       systime = mktime (&tm);
  378.     }
  379.       if (debug) printf ("Number of seconds since 1/1/1970 is %d\n", systime);
  380.     }
  381.  
  382.   if (readit)
  383.     {
  384.       printf ("%s", ctime (&systime ));
  385.     }
  386.  
  387.   if (setit || adjustit)
  388.     {
  389.       struct timeval tv;
  390.       struct timezone tz;
  391.  
  392. /* program is designed to run setuid, be secure! */
  393.  
  394.       if (getuid () != 0)
  395.     {            
  396.       fprintf (stderr, "Sorry, must be root to set or adjust time\n");
  397.       exit (2);
  398.     }
  399.  
  400.       if (adjustit)
  401.     {            /* the actual adjustment */
  402.       double exact_adjustment;
  403.  
  404.       exact_adjustment = ((double) (systime - last_time))
  405.         * factor / (24 * 60 * 60)
  406.         + not_adjusted;
  407.       if (exact_adjustment > 0)
  408.         adjustment = (int) (exact_adjustment + 0.5);
  409.       else
  410.         adjustment = (int) (exact_adjustment - 0.5);
  411.       not_adjusted = exact_adjustment - (double) adjustment;
  412.       systime += adjustment;
  413.       if (debug) {
  414.          printf ("Time since last adjustment is %d seconds\n",
  415.              (int) (systime - last_time));
  416.          printf ("Adjusting time by %d seconds\n",
  417.              adjustment);
  418.          printf ("remaining adjustment is %.3f seconds\n",
  419.              not_adjusted);
  420.       }
  421.     }
  422. #ifndef KEEP_OFF
  423.       tv.tv_sec = systime;
  424.       tv.tv_usec = 0;
  425.       tz.tz_minuteswest = timezone / 60;
  426.       tz.tz_dsttime = daylight;
  427.  
  428.       if (settimeofday (&tv, &tz) != 0)
  429.         {
  430.       fprintf (stderr,
  431.            "Unable to set time -- probably you are not root\n");
  432.       exit (1);
  433.     }
  434.       
  435.       if (debug) {
  436.      printf( "Called settimeofday:\n" );
  437.      printf( "\ttv.tv_sec = %ld, tv.tv_usec = %ld\n",
  438.          tv.tv_sec, tv.tv_usec );
  439.      printf( "\ttz.tz_minuteswest = %d, tz.tz_dsttime = %d\n",
  440.          tz.tz_minuteswest, tz.tz_dsttime );
  441.       }
  442. #endif
  443.     }
  444.   
  445.   if (writeit || (adjustit && adjustment != 0))
  446.     {
  447.       struct tm *tmp;
  448.       systime = time (NULL);
  449.       if (universal)
  450.     tmp = gmtime (&systime);
  451.       else
  452.     tmp = localtime (&systime);
  453.  
  454. #ifndef KEEP_OFF
  455.       __asm__ volatile ("cli");
  456.       save_control = cmos_read (11);   /* tell the clock it's being set */
  457.       cmos_write (11, (save_control | 0x80));
  458.       save_freq_select = cmos_read (10);       /* stop and reset prescaler */
  459.       cmos_write (10, (save_freq_select | 0x70));
  460.  
  461.       cmos_write_bcd (0, tmp->tm_sec);
  462.       cmos_write_bcd (2, tmp->tm_min);
  463.       cmos_write_bcd (4, tmp->tm_hour);
  464.       cmos_write_bcd (6, tmp->tm_wday + 3);
  465.       cmos_write_bcd (7, tmp->tm_mday);
  466.       cmos_write_bcd (8, tmp->tm_mon + 1);
  467.       cmos_write_bcd (9, tmp->tm_year);
  468.  
  469.       cmos_write (10, save_freq_select);
  470.       cmos_write (11, save_control);
  471.       __asm__ volatile ("sti");
  472. #endif
  473.       if (debug) printf ("Set to : %d:%d:%d\n", tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
  474.     }
  475.   else
  476.     if (debug) printf ("CMOS clock unchanged.\n");
  477.   /* Save data for next 'adjustit' call */
  478.   if (adjustit)
  479.     {
  480.       FILE *adj;
  481.       if ((adj = fopen (ADJPATH, "w")) == NULL)
  482.     {
  483.       perror (ADJPATH);
  484.       exit (2);
  485.     }
  486.       fprintf (adj, "%f %d %f\n", factor, systime, not_adjusted);
  487.       fclose (adj);
  488.     }
  489.   exit (0);
  490. }
  491.