service: Disconnect the connecting service when needed
[platform/upstream/connman.git] / src / inotify.c
1 /*
2  *
3  *  Connection Manager
4  *
5  *  Copyright (C) 2007-2012  Intel Corporation. All rights reserved.
6  *  Copyright (C) 2012  BMW Car IT GmbH. All rights reserved.
7  *
8  *  This program is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU General Public License version 2 as
10  *  published by the Free Software Foundation.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program; if not, write to the Free Software
19  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20  *
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include <errno.h>
28 #include <unistd.h>
29 #include <sys/stat.h>
30 #include <dirent.h>
31 #include <sys/inotify.h>
32
33 #include <connman/storage.h>
34
35 #include "connman.h"
36
37 struct connman_inotify {
38         GIOChannel *channel;
39         uint watch;
40         int wd;
41
42         GSList *list;
43 };
44
45 static GHashTable *inotify_hash;
46
47 static gboolean inotify_data(GIOChannel *channel, GIOCondition cond,
48                                                         gpointer user_data)
49 {
50         struct connman_inotify *inotify = user_data;
51         char buffer[256];
52         char *next_event;
53         gsize bytes_read;
54         GIOStatus status;
55         GSList *list;
56
57         if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
58                 inotify->watch = 0;
59                 return FALSE;
60         }
61
62         status = g_io_channel_read_chars(channel, buffer,
63                                         sizeof(buffer) -1, &bytes_read, NULL);
64
65         switch (status) {
66         case G_IO_STATUS_NORMAL:
67                 break;
68         case G_IO_STATUS_AGAIN:
69                 return TRUE;
70         default:
71                 connman_error("Reading from inotify channel failed");
72                 inotify->watch = 0;
73                 return FALSE;
74         }
75
76         next_event = buffer;
77
78         while (bytes_read > 0) {
79                 struct inotify_event *event;
80                 gchar *ident;
81                 gsize len;
82
83                 event = (struct inotify_event *) next_event;
84                 if (event->len)
85                         ident = next_event + sizeof(struct inotify_event);
86                 else
87                         ident = NULL;
88
89                 len = sizeof(struct inotify_event) + event->len;
90
91                 /* check if inotify_event block fit */
92                 if (len > bytes_read)
93                         break;
94
95                 next_event += len;
96                 bytes_read -= len;
97
98                 for (list = inotify->list; list != NULL; list = list->next) {
99                         inotify_event_cb callback = list->data;
100
101                         (*callback)(event, ident);
102                 }
103         }
104
105         return TRUE;
106 }
107
108 static int create_watch(const char *path, struct connman_inotify *inotify)
109 {
110         int fd;
111
112         DBG("Add directory watch for %s", path);
113
114         fd = inotify_init();
115         if (fd < 0)
116                 return -EIO;
117
118         inotify->wd = inotify_add_watch(fd, path,
119                                         IN_MODIFY | IN_CREATE | IN_DELETE |
120                                         IN_MOVED_TO | IN_MOVED_FROM);
121         if (inotify->wd < 0) {
122                 connman_error("Creation of %s watch failed", path);
123                 close(fd);
124                 return -EIO;
125         }
126
127         inotify->channel = g_io_channel_unix_new(fd);
128         if (inotify->channel == NULL) {
129                 connman_error("Creation of inotify channel failed");
130                 inotify_rm_watch(fd, inotify->wd);
131                 inotify->wd = 0;
132
133                 close(fd);
134                 return -EIO;
135         }
136
137         g_io_channel_set_close_on_unref(inotify->channel, TRUE);
138         g_io_channel_set_encoding(inotify->channel, NULL, NULL);
139         g_io_channel_set_buffered(inotify->channel, FALSE);
140
141         inotify->watch = g_io_add_watch(inotify->channel,
142                                 G_IO_IN | G_IO_HUP | G_IO_NVAL | G_IO_ERR,
143                                 inotify_data, inotify);
144
145         return 0;
146 }
147
148 static void remove_watch(struct connman_inotify *inotify)
149 {
150         int fd;
151
152         if (inotify->channel == NULL)
153                 return;
154
155         if (inotify->watch > 0)
156                 g_source_remove(inotify->watch);
157
158         fd = g_io_channel_unix_get_fd(inotify->channel);
159
160         if (inotify->wd >= 0)
161                 inotify_rm_watch(fd, inotify->wd);
162
163         g_io_channel_unref(inotify->channel);
164 }
165
166 int connman_inotify_register(const char *path, inotify_event_cb callback)
167 {
168         struct connman_inotify *inotify;
169         int err;
170
171         if (callback == NULL)
172                 return -EINVAL;
173
174         inotify = g_hash_table_lookup(inotify_hash, path);
175         if (inotify != NULL)
176                 goto update;
177
178         inotify = g_try_new0(struct connman_inotify, 1);
179         if (inotify == NULL)
180                 return -ENOMEM;
181
182         inotify->wd = -1;
183
184         err = create_watch(path, inotify);
185         if (err < 0) {
186                 g_free(inotify);
187                 return err;
188         }
189
190         g_hash_table_replace(inotify_hash, g_strdup(path), inotify);
191
192 update:
193         inotify->list = g_slist_prepend(inotify->list, callback);
194
195         return 0;
196 }
197
198 static void cleanup_inotify(gpointer user_data)
199 {
200         struct connman_inotify *inotify = user_data;
201
202         g_slist_free(inotify->list);
203
204         remove_watch(inotify);
205         g_free(inotify);
206 }
207
208 void connman_inotify_unregister(const char *path, inotify_event_cb callback)
209 {
210         struct connman_inotify *inotify;
211
212         inotify = g_hash_table_lookup(inotify_hash, path);
213         if (inotify == NULL)
214                 return;
215
216         inotify->list = g_slist_remove(inotify->list, callback);
217         if (inotify->list != NULL)
218                 return;
219
220         g_hash_table_remove(inotify_hash, path);
221 }
222
223 int __connman_inotify_init(void)
224 {
225         DBG("");
226
227         inotify_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
228                                                 g_free, cleanup_inotify);
229         return 0;
230 }
231
232 void __connman_inotify_cleanup(void)
233 {
234         DBG("");
235
236         g_hash_table_destroy(inotify_hash);
237 }