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