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