Do not leak fifo_path
[platform/core/system/dumpsys.git] / src / dumpsys / dumpsys.c
1 /*
2  * Copyright (c) 2019 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the License);
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <stdbool.h>
20 #include <unistd.h>
21 #include <gio/gio.h>
22 #include <gio/gunixfdlist.h>
23 #include <linux/limits.h>
24 #include <string.h>
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <fcntl.h>
28 #include <pthread.h>
29 #include <errno.h>
30 #include <dirent.h>
31 #include <ctype.h>
32
33 #include "common.h"
34
35 #define BUFF_SIZE 4096
36 #define FIFO_BASE_DIR "/run/dumpsys/priv/fifo"
37 #define METHOD_DUMP "Dump"
38 #define SERVICE_NAME_MAX_LEN 255
39
40 static int out_fd = -1;
41 static GVariant *dump_args;
42
43 static void* copy_data(void *arg)
44 {
45         int source_fd = ((int*)arg)[0];
46         int dest_fd = ((int*)arg)[1];
47
48         ssize_t read_count = 0;
49         while (read_count >= 0) {
50                 char buff[BUFF_SIZE];
51                 read_count = read(source_fd, buff, sizeof(buff));
52                 if (read_count == 0)
53                         break;
54
55                 if (read_count == -1) {
56                         if (errno == EAGAIN) {
57                                 read_count = 0;
58                                 sleep(0.1);
59                                 continue;
60                         }
61                         printf("read error: %m\n");
62                         goto out;
63                 }
64                 ssize_t bytes_to_write = read_count;
65                 while (bytes_to_write > 0) {
66                         ssize_t write_count = write(dest_fd, buff, read_count);
67                         if (write_count == -1) {
68                                 printf("write error: %m\n");
69                                 goto out;
70                         }
71                         bytes_to_write -= write_count;
72                 }
73         }
74 out:
75         return NULL;
76 }
77
78 static bool make_fifo(int *write_fd, int *read_fd)
79 {
80         bool result = false;
81         char *fifo_path = NULL;
82
83         if (asprintf(&fifo_path, "%s/dumpsys_XXXXXX", FIFO_BASE_DIR) == -1) {
84                 printf("asprintf error: %m\n");
85                 return result;
86         }
87         uid_t ruid, euid, suid;
88         if (getresuid(&ruid, &euid, &suid) == -1) {
89                 printf("getresuid error: %m\n");
90                 goto end_free;
91         }
92
93         if (seteuid(suid) == -1) {
94                 printf("setuid error: %m\n");
95                 goto end_free;
96         }
97
98         if (mktemp(fifo_path) == NULL) {
99                 printf("mktemp error: %m\n");
100                 goto end;
101         }
102
103         if (mkfifo(fifo_path, 0600) == -1) {
104                 printf("mkfifo error: %m\n");
105                 goto end;
106         }
107
108         /*
109          * We must open for reading before open for writing, because of "No such device or address" error.
110          * Read must have O_NONBLOCK flag, otherwise open will block us until
111          * someone open fifo for writing.
112          */
113         *read_fd = open(fifo_path, O_RDONLY | O_NONBLOCK);
114         if (*read_fd == -1) {
115                 printf("open fifo for reading error: %m\n");
116                 goto end;
117         }
118
119         *write_fd = open(fifo_path, O_WRONLY | O_NONBLOCK);
120         if (*write_fd == -1) {
121                 printf("open fifo for writing error: %m\n");
122                 close(*read_fd);
123                 goto end;
124         }
125
126         if (unlink(fifo_path) == -1) {
127                 printf("unlink '%s' error: %m\n", fifo_path);
128                 close(*read_fd);
129                 close(*write_fd);
130                 goto end;
131         }
132
133         int old_flags = fcntl(*read_fd, F_GETFL);
134         if (old_flags == -1) {
135                 printf("fcntl() GETFL error: %m\n");
136                 close(*read_fd);
137                 close(*write_fd);
138                 goto end;
139         }
140
141         fcntl(*read_fd, F_SETFL, old_flags & ~O_NONBLOCK);
142
143         result = true;
144 end:
145         if (seteuid(euid) == -1) {
146                 printf("setuid error: %m\n");
147                 result = false;
148         }
149 end_free:
150         free(fifo_path);
151
152         return result;
153 }
154
155 static void call_dump(const gchar *name, const gchar *application_name, GDBusConnection *connection)
156 {
157         GError *error = NULL;
158
159         GDBusMessage *method_call_message = g_dbus_message_new_method_call(name,
160                                                                            DUMPSYS_PATH,
161                                                                            DUMPSYS_NAME,
162                                                                            METHOD_DUMP);
163
164         g_dbus_message_set_body(method_call_message, dump_args);
165
166         GUnixFDList *fd_list = g_unix_fd_list_new();
167
168         int write_fd, read_fd;
169         if (!make_fifo(&write_fd, &read_fd)) {
170                 return;
171         }
172
173         pthread_t copy_thread;
174         int fd_pair2[2];
175         fd_pair2[0] = read_fd;
176         fd_pair2[1] = out_fd;
177         if (pthread_create(&copy_thread, NULL, copy_data, &fd_pair2[0]) != 0) {
178                 printf("pthread_create error: %m\n");
179                 return;
180         }
181         g_unix_fd_list_append(fd_list, write_fd, &error);
182         close(write_fd);
183         if (error != NULL) {
184                 dprintf(STDERR_FILENO, "g_unix_fd_list_append() error: %s\n", error->message);
185                 return;
186         }
187
188         g_dbus_message_set_unix_fd_list(method_call_message, fd_list);
189
190         GDBusMessage *repl = g_dbus_connection_send_message_with_reply_sync(connection,
191                                                        method_call_message,
192                                                        G_DBUS_SEND_MESSAGE_FLAGS_NONE,
193                                                        -1,
194                                                        NULL,
195                                                        NULL,
196                                                        &error);
197
198         if (repl != NULL && g_dbus_message_get_message_type(repl) == G_DBUS_MESSAGE_TYPE_ERROR)
199                 g_dbus_message_to_gerror(repl, &error);
200
201         if (error != NULL) {
202                 printf("Send message error: %s\n", error ? error->message : "(unspecified)");
203                 g_error_free(error);
204         }
205
206         g_object_unref(fd_list);
207         g_object_unref(method_call_message);
208         g_object_unref(repl);
209
210         if (pthread_join(copy_thread, NULL) != 0)
211                 printf("pthread_join error: %m\n");
212
213         close(read_fd);
214 }
215
216 static void call_dump_for_list_of_services(GDBusConnection *connection, GList* list_of_services)
217 {
218         GList *element = g_list_first(list_of_services);
219
220         while (element != NULL) {
221                 char *name = (gchar *)element->data;
222                 char service_name[SERVICE_NAME_MAX_LEN];
223
224                 snprintf(service_name, sizeof(service_name), "%s.%s", DUMPSYS_SERVICE_NAME_PREFIX, name);
225                 call_dump(service_name, name, connection);
226
227                 element = g_list_next(element);
228         }
229 }
230
231
232 static GDBusConnection* make_connection(GBusType bus_type)
233 {
234         GError *error = NULL;
235         GDBusConnection *connection = NULL;
236
237         connection = g_bus_get_sync(bus_type, NULL, &error);
238         if (connection == NULL || error != NULL)
239                 printf("make_connection error: %s\n", error->message);
240
241         return connection;
242 }
243
244 static void process_bus(GBusType bus_type, GList *list_of_services)
245 {
246         GDBusConnection *connection = make_connection(bus_type);
247         if (connection == NULL) {
248                 printf("can't connect\n");
249                 return;
250         }
251
252         call_dump_for_list_of_services(connection, list_of_services);
253
254         g_object_unref(connection);
255 }
256
257 static void process_each_bus(GList *list_of_services)
258 {
259         uid_t ruid, euid, suid;
260         if (getresuid(&ruid, &euid, &suid) == -1) {
261                 printf("getresuid error: %m\n");
262                 return;
263         }
264
265         process_bus(G_BUS_TYPE_SYSTEM, list_of_services);
266 }
267
268 static void set_dump_args(int start, int argc, char *argv[])
269 {
270         GVariantBuilder builder;
271         g_variant_builder_init(&builder, G_VARIANT_TYPE("(as)"));
272         g_variant_builder_open(&builder, G_VARIANT_TYPE("as"));
273
274         for (int i = start; i < argc; i++)
275                 g_variant_builder_add(&builder, "s", argv[i]);
276
277         g_variant_builder_close(&builder);
278         dump_args = g_variant_builder_end(&builder);
279 }
280
281 static void print_help(char **argv)
282 {
283         printf("Usage:\n");
284         printf("\t%s [-o filename] <service_name> -- [service arguments]\n\n", argv[0]);
285 }
286
287 int main(int argc, char *argv[])
288 {
289         int opt;
290         char *filename = NULL;
291         out_fd = STDOUT_FILENO;
292
293         while ((opt = getopt(argc, argv, "o:")) != -1) {
294                 switch(opt) {
295                 case 'o':
296                         filename = optarg;
297                         break;
298                 }
299         }
300
301         if (optind >= argc) {
302                 print_help(argv);
303                 return EXIT_FAILURE;
304         }
305
306         if (filename != NULL) {
307                 out_fd = open(filename, O_WRONLY | O_CREAT, 0600);
308                 if (out_fd == -1) {
309                         printf("Can not open file '%s': %m\n", filename);
310                         return EXIT_FAILURE;
311                 }
312         }
313
314         GList *list_of_services = NULL;
315
316         int i = optind;
317
318         if (i < argc)
319                 list_of_services = g_list_append(list_of_services, strdup(argv[i]));
320
321         set_dump_args(i+1, argc, argv);
322
323         process_each_bus(list_of_services);
324
325         g_list_free_full(list_of_services, free);
326
327         return EXIT_SUCCESS;
328 }