cca2ff68e06ab8ba5a2260483dbc81e1b1626483
[platform/core/pim/contacts-service.git] / common / ctsvc_inotify.c
1 /*
2  * Contacts Service
3  *
4  * Copyright (c) 2010 - 2015 Samsung Electronics Co., Ltd. All rights reserved.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  *
18  */
19 #include <errno.h>
20 #include <fcntl.h>
21 #include <unistd.h>
22 #include <sys/types.h>
23 #include <sys/inotify.h>
24
25 #include "contacts.h"
26 #include "ctsvc_internal.h"
27 #include "ctsvc_notify.h"
28 #include "ctsvc_view.h"
29 #include "ctsvc_handle.h"
30 #include "ctsvc_inotify.h"
31
32 #include <stdbool.h>
33
34 #ifdef _CONTACTS_IPC_CLIENT
35 #include "ctsvc_client_ipc.h"
36 #include "ctsvc_client_utils.h"
37 #endif
38
39 typedef struct {
40         int wd;
41         char *view_uri;
42         contacts_db_changed_cb cb;
43         void *cb_data;
44         bool blocked;
45 } noti_info;
46
47 struct socket_init_noti_info {
48         int wd;
49         int subscribe_count;
50         void (*cb)(void *);
51         void *cb_data;
52 };
53
54 static GHashTable *_ctsvc_socket_init_noti_table = NULL;
55 static int __ctsvc_inoti_ref = 0;
56 static int __inoti_fd = -1;
57 static guint __inoti_handler = 0;
58 static GSList *__noti_list = NULL;
59
60 static inline void __ctsvc_inotify_handle_callback(GSList *noti_list, int wd, uint32_t mask)
61 {
62         noti_info *noti;
63         GSList *it = NULL;
64
65         for (it = noti_list; it; it = it->next) {
66                 noti = (noti_info *)it->data;
67
68                 if (noti->wd == wd) {
69
70 #ifdef _CONTACTS_IPC_CLIENT
71                         if (ctsvc_ipc_is_busy()) {
72                                 /* hold the line */
73                                 noti->blocked = true;
74                                 continue;
75                         }
76 #endif
77                         if ((mask & IN_CLOSE_WRITE) && noti->cb) {
78                                 DBG("%s", noti->view_uri);
79                                 noti->cb(noti->view_uri, noti->cb_data);
80                         }
81                 }
82         }
83 }
84
85 static void _ctsvc_inotify_socket_init_noti_table_foreach_cb(gpointer key, gpointer value, gpointer user_data)
86 {
87         struct socket_init_noti_info *noti_info = value;
88
89         int wd = GPOINTER_TO_INT(user_data);
90         if (noti_info->wd == wd)
91                 noti_info->cb(noti_info->cb_data);
92 }
93
94 static gboolean __ctsvc_inotify_gio_cb(GIOChannel *src, GIOCondition cond, gpointer data)
95 {
96         int fd, ret;
97         struct inotify_event ie;
98         char name[FILENAME_MAX] = {0};
99
100         fd = g_io_channel_unix_get_fd(src);
101
102         while (0 < (ret = read(fd, &ie, sizeof(ie)))) {
103                 if (sizeof(ie) == ret) {
104                         if (_ctsvc_socket_init_noti_table)
105                                 g_hash_table_foreach(_ctsvc_socket_init_noti_table, _ctsvc_inotify_socket_init_noti_table_foreach_cb, GINT_TO_POINTER(ie.wd));
106
107                         if (__noti_list)
108                                 __ctsvc_inotify_handle_callback(__noti_list, ie.wd, ie.mask);
109
110                         while (0 != ie.len) {
111                                 ret = read(fd, name, (ie.len < sizeof(name)) ? ie.len : sizeof(name));
112                                 if (-1 == ret) {
113                                         if (EINTR == errno)
114                                                 continue;
115                                         else
116                                                 return TRUE;
117                                 }
118                                 if (ie.len < ret)
119                                         ie.len = 0;
120                                 else
121                                         ie.len -= ret;
122                         }
123                 } else {
124                         while (ret < sizeof(ie)) {
125                                 int read_size;
126                                 read_size = read(fd, name, sizeof(ie)-ret);
127                                 if (-1 == read_size) {
128                                         if (EINTR == errno)
129                                                 continue;
130                                         else
131                                                 return TRUE;
132                                 }
133                                 ret += read_size;
134                         }
135                 }
136         }
137
138         return TRUE;
139 }
140
141 static inline int __ctsvc_inotify_attach_handler(int fd)
142 {
143         guint ret;
144         GIOChannel *channel;
145
146         RETVM_IF(fd < 0, CONTACTS_ERROR_INVALID_PARAMETER, "fd is invalid");
147
148         channel = g_io_channel_unix_new(fd);
149         RETVM_IF(NULL == channel, CONTACTS_ERROR_SYSTEM, "System: g_io_channel_unix_new() Fail");
150
151         g_io_channel_set_flags(channel, G_IO_FLAG_NONBLOCK, NULL);
152
153         ret = g_io_add_watch(channel, G_IO_IN, __ctsvc_inotify_gio_cb, NULL);
154         g_io_channel_unref(channel);
155
156         return ret;
157 }
158
159 int ctsvc_inotify_init(void)
160 {
161         int ret;
162
163         if (0 < __ctsvc_inoti_ref) {
164                 __ctsvc_inoti_ref++;
165                 return CONTACTS_ERROR_NONE;
166         }
167         __inoti_fd = inotify_init();
168         RETVM_IF(-1 == __inoti_fd, CONTACTS_ERROR_SYSTEM,
169                         "System: inotify_init() Fail(%d)", errno);
170
171         ret = fcntl(__inoti_fd, F_SETFD, FD_CLOEXEC);
172         WARN_IF(ret < 0, "fcntl Fail(%d)", ret);
173         ret = fcntl(__inoti_fd, F_SETFL, O_NONBLOCK);
174         WARN_IF(ret < 0, "fcntl Fail(%d)", ret);
175
176         __inoti_handler = __ctsvc_inotify_attach_handler(__inoti_fd);
177         if (__inoti_handler <= 0) {
178                 /* LCOV_EXCL_START */
179                 ERR("__ctsvc_inotify_attach_handler() Fail");
180                 close(__inoti_fd);
181                 __inoti_fd = -1;
182                 __inoti_handler = 0;
183                 return CONTACTS_ERROR_SYSTEM;
184                 /* LCOV_EXCL_STOP */
185         }
186
187         __ctsvc_inoti_ref++;
188         return CONTACTS_ERROR_NONE;
189 }
190
191 static inline int __ctsvc_inotify_get_wd(int fd, const char *notipath)
192 {
193         return inotify_add_watch(fd, notipath, IN_ACCESS);
194 }
195
196 static inline int __ctsvc_inotify_watch(int fd, const char *notipath)
197 {
198         int ret;
199
200         ret = inotify_add_watch(fd, notipath, IN_CLOSE_WRITE);
201         RETVM_IF(-1 == ret, CONTACTS_ERROR_SYSTEM,
202                         "System: inotify_add_watch() Fail(%d)", errno);
203
204         return CONTACTS_ERROR_NONE;
205 }
206
207 static int __ctsvc_noti_get_file_path(const char *view_uri, char **path)
208 {
209         ctsvc_record_type_e match = ctsvc_view_get_record_type(view_uri);
210         char *file = NULL;
211
212         switch ((int)match) {
213         case CTSVC_RECORD_ADDRESSBOOK:
214                 file = CTSVC_NOTI_ADDRESSBOOK_CHANGED;
215                 break;
216         case CTSVC_RECORD_GROUP:
217                 file = CTSVC_NOTI_GROUP_CHANGED;
218                 break;
219         case CTSVC_RECORD_PERSON:
220                 file = CTSVC_NOTI_PERSON_CHANGED;
221                 break;
222         case CTSVC_RECORD_CONTACT:
223         case CTSVC_RECORD_SIMPLE_CONTACT:
224                 file = CTSVC_NOTI_CONTACT_CHANGED;
225                 break;
226         case CTSVC_RECORD_MY_PROFILE:
227                 file = CTSVC_NOTI_MY_PROFILE_CHANGED;
228                 break;
229         case CTSVC_RECORD_NAME:
230                 file = CTSVC_NOTI_NAME_CHANGED;
231                 break;
232         case CTSVC_RECORD_COMPANY:
233                 file = CTSVC_NOTI_COMPANY_CHANGED;
234                 break;
235         case CTSVC_RECORD_NOTE:
236                 file = CTSVC_NOTI_NOTE_CHANGED;
237                 break;
238         case CTSVC_RECORD_NUMBER:
239                 file = CTSVC_NOTI_NUMBER_CHANGED;
240                 break;
241         case CTSVC_RECORD_EMAIL:
242                 file = CTSVC_NOTI_EMAIL_CHANGED;
243                 break;
244         case CTSVC_RECORD_URL:
245                 file = CTSVC_NOTI_URL_CHANGED;
246                 break;
247         case CTSVC_RECORD_EVENT:
248                 file = CTSVC_NOTI_EVENT_CHANGED;
249                 break;
250         case CTSVC_RECORD_NICKNAME:
251                 file = CTSVC_NOTI_NICKNAME_CHANGED;
252                 break;
253         case CTSVC_RECORD_ADDRESS:
254                 file = CTSVC_NOTI_ADDRESS_CHANGED;
255                 break;
256         case CTSVC_RECORD_MESSENGER:
257                 file = CTSVC_NOTI_MESSENGER_CHANGED;
258                 break;
259         case CTSVC_RECORD_GROUP_RELATION:
260                 file = CTSVC_NOTI_GROUP_RELATION_CHANGED;
261                 break;
262         case CTSVC_RECORD_ACTIVITY:
263                 file = CTSVC_NOTI_ACTIVITY_CHANGED;
264                 break;
265         case CTSVC_RECORD_ACTIVITY_PHOTO:
266                 file = CTSVC_NOTI_ACTIVITY_PHOTO_CHANGED;
267                 break;
268         case CTSVC_RECORD_PROFILE:
269                 file = CTSVC_NOTI_PROFILE_CHANGED;
270                 break;
271         case CTSVC_RECORD_RELATIONSHIP:
272                 file = CTSVC_NOTI_RELATIONSHIP_CHANGED;
273                 break;
274         case CTSVC_RECORD_IMAGE:
275                 file = CTSVC_NOTI_IMAGE_CHANGED;
276                 break;
277         case CTSVC_RECORD_EXTENSION:
278                 file = CTSVC_NOTI_DATA_CHANGED;
279                 break;
280         case CTSVC_RECORD_PHONELOG:
281                 file = CTSVC_NOTI_PHONELOG_CHANGED;
282                 break;
283         case CTSVC_RECORD_SPEEDDIAL:
284                 file = CTSVC_NOTI_SPEEDDIAL_CHANGED;
285                 break;
286         case CTSVC_RECORD_SDN:
287                 file = CTSVC_NOTI_SDN_CHANGED;
288                 break;
289         case CTSVC_RECORD_SIP:
290                 file = CTSVC_NOTI_SIP_CHANGED;
291                 break;
292         case CTSVC_RECORD_RESULT:
293         default:
294                 /* LCOV_EXCL_START */
295                 ERR("The type(%s) is not supported", view_uri);
296                 return CONTACTS_ERROR_INVALID_PARAMETER;
297                 /* LCOV_EXCL_STOP */
298         }
299
300         *path = ctsvc_inotify_makepath(file);
301         if (NULL == *path) {
302                 ERR("ctsvc_inotify_makepath() fail");
303                 return CONTACTS_ERROR_SYSTEM;
304         }
305
306         return CONTACTS_ERROR_NONE;
307 }
308
309 int ctsvc_inotify_subscribe_ipc_ready(void (*cb)(void *), void *user_data)
310 {
311         char *noti_path = ctsvc_inotify_makepath(CTSVC_NOTI_IPC_READY);
312         struct socket_init_noti_info *noti_info = NULL;
313
314         if (NULL == noti_path) {
315                 ERR("ctsvc_inotify_makepath() fail");
316                 return CONTACTS_ERROR_SYSTEM;
317         }
318
319         if (NULL == _ctsvc_socket_init_noti_table)
320                 _ctsvc_socket_init_noti_table = g_hash_table_new_full(g_str_hash, g_str_equal, free, free);
321         else
322                 noti_info = g_hash_table_lookup(_ctsvc_socket_init_noti_table, noti_path);
323
324         if (NULL == noti_info) {
325                 int wd = __ctsvc_inotify_get_wd(__inoti_fd, noti_path);
326                 if (-1 == wd) {
327                         /* LCOV_EXCL_START */
328                         ERR("__ctsvc_inotify_get_wd() Fail(noti_path=%s, errno : %d)", noti_path, errno);
329                         free(noti_path);
330                         if (errno == EACCES)
331                                 return CONTACTS_ERROR_PERMISSION_DENIED;
332                         return CONTACTS_ERROR_SYSTEM;
333                         /* LCOV_EXCL_STOP */
334                 }
335
336                 int ret = __ctsvc_inotify_watch(__inoti_fd, noti_path);
337                 if (CONTACTS_ERROR_NONE != ret) {
338                         /* LCOV_EXCL_START */
339                         ERR("__ctsvc_inotify_watch() Fail");
340                         free(noti_path);
341                         return ret;
342                         /* LCOV_EXCL_STOP */
343                 }
344
345                 noti_info = calloc(1, sizeof(struct socket_init_noti_info));
346                 if (NULL == noti_info) {
347                         /* LCOV_EXCL_START */
348                         ERR("calloc() return NULL");
349                         free(noti_path);
350                         return ret;
351                         /* LCOV_EXCL_STOP */
352                 }
353
354                 noti_info->wd = wd;
355                 noti_info->cb = cb;
356                 noti_info->cb_data = user_data;
357                 g_hash_table_insert(_ctsvc_socket_init_noti_table, noti_path, noti_info);
358         } else {
359                 free(noti_path);
360         }
361         noti_info->subscribe_count++;
362         return CONTACTS_ERROR_NONE;
363 }
364
365 int ctsvc_inotify_unsubscribe_ipc_ready()
366 {
367         RETV_IF(NULL == _ctsvc_socket_init_noti_table, CONTACTS_ERROR_INVALID_PARAMETER);
368
369         char *noti_path = ctsvc_inotify_makepath(CTSVC_NOTI_IPC_READY);
370         struct socket_init_noti_info *noti_info = NULL;
371
372         if (NULL == noti_path) {
373                 ERR("ctsvc_inotify_makepath() fail");
374                 return CONTACTS_ERROR_SYSTEM;
375         }
376
377         noti_info = g_hash_table_lookup(_ctsvc_socket_init_noti_table, noti_path);
378         if (NULL == noti_info) {
379                 /* LCOV_EXCL_START */
380                 ERR("g_hash_table_lookup() return NULL");
381                 free(noti_path);
382                 return CONTACTS_ERROR_INVALID_PARAMETER;
383                 /* LCOV_EXCL_STOP */
384         }
385
386         if (1 == noti_info->subscribe_count) {
387                 int wd = noti_info->wd;
388                 inotify_rm_watch(__inoti_fd, wd);
389                 /* free noti_info automatically */
390                 g_hash_table_remove(_ctsvc_socket_init_noti_table, noti_path);
391         } else {
392                 noti_info->subscribe_count--;
393         }
394
395         free(noti_path);
396         return CONTACTS_ERROR_NONE;
397 }
398
399
400 int ctsvc_inotify_subscribe(const char *view_uri, contacts_db_changed_cb cb, void *data)
401 {
402         int ret, wd;
403         noti_info *noti, *same_noti = NULL;
404         GSList *it;
405         char *path = NULL;
406
407         RETV_IF(NULL == cb, CONTACTS_ERROR_INVALID_PARAMETER);
408         RETVM_IF(__inoti_fd < 0, CONTACTS_ERROR_SYSTEM,
409                         "__inoti_fd(%d) is invalid", __inoti_fd);
410
411         ret = __ctsvc_noti_get_file_path(view_uri, &path);
412         if (CONTACTS_ERROR_NONE != ret) {
413                 ERR("__ctsvc_noti_get_file_path() fail(%d)", ret);
414                 return ret;
415         }
416
417         wd = __ctsvc_inotify_get_wd(__inoti_fd, path);
418         if (-1 == wd) {
419                 /* LCOV_EXCL_START */
420                 ERR("__ctsvc_inotify_get_wd() Fail(errno : %d)", errno);
421                 free(path);
422                 if (errno == EACCES)
423                         return CONTACTS_ERROR_PERMISSION_DENIED;
424                 return CONTACTS_ERROR_SYSTEM;
425                 /* LCOV_EXCL_STOP */
426         }
427
428         for (it = __noti_list; it; it = it->next) {
429                 if (it->data) {
430                         same_noti = it->data;
431                         if (same_noti->wd == wd && same_noti->cb == cb
432                                         && STRING_EQUAL == strcmp(same_noti->view_uri, view_uri)
433                                         && same_noti->cb_data == data) {
434                                 break;
435                         } else {
436                                 same_noti = NULL;
437                         }
438                 }
439         }
440
441         if (same_noti) {
442                 __ctsvc_inotify_watch(__inoti_fd, path);
443                 /* LCOV_EXCL_START */
444                 ERR("The same callback(%s) is already exist", view_uri);
445                 free(path);
446                 return CONTACTS_ERROR_INVALID_PARAMETER;
447                 /* LCOV_EXCL_STOP */
448         }
449
450         ret = __ctsvc_inotify_watch(__inoti_fd, path);
451         free(path);
452         RETVM_IF(CONTACTS_ERROR_NONE != ret, ret, "__ctsvc_inotify_watch() Fail");
453
454         noti = calloc(1, sizeof(noti_info));
455         RETVM_IF(NULL == noti, CONTACTS_ERROR_OUT_OF_MEMORY, "calloc() Fail");
456
457         noti->wd = wd;
458         noti->view_uri = strdup(view_uri);
459         noti->cb_data = data;
460         noti->cb = cb;
461         noti->blocked = false;
462
463         __noti_list = g_slist_append(__noti_list, noti);
464
465         return CONTACTS_ERROR_NONE;
466 }
467
468 static inline int __ctsvc_del_noti(GSList **noti_list, int wd, const char *view_uri, void *cb, void *user_data)
469 {
470         int del_cnt, remain_cnt;
471         GSList *it, *result;
472
473         del_cnt = 0;
474         remain_cnt = 0;
475
476         it = result = *noti_list;
477         while (it) {
478                 noti_info *noti = it->data;
479                 if (noti && wd == noti->wd) {
480                         if (cb == noti->cb && user_data == noti->cb_data
481                                         && STRING_EQUAL == strcmp(noti->view_uri, view_uri)) {
482                                 it = it->next;
483                                 result = g_slist_remove(result, noti);
484                                 free(noti->view_uri);
485                                 free(noti);
486                                 del_cnt++;
487                                 continue;
488                         } else {
489                                 remain_cnt++;
490                         }
491                 }
492                 it = it->next;
493         }
494         RETVM_IF(del_cnt == 0, CONTACTS_ERROR_NO_DATA, "No Data: nothing deleted, remain_cnt : %d", remain_cnt);
495
496         *noti_list = result;
497
498         return remain_cnt;
499 }
500
501 int ctsvc_inotify_unsubscribe(const char *view_uri, contacts_db_changed_cb cb, void *user_data)
502 {
503         int ret, wd;
504         char *path = NULL;
505
506         RETV_IF(NULL == cb, CONTACTS_ERROR_INVALID_PARAMETER);
507         RETVM_IF(__inoti_fd < 0, CONTACTS_ERROR_SYSTEM,
508                         "System : __inoti_fd(%d) is invalid", __inoti_fd);
509
510         ret = __ctsvc_noti_get_file_path(view_uri, &path);
511         if (CONTACTS_ERROR_NONE != ret) {
512                 ERR("__ctsvc_noti_get_file_path() fail(%d)", ret);
513                 return ret;
514         }
515
516         wd = __ctsvc_inotify_get_wd(__inoti_fd, path);
517         if (-1 == wd) {
518                 /* LCOV_EXCL_START */
519                 ERR("__ctsvc_inotify_get_wd() Fail(errno : %d)", errno);
520                 free(path);
521                 if (errno == EACCES)
522                         return CONTACTS_ERROR_PERMISSION_DENIED;
523                 return CONTACTS_ERROR_SYSTEM;
524                 /* LCOV_EXCL_STOP */
525         }
526
527         ret = __ctsvc_del_noti(&__noti_list, wd, view_uri, cb, user_data);
528         WARN_IF(ret < CONTACTS_ERROR_NONE, "__ctsvc_del_noti() Fail(%d)", ret);
529
530         if (0 == ret) {
531                 free(path);
532                 return inotify_rm_watch(__inoti_fd, wd);
533         }
534
535         ret =  __ctsvc_inotify_watch(__inoti_fd, path);
536         free(path);
537         return ret;
538 }
539
540 static void __clear_nslot_list(gpointer data, gpointer user_data)
541 {
542         noti_info *noti = (noti_info *)data;
543
544         free(noti->view_uri);
545         free(noti);
546 }
547
548 static inline gboolean __ctsvc_inotify_detach_handler(guint id)
549 {
550         return g_source_remove(id);
551 }
552
553 void ctsvc_inotify_close(void)
554 {
555         if (1 < __ctsvc_inoti_ref) {
556                 DBG("inotify ref count : %d", __ctsvc_inoti_ref);
557                 __ctsvc_inoti_ref--;
558                 return;
559         } else if (__ctsvc_inoti_ref < 1) {
560                 DBG("Please call connection API. inotify ref count : %d", __ctsvc_inoti_ref);
561                 return;
562         }
563
564         __ctsvc_inoti_ref--;
565
566         if (__inoti_handler) {
567                 __ctsvc_inotify_detach_handler(__inoti_handler);
568                 __inoti_handler = 0;
569         }
570
571         if (__noti_list) {
572                 g_slist_foreach(__noti_list, __clear_nslot_list, NULL);
573                 g_slist_free(__noti_list);
574                 __noti_list = NULL;
575         }
576
577         if (0 <= __inoti_fd) {
578                 close(__inoti_fd);
579                 __inoti_fd = -1;
580         }
581 }
582
583 char*  ctsvc_inotify_makepath(const char *file)
584 {
585         RETV_IF(NULL == file, NULL);
586
587         static int user_id_max = 10;
588         int path_len = 0;
589         char *path = NULL;
590         uid_t uid = getuid();
591
592 #ifdef _CONTACTS_IPC_CLIENT
593         if (ctsvc_client_is_in_system_session()) {
594                 if (CONTACTS_ERROR_NONE != ctsvc_client_get_active_uid(&uid))
595                         return NULL;
596         }
597 #endif
598
599         path_len = strlen(CTSVC_NOTI_PATH) + user_id_max + strlen(file) + 1;
600         path = calloc(1, path_len);
601         if (NULL == path) {
602                 ERR("calloc() fail");
603                 return NULL;
604         }
605
606         snprintf(path, path_len, CTSVC_NOTI_PATH"/%s", uid, file);
607         DBG("%s", path);
608         return path;
609 }
610