5 * Copyright (C) 2007-2012 Intel Corporation. All rights reserved.
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
35 #include <sys/inotify.h>
41 #define ETC_SYSCONFIG_CLOCK "/etc/sysconfig/clock"
42 #define USR_SHARE_ZONEINFO "/usr/share/zoneinfo"
43 #define USR_SHARE_ZONEINFO_MAP USR_SHARE_ZONEINFO "/zone1970.tab"
45 static char *read_key_file(const char *pathname, const char *key)
48 char *map, *ptr, *str;
52 fd = open(pathname, O_RDONLY | O_CLOEXEC);
56 if (fstat(fd, &st) < 0) {
61 map = mmap(0, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
62 if (!map || map == MAP_FAILED) {
71 while (ptrlen > keylen + 1) {
72 int cmp = strncmp(ptr, key, keylen);
78 if (*(ptr - 1) == '\n' && *(ptr + keylen) == '=')
82 ptr = memchr(ptr + 1, key[0], ptrlen - 1);
86 ptrlen = st.st_size - (ptr - map);
92 ptrlen = st.st_size - (ptr - map);
94 end = memchr(ptr, '\n', ptrlen);
98 val = memchr(ptr, '"', ptrlen);
100 end = memchr(val + 1, '"', end - val - 1);
102 str = g_strndup(val + 1, end - val - 1);
106 str = g_strndup(ptr + keylen + 1, ptrlen - keylen - 1);
110 munmap(map, st.st_size);
117 static int compare_file(void *src_map, struct stat *src_st,
118 const char *pathname)
124 fd = open(pathname, O_RDONLY | O_CLOEXEC);
128 if (fstat(fd, &dst_st) < 0) {
133 if (src_st->st_size != dst_st.st_size) {
138 dst_map = mmap(0, dst_st.st_size, PROT_READ, MAP_SHARED, fd, 0);
139 if (!dst_map || dst_map == MAP_FAILED) {
144 result = memcmp(src_map, dst_map, src_st->st_size);
146 munmap(dst_map, dst_st.st_size);
153 static char *find_origin(void *src_map, struct stat *src_st,
154 const char *basepath, const char *subpath)
158 char *str, pathname[PATH_MAX];
163 strncpy(pathname, basepath, sizeof(pathname) - 1);
165 snprintf(pathname, sizeof(pathname),
166 "%s/%s", basepath, subpath);
168 dir = opendir(pathname);
172 while ((d = readdir(dir))) {
173 if (strcmp(d->d_name, ".") == 0 ||
174 strcmp(d->d_name, "..") == 0 ||
175 strcmp(d->d_name, "posix") == 0 ||
176 strcmp(d->d_name, "right") == 0)
182 snprintf(pathname, PATH_MAX,
183 "%s/%s", basepath, d->d_name);
185 snprintf(pathname, PATH_MAX,
186 "%s/%s/%s", basepath,
189 if (compare_file(src_map, src_st, pathname) == 0) {
191 str = g_strdup(d->d_name);
193 str = g_strdup_printf("%s/%s",
201 * If there is no d_type support use fstatat()
202 * to check if d_name is directory
204 ret = fstatat(dirfd(dir), d->d_name, &buf, 0);
207 if ((buf.st_mode & S_IFDIR) == 0)
212 strncpy(pathname, d->d_name, sizeof(pathname));
214 snprintf(pathname, sizeof(pathname),
215 "%s/%s", subpath, d->d_name);
217 str = find_origin(src_map, src_st, basepath, pathname);
231 static char *get_timezone_alpha2(const char *zone)
244 fd = open(USR_SHARE_ZONEINFO_MAP, O_RDONLY | O_CLOEXEC);
246 connman_warn("failed to open zoneinfo map %s",
247 USR_SHARE_ZONEINFO_MAP);
251 if (fstat(fd, &st) < 0 || !S_ISREG(st.st_mode)) {
252 connman_warn("zoneinfo map does not exist/not regular file");
257 channel = g_io_channel_unix_new(fd);
259 connman_warn("failed to create io channel for %s",
260 USR_SHARE_ZONEINFO_MAP);
265 DBG("read %s for %s", USR_SHARE_ZONEINFO_MAP, zone);
266 g_io_channel_set_encoding(channel, "UTF-8", NULL);
268 while (g_io_channel_read_line(channel, &line, &len, NULL, NULL) ==
269 G_IO_STATUS_NORMAL) {
270 if (!line || !*line || *line == '#' || *line == '\n') {
275 /* File format: Countrycodes Coordinates TZ Comments */
276 tokens = g_strsplit_set(line, " \t", 4);
278 connman_warn("line %s failed to parse", line);
283 if (g_strv_length(tokens) >= 3 && !g_strcmp0(
284 g_strstrip(tokens[2]), zone)) {
286 * Multiple country codes can be listed, use the first
289 alpha2 = g_strndup(g_strstrip(tokens[0]), 2);
296 if (strlen(alpha2) != 2) {
297 connman_warn("Invalid ISO3166 code %s", alpha2);
301 DBG("Zone %s ISO3166 country code %s", zone,
309 g_io_channel_unref(channel);
315 char *__connman_timezone_lookup(void)
323 zone = read_key_file(ETC_SYSCONFIG_CLOCK, "ZONE");
325 DBG("sysconfig zone %s", zone);
327 fd = open(connman_setting_get_string("Localtime"),
328 O_RDONLY | O_CLOEXEC);
334 if (fstat(fd, &st) < 0)
337 if (S_ISREG(st.st_mode)) {
338 map = mmap(0, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
339 if (!map || map == MAP_FAILED) {
347 char pathname[PATH_MAX];
349 snprintf(pathname, PATH_MAX, "%s/%s",
350 USR_SHARE_ZONEINFO, zone);
352 if (compare_file(map, &st, pathname) != 0) {
359 zone = find_origin(map, &st, USR_SHARE_ZONEINFO, NULL);
361 munmap(map, st.st_size);
370 DBG("localtime zone %s", zone);
372 if (connman_setting_get_bool("RegdomFollowsTimezone")) {
373 alpha2 = get_timezone_alpha2(zone);
375 DBG("change regdom to %s", alpha2);
376 connman_technology_set_regdom(alpha2);
384 static int write_file(void *src_map, struct stat *src_st, const char *pathname)
390 DBG("pathname %s", pathname);
392 if (lstat(pathname, &st) == 0) {
393 if (S_ISLNK(st.st_mode))
397 fd = open(pathname, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
401 written = write(fd, src_map, src_st->st_size);
411 int __connman_timezone_change(const char *zone)
414 char *map, pathname[PATH_MAX];
417 DBG("zone %s", zone);
419 snprintf(pathname, PATH_MAX, "%s/%s", USR_SHARE_ZONEINFO, zone);
421 fd = open(pathname, O_RDONLY | O_CLOEXEC);
425 if (fstat(fd, &st) < 0) {
430 map = mmap(0, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
431 if (!map || map == MAP_FAILED) {
436 err = write_file(map, &st, connman_setting_get_string("Localtime"));
438 munmap(map, st.st_size);
445 static guint inotify_watch = 0;
447 static gboolean inotify_data(GIOChannel *channel, GIOCondition cond,
457 if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
462 status = g_io_channel_read_chars(channel, buffer, sizeof(buffer),
466 case G_IO_STATUS_NORMAL:
468 case G_IO_STATUS_AGAIN:
475 DBG("bytes read %zd", bytes_read);
477 while (bytes_read > 0) {
478 struct inotify_event *event = ptr;
480 if (bytes_read < sizeof(*event))
483 ptr += sizeof(*event);
484 bytes_read -= sizeof(*event);
489 if (bytes_read < event->len)
493 bytes_read -= event->len;
495 if (g_strcmp0(event->name, "localtime") == 0)
496 __connman_clock_update_timezone();
502 int __connman_timezone_init(void)
510 fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
514 channel = g_io_channel_unix_new(fd);
520 g_io_channel_set_close_on_unref(channel, TRUE);
521 g_io_channel_set_encoding(channel, NULL, NULL);
522 g_io_channel_set_buffered(channel, FALSE);
524 inotify_watch = g_io_add_watch(channel,
525 G_IO_IN | G_IO_HUP | G_IO_NVAL | G_IO_ERR,
528 g_io_channel_unref(channel);
530 dirname = g_path_get_dirname(connman_setting_get_string("Localtime"));
532 wd = inotify_add_watch(fd, dirname, IN_CREATE | IN_DONT_FOLLOW |
533 IN_CLOSE_WRITE | IN_MOVED_TO);
543 void __connman_timezone_cleanup(void)
547 if (inotify_watch > 0) {
548 g_source_remove(inotify_watch);