aboutsummaryrefslogtreecommitdiff
path: root/toys/pending/hwclock.c
blob: 980d1651b1fdef3d63db1ec61d5425f2ab513f13 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
/* hwclock.c - get and set the hwclock
 *
 * Copyright 2014 Bilal Qureshi <bilal.jmi@gmail.com>
 *
 * No Standard.
 *
USE_HWCLOCK(NEWTOY(hwclock, "f(rtc):u(utc)l(localtime)t(systz)w(systohc)s(hctosys)r(show)[!ul][!rs][!rw][!rt][!sw][!st][!wt]", TOYFLAG_USR|TOYFLAG_BIN))

config HWCLOCK
  bool "hwclock"
  default n
  help
    usage: hwclock [-r|--show] [-s|--hctosys] [-w|--systohc] [-t|--systz] [-l|--localtime] 
    [-u|--utc] [-f|--rtc FILE]

    -f FILE Use specified device file (e.g. /dev/rtc2) instead of default
    -l      Assume hardware clock is kept in localtime
    -r      Show hardware clock time
    -s      Set system time from hardware clock
    -t      Set the system time based on the current timezone 
    -u      Assume hardware clock is kept in UTC
    -w      Set hardware clock from system time
*/
#define FOR_hwclock
#include "toys.h"
#include <linux/rtc.h>

GLOBALS(
  char *fname;

  int utc;
)

static int rtc_open(char **dev_rtc, int flag)
{
  if (!*dev_rtc) {
    int fd; 

    if ((fd = open((*dev_rtc = "/dev/rtc"), flag)) != -1) return fd;
    else if ((fd = open((*dev_rtc = "/dev/rtc0"), flag)) != -1) return fd;
    else *dev_rtc = "/dev/misc/rtc";
  }
  return xopen(*dev_rtc, flag);
}

static time_t get_rtc()
{
  struct tm time;
  time_t tm;
  char *ptz_old = NULL;
  int fd = rtc_open(&TT.fname, O_RDONLY);

  memset(&time, 0, sizeof(time));
  xioctl(fd, RTC_RD_TIME, &time);
  close(fd);
  if (TT.utc) {
    ptz_old = getenv("TZ");
    if (putenv((char*)"TZ=UTC0")) perror_exit("putenv");
    tzset();
  }
  if ((tm = mktime(&time)) < 0) error_exit("mktime failed");
  if (TT.utc) {
    if (unsetenv("TZ") < 0) perror_exit("unsetenv");
    if (ptz_old && putenv(ptz_old - 3)) perror_exit("putenv");
    tzset();
  }
  return tm;
}

static void set_sysclock_from_hwclock()
{
  struct timezone tmzone;
  struct timeval tmval;

  tmzone.tz_minuteswest = timezone / 60 - 60 * daylight;
  tmzone.tz_dsttime = 0;
  tmval.tv_sec = get_rtc();
  tmval.tv_usec = 0;
  if (settimeofday(&tmval, &tmzone) < 0) perror_exit("settimeofday");
}

static void set_hwclock_from_sysclock()
{
  struct timeval tmval;
  struct tm time;
  int fd = rtc_open(&TT.fname, O_WRONLY);

  if (gettimeofday(&tmval, NULL) < 0) perror_exit("gettimeofday");
  // converting a time value to broken-down UTC time
  if (TT. utc && !gmtime_r((time_t*)&tmval.tv_sec, &time)) 
    error_exit("gmtime_r failed");
  // converting a time value to a broken-down localtime
  else if (!(localtime_r((time_t*)&tmval.tv_sec, &time)))
    error_exit("localtime_r failed");

  /* The value of tm_isdst will positive if daylight saving time is in effect,
   * zero if it is not and negative if the information is not available. 
   * */
  time.tm_isdst = 0;
  xioctl(fd, RTC_SET_TIME, &time);
  close(fd);
}

static void set_sysclock_timezone()
{
  struct timezone tmzone;
  struct timeval tmval;
  struct tm *pb;

  if (gettimeofday(&tmval, NULL) < 0) perror_exit("gettimeofday");
  if (!(pb = localtime(&tmval.tv_sec))) error_exit("localtime failed");
  // extern long timezone => defined in header sys/time.h
  tmzone.tz_minuteswest = timezone / 60;
  if (pb->tm_isdst) tmzone.tz_minuteswest -= 60;
  tmzone.tz_dsttime = 0; // daylight saving time is not in effect
  if (gettimeofday(&tmval, NULL) < 0) perror_exit("gettimeofday");
  if (!TT.utc) tmval.tv_sec += tmzone.tz_minuteswest * 60;
  if (settimeofday(&tmval, &tmzone) < 0) perror_exit("settimeofday");
}

static void rtc_adjtime()
{
  char *line = NULL;
  int fd = open("/etc/adjtime", O_RDONLY);

  if (fd != -1) {
    for (; (line = get_line(fd)); free(line)) {
      if (!strncmp(line, "UTC", 3)) {
        TT.utc = 1;
        break;
      }
    }
    close(fd);
  }
}

static void display_hwclock()
{
  time_t tm = get_rtc();
  char *s, *pctm = ctime(&tm);

  if (pctm) {
    if ((s = strrchr(pctm, '\n'))) *s = '\0';
    xprintf("%s  0.000000 seconds\n", pctm);
  } else error_exit("failed to convert a time value to a date & time string");
}

void hwclock_main()
{
  (!(toys.optflags & FLAG_u)) ? rtc_adjtime() : (TT.utc = 1); // check for UTC
  if (toys.optflags & FLAG_w) set_hwclock_from_sysclock();
  else if (toys.optflags & FLAG_s) set_sysclock_from_hwclock(); 
  else if (toys.optflags & FLAG_t) set_sysclock_timezone();
  else if ((toys.optflags & FLAG_r) || (toys.optflags & FLAG_l) 
      || !*toys.optargs) display_hwclock();
  else {
    toys.exithelp++;
    error_exit("invalid option '%s'", *toys.optargs);
  }
}