timezone: Add support for retrieving current timezone
authorMarcel Holtmann <marcel@holtmann.org>
Mon, 18 Apr 2011 20:15:49 +0000 (13:15 -0700)
committerMarcel Holtmann <marcel@holtmann.org>
Mon, 18 Apr 2011 20:15:49 +0000 (13:15 -0700)
src/timezone.c

index 339542e..d0045b1 100644 (file)
 #include <config.h>
 #endif
 
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
 #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);
+       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 == NULL || 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 == NULL)
+                       break;
+
+               ptrlen = st.st_size - (ptr - map);
+       }
+
+       if (ptr != NULL) {
+               char *end, *val;
+
+               ptrlen = st.st_size - (ptr - map);
+
+               end = memchr(ptr, '\n', ptrlen);
+               if (end != NULL)
+                       ptrlen = end - ptr;
+
+               val = memchr(ptr, '"', ptrlen);
+               if (val != NULL) {
+                       end = memchr(val + 1, '"', end - val - 1);
+                       if (end != NULL)
+                               str = strndup(val + 1, end - val - 1);
+                       else
+                               str = NULL;
+               } else
+                       str = 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);
+       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 == NULL || 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)
+{
+       DIR *dir;
+       struct dirent *d;
+       char *str, pathname[PATH_MAX];
+
+       dir = opendir(basepath);
+       if (dir == NULL)
+               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;
+
+               snprintf(pathname, PATH_MAX, "%s/%s", basepath, d->d_name);
+
+               switch (d->d_type) {
+               case DT_REG:
+                       if (compare_file(src_map, src_st, pathname) == 0) {
+                               closedir(dir);
+                               return strdup(d->d_name);
+                       }
+                       break;
+               case DT_DIR:
+                       str = find_origin(src_map, src_st, pathname);
+                       if (str != NULL) {
+                               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);
+       if (fd < 0) {
+               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 == NULL || map == MAP_FAILED) {
+                       free(zone);
+                       zone = NULL;
+
+                       goto done;
+               }
+
+               if (zone != NULL) {
+                       char pathname[PATH_MAX];
+
+                       snprintf(pathname, PATH_MAX, "%s/%s",
+                                               USR_SHARE_ZONEINFO, zone);
+
+                       if (compare_file(map, &st, pathname) != 0) {
+                               free(zone);
+                               zone = NULL;
+                       }
+               }
+
+               if (zone == NULL)
+                       zone = find_origin(map, &st, USR_SHARE_ZONEINFO);
+
+               munmap(map, st.st_size);
+       } else {
+               free(zone);
+               zone = NULL;
+       }
+
+done:
+       close(fd);
+
+       DBG("localtime zone %s", zone);
+
+       return zone;
+}