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