#include <dlfcn.h>
#include <sys/stat.h>
#include <gio/gio.h>
+#include <dbus/dbus.h>
#include "macro.h"
#include "util.h"
#include "proc-common.h"
#include "proc-main.h"
#include "freezer.h"
+#include "cgroup.h"
#define FREEZER_MODULE_PATH FREEZER_DIR"/libsystem-freezer.so.0"
+static const int SOCKET_OVERFLOW_MIN_KILL_AGE_SECONDS = 5; // don't kill processes younger than this.
+static const int SOCKET_OVERFLOW_MIN_KILL_THAW_SECONDS = 5; // don't kill processes which were thawed this recently.
+
+/* SOCKET_OVERFLOW_SHOULD_REFREEZE:
+ *
+ * if FALSE: a process that gets thawed by SocketOverflow is PERMANENTLY
+ * prevented from freezing ever again.
+ *
+ * if TRUE: a process that gets thawed by SocketOverflow is TEMPORARILY
+ * prevented from freezing for specified time, then it is frozen again. */
+static const bool SOCKET_OVERFLOW_SHOULD_REFREEZE = false;
+static const int SOCKET_OVERFLOW_REFREEZE_MILLISECONDS = 5000;
+
static int freezer_init_check;
/******************************************** Freezer symbols *************************************************/
/* Add methods here */
};
+static gboolean freezer_refreeze_cb(gpointer *data)
+{
+ const int pid = GPOINTER_TO_INT(data);
+
+ /* NB: this pointer cannot be cached and needs to be
+ * looked up every time. This is because the app can
+ * die before it is handled and so the pointer can
+ * become invalidated. */
+ struct proc_app_info *const pai = find_app_info(pid);
+ if (!pai)
+ return FALSE;
+
+ pai->dont_freeze = false;
+ resourced_freezer_app_suspend(& (struct proc_status) { .pid = pid, .pai = pai });
+
+ return FALSE;
+}
+
+static void cleanup_pids_array(GArray **pids)
+{
+ assert(*pids);
+ g_array_free(*pids, TRUE);
+}
+
+static int is_frozen(int frozenstein)
+{
+ __attribute__((cleanup(cleanup_pids_array))) GArray *refrigerati = NULL;
+
+ int ret = cgroup_get_pids("/sys/fs/cgroup/freezer/frozen", &refrigerati);
+ if (ret < 0) {
+ _E("Failed to get list of frozen pids: %s", strerror(-ret));
+ return ret;
+ }
+
+ for (int i = 0; i < refrigerati->len; ++i)
+ if (frozenstein == g_array_index(refrigerati, pid_t, i))
+ return 1;
+
+ return 0;
+}
+
+static int get_pid_from_connection_name(const char *name)
+{
+ assert(name);
+
+ GError *err = NULL;
+ GVariant *reply = g_dbus_connection_call_sync(d_bus_get_connection(),
+ "org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "org.freedesktop.DBus",
+ "GetConnectionUnixProcessID",
+ g_variant_new("(s)", name),
+ G_VARIANT_TYPE("(u)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &err);
+
+ if (err) {
+ g_error_free(err);
+ return -1;
+ } else if (!reply)
+ return -1;
+
+ unsigned int pid = 0;
+ g_variant_get(reply, "(u)", &pid);
+ g_variant_unref(reply);
+
+ if (!pid)
+ return -1;
+
+ return (int) pid;
+}
+
+static bool is_process_too_young_to_die(const struct proc_app_info *pai)
+{
+ unsigned long current_time;
+ if (proc_get_uptime(¤t_time) != RESOURCED_ERROR_NONE)
+ return false;
+
+ return pai->starttime + SOCKET_OVERFLOW_MIN_KILL_AGE_SECONDS > current_time;
+}
+
+static void maybe_kill(int pid, const struct proc_app_info *pai)
+{
+ /* Don't kill processes created recently. If a process
+ * was created recently, it probably means that it has
+ * actually replaced an earlier process which had the
+ * overflow and just received the same PID. */
+ if (is_process_too_young_to_die(pai))
+ return;
+
+ kill(pid, SIGKILL); // 🚮
+}
+
+static bool was_thawed_recently(const struct proc_app_info *pai)
+{
+ unsigned long current_time;
+ if (proc_get_uptime(¤t_time) != RESOURCED_ERROR_NONE)
+ return false;
+
+ /* `last_thaw_time` may look like it never gets updated here,
+ * this is because it happens inside `resourced_freezer_wakeup`
+ * inside the `libsystem-freezer.so` library. */
+ return pai->last_thaw_time + SOCKET_OVERFLOW_MIN_KILL_THAW_SECONDS > current_time;
+}
+
+static void freezer_socket_overflow_handler(GVariant *params)
+{
+ /* This signal means that the DBus daemon is unable to send data
+ * to given connection. The assumption here is that the process
+ * behind the connection is most likely frozen (since otherwise
+ * it would just wake up to fetch the message). */
+
+ const char *connection_name = NULL;
+ g_variant_get(params, "(&s)", &connection_name);
+ if (!connection_name)
+ return;
+
+ int frozenstein = get_pid_from_connection_name(connection_name);
+ if (frozenstein < 0)
+ return;
+
+ struct proc_app_info *const pai = find_app_info(frozenstein);
+ if (!pai) {
+ /* Don't kill services/daemons, this is
+ * a requirement (they aren't supposed
+ * to be frozen, anyway). However, it's
+ * still a suspicious situation so log it. */
+ _E("Non-app process %d has a D-Bus socket overflow!", frozenstein);
+ return;
+ }
+
+ if (was_thawed_recently(pai)) {
+ /* The process was recently thawed. It shouldn't be
+ * killed, and there is no need to thaw it again.
+ * But, prevent it from freezing to make sure it can
+ * handle the socket overflow. */
+ pai->dont_freeze = true;
+ return;
+ }
+
+ if (!is_frozen(frozenstein)) {
+ /* If it has a socket overflow but is NOT frozen,
+ * it seems it's broken enough to be killed. */
+ maybe_kill(frozenstein, pai);
+ return;
+ }
+
+ pai->dont_freeze = true;
+ resourced_freezer_wakeup(& (struct proc_status) { .pid = frozenstein, .pai = pai });
+
+ if (SOCKET_OVERFLOW_SHOULD_REFREEZE) {
+ /* The delay is arbitrary but should be long enough
+ * to let the process handle its DBus message queue. */
+ guint timeout_source = g_timeout_add(SOCKET_OVERFLOW_REFREEZE_MILLISECONDS,
+ G_SOURCE_FUNC(freezer_refreeze_cb),
+ GINT_TO_POINTER(frozenstein));
+
+ /* I don't think we can make use of this.
+ * Cleanup should be handled automatically anyway. */
+ (void) timeout_source;
+ }
+}
+
static void freezer_dbus_init(bool is_suspend)
{
resourced_ret_c ret;
SIGNAL_FREEZER_SERVICE,
(void *)resourced_freezer_dbus_service_signal_handler,
NULL);
+ d_bus_register_signal(DBUS_PATH_DBUS,
+ DBUS_INTERFACE_TIZEN,
+ DBUS_TIZEN_CONNECTION_OVERFLOW_SIGNAL,
+ (void *)freezer_socket_overflow_handler,
+ NULL);
ret = d_bus_register_methods(RESOURCED_PATH_FREEZER, dbus_methods_xml,
dbus_methods, ARRAY_SIZE(dbus_methods));