e346b11a65434c536e6eba5b337e826c30a6e549
[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 #define _GNU_SOURCE
27 #include <errno.h>
28 #include <stdio.h>
29 #include <fcntl.h>
30 #include <unistd.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <dirent.h>
34 #include <sys/stat.h>
35 #include <sys/mman.h>
36 #include <sys/inotify.h>
37
38 #include <glib.h>
39
40 #include "connman.h"
41
42 #define ETC_LOCALTIME           "/etc/localtime"
43 #define ETC_SYSCONFIG_CLOCK     "/etc/sysconfig/clock"
44 #define USR_SHARE_ZONEINFO      "/usr/share/zoneinfo"
45
46 static char *read_key_file(const char *pathname, const char *key)
47 {
48         struct stat st;
49         char *map, *ptr, *str;
50         off_t ptrlen, keylen;
51         int fd;
52
53         fd = open(pathname, O_RDONLY | O_CLOEXEC);
54         if (fd < 0)
55                 return NULL;
56
57         if (fstat(fd, &st) < 0) {
58                 close(fd);
59                 return NULL;
60         }
61
62         map = mmap(0, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
63         if (!map || map == MAP_FAILED) {
64                 close(fd);
65                 return NULL;
66         }
67
68         ptr = map;
69         ptrlen = st.st_size;
70         keylen = strlen(key);
71
72         while (ptrlen > keylen + 1) {
73                 int cmp = strncmp(ptr, key, keylen);
74
75                 if (cmp == 0) {
76                         if (ptr == map)
77                                 break;
78
79                         if (*(ptr - 1) == '\n' && *(ptr + keylen) == '=')
80                                 break;
81                 }
82
83                 ptr = memchr(ptr + 1, key[0], ptrlen - 1);
84                 if (!ptr)
85                         break;
86
87                 ptrlen = st.st_size - (ptr - map);
88         }
89
90         if (ptr) {
91                 char *end, *val;
92
93                 ptrlen = st.st_size - (ptr - map);
94
95                 end = memchr(ptr, '\n', ptrlen);
96                 if (end)
97                         ptrlen = end - ptr;
98
99                 val = memchr(ptr, '"', ptrlen);
100                 if (val) {
101                         end = memchr(val + 1, '"', end - val - 1);
102                         if (end)
103                                 str = g_strndup(val + 1, end - val - 1);
104                         else
105                                 str = NULL;
106                 } else
107                         str = g_strndup(ptr + keylen + 1, ptrlen - keylen - 1);
108         } else
109                 str = NULL;
110
111         munmap(map, st.st_size);
112
113         close(fd);
114
115         return str;
116 }
117
118 static int compare_file(void *src_map, struct stat *src_st,
119                                                 const char *pathname)
120 {
121         struct stat dst_st;
122         void *dst_map;
123         int fd, result;
124
125         fd = open(pathname, O_RDONLY | O_CLOEXEC);
126         if (fd < 0)
127                 return -1;
128
129         if (fstat(fd, &dst_st) < 0) {
130                 close(fd);
131                 return -1;
132         }
133
134         if (src_st->st_size != dst_st.st_size) {
135                 close(fd);
136                 return -1;
137         }
138
139         dst_map = mmap(0, dst_st.st_size, PROT_READ, MAP_SHARED, fd, 0);
140         if (!dst_map || dst_map == MAP_FAILED) {
141                 close(fd);
142                 return -1;
143         }
144
145         result = memcmp(src_map, dst_map, src_st->st_size);
146
147         munmap(dst_map, dst_st.st_size);
148
149         close(fd);
150
151         return result;
152 }
153
154 static char *find_origin(void *src_map, struct stat *src_st,
155                                 const char *basepath, const char *subpath)
156 {
157         DIR *dir;
158         struct dirent *d;
159         char *str, pathname[PATH_MAX];
160         struct stat buf;
161         int ret;
162
163         if (!subpath)
164                 strncpy(pathname, basepath, sizeof(pathname) - 1);
165         else
166                 snprintf(pathname, sizeof(pathname),
167                                         "%s/%s", basepath, subpath);
168
169         dir = opendir(pathname);
170         if (!dir)
171                 return NULL;
172
173         while ((d = readdir(dir))) {
174                 if (strcmp(d->d_name, ".") == 0 ||
175                                 strcmp(d->d_name, "..") == 0 ||
176                                 strcmp(d->d_name, "posix") == 0 ||
177                                 strcmp(d->d_name, "right") == 0)
178                         continue;
179
180                 switch (d->d_type) {
181                 case DT_REG:
182                         if (!subpath)
183                                 snprintf(pathname, PATH_MAX,
184                                                 "%s/%s", basepath, d->d_name);
185                         else
186                                 snprintf(pathname, PATH_MAX,
187                                                 "%s/%s/%s", basepath,
188                                                         subpath, d->d_name);
189
190                         if (compare_file(src_map, src_st, pathname) == 0) {
191                                 str = g_strdup_printf("%s/%s",
192                                                         subpath, d->d_name);
193                                 closedir(dir);
194                                 return str;
195                         }
196                         break;
197                 case DT_UNKNOWN:
198                         /*
199                          * If there is no d_type support use fstatat()
200                          * to check if d_name is directory
201                          */
202                         ret = fstatat(dirfd(dir), d->d_name, &buf, 0);
203                         if (ret < 0)
204                                 continue;
205                         if ((buf.st_mode & S_IFDIR) == 0)
206                                 continue;
207                         /* fall through */
208                 case DT_DIR:
209                         if (!subpath)
210                                 strncpy(pathname, d->d_name, sizeof(pathname));
211                         else
212                                 snprintf(pathname, sizeof(pathname),
213                                                 "%s/%s", subpath, d->d_name);
214
215                         str = find_origin(src_map, src_st, basepath, pathname);
216                         if (str) {
217                                 closedir(dir);
218                                 return str;
219                         }
220                         break;
221                 }
222         }
223
224         closedir(dir);
225
226         return NULL;
227 }
228
229 char *__connman_timezone_lookup(void)
230 {
231         struct stat st;
232         void *map;
233         int fd;
234         char *zone;
235
236         zone = read_key_file(ETC_SYSCONFIG_CLOCK, "ZONE");
237
238         DBG("sysconfig zone %s", zone);
239
240         fd = open(ETC_LOCALTIME, O_RDONLY | O_CLOEXEC);
241         if (fd < 0) {
242                 g_free(zone);
243                 return NULL;
244         }
245
246         if (fstat(fd, &st) < 0)
247                 goto done;
248
249         if (S_ISREG(st.st_mode)) {
250                 map = mmap(0, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
251                 if (!map || map == MAP_FAILED) {
252                         g_free(zone);
253                         zone = NULL;
254
255                         goto done;
256                 }
257
258                 if (zone) {
259                         char pathname[PATH_MAX];
260
261                         snprintf(pathname, PATH_MAX, "%s/%s",
262                                                 USR_SHARE_ZONEINFO, zone);
263
264                         if (compare_file(map, &st, pathname) != 0) {
265                                 g_free(zone);
266                                 zone = NULL;
267                         }
268                 }
269
270                 if (!zone)
271                         zone = find_origin(map, &st, USR_SHARE_ZONEINFO, NULL);
272
273                 munmap(map, st.st_size);
274         } else {
275                 g_free(zone);
276                 zone = NULL;
277         }
278
279 done:
280         close(fd);
281
282         DBG("localtime zone %s", zone);
283
284         return zone;
285 }
286
287 static int write_file(void *src_map, struct stat *src_st, const char *pathname)
288 {
289         struct stat st;
290         int fd;
291         ssize_t written;
292
293         DBG("pathname %s", pathname);
294
295         if (lstat(pathname, &st) == 0) {
296                 if (S_ISLNK(st.st_mode))
297                         unlink(pathname);
298         }
299
300         fd = open(pathname, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
301         if (fd < 0)
302                 return -EIO;
303
304         written = write(fd, src_map, src_st->st_size);
305
306         close(fd);
307
308         if (written < 0)
309                 return -EIO;
310
311         return 0;
312 }
313
314 int __connman_timezone_change(const char *zone)
315 {
316         struct stat st;
317         char *map, pathname[PATH_MAX];
318         int fd, err;
319
320         DBG("zone %s", zone);
321
322         snprintf(pathname, PATH_MAX, "%s/%s", USR_SHARE_ZONEINFO, zone);
323
324         fd = open(pathname, O_RDONLY | O_CLOEXEC);
325         if (fd < 0)
326                 return -EINVAL;
327
328         if (fstat(fd, &st) < 0) {
329                 close(fd);
330                 return -EIO;
331         }
332
333         map = mmap(0, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
334         if (!map || map == MAP_FAILED) {
335                 close(fd);
336                 return -EIO;
337         }
338
339         err = write_file(map, &st, ETC_LOCALTIME);
340
341         munmap(map, st.st_size);
342
343         close(fd);
344
345         return err;
346 }
347
348 static guint inotify_watch = 0;
349
350 static gboolean inotify_data(GIOChannel *channel, GIOCondition cond,
351                                                         gpointer user_data)
352 {
353         char buffer[256];
354         void *ptr = buffer;
355         GIOStatus status;
356         gsize bytes_read;
357
358         DBG("");
359
360         if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
361                 inotify_watch = 0;
362                 return FALSE;
363         }
364
365         status = g_io_channel_read_chars(channel, buffer, sizeof(buffer),
366                                                         &bytes_read, NULL);
367
368         switch (status) {
369         case G_IO_STATUS_NORMAL:
370                 break;
371         case G_IO_STATUS_AGAIN:
372                 return TRUE;
373         default:
374                 inotify_watch = 0;
375                 return FALSE;
376         }
377
378         DBG("bytes read %zd", bytes_read);
379
380         while (bytes_read > 0) {
381                 struct inotify_event *event = ptr;
382
383                 if (bytes_read < sizeof(*event))
384                         break;
385
386                 ptr += sizeof(*event);
387                 bytes_read -= sizeof(*event);
388
389                 if (event->len == 0)
390                         continue;
391
392                 if (bytes_read < event->len)
393                         break;
394
395                 ptr += event->len;
396                 bytes_read -= event->len;
397
398                 if (g_strcmp0(event->name, "localtime") == 0)
399                         __connman_clock_update_timezone();
400         }
401
402         return TRUE;
403 }
404
405 int __connman_timezone_init(void)
406 {
407         GIOChannel *channel;
408         char *dirname;
409         int fd, wd;
410
411         DBG("");
412
413         fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
414         if (fd < 0)
415                 return -EIO;
416
417         channel = g_io_channel_unix_new(fd);
418         if (!channel) {
419                 close(fd);
420                 return -EIO;
421         }
422
423         g_io_channel_set_close_on_unref(channel, TRUE);
424         g_io_channel_set_encoding(channel, NULL, NULL);
425         g_io_channel_set_buffered(channel, FALSE);
426
427         inotify_watch = g_io_add_watch(channel,
428                                 G_IO_IN | G_IO_HUP | G_IO_NVAL | G_IO_ERR,
429                                 inotify_data, NULL);
430
431         g_io_channel_unref(channel);
432
433         dirname = g_path_get_dirname(ETC_LOCALTIME);
434
435         wd = inotify_add_watch(fd, dirname, IN_DONT_FOLLOW |
436                                                 IN_CLOSE_WRITE | IN_MOVED_TO);
437
438         g_free(dirname);
439
440         if (wd < 0)
441                 return -EIO;
442
443         return 0;
444 }
445
446 void __connman_timezone_cleanup(void)
447 {
448         DBG("");
449
450         if (inotify_watch > 0) {
451                 g_source_remove(inotify_watch);
452                 inotify_watch = 0;
453         }
454 }