1 /*
2 * Copyright (c) 2018-2021 mcumgr authors
3 * Copyright (c) 2022 Laird Connectivity
4 * Copyright (c) 2022-2023 Nordic Semiconductor ASA
5 *
6 * SPDX-License-Identifier: Apache-2.0
7 */
8
9 #include <zephyr/kernel.h>
10 #include <zephyr/sys/util.h>
11 #include <zephyr/fs/fs.h>
12 #include <zephyr/mgmt/mcumgr/mgmt/mgmt.h>
13 #include <zephyr/mgmt/mcumgr/smp/smp.h>
14 #include <zephyr/mgmt/mcumgr/mgmt/handlers.h>
15 #include <zephyr/mgmt/mcumgr/grp/fs_mgmt/fs_mgmt.h>
16 #include <zephyr/mgmt/mcumgr/grp/fs_mgmt/fs_mgmt_hash_checksum.h>
17 #include <zephyr/logging/log.h>
18 #include <assert.h>
19 #include <limits.h>
20 #include <string.h>
21 #include <stdio.h>
22
23 #include <zcbor_common.h>
24 #include <zcbor_decode.h>
25 #include <zcbor_encode.h>
26
27 #include <mgmt/mcumgr/util/zcbor_bulk.h>
28 #include <mgmt/mcumgr/grp/fs_mgmt/fs_mgmt_config.h>
29
30 #if defined(CONFIG_MCUMGR_GRP_FS_CHECKSUM_IEEE_CRC32)
31 #include <mgmt/mcumgr/grp/fs_mgmt/fs_mgmt_hash_checksum_crc32.h>
32 #endif
33
34 #if defined(CONFIG_MCUMGR_GRP_FS_HASH_SHA256)
35 #include <mgmt/mcumgr/grp/fs_mgmt/fs_mgmt_hash_checksum_sha256.h>
36 #endif
37
38 #if defined(CONFIG_MCUMGR_MGMT_NOTIFICATION_HOOKS)
39 #include <zephyr/mgmt/mcumgr/mgmt/callbacks.h>
40 #endif
41
42 #ifdef CONFIG_MCUMGR_GRP_FS_CHECKSUM_HASH
43 /* Define default hash/checksum */
44 #if defined(CONFIG_MCUMGR_GRP_FS_CHECKSUM_IEEE_CRC32)
45 #define MCUMGR_GRP_FS_CHECKSUM_HASH_DEFAULT "crc32"
46 #elif defined(CONFIG_MCUMGR_GRP_FS_HASH_SHA256)
47 #define MCUMGR_GRP_FS_CHECKSUM_HASH_DEFAULT "sha256"
48 #else
49 #error "Missing mcumgr fs checksum/hash algorithm selection?"
50 #endif
51
52 /* Define largest hach/checksum output size (bytes) */
53 #if defined(CONFIG_MCUMGR_GRP_FS_HASH_SHA256)
54 #define MCUMGR_GRP_FS_CHECKSUM_HASH_LARGEST_OUTPUT_SIZE 32
55 #elif defined(CONFIG_MCUMGR_GRP_FS_CHECKSUM_IEEE_CRC32)
56 #define MCUMGR_GRP_FS_CHECKSUM_HASH_LARGEST_OUTPUT_SIZE 4
57 #endif
58 #endif
59
60 LOG_MODULE_REGISTER(mcumgr_fs_grp, CONFIG_MCUMGR_GRP_FS_LOG_LEVEL);
61
62 #define HASH_CHECKSUM_TYPE_SIZE 8
63
64 #define HASH_CHECKSUM_SUPPORTED_COLUMNS_MAX 4
65
66 #if CONFIG_MCUMGR_GRP_FS_FILE_SEMAPHORE_TAKE_TIME == 0
67 #define FILE_SEMAPHORE_MAX_TAKE_TIME K_NO_WAIT
68 #else
69 #define FILE_SEMAPHORE_MAX_TAKE_TIME K_MSEC(CONFIG_MCUMGR_GRP_FS_FILE_SEMAPHORE_TAKE_TIME)
70 #endif
71
72 #define FILE_SEMAPHORE_MAX_TAKE_TIME_WORK_HANDLER K_MSEC(500)
73 #define FILE_CLOSE_IDLE_TIME K_MSEC(CONFIG_MCUMGR_GRP_FS_FILE_AUTOMATIC_IDLE_CLOSE_TIME)
74
75 enum {
76 STATE_NO_UPLOAD_OR_DOWNLOAD = 0,
77 STATE_UPLOAD,
78 STATE_DOWNLOAD,
79 };
80
81 static struct {
82 /** Whether an upload or download is currently in progress. */
83 uint8_t state;
84
85 /** Expected offset of next upload/download request. */
86 size_t off;
87
88 /**
89 * Total length of file currently being uploaded/downloaded. Note that for file
90 * uploads, it is possible for this to be lost in which case it is not known when
91 * the file can be closed, and the automatic close will need to close the file.
92 */
93 size_t len;
94
95 /** Path of file being accessed. */
96 char path[CONFIG_MCUMGR_GRP_FS_PATH_LEN + 1];
97
98 /** File handle. */
99 struct fs_file_t file;
100
101 /** Semaphore lock. */
102 struct k_sem lock_sem;
103
104 /** Which transport owns the lock on the on-going file transfer. */
105 void *transport;
106
107 /** Delayed workqueue used to close the file after a period of inactivity. */
108 struct k_work_delayable file_close_work;
109 } fs_mgmt_ctxt;
110
111 static const struct mgmt_handler fs_mgmt_handlers[];
112
113 #if defined(CONFIG_MCUMGR_GRP_FS_CHECKSUM_HASH)
114 /* Hash/checksum iterator information passing structure */
115 struct fs_mgmt_hash_checksum_iterator_info {
116 zcbor_state_t *zse;
117 bool ok;
118 };
119 #endif
120
121 /* Clean up open file state */
fs_mgmt_cleanup(void)122 static void fs_mgmt_cleanup(void)
123 {
124 if (fs_mgmt_ctxt.state != STATE_NO_UPLOAD_OR_DOWNLOAD) {
125 fs_mgmt_ctxt.state = STATE_NO_UPLOAD_OR_DOWNLOAD;
126 fs_mgmt_ctxt.off = 0;
127 fs_mgmt_ctxt.len = 0;
128 memset(fs_mgmt_ctxt.path, 0, sizeof(fs_mgmt_ctxt.path));
129 fs_close(&fs_mgmt_ctxt.file);
130 fs_mgmt_ctxt.transport = NULL;
131 }
132 }
133
file_close_work_handler(struct k_work * work)134 static void file_close_work_handler(struct k_work *work)
135 {
136 if (k_sem_take(&fs_mgmt_ctxt.lock_sem, FILE_SEMAPHORE_MAX_TAKE_TIME_WORK_HANDLER)) {
137 /* Re-schedule to retry */
138 k_work_reschedule(&fs_mgmt_ctxt.file_close_work, FILE_CLOSE_IDLE_TIME);
139 return;
140 }
141
142 fs_mgmt_cleanup();
143
144 k_sem_give(&fs_mgmt_ctxt.lock_sem);
145 }
146
fs_mgmt_filelen(const char * path,size_t * out_len)147 static int fs_mgmt_filelen(const char *path, size_t *out_len)
148 {
149 struct fs_dirent dirent;
150 int rc;
151
152 rc = fs_stat(path, &dirent);
153
154 if (rc == -EINVAL) {
155 return FS_MGMT_ERR_FILE_INVALID_NAME;
156 } else if (rc == -ENOENT) {
157 return FS_MGMT_ERR_FILE_NOT_FOUND;
158 } else if (rc != 0) {
159 return FS_MGMT_ERR_UNKNOWN;
160 }
161
162 if (dirent.type != FS_DIR_ENTRY_FILE) {
163 return FS_MGMT_ERR_FILE_IS_DIRECTORY;
164 }
165
166 *out_len = dirent.size;
167
168 return FS_MGMT_ERR_OK;
169 }
170
171 /**
172 * Encodes a file upload response.
173 */
fs_mgmt_file_rsp(zcbor_state_t * zse,int rc,uint64_t off)174 static bool fs_mgmt_file_rsp(zcbor_state_t *zse, int rc, uint64_t off)
175 {
176 bool ok = true;
177
178 if (IS_ENABLED(CONFIG_MCUMGR_SMP_LEGACY_RC_BEHAVIOUR) || rc != 0) {
179 ok = zcbor_tstr_put_lit(zse, "rc") &&
180 zcbor_int32_put(zse, rc);
181 }
182
183 return ok && zcbor_tstr_put_lit(zse, "off") &&
184 zcbor_uint64_put(zse, off);
185 }
186
187 /**
188 * Cleans up open file handle and state when upload is finished.
189 */
fs_mgmt_upload_download_finish_check(void)190 static void fs_mgmt_upload_download_finish_check(void)
191 {
192 if (fs_mgmt_ctxt.len > 0 && fs_mgmt_ctxt.off >= fs_mgmt_ctxt.len) {
193 #if defined(CONFIG_MCUMGR_GRP_FS_FILE_ACCESS_HOOK)
194 char path[CONFIG_MCUMGR_GRP_FS_PATH_LEN + 1];
195 struct fs_mgmt_file_access file_access_data = {
196 .filename = path,
197 };
198 int32_t err_rc;
199 uint16_t err_group;
200
201 strcpy(path, fs_mgmt_ctxt.path);
202
203 switch (fs_mgmt_ctxt.state) {
204 case STATE_DOWNLOAD:
205 file_access_data.access = FS_MGMT_FILE_ACCESS_READ;
206 break;
207
208 case STATE_UPLOAD:
209 file_access_data.access = FS_MGMT_FILE_ACCESS_WRITE;
210 break;
211
212 default:
213 break;
214 }
215 #endif
216
217 /* File upload/download has finished, clean up */
218 k_work_cancel_delayable(&fs_mgmt_ctxt.file_close_work);
219 fs_mgmt_cleanup();
220
221 #if defined(CONFIG_MCUMGR_GRP_FS_FILE_ACCESS_HOOK)
222 /* Warn application that file download/upload is done. */
223 (void)mgmt_callback_notify(MGMT_EVT_OP_FS_MGMT_FILE_ACCESS_DONE, &file_access_data,
224 sizeof(file_access_data), &err_rc, &err_group);
225 #endif
226 } else {
227 k_work_reschedule(&fs_mgmt_ctxt.file_close_work, FILE_CLOSE_IDLE_TIME);
228 }
229 }
230
231 /**
232 * Command handler: fs file (read)
233 */
fs_mgmt_file_download(struct smp_streamer * ctxt)234 static int fs_mgmt_file_download(struct smp_streamer *ctxt)
235 {
236 uint8_t file_data[MCUMGR_GRP_FS_DL_CHUNK_SIZE];
237 char path[CONFIG_MCUMGR_GRP_FS_PATH_LEN + 1];
238 uint64_t off = ULLONG_MAX;
239 ssize_t bytes_read = 0;
240 int rc;
241 zcbor_state_t *zse = ctxt->writer->zs;
242 zcbor_state_t *zsd = ctxt->reader->zs;
243 bool ok;
244 struct zcbor_string name = { 0 };
245 size_t decoded;
246
247 struct zcbor_map_decode_key_val fs_download_decode[] = {
248 ZCBOR_MAP_DECODE_KEY_DECODER("off", zcbor_uint64_decode, &off),
249 ZCBOR_MAP_DECODE_KEY_DECODER("name", zcbor_tstr_decode, &name),
250 };
251
252 #if defined(CONFIG_MCUMGR_GRP_FS_FILE_ACCESS_HOOK)
253 struct fs_mgmt_file_access file_access_data = {
254 .access = FS_MGMT_FILE_ACCESS_READ,
255 .filename = path,
256 };
257
258 enum mgmt_cb_return status;
259 int32_t err_rc;
260 uint16_t err_group;
261 #endif
262
263 ok = zcbor_map_decode_bulk(zsd, fs_download_decode, ARRAY_SIZE(fs_download_decode),
264 &decoded) == 0;
265
266 if (!ok || off == ULLONG_MAX || name.len == 0 || name.len > (sizeof(path) - 1)) {
267 return MGMT_ERR_EINVAL;
268 }
269
270 memcpy(path, name.value, name.len);
271 path[name.len] = '\0';
272
273 if (k_sem_take(&fs_mgmt_ctxt.lock_sem, FILE_SEMAPHORE_MAX_TAKE_TIME)) {
274 return MGMT_ERR_EBUSY;
275 }
276
277 /* Check if this download is already in progress */
278 if (ctxt->smpt != fs_mgmt_ctxt.transport ||
279 fs_mgmt_ctxt.state != STATE_DOWNLOAD ||
280 strcmp(path, fs_mgmt_ctxt.path)) {
281 #if defined(CONFIG_MCUMGR_GRP_FS_FILE_ACCESS_HOOK)
282 /* Send request to application to check if access should be allowed or not */
283 status = mgmt_callback_notify(MGMT_EVT_OP_FS_MGMT_FILE_ACCESS, &file_access_data,
284 sizeof(file_access_data), &err_rc, &err_group);
285
286 if (status != MGMT_CB_OK) {
287 if (status == MGMT_CB_ERROR_RC) {
288 k_sem_give(&fs_mgmt_ctxt.lock_sem);
289 return err_rc;
290 }
291
292 ok = smp_add_cmd_err(zse, err_group, (uint16_t)err_rc);
293 goto end;
294 }
295 #endif
296
297 fs_mgmt_cleanup();
298 }
299
300 /* Open new file */
301 if (fs_mgmt_ctxt.state == STATE_NO_UPLOAD_OR_DOWNLOAD) {
302 rc = fs_mgmt_filelen(path, &fs_mgmt_ctxt.len);
303
304 if (rc != FS_MGMT_ERR_OK) {
305 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS, rc);
306 goto end;
307 }
308
309 fs_mgmt_ctxt.off = 0;
310 fs_file_t_init(&fs_mgmt_ctxt.file);
311 rc = fs_open(&fs_mgmt_ctxt.file, path, FS_O_READ);
312
313 if (rc != 0) {
314 if (rc == -EINVAL) {
315 rc = FS_MGMT_ERR_FILE_INVALID_NAME;
316 } else if (rc == -ENOENT) {
317 rc = FS_MGMT_ERR_FILE_NOT_FOUND;
318 } else {
319 rc = FS_MGMT_ERR_UNKNOWN;
320 }
321
322 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS, rc);
323 goto end;
324 }
325
326 strcpy(fs_mgmt_ctxt.path, path);
327 fs_mgmt_ctxt.state = STATE_DOWNLOAD;
328 fs_mgmt_ctxt.transport = ctxt->smpt;
329 }
330
331 /* Seek to desired offset */
332 if (off != fs_mgmt_ctxt.off) {
333 rc = fs_seek(&fs_mgmt_ctxt.file, off, FS_SEEK_SET);
334
335 if (rc != 0) {
336 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS,
337 FS_MGMT_ERR_FILE_SEEK_FAILED);
338 fs_mgmt_cleanup();
339 goto end;
340 }
341
342 fs_mgmt_ctxt.off = off;
343 }
344
345 /* Only the response to the first download request contains the total file
346 * length.
347 */
348
349 /* Read the requested chunk from the file. */
350 bytes_read = fs_read(&fs_mgmt_ctxt.file, file_data, MCUMGR_GRP_FS_DL_CHUNK_SIZE);
351
352 if (bytes_read < 0) {
353 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS, FS_MGMT_ERR_FILE_READ_FAILED);
354 fs_mgmt_cleanup();
355 goto end;
356 }
357
358 /* Increment offset */
359 fs_mgmt_ctxt.off += bytes_read;
360
361 /* Encode the response. */
362 ok = fs_mgmt_file_rsp(zse, MGMT_ERR_EOK, off) &&
363 zcbor_tstr_put_lit(zse, "data") &&
364 zcbor_bstr_encode_ptr(zse, file_data, bytes_read) &&
365 ((off != 0) ||
366 (zcbor_tstr_put_lit(zse, "len") && zcbor_uint64_put(zse, fs_mgmt_ctxt.len)));
367
368 fs_mgmt_upload_download_finish_check();
369
370 end:
371 rc = (ok ? MGMT_ERR_EOK : MGMT_ERR_EMSGSIZE);
372 k_sem_give(&fs_mgmt_ctxt.lock_sem);
373
374 return rc;
375 }
376
377 /**
378 * Command handler: fs file (write)
379 */
fs_mgmt_file_upload(struct smp_streamer * ctxt)380 static int fs_mgmt_file_upload(struct smp_streamer *ctxt)
381 {
382 char file_name[CONFIG_MCUMGR_GRP_FS_PATH_LEN + 1];
383 unsigned long long len = ULLONG_MAX;
384 unsigned long long off = ULLONG_MAX;
385 bool ok;
386 int rc;
387 zcbor_state_t *zse = ctxt->writer->zs;
388 zcbor_state_t *zsd = ctxt->reader->zs;
389 struct zcbor_string name = { 0 };
390 struct zcbor_string file_data = { 0 };
391 size_t decoded = 0;
392 ssize_t existing_file_size = 0;
393
394 struct zcbor_map_decode_key_val fs_upload_decode[] = {
395 ZCBOR_MAP_DECODE_KEY_DECODER("off", zcbor_uint64_decode, &off),
396 ZCBOR_MAP_DECODE_KEY_DECODER("name", zcbor_tstr_decode, &name),
397 ZCBOR_MAP_DECODE_KEY_DECODER("data", zcbor_bstr_decode, &file_data),
398 ZCBOR_MAP_DECODE_KEY_DECODER("len", zcbor_uint64_decode, &len),
399 };
400
401 #if defined(CONFIG_MCUMGR_GRP_FS_FILE_ACCESS_HOOK)
402 struct fs_mgmt_file_access file_access_data = {
403 .access = FS_MGMT_FILE_ACCESS_WRITE,
404 .filename = file_name,
405 };
406
407 enum mgmt_cb_return status;
408 int32_t err_rc;
409 uint16_t err_group;
410 #endif
411
412 ok = zcbor_map_decode_bulk(zsd, fs_upload_decode, ARRAY_SIZE(fs_upload_decode),
413 &decoded) == 0;
414
415 if (!ok || off == ULLONG_MAX || name.len == 0 || name.len > (sizeof(file_name) - 1) ||
416 (off == 0 && len == ULLONG_MAX)) {
417 return MGMT_ERR_EINVAL;
418 }
419
420 memcpy(file_name, name.value, name.len);
421 file_name[name.len] = '\0';
422
423 if (k_sem_take(&fs_mgmt_ctxt.lock_sem, FILE_SEMAPHORE_MAX_TAKE_TIME)) {
424 return MGMT_ERR_EBUSY;
425 }
426
427 /* Check if this upload is already in progress */
428 if (ctxt->smpt != fs_mgmt_ctxt.transport ||
429 fs_mgmt_ctxt.state != STATE_UPLOAD ||
430 strcmp(file_name, fs_mgmt_ctxt.path)) {
431 #if defined(CONFIG_MCUMGR_GRP_FS_FILE_ACCESS_HOOK)
432 /* Send request to application to check if access should be allowed or not */
433 status = mgmt_callback_notify(MGMT_EVT_OP_FS_MGMT_FILE_ACCESS, &file_access_data,
434 sizeof(file_access_data), &err_rc, &err_group);
435
436 if (status != MGMT_CB_OK) {
437 if (status == MGMT_CB_ERROR_RC) {
438 k_sem_give(&fs_mgmt_ctxt.lock_sem);
439 return err_rc;
440 }
441
442 ok = smp_add_cmd_err(zse, err_group, (uint16_t)err_rc);
443 goto end;
444 }
445 #endif
446
447 fs_mgmt_cleanup();
448 }
449
450 /* Open new file */
451 if (fs_mgmt_ctxt.state == STATE_NO_UPLOAD_OR_DOWNLOAD) {
452 fs_mgmt_ctxt.off = 0;
453 fs_file_t_init(&fs_mgmt_ctxt.file);
454 rc = fs_open(&fs_mgmt_ctxt.file, file_name, FS_O_CREATE | FS_O_WRITE);
455
456 if (rc != 0) {
457 if (rc == -EINVAL) {
458 rc = FS_MGMT_ERR_FILE_INVALID_NAME;
459 } else if (rc == -ENOENT) {
460 rc = FS_MGMT_ERR_MOUNT_POINT_NOT_FOUND;
461 } else if (rc == -EROFS) {
462 rc = FS_MGMT_ERR_READ_ONLY_FILESYSTEM;
463 } else {
464 rc = FS_MGMT_ERR_UNKNOWN;
465 }
466
467 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS, rc);
468 goto end;
469 }
470
471 strcpy(fs_mgmt_ctxt.path, file_name);
472 fs_mgmt_ctxt.state = STATE_UPLOAD;
473 fs_mgmt_ctxt.transport = ctxt->smpt;
474 }
475
476 if (off == 0) {
477 /* Store the uploaded file size from the first packet, this will allow
478 * closing the file when the full upload has finished, however the file
479 * will remain opened if the upload state is lost. It will, however,
480 * still be closed automatically after a timeout.
481 */
482 fs_mgmt_ctxt.len = len;
483 rc = fs_mgmt_filelen(file_name, &existing_file_size);
484
485 if (rc != 0) {
486 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS, rc);
487 fs_mgmt_cleanup();
488 goto end;
489 }
490 } else if (fs_mgmt_ctxt.off == 0) {
491 rc = fs_mgmt_filelen(file_name, &fs_mgmt_ctxt.off);
492
493 if (rc != 0) {
494 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS, rc);
495 fs_mgmt_cleanup();
496 goto end;
497 }
498 }
499
500 /* Verify that the data offset matches the expected offset (i.e. current size of file) */
501 if (off > 0 && off != fs_mgmt_ctxt.off) {
502 /* Offset mismatch, send file length, client needs to handle this */
503 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS, FS_MGMT_ERR_FILE_OFFSET_NOT_VALID);
504 ok = zcbor_tstr_put_lit(zse, "len") &&
505 zcbor_uint64_put(zse, fs_mgmt_ctxt.off);
506
507 /* Because the client would most likely decide to abort and transfer and start
508 * again, clean everything up and release the file handle so it can be used
509 * elsewhere (if needed).
510 */
511 fs_mgmt_cleanup();
512 goto end;
513 }
514
515 if (file_data.len > 0) {
516 /* Write the data chunk to the file. */
517 if (off == 0 && existing_file_size != 0) {
518 /* Offset is 0 and existing file exists with data, attempt to truncate
519 * the file size to 0
520 */
521 rc = fs_seek(&fs_mgmt_ctxt.file, 0, FS_SEEK_SET);
522
523 if (rc != 0) {
524 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS,
525 FS_MGMT_ERR_FILE_SEEK_FAILED);
526 fs_mgmt_cleanup();
527 goto end;
528 }
529
530 rc = fs_truncate(&fs_mgmt_ctxt.file, 0);
531
532 if (rc == -ENOTSUP) {
533 /* Truncation not supported by filesystem, therefore close file,
534 * delete it then re-open it
535 */
536 fs_close(&fs_mgmt_ctxt.file);
537
538 rc = fs_unlink(file_name);
539 if (rc < 0 && rc != -ENOENT) {
540 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS,
541 FS_MGMT_ERR_FILE_DELETE_FAILED);
542 fs_mgmt_cleanup();
543 goto end;
544 }
545
546 rc = fs_open(&fs_mgmt_ctxt.file, file_name, FS_O_CREATE |
547 FS_O_WRITE);
548 }
549
550 if (rc < 0) {
551 /* Failed to truncate file */
552 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS,
553 FS_MGMT_ERR_FILE_TRUNCATE_FAILED);
554 fs_mgmt_cleanup();
555 goto end;
556 }
557 } else if (fs_tell(&fs_mgmt_ctxt.file) != off) {
558 /* The offset has been validated to be file size previously, seek to
559 * the end of the file to write the new data.
560 */
561 rc = fs_seek(&fs_mgmt_ctxt.file, 0, FS_SEEK_END);
562
563 if (rc < 0) {
564 /* Failed to seek in file */
565 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS,
566 FS_MGMT_ERR_FILE_SEEK_FAILED);
567 fs_mgmt_cleanup();
568 goto end;
569 }
570 }
571
572 rc = fs_write(&fs_mgmt_ctxt.file, file_data.value, file_data.len);
573
574 if (rc < 0) {
575 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS,
576 FS_MGMT_ERR_FILE_WRITE_FAILED);
577 fs_mgmt_cleanup();
578 goto end;
579 }
580
581 fs_mgmt_ctxt.off += file_data.len;
582 }
583
584 /* Send the response. */
585 ok = fs_mgmt_file_rsp(zse, MGMT_ERR_EOK, fs_mgmt_ctxt.off);
586 fs_mgmt_upload_download_finish_check();
587
588 end:
589 rc = (ok ? MGMT_ERR_EOK : MGMT_ERR_EMSGSIZE);
590 k_sem_give(&fs_mgmt_ctxt.lock_sem);
591
592 return rc;
593 }
594
595 #if defined(CONFIG_MCUMGR_GRP_FS_FILE_STATUS)
596 /**
597 * Command handler: fs stat (read)
598 */
fs_mgmt_file_status(struct smp_streamer * ctxt)599 static int fs_mgmt_file_status(struct smp_streamer *ctxt)
600 {
601 char path[CONFIG_MCUMGR_GRP_FS_PATH_LEN + 1];
602 size_t file_len;
603 int rc;
604 zcbor_state_t *zse = ctxt->writer->zs;
605 zcbor_state_t *zsd = ctxt->reader->zs;
606 bool ok;
607 struct zcbor_string name = { 0 };
608 size_t decoded;
609
610 struct zcbor_map_decode_key_val fs_status_decode[] = {
611 ZCBOR_MAP_DECODE_KEY_DECODER("name", zcbor_tstr_decode, &name),
612 };
613
614 #if defined(CONFIG_MCUMGR_GRP_FS_FILE_ACCESS_HOOK)
615 struct fs_mgmt_file_access file_access_data = {
616 .access = FS_MGMT_FILE_ACCESS_STATUS,
617 .filename = path,
618 };
619
620 enum mgmt_cb_return status;
621 int32_t err_rc;
622 uint16_t err_group;
623 #endif
624
625 ok = zcbor_map_decode_bulk(zsd, fs_status_decode,
626 ARRAY_SIZE(fs_status_decode), &decoded) == 0;
627
628 if (!ok || name.len == 0 || name.len > (sizeof(path) - 1)) {
629 return MGMT_ERR_EINVAL;
630 }
631
632 /* Copy path and ensure it is null-teminated */
633 memcpy(path, name.value, name.len);
634 path[name.len] = '\0';
635
636 #if defined(CONFIG_MCUMGR_GRP_FS_FILE_ACCESS_HOOK)
637 /* Send request to application to check if access should be allowed or not */
638 status = mgmt_callback_notify(MGMT_EVT_OP_FS_MGMT_FILE_ACCESS, &file_access_data,
639 sizeof(file_access_data), &err_rc, &err_group);
640
641 if (status != MGMT_CB_OK) {
642 if (status == MGMT_CB_ERROR_RC) {
643 return err_rc;
644 }
645
646 ok = smp_add_cmd_err(zse, err_group, (uint16_t)err_rc);
647 return ok ? MGMT_ERR_EOK : MGMT_ERR_EMSGSIZE;
648 }
649 #endif
650
651 /* Retrieve file size */
652 rc = fs_mgmt_filelen(path, &file_len);
653
654 if (rc != 0) {
655 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS, rc);
656 goto end;
657 }
658
659 /* Encode the response. */
660 if (IS_ENABLED(CONFIG_MCUMGR_SMP_LEGACY_RC_BEHAVIOUR)) {
661 ok = zcbor_tstr_put_lit(zse, "rc") &&
662 zcbor_int32_put(zse, rc);
663 }
664
665 ok = ok && zcbor_tstr_put_lit(zse, "len") &&
666 zcbor_uint64_put(zse, file_len);
667
668 end:
669 if (!ok) {
670 return MGMT_ERR_EMSGSIZE;
671 }
672
673 return MGMT_ERR_EOK;
674 }
675 #endif
676
677 #if defined(CONFIG_MCUMGR_GRP_FS_CHECKSUM_HASH)
678 /**
679 * Command handler: fs hash/checksum (read)
680 */
fs_mgmt_file_hash_checksum(struct smp_streamer * ctxt)681 static int fs_mgmt_file_hash_checksum(struct smp_streamer *ctxt)
682 {
683 char path[CONFIG_MCUMGR_GRP_FS_PATH_LEN + 1];
684 char type_arr[HASH_CHECKSUM_TYPE_SIZE + 1] = MCUMGR_GRP_FS_CHECKSUM_HASH_DEFAULT;
685 char output[MCUMGR_GRP_FS_CHECKSUM_HASH_LARGEST_OUTPUT_SIZE];
686 uint64_t len = ULLONG_MAX;
687 uint64_t off = 0;
688 size_t file_len;
689 int rc;
690 zcbor_state_t *zse = ctxt->writer->zs;
691 zcbor_state_t *zsd = ctxt->reader->zs;
692 bool ok;
693 struct zcbor_string type = { 0 };
694 struct zcbor_string name = { 0 };
695 size_t decoded;
696 struct fs_file_t file;
697 const struct fs_mgmt_hash_checksum_group *group = NULL;
698
699 struct zcbor_map_decode_key_val fs_hash_checksum_decode[] = {
700 ZCBOR_MAP_DECODE_KEY_DECODER("type", zcbor_tstr_decode, &type),
701 ZCBOR_MAP_DECODE_KEY_DECODER("name", zcbor_tstr_decode, &name),
702 ZCBOR_MAP_DECODE_KEY_DECODER("off", zcbor_uint64_decode, &off),
703 ZCBOR_MAP_DECODE_KEY_DECODER("len", zcbor_uint64_decode, &len),
704 };
705
706 #if defined(CONFIG_MCUMGR_GRP_FS_FILE_ACCESS_HOOK)
707 struct fs_mgmt_file_access file_access_data = {
708 .access = FS_MGMT_FILE_ACCESS_HASH_CHECKSUM,
709 .filename = path,
710 };
711
712 enum mgmt_cb_return status;
713 int32_t err_rc;
714 uint16_t err_group;
715 #endif
716
717 ok = zcbor_map_decode_bulk(zsd, fs_hash_checksum_decode,
718 ARRAY_SIZE(fs_hash_checksum_decode), &decoded) == 0;
719
720 if (!ok || name.len == 0 || name.len > (sizeof(path) - 1) ||
721 type.len > (sizeof(type_arr) - 1) || len == 0) {
722 return MGMT_ERR_EINVAL;
723 }
724
725 /* Copy strings and ensure they are null-teminated */
726 memcpy(path, name.value, name.len);
727 path[name.len] = '\0';
728
729 if (type.len != 0) {
730 memcpy(type_arr, type.value, type.len);
731 type_arr[type.len] = '\0';
732 }
733
734 /* Search for supported hash/checksum */
735 group = fs_mgmt_hash_checksum_find_handler(type_arr);
736
737 if (group == NULL) {
738 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS,
739 FS_MGMT_ERR_CHECKSUM_HASH_NOT_FOUND);
740 goto end;
741 }
742
743 #if defined(CONFIG_MCUMGR_GRP_FS_FILE_ACCESS_HOOK)
744 /* Send request to application to check if access should be allowed or not */
745 status = mgmt_callback_notify(MGMT_EVT_OP_FS_MGMT_FILE_ACCESS, &file_access_data,
746 sizeof(file_access_data), &err_rc, &err_group);
747
748 if (status != MGMT_CB_OK) {
749 if (status == MGMT_CB_ERROR_RC) {
750 return err_rc;
751 }
752
753 ok = smp_add_cmd_err(zse, err_group, (uint16_t)err_rc);
754 return ok ? MGMT_ERR_EOK : MGMT_ERR_EMSGSIZE;
755 }
756 #endif
757
758 /* Check provided offset is valid for target file */
759 rc = fs_mgmt_filelen(path, &file_len);
760
761 if (rc != 0) {
762 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS, rc);
763 goto end;
764 }
765
766 if (file_len <= off) {
767 /* Requested offset is larger than target file size or file length is 0, which
768 * means no hash/checksum can be performed
769 */
770 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS,
771 (file_len == 0 ? FS_MGMT_ERR_FILE_EMPTY :
772 FS_MGMT_ERR_FILE_OFFSET_LARGER_THAN_FILE));
773 goto end;
774 }
775
776 /* Open file for reading and pass to hash/checksum generation function */
777 fs_file_t_init(&file);
778 rc = fs_open(&file, path, FS_O_READ);
779
780 if (rc != 0) {
781 if (rc == -EINVAL) {
782 rc = FS_MGMT_ERR_FILE_INVALID_NAME;
783 } else if (rc == -ENOENT) {
784 rc = FS_MGMT_ERR_FILE_NOT_FOUND;
785 } else {
786 rc = FS_MGMT_ERR_UNKNOWN;
787 }
788
789 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS, rc);
790 goto end;
791 }
792
793 /* Seek to file's desired offset, if parameter was provided */
794 if (off != 0) {
795 rc = fs_seek(&file, off, FS_SEEK_SET);
796
797 if (rc != 0) {
798 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS,
799 FS_MGMT_ERR_FILE_SEEK_FAILED);
800 fs_close(&file);
801 goto end;
802 }
803 }
804
805 /* Calculate hash/checksum using function */
806 file_len = 0;
807 rc = group->function(&file, output, &file_len, len);
808
809 fs_close(&file);
810
811 /* Encode the response */
812 if (rc != 0) {
813 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS, rc);
814 goto end;
815 }
816
817 ok &= zcbor_tstr_put_lit(zse, "type") &&
818 zcbor_tstr_put_term(zse, type_arr, sizeof(type_arr));
819
820 if (off != 0) {
821 ok &= zcbor_tstr_put_lit(zse, "off") &&
822 zcbor_uint64_put(zse, off);
823 }
824
825 ok &= zcbor_tstr_put_lit(zse, "len") &&
826 zcbor_uint64_put(zse, file_len) &&
827 zcbor_tstr_put_lit(zse, "output");
828
829 if (group->byte_string == true) {
830 /* Output is a byte string */
831 ok &= zcbor_bstr_encode_ptr(zse, output, group->output_size);
832 } else {
833 /* Output is a number */
834 uint64_t tmp_val = 0;
835
836 if (group->output_size == sizeof(uint8_t)) {
837 tmp_val = (uint64_t)(*(uint8_t *)output);
838 #if MCUMGR_GRP_FS_CHECKSUM_HASH_LARGEST_OUTPUT_SIZE > 1
839 } else if (group->output_size == sizeof(uint16_t)) {
840 tmp_val = (uint64_t)(*(uint16_t *)output);
841 #if MCUMGR_GRP_FS_CHECKSUM_HASH_LARGEST_OUTPUT_SIZE > 2
842 } else if (group->output_size == sizeof(uint32_t)) {
843 tmp_val = (uint64_t)(*(uint32_t *)output);
844 #if MCUMGR_GRP_FS_CHECKSUM_HASH_LARGEST_OUTPUT_SIZE > 4
845 } else if (group->output_size == sizeof(uint64_t)) {
846 tmp_val = (*(uint64_t *)output);
847 #endif
848 #endif
849 #endif
850 } else {
851 LOG_ERR("Unable to handle numerical checksum size %u",
852 group->output_size);
853
854 return MGMT_ERR_EUNKNOWN;
855 }
856
857 ok &= zcbor_uint64_put(zse, tmp_val);
858 }
859
860 end:
861 if (!ok) {
862 return MGMT_ERR_EMSGSIZE;
863 }
864
865 return MGMT_ERR_EOK;
866 }
867
868 #if defined(CONFIG_MCUMGR_GRP_FS_CHECKSUM_HASH_SUPPORTED_CMD)
869 /* Callback for supported hash/checksum types to encode details on one type into CBOR map */
fs_mgmt_supported_hash_checksum_callback(const struct fs_mgmt_hash_checksum_group * group,void * user_data)870 static void fs_mgmt_supported_hash_checksum_callback(
871 const struct fs_mgmt_hash_checksum_group *group,
872 void *user_data)
873 {
874 struct fs_mgmt_hash_checksum_iterator_info *ctx =
875 (struct fs_mgmt_hash_checksum_iterator_info *)user_data;
876
877 if (!ctx->ok) {
878 return;
879 }
880
881 ctx->ok = zcbor_tstr_encode_ptr(ctx->zse, group->group_name, strlen(group->group_name)) &&
882 zcbor_map_start_encode(ctx->zse, HASH_CHECKSUM_SUPPORTED_COLUMNS_MAX) &&
883 zcbor_tstr_put_lit(ctx->zse, "format") &&
884 zcbor_uint32_put(ctx->zse, (uint32_t)group->byte_string) &&
885 zcbor_tstr_put_lit(ctx->zse, "size") &&
886 zcbor_uint32_put(ctx->zse, (uint32_t)group->output_size) &&
887 zcbor_map_end_encode(ctx->zse, HASH_CHECKSUM_SUPPORTED_COLUMNS_MAX);
888 }
889
890 /**
891 * Command handler: fs supported hash/checksum (read)
892 */
893 static int
fs_mgmt_supported_hash_checksum(struct smp_streamer * ctxt)894 fs_mgmt_supported_hash_checksum(struct smp_streamer *ctxt)
895 {
896 zcbor_state_t *zse = ctxt->writer->zs;
897 struct fs_mgmt_hash_checksum_iterator_info itr_ctx = {
898 .zse = zse,
899 };
900
901 itr_ctx.ok = zcbor_tstr_put_lit(zse, "types") &&
902 zcbor_map_start_encode(zse, CONFIG_MCUMGR_GRP_FS_CHECKSUM_HASH_SUPPORTED_MAX_TYPES);
903
904 if (!itr_ctx.ok) {
905 return MGMT_ERR_EMSGSIZE;
906 }
907
908 fs_mgmt_hash_checksum_find_handlers(fs_mgmt_supported_hash_checksum_callback, &itr_ctx);
909
910 if (!itr_ctx.ok ||
911 !zcbor_map_end_encode(zse, CONFIG_MCUMGR_GRP_FS_CHECKSUM_HASH_SUPPORTED_MAX_TYPES)) {
912 return MGMT_ERR_EMSGSIZE;
913 }
914
915 return MGMT_ERR_EOK;
916 }
917 #endif
918 #endif
919
920 /**
921 * Command handler: fs opened file (write)
922 */
fs_mgmt_close_opened_file(struct smp_streamer * ctxt)923 static int fs_mgmt_close_opened_file(struct smp_streamer *ctxt)
924 {
925 if (k_sem_take(&fs_mgmt_ctxt.lock_sem, FILE_SEMAPHORE_MAX_TAKE_TIME)) {
926 return MGMT_ERR_EBUSY;
927 }
928
929 fs_mgmt_cleanup();
930
931 k_sem_give(&fs_mgmt_ctxt.lock_sem);
932
933 return MGMT_ERR_EOK;
934 }
935
936 #ifdef CONFIG_MCUMGR_SMP_SUPPORT_ORIGINAL_PROTOCOL
937 /*
938 * @brief Translate FS mgmt group error code into MCUmgr error code
939 *
940 * @param ret #fs_mgmt_err_code_t error code
941 *
942 * @return #mcumgr_err_t error code
943 */
fs_mgmt_translate_error_code(uint16_t err)944 static int fs_mgmt_translate_error_code(uint16_t err)
945 {
946 int rc;
947
948 switch (err) {
949 case FS_MGMT_ERR_FILE_INVALID_NAME:
950 case FS_MGMT_ERR_CHECKSUM_HASH_NOT_FOUND:
951 rc = MGMT_ERR_EINVAL;
952 break;
953
954 case FS_MGMT_ERR_FILE_NOT_FOUND:
955 case FS_MGMT_ERR_MOUNT_POINT_NOT_FOUND:
956 rc = MGMT_ERR_ENOENT;
957 break;
958
959 case FS_MGMT_ERR_UNKNOWN:
960 case FS_MGMT_ERR_FILE_IS_DIRECTORY:
961 case FS_MGMT_ERR_FILE_OPEN_FAILED:
962 case FS_MGMT_ERR_FILE_SEEK_FAILED:
963 case FS_MGMT_ERR_FILE_READ_FAILED:
964 case FS_MGMT_ERR_FILE_TRUNCATE_FAILED:
965 case FS_MGMT_ERR_FILE_DELETE_FAILED:
966 case FS_MGMT_ERR_FILE_WRITE_FAILED:
967 case FS_MGMT_ERR_FILE_OFFSET_NOT_VALID:
968 case FS_MGMT_ERR_FILE_OFFSET_LARGER_THAN_FILE:
969 case FS_MGMT_ERR_READ_ONLY_FILESYSTEM:
970 default:
971 rc = MGMT_ERR_EUNKNOWN;
972 }
973
974 return rc;
975 }
976 #endif
977
978 static const struct mgmt_handler fs_mgmt_handlers[] = {
979 [FS_MGMT_ID_FILE] = {
980 .mh_read = fs_mgmt_file_download,
981 .mh_write = fs_mgmt_file_upload,
982 },
983 #if defined(CONFIG_MCUMGR_GRP_FS_FILE_STATUS)
984 [FS_MGMT_ID_STAT] = {
985 .mh_read = fs_mgmt_file_status,
986 .mh_write = NULL,
987 },
988 #endif
989 #if defined(CONFIG_MCUMGR_GRP_FS_CHECKSUM_HASH)
990 [FS_MGMT_ID_HASH_CHECKSUM] = {
991 .mh_read = fs_mgmt_file_hash_checksum,
992 .mh_write = NULL,
993 },
994 #if defined(CONFIG_MCUMGR_GRP_FS_CHECKSUM_HASH_SUPPORTED_CMD)
995 [FS_MGMT_ID_SUPPORTED_HASH_CHECKSUM] = {
996 .mh_read = fs_mgmt_supported_hash_checksum,
997 .mh_write = NULL,
998 },
999 #endif
1000 #endif
1001 [FS_MGMT_ID_OPENED_FILE] = {
1002 .mh_read = NULL,
1003 .mh_write = fs_mgmt_close_opened_file,
1004 },
1005 };
1006
1007 #define FS_MGMT_HANDLER_CNT ARRAY_SIZE(fs_mgmt_handlers)
1008
1009 static struct mgmt_group fs_mgmt_group = {
1010 .mg_handlers = fs_mgmt_handlers,
1011 .mg_handlers_count = FS_MGMT_HANDLER_CNT,
1012 .mg_group_id = MGMT_GROUP_ID_FS,
1013 #ifdef CONFIG_MCUMGR_SMP_SUPPORT_ORIGINAL_PROTOCOL
1014 .mg_translate_error = fs_mgmt_translate_error_code,
1015 #endif
1016 #ifdef CONFIG_MCUMGR_GRP_ENUM_DETAILS_NAME
1017 .mg_group_name = "fs mgmt",
1018 #endif
1019 };
1020
fs_mgmt_register_group(void)1021 static void fs_mgmt_register_group(void)
1022 {
1023 /* Initialise state variables */
1024 fs_mgmt_ctxt.state = STATE_NO_UPLOAD_OR_DOWNLOAD;
1025 k_sem_init(&fs_mgmt_ctxt.lock_sem, 1, 1);
1026 k_work_init_delayable(&fs_mgmt_ctxt.file_close_work, file_close_work_handler);
1027
1028 mgmt_register_group(&fs_mgmt_group);
1029
1030 #if defined(CONFIG_MCUMGR_GRP_FS_CHECKSUM_HASH)
1031 /* Register any supported hash or checksum functions */
1032 #if defined(CONFIG_MCUMGR_GRP_FS_CHECKSUM_IEEE_CRC32)
1033 fs_mgmt_hash_checksum_register_crc32();
1034 #endif
1035
1036 #if defined(CONFIG_MCUMGR_GRP_FS_HASH_SHA256)
1037 fs_mgmt_hash_checksum_register_sha256();
1038 #endif
1039 #endif
1040 }
1041
1042 MCUMGR_HANDLER_DEFINE(fs_mgmt, fs_mgmt_register_group);
1043