Imported Upstream version 1.42
[platform/upstream/connman.git] / src / timezone.c
1 /*
2  *
3  *  Connection Manager
4  *
5  *  Copyright (C) 2007-2012  Intel Corporation. All rights reserved.
6  *
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.
10  *
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.
15  *
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
19  *
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <errno.h>
27 #include <stdio.h>
28 #include <fcntl.h>
29 #include <unistd.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <dirent.h>
33 #include <sys/stat.h>
34 #include <sys/mman.h>
35 #include <sys/inotify.h>
36
37 #include <glib.h>
38
39 #include "connman.h"
40
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"
44
45 static char *read_key_file(const char *pathname, const char *key)
46 {
47         struct stat st;
48         char *map, *ptr, *str;
49         off_t ptrlen, keylen;
50         int fd;
51
52         fd = open(pathname, O_RDONLY | O_CLOEXEC);
53         if (fd < 0)
54                 return NULL;
55
56         if (fstat(fd, &st) < 0) {
57                 close(fd);
58                 return NULL;
59         }
60
61         map = mmap(0, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
62         if (!map || map == MAP_FAILED) {
63                 close(fd);
64                 return NULL;
65         }
66
67         ptr = map;
68         ptrlen = st.st_size;
69         keylen = strlen(key);
70
71         while (ptrlen > keylen + 1) {
72                 int cmp = strncmp(ptr, key, keylen);
73
74                 if (cmp == 0) {
75                         if (ptr == map)
76                                 break;
77
78                         if (*(ptr - 1) == '\n' && *(ptr + keylen) == '=')
79                                 break;
80                 }
81
82                 ptr = memchr(ptr + 1, key[0], ptrlen - 1);
83                 if (!ptr)
84                         break;
85
86                 ptrlen = st.st_size - (ptr - map);
87         }
88
89         if (ptr) {
90                 char *end, *val;
91
92                 ptrlen = st.st_size - (ptr - map);
93
94                 end = memchr(ptr, '\n', ptrlen);
95                 if (end)
96                         ptrlen = end - ptr;
97
98                 val = memchr(ptr, '"', ptrlen);
99                 if (val) {
100                         end = memchr(val + 1, '"', end - val - 1);
101                         if (end)
102                                 str = g_strndup(val + 1, end - val - 1);
103                         else
104                                 str = NULL;
105                 } else
106                         str = g_strndup(ptr + keylen + 1, ptrlen - keylen - 1);
107         } else
108                 str = NULL;
109
110         munmap(map, st.st_size);
111
112         close(fd);
113
114         return str;
115 }
116
117 static int compare_file(void *src_map, struct stat *src_st,
118                                                 const char *pathname)
119 {
120         struct stat dst_st;
121         void *dst_map;
122         int fd, result;
123
124         fd = open(pathname, O_RDONLY | O_CLOEXEC);
125         if (fd < 0)
126                 return -1;
127
128         if (fstat(fd, &dst_st) < 0) {
129                 close(fd);
130                 return -1;
131         }
132
133         if (src_st->st_size != dst_st.st_size) {
134                 close(fd);
135                 return -1;
136         }
137
138         dst_map = mmap(0, dst_st.st_size, PROT_READ, MAP_SHARED, fd, 0);
139         if (!dst_map || dst_map == MAP_FAILED) {
140                 close(fd);
141                 return -1;
142         }
143
144         result = memcmp(src_map, dst_map, src_st->st_size);
145
146         munmap(dst_map, dst_st.st_size);
147
148         close(fd);
149
150         return result;
151 }
152
153 static char *find_origin(void *src_map, struct stat *src_st,
154                                 const char *basepath, const char *subpath)
155 {
156         DIR *dir;
157         struct dirent *d;
158         char *str, pathname[PATH_MAX];
159         struct stat buf;
160         int ret;
161
162         if (!subpath)
163                 strncpy(pathname, basepath, sizeof(pathname) - 1);
164         else
165                 snprintf(pathname, sizeof(pathname),
166                                         "%s/%s", basepath, subpath);
167
168         dir = opendir(pathname);
169         if (!dir)
170                 return NULL;
171
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)
177                         continue;
178
179                 switch (d->d_type) {
180                 case DT_REG:
181                         if (!subpath)
182                                 snprintf(pathname, PATH_MAX,
183                                                 "%s/%s", basepath, d->d_name);
184                         else
185                                 snprintf(pathname, PATH_MAX,
186                                                 "%s/%s/%s", basepath,
187                                                         subpath, d->d_name);
188
189                         if (compare_file(src_map, src_st, pathname) == 0) {
190                                 if (!subpath)
191                                         str = g_strdup(d->d_name);
192                                 else
193                                         str = g_strdup_printf("%s/%s",
194                                                         subpath, d->d_name);
195                                 closedir(dir);
196                                 return str;
197                         }
198                         break;
199                 case DT_UNKNOWN:
200                         /*
201                          * If there is no d_type support use fstatat()
202                          * to check if d_name is directory
203                          */
204                         ret = fstatat(dirfd(dir), d->d_name, &buf, 0);
205                         if (ret < 0)
206                                 continue;
207                         if ((buf.st_mode & S_IFDIR) == 0)
208                                 continue;
209                         /* fall through */
210                 case DT_DIR:
211                         if (!subpath)
212                                 strncpy(pathname, d->d_name, sizeof(pathname));
213                         else
214                                 snprintf(pathname, sizeof(pathname),
215                                                 "%s/%s", subpath, d->d_name);
216
217                         str = find_origin(src_map, src_st, basepath, pathname);
218                         if (str) {
219                                 closedir(dir);
220                                 return str;
221                         }
222                         break;
223                 }
224         }
225
226         closedir(dir);
227
228         return NULL;
229 }
230
231 static char *get_timezone_alpha2(const char *zone)
232 {
233         GIOChannel *channel;
234         struct stat st;
235         char **tokens;
236         char *line;
237         char *alpha2 = NULL;
238         gsize len;
239         int fd;
240
241         if (!zone)
242                 return NULL;
243
244         fd = open(USR_SHARE_ZONEINFO_MAP, O_RDONLY | O_CLOEXEC);
245         if (fd < 0) {
246                 connman_warn("failed to open zoneinfo map %s",
247                                                         USR_SHARE_ZONEINFO_MAP);
248                 return NULL;
249         }
250
251         if (fstat(fd, &st) < 0 || !S_ISREG(st.st_mode)) {
252                 connman_warn("zoneinfo map does not exist/not regular file");
253                 close(fd);
254                 return NULL;
255         }
256
257         channel = g_io_channel_unix_new(fd);
258         if (!channel) {
259                 connman_warn("failed to create io channel for %s",
260                                                         USR_SHARE_ZONEINFO_MAP);
261                 close(fd);
262                 return NULL;
263         }
264
265         DBG("read %s for %s", USR_SHARE_ZONEINFO_MAP, zone);
266         g_io_channel_set_encoding(channel, "UTF-8", NULL);
267
268         while (g_io_channel_read_line(channel, &line, &len, NULL, NULL) ==
269                                                         G_IO_STATUS_NORMAL) {
270                 if (!line || !*line || *line == '#' || *line == '\n') {
271                         g_free(line);
272                         continue;
273                 }
274
275                 /* File format: Countrycodes Coordinates TZ Comments */
276                 tokens = g_strsplit_set(line, " \t", 4);
277                 if (!tokens) {
278                         connman_warn("line %s failed to parse", line);
279                         g_free(line);
280                         continue;
281                 }
282
283                 if (g_strv_length(tokens) >= 3 && !g_strcmp0(
284                                                 g_strstrip(tokens[2]), zone)) {
285                         /*
286                          * Multiple country codes can be listed, use the first
287                          * 2 chars.
288                          */
289                         alpha2 = g_strndup(g_strstrip(tokens[0]), 2);
290                 }
291
292                 g_strfreev(tokens);
293                 g_free(line);
294
295                 if (alpha2) {
296                         if (strlen(alpha2) != 2) {
297                                 connman_warn("Invalid ISO3166 code %s", alpha2);
298                                 g_free(alpha2);
299                                 alpha2 = NULL;
300                         } else {
301                                 DBG("Zone %s ISO3166 country code %s", zone,
302                                                                         alpha2);
303                         }
304
305                         break;
306                 }
307         }
308
309         g_io_channel_unref(channel);
310         close(fd);
311
312         return alpha2;
313 }
314
315 char *__connman_timezone_lookup(void)
316 {
317         struct stat st;
318         void *map;
319         int fd;
320         char *zone;
321         char *alpha2;
322
323         zone = read_key_file(ETC_SYSCONFIG_CLOCK, "ZONE");
324
325         DBG("sysconfig zone %s", zone);
326
327         fd = open(connman_setting_get_string("Localtime"),
328                                                         O_RDONLY | O_CLOEXEC);
329         if (fd < 0) {
330                 g_free(zone);
331                 return NULL;
332         }
333
334         if (fstat(fd, &st) < 0)
335                 goto done;
336
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) {
340                         g_free(zone);
341                         zone = NULL;
342
343                         goto done;
344                 }
345
346                 if (zone) {
347                         char pathname[PATH_MAX];
348
349                         snprintf(pathname, PATH_MAX, "%s/%s",
350                                                 USR_SHARE_ZONEINFO, zone);
351
352                         if (compare_file(map, &st, pathname) != 0) {
353                                 g_free(zone);
354                                 zone = NULL;
355                         }
356                 }
357
358                 if (!zone)
359                         zone = find_origin(map, &st, USR_SHARE_ZONEINFO, NULL);
360
361                 munmap(map, st.st_size);
362         } else {
363                 g_free(zone);
364                 zone = NULL;
365         }
366
367 done:
368         close(fd);
369
370         DBG("localtime zone %s", zone);
371
372         if (connman_setting_get_bool("RegdomFollowsTimezone")) {
373                 alpha2 = get_timezone_alpha2(zone);
374                 if (alpha2) {
375                         DBG("change regdom to %s", alpha2);
376                         connman_technology_set_regdom(alpha2);
377                         g_free(alpha2);
378                 }
379         }
380
381         return zone;
382 }
383
384 static int write_file(void *src_map, struct stat *src_st, const char *pathname)
385 {
386         struct stat st;
387         int fd;
388         ssize_t written;
389
390         DBG("pathname %s", pathname);
391
392         if (lstat(pathname, &st) == 0) {
393                 if (S_ISLNK(st.st_mode))
394                         unlink(pathname);
395         }
396
397         fd = open(pathname, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
398         if (fd < 0)
399                 return -EIO;
400
401         written = write(fd, src_map, src_st->st_size);
402
403         close(fd);
404
405         if (written < 0)
406                 return -EIO;
407
408         return 0;
409 }
410
411 int __connman_timezone_change(const char *zone)
412 {
413         struct stat st;
414         char *map, pathname[PATH_MAX];
415         int fd, err;
416
417         DBG("zone %s", zone);
418
419         snprintf(pathname, PATH_MAX, "%s/%s", USR_SHARE_ZONEINFO, zone);
420
421         fd = open(pathname, O_RDONLY | O_CLOEXEC);
422         if (fd < 0)
423                 return -EINVAL;
424
425         if (fstat(fd, &st) < 0) {
426                 close(fd);
427                 return -EIO;
428         }
429
430         map = mmap(0, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
431         if (!map || map == MAP_FAILED) {
432                 close(fd);
433                 return -EIO;
434         }
435
436         err = write_file(map, &st, connman_setting_get_string("Localtime"));
437
438         munmap(map, st.st_size);
439
440         close(fd);
441
442         return err;
443 }
444
445 static guint inotify_watch = 0;
446
447 static gboolean inotify_data(GIOChannel *channel, GIOCondition cond,
448                                                         gpointer user_data)
449 {
450         char buffer[256];
451         void *ptr = buffer;
452         GIOStatus status;
453         gsize bytes_read;
454
455         DBG("");
456
457         if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
458                 inotify_watch = 0;
459                 return FALSE;
460         }
461
462         status = g_io_channel_read_chars(channel, buffer, sizeof(buffer),
463                                                         &bytes_read, NULL);
464
465         switch (status) {
466         case G_IO_STATUS_NORMAL:
467                 break;
468         case G_IO_STATUS_AGAIN:
469                 return TRUE;
470         default:
471                 inotify_watch = 0;
472                 return FALSE;
473         }
474
475         DBG("bytes read %zd", bytes_read);
476
477         while (bytes_read > 0) {
478                 struct inotify_event *event = ptr;
479
480                 if (bytes_read < sizeof(*event))
481                         break;
482
483                 ptr += sizeof(*event);
484                 bytes_read -= sizeof(*event);
485
486                 if (event->len == 0)
487                         continue;
488
489                 if (bytes_read < event->len)
490                         break;
491
492                 ptr += event->len;
493                 bytes_read -= event->len;
494
495                 if (g_strcmp0(event->name, "localtime") == 0)
496                         __connman_clock_update_timezone();
497         }
498
499         return TRUE;
500 }
501
502 int __connman_timezone_init(void)
503 {
504         GIOChannel *channel;
505         char *dirname;
506         int fd, wd;
507
508         DBG("");
509
510         fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
511         if (fd < 0)
512                 return -EIO;
513
514         channel = g_io_channel_unix_new(fd);
515         if (!channel) {
516                 close(fd);
517                 return -EIO;
518         }
519
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);
523
524         inotify_watch = g_io_add_watch(channel,
525                                 G_IO_IN | G_IO_HUP | G_IO_NVAL | G_IO_ERR,
526                                 inotify_data, NULL);
527
528         g_io_channel_unref(channel);
529
530         dirname = g_path_get_dirname(connman_setting_get_string("Localtime"));
531
532         wd = inotify_add_watch(fd, dirname, IN_CREATE | IN_DONT_FOLLOW |
533                                                 IN_CLOSE_WRITE | IN_MOVED_TO);
534
535         g_free(dirname);
536
537         if (wd < 0)
538                 return -EIO;
539
540         return 0;
541 }
542
543 void __connman_timezone_cleanup(void)
544 {
545         DBG("");
546
547         if (inotify_watch > 0) {
548                 g_source_remove(inotify_watch);
549                 inotify_watch = 0;
550         }
551 }