+/*
+ * crash-service
+ *
+ * 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.
+ */
+
+#include <gio/gio.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <sys/select.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "crash-manager/crash-manager.h"
+#include "libcrash-service.h"
+#include "shared/log.h"
+
+/* Dbus activation */
+#define CRASH_BUS_NAME "org.tizen.system.crash.livedump"
+#define CRASH_OBJECT_PATH "/Org/Tizen/System/Crash/Livedump"
+
+#define TIMEOUT_INTERVAL_SEC 60
+#define TIMEOUT_LIVEDUMP_SEC 50
+
+#define CS_ERROR 1
+#define CS_ERR_PARAM 1
+#define CS_ERR_TIMEOUT 2
+#define CS_ERR_READ 3
+#define CS_ERR_INTERNAL 4
+
+static GMainLoop *loop;
+static GMutex timeout_mutex;
+static guint timeout_id;
+static GDBusNodeInfo *introspection_data;
+static const gchar introspection_xml[] =
+"<node>"
+" <interface name='org.tizen.system.crash.livedump'>"
+" <method name='livedump_pid'>"
+" <arg type='i' name='pid' direction='in'/>"
+" <arg type='s' name='dump_reason' direction='in'/>"
+" <arg type='s' name='report_path' direction='out'/>"
+" </method>"
+" </interface>"
+"</node>";
+
+void child_exit(int sig)
+{
+ wait(NULL);
+}
+
+static int timeout_cb(gpointer data)
+{
+ _I("Time out!");
+ g_main_loop_quit((GMainLoop *)data);
+
+ return 0;
+}
+
+static void add_timeout(void)
+{
+ g_mutex_lock(&timeout_mutex);
+
+ if (timeout_id)
+ g_source_remove(timeout_id);
+ timeout_id = g_timeout_add_seconds(TIMEOUT_INTERVAL_SEC, timeout_cb, loop);
+
+ g_mutex_unlock(&timeout_mutex);
+ _D("Add loop timeout (%d)", TIMEOUT_INTERVAL_SEC);
+}
+
+static void remove_timeout(void)
+{
+ g_mutex_lock(&timeout_mutex);
+
+ if (timeout_id) {
+ g_source_remove(timeout_id);
+ timeout_id = 0;
+ }
+
+ g_mutex_unlock(&timeout_mutex);
+ _D("Remove loop timeout");
+}
+
+struct livedump_cb_data {
+ int read_fd;
+ GDBusMethodInvocation *invocation;
+ GSource* source;
+ pid_t child_pid;
+};
+
+static bool data_ready(int fd)
+{
+ fd_set rfd;
+ FD_ZERO(&rfd);
+ FD_SET(fd, &rfd);
+ struct timeval tv = {.tv_sec = 0, .tv_usec = 0};
+ int retval = select(fd+1, &rfd, NULL, NULL, &tv);
+ if (retval == -1)
+ _E("select() error: %m");
+ return retval == 1;
+}
+
+static gboolean read_result_cb(gpointer data)
+{
+ struct livedump_cb_data *cb_data = (struct livedump_cb_data*)data;
+ char report_path[PATH_MAX];
+
+ if (!data_ready(cb_data->read_fd)) {
+ _I("Report is not ready after %d seconds.", TIMEOUT_LIVEDUMP_SEC);
+ g_dbus_method_invocation_return_error(cb_data->invocation,
+ CS_ERROR,
+ CS_ERR_TIMEOUT,
+ "Report is not ready");
+ kill(cb_data->child_pid, SIGKILL);
+ goto end;
+ } else {
+ if (read(cb_data->read_fd, report_path, sizeof(report_path)) == -1) {
+ _E("Read from child error: %m");
+ g_dbus_method_invocation_return_error(cb_data->invocation,
+ CS_ERROR,
+ CS_ERR_READ,
+ "Error while obtaining report path");
+ goto end;
+ }
+ }
+
+ g_dbus_method_invocation_return_value(cb_data->invocation,
+ g_variant_new("(s)", report_path));
+end:
+ close(cb_data->read_fd);
+ free(cb_data);
+ return G_SOURCE_REMOVE;
+}
+
+static bool livedump_run(int write_fd, pid_t pid, const gchar *dump_reason)
+{
+ char report_path[PATH_MAX];
+
+ if (!crash_manager_livedump_pid(pid, dump_reason, report_path, sizeof(report_path))) {
+ _E("crash_manager_livedump_pid error");
+ return false;
+ }
+
+ if (write(write_fd, report_path, strlen(report_path) + 1) == -1) {
+ _E("Write report_path error: %m");
+ close(write_fd);
+ return false;
+ }
+
+ close(write_fd);
+ return true;
+}
+
+static void livedump_pid_handler(GDBusMethodInvocation *invocation, pid_t pid, gchar *dump_reason)
+{
+ /*
+ * We want to run the livedump in different process so as not to
+ * block the main loop, to be able to handle many method calls
+ * in the same time, so we need a communication cannel to send
+ * back a result report path.
+ */
+ int fds[2];
+ if (pipe(fds) == -1) {
+ _E("Pipe error: %m");
+ g_dbus_method_invocation_return_error(invocation,
+ CS_ERROR,
+ CS_ERR_INTERNAL,
+ "Internal error");
+ return;
+ }
+
+ struct livedump_cb_data *cb_data = (struct livedump_cb_data*)malloc(sizeof(struct livedump_cb_data));
+ if (cb_data == NULL) {
+ _E("cb_data malloc() error: %m");
+ exit(EXIT_FAILURE);
+ }
+ cb_data->read_fd = fds[0];
+ cb_data->invocation = invocation;
+
+ GSource *source = g_timeout_source_new_seconds(TIMEOUT_LIVEDUMP_SEC);
+ cb_data->source = source;
+ g_source_add_unix_fd(source, fds[0], G_IO_IN);
+ g_source_set_callback(source, read_result_cb, cb_data, NULL);
+ g_source_attach(source, NULL);
+
+ pid_t child_pid = fork();
+ if (child_pid == 0) {
+ close(fds[0]);
+ if (livedump_run(fds[1], pid, dump_reason))
+ exit(EXIT_SUCCESS);
+ else
+ exit(EXIT_FAILURE);
+ } else if (child_pid > 0) {
+ cb_data->child_pid = child_pid;
+ close(fds[1]);
+ } else {
+ _E("fork() error: %m");
+ g_dbus_method_invocation_return_error(invocation,
+ CS_ERROR,
+ CS_ERR_INTERNAL,
+ "Internal error");
+ close(fds[0]);
+ close(fds[1]);
+ }
+}
+
+static void method_call_handler(GDBusConnection *conn,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *iface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ remove_timeout();
+
+ if (g_strcmp0(method_name, "livedump_pid") == 0) {
+ gchar *dump_reason;
+ const pid_t pid;
+
+ if (g_variant_is_of_type(parameters, G_VARIANT_TYPE("(is)"))) {
+ g_variant_get(parameters, "(is)", &pid, &dump_reason);
+ livedump_pid_handler(invocation, pid, dump_reason);
+ } else {
+ _E("Parameters are not of the correct type (is)");
+ g_dbus_method_invocation_return_error(invocation,
+ CS_ERROR,
+ CS_ERR_PARAM,
+ "Parameters are not of the correct type (is)");
+ }
+ }
+
+ add_timeout();
+}
+
+static const GDBusInterfaceVTable interface_vtable = {
+ method_call_handler,
+ NULL,
+ NULL
+};
+
+static void on_bus_acquired(GDBusConnection *conn,
+ const gchar *name,
+ gpointer user_data)
+{
+ guint registration_id;
+
+ GError *error = NULL;
+ registration_id = g_dbus_connection_register_object(conn,
+ CRASH_OBJECT_PATH,
+ introspection_data->interfaces[0],
+ &interface_vtable, NULL, NULL, &error);
+ if (registration_id == 0 || error) {
+ _E("Failed to g_dbus_connection_register_object: %s", error ? error->message : "");
+ if (error)
+ g_error_free(error);
+ }
+}
+
+static void on_name_acquired(GDBusConnection *conn,
+ const gchar *name,
+ gpointer user_data)
+{
+ _D("Acquired the name %s on the system bus", name);
+}
+
+static void on_name_lost(GDBusConnection *conn,
+ const gchar *name,
+ gpointer user_data)
+{
+ _D("Lost the name %s on the system bus", name);
+}
+
+static bool dbus_init(void)
+{
+ GError *error = NULL;
+
+ introspection_data =
+ g_dbus_node_info_new_for_xml(introspection_xml, NULL);
+ if (introspection_data == NULL) {
+ _E("Failed to init g_dbus_info_new_for_xml");
+ return false;
+ }
+
+ GDBusConnection *conn = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error);
+ if (!conn || error) {
+ _E("Failed to get dbus: %s", error ? error->message : "");
+ if (error)
+ g_error_free(error);
+ return false;
+ }
+
+ g_bus_own_name(G_BUS_TYPE_SYSTEM, CRASH_BUS_NAME,
+ G_BUS_NAME_OWNER_FLAGS_NONE, on_bus_acquired,
+ on_name_acquired, on_name_lost, NULL, NULL);
+
+ return true;
+}
+
+int main(void)
+{
+ signal(SIGCHLD, child_exit);
+ loop = g_main_loop_new(NULL, false);
+
+ if (!dbus_init()) {
+ g_main_loop_unref(loop);
+ return EXIT_FAILURE;
+ }
+
+ g_mutex_init(&timeout_mutex);
+ add_timeout();
+
+ g_main_loop_run(loop);
+
+ if (introspection_data)
+ g_dbus_node_info_unref(introspection_data);
+ g_mutex_clear(&timeout_mutex);
+ g_main_loop_unref(loop);
+
+ return EXIT_SUCCESS;
+}