X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=src%2Ftimezone.c;h=cc499097e59167f4c294f684ad7800b79934abbb;hb=refs%2Ftags%2Fupstream%2F1.38;hp=339542e43aed46f38f39f143b97aa27ac164045f;hpb=e8797320e8cf680e9c84b5462f0ba9c58a05b46e;p=platform%2Fupstream%2Fconnman.git diff --git a/src/timezone.c b/src/timezone.c index 339542e..cc49909 100644 --- a/src/timezone.c +++ b/src/timezone.c @@ -2,7 +2,7 @@ * * Connection Manager * - * Copyright (C) 2007-2010 Intel Corporation. All rights reserved. + * Copyright (C) 2007-2012 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -23,9 +23,434 @@ #include #endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + #include "connman.h" -char *__connman_timezone_lookup(void) +#define ETC_LOCALTIME "/etc/localtime" +#define ETC_SYSCONFIG_CLOCK "/etc/sysconfig/clock" +#define USR_SHARE_ZONEINFO "/usr/share/zoneinfo" + +static char *read_key_file(const char *pathname, const char *key) +{ + struct stat st; + char *map, *ptr, *str; + off_t ptrlen, keylen; + int fd; + + fd = open(pathname, O_RDONLY | O_CLOEXEC); + if (fd < 0) + return NULL; + + if (fstat(fd, &st) < 0) { + close(fd); + return NULL; + } + + map = mmap(0, st.st_size, PROT_READ, MAP_SHARED, fd, 0); + if (!map || map == MAP_FAILED) { + close(fd); + return NULL; + } + + ptr = map; + ptrlen = st.st_size; + keylen = strlen(key); + + while (ptrlen > keylen + 1) { + int cmp = strncmp(ptr, key, keylen); + + if (cmp == 0) { + if (ptr == map) + break; + + if (*(ptr - 1) == '\n' && *(ptr + keylen) == '=') + break; + } + + ptr = memchr(ptr + 1, key[0], ptrlen - 1); + if (!ptr) + break; + + ptrlen = st.st_size - (ptr - map); + } + + if (ptr) { + char *end, *val; + + ptrlen = st.st_size - (ptr - map); + + end = memchr(ptr, '\n', ptrlen); + if (end) + ptrlen = end - ptr; + + val = memchr(ptr, '"', ptrlen); + if (val) { + end = memchr(val + 1, '"', end - val - 1); + if (end) + str = g_strndup(val + 1, end - val - 1); + else + str = NULL; + } else + str = g_strndup(ptr + keylen + 1, ptrlen - keylen - 1); + } else + str = NULL; + + munmap(map, st.st_size); + + close(fd); + + return str; +} + +static int compare_file(void *src_map, struct stat *src_st, + const char *pathname) { + struct stat dst_st; + void *dst_map; + int fd, result; + + fd = open(pathname, O_RDONLY | O_CLOEXEC); + if (fd < 0) + return -1; + + if (fstat(fd, &dst_st) < 0) { + close(fd); + return -1; + } + + if (src_st->st_size != dst_st.st_size) { + close(fd); + return -1; + } + + dst_map = mmap(0, dst_st.st_size, PROT_READ, MAP_SHARED, fd, 0); + if (!dst_map || dst_map == MAP_FAILED) { + close(fd); + return -1; + } + + result = memcmp(src_map, dst_map, src_st->st_size); + + munmap(dst_map, dst_st.st_size); + + close(fd); + + return result; +} + +static char *find_origin(void *src_map, struct stat *src_st, + const char *basepath, const char *subpath) +{ + DIR *dir; + struct dirent *d; + char *str, pathname[PATH_MAX]; + struct stat buf; + int ret; + + if (!subpath) + strncpy(pathname, basepath, sizeof(pathname) - 1); + else + snprintf(pathname, sizeof(pathname), + "%s/%s", basepath, subpath); + + dir = opendir(pathname); + if (!dir) + return NULL; + + while ((d = readdir(dir))) { + if (strcmp(d->d_name, ".") == 0 || + strcmp(d->d_name, "..") == 0 || + strcmp(d->d_name, "posix") == 0 || + strcmp(d->d_name, "right") == 0) + continue; + + switch (d->d_type) { + case DT_REG: + if (!subpath) + snprintf(pathname, PATH_MAX, + "%s/%s", basepath, d->d_name); + else + snprintf(pathname, PATH_MAX, + "%s/%s/%s", basepath, + subpath, d->d_name); + + if (compare_file(src_map, src_st, pathname) == 0) { + if (!subpath) + str = g_strdup(d->d_name); + else + str = g_strdup_printf("%s/%s", + subpath, d->d_name); + closedir(dir); + return str; + } + break; + case DT_UNKNOWN: + /* + * If there is no d_type support use fstatat() + * to check if d_name is directory + */ + ret = fstatat(dirfd(dir), d->d_name, &buf, 0); + if (ret < 0) + continue; + if ((buf.st_mode & S_IFDIR) == 0) + continue; + /* fall through */ + case DT_DIR: + if (!subpath) + strncpy(pathname, d->d_name, sizeof(pathname)); + else + snprintf(pathname, sizeof(pathname), + "%s/%s", subpath, d->d_name); + + str = find_origin(src_map, src_st, basepath, pathname); + if (str) { + closedir(dir); + return str; + } + break; + } + } + + closedir(dir); + return NULL; } + +char *__connman_timezone_lookup(void) +{ + struct stat st; + void *map; + int fd; + char *zone; + + zone = read_key_file(ETC_SYSCONFIG_CLOCK, "ZONE"); + + DBG("sysconfig zone %s", zone); + + fd = open(ETC_LOCALTIME, O_RDONLY | O_CLOEXEC); + if (fd < 0) { + g_free(zone); + return NULL; + } + + if (fstat(fd, &st) < 0) + goto done; + + if (S_ISREG(st.st_mode)) { + map = mmap(0, st.st_size, PROT_READ, MAP_SHARED, fd, 0); + if (!map || map == MAP_FAILED) { + g_free(zone); + zone = NULL; + + goto done; + } + + if (zone) { + char pathname[PATH_MAX]; + + snprintf(pathname, PATH_MAX, "%s/%s", + USR_SHARE_ZONEINFO, zone); + + if (compare_file(map, &st, pathname) != 0) { + g_free(zone); + zone = NULL; + } + } + + if (!zone) + zone = find_origin(map, &st, USR_SHARE_ZONEINFO, NULL); + + munmap(map, st.st_size); + } else { + g_free(zone); + zone = NULL; + } + +done: + close(fd); + + DBG("localtime zone %s", zone); + + return zone; +} + +static int write_file(void *src_map, struct stat *src_st, const char *pathname) +{ + struct stat st; + int fd; + ssize_t written; + + DBG("pathname %s", pathname); + + if (lstat(pathname, &st) == 0) { + if (S_ISLNK(st.st_mode)) + unlink(pathname); + } + + fd = open(pathname, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644); + if (fd < 0) + return -EIO; + + written = write(fd, src_map, src_st->st_size); + + close(fd); + + if (written < 0) + return -EIO; + + return 0; +} + +int __connman_timezone_change(const char *zone) +{ + struct stat st; + char *map, pathname[PATH_MAX]; + int fd, err; + + DBG("zone %s", zone); + + snprintf(pathname, PATH_MAX, "%s/%s", USR_SHARE_ZONEINFO, zone); + + fd = open(pathname, O_RDONLY | O_CLOEXEC); + if (fd < 0) + return -EINVAL; + + if (fstat(fd, &st) < 0) { + close(fd); + return -EIO; + } + + map = mmap(0, st.st_size, PROT_READ, MAP_SHARED, fd, 0); + if (!map || map == MAP_FAILED) { + close(fd); + return -EIO; + } + + err = write_file(map, &st, ETC_LOCALTIME); + + munmap(map, st.st_size); + + close(fd); + + return err; +} + +static guint inotify_watch = 0; + +static gboolean inotify_data(GIOChannel *channel, GIOCondition cond, + gpointer user_data) +{ + char buffer[256]; + void *ptr = buffer; + GIOStatus status; + gsize bytes_read; + + DBG(""); + + if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) { + inotify_watch = 0; + return FALSE; + } + + status = g_io_channel_read_chars(channel, buffer, sizeof(buffer), + &bytes_read, NULL); + + switch (status) { + case G_IO_STATUS_NORMAL: + break; + case G_IO_STATUS_AGAIN: + return TRUE; + default: + inotify_watch = 0; + return FALSE; + } + + DBG("bytes read %zd", bytes_read); + + while (bytes_read > 0) { + struct inotify_event *event = ptr; + + if (bytes_read < sizeof(*event)) + break; + + ptr += sizeof(*event); + bytes_read -= sizeof(*event); + + if (event->len == 0) + continue; + + if (bytes_read < event->len) + break; + + ptr += event->len; + bytes_read -= event->len; + + if (g_strcmp0(event->name, "localtime") == 0) + __connman_clock_update_timezone(); + } + + return TRUE; +} + +int __connman_timezone_init(void) +{ + GIOChannel *channel; + char *dirname; + int fd, wd; + + DBG(""); + + fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); + if (fd < 0) + return -EIO; + + channel = g_io_channel_unix_new(fd); + if (!channel) { + close(fd); + return -EIO; + } + + g_io_channel_set_close_on_unref(channel, TRUE); + g_io_channel_set_encoding(channel, NULL, NULL); + g_io_channel_set_buffered(channel, FALSE); + + inotify_watch = g_io_add_watch(channel, + G_IO_IN | G_IO_HUP | G_IO_NVAL | G_IO_ERR, + inotify_data, NULL); + + g_io_channel_unref(channel); + + dirname = g_path_get_dirname(ETC_LOCALTIME); + + wd = inotify_add_watch(fd, dirname, IN_DONT_FOLLOW | + IN_CLOSE_WRITE | IN_MOVED_TO); + + g_free(dirname); + + if (wd < 0) + return -EIO; + + return 0; +} + +void __connman_timezone_cleanup(void) +{ + DBG(""); + + if (inotify_watch > 0) { + g_source_remove(inotify_watch); + inotify_watch = 0; + } +}