Enhance display/power lock managemenent 01/242701/6 accepted/tizen/unified/20200902.145534 submit/tizen/20200902.021453
authorYoungjae Cho <y0.cho@samsung.com>
Mon, 31 Aug 2020 02:38:10 +0000 (11:38 +0900)
committerHyotaek Shim <hyotaek.shim@samsung.com>
Tue, 1 Sep 2020 05:56:21 +0000 (05:56 +0000)
Summary:
 - Detect someone who holds lock longer than 30 minutes.
 - If a general application is detected, deviced generates signal
   with time information in addtion to pid.
 - If a specific daemon is detected, deviced tries to kill it directly.

For general applications:
   Added an addition information, lock holding time in second, to the
  signal "pmlock_expired". This makes someone, who is in charge of
  application lifecycle and notification, possible to determine whether
  to kill that application or not.

For some specific killable daemon:
   Added list of killable daemon to conf file and deviced loads that list.
  If a killable daemon holds lock longer than 30 minutes, deviced directly
  sends SIGTERM to that daemon, releasing lock. After for a while, deviced
  checks once more, if the daemon is still alive then, sends SIGKILL.

Change-Id: Ia0d433facdcc7814e19278619a58a111ff7ff7c3
Signed-off-by: Youngjae Cho <y0.cho@samsung.com>
plugins/wearable/display/core.c
src/display/core.h
src/display/display-lock.c
src/display/display-lock.h

index 4a5c8e0..6f3bb43 100644 (file)
@@ -69,6 +69,7 @@
 #include "shared/plugin.h"
 
 #define DISPLAY_CONF_FILE    "/etc/deviced/display.conf"
+#define POWERLOCK_CONF_FILE  "/etc/deviced/powerlock.conf"
 
 #ifndef VCONFKEY_HOMESCREEN_TUTORIAL_OOBE_ENABLED
 #define VCONFKEY_HOMESCREEN_TUTORIAL_OOBE_ENABLED      "db/private/com.samsung.w-home/tutorial_oobe_enabled"
@@ -189,6 +190,19 @@ static int trans_table[S_END][EVENT_END] = {
        (b.tv_sec * 1000000 + b.tv_usec)) \
        / 1000)
 
