Imported Upstream version 1.29
[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-2014  BMW Car IT GmbH.
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         unsigned int refcount;
39
40         GIOChannel *channel;
41         uint watch;
42         int wd;
43
44         GSList *list;
45 };
46
47 static void cleanup_inotify(gpointer user_data);
48
49 static void connman_inotify_ref(struct connman_inotify *i)
50 {
51         __sync_fetch_and_add(&i->refcount, 1);
52 }
53
54 static void connman_inotify_unref(gpointer data)
55 {
56         struct connman_inotify *i = data;
57
58         if (__sync_fetch_and_sub(&i->refcount, 1) != 1)
59                 return;
60
61         cleanup_inotify(data);
62 }
63
64 static GHashTable *inotify_hash;
65
66 static gboolean inotify_data(GIOChannel *channel, GIOCondition cond,
67                                                         gpointer user_data)
68 {
69         struct connman_inotify *inotify = user_data;
70         char buffer[sizeof(struct inotify_event) + NAME_MAX + 1];
71         char *next_event;
72         gsize bytes_read;
73         GIOStatus status;
74         GSList *list;
75
76         if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
77                 inotify->watch = 0;
78                 return FALSE;
79         }
80
81         status = g_io_channel_read_chars(channel, buffer,
82                                         sizeof(buffer), &bytes_read, NULL);
83
84         switch (status) {
85         case G_IO_STATUS_NORMAL:
86                 break;
87         case G_IO_STATUS_AGAIN:
88                 return TRUE;
89         default:
90                 connman_error("Reading from inotify channel failed");
91                 inotify->watch = 0;
92                 return FALSE;
93         }
94
95         next_event = buffer;
96
97         connman_inotify_ref(inotify);
98
99         while (bytes_read > 0) {
100                 struct inotify_event *event;
101                 gchar *ident;
102                 gsize len;
103
104                 event = (struct inotify_event *) next_event;
105                 if (event->len)
106                         ident = next_event + sizeof(struct inotify_event);
107                 else
108                         ident = NULL;
109
110                 len = sizeof(struct inotify_event) + event->len;
111
112                 /* check if inotify_event block fit */
113                 if (len > bytes_read)
114                         break;
115
116                 next_event += len;
117                 bytes_read -= len;
118
119                 for (list = inotify->list; list; list = list->next) {
120                         inotify_event_cb callback = list->data;
121
122                         (*callback)(event, ident);
123                 }
124         }
125
126         connman_inotify_unref(inotify);
127
128         return TRUE;
129 }
130
131 static int create_watch(const char *path, struct connman_inotify *inotify)
132 {
133         int fd;
134
135         DBG("Add directory watch for %s", path);
136
137         fd = inotify_init();
138         if (fd < 0)
139                 return -EIO;
140
141         inotify->wd = inotify_add_watch(fd, path,
142                                         IN_MODIFY | IN_CREATE | IN_DELETE |
143                                         IN_MOVED_TO | IN_MOVED_FROM);
144         if (inotify->wd < 0) {
145                 connman_error("Creation of %s watch failed", path);
146                 close(fd);
147                 return -EIO;
148         }
149
150         inotify->channel = g_io_channel_unix_new(fd);
151         if (!inotify->channel) {
152                 connman_error("Creation of inotify channel failed");
153                 inotify_rm_watch(fd, inotify->wd);
154                 inotify->wd = 0;
155
156                 close(fd);
157                 return -EIO;
158         }
159
160         g_io_channel_set_close_on_unref(inotify->channel, TRUE);
161         g_io_channel_set_encoding(inotify->channel, NULL, NULL);
162         g_io_channel_set_buffered(inotify->channel, FALSE);
163
164         inotify->watch = g_io_add_watch(inotify->channel,
165                                 G_IO_IN | G_IO_HUP | G_IO_NVAL | G_IO_ERR,
166                                 inotify_data, inotify);
167
168         return 0;
169 }
170
171 static void remove_watch(struct connman_inotify *inotify)
172 {
173         int fd;
174
175         if (!inotify->channel)
176                 return;
177
178         if (inotify->watch > 0)
179                 g_source_remove(inotify->watch);
180
181         fd = g_io_channel_unix_get_fd(inotify->channel);
182
183         if (inotify->wd >= 0)
184                 inotify_rm_watch(fd, inotify->wd);
185
186         g_io_channel_unref(inotify->channel);
187 }
188
189 int connman_inotify_register(const char *path, inotify_event_cb callback)
190 {
191         struct connman_inotify *inotify;
192         int err;
193
194         if (!callback)
195                 return -EINVAL;
196
197         inotify = g_hash_table_lookup(inotify_hash, path);
198         if (inotify)
199                 goto update;
200
201         inotify = g_try_new0(struct connman_inotify, 1);
202         if (!inotify)
203                 return -ENOMEM;
204
205         inotify->refcount = 1;
206         inotify->wd = -1;
207
208         err = create_watch(path, inotify);
209         if (err < 0) {
210                 g_free(inotify);
211                 return err;
212         }
213
214         g_hash_table_replace(inotify_hash, g_strdup(path), inotify);
215
216 update:
217         inotify->list = g_slist_prepend(inotify->list, callback);
218
219         return 0;
220 }
221
222 static void cleanup_inotify(gpointer user_data)
223 {
224         struct connman_inotify *inotify = user_data;
225
226         g_slist_free(inotify->list);
227
228         remove_watch(inotify);
229         g_free(inotify);
230 }
231
232 void connman_inotify_unregister(const char *path, inotify_event_cb callback)
233 {
234         struct connman_inotify *inotify;
235
236         inotify = g_hash_table_lookup(inotify_hash, path);
237         if (!inotify)
238                 return;
239
240         inotify->list = g_slist_remove(inotify->list, callback);
241         if (inotify->list)
242                 return;
243
244         g_hash_table_remove(inotify_hash, path);
245 }
246
247 int __connman_inotify_init(void)
248 {
249         DBG("");
250
251         inotify_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
252                                                 g_free, connman_inotify_unref);
253         return 0;
254 }
255
256 void __connman_inotify_cleanup(void)
257 {
258         DBG("");
259
260         g_hash_table_destroy(inotify_hash);
261 }