aboutsummaryrefslogtreecommitdiff
path: root/toys/pending/hwclock.c
blob: 003174f7923b41bbcd770200a11249aa92225fd7 (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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
/* hwclock.c - get and set the hwclock
 *
 * Copyright 2014 Bilal Qureshi <bilal.jmi@gmail.com>
 *
 * No Standard.
 *
USE_HWCLOCK(NEWTOY(hwclock, ">0(fast)f(rtc):u(utc)l(localtime)t(systz)w(systohc)s(hctosys)r(show)[!ul][!rsw]", TOYFLAG_USR|TOYFLAG_BIN))

config HWCLOCK
  bool "hwclock"
  default n
  help
    usage: hwclock [-rswtluf]

    -f FILE Use specified device file instead of /dev/rtc (--show)
    -l      Hardware clock uses localtime (--localtime)
    -r      Show hardware clock time (--show)
    -s      Set system time from hardware clock (--hctosys)
    -t      Set the system time based on the current timezone (--systz)
    -u      Hardware clock uses UTC (--utc)
    -w      Set hardware clock from system time (--systohc)
*/
#define FOR_hwclock
#include "toys.h"
#include <linux/rtc.h>

GLOBALS(
  char *fname;

  int utc;
)

static int check_hctosys(struct dirtree* node)
{
  FILE *fp;

  if (!node->parent) return DIRTREE_RECURSE;

  snprintf(toybuf, sizeof(toybuf), "/sys/class/rtc/%s/hctosys", node->name);
  fp = fopen(toybuf, "r");
  if (fp) {
    int hctosys = 0;
    int items = fscanf(fp, "%d", &hctosys);
    fclose(fp);
    if (items == 1 && hctosys == 1) {
      snprintf(toybuf, sizeof(toybuf), "/dev/%s", node->name);
      TT.fname = toybuf;
      return DIRTREE_ABORT;
    }
  }
  return 0;
}

// Search /sys/class/rtc for the RTC that the system clock is set from.
// See the kernel's Documentation/rtc.txt.
static int open_wall_clock_rtc(int flag)
{
  TT.fname = NULL;
  dirtree_read("/sys/class/rtc", check_hctosys);
  return TT.fname ? xopen(TT.fname, flag) : -1;
}

static int rtc_open(int flag)
{
  if (!TT.fname) {
    int fd; 

    if ((fd = open((TT.fname = "/dev/rtc"), flag)) != -1) return fd;
    else if ((fd = open_wall_clock_rtc(flag)) != -1) return fd;
    else TT.fname = "/dev/misc/rtc";
  }
  return xopen(TT.fname, flag);
}

static time_t get_rtc()
{
  struct tm time;
  time_t tm;
  char *ptz_old = 0;
  int fd = rtc_open(O_RDONLY);

  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(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");
}

void hwclock_main()
{
  // check for UTC
  if (!(toys.optflags & FLAG_u)) {
    FILE *fp = fopen("/etc/adjtime", "r");

    if (fp) {
      char *line = NULL;
      size_t st;

      while (0 < getline(&line, &st, fp)) {
        if (!strncmp(line, "UTC", 3)) {
          TT.utc = 1;
          break;
        }
        free(line);
      }
      fclose(fp);
    }
  } else TT.utc = 1;

  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)
  {
    time_t tm = get_rtc();
    char *s, *pctm = ctime(&tm);

    // ctime() is defined as equivalent to asctime(localtime(t)),
    // which is defined to overflow its buffer rather than return NULL.
    // if (!pctm) error_exit("can't happen");
    if ((s = strrchr(pctm, '\n'))) *s = '\0';
    // TODO: implement this.
    xprintf("%s  0.000000 seconds\n", pctm);
  }
}