+#define KILLABLE_DAEMON_LOCK_LIMIT  1800  /* seconds, 30min */
+#define FORCE_RELEASE_LOCK_INTERVAL 5     /* seconds */
+
+static void get_comm(pid_t pid, char *comm);
+static bool is_killable_daemon(pid_t pid);
+static gboolean pmlock_terminate_daemon_to_release_lock(gpointer data);
+static int pmlock_check(void *data);
+static int powerlock_load_config(struct parse_result *result, void *user_data);
+static void free_killable_daemon_list(void);
+
+static dd_list *display_lock_killable_daemon;
+static bool initialized_killable_daemon_list;
+
 static struct display_config display_conf = {
        .lock_wait_time         = LOCK_SCREEN_WATING_TIME,
        .longpress_interval     = LONG_PRESS_INTERVAL,
@@ -206,6 +220,7 @@ static struct display_config display_conf = {
        .timeout_enable         = true,
        .input_support          = true,
        .lockcheck_timeout      = 600,
+       .pmlock_check = pmlock_check,
        .aod_enter_level        = 40,
        .aod_tsp                = true,
        .touch_wakeup           = false,
@@ -319,6 +334,181 @@ static const char* __device_flags_to_string(enum device_flags flags)
                return UNKNOWN_STR;
 }
 
+static void get_comm(pid_t pid, char *comm)
+{
+       char buf[PATH_MAX];
+       int fd, r;
+
+       if (pid >= INTERNAL_LOCK_BASE)
+               snprintf(buf, PATH_MAX, "/proc/%d/comm", getpid());
+       else
+               snprintf(buf, PATH_MAX, "/proc/%d/comm", pid);
+
+       fd = open(buf, O_RDONLY);
+       if (fd < 0) {
+               comm[0] = '\0';
+               _E("Process(%d) does not exist now(may be dead without unlock).", pid);
+               return;
+       }
+
+       r = read(fd, comm, PATH_MAX);
+       if ((r > 0) && (r < PATH_MAX)) {
+               if (comm[r - 1] == '\n')
+                       comm[r - 1] = '\0';
+               else
+                       comm[r] = '\0';
+       } else
+               comm[0] = '\0';
+
+       close(fd);
+}
+
+static bool is_killable_daemon(pid_t pid)
+{
+       char pname[PATH_MAX] = {0, };
+       const char *blacklist;
+       dd_list *l;
+
+       get_comm(pid, pname);
+       if (!(*pname))
+               return false;
+
+       DD_LIST_FOREACH(display_lock_killable_daemon, l, blacklist) {
+               if (MATCH(pname, blacklist))
+                       return true;
+       }
+
+       return false;
+}
+
+static gboolean pmlock_terminate_daemon_to_release_lock(gpointer data)
+{
+       pid_t pid;
+       PmLockNode *node;
+       enum state_t state;
+       int ret;
+       bool pid_exist;
+       char pname[PATH_MAX] = {0, };
+
+       if (!data) {
+               _E("Invalid parameter.");
+               return G_SOURCE_REMOVE;
+       }
+
+       node = (PmLockNode *) data;
+       state = node->state;
+       pid = node->pid;
+
+       if (pid <= 0) {
+               _E("Invalid lock pid.");
+               del_node(state, node);
+               return G_SOURCE_REMOVE;
+       }
+
+       if (!node->killable_daemon) {
+               _E("Incorrect checker, this is not a killable daemon. Stop checking lock.");
+               return G_SOURCE_REMOVE;
+       }
+
+       pid_exist = (kill(pid, 0) == 0);
+       if (pid_exist)
+               get_comm(pid, pname);
+
+       if (node->force_release == false) {
+               /* Stop checking lock if process had been terminated */
+               if (!pid_exist) {
+                       del_node(state, node);
+                       _I("Process %d not found. Stop checking lock.", pid);
+                       return G_SOURCE_REMOVE;
+               }
+
+               /* KILLABLE_DAEMON_LOCK_LIMIT is expired. Kill the daemon */
+               CRITICAL_LOG("%s(%d) holds %s lock for %ds. kill SIGTERM.",
+                       *pname ? pname : "Unknown", pid, states[state].name, KILLABLE_DAEMON_LOCK_LIMIT);
+               ret = kill(pid, SIGTERM);
+               if (ret < 0)
+                       CRITICAL_LOG("Failed to send SIGTERM to process %s(%d), %d.",
+                               *pname ? pname : "Unknown", pid, errno);
+
+               node->force_release = true;
+               g_timeout_add_seconds(FORCE_RELEASE_LOCK_INTERVAL,
+                               pmlock_terminate_daemon_to_release_lock, (gpointer)node);
+       } else if (node->force_release == true) {
+               /* kill confirmation */
+               if (pid_exist) {
+                       CRITICAL_LOG("%s(%d) is still alive, kill SIGKILL.",
+                               *pname ? pname : "Unknown", pid);
+
+                       ret = kill(pid, SIGKILL);
+                       if (ret < 0)
+                               CRITICAL_LOG("Failed to kill process %s(%d), %d.",
+                                       *pname ? pname : "Unknown", pid, errno);
+               }
+
+               /* release lock */
+               CRITICAL_LOG("Release %s lock occupied by PID %d.", states[state].name, pid);
+               del_node(state, node);
+               set_unlock_time(pid, state);
+
+               if (!timeout_src_id)
+                       states[get_pm_cur_state()].trans(EVENT_TIMEOUT);
+       }
+
+       return G_SOURCE_REMOVE;
+}
+
+static int pmlock_check(void *data)
+{
+       PmLockNode *node;
+       int ret;
+
+       assert(data);
+       node = (PmLockNode *) data;
+
+       if (!initialized_killable_daemon_list) {
+               ret = config_parse(POWERLOCK_CONF_FILE, powerlock_load_config, NULL);
+               /* config file may not exist */
+               if (ret < 0 && ret != -ENOENT)
+                       _W("Failed to load %s, %d.", POWERLOCK_CONF_FILE, ret);
+
+               if (status == DEVICE_OPS_STATUS_UNINIT) {
+                       _W("Lock request before display init. Preloaded killable list.");
+                       initialized_killable_daemon_list = true;
+               } else if (status == DEVICE_OPS_STATUS_STOP) {
+                       _W("Lock request after display stop. Loaded list will be freed immediately.");
+                       node->killable_daemon = is_killable_daemon(node->pid);
+                       free_killable_daemon_list();
+                       goto killable_marked;
+               }
+       }
+
+       node->killable_daemon = is_killable_daemon(node->pid);
+
+killable_marked:
+       /* use default lock checker */
+       if (!node->killable_daemon)
+               return 0;
+
+       return g_timeout_add_seconds(KILLABLE_DAEMON_LOCK_LIMIT,
+               pmlock_terminate_daemon_to_release_lock, (gpointer)node);
+}
+
+static void free_killable_daemon_list(void)
+{
+       dd_list *l, *l_next;
+       char *blacklist;
+
+       if (!display_lock_killable_daemon)
+               return;
+
+       DD_LIST_FOREACH_SAFE(display_lock_killable_daemon, l, l_next, blacklist) {
+               DD_LIST_REMOVE(display_lock_killable_daemon, blacklist);
+               free(blacklist);
+       }
+       display_lock_killable_daemon = NULL;
+       initialized_killable_daemon_list = false;
+}
+
 static unsigned long get_lcd_on_flags(void)
 {
        unsigned long flags = NORMAL_MODE;
@@ -1089,9 +1279,13 @@ static void proc_condition_lock(PMMsg *data)
        holdkey_block = GET_COND_FLAG(data->cond) & PM_FLAG_BLOCK_HOLDKEY;
 
        tmp = find_node(state, pid);
-       if (!tmp)
-               add_node(state, pid, cond_timeout_id, holdkey_block);
-       else {
+       if (!tmp) {
+               tmp = add_node(state, pid, cond_timeout_id, holdkey_block);
+               if (!tmp) {
+                       _E("Failed to acquire lock, state: %d, pid: %d.", state, pid);
+                       return;
+               }
+       } else {
                update_lock_timer(data, tmp, cond_timeout_id);
                tmp->holdkey_block = holdkey_block;
        }
@@ -1122,8 +1316,8 @@ static void proc_condition_lock(PMMsg *data)
                }
        }
 
-       _SD("be requested LOCK info pname(%s), holdkeyblock(%d) flags(%d)",
-           pname, holdkey_block, flags);
+       _SD("be requested LOCK info pname(%s), holdkeyblock(%d) flags(%d) killable_daemon(%d)",
+           pname, holdkey_block, flags, tmp->killable_daemon);
        set_lock_time(pid, pname, state);
 
        device_notify(DEVICE_NOTIFIER_DISPLAY_LOCK, (void *)&value);
@@ -2172,6 +2366,26 @@ static int display_load_config(struct parse_result *result, void *user_data)
        return 0;
 }
 
