1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. 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,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20 #include <string.h>
21 #include <stdio.h>
22
23 #include "mgmt/mgmt.h"
24 #include "cborattr/cborattr.h"
25 #include "tinycbor/cbor_cnt_writer.h"
26 #include "log_mgmt/log_mgmt.h"
27 #include "log_mgmt/log_mgmt_impl.h"
28 #include "log_mgmt/log_mgmt_config.h"
29 #include "log/log.h"
30
31 /* Log mgmt encoder context used for multiple calls of the
32 * entry encode function since the function gets called twice,
33 * once for counter encoder and the second time for the actual
34 * encoding
35 */
36 struct log_mgmt_enc_ctxt {
37 CborEncoder mapenc;
38 CborEncoder msgenc;
39 };
40
41 /** Context used during walks. */
42 struct log_walk_ctxt {
43 /* last encoded index */
44 uint32_t last_enc_index;
45 /* The number of bytes encoded to the response so far. */
46 size_t rsp_len;
47 /* The encoder to use to write the current log entry. */
48 struct CborEncoder *enc;
49 /* Counter per encoder to understand if we are encoding the first chunk */
50 uint32_t counter;
51 /* Log management encode context containing map and msg encoder */
52 struct log_mgmt_enc_ctxt lmec;
53 };
54
55 static mgmt_handler_fn log_mgmt_show;
56 static mgmt_handler_fn log_mgmt_clear;
57 static mgmt_handler_fn log_mgmt_module_list;
58 static mgmt_handler_fn log_mgmt_level_list;
59 static mgmt_handler_fn log_mgmt_logs_list;
60
61 static struct mgmt_handler log_mgmt_handlers[] = {
62 [LOG_MGMT_ID_SHOW] = { log_mgmt_show, NULL },
63 [LOG_MGMT_ID_CLEAR] = { NULL, log_mgmt_clear },
64 [LOG_MGMT_ID_MODULE_LIST] = { log_mgmt_module_list, NULL },
65 [LOG_MGMT_ID_LEVEL_LIST] = { log_mgmt_level_list, NULL },
66 [LOG_MGMT_ID_LOGS_LIST] = { log_mgmt_logs_list, NULL },
67 };
68
69 #define LOG_MGMT_HANDLER_CNT \
70 sizeof log_mgmt_handlers / sizeof log_mgmt_handlers[0]
71
72 static struct mgmt_group log_mgmt_group = {
73 .mg_handlers = log_mgmt_handlers,
74 .mg_handlers_count = LOG_MGMT_HANDLER_CNT,
75 .mg_group_id = MGMT_GROUP_ID_LOG,
76 };
77
78 static int
log_mgmt_encode_entry(CborEncoder * enc,const struct log_mgmt_entry * entry,size_t * out_len,struct log_mgmt_enc_ctxt * lmec)79 log_mgmt_encode_entry(CborEncoder *enc, const struct log_mgmt_entry *entry,
80 size_t *out_len, struct log_mgmt_enc_ctxt *lmec)
81 {
82 CborError err = CborNoError;
83 uint16_t chunklen;
84 uint16_t len;
85 int off;
86 uint16_t bytes_encoded;
87
88 len = cbor_encode_bytes_written(enc);
89
90 bytes_encoded = 0;
91 /* If offset is 0, we encode the keys for maps and other fields which are
92 * necessary per entry since only the keys need to be encoded only in the
93 * for the first offset
94 */
95 if (entry->offset == 0) {
96 err |= cbor_encoder_create_map(enc, &lmec->mapenc, CborIndefiniteLength);
97
98 switch (entry->type) {
99 case LOG_MGMT_ETYPE_CBOR:
100 err |= cbor_encode_text_stringz(&lmec->mapenc, "type");
101 err |= cbor_encode_text_stringz(&lmec->mapenc, "cbor");
102 break;
103 case LOG_MGMT_ETYPE_BINARY:
104 err |= cbor_encode_text_stringz(&lmec->mapenc, "type");
105 err |= cbor_encode_text_stringz(&lmec->mapenc, "bin");
106 break;
107 case LOG_MGMT_ETYPE_STRING:
108 err |= cbor_encode_text_stringz(&lmec->mapenc, "type");
109 err |= cbor_encode_text_stringz(&lmec->mapenc, "str");
110 break;
111 default:
112 return LOG_MGMT_ERR_ECORRUPT;
113 }
114 err |= cbor_encode_text_stringz(&lmec->mapenc, "ts");
115 err |= cbor_encode_int(&lmec->mapenc, entry->ts);
116 err |= cbor_encode_text_stringz(&lmec->mapenc, "level");
117 err |= cbor_encode_uint(&lmec->mapenc, entry->level);
118 err |= cbor_encode_text_stringz(&lmec->mapenc, "index");
119 err |= cbor_encode_uint(&lmec->mapenc, entry->index);
120 err |= cbor_encode_text_stringz(&lmec->mapenc, "module");
121 err |= cbor_encode_uint(&lmec->mapenc, entry->module);
122 if (entry->flags & LOG_MGMT_FLAGS_IMG_HASH) {
123 err |= cbor_encode_text_stringz(&lmec->mapenc, "imghash");
124 err |= cbor_encode_byte_string(&lmec->mapenc, entry->imghash,
125 LOG_MGMT_IMG_HASHLEN);
126 }
127
128 err |= cbor_encode_text_stringz(&lmec->mapenc, "msg");
129
130 /*
131 * Write entry data as byte string. Since this may not fit into single
132 * chunk of data we will write as indefinite-length byte string which is
133 * basically a indefinite-length container with definite-length strings
134 * inside.
135 */
136 err |= cbor_encoder_create_indef_byte_string(&lmec->mapenc, &lmec->msgenc);
137 if (out_len != NULL) {
138 for (off = 0; off < entry->len; off += entry->chunklen) {
139 chunklen = entry->chunklen;
140 if (entry->chunklen > entry->len - off) {
141 chunklen = entry->len - off;
142 }
143 err |= cbor_encode_byte_string(&lmec->msgenc, entry->data, chunklen);
144 bytes_encoded += chunklen;
145 }
146 } else {
147 err |= cbor_encode_byte_string(&lmec->msgenc, entry->data, entry->chunklen);
148 bytes_encoded = entry->chunklen;
149 }
150 } else {
151 /*
152 * The else case is executed for non-first chunks of data to be encoded
153 *
154 * Write entry data as byte string. Since this may not fit into single
155 * chunk of data we will write as indefinite-length byte string which is
156 * basically a indefinite-length container with definite-length strings
157 * inside.
158 */
159 err |= cbor_encode_byte_string(&lmec->msgenc, entry->data, entry->chunklen);
160 bytes_encoded = entry->chunklen;
161 }
162
163 /*
164 * Containers need to get closed when encoding is done, the only way to know at
165 * this point in the code that encoding is done is using the number of bytes
166 * that got encoded and comparing it to the length of the entry
167 */
168 if (entry->offset + bytes_encoded >= entry->len) {
169 err |= cbor_encoder_close_container(&lmec->mapenc, &lmec->msgenc);
170 err |= cbor_encoder_close_container(enc, &lmec->mapenc);
171 }
172
173 if (out_len) {
174 *out_len = cbor_encode_bytes_written(enc) - len;
175 }
176
177 if (err != 0) {
178 return LOG_MGMT_ERR_ENOMEM;
179 }
180
181 return LOG_MGMT_ERR_EOK;
182 }
183
184 static int
log_mgmt_cb_encode(struct log_mgmt_entry * entry,void * arg)185 log_mgmt_cb_encode(struct log_mgmt_entry *entry, void *arg)
186 {
187 struct CborCntWriter cnt_writer;
188 struct log_mgmt_enc_ctxt *lmec;
189 struct log_walk_ctxt *ctxt;
190 CborEncoder cnt_encoder;
191 size_t entry_len;
192 int rc;
193
194 ctxt = arg;
195 lmec = &ctxt->lmec;
196
197 if (entry->offset == 0) {
198 /*
199 * First, determine if this entry would fit using
200 * a counter encoder
201 */
202
203 cbor_cnt_writer_init(&cnt_writer);
204 #ifdef __ZEPHYR__
205 cbor_encoder_cust_writer_init(&cnt_encoder, &cnt_writer.enc, 0);
206 #else
207 cbor_encoder_init(&cnt_encoder, &cnt_writer.enc, 0);
208 #endif
209 rc = log_mgmt_encode_entry(&cnt_encoder, entry, &entry_len, lmec);
210 if (rc != 0) {
211 return rc;
212 }
213
214 /*
215 * Check if the response is too long. If more than one entry is in the
216 * response we will not add the current one and will return ENOMEM. If this
217 * is just a single entry we add the generic too long message text.
218 */
219 /* `+ 1` to account for the CBOR array terminator. */
220 if (ctxt->rsp_len + entry_len + 1 > LOG_MGMT_MAX_RSP_LEN) {
221 /*
222 * Is this just a single entry? If so, encode the generic error
223 * message in the "msg" field of the response
224 */
225 if (ctxt->counter == 0) {
226 entry->type = LOG_ETYPE_STRING;
227 snprintf((char *)entry->data, LOG_MGMT_MAX_RSP_LEN,
228 "error: entry too large (%zu bytes)", entry_len);
229 }
230
231 /* We want a negative error code here */
232 return -1 * LOG_MGMT_ERR_EUNKNOWN;
233 }
234 ctxt->rsp_len += entry_len;
235 }
236
237 /*** The entry fits. Now encode it. */
238 rc = log_mgmt_encode_entry(ctxt->enc, entry, NULL, lmec);
239 if (rc != 0) {
240 return rc;
241 }
242
243 ctxt->counter++;
244 ctxt->last_enc_index = entry->index;
245
246 return 0;
247 }
248
249 static int
log_encode_entries(const struct log_mgmt_log * log,CborEncoder * enc,int64_t timestamp,uint32_t index)250 log_encode_entries(const struct log_mgmt_log *log, CborEncoder *enc,
251 int64_t timestamp, uint32_t index)
252 {
253 struct CborCntWriter cnt_writer;
254 struct log_mgmt_filter filter;
255 struct log_walk_ctxt ctxt;
256 CborEncoder cnt_encoder;
257 CborEncoder entries;
258 CborError err;
259 int rsp_len;
260 int rc;
261
262 err = 0;
263 rsp_len = 0;
264 /* this code counts how long the message would be if we encoded
265 * this outer structure using cbor. */
266 cbor_cnt_writer_init(&cnt_writer);
267 cbor_encoder_init(&cnt_encoder, &cnt_writer.enc, 0);
268 err |= cbor_encode_text_stringz(&cnt_encoder, "entries");
269 err |= cbor_encoder_create_array(&cnt_encoder, &entries,
270 CborIndefiniteLength);
271 err |= cbor_encoder_close_container(&cnt_encoder, &entries);
272 rsp_len = cbor_encode_bytes_written(enc) +
273 cbor_encode_bytes_written(&cnt_encoder);
274 if (rsp_len > LOG_MGMT_MAX_RSP_LEN) {
275 rc = LOG_MGMT_ERR_EUNKNOWN;
276 goto err;
277 }
278
279 err |= cbor_encode_text_stringz(enc, "entries");
280 err |= cbor_encoder_create_array(enc, &entries, CborIndefiniteLength);
281
282 filter = (struct log_mgmt_filter) {
283 .min_timestamp = timestamp,
284 .min_index = index,
285 };
286 ctxt = (struct log_walk_ctxt) {
287 .enc = &entries,
288 .rsp_len = cbor_encode_bytes_written(enc),
289 };
290
291 rc = log_mgmt_impl_foreach_entry(log->name, &filter,
292 log_mgmt_cb_encode, &ctxt);
293 if (rc < 0) {
294 /*
295 * If we receive negative error code from the walk function,
296 * make sure it gets converted to a positive error code
297 */
298 rc = -1 * rc;
299 }
300
301 err |= cbor_encoder_close_container(enc, &entries);
302
303 if (err != 0) {
304 return LOG_MGMT_ERR_ENOMEM;
305 }
306
307 err:
308 #if LOG_MGMT_READ_WATERMARK_UPDATE
309 if (!rc || rc == LOG_MGMT_ERR_EUNKNOWN) {
310 log_mgmt_impl_set_watermark(log, ctxt.last_enc_index);
311 }
312 #endif
313 return rc;
314 }
315
316 static int
log_encode(const struct log_mgmt_log * log,CborEncoder * ctxt,int64_t timestamp,uint32_t index)317 log_encode(const struct log_mgmt_log *log, CborEncoder *ctxt,
318 int64_t timestamp, uint32_t index)
319 {
320 CborEncoder logs;
321 CborError err;
322 int rc;
323
324 err = 0;
325 err |= cbor_encoder_create_map(ctxt, &logs, CborIndefiniteLength);
326 err |= cbor_encode_text_stringz(&logs, "name");
327 err |= cbor_encode_text_stringz(&logs, log->name);
328 err |= cbor_encode_text_stringz(&logs, "type");
329 err |= cbor_encode_uint(&logs, log->type);
330
331 rc = log_encode_entries(log, &logs, timestamp, index);
332 if (rc != 0) {
333 cbor_encoder_close_container(ctxt, &logs);
334 return rc;
335 }
336
337 err |= cbor_encoder_close_container(ctxt, &logs);
338
339 if (err != 0) {
340 return LOG_MGMT_ERR_ENOMEM;
341 }
342
343 return 0;
344 }
345
346 /**
347 * Command handler: log show
348 */
349 static int
log_mgmt_show(struct mgmt_ctxt * ctxt)350 log_mgmt_show(struct mgmt_ctxt *ctxt)
351 {
352 char name[LOG_MGMT_NAME_LEN];
353 struct log_mgmt_log log;
354 CborEncoder logs;
355 CborError err;
356 uint64_t index;
357 uint32_t next_idx;
358 int64_t timestamp;
359 int name_len;
360 int log_idx;
361 int rc;
362
363 const struct cbor_attr_t attr[] = {
364 {
365 .attribute = "log_name",
366 .type = CborAttrTextStringType,
367 .addr.string = name,
368 .len = sizeof(name),
369 },
370 {
371 .attribute = "ts",
372 .type = CborAttrIntegerType,
373 .addr.integer = ×tamp,
374 },
375 {
376 .attribute = "index",
377 .type = CborAttrUnsignedIntegerType,
378 .addr.uinteger = &index,
379 },
380 {
381 .attribute = NULL,
382 },
383 };
384
385 name[0] = '\0';
386 rc = cbor_read_object(&ctxt->it, attr);
387 if (rc != 0) {
388 return LOG_MGMT_ERR_EINVAL;
389 }
390 name_len = strlen(name);
391
392 err = 0;
393 #if LOG_MGMT_GLOBAL_IDX
394 /* Determine the index that the next log entry would use. */
395 rc = log_mgmt_impl_get_next_idx(&next_idx);
396 if (rc != 0) {
397 return LOG_MGMT_ERR_EUNKNOWN;
398 }
399 #else
400 /* This field is deprecated. Set it to 0 until it gets removed. */
401 next_idx = 0;
402 #endif
403
404 err |= cbor_encode_text_stringz(&ctxt->encoder, "next_index");
405 err |= cbor_encode_uint(&ctxt->encoder, next_idx);
406
407 err |= cbor_encode_text_stringz(&ctxt->encoder, "logs");
408 err |= cbor_encoder_create_array(&ctxt->encoder, &logs,
409 CborIndefiniteLength);
410
411 /* Iterate list of logs, encoding each that matches the client request. */
412 for (log_idx = 0; ; log_idx++) {
413 rc = log_mgmt_impl_get_log(log_idx, &log);
414 if (rc == LOG_MGMT_ERR_ENOENT) {
415 /* Log list fully iterated. */
416 if (name_len != 0) {
417 /* Client specified log name, but the log wasn't found. */
418 cbor_encoder_close_container(&ctxt->encoder, &logs);
419 return LOG_MGMT_ERR_ENOENT;
420 } else {
421 break;
422 }
423 } else if (rc != 0) {
424 goto err;
425 }
426
427 /* Stream logs cannot be read. */
428 if (log.type != LOG_MGMT_TYPE_STREAM) {
429 if (name_len == 0 || strcmp(name, log.name) == 0) {
430 rc = log_encode(&log, &logs, timestamp, index);
431 if (rc) {
432 goto err;
433 }
434
435 /* If the client specified this log, he isn't interested in the
436 * remaining ones.
437 */
438 if (name_len > 0) {
439 break;
440 }
441 }
442 }
443 }
444
445 err:
446 err |= cbor_encoder_close_container(&ctxt->encoder, &logs);
447 err |= cbor_encode_text_stringz(&ctxt->encoder, "rc");
448 err |= cbor_encode_int(&ctxt->encoder, rc);
449
450 if (err != 0) {
451 return LOG_MGMT_ERR_ENOMEM;
452 }
453
454 return 0;
455 }
456
457 /**
458 * Command handler: log module_list
459 */
460 static int
log_mgmt_module_list(struct mgmt_ctxt * ctxt)461 log_mgmt_module_list(struct mgmt_ctxt *ctxt)
462 {
463 const char *module_name;
464 CborEncoder modules;
465 CborError err;
466 int module;
467 int rc;
468
469 err = 0;
470 err |= cbor_encode_text_stringz(&ctxt->encoder, "rc");
471 err |= cbor_encode_int(&ctxt->encoder, LOG_MGMT_ERR_EOK);
472 err |= cbor_encode_text_stringz(&ctxt->encoder, "module_map");
473 err |= cbor_encoder_create_map(&ctxt->encoder, &modules,
474 CborIndefiniteLength);
475
476 for (module = 0; ; module++) {
477 rc = log_mgmt_impl_get_module(module, &module_name);
478 if (rc == LOG_MGMT_ERR_ENOENT) {
479 break;
480 }
481 if (rc != 0) {
482 cbor_encoder_close_container(&ctxt->encoder, &modules);
483 return rc;
484 }
485
486 if (module_name != NULL) {
487 err |= cbor_encode_text_stringz(&modules, module_name);
488 err |= cbor_encode_uint(&modules, module);
489 }
490 }
491
492 err |= cbor_encoder_close_container(&ctxt->encoder, &modules);
493
494 if (err != 0) {
495 return LOG_MGMT_ERR_ENOMEM;
496 }
497
498 return 0;
499 }
500
501 /**
502 * Command handler: log list
503 */
504 static int
log_mgmt_logs_list(struct mgmt_ctxt * ctxt)505 log_mgmt_logs_list(struct mgmt_ctxt *ctxt)
506 {
507 struct log_mgmt_log log;
508 CborEncoder log_list;
509 CborError err;
510 int log_idx;
511 int rc;
512
513 err = 0;
514 err |= cbor_encode_text_stringz(&ctxt->encoder, "rc");
515 err |= cbor_encode_int(&ctxt->encoder, LOG_MGMT_ERR_EOK);
516 err |= cbor_encode_text_stringz(&ctxt->encoder, "log_list");
517 err |= cbor_encoder_create_array(&ctxt->encoder, &log_list,
518 CborIndefiniteLength);
519
520 for (log_idx = 0; ; log_idx++) {
521 rc = log_mgmt_impl_get_log(log_idx, &log);
522 if (rc == LOG_MGMT_ERR_ENOENT) {
523 break;
524 }
525 if (rc != 0) {
526 cbor_encoder_close_container(&ctxt->encoder, &log_list);
527 return rc;
528 }
529
530 if (log.type != LOG_MGMT_TYPE_STREAM) {
531 err |= cbor_encode_text_stringz(&log_list, log.name);
532 }
533 }
534
535 err |= cbor_encoder_close_container(&ctxt->encoder, &log_list);
536
537 if (err != 0) {
538 return LOG_MGMT_ERR_ENOMEM;
539 }
540
541 return 0;
542 }
543
544 /**
545 * Command handler: log level_list
546 */
547 static int
log_mgmt_level_list(struct mgmt_ctxt * ctxt)548 log_mgmt_level_list(struct mgmt_ctxt *ctxt)
549 {
550 const char *level_name;
551 CborEncoder level_map;
552 CborError err;
553 int level;
554 int rc;
555
556 err = 0;
557 err |= cbor_encode_text_stringz(&ctxt->encoder, "rc");
558 err |= cbor_encode_int(&ctxt->encoder, LOG_MGMT_ERR_EOK);
559 err |= cbor_encode_text_stringz(&ctxt->encoder, "level_map");
560 err |= cbor_encoder_create_map(&ctxt->encoder, &level_map,
561 CborIndefiniteLength);
562
563 for (level = 0; ; level++) {
564 rc = log_mgmt_impl_get_level(level, &level_name);
565 if (rc == LOG_MGMT_ERR_ENOENT) {
566 break;
567 }
568 if (rc != 0) {
569 cbor_encoder_close_container(&ctxt->encoder, &level_map);
570 return rc;
571 }
572
573 if (level_name != NULL) {
574 err |= cbor_encode_text_stringz(&level_map, level_name);
575 err |= cbor_encode_uint(&level_map, level);
576 }
577 }
578
579 err |= cbor_encoder_close_container(&ctxt->encoder, &level_map);
580
581 if (err != 0) {
582 return LOG_MGMT_ERR_ENOMEM;
583 }
584
585 return 0;
586 }
587
588 /**
589 * Command handler: log clear
590 */
591 static int
log_mgmt_clear(struct mgmt_ctxt * ctxt)592 log_mgmt_clear(struct mgmt_ctxt *ctxt)
593 {
594 struct log_mgmt_log log;
595 char name[LOG_MGMT_NAME_LEN] = {0};
596 int name_len;
597 int log_idx;
598 int rc;
599
600 const struct cbor_attr_t attr[] = {
601 {
602 .attribute = "log_name",
603 .type = CborAttrTextStringType,
604 .addr.string = name,
605 .len = sizeof(name)
606 },
607 {
608 .attribute = NULL
609 },
610 };
611
612 name[0] = '\0';
613 rc = cbor_read_object(&ctxt->it, attr);
614 if (rc != 0) {
615 return LOG_MGMT_ERR_EINVAL;
616 }
617 name_len = strlen(name);
618
619 for (log_idx = 0; ; log_idx++) {
620 rc = log_mgmt_impl_get_log(log_idx, &log);
621 if (rc == LOG_MGMT_ERR_ENOENT) {
622 return 0;
623 }
624 if (rc != 0) {
625 return rc;
626 }
627
628 if (log.type != LOG_MGMT_TYPE_STREAM) {
629 if (name_len == 0 || strcmp(log.name, name) == 0) {
630 rc = log_mgmt_impl_clear(log.name);
631 if (rc != 0) {
632 return rc;
633 }
634
635 if (name_len != 0) {
636 return 0;
637 }
638 }
639 }
640 }
641
642 if (name_len != 0) {
643 return LOG_MGMT_ERR_ENOENT;
644 }
645
646 return 0;
647 }
648
649 void
log_mgmt_register_group(void)650 log_mgmt_register_group(void)
651 {
652 mgmt_register_group(&log_mgmt_group);
653 }
654