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 = &timestamp,
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