+static int powerlock_load_config(struct parse_result *result, void *user_data)
+{
+       char *name = NULL;
+
+       _D("powerlock_load_config: section=%s name=%s value=%s", result->section, result->name, result->value);
+
+       if (MATCH(result->section, "KillableDaemon") && MATCH(result->name, "KillableList")) {
+               name = strndup(result->value, PATH_MAX - 1);
+               if (!name) {
+                       _E("Not enough memory.");
+                       return -ENOMEM;
+               }
+
+               CRITICAL_LOG("Add %s to killable daemon list.", name);
+               DD_LIST_APPEND(display_lock_killable_daemon, name);
+       }
+
+       return 0;
+}
+
 static gboolean delayed_init_dpms(gpointer data)
 {
        int timeout;
@@ -2331,6 +2545,12 @@ static void display_init(void *data)
                _W("Failed to load '%s', use default value: %d",
                    DISPLAY_CONF_FILE, ret);
 
+       ret = config_parse(POWERLOCK_CONF_FILE, powerlock_load_config, NULL);
+       /* config file may not exist */
+       if (ret < 0 && ret != -ENOENT)
+               _W("Failed to load %s, %d.", POWERLOCK_CONF_FILE, ret);
+       initialized_killable_daemon_list = true;
+
        register_kernel_uevent_control(&lcd_uevent_ops);
        register_kernel_uevent_control(&sec_dsim_uevent_ops);
 
@@ -2489,6 +2709,7 @@ static void display_exit(void *data)
 
        exit_lcd_operation();
        free_lock_info_list();
+       free_killable_daemon_list();
 
        /* free display service */
        display_service_free();
index caa97dc..5bfb1d1 100644 (file)
@@ -126,6 +126,13 @@ struct display_config {
        int accel_sensor_on;
        int continuous_sampling;
        int lockcheck_timeout;
+
+       /* Define pmlock checker.
+        * Return id of the lock checker.
+        *
+        * Returning 0 will use default lock checker */
+       int (*pmlock_check) (void *data);
+
        int aod_enter_level;
        bool aod_tsp;
        bool timeout_enable;
index 4450a76..b1e08d4 100644 (file)
@@ -37,6 +37,8 @@ static struct _backlight_ops *backlight_ops;
 static dd_list *cond_head[S_END];
 static int trans_condition;
 
+static struct display_config *display_conf;
+
 bool check_lock_state(int state)
 {
        dd_list *elem;
@@ -62,7 +64,7 @@ static void refresh_app_cond()
                trans_condition |= MASK_OFF;
 }
 
-static void pmlock_check_cb(GVariant *var, void *user_data, GError *err)
+static void default_pmlock_check_cb(GVariant *var, void *user_data, GError *err)
 {
        pid_t pid = 0;
        int ret, detected = 0;
@@ -97,7 +99,7 @@ static void pmlock_check_cb(GVariant *var, void *user_data, GError *err)
                                                DEVICED_PATH_DISPLAY,
                                                DEVICED_INTERFACE_DISPLAY,
                                                "pmlock_expired",
-                                               g_variant_new("(i)", pid));
+                                               g_variant_new("(ii)", pid, (int)diff));
        if (ret < 0)
                _E("Failed to send dbus pmlock_expired");
 
@@ -107,12 +109,11 @@ out:
        g_variant_unref(var);
 }
 
