dnsproxy: Fix __connman_dnsproxy_add_listener() error handling
[framework/connectivity/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 <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_LOCALTIME           "/etc/localtime"
42 #define ETC_SYSCONFIG_CLOCK     "/etc/sysconfig/clock"
43 #define USR_SHARE_ZONEINFO      "/usr/share/zoneinfo"
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);
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 == NULL || 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 == NULL)
84                         break;
85
86                 ptrlen = st.st_size - (ptr - map);
87         }
88
89         if (ptr != NULL) {
90                 char *end, *val;
91
92                 ptrlen = st.st_size - (ptr - map);
93
94                 end = memchr(ptr, '\n', ptrlen);
95                 if (end != NULL)
96                         ptrlen = end - ptr;
97
98                 val = memchr(ptr, '"', ptrlen);
99                 if (val != NULL) {
100                         end = memchr(val + 1, '"', end - val - 1);
101                         if (end != NULL)
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);
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 == NULL || 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
160         if (subpath == NULL)
161                 strncpy(pathname, basepath, sizeof(pathname));
162         else
163                 snprintf(pathname, sizeof(pathname),
164                                         "%s/%s", basepath, subpath);
165
166         dir = opendir(pathname);
167         if (dir == NULL)
168                 return NULL;
169
170         while ((d = readdir(dir))) {
171                 if (strcmp(d->d_name, ".") == 0 ||
172                                 strcmp(d->d_name, "..") == 0 ||
173                                 strcmp(d->d_name, "posix") == 0 ||
174                                 strcmp(d->d_name, "right") == 0)
175                         continue;
176
177                 switch (d->d_type) {
178                 case DT_REG:
179                         if (subpath == NULL)
180                                 snprintf(pathname, PATH_MAX,
181                                                 "%s/%s", basepath, d->d_name);
182                         else
183                                 snprintf(pathname, PATH_MAX,
184                                                 "%s/%s/%s", basepath,
185                                                         subpath, d->d_name);
186
187                         if (compare_file(src_map, src_st, pathname) == 0) {
188                                 str = g_strdup_printf("%s/%s",
189                                                         subpath, d->d_name);
190                                 closedir(dir);
191                                 return str;
192                         }
193                         break;
194                 case DT_DIR:
195                         if (subpath == NULL)
196                                 strncpy(pathname, d->d_name, sizeof(pathname));
197                         else
198                                 snprintf(pathname, sizeof(pathname),
199                                                 "%s/%s", subpath, d->d_name);
200
201                         str = find_origin(src_map, src_st, basepath, pathname);
202                         if (str != NULL) {
203                                 closedir(dir);
204                                 return str;
205                         }
206                         break;
207                 }
208         }
209
210         closedir(dir);
211
212         return NULL;
213 }
214
215 char *__connman_timezone_lookup(void)
216 {
217         struct stat st;
218         void *map;
219         int fd;
220         char *zone;
221
222         zone = read_key_file(ETC_SYSCONFIG_CLOCK, "ZONE");
223
224         DBG("sysconfig zone %s", zone);
225
226         fd = open(ETC_LOCALTIME, O_RDONLY);
227         if (fd < 0) {
228                 g_free(zone);
229                 return NULL;
230         }
231
232         if (fstat(fd, &st) < 0)
233                 goto done;
234
235         if (S_ISREG(st.st_mode)) {
236                 map = mmap(0, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
237                 if (map == NULL || map == MAP_FAILED) {
238                         g_free(zone);
239                         zone = NULL;
240
241                         goto done;
242                 }
243
244                 if (zone != NULL) {
245                         char pathname[PATH_MAX];
246
247                         snprintf(pathname, PATH_MAX, "%s/%s",
248                                                 USR_SHARE_ZONEINFO, zone);
249
250                         if (compare_file(map, &st, pathname) != 0) {
251                                 g_free(zone);
252                                 zone = NULL;
253                         }
254                 }
255
256                 if (zone == NULL)
257                         zone = find_origin(map, &st, USR_SHARE_ZONEINFO, NULL);
258
259                 munmap(map, st.st_size);
260         } else {
261                 g_free(zone);
262                 zone = NULL;
263         }
264
265 done:
266         close(fd);
267
268         DBG("localtime zone %s", zone);
269
270         return zone;
271 }
272
273 static int write_file(void *src_map, struct stat *src_st, const char *pathname)
274 {
275         struct stat st;
276         int fd;
277         ssize_t written;
278
279         DBG("pathname %s", pathname);
280
281         if (lstat(pathname, &st) == 0) {
282                 if (S_ISLNK(st.st_mode))
283                         unlink(pathname);
284         }
285
286         fd = open(pathname, O_WRONLY | O_CREAT | O_TRUNC, 0644);
287         if (fd < 0)
288                 return -EIO;
289
290         written = write(fd, src_map, src_st->st_size);
291
292         close(fd);
293
294         if (written < 0)
295                 return -EIO;
296
297         return 0;
298 }
299
300 int __connman_timezone_change(const char *zone)
301 {
302         struct stat st;
303         char *map, pathname[PATH_MAX];
304         int fd, err;
305
306         DBG("zone %s", zone);
307
308         snprintf(pathname, PATH_MAX, "%s/%s", USR_SHARE_ZONEINFO, zone);
309
310         fd = open(pathname, O_RDONLY);
311         if (fd < 0)
312                 return -EINVAL;
313
314         if (fstat(fd, &st) < 0) {
315                 close(fd);
316                 return -EIO;
317         }
318
319         map = mmap(0, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
320         if (map == NULL || map == MAP_FAILED) {
321                 close(fd);
322                 return -EIO;
323         }
324
325         err = write_file(map, &st, ETC_LOCALTIME);
326
327         munmap(map, st.st_size);
328
329         close(fd);
330
331         return err;
332 }
333
334 static guint inotify_watch = 0;
335
336 static gboolean inotify_data(GIOChannel *channel, GIOCondition cond,
337                                                         gpointer user_data)
338 {
339         char buffer[256];
340         void *ptr = buffer;
341         GIOStatus status;
342         gsize bytes_read;
343
344         DBG("");
345
346         if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
347                 inotify_watch = 0;
348                 return FALSE;
349         }
350
351         status = g_io_channel_read_chars(channel, buffer, sizeof(buffer),
352                                                         &bytes_read, NULL);
353
354         switch (status) {
355         case G_IO_STATUS_NORMAL:
356                 break;
357         case G_IO_STATUS_AGAIN:
358                 return TRUE;
359         default:
360                 inotify_watch = 0;
361                 return FALSE;
362         }
363
364         DBG("bytes read %zd", bytes_read);
365
366         while (bytes_read > 0) {
367                 struct inotify_event *event = ptr;
368
369                 if (bytes_read < sizeof(*event))
370                         break;
371
372                 ptr += sizeof(*event);
373                 bytes_read -= sizeof(*event);
374
375                 if (event->len == 0)
376                         continue;
377
378                 if (bytes_read < event->len)
379                         break;
380
381                 ptr += event->len;
382                 bytes_read -= event->len;
383
384                 if (g_strcmp0(event->name, "localtime") == 0)
385                         __connman_clock_update_timezone();
386         }
387
388         return TRUE;
389 }
390
391 int __connman_timezone_init(void)
392 {
393         GIOChannel *channel;
394         char *dirname;
395         int fd, wd;
396
397         DBG("");
398
399         fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
400         if (fd < 0)
401                 return -EIO;
402
403         channel = g_io_channel_unix_new(fd);
404         if (channel == NULL) {
405                 close(fd);
406                 return -EIO;
407         }
408
409         g_io_channel_set_close_on_unref(channel, TRUE);
410         g_io_channel_set_encoding(channel, NULL, NULL);
411         g_io_channel_set_buffered(channel, FALSE);
412
413         inotify_watch = g_io_add_watch(channel,
414                                 G_IO_IN | G_IO_HUP | G_IO_NVAL | G_IO_ERR,
415                                 inotify_data, NULL);
416
417         g_io_channel_unref(channel);
418
419         dirname = g_path_get_dirname(ETC_LOCALTIME);
420
421         wd = inotify_add_watch(fd, dirname, IN_DONT_FOLLOW |
422                                                 IN_MODIFY | IN_MOVED_TO);
423
424         g_free(dirname);
425
426         if (wd < 0)
427                 return -EIO;
428
429         return 0;
430 }
431
432 void __connman_timezone_cleanup(void)
433 {
434         DBG("");
435
436         if (inotify_watch > 0) {
437                 g_source_remove(inotify_watch);
438                 inotify_watch = 0;
439         }
440 }