2 * Copyright 2013 Samsung Electronics Co., Ltd
4 * Licensed under the Flora License, Version 1.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://floralicense.org/license/
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
29 #include "mf-search.h"
30 #include "mf-search-internal.h"
31 #include "mf-fs-util.h"
33 #define APPEND_SIZE 2 /* for null and slash */
35 #define NORMALIZE_OPTION G_NORMALIZE_NFD
37 #ifdef CHECK_RESTRICTED_PATH
39 * This code should be revised.
40 * How to get restricted path information?
41 * I think this module should not depend on other lib(except glib and stdlib).
43 #define ROOT_UMS "/opt/usr/media"
44 #define ROOT_MMC "/opt/storage/sdcard"
45 #endif /* CHECK_RESTRICTED_PATH */
47 int flagSearchMsg = 1;
48 pthread_mutex_t gLockSearchMsg;
49 pthread_cond_t gCondSearchMsg;
51 static void __mf_search_tx_wait();
52 static void __mf_search_result_publish_msg(mf_search_pipe_msg_type type, void *result, void *user_data);
54 inline static void __mf_search_cmd_lock(ms_handle_t *handle)
56 if (handle && handle->cmd_lock) {
57 g_mutex_lock(handle->cmd_lock);
62 inline static void __mf_search_cmd_unlock(ms_handle_t *handle)
64 if (handle && handle->cmd_lock) {
65 g_mutex_unlock(handle->cmd_lock);
70 inline static void __mf_search_thread_lock(ms_handle_t *handle)
72 if (handle && handle->thread_mutex) {
73 g_mutex_lock(handle->thread_mutex);
78 inline static void __mf_search_thread_unlock(ms_handle_t *handle)
80 if (handle && handle->thread_mutex) {
81 g_mutex_unlock(handle->thread_mutex);
86 inline static void __mf_search_args_free(ms_args_t *args)
89 if (args->root_path) {
90 g_list_foreach(args->root_path, (GFunc) g_free, NULL);
91 g_list_free(args->root_path);
92 args->root_path = NULL;
103 inline static void __mf_search_result_free(mf_search_result_t *result)
106 if (result->current_dir) {
107 g_free(result->current_dir);
108 result->current_dir = NULL;
110 if (result->dir_list) {
111 g_list_foreach(result->dir_list, (GFunc) g_free, NULL);
112 g_list_free(result->dir_list);
113 result->dir_list = NULL;
115 if (result->file_list) {
116 g_list_foreach(result->file_list, (GFunc) g_free, NULL);
117 g_list_free(result->file_list);
118 result->file_list = NULL;
125 #ifdef CHECK_RESTRICTED_PATH
126 gboolean __mf_search_check_licet_path(const char *path)
128 return (gboolean) (strstr(path, ROOT_UMS) || strstr(path, ROOT_MMC));
130 #endif /*CHECK_RESTRICTED_PATH*/
133 /*This function is for testing and should be revised for performance before applying*/
134 static inline gboolean __has_nonspacing_mark(const char *nstr)
137 const char *p_str = nstr;
138 while (p_str && *p_str) {
140 uc = g_utf8_get_char(p_str);
141 if (g_unichar_type(uc) == G_UNICODE_NON_SPACING_MARK) {
144 p_str = g_utf8_next_char(p_str);
151 static gboolean __mf_search_NFD_strstr(const char *str, const char *needle)
164 n_len = strlen(needle);
173 if (__has_nonspacing_mark(str)) {
174 const char *p_str = str;
175 const char *end = p_str + s_len - n_len;
177 while (p_str && p_str <= end && *p_str) {
178 const char *s = p_str;
179 const char *n = needle;
183 sc = g_utf8_get_char(s);
184 nc = g_utf8_get_char(n);
185 if (g_unichar_type(sc) == G_UNICODE_NON_SPACING_MARK) {
186 if (g_unichar_type(nc) == G_UNICODE_NON_SPACING_MARK) {
190 s = g_utf8_next_char(s);
191 n = g_utf8_next_char(n);
194 s = g_utf8_next_char(s);
196 } else if (sc != nc) {
199 s = g_utf8_next_char(s);
200 n = g_utf8_next_char(n);
209 p_str = g_utf8_next_char(p_str);
212 return (gboolean) (!(!strstr(str, needle)));
217 static GList *__mf_search_do_find(const char *root,
219 mf_search_option option,
222 DIR *directory = NULL;
223 GList *candidate = NULL;
225 char *up_needle = NULL;
226 char *up_name = NULL;
227 char err_buf[MF_ERR_BUF] = {0,};
230 ms_error("handle is NULL");
234 if (!handle->result) {
235 ms_error("handle->result is NULL");
240 ms_error("invaild args");
244 if (!needle && !handle->args->func) {
248 if (!g_file_test(root, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) {
249 ms_error("invaild root_path : %s", root);
253 directory = opendir(root);
255 mf_search_result_t *result = NULL;
256 struct dirent *entry = NULL;
258 result = handle->result;
259 __mf_search_thread_lock(handle);
260 if (result->current_dir) {
261 g_free(result->current_dir);
263 result->current_dir = g_strdup(root);
264 __mf_search_thread_unlock(handle);
265 while ((entry = readdir(directory)) != NULL) {
266 if (!(option & MF_SEARCH_OPT_HIDDEN) && (0 == strncmp(entry->d_name, ".", 1))) {
267 ms_debug("[%s] is hidden file. Skip it", entry->d_name);
271 if (handle->is_stop == TRUE) {
272 mf_debug("break from do find");
276 if (entry->d_type & DT_REG) {
277 if (option & MF_SEARCH_OPT_FILE) {
278 __mf_search_thread_lock(handle);
279 result->total_count++;
280 __mf_search_thread_unlock(handle);
282 up_name = g_utf8_strup(entry->d_name, strlen(entry->d_name));
283 gchar *nor_str = g_utf8_normalize(up_name, -1, NORMALIZE_OPTION);
284 if (handle->args->func) {
286 gssize len = strlen(root) + strlen(entry->d_name) + APPEND_SIZE; /* for null and slash*/
287 path = g_malloc(sizeof(gchar) * len);
289 g_snprintf(path, len, "%s/%s", root, entry->d_name);
290 int category = handle->args->func(nor_str);
291 if (category == handle->args->category) {
292 g_snprintf(path, len, "%s/%s", root, entry->d_name);
294 __mf_search_thread_lock(handle);
295 result->file_list = g_list_append(result->file_list, (gpointer) path);
296 result->is_end = FALSE;
297 __mf_search_thread_unlock(handle);
299 __mf_search_tx_wait();
300 __mf_search_result_publish_msg(MF_SEARCH_PIPE_MSG_RESULT_REPORT, result,
301 handle->args->user_data);
303 /*1 TODO: how can i handle else case?*/
306 up_needle = g_utf8_strup(needle, strlen(needle));
307 /* should we check the return value for further use? */
308 if (__mf_search_NFD_strstr(nor_str, up_needle))
311 gssize len = strlen(root) + strlen(entry->d_name) + APPEND_SIZE; /* for null and slash*/
312 path = g_malloc(sizeof(gchar) * len);
314 g_snprintf(path, len, "%s/%s", root, entry->d_name);
316 __mf_search_thread_lock(handle);
317 result->file_list = g_list_append(result->file_list, (gpointer) path);
318 result->is_end = FALSE;
319 __mf_search_thread_unlock(handle);
321 __mf_search_tx_wait();
322 __mf_search_result_publish_msg(MF_SEARCH_PIPE_MSG_RESULT_REPORT, result,
323 handle->args->user_data);
325 /*1 TODO: how can i handle else case?*/
336 } else if (entry->d_type & DT_DIR) {
340 len = strlen(entry->d_name);
341 /*skip current and upper directory*/
342 if (0 == strncmp(entry->d_name, ".", strlen(".")) || 0 == strncmp(entry->d_name, "..", strlen(".."))) {
345 /* we are not going to search /opt/media/SLP_Debug folder */
346 if ((strlen(result->current_dir) == strlen(PHONE_FOLDER)) && (strcmp(result->current_dir, PHONE_FOLDER) == 0)
347 && (strlen(entry->d_name) == strlen(DEBUG_FOLDER)) && (strcmp(entry->d_name, DEBUG_FOLDER) == 0)) {
348 ms_debug("[%s] is hidden folder. Skip it", entry->d_name);
352 len = strlen(root) + strlen(entry->d_name) + APPEND_SIZE; /* for null and slash */
353 path = g_malloc(sizeof(gchar) * len);
355 g_snprintf(path, len, "%s/%s", root, entry->d_name);
356 candidate = g_list_append(candidate, (gpointer) path);
358 /*1 TODO: how can i handle else case?*/
359 if (option & MF_SEARCH_OPT_DIR) {
360 __mf_search_thread_lock(handle);
361 result->total_count++;
362 __mf_search_thread_unlock(handle);
364 up_name = g_utf8_strup(entry->d_name, strlen(entry->d_name));
365 if (up_name && needle) {
366 up_needle = g_utf8_strup(needle, strlen(needle));
367 gchar *nor_str = g_utf8_normalize(up_name, -1, NORMALIZE_OPTION);
368 if (__mf_search_NFD_strstr(nor_str, up_needle))
370 __mf_search_thread_lock(handle);
371 result->dir_list = g_list_append(result->dir_list, (gpointer) g_strdup(path));
372 result->is_end = FALSE;
373 __mf_search_thread_unlock(handle);
374 __mf_search_tx_wait();
375 __mf_search_result_publish_msg(MF_SEARCH_PIPE_MSG_RESULT_REPORT, result, handle->args->user_data);
390 MF_FILE_ERROR_LOG(err_buf, "open failed", root);
396 static gpointer __mf_search_find_thread(gpointer data)
398 ms_handle_t *handle = (ms_handle_t *) data;
400 ms_args_t *args = NULL;
401 mf_search_result_t *result = NULL;
403 result = handle->result;
406 if (args && result) {
408 GList *candidate = NULL; /*use this list as stack*/
409 root = args->root_path;
411 char *path = (char *)root->data;
413 /*push root paths to stack*/
414 candidate = g_list_append(candidate, (gpointer) g_strdup(path));
416 root = g_list_next(root);
420 GList *new_list = NULL;
424 __mf_search_thread_lock(handle);
425 if (handle->is_stop) {
426 __mf_search_thread_unlock(handle);
427 result->is_end = TRUE;
428 goto MF_FIND_THREAD_EXIT;
430 __mf_search_thread_unlock(handle);
432 list = g_list_first(candidate);
433 /*pop one path from stack*/
434 candidate = g_list_remove_link(candidate, list);
435 item = (gchar *) list->data;
437 ms_debug("current : %s", item);
439 __mf_search_thread_lock(handle);
440 if (result->current_dir) {
441 g_free(result->current_dir);
443 result->current_dir = g_strdup(item);
444 __mf_search_thread_unlock(handle);
446 /*publish root change message here*/
447 __mf_search_tx_wait();
448 if (handle->is_stop) {
449 result->is_end = TRUE;
450 goto MF_FIND_THREAD_EXIT;
452 __mf_search_result_publish_msg(MF_SEARCH_PIPE_MSG_ROOT_CHANGE, item, args->user_data);
453 new_list = __mf_search_do_find(item, args->needle, args->option, handle);
459 /*push new paths to stack*/
460 candidate = g_list_concat(new_list, candidate);
463 __mf_search_thread_lock(handle);
464 result->is_end = TRUE;
465 __mf_search_thread_unlock(handle);
466 __mf_search_tx_wait();
467 __mf_search_result_publish_msg(MF_SEARCH_PIPE_MSG_FINISHED, handle->result, args->user_data);
470 g_list_foreach(candidate, (GFunc) g_free, NULL);
471 g_list_free(candidate);
475 ms_error("args : %p or result : %p is not allocated yet!!", handle->args, handle->result);
478 /*g_thread_exit(NULL);*/
482 int _mf_search_init(ms_handle_t **handle)
485 ms_handle_t *ms_handle = NULL;
490 return MF_SEARCH_ERROR_INVAL_P;
493 ms_handle = g_malloc0(sizeof(ms_handle_t));
494 if (ms_handle == NULL) {
495 ms_error("Fail to allocate memory for handle ");
497 return MF_SEARCH_ERROR_ALLOC;
500 ms_handle->state = MF_SEARCH_STATE_INIT;
501 ms_handle->is_stop = FALSE;
503 lock = g_mutex_new();
505 ms_error("Fail to create cmd_lock");
507 return MF_SEARCH_ERROR_ALLOC;
509 ms_handle->cmd_lock = lock;
513 ms_info("Success to make search handle : %p", ms_handle);
514 return MF_SEARCH_ERROR_NONE;
517 int _mf_search_start(ms_handle_t *handle,
518 const char **root_path,
519 unsigned int path_num,
521 mf_search_option option,
523 mf_search_filter_cb func,
526 ms_args_t *args = NULL;
527 mf_search_result_t *result = NULL;
528 mf_search_option l_opt = MF_SEARCH_OPT_NONE;
529 int ret = MF_SEARCH_ERROR_NONE;
533 ms_error("handle is NULL");
534 return MF_SEARCH_ERROR_INVAL_P;
537 if (handle->state != MF_SEARCH_STATE_INIT) {
538 ms_error("invaild state : %d", handle->state);
539 return MF_SEARCH_ERROR_INVAL_S;
542 if (!root_path || path_num < 1) {
543 ms_error("invaild arguments - root[%p], path_num[%d], needle[%p]", root_path, path_num, needle);
544 return MF_SEARCH_ERROR_INVAL_P;
546 if (!needle && !func) {
547 return MF_SEARCH_ERROR_INVAL_P;
550 __mf_search_cmd_lock(handle);
553 __mf_search_args_free(handle->args);
556 handle->args = args = g_malloc0(sizeof(ms_args_t));
558 ms_error("fail to alloc args");
559 ret = MF_SEARCH_ERROR_ALLOC;
563 if (option == MF_SEARCH_OPT_NONE) {
564 ms_warn("option is MF_SEARCH_OPT_NONE, set all option automatically ");
565 l_opt = MF_SEARCH_OPT_HIDDEN | MF_SEARCH_OPT_DIR | MF_SEARCH_OPT_FILE;
570 for (i = 0; i < path_num; i++) {
571 const char *path = root_path[i];
572 mf_debug("%d th root path is %s", i, path);
573 #ifdef CHECK_RESTRICTED_PATH
574 if (!__mf_search_check_licet_path(path)) {
575 ms_error("%dth root path[%s] is invaild", i, path);
576 ret = MF_SEARCH_ERROR_INVAL_P;
579 #endif /*CHECK_RESTRICTED_PATH*/
580 if (g_file_test(path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)
581 && ((l_opt & MF_SEARCH_OPT_HIDDEN) || strncmp(path, ".", 1))
583 gchar *new_path = NULL;
584 gssize len = strlen(path);
586 if (path[len - 1] == '/') {
587 new_path = g_strndup(path, len - 1);
589 new_path = g_strndup(path, len);
591 args->root_path = g_list_append(args->root_path, (gpointer) new_path);
593 ms_error("Fail to test %dth root path[%s]", i, path);
594 ret = MF_SEARCH_ERROR_INVAL_P;
598 args->user_data = user_data;
601 args->needle = g_utf8_normalize(needle, -1, NORMALIZE_OPTION);
603 ms_error("fail to alloc args->needle");
607 args->option = l_opt;
609 args->category = category;
610 if (handle->result) {
611 __mf_search_result_free(handle->result);
612 handle->result = NULL;
614 handle->result = result = g_malloc0(sizeof(mf_search_result_t));
616 ms_error("fail to alloc result");
617 ret = MF_SEARCH_ERROR_ALLOC;
621 handle->thread_mutex = g_mutex_new();
622 if (!handle->thread_mutex) {
623 ms_error("fail to alloc handle->thread_mutex");
624 ret = MF_SEARCH_ERROR_ALLOC;
628 handle->is_stop = FALSE;
629 handle->result->is_end = FALSE;
631 /*create thread for find item.*/
632 handle->thread_h = g_thread_create(__mf_search_find_thread, handle, TRUE, NULL);
633 if (!handle->thread_h) {
634 ms_error("fail to create __mf_search_find_thread");
635 ret = MF_SEARCH_ERROR_INTERNAL;
638 /*create idler for reporting find result.*/
639 handle->state = MF_SEARCH_STATE_SEARCH;
640 __mf_search_cmd_unlock(handle);
641 return MF_SEARCH_ERROR_NONE;
645 __mf_search_args_free(args);
650 __mf_search_result_free(result);
651 handle->result = NULL;
654 if (handle->thread_mutex) {
655 g_mutex_free(handle->thread_mutex);
656 handle->thread_mutex = NULL;
659 if (handle->thread_h) {
660 __mf_search_thread_lock(handle);
661 handle->is_stop = TRUE;
662 __mf_search_thread_unlock(handle);
663 g_thread_join(handle->thread_h);
664 handle->thread_h = NULL;
666 __mf_search_cmd_unlock(handle);
671 int _mf_search_stop(ms_handle_t *handle)
676 ms_error("handle is NULL");
677 return MF_SEARCH_ERROR_INVAL_P;
680 if (handle->state != MF_SEARCH_STATE_SEARCH) {
681 ms_error("invaild state : %d", handle->state);
682 return MF_SEARCH_ERROR_INVAL_S;
685 __mf_search_cmd_lock(handle);
687 __mf_search_thread_lock(handle);
688 handle->is_stop = TRUE;
689 __mf_search_thread_unlock(handle);
691 pthread_mutex_lock(&gLockSearchMsg);
692 if (flagSearchMsg == 0) {
694 pthread_cond_signal(&gCondSearchMsg);
696 pthread_mutex_unlock(&gLockSearchMsg);
698 if (handle->thread_h) {
699 g_thread_join(handle->thread_h);
700 handle->thread_h = NULL;
703 if (handle->thread_mutex) {
704 g_mutex_free(handle->thread_mutex);
705 handle->thread_mutex = NULL;
709 __mf_search_args_free(handle->args);
712 if (handle->result) {
713 __mf_search_result_free(handle->result);
714 handle->result = NULL;
717 handle->state = MF_SEARCH_STATE_INIT;
718 handle->is_stop = FALSE;
720 __mf_search_cmd_unlock(handle);
722 return MF_SEARCH_ERROR_NONE;
725 void _mf_search_finalize(ms_handle_t **handle)
727 ms_handle_t *ms_handle = *handle;
732 ms_warn("invaild handle");
736 if (ms_handle->state == MF_SEARCH_STATE_SEARCH) {
737 mf_search_stop(ms_handle);
739 /* __mf_search_cmd_lock(ms_handle); */
740 /* __mf_search_cmd_unlock(ms_handle); */
742 if (ms_handle->cmd_lock) {
743 g_mutex_free(ms_handle->cmd_lock);
744 ms_handle->cmd_lock = NULL;
752 /*+++++++++++++++++++++++++ UTIL APIs ++++++++++++++++++++++++++++++ */
753 static void __mf_search_tx_wait()
755 pthread_mutex_lock(&gLockSearchMsg);
756 while (flagSearchMsg == 0) {
757 pthread_cond_wait(&gCondSearchMsg, &gLockSearchMsg);
760 pthread_mutex_unlock(&gLockSearchMsg);
763 static void __mf_search_result_publish_msg(mf_search_pipe_msg_type type, void *result, void *user_data)
765 struct appdata *ap = (struct appdata *)user_data;
766 /*generate message block*/
767 mf_search_pipe_msg msg;
768 memset(&msg, 0, sizeof(mf_search_pipe_msg));
770 msg.mf_sp_msg_type = type;
771 if (msg.mf_sp_msg_type == MF_SEARCH_PIPE_MSG_RESULT_REPORT) {
772 msg.report_result = g_strdup((gchar *) result);
773 msg.current_path = NULL;
774 } else if (msg.mf_sp_msg_type == MF_SEARCH_PIPE_MSG_ROOT_CHANGE) {
775 msg.report_result = NULL;
776 msg.current_path = g_strdup((gchar *) result);
777 mf_debug("current path is %s", msg.current_path);
778 } else if (msg.mf_sp_msg_type == MF_SEARCH_PIPE_MSG_FINISHED) {
779 msg.report_result = result;
780 msg.current_path = NULL;
782 msg.report_result = NULL;
783 msg.current_path = NULL;
786 /*write message to pipe*/
787 ecore_pipe_write(ap->mf_FileOperation.sync_pipe, &msg, sizeof(msg));
790 gchar *_mf_search_result_dir_get(mf_search_result_t * result)
795 list = result->dir_list;
796 if (list && list->data) {
797 gchar *item = (gchar *) list->data;
798 result->dir_list = g_list_remove(list, item);
805 gchar *_mf_search_result_file_get(mf_search_result_t * result)
810 list = result->file_list;
811 if (list && list->data) {
812 gchar *item = (gchar *) list->data;
813 result->file_list = g_list_remove(list, item);
820 gboolean _mf_search_result_is_end(mf_search_result_t *result)
822 gboolean end = FALSE;
824 end = result->is_end;
829 guint _mf_search_result_total_count_get(mf_search_result_t *result)
833 count = result->total_count;
838 gchar *_mf_search_result_current_dir_get(mf_search_result_t * result)
842 if (result->current_dir) {
843 c_dir = result->current_dir;
844 result->current_dir = NULL;