-static gboolean pmlock_check(void *data)
+static gboolean default_pmlock_check(void *data)
 {
        const char *arr[2];
        char chr_pid[PID_MAX];
        PmLockNode *node;
-       GVariant *v;
        enum state_t state;
        pid_t pid;
        int ret;
@@ -122,8 +123,9 @@ static gboolean pmlock_check(void *data)
                return G_SOURCE_REMOVE;
        }
 
-       v = (GVariant*)data;
-       g_variant_get(v, "(ii)", &state, &pid);
+       node = (PmLockNode *) data;
+       state = node->state;
+       pid = node->pid;
 
        if (state == S_LCDOFF && backlight_ops->get_lcd_power() == DPMS_ON) {
                _D("Lcd state is PM_LCD_POWER_ON");
@@ -152,7 +154,6 @@ static gboolean pmlock_check(void *data)
                break;
        default:
                _E("Invalid state.");
-               g_variant_unref(v);
                return G_SOURCE_REMOVE;
        }
 
@@ -160,7 +161,7 @@ static gboolean pmlock_check(void *data)
                RESOURCED_PATH_PROCESS,
                RESOURCED_INTERFACE_PROCESS,
                METHOD_APP_STATUS,
-               "is", arr, pmlock_check_cb, -1, (void *)(intptr_t)state);
+               "is", arr, default_pmlock_check_cb, -1, (void *)(intptr_t)state);
        if (ret < 0)
                _E("Failed to call dbus method");
 
