Release version 0.25.2
[platform/core/appfw/pkgmgr-info.git] / tool / pkg-db-recovery.c
index 4d92612..f48d2b4 100644 (file)
@@ -20,6 +20,7 @@
 
 #define _GNU_SOURCE
 
+#include <ctype.h>
 #include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
@@ -39,6 +40,7 @@
 #include <tzplatform_config.h>
 
 #include <pkgmgr_parser_db.h>
+#include <pkgmgr_parser_db_queries.h>
 
 #ifdef LOG_TAG
 #undef LOG_TAG
@@ -58,6 +60,9 @@
 
 #define PKGMGR_PARSER_DB_FILE tzplatform_mkpath(TZ_SYS_DB, ".pkgmgr_parser.db")
 #define PKGMGR_CERT_DB_FILE tzplatform_mkpath(TZ_SYS_DB, ".pkgmgr_cert.db")
+#define NEED_RECOVERY_FILE tzplatform_mkpath(TZ_SYS_DB, ".need_pkg_recovery")
+
+#define GLOBAL_USER tzplatform_getuid(TZ_SYS_GLOBALAPP_USER)
 
 typedef struct user_info {
        uid_t uid;
@@ -66,6 +71,27 @@ typedef struct user_info {
 
 static GList *user_info_list;
 
+char *string_trim_inplace(char *s) {
+       char *original = s;
+       size_t len = 0;
+
+       while (isspace((unsigned char) *s))
+               s++;
+
+       if (*s) {
+               char *p = s;
+               while (*p)
+                       p++;
+               while (isspace((unsigned char) *(--p)));
+               p[1] = '\0';
+               len = (size_t) (p - s + 1);
+       }
+
+       if (len > MAX_QUERY_LEN)
+               return NULL;
+       return (s == original) ? s : memmove(original, s, len + 1);
+}
+
 static char *__get_dbpath(uid_t uid)
 {
        const char *db_path;
@@ -94,10 +120,45 @@ static int __db_busy_handler(void *data, int count)
        }
 }
 
+static bool __check_aborted(uid_t uid)
+{
+       char buf[BUFSIZE];
+
+       snprintf(buf, sizeof(buf), "%s_%d", NEED_RECOVERY_FILE, uid);
+       if (access(buf, F_OK) == 0) {
+               return true;
+       } else {
+               if (errno != ENOENT)
+                       LOGE("failed to access %s, errno: %d", buf, errno);
+               return false;
+       }
+}
+
+static void __create_need_to_recovery_file(uid_t uid)
+{
+       int fd;
+       char buf[BUFSIZE];
+
+       snprintf(buf, sizeof(buf), "%s_%d", NEED_RECOVERY_FILE, uid);
+       fd = open(buf, O_CREAT | O_WRONLY, S_IRWXU);
+       if (fd == -1)
+               LOGE("failed to create file: %s, errno: %d", buf, errno);
+       else
+               close(fd);
+}
+
+static void __remove_need_to_recovery_file(uid_t uid)
+{
+       char buf[BUFSIZE];
+
+       snprintf(buf, sizeof(buf), "%s_%d", NEED_RECOVERY_FILE, uid);
+       if (unlink(buf))
+               LOGE("failed to remove file: %s, errno: %d", buf, errno);
+}
+
 static bool __integrity_check(const char *db_path)
 {
        int ret = -1;
-       int persist_wal = 1;
        sqlite3_stmt *stmt = NULL;
        const char *check_result;
        static const char integrity_check_query[] =
@@ -108,6 +169,7 @@ static bool __integrity_check(const char *db_path)
                        SQLITE_OPEN_READWRITE, NULL);
        if (ret != SQLITE_OK) {
                LOGE("Failed to open db");
+               sqlite3_close_v2(db);
                return false;
        }
 
@@ -119,14 +181,6 @@ static bool __integrity_check(const char *db_path)
                return ret;
        }
 
-       ret = sqlite3_file_control(db, NULL, SQLITE_FCNTL_PERSIST_WAL, &persist_wal);
-       if (ret != SQLITE_OK) {
-               LOGE("failed to sqlite3_file_control: %s",
-                               sqlite3_errmsg(db));
-               sqlite3_close_v2(db);
-               return ret;
-       }
-
        ret = sqlite3_prepare_v2(db, integrity_check_query,
                        strlen(integrity_check_query), &stmt, NULL);
        if (ret != SQLITE_OK) {
@@ -161,128 +215,6 @@ err:
        return false;
 }
 
