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