cleanup: add log file for file space usage
[platform/core/system/storaged.git] / src / storage / cleanup.c
1 /*
2  * Copyright (c) 2019, Samsung Electronics Co., Ltd. All rights reserved.
3  *
4  * Licensed under the Apache License, Version 2.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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 #define _GNU_SOURCE
18 #include <unistd.h>
19 #include <stdio.h>
20 #include <stdbool.h>
21 #include <stdlib.h>
22 #include <pthread.h>
23 #include <assert.h>
24 #include <json-c/json.h>
25 #include <sys/stat.h>
26 #include <sys/types.h>
27 #include <grp.h>
28 #include <gmodule.h>
29 #include <limits.h>
30 #include <regex.h>
31 #include <time.h>
32
33 #include "log.h"
34 #include "cleanup.h"
35 #include "cleanup_config.h"
36
37 enum cleanup_running_type {
38         CLEANUP_TYPE_NONE       = 0,
39         CLEANUP_TYPE_SYSTEM,
40         CLEANUP_TYPE_USER
41 };
42
43 struct cleanup_request {
44         int cleanup_mode;
45         int level;
46 };
47
48 struct cleanup_history {
49         enum cleanup_running_type type;
50         int level;
51         time_t tm;
52 };
53
54 #define REMOVE(path) {  \
55         if(remove(path) != 0) {         \
56                 _E("Failed to remove(%s): %d", path, errno);    \
57                 return -errno;                  \
58         }       \
59         _D("Remove (%s)", path);        \
60 }
61
62 #define ASSERT(ret, format, arg...) {\
63         if (!ret) {     \
64                 _E(format, ##arg);      \
65                 assert(ret);    \
66         }       \
67 }
68
69 #define CLEANUP_INTERVAL_SEC    (10 * 60)
70
71 #define CLEANUP_STORAGE_FULL_LOG_PATH           "/var/log/storage"
72 #define CLEANUP_STORAGE_FULL_SYSTEM_FILE        CLEANUP_STORAGE_FULL_LOG_PATH"/opt_full.log"
73 #define CLEANUP_STORAGE_FULL_USER_FILE          CLEANUP_STORAGE_FULL_LOG_PATH"/opt_usr_full.log"
74
75 static pthread_mutex_t mutex_cancel;
76 static pthread_mutex_t mutex_lock;
77 static pthread_t cleanup_th = 0;
78 static GList *request_queue = NULL;
79 static int cleanup_canceled = 0;
80 static GSList *history = NULL;
81
82 static int is_cleanup_canceled()
83 {
84         int ret = 0;
85         ASSERT((pthread_mutex_lock(&mutex_cancel) == 0), "Assert: Failed to pthread_mutex_lock for cancel.");
86         ret = cleanup_canceled;
87         pthread_mutex_unlock(&mutex_cancel);
88
89         return ret;
90 }
91
92 static void cleanup_cancel()
93 {
94         ASSERT((pthread_mutex_lock(&mutex_cancel) == 0), "Assert: Failed to pthread_mutex_lock for cancel.");
95         cleanup_canceled = 1;
96         pthread_mutex_unlock(&mutex_cancel);
97 }
98
99 static int find_sub_except_item(const char *item, const char *path)
100 {
101         if (strstr(item, path)) {
102                 _D("Find except item: %s, path: %s", item, path);
103                 return 0;
104         }
105
106         return -1;
107 }
108
109 static int remove_dir(const char *path, GList *except)
110 {
111         if (!path)
112                 return -EINVAL;
113
114         if (!except)
115                 REMOVE(path)
116         else if (except && !g_list_find_custom(except, path, (GCompareFunc)find_sub_except_item))
117                 REMOVE(path);
118
119         return 0;
120 }
121
122 static int remove_oldfile(const char *path)
123 {
124         regex_t regex;
125         int ret = 0;
126
127         if (!path)
128                 return -EINVAL;
129
130         ret = regcomp(&regex, "\\.[0-9]+$", REG_EXTENDED);
131         if (ret)
132                 return -EINVAL;
133
134         if (!regexec(&regex, path, 0, NULL, 0)) {
135                 if(remove(path) != 0) {
136                         _E("Failed to remove(%s): %d", path, errno);
137                         ret = -errno;
138                 } else
139                         _D("Remove (%s)", path);
140         }
141         regfree(&regex);
142
143         return ret;
144 }
145
146 static int cleanup_recursive(const char *path, GList *except, int target)
147 {
148         DIR *dir = NULL;
149         struct dirent *dent;
150         struct stat fstat;
151         char sub_path[PATH_MAX] = {0,};
152         int ret;
153
154         if (except && g_list_find_custom(except, path, (GCompareFunc)strcmp))
155                 return 0;
156
157         if (is_cleanup_canceled())
158                 return 0;
159
160         ret = lstat(path, &fstat);
161         if (ret)
162                 return -EINVAL;
163
164         if ((fstat.st_mode & S_IFMT) == S_IFDIR) {
165                 if (access(path, W_OK) != 0) {
166                         _E("Failed to access file status(%s).", path);
167                         return -EINVAL;
168                 }
169
170                 dir = opendir(path);
171                 if (!dir) {
172                         _E("Failed to open dir(%s).", path);
173                         return -EINVAL;
174                 }
175
176                 while((dent = readdir(dir))) {
177                         if(strcmp(dent->d_name, ".") == 0 ||
178                                 strcmp(dent->d_name, "..") == 0)
179                                 continue;
180
181                         if (is_cleanup_canceled()) {
182                                 ret = -EINVAL;
183                                 break;
184                         }
185
186                         if (PATH_MAX <= (strlen(path) + strlen(dent->d_name) + 1)) {
187                                 _E("File path sould be shorter than %d. But %zu.", PATH_MAX, strlen(path) + strlen(dent->d_name) + 1);
188                                 continue;
189                         }
190
191                         snprintf(sub_path, PATH_MAX, "%s/%s", path, dent->d_name);
192                         ret = cleanup_recursive(sub_path, except, (target == CLEANUP_TARGET_OLDFILE)? target: CLEANUP_TARGET_ALL);
193                         if (ret != 0)
194                                 break;
195                 }
196                 closedir(dir);
197                 if (!ret && (target == CLEANUP_TARGET_ALL))
198                         ret = remove_dir(path, except);
199         } else if (target == CLEANUP_TARGET_OLDFILE)
200                 remove_oldfile(path);
201         else
202                 REMOVE(path);
203
204         return ret;
205 }
206
207 static bool check_history(enum cleanup_running_type type, int level)
208 {
209         time_t interval;
210         GSList *list = NULL;
211         struct cleanup_history *item = NULL;
212         bool ret = true;
213
214         /*
215          * When requesting the same or lower level of cleanup again
216            (warning->warning, full->critical or critical->warning),
217            do it at a specific interval (10 minutes).
218
219          * When requesting the higher level of cleanup again
220            (warning->critical or critical->full),
221            do it immediately.
222         */
223         for (list = history; list != NULL; list = g_slist_next(list)) {
224                 item = list->data;
225                 if (item->type == type) {
226                         if (item->level > level)
227                                 break;
228
229                         interval = time(NULL) - item->tm;
230                         if (interval < CLEANUP_INTERVAL_SEC) {
231                                 //_D("Cleanup(type:%d, level:%d) is already requested before %u seconds.", type, level, (unsigned int)interval);
232                                 ret = false;
233                         }
234
235                         break;
236                 }
237         }
238
239         return ret;
240 }
241
242 static void update_history(enum cleanup_running_type type, int level)
243 {
244         GSList *list = NULL;
245         struct cleanup_history *item = NULL, *find = NULL;
246
247         for (list = history; list != NULL; list = g_slist_next(list)) {
248                 item = list->data;
249                 if (item->type == type) {
250                         find = item;
251                         break;
252                 }
253         }
254
255         if (find == NULL) {
256                 find = malloc(sizeof(struct cleanup_history));
257                 if (!find) {
258                         _E("Failed to call malloc function.");
259                         return ;
260                 }
261                 find->type = type;
262                 find->level = level;
263                 find->tm = time(NULL);
264                 history = g_slist_prepend(history, find);
265         } else {
266                 find->level = level;
267                 find->tm = time(NULL);
268         }
269 }
270
271 static void save_log_file(enum cleanup_running_type type, int path_id)
272 {
273         const char *logpath;
274         char *du_cmd = NULL;
275         int ret;
276         struct group *group_entry;
277         const char *cleanup_path;
278
279         if (type == CLEANUP_TYPE_SYSTEM)
280                 logpath = CLEANUP_STORAGE_FULL_SYSTEM_FILE;
281         else if (type == CLEANUP_TYPE_USER)
282                 logpath = CLEANUP_STORAGE_FULL_USER_FILE;
283         else {
284                 _E("Invalied cleanup type : %u", type);
285                 return ;
286         }
287
288         cleanup_path = tzplatform_getenv(path_id);
289         if (!cleanup_path) {
290                 _E("Failed to get cleanup root path.");
291                 return ;
292         }
293
294         errno = 0;
295         ret = mkdir(CLEANUP_STORAGE_FULL_LOG_PATH, 0755);
296         if ((ret < 0) && (errno != EEXIST)) {
297                 _E("Failed to mkdir: %m");
298                 return ;
299         }
300
301         ret = asprintf(&du_cmd, "du -ah %s/>%s 2>&1", cleanup_path, logpath);
302         if (ret < 0) {
303                 _E("Failed to allocate memory.");
304                 return ;
305         }
306
307         ret = system(du_cmd);
308         if ((ret == -1) || (ret == 127))
309                 _E("Failed to run '%s' command: %d", du_cmd, ret);
310         else
311                 _D("Save log: %s", du_cmd);
312
313         free(du_cmd);
314
315         errno = 0;
316         group_entry = getgrnam("system_share");
317         if (!group_entry) {
318                 _E("Failed to getgrnam: %m");
319                 return ;
320         }
321
322         if (chown(logpath, -1, group_entry->gr_gid) < 0) {
323                 _E("Failed to chown: %m");
324                 return ;
325         }
326
327         if (chmod(logpath, 0666) < 0) {
328                 _E("Failed to chmod: %m");
329                 return ;
330         }
331 }
332
333 static int add_request_queue(int type, int level)
334 {
335         int ret = 0;
336         struct cleanup_request *request = NULL, *item = NULL;
337         GList *list = NULL;
338
339         ASSERT((pthread_mutex_lock(&mutex_lock) == 0), "Assert: Failed to pthread_mutex_lock.");
340
341         for (list = g_list_first(request_queue); list != NULL; list = g_list_next(list)) {
342                 item = list->data;
343
344                 if ((item->cleanup_mode == type) && (item->level == level)) {
345                         _D("cleanup request is already added.");
346                         goto cleanup;
347                 }
348         }
349
350         request = malloc(sizeof(struct cleanup_request));
351         if (!request) {
352                 ret = -ENOMEM;
353                 goto cleanup;
354         }
355
356         request->cleanup_mode = type;
357         request->level = level;
358         request_queue = g_list_append(request_queue, request);
359
360 cleanup:
361         pthread_mutex_unlock(&mutex_lock);
362         return ret;
363 }
364
365 static void remove_request_queue(struct cleanup_request *item)
366 {
367         ASSERT((pthread_mutex_lock(&mutex_lock) == 0), "Assert: Failed to pthread_mutex_lock.");
368
369         request_queue = g_list_remove(request_queue, item);
370         free(item);
371         pthread_mutex_unlock(&mutex_lock);
372 }
373
374 static struct cleanup_request *get_request_queue()
375 {
376         GList *list = NULL;
377
378         ASSERT((pthread_mutex_lock(&mutex_lock) == 0), "Assert: Failed to pthread_mutex_lock.");
379
380         list = g_list_first(request_queue);
381         pthread_mutex_unlock(&mutex_lock);
382
383         return (list)? list->data :NULL;
384 }
385
386 static GList *get_cleanup_config(int type)
387 {
388         if (type == CLEANUP_TYPE_SYSTEM)
389                 return get_cleanup_config_system();
390         else if (type == CLEANUP_TYPE_USER)
391                 return get_cleanup_config_user();
392
393         return NULL;
394 }
395
396 // Called by thread
397 void *cleanup_storage_start(void *arg)
398 {
399         GList *list_config = NULL;
400         GList *list = NULL;
401         struct cleanup_config *config = NULL;
402         struct cleanup_request *request = NULL;
403
404         pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
405         while(true) {
406                 request = get_request_queue();
407                 if (!request) {
408                         _D("There is no more cleanup request.");
409                         break;
410                 }
411
412                 _D("Cleanup: mode=%d, level=%d", request->cleanup_mode, request->level);
413                 list_config = get_cleanup_config(request->cleanup_mode);
414                 for (list= g_list_first(list_config); NULL != list; list = g_list_next(list)) {
415                         config = list->data;
416                         _D("config path:%s, target:%d", config->path, config->target);
417                         if (request->level <= config->level)
418                                 cleanup_recursive(config->path, config->exclusion_list, config->target);
419                 }
420                 remove_request_queue(request);
421         }
422         cleanup_th = 0;
423         _D("Cleanup thread exit.");
424         return NULL;
425 }
426
427 void cleanup_storage(enum tzplatform_variable path_id, int level)
428 {
429         bool bth = (get_request_queue() == NULL);
430         enum cleanup_running_type type = CLEANUP_TYPE_NONE;
431         int ret;
432
433         if (path_id == TZ_SYS_OPT)
434                 type = CLEANUP_TYPE_SYSTEM;
435         else if (path_id == TZ_SYS_USER)
436                 type = CLEANUP_TYPE_USER;
437         else {
438                 _D("Not supported path type: %s", tzplatform_getname(path_id));
439                 return ;
440         }
441
442         if (!get_cleanup_config(type)) {
443                 _D("There is no list for cleanup.");
444                 return ;
445         }
446
447         if (!check_history(type, level))
448                 return ;
449
450         if (add_request_queue(type, level) != 0) {
451                 _E("Failed to add request.");
452                 return ;
453         }
454         //_D("Add cleanup request.(type:%d, level:%d)", type, level);
455
456         update_history(type, level);
457         save_log_file(type, path_id);
458
459         if (bth) {
460                 ret = pthread_create(&cleanup_th, NULL, cleanup_storage_start, NULL);
461                 if (ret != 0)
462                         _E("Failed to start pthread: %d", ret);
463         }
464 }
465
466 void init_cleanup_storage()
467 {
468         load_cleanup_config();
469
470         pthread_mutex_init(&mutex_lock, NULL);
471         pthread_mutex_init(&mutex_cancel, NULL);
472 }
473
474 void free_cleanup_storage()
475 {
476         ASSERT((pthread_mutex_lock(&mutex_lock) == 0), "Assert: Failed to pthread_mutex_lock.");
477         if (request_queue)
478                 g_list_free_full(request_queue, free);
479         request_queue = NULL;
480         pthread_mutex_unlock(&mutex_lock);
481
482         if (history)
483                 g_slist_free_full(history, free);
484         history = NULL;
485
486         if (cleanup_th) {
487                 _D("Cancel cleanup thread %d.", (int)cleanup_th);
488                 cleanup_cancel();
489                 //pthread_cancel(cleanup_th);
490                 pthread_join(cleanup_th, NULL);
491                 _D("Exit cleanup thread %d.", (int)cleanup_th);
492         }
493
494         free_cleanup_config();
495 }