e9ed86f866ee1df3198d2a3aed649305fe003e5a
[platform/core/system/dlog.git] / src / logutil / logutil.c
1 /*
2  * Copyright (c) 2016-2020, Samsung Electronics Co., Ltd. All rights reserved.
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 <assert.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20
21 #include <fcntl.h>
22 #include <getopt.h>
23 #include <sys/epoll.h>
24
25 #include <log_file.h>
26 #include <sort_vector.h>
27 #include <dlog-internal.h>
28
29 #include "logutil_doc.h"
30
31 /**
32  * @addtogroup DLOG_IMPLEMENTATION
33  * @{
34  * @defgroup DLOG_UTIL Util
35  * @brief The log retrieval utility
36  * @details The utility responsible for reading and processing logs for the user.
37  * @{
38  */
39
40 // buffers to use by default, when nothing specified
41 static const int default_buffers = (1 << LOG_ID_MAIN) | (1 << LOG_ID_SYSTEM) | (1 << LOG_ID_APPS);
42
43 typedef enum action_e {
44         ACTION_PRINT = 0,
45         ACTION_GET_CAPACITY,
46         ACTION_CLEAR,
47 } action_e;
48
49 static void show_version(const char *name)
50 {
51         printf("%s version: %s-%s\n", name, __DLOG_VERSION, __DLOG_RELEASE);
52 }
53
54 static int parse_options(int argc, char **argv, struct log_file *l_file, int *enabled_buffers, action_e *action,
55         dlogutil_config_s *config, dlogutil_mode_e *mode, unsigned int *dump_size, dlogutil_sorting_order_e *sort_by)
56 {
57         assert(argv);
58         assert(l_file);
59         assert(enabled_buffers);
60         assert(action);
61         assert(config);
62         assert(mode);
63         assert(sort_by);
64
65         *mode = DLOGUTIL_MODE_CONTINUOUS;
66         *dump_size = DLOGUTIL_MAX_DUMP_SIZE;
67
68         while (1) {
69                 static const struct option long_options[] = {
70                         {"tid"    , required_argument, NULL,   0},
71                         {"pid"    , required_argument, NULL,   1},
72                         {"version",       no_argument, NULL,   2},
73                         {"color"  , required_argument, NULL,   3},
74                         {"sort-by", required_argument, NULL,   4},
75                         {"help"   ,       no_argument, NULL, 'h'},
76                         {0}
77                 };
78                 int err_arg_nondigit = 0;
79                 int option = getopt_long(argc, argv, "cdmt:gsf:r:n:v:b:u:h", long_options, NULL);
80
81                 if (option < 0)
82                         break;
83
84                 switch (option) {
85                 case 0: { /* tid filter */
86                         int tid;
87                         if (sscanf(optarg, "%d", &tid) != 1)
88                                 err_arg_nondigit = 1;
89                         else if (dlogutil_config_filter_tid(config, tid))
90                                 goto enomem;
91                         break;
92                 }
93                 case 1: { /* pid filter */
94                         int pid;
95                         if (sscanf(optarg, "%d", &pid) != 1)
96                                 err_arg_nondigit = 1;
97                         else if (dlogutil_config_filter_pid(config, pid))
98                                 goto enomem;
99                         break;
100                 }
101                 case 2: { /* version */
102                         show_version(argv[0]);
103                         return 1;
104                 }
105                 case 3: /* colored headers */
106                         l_file->colors_auto = false;
107                         if (!strcmp(optarg, "always"))
108                                 l_file->format.color = true;
109                         else if (!strcmp(optarg, "auto"))
110                                 l_file->colors_auto = true;
111                         else if (!strcmp(optarg, "never"))
112                                 l_file->format.color = false;
113                         else {
114                                 ERR("Error: invalid color setting\n");
115                                 return -EINVAL;
116                         }
117                         break;
118                 case 4: /* timestamp */
119                         if (!strcmp(optarg, "default"))
120                                 *sort_by = DLOGUTIL_SORT_DEFAULT;
121                         else {
122                                 *sort_by = get_order_from_string(optarg);
123                                 if (*sort_by == DLOGUTIL_SORT_DEFAULT) {
124                                         ERR("Error: invalid sort by\n");
125                                         return -EINVAL;
126                                 }
127                         }
128                         break;
129                 case 'd':
130                         *mode = DLOGUTIL_MODE_DUMP;
131                         break;
132                 case 'm':
133                         *mode = DLOGUTIL_MODE_MONITOR;
134                         break;
135                 case 't':
136                         *mode = DLOGUTIL_MODE_DUMP;
137                         if (sscanf(optarg, "%u", dump_size) != 1)
138                                 err_arg_nondigit = 1;
139                         break;
140                 case 'c':
141                         *action = ACTION_CLEAR;
142                         break;
143                 case 'g':
144                         *action = ACTION_GET_CAPACITY;
145                         break;
146                 case 'b': {
147                         log_id_t id = log_id_by_name(optarg);
148                         if (id == LOG_ID_INVALID) {
149                                 ERR("Error: there is no buffer \"%s\"\n", optarg);
150                                 return -ENOENT;
151                         }
152                         bit_set(enabled_buffers, id);
153                         break;
154                 }
155                 case 'u': {
156                         unsigned size;
157                         if (sscanf(optarg, "%u", &size) != 1)
158                                 err_arg_nondigit = 1;
159                         if (size == 0)
160                                 dlogutil_config_sorting_disable(config);
161                         else
162                                 dlogutil_config_sorting_enable_with_size(config, size);
163                         break;
164                 }
165                 case 'f':
166                         if (logfile_set_path(l_file, optarg) < 0)
167                                 goto enomem;
168                         break;
169                 case 'v': {
170                         l_file->format.format = log_format_from_string(optarg);
171                         if (l_file->format.format == FORMAT_OFF) {
172                                 ERR("Error: invalid format\n");
173                                 return -EINVAL;
174                         }
175                         break;
176                 }
177                 case 's':
178                         if (dlogutil_config_filter_filterspec(config, "*:S"))
179                                 goto enomem;
180                         break;
181                 case 'r':
182                         if (sscanf(optarg, "%zu", &l_file->rotate_size_kbytes) != 1)
183                                 err_arg_nondigit = 1;
184                         break;
185                 case 'n':
186                         if (sscanf(optarg, "%zu", &l_file->max_rotated) != 1)
187                                 err_arg_nondigit = 1;
188                         break;
189                 case 'h':
190                         show_help(argv[0], true);
191                         return 1;
192                 default: // invalid option or missing mandatory parameter
193                         show_help(argv[0], false);
194                         return -EINVAL;
195                 }
196
197                 if (err_arg_nondigit) {
198                         ERR("Error: -%c requires a numerical parameter\n", option);
199                         return -EINVAL;
200                 }
201         }
202
203         while (optind < argc) {
204                 int r = dlogutil_config_filter_filterspec(config, argv[optind++]);
205                 switch (r) {
206                 case TIZEN_ERROR_INVALID_PARAMETER:
207                         /* We assert filter is not NULL above, and the system should
208                          * guarantee that argv[i] are also not NULL. Therefore this
209                          * error always means invalid string contents passed by the
210                          * user, for example `dlogutil :D`. */
211                         show_help(argv[0], false);
212                         return -EINVAL;
213                 case TIZEN_ERROR_OUT_OF_MEMORY:
214                         goto enomem;
215                 case TIZEN_ERROR_NONE:
216                         continue;
217                 default:
218                         assert(false);
219                 }
220         }
221
222         if (*enabled_buffers == 0)
223                 *enabled_buffers = default_buffers;
224
225         return 0;
226
227 enomem:
228         ERR("Error: out of memory\n");
229         return -ENOMEM;
230 }
231
232 static void config_cleanup(dlogutil_config_s *const *config) {
233         assert(config);
234         dlogutil_config_destroy(*config);
235 }
236
237 static int print_buffer_capacity(dlogutil_state_s *state, log_id_t buffer)
238 {
239         const char *name;
240         int r = dlogutil_buffer_get_name(buffer, &name);
241         if (r != 0)
242                 return r;
243
244         log_id_t aliased;
245         r = dlogutil_buffer_get_alias(state, buffer, &aliased);
246         if (r != 0)
247                 return r;
248         if (aliased == LOG_ID_INVALID) {
249                 printf("%s: disabled\n", name);
250                 return TIZEN_ERROR_NONE;
251         } else if (aliased != buffer) {
252                 const char *aliased_name;
253                 int r = dlogutil_buffer_get_name(aliased, &aliased_name);
254                 if (r != 0)
255                         return r;
256
257                 printf("%s: alias of %s\n", name, aliased_name);
258                 return TIZEN_ERROR_NONE;
259         }
260
261         unsigned int usage, capacity;
262         r = dlogutil_buffer_get_capacity(state, buffer, &capacity);
263         if (r != 0)
264                 return r;
265
266         r = dlogutil_buffer_get_usage(state, buffer, &usage);
267         if (r != 0)
268                 return r;
269
270         printf("%s: %u KiB, of which %u used (%u%%)\n", name, BtoKiB(capacity), BtoKiB(usage), 100 * usage / capacity);
271         return 0;
272 }
273
274 static int clear_buffer(dlogutil_state_s *state, log_id_t buffer)
275 {
276         log_id_t aliased;
277         int r = dlogutil_buffer_get_alias(state, buffer, &aliased);
278         if (r != 0)
279                 return r;
280         if (aliased != buffer)
281                 return 0;
282
283         return dlogutil_buffer_clear(state, buffer);
284 }
285
286 static int for_each_buffer(int enabled_buffers, int (*func)(dlogutil_state_s *state, log_id_t buffer))
287 {
288         __attribute__((cleanup(config_cleanup))) dlogutil_config_s *c = dlogutil_config_create();
289         int r;
290
291         for (int i = 0; i < LOG_ID_MAX; ++i) {
292                 const int single_buf_mask = (1 << i);
293                 if (single_buf_mask & enabled_buffers) {
294                         r = dlogutil_config_buffer_add(c, i);
295                         if (r != 0)
296                                 return r;
297                 }
298         }
299
300
301         dlogutil_state_s *s;
302         r = dlogutil_config_connect(c, &s);
303         if (r != 0)
304                 return r;
305
306         for (int i = 0; i < LOG_ID_MAX; ++i) {
307                 const int single_buf_mask = (1 << i);
308                 if (single_buf_mask & enabled_buffers) {
309                         r = func(s, i);
310                         if (r != 0)
311                                 return r;
312                 }
313         }
314
315         return 0;
316 }
317
318 /**
319  * @return An error code
320  * @retval 0 Success
321  * @retval 1 Silent failure
322  * @retval <0 Failure as denoted by value: -errno
323  */
324 static int do_print(dlogutil_mode_e mode, unsigned int dump_size, int enabled_buffers, dlogutil_sorting_order_e sort_by,
325         dlogutil_config_s *config, struct log_file *l_file)
326 {
327         /* Optimisation for short-lived (i.e. dumping) instances.
328          *
329          * We can assume that such instances will not see a timezone
330          * change happen during their lifetime, and even if they do,
331          * it is probably best if they ignore it (a dump would ideally
332          * behave as if it was atomic). This means we can optimize the
333          * behaviour where printing the timestamp requires us to check
334          * whether the timezone changed, which involves calling stat()
335          * on /etc/localtime, which is not cheap on its own and made
336          * even worse when it has to go through multiple symlinks.
337          *
338          * glibc has a feature where setting the TZ environmental var
339          * will cache the timezone, achieving precisely what we want. */
340         if (mode != DLOGUTIL_MODE_CONTINUOUS && !getenv("TZ"))
341                 putenv((char *) "TZ=:/etc/localtime");
342
343         int r = l_file->path ? logfile_open(l_file) : 0;
344         if (r < 0) {
345                 errno = -r;
346                 ERR("Error while creating the output file: %m\n");
347                 return 1;
348         }
349
350         if (sort_by == DLOGUTIL_SORT_DEFAULT)
351                 sort_by = get_format_sorting(l_file->format.format);
352         dlogutil_config_order_set(config, sort_by);
353
354         /* We implicitly assume three things here:
355          * 1. if a timestamp is available for one of the buffers,
356          *    it is available for all of them (which is true),
357          * 2. enabled_buffers != 0 (set by parse_options),
358          * 3. we will sort by the format timestamp if it is available
359          *    and the default if it is not (which isn't 100% true,
360          *    but is a good approximation). */
361         int one_buffer = 0;
362         {
363                 // one_buffer will contain exactly one of the enabled buffers
364                 int enabled_buffers_copy = enabled_buffers;
365                 while ((enabled_buffers_copy & 1) != 1) {
366                         enabled_buffers_copy >>= 1;
367                         one_buffer += 1;
368                 }
369         }
370
371         bool available = false;
372         if (sort_by != DLOGUTIL_SORT_DEFAULT) {
373                 r = dlogutil_buffer_check_ts_type_available((dlogutil_buffer_e) one_buffer, sort_by, &available);
374                 if (r < 0) {
375                         errno = -r;
376                         ERR("Error while checking timestamp availability: %m");
377                         return 1;
378                 }
379         }
380         if (!available) {
381                 r = dlogutil_buffer_get_default_ts_type((dlogutil_buffer_e) one_buffer, &sort_by);
382                 if (r < 0) {
383                         errno = -r;
384                         ERR("Error while fetching default timestamp type: %m");
385                         return 1;
386                 }
387         }
388
389         /* TODO: this should be done right when parsing options, but
390          * for now clear() and getsize() don't support that yet */
391         for (int i = 0; i < LOG_ID_MAX; ++i)
392                 if (enabled_buffers & (1 << i))
393                         dlogutil_config_buffer_add(config, (log_id_t) i);
394
395         dlogutil_state_s *state;
396         if (mode == DLOGUTIL_MODE_MONITOR)
397                 r = dlogutil_config_mode_set_monitor(config);
398         else if (mode == DLOGUTIL_MODE_CONTINUOUS)
399                 r = dlogutil_config_mode_set_continuous(config);
400         else
401                 r = dlogutil_config_mode_set_dump(config, dump_size);
402
403         if (!r)
404                 r = dlogutil_config_connect(config, &state);
405
406         if (r < 0) {
407                 errno = -r;
408                 ERR("Error while preparing libdlogutil: %m");
409                 return 1;
410         }
411
412         for (;;) {
413                 __attribute__((cleanup(free_ptr))) dlogutil_entry_s *entry;
414
415                 r = dlogutil_get_log(state, -1, &entry);
416                 if (r == TIZEN_ERROR_NO_DATA)
417                         return 0;
418                 else if (r < 0) {
419                         errno = -r;
420                         ERR("Error while retrieving a log: %m");
421                         return 1;
422                 }
423
424                 r = logfile_write_with_rotation(entry, l_file, sort_by);
425                 if (r < 0) {
426                         errno = -r;
427                         ERR("Error while printing a log: %m");
428                         return 1;
429                 } else if (r > 0)
430                         return 0; // Quiet failure for some reason
431         }
432 }
433
434 #ifndef UNIT_TEST
435 int main(int argc, char **argv)
436 {
437         int enabled_buffers = 0; // bitset
438         action_e action = ACTION_PRINT;
439         __attribute__ ((cleanup(logfile_free))) struct log_file l_file;
440         logfile_init(&l_file);
441         logfile_set_fd(&l_file, fileno(stdout), 0);
442
443         __attribute__((cleanup(config_cleanup))) dlogutil_config_s *config = dlogutil_config_create();
444         if (!config) {
445                 errno = ENOMEM;
446                 ERR("Error while initialising: %m\n");
447                 return EXIT_FAILURE;
448         }
449         dlogutil_mode_e mode;
450         unsigned int dump_size;
451         dlogutil_sorting_order_e sort_by = DLOGUTIL_SORT_DEFAULT;
452
453         int r = parse_options(argc, argv, &l_file, &enabled_buffers, &action, config, &mode, &dump_size, &sort_by);
454         if (r)
455                 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
456
457         switch (action) {
458         case ACTION_PRINT: {
459                 r = do_print(mode, dump_size, enabled_buffers, sort_by, config, &l_file);
460                 break;
461         }
462         case ACTION_GET_CAPACITY: {
463                 r = for_each_buffer(enabled_buffers, print_buffer_capacity);
464                 break;
465         }
466         case ACTION_CLEAR:
467                 r = for_each_buffer(enabled_buffers, clear_buffer);
468                 break;
469         }
470
471         if (r < 0) {
472                 static const char *action_names[] = {
473                         [ACTION_PRINT] = "printing logs",
474                         [ACTION_GET_CAPACITY] = "getting capacity",
475                         [ACTION_CLEAR] = "clearing buffer",
476                 };
477                 errno = -r;
478                 ERR("Error while %s: %m\n", action_names[action]);
479                 return EXIT_FAILURE;
480         }
481
482         return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
483 }
484 #endif
485
486 /**
487  * @}
488  * @}
489  */