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