From 94b941f2ee66702934f3d7168faa7c5b166a0aa5 Mon Sep 17 00:00:00 2001 From: Yunmi Ha Date: Thu, 31 Oct 2019 14:28:24 +0900 Subject: [PATCH] Add CleanupStorage Function Refer to the config file, delete the data according to the storage level. config: /etc/storaged/cleanup-storage.conf Change-Id: I589c7a03fa65ac17a62d12bcd301510485ea8731 Signed-off-by: Yunmi Ha --- packaging/storaged.spec | 1 + src/storage/CMakeLists.txt | 2 + src/storage/cleanup.c | 314 ++++++++++++++++++++++++++++++++ src/storage/cleanup.h | 30 +++ src/storage/cleanup_config.c | 311 +++++++++++++++++++++++++++++++ src/storage/cleanup_config.h | 45 +++++ src/storage/sample-cleanup-storage.conf | 30 +++ src/storage/storage.c | 6 + 8 files changed, 739 insertions(+) create mode 100644 src/storage/cleanup.c create mode 100644 src/storage/cleanup.h create mode 100644 src/storage/cleanup_config.c create mode 100644 src/storage/cleanup_config.h create mode 100644 src/storage/sample-cleanup-storage.conf diff --git a/packaging/storaged.spec b/packaging/storaged.spec index a4e8e0f..78ec20d 100644 --- a/packaging/storaged.spec +++ b/packaging/storaged.spec @@ -28,6 +28,7 @@ BuildRequires: pkgconfig(app2sd) BuildRequires: pkgconfig(blkid) BuildRequires: pkgconfig(mount) BuildRequires: pkgconfig(libsyscommon) +BuildRequires: pkgconfig(json-c) BuildRequires: pkgconfig(capi-ui-efl-util) BuildRequires: pkgconfig(capi-appfw-application) diff --git a/src/storage/CMakeLists.txt b/src/storage/CMakeLists.txt index 43a614b..0b62347 100644 --- a/src/storage/CMakeLists.txt +++ b/src/storage/CMakeLists.txt @@ -6,6 +6,7 @@ pkg_check_modules(${PROJECT_NAME}_pkgs REQUIRED dlog gio-2.0 glib-2.0 + json-c storage vconf libsyscommon @@ -19,6 +20,7 @@ SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -Wall -Werror -fvisibility=hidden -rdynamic") SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EXTRA_CFLAGS} -fPIC") SET(CMAKE_C_FLAGS_DEBUG "-O0 -g") +INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/src/storage) INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/src/shared) FILE(GLOB ALL_SRCS "*.c") diff --git a/src/storage/cleanup.c b/src/storage/cleanup.c new file mode 100644 index 0000000..af6e0ec --- /dev/null +++ b/src/storage/cleanup.c @@ -0,0 +1,314 @@ +/* + * Copyright (c) 2019, Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "cleanup.h" +#include "cleanup_config.h" + +enum cleanup_running_type { + CLEANUP_TYPE_NONE = 0, + CLEANUP_TYPE_SYSTEM, + CLEANUP_TYPE_USER +}; + +struct cleanup_request { + int cleanup_mode; + int level; +}; + +#define REMOVE(path) { \ + if(remove(path) != 0) { \ + _E("Failed to remove(%s): %d", path, errno); \ + return -errno; \ + } \ + _D("Remove (%s)", path); \ +} + +#define ASSERT(ret, format, arg...) {\ + if (!ret) { \ + _E(format, ##arg); \ + assert(ret); \ + } \ +} + +static pthread_mutex_t mutex_cancel; +static pthread_mutex_t mutex_lock; +static pthread_t cleanup_th = 0; +static GList *request_queue = NULL; +static int cleanup_canceled = 0; + +static int is_cleanup_canceled() +{ + int ret = 0; + ASSERT((pthread_mutex_lock(&mutex_cancel) == 0), "Assert: Failed to pthread_mutex_lock for cancel."); + ret = cleanup_canceled; + pthread_mutex_unlock(&mutex_cancel); + + return ret; +} + +static void cleanup_cancel() +{ + ASSERT((pthread_mutex_lock(&mutex_cancel) == 0), "Assert: Failed to pthread_mutex_lock for cancel."); + cleanup_canceled = 1; + pthread_mutex_unlock(&mutex_cancel); +} + +static int find_sub_except_item(const char *item, const char *path) +{ + if (strstr(item, path)) { + _D("Find except item: %s, path: %s", item, path); + return 0; + } + + return -1; +} + +static int remove_path(const char *path, GList *except) +{ + if (!path) + return -EINVAL; + + if (!except) + REMOVE(path) + else if (except && !g_list_find_custom(except, path, (GCompareFunc)find_sub_except_item)) + REMOVE(path); + + return 0; +} + +static int cleanup_recursive(const char *path, GList *except) +{ + DIR *dir = NULL; + struct dirent *dent; + struct stat fstat; + char sub_path[PATH_MAX] = {0,}; + int ret; + + if (except && g_list_find_custom(except, path, (GCompareFunc)strcmp)) + return 0; + + if (is_cleanup_canceled()) + return 0; + + ret = lstat(path, &fstat); + if (ret) + return -EINVAL; + + if ((fstat.st_mode & S_IFMT) == S_IFDIR) { + if (access(path, W_OK) != 0) { + _E("Failed to access file status(%s).", path); + return -EINVAL; + } + + dir = opendir(path); + if (!dir) { + _E("Failed to open dir(%s).", path); + return -EINVAL; + } + + while((dent = readdir(dir))) { + if(strcmp(dent->d_name, ".") == 0 || + strcmp(dent->d_name, "..") == 0) + continue; + + if (is_cleanup_canceled()) { + ret = -EINVAL; + break; + } + + if (PATH_MAX <= (strlen(path) + strlen(dent->d_name) + 1)) { + _E("File path sould be shorter than %d. But %d.", PATH_MAX, strlen(path) + strlen(dent->d_name) + 1); + continue; + } + + snprintf(sub_path, PATH_MAX, "%s/%s", path, dent->d_name); + ret = cleanup_recursive(sub_path, except); + if (ret != 0) + break; + } + closedir(dir); + if (!ret) + ret = remove_path(path, except); + } else + REMOVE(path); + + return ret; +} + +static int add_request_queue(int type, int level) +{ + int ret = 0; + struct cleanup_request *request = NULL, *item = NULL; + GList *list = NULL; + + ASSERT((pthread_mutex_lock(&mutex_lock) == 0), "Assert: Failed to pthread_mutex_lock."); + + for (list = g_list_first(request_queue); list != NULL; list = g_list_next(list)) { + item = list->data; + + if ((item->cleanup_mode == type) && (item->level == level)) { + _D("cleanup request is already added."); + goto cleanup; + } + } + + request = malloc(sizeof(struct cleanup_request)); + if (!request) { + ret = -ENOMEM; + goto cleanup; + } + + request->cleanup_mode = type; + request->level = level; + request_queue = g_list_append(request_queue, request); + +cleanup: + pthread_mutex_unlock(&mutex_lock); + return ret; +} + +static void remove_request_queue(struct cleanup_request *item) +{ + ASSERT((pthread_mutex_lock(&mutex_lock) == 0), "Assert: Failed to pthread_mutex_lock."); + + request_queue = g_list_remove(request_queue, item); + free(item); + pthread_mutex_unlock(&mutex_lock); +} + +static struct cleanup_request *get_request_queue() +{ + GList *list = NULL; + + ASSERT((pthread_mutex_lock(&mutex_lock) == 0), "Assert: Failed to pthread_mutex_lock."); + + list = g_list_first(request_queue); + pthread_mutex_unlock(&mutex_lock); + + return (list)? list->data :NULL; +} + +static GList *get_cleanup_config(int type) +{ + if (type == CLEANUP_TYPE_SYSTEM) + return get_cleanup_config_system(); + else if (type == CLEANUP_TYPE_USER) + return get_cleanup_config_user(); + + return NULL; +} + +// Called by thread +void *cleanup_storage_start(void *arg) +{ + GList *list_config = NULL; + GList *list = NULL; + struct cleanup_config *config = NULL; + struct cleanup_request *request = NULL; + + pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); + while(true) { + request = get_request_queue(); + if (!request) { + _D("There is no more cleanup request."); + break; + } + + _D("Cleanup: mode=%d, level=%d", request->cleanup_mode, request->level); + list_config = get_cleanup_config(request->cleanup_mode); + for (list= g_list_first(list_config); NULL != list; list = g_list_next(list)) { + config = list->data; + if (request->level <= config->level) + cleanup_recursive(config->path, config->exclusion_list); + } + + remove_request_queue(request); + } + cleanup_th = 0; + _D("Cleanup thread exit."); + return NULL; +} + +void cleanup_storage(enum tzplatform_variable path_id, int level) +{ + bool bth = (get_request_queue() == NULL); + int type = CLEANUP_TYPE_NONE; + int ret; + + if (path_id == TZ_SYS_OPT) + type = CLEANUP_TYPE_SYSTEM; + else if (path_id == TZ_SYS_USER) + type = CLEANUP_TYPE_USER; + else { + _D("Not supported path type: %d", path_id); + return ; + } + + if (!get_cleanup_config(type)) { + _D("There is no list for cleanup."); + return ; + } + + if (add_request_queue(type, level) != 0) { + _E("Failed to add request."); + return ; + } + //_D("Add cleanup request.(type:%d, level:%d)", type, level); + + if (bth) { + ret = pthread_create(&cleanup_th, NULL, cleanup_storage_start, NULL); + if (ret != 0) + _E("Failed to start pthread: %d", ret); + } +} + +void init_cleanup_storage() +{ + load_cleanup_config(); + + pthread_mutex_init(&mutex_lock, NULL); + pthread_mutex_init(&mutex_cancel, NULL); +} + +void free_cleanup_storage() +{ + ASSERT((pthread_mutex_lock(&mutex_lock) == 0), "Assert: Failed to pthread_mutex_lock."); + if (request_queue) + g_list_free_full(request_queue, free); + request_queue = NULL; + pthread_mutex_unlock(&mutex_lock); + + if (cleanup_th) { + _D("Cancel cleanup thread %d.", (int)cleanup_th); + cleanup_cancel(); + //pthread_cancel(cleanup_th); + pthread_join(cleanup_th, NULL); + _D("Exit cleanup thread %d.", (int)cleanup_th); + } + + free_cleanup_config(); +} diff --git a/src/storage/cleanup.h b/src/storage/cleanup.h new file mode 100644 index 0000000..46bfaa1 --- /dev/null +++ b/src/storage/cleanup.h @@ -0,0 +1,30 @@ +/* + * storaged + * + * Copyright (c) 2019 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __STORAGED_CLEANUP_H__ +#define __STORAGED_CLEANUP_H__ + +#include +#include + +void init_cleanup_storage(); +void free_cleanup_storage(); + +void cleanup_storage(enum tzplatform_variable path_id, int level); + +#endif /* #define __STORAGED_CLEANUP_H___ */ diff --git a/src/storage/cleanup_config.c b/src/storage/cleanup_config.c new file mode 100644 index 0000000..2717992 --- /dev/null +++ b/src/storage/cleanup_config.c @@ -0,0 +1,311 @@ +/* + * Copyright (c) 2019, Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#include "log.h" +#include "cleanup_config.h" + +char *cleanup_level_str[] = { + "full", + "critical", + "warning", + "normal" +}; + +#define DEF_CLEANUP_CONFIG_FILE "/etc/storaged/cleanup-storage.conf" +#define RESULT(ret, val) \ +{ \ + if (ret) \ + *ret = val; \ +} + +static GList *cleanup_list_system = NULL; +static GList *cleanup_list_user = NULL; + +static void free_config_item(void *data) +{ + struct cleanup_config *item = data; + + if (item->path) { + free(item->path); + item->path = NULL; + } + + if (item->exclusion_list) { + g_list_free_full(item->exclusion_list, free); + item->exclusion_list = NULL; + } + + free(item); +} + +void free_cleanup_config() +{ + if (cleanup_list_system) { + g_list_free_full(cleanup_list_system, free_config_item); + cleanup_list_system = NULL; + } + + if (cleanup_list_user) { + g_list_free_full(cleanup_list_user, free_config_item); + cleanup_list_user = NULL; + } +} + +static int get_config_string_field(struct json_object *root, const char *key, char** value) +{ + struct json_object *node = NULL; + const char *node_value; + + if (!json_object_object_get_ex(root, key, &node)) { + _D("Config doesn't contain path param."); + return 0; + } + + if (!json_object_is_type(node, json_type_string)) { + _E("'%s' type should be string. but it isn't.", key); + return -EINVAL; + } + + node_value = json_object_get_string(node); + *value = strdup(node_value); + if (*value == NULL) { + _E("Failed to duplicate string."); + return -ENOMEM; + } + + return 0; +} + +static GList *get_config_array_field(struct json_object *root, const char *key, int *ret) +{ + struct json_object *node = NULL, *obj; + int i, len; + const char *str = NULL; + char *temp; + GList *list = NULL; + + if (!json_object_object_get_ex(root, key, &node)) { + _D("Config doesn't contain '%s' param.", key); + RESULT(ret, 0); + return NULL; + } + + if (!json_object_is_type(node, json_type_array)) { + _E("'%s' type should be array. but it isn't.", key); + RESULT(ret, -EINVAL); + return NULL; + } + + len = json_object_array_length(node); + for (i = 0; i < len; i++) { + obj = json_object_array_get_idx(node, i); + str = json_object_get_string(obj); + if (!str) + { + _E("Failed to get stirng value from json object."); + RESULT(ret, -EINVAL); + goto cleanup; + } + + temp = strdup(str); + if (!temp) { + _E("Failed to duplicate array item string"); + RESULT(ret, -ENOMEM); + goto cleanup; + } + + list = g_list_append(list, temp); + } + + RESULT(ret, 0); + return list; + +cleanup: + g_list_free_full(list, free); + return NULL; +} + +static int get_cleanup_level(const char *level_str) +{ + int ret = -EINVAL; + int i; + + if (level_str == NULL) + return -EINVAL; + + for (i = 0; i< CLEANUP_LEVEL_NONE; i++) { + if (strcmp(level_str, cleanup_level_str[i]) == 0) + break; + } + + if (i < CLEANUP_LEVEL_NONE) + ret = i; + + return ret; +} + +static int add_config_item(struct json_object *obj, struct cleanup_config *item) +{ + char *temp = NULL; + int ret; + + if (!item) { + _E("Parameter is invalid."); + return -EINVAL; + } + + if (get_config_string_field(obj, "level", &temp) != 0) { + _E("There is no level node."); + return -EINVAL; + } + + item->level = get_cleanup_level(temp); + free(temp); + + if (item->level < 0) { + _E("Invalid level value."); + return -EINVAL; + } + + if (get_config_string_field(obj, "path", &item->path) != 0) + return -EINVAL; + + //todo. + //check duplicated path + + item->exclusion_list = get_config_array_field(obj, "except", &ret); + if (ret != 0) + return -EINVAL; + + //_D("Get config item: level=%d, path=%s, except count=%d", item->level, item->path, g_list_length(item->exclusion_list)); + return 0; +} + +static GList *parsing_json_array_object_to_list(struct json_object *array, int *ret) +{ + int i, len, item_size; + GList *list = NULL; + struct cleanup_config *item = NULL; + struct json_object *obj = NULL; + + if (!json_object_is_type(array, json_type_array)) { + _E("Config value is not an array."); + RESULT(ret, -EINVAL); + return NULL; + } + + len = json_object_array_length(array); + for (i = 0; i < len; ++i) { + obj = json_object_array_get_idx(array, i); + if (!json_object_is_type(obj, json_type_object)) { + _E("Array item is not an object."); + RESULT(ret, -EINVAL); + goto cleanup; + } + + item_size = sizeof(struct cleanup_config); + item = malloc(item_size); + if (!item) { + _E("Memory allocation fail. size=%d", item_size); + RESULT(ret, -ENOMEM); + goto cleanup; + } + memset(item, '\0', item_size); + + if (add_config_item(json_object_get(obj), item) != 0) { + RESULT(ret, -EINVAL); + goto cleanup; + } + list = g_list_append(list, item); + } + RESULT(ret, 0); + return list; +cleanup: + if (item) + free(item); + + g_list_free_full(list, free_config_item); + return NULL; +} + +int load_json_object_with_file(const char *file) +{ + struct json_object *root_node, *array; + const char *root = NULL; + int ret = 0; + + root_node = json_object_from_file(file); + if (root_node == NULL) { + _E("Could not create object from json file '%s'", file); + return -EINVAL; + } + + root = tzplatform_getenv(TZ_SYS_OPT); + if (json_object_object_get_ex(root_node, root, &array)) { + cleanup_list_system = parsing_json_array_object_to_list(array, &ret); + if(ret != 0) + goto cleanup; + _D("'%s' config has %d items.", root, g_list_length(cleanup_list_system)); + } + + root = tzplatform_getenv(TZ_SYS_USER); + if (json_object_object_get_ex(root_node, root, &array)) { + cleanup_list_user = parsing_json_array_object_to_list(array, &ret); + if (ret != 0) + goto cleanup; + _D("'%s' config has %d items.", root, g_list_length(cleanup_list_user)); + } + +cleanup: + json_object_put(root_node); + return ret; +} + +int load_cleanup_config() +{ + int ret = 0; + + ret = access(DEF_CLEANUP_CONFIG_FILE, F_OK|R_OK); + if (ret) { + if (errno == ENOENT) { + _D("Config file doesn't exist '%s'", DEF_CLEANUP_CONFIG_FILE); + return -ENOENT; + } + _E("Could not access config file '%s'", DEF_CLEANUP_CONFIG_FILE); + return -errno; + } + + _D("Using cleanup config file: '%s'", DEF_CLEANUP_CONFIG_FILE); + + ret = load_json_object_with_file(DEF_CLEANUP_CONFIG_FILE); + + return ret; +} + +GList *get_cleanup_config_system() +{ + return cleanup_list_system; +} + +GList *get_cleanup_config_user() +{ + return cleanup_list_user; +} diff --git a/src/storage/cleanup_config.h b/src/storage/cleanup_config.h new file mode 100644 index 0000000..c7614eb --- /dev/null +++ b/src/storage/cleanup_config.h @@ -0,0 +1,45 @@ +/* + * storaged + * + * Copyright (c) 2019 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __STORAGED_CLEANUP_CONFIG_H__ +#define __STORAGED_CLEANUP_CONFIG_H__ + +#include +#include + +enum cleanup_level { + CLEANUP_LEVEL_FULL = 0, + CLEANUP_LEVEL_CRITICAL, + CLEANUP_LEVEL_WARNING, + CLEANUP_LEVEL_NORMAL, + CLEANUP_LEVEL_NONE +}; + +struct cleanup_config { + int level; + char *path; + GList *exclusion_list; +}; + +int load_cleanup_config(); +void free_cleanup_config(); + +GList *get_cleanup_config_system(); +GList *get_cleanup_config_user(); + +#endif /* #define __STORAGED_CLEANUP_CONFIG_H___ */ diff --git a/src/storage/sample-cleanup-storage.conf b/src/storage/sample-cleanup-storage.conf new file mode 100644 index 0000000..5325515 --- /dev/null +++ b/src/storage/sample-cleanup-storage.conf @@ -0,0 +1,30 @@ +{ + "/opt":[ + { + "level":"critical", + "path":"/opt/val/test", + "except":[ + "/opt/val/test/except1", + "/opt/val/test/except2" + ] + } + ], + "/opt/usr":[ + { + "level":"full", + "path":"/opt/usr/logtest" + }, + { + "level":"full", + "path":"/opt/usr/etc/test" + }, + { + "level":"full", + "path":"/opt/usr/etc/test_crash" + }, + { + "level":"critical", + "path":"/opt/usr/data/test_GL" + } + ] +} diff --git a/src/storage/storage.c b/src/storage/storage.c index 24a613f..1badb90 100644 --- a/src/storage/storage.c +++ b/src/storage/storage.c @@ -38,6 +38,7 @@ #include "config-parser.h" #include "module-intf.h" #include "storaged_common.h" +#include "cleanup.h" #define MEMORY_STATUS_TMP_PATH "/tmp" #define MEMORY_STATUS_OPT_PATH "/opt" @@ -185,6 +186,8 @@ out: g_variant_new("(is)", path, value)); if (ret < 0) _E("Failed to send dbus signal"); + + cleanup_storage(path, level); } static void _popup_cb(GVariant *var, void *user_data, GError *err) @@ -721,6 +724,8 @@ static void storage_init(void *data) if (ret < 0) _E("Failed to make directory: %d", errno); + init_cleanup_storage(); + id_storage_poweroff = subscribe_dbus_signal(NULL, DEVICED_PATH_POWEROFF, DEVICED_INTERFACE_POWEROFF, SIGNAL_POWEROFF_STATE, @@ -731,6 +736,7 @@ static void storage_init(void *data) static void storage_exit(void *data) { + free_cleanup_storage(); /* unregister notifier for below each event */ unsubscribe_dbus_signal(NULL, id_storage_poweroff); } -- 2.7.4