Add crash manager API
[platform/core/system/crash-worker.git] / src / crash-service / crash-service.c
1 /*
2  * crash-service
3  *
4  * Copyright (c) 2019 Samsung Electronics Co., Ltd.
5  *
6  * Licensed under the Apache License, Version 2.0 (the License);
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18
19 #include <gio/gio.h>
20 #include <stdbool.h>
21 #include <stdlib.h>
22 #include <sys/select.h>
23 #include <sys/types.h>
24 #include <sys/wait.h>
25 #include <unistd.h>
26
27 #include "crash-manager/crash-manager.h"
28 #include "libcrash-service.h"
29 #include "shared/log.h"
30
31 /* Dbus activation */
32 #define CRASH_BUS_NAME          "org.tizen.system.crash.livedump"
33 #define CRASH_OBJECT_PATH       "/Org/Tizen/System/Crash/Livedump"
34
35 #define TIMEOUT_INTERVAL_SEC 60
36 #define TIMEOUT_LIVEDUMP_SEC 50
37
38 #define CS_ERROR           1
39 #define CS_ERR_PARAM       1
40 #define CS_ERR_TIMEOUT     2
41 #define CS_ERR_READ        3
42 #define CS_ERR_INTERNAL    4
43
44 static GMainLoop *loop;
45 static GMutex timeout_mutex;
46 static guint timeout_id;
47 static GDBusNodeInfo *introspection_data;
48 static const gchar introspection_xml[] =
49 "<node>"
50 " <interface name='org.tizen.system.crash.livedump'>"
51 "  <method name='livedump_pid'>"
52 "   <arg type='i' name='pid' direction='in'/>"
53 "   <arg type='s' name='dump_reason' direction='in'/>"
54 "   <arg type='s' name='report_path' direction='out'/>"
55 "  </method>"
56 " </interface>"
57 "</node>";
58
59 void child_exit(int sig)
60 {
61         wait(NULL);
62 }
63
64 static int timeout_cb(gpointer data)
65 {
66         _I("Time out!");
67         g_main_loop_quit((GMainLoop *)data);
68
69         return 0;
70 }
71
72 static void add_timeout(void)
73 {
74         g_mutex_lock(&timeout_mutex);
75
76         if (timeout_id)
77                 g_source_remove(timeout_id);
78         timeout_id = g_timeout_add_seconds(TIMEOUT_INTERVAL_SEC, timeout_cb, loop);
79
80         g_mutex_unlock(&timeout_mutex);
81         _D("Add loop timeout (%d)", TIMEOUT_INTERVAL_SEC);
82 }
83
84 static void remove_timeout(void)
85 {
86         g_mutex_lock(&timeout_mutex);
87
88         if (timeout_id) {
89                 g_source_remove(timeout_id);
90                 timeout_id = 0;
91         }
92
93         g_mutex_unlock(&timeout_mutex);
94         _D("Remove loop timeout");
95 }
96
97 struct livedump_cb_data {
98         int read_fd;
99         GDBusMethodInvocation *invocation;
100         GSource* source;
101         pid_t child_pid;
102 };
103
104 static bool data_ready(int fd)
105 {
106         fd_set rfd;
107         FD_ZERO(&rfd);
108         FD_SET(fd, &rfd);
109         struct timeval tv = {.tv_sec = 0, .tv_usec = 0};
110         int retval = select(fd+1, &rfd, NULL, NULL, &tv);
111         if (retval == -1)
112                 _E("select() error: %m");
113         return retval == 1;
114 }
115
116 static gboolean read_result_cb(gpointer data)
117 {
118         struct livedump_cb_data *cb_data = (struct livedump_cb_data*)data;
119         char report_path[PATH_MAX];
120
121         if (!data_ready(cb_data->read_fd)) {
122                 _I("Report is not ready after %d seconds.", TIMEOUT_LIVEDUMP_SEC);
123                 g_dbus_method_invocation_return_error(cb_data->invocation,
124                                                       CS_ERROR,
125                                                       CS_ERR_TIMEOUT,
126                                                       "Report is not ready");
127                 kill(cb_data->child_pid, SIGKILL);
128                 goto end;
129         } else {
130                 if (read(cb_data->read_fd, report_path, sizeof(report_path)) == -1) {
131                         _E("Read from child error: %m");
132                         g_dbus_method_invocation_return_error(cb_data->invocation,
133                                                               CS_ERROR,
134                                                               CS_ERR_READ,
135                                                               "Error while obtaining report path");
136                         goto end;
137                 }
138         }
139
140         g_dbus_method_invocation_return_value(cb_data->invocation,
141                                               g_variant_new("(s)", report_path));
142 end:
143         close(cb_data->read_fd);
144         free(cb_data);
145         return G_SOURCE_REMOVE;
146 }
147
148 static bool livedump_run(int write_fd, pid_t pid, const gchar *dump_reason)
149 {
150         char report_path[PATH_MAX];
151
152         if (!crash_manager_livedump_pid(pid, dump_reason, report_path, sizeof(report_path))) {
153                 _E("crash_manager_livedump_pid error");
154                 return false;
155         }
156
157         if (write(write_fd, report_path, strlen(report_path) + 1) == -1) {
158                 _E("Write report_path error: %m");
159                 close(write_fd);
160                 return false;
161         }
162
163         close(write_fd);
164         return true;
165 }
166
167 static void livedump_pid_handler(GDBusMethodInvocation *invocation, pid_t pid, gchar *dump_reason)
168 {
169         /*
170          * We want to run the livedump in different process so as not to
171          * block the main loop, to be able to handle many method calls
172          * in the same time, so we need a communication cannel to send
173          * back a result report path.
174          */
175         int fds[2];
176         if (pipe(fds) == -1) {
177                 _E("Pipe error: %m");
178                 g_dbus_method_invocation_return_error(invocation,
179                                                       CS_ERROR,
180                                                       CS_ERR_INTERNAL,
181                                                       "Internal error");
182                 return;
183         }
184
185         struct livedump_cb_data *cb_data = (struct livedump_cb_data*)malloc(sizeof(struct livedump_cb_data));
186         if (cb_data == NULL) {
187                 _E("cb_data malloc() error: %m");
188                 exit(EXIT_FAILURE);
189         }
190         cb_data->read_fd = fds[0];
191         cb_data->invocation = invocation;
192
193         GSource *source = g_timeout_source_new_seconds(TIMEOUT_LIVEDUMP_SEC);
194         cb_data->source = source;
195         g_source_add_unix_fd(source, fds[0], G_IO_IN);
196         g_source_set_callback(source, read_result_cb, cb_data, NULL);
197         g_source_attach(source, NULL);
198
199         pid_t child_pid = fork();
200         if (child_pid == 0) {
201                 close(fds[0]);
202                 if (livedump_run(fds[1], pid, dump_reason))
203                         exit(EXIT_SUCCESS);
204                 else
205                         exit(EXIT_FAILURE);
206         } else if (child_pid > 0) {
207                 cb_data->child_pid = child_pid;
208                 close(fds[1]);
209         } else {
210                 _E("fork() error: %m");
211                 g_dbus_method_invocation_return_error(invocation,
212                                                       CS_ERROR,
213                                                       CS_ERR_INTERNAL,
214                                                       "Internal error");
215                 close(fds[0]);
216                 close(fds[1]);
217         }
218 }
219
220 static void method_call_handler(GDBusConnection *conn,
221                                 const gchar *sender,
222                                 const gchar *object_path,
223                                 const gchar *iface_name,
224                                 const gchar *method_name,
225                                 GVariant *parameters,
226                                 GDBusMethodInvocation *invocation,
227                                 gpointer user_data)
228 {
229         remove_timeout();
230
231         if (g_strcmp0(method_name, "livedump_pid") == 0) {
232                 gchar *dump_reason;
233                 const pid_t pid;
234
235                 if (g_variant_is_of_type(parameters, G_VARIANT_TYPE("(is)"))) {
236                         g_variant_get(parameters, "(is)", &pid, &dump_reason);
237                         livedump_pid_handler(invocation, pid, dump_reason);
238                 } else {
239                         _E("Parameters are not of the correct type (is)");
240                         g_dbus_method_invocation_return_error(invocation,
241                                                               CS_ERROR,
242                                                               CS_ERR_PARAM,
243                                                               "Parameters are not of the correct type (is)");
244                 }
245         }
246
247         add_timeout();
248 }
249
250 static const GDBusInterfaceVTable interface_vtable = {
251         method_call_handler,
252         NULL,
253         NULL
254 };
255
256 static void on_bus_acquired(GDBusConnection *conn,
257                             const gchar *name,
258                             gpointer user_data)
259 {
260         guint registration_id;
261
262         GError *error = NULL;
263         registration_id = g_dbus_connection_register_object(conn,
264                                 CRASH_OBJECT_PATH,
265                                 introspection_data->interfaces[0],
266                                 &interface_vtable, NULL, NULL, &error);
267         if (registration_id == 0 || error) {
268                 _E("Failed to g_dbus_connection_register_object: %s", error ? error->message : "");
269                 if (error)
270                         g_error_free(error);
271         }
272 }
273
274 static void on_name_acquired(GDBusConnection *conn,
275                              const gchar *name,
276                              gpointer user_data)
277 {
278         _D("Acquired the name %s on the system bus", name);
279 }
280
281 static void on_name_lost(GDBusConnection *conn,
282                          const gchar *name,
283                          gpointer user_data)
284 {
285         _D("Lost the name %s on the system bus", name);
286 }
287
288 static bool dbus_init(void)
289 {
290         GError *error = NULL;
291
292         introspection_data =
293                 g_dbus_node_info_new_for_xml(introspection_xml, NULL);
294         if (introspection_data == NULL) {
295                 _E("Failed to init g_dbus_info_new_for_xml");
296                 return false;
297         }
298
299         GDBusConnection *conn = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error);
300         if (!conn || error) {
301                 _E("Failed to get dbus: %s", error ? error->message : "");
302                 if (error)
303                         g_error_free(error);
304                 return false;
305         }
306
307         g_bus_own_name(G_BUS_TYPE_SYSTEM, CRASH_BUS_NAME,
308                        G_BUS_NAME_OWNER_FLAGS_NONE, on_bus_acquired,
309                        on_name_acquired, on_name_lost, NULL, NULL);
310
311         return true;
312 }
313
314 int main(void)
315 {
316         signal(SIGCHLD, child_exit);
317         loop = g_main_loop_new(NULL, false);
318
319         if (!dbus_init()) {
320                 g_main_loop_unref(loop);
321                 return EXIT_FAILURE;
322         }
323
324         g_mutex_init(&timeout_mutex);
325         add_timeout();
326
327         g_main_loop_run(loop);
328
329         if (introspection_data)
330                 g_dbus_node_info_unref(introspection_data);
331         g_mutex_clear(&timeout_mutex);
332         g_main_loop_unref(loop);
333
334         return EXIT_SUCCESS;
335 }