@@ -184,41 +185,39 @@ PmLockNode *find_node(enum state_t s_index, pid_t pid)
 PmLockNode *add_node(enum state_t s_index, pid_t pid, guint timeout_id,
                bool holdkey_block)
 {
-       guint warning_id = 0;
        PmLockNode *n;
-       GVariant *v = NULL;
        time_t now;
-       struct display_config *display_conf = get_display_config();
-       if (!display_conf) {
-               _E("Failed to get display configuration.");
-               return NULL;
-       }
 
-       n = (PmLockNode *) malloc(sizeof(PmLockNode));
+       assert(display_conf);
+
+       n = (PmLockNode *) calloc(1, sizeof(PmLockNode));
        if (n == NULL) {
-               _E("Not enough memory, add cond. fail");
+               _E("Not enough memory, failed to alloc lock node.");
                return NULL;
        }
 
-       if (pid < INTERNAL_LOCK_BASE) {
-               v = g_variant_new("(ii)", s_index, pid);
-               if (v) {
-                       warning_id = g_timeout_add_seconds(display_conf->lockcheck_timeout,
-                                       pmlock_check, (void *)v);
-               } else {
-                       _E("Failed to make GVariant.");
-               }
-       }
-
        time(&now);
+       n->state = s_index;
        n->pid = pid;
        n->timeout_id = timeout_id;
-       n->warning_id = warning_id;
-       n->warning_param = v;
        n->time = now;
        n->holdkey_block = holdkey_block;
        n->background = false;
        n->broadcast_warning = true;
+
+       if (pid < INTERNAL_LOCK_BASE) {
+               /* check if this lock node needs custom-defined lock checker.
+                * n->warning_id would be 0 if fails to register the checker,
+                * or there is no need to use that checker */
+               if (display_conf->pmlock_check)
+                       n->warning_id = display_conf->pmlock_check(n);
+
+               /* use default lock checker */
+               if (!n->warning_id)
+                       n->warning_id = g_timeout_add_seconds(display_conf->lockcheck_timeout,
+                               default_pmlock_check, (gpointer)n);
+       }
+
        DD_LIST_APPEND(cond_head[s_index], n);
 
        refresh_app_cond();
@@ -403,4 +402,9 @@ static void __CONSTRUCTOR__ initialize(void)
        backlight_ops = get_backlight_ops();
        if (!backlight_ops)
                _E("Failed to get backlight operator.");
+
+       display_conf = get_display_config();
+       if (!display_conf) {
+               _E("Failed to get display config.");
+       }
 }
index bde19db..c52ac84 100644 (file)
@@ -27,6 +27,7 @@
 #include "core.h"
 
 typedef struct _pm_lock_node {
+       enum state_t state;
        pid_t pid;
        guint timeout_id;
        guint warning_id;
@@ -35,6 +36,18 @@ typedef struct _pm_lock_node {
        bool holdkey_block;
        bool background;
        bool broadcast_warning;
+
+       /* Set true when the lock holder is an entry of
+        * the list display_lock_killable_daemon.
+        * If true, deviced tries to kill this lock holder when
+        * the holder holds lock longer than KILLABLE_DAEMON_LOCK_LIMIT */
+       bool killable_daemon;
+
+       /* Set true when the lock holder holds lock
+        * longer than KILLABLE_DAEMON_LOCK_LIMIT.
+        * After a while, FORCE_RELEASE_LOCK_INTERVAL,
+        * this lock will be released. */
+       bool force_release;
 } PmLockNode;
 
 bool check_lock_state(int state);