-static bool __change_owner(const char *files[2], uid_t uid)
-{
-       int ret;
-       int i;
-       int fd;
-       struct passwd pwd;
-       struct passwd *result;
-       char buf[BUFSIZE];
-       struct stat sb;
-       mode_t mode;
-
-       if (uid == OWNER_ROOT) {
-               ret = getpwnam_r(APPFW_USER, &pwd, buf, sizeof(buf), &result);
-               if (result == NULL) {
-                       if (ret == 0)
-                               LOGE("no such user: %d", uid);
-                       else
-                               LOGE("getpwnam_r failed: %d", errno);
-                       return false;
-               }
-               uid = pwd.pw_uid;
-       }
-
-       ret = getpwuid_r(uid, &pwd, buf, sizeof(buf), &result);
-       if (result == NULL) {
-               if (ret == 0)
-                       LOGE("no such user: %d", uid);
-               else
-                       LOGE("getpwuid_r failed: %d", errno);
-               return false;
-       }
-
-       for (i = 0; i < 2; i++) {
-               fd = open(files[i], O_RDONLY);
-               if (fd == -1) {
-                       LOGE("open %s failed: %d", files[i], errno);
-                       return false;
-               }
-               ret = fstat(fd, &sb);
-               if (ret == -1) {
-                       LOGE("stat %s failed: %d", files[i], errno);
-                       close(fd);
-                       return false;
-               }
-               if (S_ISLNK(sb.st_mode)) {
-                       LOGE("%s is symlink!", files[i]);
-                       close(fd);
-                       return false;
-               }
-               ret = fchown(fd, uid, pwd.pw_gid);
-               if (ret == -1) {
-                       LOGE("fchown %s failed: %d", files[i], errno);
-                       close(fd);
-                       return false;
-               }
-
-               mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH;
-               if (strstr(files[0], PKGMGR_CERT_DB_FILE) != NULL)
-                       mode |= S_IWOTH;
-               ret = fchmod(fd, mode);
-               if (ret == -1) {
-                       LOGE("fchmod %s failed: %d", files[i], errno);
-                       close(fd);
-                       return false;;
-               }
-               close(fd);
-       }
-
-       return true;
-}
-
-static void __change_permission(uid_t uid)
-{
-       const char *files[2];
-       char *parser_dbpath = NULL;
-       char journal_file[PATH_MAX];
-       GList *tmp_list;
-       user_info *tmp_info;
-
-       if (uid < REGULAR_USER) {
-               files[0] = PKGMGR_PARSER_DB_FILE;
-               snprintf(journal_file, sizeof(journal_file),
-                               "%s-journal", PKGMGR_PARSER_DB_FILE);
-               files[1] = journal_file;
-
-               if (!__change_owner(files, uid)) {
-                       LOGE("Failed to change ownership");
-                       return;
-               }
-
-               files[0] = PKGMGR_CERT_DB_FILE;
-               snprintf(journal_file, sizeof(journal_file),
-                               "%s-journal", PKGMGR_CERT_DB_FILE);
-               files[1] = journal_file;
-               if (!__change_owner(files, uid)) {
-                       LOGE("Failed to change ownership");
-                       return;
-               }
-       } else {
-               for (tmp_list = user_info_list; tmp_list != NULL;
-                               tmp_list = g_list_next(tmp_list)) {
-                       tmp_info = (user_info *)tmp_list->data;
-                       if (!tmp_info)
-                               continue;
-
-                       if (tmp_info->uid == uid) {
-                               parser_dbpath = tmp_info->db_path;
-                               break;
-                       }
-               }
-               files[0] = parser_dbpath;
-               snprintf(journal_file, sizeof(journal_file),
-                               "%s-journal", parser_dbpath);
-               files[1] = journal_file;
-
-               if (!__change_owner(files, uid)) {
-                       LOGE("Failed to change ownership");
-                       return;
-               }
-       }
-}
-
 static void _xsystem(const char *argv[])
 {
        int status = 0;
@@ -333,22 +265,23 @@ static void __initdb(uid_t uid)
        const char *initdb_ro[] =  { "/usr/bin/pkg_initdb",
                        "--recover-db", "--keep-db", NULL};
 
+       __create_need_to_recovery_file(uid);
        __create_db(uid);
-       __change_permission(uid);
        snprintf(uid_string, sizeof(uid_string), "%d", (int)uid);
        _xsystem((uid > REGULAR_USER) ? initdb_rw : initdb_ro);
+       __remove_need_to_recovery_file(uid);
 }
 
 static void __initdb_all()
 {
        GList *tmp_list = NULL;
        user_info *tmp_info;
-       __initdb(getuid());
+       __initdb(GLOBAL_USER);
 
        for (tmp_list = user_info_list;
                        tmp_list != NULL; tmp_list = g_list_next(tmp_list)) {
                tmp_info = (user_info *)tmp_list->data;
-               if (!tmp_info || tmp_info->uid < REGULAR_USER)
+               if (!tmp_info)
                        continue;
                __initdb(tmp_info->uid);
        }
@@ -358,16 +291,23 @@ static void __check_user_db()
 {
        GList *tmp_list = NULL;
        user_info *tmp_info;
+       bool need_recovery;
 
        for (tmp_list = user_info_list;
                        tmp_list != NULL; tmp_list = g_list_next(tmp_list)) {
+               need_recovery = false;
                tmp_info = (user_info *)tmp_list->data;
                if (!tmp_info)
                        continue;
                if (!__integrity_check(tmp_info->db_path)) {
                        LOGE("User db for %d has corrupted", (int)tmp_info->uid);
-                       __initdb(tmp_info->uid);
+                       need_recovery = true;
+               } else if (__check_aborted(tmp_info->uid)) {
+                       need_recovery = true;
                }
+
+               if (need_recovery)
+                       __initdb(tmp_info->uid);
        }
 }
 
@@ -398,10 +338,19 @@ static void _get_user_list()
 
        snprintf(traverse_path, sizeof(traverse_path), "%s/user", db_path);
        dir = opendir(traverse_path);
+       if (!dir) {
+               LOGE("Failed to open dir: %d (%s)", errno, traverse_path);
+               return;
+       }
 
        while ((ent = readdir(dir)) != NULL) {
-               snprintf(abs_dirname, PATH_MAX, "%s/%s", traverse_path,
+               ret = snprintf(abs_dirname, PATH_MAX, "%s/%s", traverse_path,
                         ent->d_name);
+               if (ret < 0 || ret > PATH_MAX) {
+                       LOGE("snprintf fail");
+                       closedir(dir);
+                       return;
+               }
 
                ret = stat(abs_dirname, &stats);
                if (ret != 0) {
@@ -412,33 +361,170 @@ static void _get_user_list()
                if (!strcmp(".", ent->d_name) || !strcmp("..", ent->d_name) ||
                                !S_ISDIR(stats.st_mode))
                        continue;
+               uid = (uid_t)atoi(ent->d_name);
+               if (uid < REGULAR_USER)
+                       continue;
+
                info = calloc(1, sizeof(user_info));
                if (!info) {
                        closedir(dir);
                        return;
                }
 
-               uid = (uid_t)atoi(ent->d_name);
-               if (!uid) {
-                       free(info);
-                       continue;
-               }
                info->uid = uid;
                info->db_path = __get_dbpath(uid);
-               user_info_list = g_list_append(user_info_list, info);
+               user_info_list = g_list_prepend(user_info_list, info);
        }
 
        closedir(dir);
 }
 
+static bool __check_db_schema(const char *db_path,
+               const char **db_tables, const char **init_queries) {
+       int ret = -1;
+       int i;
+       sqlite3_stmt *stmt = NULL;
+       const char *check_result;
+       char *schema_in_library, *schema_in_db;
+       static const char table_schema_query[] =
+                       "SELECT sql from sqlite_master WHERE name=?";
+       sqlite3 *db;
+       char *schema_lib_tmp;
+       char *schema_lib_ptr;
+       char *schema_db_tmp;
+       char *schema_db_ptr;
+       char *line_db;
+       char *line_library;
+
+       ret = sqlite3_open_v2(db_path, &db,
+                       SQLITE_OPEN_READONLY, NULL);
+       if (ret != SQLITE_OK) {
+               LOGE("Failed to open db");
+               sqlite3_close_v2(db);
+               return false;
+       }
+
+       ret = sqlite3_busy_handler(db, __db_busy_handler, NULL);
+       if (ret != SQLITE_OK) {
+               LOGE("failed to register busy handler: %s",
+                               sqlite3_errmsg(db));
+               sqlite3_close_v2(db);
+               return ret;
+       }
+       for (i = 0; db_tables[i] != NULL; i++) {
+               ret = sqlite3_prepare_v2(db, table_schema_query,
+                               strlen(table_schema_query), &stmt, NULL);
+               if (ret != SQLITE_OK) {
+                       LOGE("Failed to check db schema : %s",
+                                       sqlite3_errmsg(db));
+                       goto err;
+               }
+
+               ret = sqlite3_bind_text(stmt, 1,
+                               db_tables[i],-1, SQLITE_STATIC);
+
+               ret = sqlite3_step(stmt);
+               if (ret != SQLITE_ROW) {
+                       LOGE("Failed to check db schema :%s",
+                                       sqlite3_errmsg(db));
+                       goto err;
+               }
+
+               check_result = (const char *)sqlite3_column_text(stmt, 0);
+               if (!check_result) {
+                       LOGE("Failed to check db schema :%s",
+                                       sqlite3_errmsg(db));
+                       goto err;
+               }
+
+               schema_in_library = strstr(init_queries[i], db_tables[i]);
+               if (schema_in_library == NULL) {
+                       LOGE("Failed to get initialization query from library");
+                       goto err;
+               }
+               schema_lib_tmp = strdup(schema_in_library);
+               schema_lib_ptr = schema_lib_tmp;
+               if (schema_lib_tmp == NULL) {
+                       LOGE("Out of memory");
+                       goto err;
+               }
+
+               schema_in_db = strstr(check_result, db_tables[i]);
+               if (schema_in_db == NULL) {
+                       LOGE("Failed to get initialization query from db");
+                       free(schema_lib_ptr);
+                       goto err;
+               }
+               schema_db_tmp = strdup(schema_in_db);
+               schema_db_ptr = schema_db_tmp;
+               if (schema_db_tmp == NULL) {
+                       LOGE("Out of memory");
+                       free(schema_lib_ptr);
+                       goto err;
+               }
+
+               while (true) {
+                       line_db = strsep(&schema_db_tmp, ",");
+                       line_library = strsep(&schema_lib_tmp, ",");
+                       if (line_db == NULL || line_library == NULL)
+                               break;
+
+                       if (string_trim_inplace(line_db) == NULL ||
+                                       string_trim_inplace(line_library) == NULL)
+                               break;
+
+                       ret = strcmp(string_trim_inplace(line_db),
+                                       string_trim_inplace(line_library));
+                       if (ret != 0)
+                               break;
+               }
+
+               free(schema_lib_ptr);
+               free(schema_db_ptr);
+
+               if (ret != 0) {
+                       LOGE("Broken schema detected in table[%s], query[%s]",
+                                       db_tables[i], schema_in_db);
+                       goto err;
+               }
+
+               sqlite3_finalize(stmt);
+               stmt = NULL;
+       }
+
+       sqlite3_close(db);
+       return true;
+
+err:
+       sqlite3_finalize(stmt);
+       sqlite3_close(db);
+       return false;
+}
+
 static void _check_db()
 {
+       int need_recovery = false;
+
        if (!__integrity_check(PKGMGR_CERT_DB_FILE)) {
                LOGE("Cert db corrupted. restore entire pkgmgr db");
-               __initdb_all();
-               return;
+               need_recovery = true;
        } else if (!__integrity_check(PKGMGR_PARSER_DB_FILE)) {
                LOGE("Global parser db corrupted. restore entire pkgmgr db");
+               need_recovery = true;
+       } else if (__check_aborted(GLOBAL_USER)) {
+               LOGE("Previous recovery was aborted. restore entire pkgmgr db");
+               need_recovery = true;
+       } else if (!__check_db_schema(PKGMGR_PARSER_DB_FILE,
+                       PARSER_TABLES, PARSER_INIT_QUERIES)) {
+               LOGE("Global parser db schema was broken. restore entire pkgmgr db");
+               need_recovery = true;
+       } else if (!__check_db_schema(PKGMGR_CERT_DB_FILE,
+                       CERT_TABLES, CERT_INIT_QUERIES)) {
+               LOGE("Cert db schema was broken. restore entire pkgmgr db");
+               need_recovery = true;
+       }
+
+       if (need_recovery) {
                __initdb_all();
                return;
        }