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 /* File upload/download has finished, clean up */
194 k_work_cancel_delayable(&fs_mgmt_ctxt.file_close_work);
195 fs_mgmt_cleanup();
196 } else {
197 k_work_reschedule(&fs_mgmt_ctxt.file_close_work, FILE_CLOSE_IDLE_TIME);
198 }
199 }
200
201 /**
202 * Command handler: fs file (read)
203 */
fs_mgmt_file_download(struct smp_streamer * ctxt)204 static int fs_mgmt_file_download(struct smp_streamer *ctxt)
205 {
206 uint8_t file_data[MCUMGR_GRP_FS_DL_CHUNK_SIZE];
207 char path[CONFIG_MCUMGR_GRP_FS_PATH_LEN + 1];
208 uint64_t off = ULLONG_MAX;
209 ssize_t bytes_read = 0;
210 int rc;
211 zcbor_state_t *zse = ctxt->writer->zs;
212 zcbor_state_t *zsd = ctxt->reader->zs;
213 bool ok;
214 struct zcbor_string name = { 0 };
215 size_t decoded;
216
217 struct zcbor_map_decode_key_val fs_download_decode[] = {
218 ZCBOR_MAP_DECODE_KEY_DECODER("off", zcbor_uint64_decode, &off),
219 ZCBOR_MAP_DECODE_KEY_DECODER("name", zcbor_tstr_decode, &name),
220 };
221
222 #if defined(CONFIG_MCUMGR_GRP_FS_FILE_ACCESS_HOOK)
223 struct fs_mgmt_file_access file_access_data = {
224 .access = FS_MGMT_FILE_ACCESS_READ,
225 .filename = path,
226 };
227
228 enum mgmt_cb_return status;
229 int32_t err_rc;
230 uint16_t err_group;
231 #endif
232
233 ok = zcbor_map_decode_bulk(zsd, fs_download_decode, ARRAY_SIZE(fs_download_decode),
234 &decoded) == 0;
235
236 if (!ok || off == ULLONG_MAX || name.len == 0 || name.len > (sizeof(path) - 1)) {
237 return MGMT_ERR_EINVAL;
238 }
239
240 memcpy(path, name.value, name.len);
241 path[name.len] = '\0';
242
243 if (k_sem_take(&fs_mgmt_ctxt.lock_sem, FILE_SEMAPHORE_MAX_TAKE_TIME)) {
244 return MGMT_ERR_EBUSY;
245 }
246
247 /* Check if this download is already in progress */
248 if (ctxt->smpt != fs_mgmt_ctxt.transport ||
249 fs_mgmt_ctxt.state != STATE_DOWNLOAD ||
250 strcmp(path, fs_mgmt_ctxt.path)) {
251 #if defined(CONFIG_MCUMGR_GRP_FS_FILE_ACCESS_HOOK)
252 /* Send request to application to check if access should be allowed or not */
253 status = mgmt_callback_notify(MGMT_EVT_OP_FS_MGMT_FILE_ACCESS, &file_access_data,
254 sizeof(file_access_data), &err_rc, &err_group);
255
256 if (status != MGMT_CB_OK) {
257 if (status == MGMT_CB_ERROR_RC) {
258 k_sem_give(&fs_mgmt_ctxt.lock_sem);
259 return err_rc;
260 }
261
262 ok = smp_add_cmd_err(zse, err_group, (uint16_t)err_rc);
263 goto end;
264 }
265 #endif
266
267 fs_mgmt_cleanup();
268 }
269
270 /* Open new file */
271 if (fs_mgmt_ctxt.state == STATE_NO_UPLOAD_OR_DOWNLOAD) {
272 rc = fs_mgmt_filelen(path, &fs_mgmt_ctxt.len);
273
274 if (rc != FS_MGMT_ERR_OK) {
275 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS, rc);
276 goto end;
277 }
278
279 fs_mgmt_ctxt.off = 0;
280 fs_file_t_init(&fs_mgmt_ctxt.file);
281 rc = fs_open(&fs_mgmt_ctxt.file, path, FS_O_READ);
282
283 if (rc != 0) {
284 if (rc == -EINVAL) {
285 rc = FS_MGMT_ERR_FILE_INVALID_NAME;
286 } else if (rc == -ENOENT) {
287 rc = FS_MGMT_ERR_FILE_NOT_FOUND;
288 } else {
289 rc = FS_MGMT_ERR_UNKNOWN;
290 }
291
292 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS, rc);
293 goto end;
294 }
295
296 strcpy(fs_mgmt_ctxt.path, path);
297 fs_mgmt_ctxt.state = STATE_DOWNLOAD;
298 fs_mgmt_ctxt.transport = ctxt->smpt;
299 }
300
301 /* Seek to desired offset */
302 if (off != fs_mgmt_ctxt.off) {
303 rc = fs_seek(&fs_mgmt_ctxt.file, off, FS_SEEK_SET);
304
305 if (rc != 0) {
306 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS,
307 FS_MGMT_ERR_FILE_SEEK_FAILED);
308 fs_mgmt_cleanup();
309 goto end;
310 }
311
312 fs_mgmt_ctxt.off = off;
313 }
314
315 /* Only the response to the first download request contains the total file
316 * length.
317 */
318
319 /* Read the requested chunk from the file. */
320 bytes_read = fs_read(&fs_mgmt_ctxt.file, file_data, MCUMGR_GRP_FS_DL_CHUNK_SIZE);
321
322 if (bytes_read < 0) {
323 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS, FS_MGMT_ERR_FILE_READ_FAILED);
324 fs_mgmt_cleanup();
325 goto end;
326 }
327
328 /* Increment offset */
329 fs_mgmt_ctxt.off += bytes_read;
330
331 /* Encode the response. */
332 ok = fs_mgmt_file_rsp(zse, MGMT_ERR_EOK, off) &&
333 zcbor_tstr_put_lit(zse, "data") &&
334 zcbor_bstr_encode_ptr(zse, file_data, bytes_read) &&
335 ((off != 0) ||
336 (zcbor_tstr_put_lit(zse, "len") && zcbor_uint64_put(zse, fs_mgmt_ctxt.len)));
337
338 fs_mgmt_upload_download_finish_check();
339
340 end:
341 rc = (ok ? MGMT_ERR_EOK : MGMT_ERR_EMSGSIZE);
342 k_sem_give(&fs_mgmt_ctxt.lock_sem);
343
344 return rc;
345 }
346
347 /**
348 * Command handler: fs file (write)
349 */
fs_mgmt_file_upload(struct smp_streamer * ctxt)350 static int fs_mgmt_file_upload(struct smp_streamer *ctxt)
351 {
352 char file_name[CONFIG_MCUMGR_GRP_FS_PATH_LEN + 1];
353 unsigned long long len = ULLONG_MAX;
354 unsigned long long off = ULLONG_MAX;
355 bool ok;
356 int rc;
357 zcbor_state_t *zse = ctxt->writer->zs;
358 zcbor_state_t *zsd = ctxt->reader->zs;
359 struct zcbor_string name = { 0 };
360 struct zcbor_string file_data = { 0 };
361 size_t decoded = 0;
362 ssize_t existing_file_size = 0;
363
364 struct zcbor_map_decode_key_val fs_upload_decode[] = {
365 ZCBOR_MAP_DECODE_KEY_DECODER("off", zcbor_uint64_decode, &off),
366 ZCBOR_MAP_DECODE_KEY_DECODER("name", zcbor_tstr_decode, &name),
367 ZCBOR_MAP_DECODE_KEY_DECODER("data", zcbor_bstr_decode, &file_data),
368 ZCBOR_MAP_DECODE_KEY_DECODER("len", zcbor_uint64_decode, &len),
369 };
370
371 #if defined(CONFIG_MCUMGR_GRP_FS_FILE_ACCESS_HOOK)
372 struct fs_mgmt_file_access file_access_data = {
373 .access = FS_MGMT_FILE_ACCESS_WRITE,
374 .filename = file_name,
375 };
376
377 enum mgmt_cb_return status;
378 int32_t err_rc;
379 uint16_t err_group;
380 #endif
381
382 ok = zcbor_map_decode_bulk(zsd, fs_upload_decode, ARRAY_SIZE(fs_upload_decode),
383 &decoded) == 0;
384
385 if (!ok || off == ULLONG_MAX || name.len == 0 || name.len > (sizeof(file_name) - 1) ||
386 (off == 0 && len == ULLONG_MAX)) {
387 return MGMT_ERR_EINVAL;
388 }
389
390 memcpy(file_name, name.value, name.len);
391 file_name[name.len] = '\0';
392
393 if (k_sem_take(&fs_mgmt_ctxt.lock_sem, FILE_SEMAPHORE_MAX_TAKE_TIME)) {
394 return MGMT_ERR_EBUSY;
395 }
396
397 /* Check if this upload is already in progress */
398 if (ctxt->smpt != fs_mgmt_ctxt.transport ||
399 fs_mgmt_ctxt.state != STATE_UPLOAD ||
400 strcmp(file_name, fs_mgmt_ctxt.path)) {
401 #if defined(CONFIG_MCUMGR_GRP_FS_FILE_ACCESS_HOOK)
402 /* Send request to application to check if access should be allowed or not */
403 status = mgmt_callback_notify(MGMT_EVT_OP_FS_MGMT_FILE_ACCESS, &file_access_data,
404 sizeof(file_access_data), &err_rc, &err_group);
405
406 if (status != MGMT_CB_OK) {
407 if (status == MGMT_CB_ERROR_RC) {
408 k_sem_give(&fs_mgmt_ctxt.lock_sem);
409 return err_rc;
410 }
411
412 ok = smp_add_cmd_err(zse, err_group, (uint16_t)err_rc);
413 goto end;
414 }
415 #endif
416
417 fs_mgmt_cleanup();
418 }
419
420 /* Open new file */
421 if (fs_mgmt_ctxt.state == STATE_NO_UPLOAD_OR_DOWNLOAD) {
422 fs_mgmt_ctxt.off = 0;
423 fs_file_t_init(&fs_mgmt_ctxt.file);
424 rc = fs_open(&fs_mgmt_ctxt.file, file_name, FS_O_CREATE | FS_O_WRITE);
425
426 if (rc != 0) {
427 if (rc == -EINVAL) {
428 rc = FS_MGMT_ERR_FILE_INVALID_NAME;
429 } else if (rc == -ENOENT) {
430 rc = FS_MGMT_ERR_MOUNT_POINT_NOT_FOUND;
431 } else if (rc == -EROFS) {
432 rc = FS_MGMT_ERR_READ_ONLY_FILESYSTEM;
433 } else {
434 rc = FS_MGMT_ERR_UNKNOWN;
435 }
436
437 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS, rc);
438 goto end;
439 }
440
441 strcpy(fs_mgmt_ctxt.path, file_name);
442 fs_mgmt_ctxt.state = STATE_UPLOAD;
443 fs_mgmt_ctxt.transport = ctxt->smpt;
444 }
445
446 if (off == 0) {
447 /* Store the uploaded file size from the first packet, this will allow
448 * closing the file when the full upload has finished, however the file
449 * will remain opened if the upload state is lost. It will, however,
450 * still be closed automatically after a timeout.
451 */
452 fs_mgmt_ctxt.len = len;
453 rc = fs_mgmt_filelen(file_name, &existing_file_size);
454
455 if (rc != 0) {
456 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS, rc);
457 fs_mgmt_cleanup();
458 goto end;
459 }
460 } else if (fs_mgmt_ctxt.off == 0) {
461 rc = fs_mgmt_filelen(file_name, &fs_mgmt_ctxt.off);
462
463 if (rc != 0) {
464 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS, rc);
465 fs_mgmt_cleanup();
466 goto end;
467 }
468 }
469
470 /* Verify that the data offset matches the expected offset (i.e. current size of file) */
471 if (off > 0 && off != fs_mgmt_ctxt.off) {
472 /* Offset mismatch, send file length, client needs to handle this */
473 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS, FS_MGMT_ERR_FILE_OFFSET_NOT_VALID);
474 ok = zcbor_tstr_put_lit(zse, "len") &&
475 zcbor_uint64_put(zse, fs_mgmt_ctxt.off);
476
477 /* Because the client would most likely decide to abort and transfer and start
478 * again, clean everything up and release the file handle so it can be used
479 * elsewhere (if needed).
480 */
481 fs_mgmt_cleanup();
482 goto end;
483 }
484
485 if (file_data.len > 0) {
486 /* Write the data chunk to the file. */
487 if (off == 0 && existing_file_size != 0) {
488 /* Offset is 0 and existing file exists with data, attempt to truncate
489 * the file size to 0
490 */
491 rc = fs_seek(&fs_mgmt_ctxt.file, 0, FS_SEEK_SET);
492
493 if (rc != 0) {
494 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS,
495 FS_MGMT_ERR_FILE_SEEK_FAILED);
496 fs_mgmt_cleanup();
497 goto end;
498 }
499
500 rc = fs_truncate(&fs_mgmt_ctxt.file, 0);
501
502 if (rc == -ENOTSUP) {
503 /* Truncation not supported by filesystem, therefore close file,
504 * delete it then re-open it
505 */
506 fs_close(&fs_mgmt_ctxt.file);
507
508 rc = fs_unlink(file_name);
509 if (rc < 0 && rc != -ENOENT) {
510 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS,
511 FS_MGMT_ERR_FILE_DELETE_FAILED);
512 fs_mgmt_cleanup();
513 goto end;
514 }
515
516 rc = fs_open(&fs_mgmt_ctxt.file, file_name, FS_O_CREATE |
517 FS_O_WRITE);
518 }
519
520 if (rc < 0) {
521 /* Failed to truncate file */
522 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS,
523 FS_MGMT_ERR_FILE_TRUNCATE_FAILED);
524 fs_mgmt_cleanup();
525 goto end;
526 }
527 } else if (fs_tell(&fs_mgmt_ctxt.file) != off) {
528 /* The offset has been validated to be file size previously, seek to
529 * the end of the file to write the new data.
530 */
531 rc = fs_seek(&fs_mgmt_ctxt.file, 0, FS_SEEK_END);
532
533 if (rc < 0) {
534 /* Failed to seek in file */
535 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS,
536 FS_MGMT_ERR_FILE_SEEK_FAILED);
537 fs_mgmt_cleanup();
538 goto end;
539 }
540 }
541
542 rc = fs_write(&fs_mgmt_ctxt.file, file_data.value, file_data.len);
543
544 if (rc < 0) {
545 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS,
546 FS_MGMT_ERR_FILE_WRITE_FAILED);
547 fs_mgmt_cleanup();
548 goto end;
549 }
550
551 fs_mgmt_ctxt.off += file_data.len;
552 }
553
554 /* Send the response. */
555 ok = fs_mgmt_file_rsp(zse, MGMT_ERR_EOK, fs_mgmt_ctxt.off);
556 fs_mgmt_upload_download_finish_check();
557
558 end:
559 rc = (ok ? MGMT_ERR_EOK : MGMT_ERR_EMSGSIZE);
560 k_sem_give(&fs_mgmt_ctxt.lock_sem);
561
562 return rc;
563 }
564
565 #if defined(CONFIG_MCUMGR_GRP_FS_FILE_STATUS)
566 /**
567 * Command handler: fs stat (read)
568 */
fs_mgmt_file_status(struct smp_streamer * ctxt)569 static int fs_mgmt_file_status(struct smp_streamer *ctxt)
570 {
571 char path[CONFIG_MCUMGR_GRP_FS_PATH_LEN + 1];
572 size_t file_len;
573 int rc;
574 zcbor_state_t *zse = ctxt->writer->zs;
575 zcbor_state_t *zsd = ctxt->reader->zs;
576 bool ok;
577 struct zcbor_string name = { 0 };
578 size_t decoded;
579
580 struct zcbor_map_decode_key_val fs_status_decode[] = {
581 ZCBOR_MAP_DECODE_KEY_DECODER("name", zcbor_tstr_decode, &name),
582 };
583
584 #if defined(CONFIG_MCUMGR_GRP_FS_FILE_ACCESS_HOOK)
585 struct fs_mgmt_file_access file_access_data = {
586 .access = FS_MGMT_FILE_ACCESS_STATUS,
587 .filename = path,
588 };
589
590 enum mgmt_cb_return status;
591 int32_t err_rc;
592 uint16_t err_group;
593 #endif
594
595 ok = zcbor_map_decode_bulk(zsd, fs_status_decode,
596 ARRAY_SIZE(fs_status_decode), &decoded) == 0;
597
598 if (!ok || name.len == 0 || name.len > (sizeof(path) - 1)) {
599 return MGMT_ERR_EINVAL;
600 }
601
602 /* Copy path and ensure it is null-teminated */
603 memcpy(path, name.value, name.len);
604 path[name.len] = '\0';
605
606 #if defined(CONFIG_MCUMGR_GRP_FS_FILE_ACCESS_HOOK)
607 /* Send request to application to check if access should be allowed or not */
608 status = mgmt_callback_notify(MGMT_EVT_OP_FS_MGMT_FILE_ACCESS, &file_access_data,
609 sizeof(file_access_data), &err_rc, &err_group);
610
611 if (status != MGMT_CB_OK) {
612 if (status == MGMT_CB_ERROR_RC) {
613 return err_rc;
614 }
615
616 ok = smp_add_cmd_err(zse, err_group, (uint16_t)err_rc);
617 return ok ? MGMT_ERR_EOK : MGMT_ERR_EMSGSIZE;
618 }
619 #endif
620
621 /* Retrieve file size */
622 rc = fs_mgmt_filelen(path, &file_len);
623
624 if (rc != 0) {
625 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS, rc);
626 goto end;
627 }
628
629 /* Encode the response. */
630 if (IS_ENABLED(CONFIG_MCUMGR_SMP_LEGACY_RC_BEHAVIOUR)) {
631 ok = zcbor_tstr_put_lit(zse, "rc") &&
632 zcbor_int32_put(zse, rc);
633 }
634
635 ok = ok && zcbor_tstr_put_lit(zse, "len") &&
636 zcbor_uint64_put(zse, file_len);
637
638 end:
639 if (!ok) {
640 return MGMT_ERR_EMSGSIZE;
641 }
642
643 return MGMT_ERR_EOK;
644 }
645 #endif
646
647 #if defined(CONFIG_MCUMGR_GRP_FS_CHECKSUM_HASH)
648 /**
649 * Command handler: fs hash/checksum (read)
650 */
fs_mgmt_file_hash_checksum(struct smp_streamer * ctxt)651 static int fs_mgmt_file_hash_checksum(struct smp_streamer *ctxt)
652 {
653 char path[CONFIG_MCUMGR_GRP_FS_PATH_LEN + 1];
654 char type_arr[HASH_CHECKSUM_TYPE_SIZE + 1] = MCUMGR_GRP_FS_CHECKSUM_HASH_DEFAULT;
655 char output[MCUMGR_GRP_FS_CHECKSUM_HASH_LARGEST_OUTPUT_SIZE];
656 uint64_t len = ULLONG_MAX;
657 uint64_t off = 0;
658 size_t file_len;
659 int rc;
660 zcbor_state_t *zse = ctxt->writer->zs;
661 zcbor_state_t *zsd = ctxt->reader->zs;
662 bool ok;
663 struct zcbor_string type = { 0 };
664 struct zcbor_string name = { 0 };
665 size_t decoded;
666 struct fs_file_t file;
667 const struct fs_mgmt_hash_checksum_group *group = NULL;
668
669 struct zcbor_map_decode_key_val fs_hash_checksum_decode[] = {
670 ZCBOR_MAP_DECODE_KEY_DECODER("type", zcbor_tstr_decode, &type),
671 ZCBOR_MAP_DECODE_KEY_DECODER("name", zcbor_tstr_decode, &name),
672 ZCBOR_MAP_DECODE_KEY_DECODER("off", zcbor_uint64_decode, &off),
673 ZCBOR_MAP_DECODE_KEY_DECODER("len", zcbor_uint64_decode, &len),
674 };
675
676 #if defined(CONFIG_MCUMGR_GRP_FS_FILE_ACCESS_HOOK)
677 struct fs_mgmt_file_access file_access_data = {
678 .access = FS_MGMT_FILE_ACCESS_HASH_CHECKSUM,
679 .filename = path,
680 };
681
682 enum mgmt_cb_return status;
683 int32_t err_rc;
684 uint16_t err_group;
685 #endif
686
687 ok = zcbor_map_decode_bulk(zsd, fs_hash_checksum_decode,
688 ARRAY_SIZE(fs_hash_checksum_decode), &decoded) == 0;
689
690 if (!ok || name.len == 0 || name.len > (sizeof(path) - 1) ||
691 type.len > (sizeof(type_arr) - 1) || len == 0) {
692 return MGMT_ERR_EINVAL;
693 }
694
695 /* Copy strings and ensure they are null-teminated */
696 memcpy(path, name.value, name.len);
697 path[name.len] = '\0';
698
699 if (type.len != 0) {
700 memcpy(type_arr, type.value, type.len);
701 type_arr[type.len] = '\0';
702 }
703
704 /* Search for supported hash/checksum */
705 group = fs_mgmt_hash_checksum_find_handler(type_arr);
706
707 if (group == NULL) {
708 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS,
709 FS_MGMT_ERR_CHECKSUM_HASH_NOT_FOUND);
710 goto end;
711 }
712
713 #if defined(CONFIG_MCUMGR_GRP_FS_FILE_ACCESS_HOOK)
714 /* Send request to application to check if access should be allowed or not */
715 status = mgmt_callback_notify(MGMT_EVT_OP_FS_MGMT_FILE_ACCESS, &file_access_data,
716 sizeof(file_access_data), &err_rc, &err_group);
717
718 if (status != MGMT_CB_OK) {
719 if (status == MGMT_CB_ERROR_RC) {
720 return err_rc;
721 }
722
723 ok = smp_add_cmd_err(zse, err_group, (uint16_t)err_rc);
724 return ok ? MGMT_ERR_EOK : MGMT_ERR_EMSGSIZE;
725 }
726 #endif
727
728 /* Check provided offset is valid for target file */
729 rc = fs_mgmt_filelen(path, &file_len);
730
731 if (rc != 0) {
732 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS, rc);
733 goto end;
734 }
735
736 if (file_len <= off) {
737 /* Requested offset is larger than target file size or file length is 0, which
738 * means no hash/checksum can be performed
739 */
740 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS,
741 (file_len == 0 ? FS_MGMT_ERR_FILE_EMPTY :
742 FS_MGMT_ERR_FILE_OFFSET_LARGER_THAN_FILE));
743 goto end;
744 }
745
746 /* Open file for reading and pass to hash/checksum generation function */
747 fs_file_t_init(&file);
748 rc = fs_open(&file, path, FS_O_READ);
749
750 if (rc != 0) {
751 if (rc == -EINVAL) {
752 rc = FS_MGMT_ERR_FILE_INVALID_NAME;
753 } else if (rc == -ENOENT) {
754 rc = FS_MGMT_ERR_FILE_NOT_FOUND;
755 } else {
756 rc = FS_MGMT_ERR_UNKNOWN;
757 }
758
759 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS, rc);
760 goto end;
761 }
762
763 /* Seek to file's desired offset, if parameter was provided */
764 if (off != 0) {
765 rc = fs_seek(&file, off, FS_SEEK_SET);
766
767 if (rc != 0) {
768 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS,
769 FS_MGMT_ERR_FILE_SEEK_FAILED);
770 fs_close(&file);
771 goto end;
772 }
773 }
774
775 /* Calculate hash/checksum using function */
776 file_len = 0;
777 rc = group->function(&file, output, &file_len, len);
778
779 fs_close(&file);
780
781 /* Encode the response */
782 if (rc != 0) {
783 ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_FS, rc);
784 goto end;
785 }
786
787 ok &= zcbor_tstr_put_lit(zse, "type") &&
788 zcbor_tstr_put_term(zse, type_arr, sizeof(type_arr));
789
790 if (off != 0) {
791 ok &= zcbor_tstr_put_lit(zse, "off") &&
792 zcbor_uint64_put(zse, off);
793 }
794
795 ok &= zcbor_tstr_put_lit(zse, "len") &&
796 zcbor_uint64_put(zse, file_len) &&
797 zcbor_tstr_put_lit(zse, "output");
798
799 if (group->byte_string == true) {
800 /* Output is a byte string */
801 ok &= zcbor_bstr_encode_ptr(zse, output, group->output_size);
802 } else {
803 /* Output is a number */
804 uint64_t tmp_val = 0;
805
806 if (group->output_size == sizeof(uint8_t)) {
807 tmp_val = (uint64_t)(*(uint8_t *)output);
808 #if MCUMGR_GRP_FS_CHECKSUM_HASH_LARGEST_OUTPUT_SIZE > 1
809 } else if (group->output_size == sizeof(uint16_t)) {
810 tmp_val = (uint64_t)(*(uint16_t *)output);
811 #if MCUMGR_GRP_FS_CHECKSUM_HASH_LARGEST_OUTPUT_SIZE > 2
812 } else if (group->output_size == sizeof(uint32_t)) {
813 tmp_val = (uint64_t)(*(uint32_t *)output);
814 #if MCUMGR_GRP_FS_CHECKSUM_HASH_LARGEST_OUTPUT_SIZE > 4
815 } else if (group->output_size == sizeof(uint64_t)) {
816 tmp_val = (*(uint64_t *)output);
817 #endif
818 #endif
819 #endif
820 } else {
821 LOG_ERR("Unable to handle numerical checksum size %u",
822 group->output_size);
823
824 return MGMT_ERR_EUNKNOWN;
825 }
826
827 ok &= zcbor_uint64_put(zse, tmp_val);
828 }
829
830 end:
831 if (!ok) {
832 return MGMT_ERR_EMSGSIZE;
833 }
834
835 return MGMT_ERR_EOK;
836 }
837
838 #if defined(CONFIG_MCUMGR_GRP_FS_CHECKSUM_HASH_SUPPORTED_CMD)
839 /* 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)840 static void fs_mgmt_supported_hash_checksum_callback(
841 const struct fs_mgmt_hash_checksum_group *group,
842 void *user_data)
843 {
844 struct fs_mgmt_hash_checksum_iterator_info *ctx =
845 (struct fs_mgmt_hash_checksum_iterator_info *)user_data;
846
847 if (!ctx->ok) {
848 return;
849 }
850
851 ctx->ok = zcbor_tstr_encode_ptr(ctx->zse, group->group_name, strlen(group->group_name)) &&
852 zcbor_map_start_encode(ctx->zse, HASH_CHECKSUM_SUPPORTED_COLUMNS_MAX) &&
853 zcbor_tstr_put_lit(ctx->zse, "format") &&
854 zcbor_uint32_put(ctx->zse, (uint32_t)group->byte_string) &&
855 zcbor_tstr_put_lit(ctx->zse, "size") &&
856 zcbor_uint32_put(ctx->zse, (uint32_t)group->output_size) &&
857 zcbor_map_end_encode(ctx->zse, HASH_CHECKSUM_SUPPORTED_COLUMNS_MAX);
858 }
859
860 /**
861 * Command handler: fs supported hash/checksum (read)
862 */
863 static int
fs_mgmt_supported_hash_checksum(struct smp_streamer * ctxt)864 fs_mgmt_supported_hash_checksum(struct smp_streamer *ctxt)
865 {
866 zcbor_state_t *zse = ctxt->writer->zs;
867 struct fs_mgmt_hash_checksum_iterator_info itr_ctx = {
868 .zse = zse,
869 };
870
871 itr_ctx.ok = zcbor_tstr_put_lit(zse, "types") &&
872 zcbor_map_start_encode(zse, CONFIG_MCUMGR_GRP_FS_CHECKSUM_HASH_SUPPORTED_MAX_TYPES);
873
874 if (!itr_ctx.ok) {
875 return MGMT_ERR_EMSGSIZE;
876 }
877
878 fs_mgmt_hash_checksum_find_handlers(fs_mgmt_supported_hash_checksum_callback, &itr_ctx);
879
880 if (!itr_ctx.ok ||
881 !zcbor_map_end_encode(zse, CONFIG_MCUMGR_GRP_FS_CHECKSUM_HASH_SUPPORTED_MAX_TYPES)) {
882 return MGMT_ERR_EMSGSIZE;
883 }
884
885 return MGMT_ERR_EOK;
886 }
887 #endif
888 #endif
889
890 /**
891 * Command handler: fs opened file (write)
892 */
fs_mgmt_close_opened_file(struct smp_streamer * ctxt)893 static int fs_mgmt_close_opened_file(struct smp_streamer *ctxt)
894 {
895 if (k_sem_take(&fs_mgmt_ctxt.lock_sem, FILE_SEMAPHORE_MAX_TAKE_TIME)) {
896 return MGMT_ERR_EBUSY;
897 }
898
899 fs_mgmt_cleanup();
900
901 k_sem_give(&fs_mgmt_ctxt.lock_sem);
902
903 return MGMT_ERR_EOK;
904 }
905
906 #ifdef CONFIG_MCUMGR_SMP_SUPPORT_ORIGINAL_PROTOCOL
907 /*
908 * @brief Translate FS mgmt group error code into MCUmgr error code
909 *
910 * @param ret #fs_mgmt_err_code_t error code
911 *
912 * @return #mcumgr_err_t error code
913 */
fs_mgmt_translate_error_code(uint16_t err)914 static int fs_mgmt_translate_error_code(uint16_t err)
915 {
916 int rc;
917
918 switch (err) {
919 case FS_MGMT_ERR_FILE_INVALID_NAME:
920 case FS_MGMT_ERR_CHECKSUM_HASH_NOT_FOUND:
921 rc = MGMT_ERR_EINVAL;
922 break;
923
924 case FS_MGMT_ERR_FILE_NOT_FOUND:
925 case FS_MGMT_ERR_MOUNT_POINT_NOT_FOUND:
926 rc = MGMT_ERR_ENOENT;
927 break;
928
929 case FS_MGMT_ERR_UNKNOWN:
930 case FS_MGMT_ERR_FILE_IS_DIRECTORY:
931 case FS_MGMT_ERR_FILE_OPEN_FAILED:
932 case FS_MGMT_ERR_FILE_SEEK_FAILED:
933 case FS_MGMT_ERR_FILE_READ_FAILED:
934 case FS_MGMT_ERR_FILE_TRUNCATE_FAILED:
935 case FS_MGMT_ERR_FILE_DELETE_FAILED:
936 case FS_MGMT_ERR_FILE_WRITE_FAILED:
937 case FS_MGMT_ERR_FILE_OFFSET_NOT_VALID:
938 case FS_MGMT_ERR_FILE_OFFSET_LARGER_THAN_FILE:
939 case FS_MGMT_ERR_READ_ONLY_FILESYSTEM:
940 default:
941 rc = MGMT_ERR_EUNKNOWN;
942 }
943
944 return rc;
945 }
946 #endif
947
948 static const struct mgmt_handler fs_mgmt_handlers[] = {
949 [FS_MGMT_ID_FILE] = {
950 .mh_read = fs_mgmt_file_download,
951 .mh_write = fs_mgmt_file_upload,
952 },
953 #if defined(CONFIG_MCUMGR_GRP_FS_FILE_STATUS)
954 [FS_MGMT_ID_STAT] = {
955 .mh_read = fs_mgmt_file_status,
956 .mh_write = NULL,
957 },
958 #endif
959 #if defined(CONFIG_MCUMGR_GRP_FS_CHECKSUM_HASH)
960 [FS_MGMT_ID_HASH_CHECKSUM] = {
961 .mh_read = fs_mgmt_file_hash_checksum,
962 .mh_write = NULL,
963 },
964 #if defined(CONFIG_MCUMGR_GRP_FS_CHECKSUM_HASH_SUPPORTED_CMD)
965 [FS_MGMT_ID_SUPPORTED_HASH_CHECKSUM] = {
966 .mh_read = fs_mgmt_supported_hash_checksum,
967 .mh_write = NULL,
968 },
969 #endif
970 #endif
971 [FS_MGMT_ID_OPENED_FILE] = {
972 .mh_read = NULL,
973 .mh_write = fs_mgmt_close_opened_file,
974 },
975 };
976
977 #define FS_MGMT_HANDLER_CNT ARRAY_SIZE(fs_mgmt_handlers)
978
979 static struct mgmt_group fs_mgmt_group = {
980 .mg_handlers = fs_mgmt_handlers,
981 .mg_handlers_count = FS_MGMT_HANDLER_CNT,
982 .mg_group_id = MGMT_GROUP_ID_FS,
983 #ifdef CONFIG_MCUMGR_SMP_SUPPORT_ORIGINAL_PROTOCOL
984 .mg_translate_error = fs_mgmt_translate_error_code,
985 #endif
986 #ifdef CONFIG_MCUMGR_GRP_ENUM_DETAILS_NAME
987 .mg_group_name = "fs mgmt",
988 #endif
989 };
990
fs_mgmt_register_group(void)991 static void fs_mgmt_register_group(void)
992 {
993 /* Initialise state variables */
994 fs_mgmt_ctxt.state = STATE_NO_UPLOAD_OR_DOWNLOAD;
995 k_sem_init(&fs_mgmt_ctxt.lock_sem, 1, 1);
996 k_work_init_delayable(&fs_mgmt_ctxt.file_close_work, file_close_work_handler);
997
998 mgmt_register_group(&fs_mgmt_group);
999
1000 #if defined(CONFIG_MCUMGR_GRP_FS_CHECKSUM_HASH)
1001 /* Register any supported hash or checksum functions */
1002 #if defined(CONFIG_MCUMGR_GRP_FS_CHECKSUM_IEEE_CRC32)
1003 fs_mgmt_hash_checksum_register_crc32();
1004 #endif
1005
1006 #if defined(CONFIG_MCUMGR_GRP_FS_HASH_SHA256)
1007 fs_mgmt_hash_checksum_register_sha256();
1008 #endif
1009 #endif
1010 }
1011
1012 MCUMGR_HANDLER_DEFINE(fs_mgmt, fs_mgmt_register_group);
1013