1 /*
2  * Copyright (c) 2020 Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <zephyr/logging/log_backend.h>
10 #include <zephyr/logging/log_output_dict.h>
11 #include <zephyr/logging/log_backend_std.h>
12 #include <assert.h>
13 #include <zephyr/fs/fs.h>
14 
15 #define MAX_PATH_LEN 256
16 #define MAX_FLASH_WRITE_SIZE 256
17 #define LOG_PREFIX_LEN (sizeof(CONFIG_LOG_BACKEND_FS_FILE_PREFIX) - 1)
18 #define MAX_FILE_NUMERAL 9999
19 #define FILE_NUMERAL_LEN 4
20 
21 enum backend_fs_state {
22 	BACKEND_FS_NOT_INITIALIZED = 0,
23 	BACKEND_FS_CORRUPTED,
24 	BACKEND_FS_OK
25 };
26 
27 static struct fs_file_t fs_file;
28 static enum backend_fs_state backend_state = BACKEND_FS_NOT_INITIALIZED;
29 static int file_ctr, newest, oldest;
30 
31 static int allocate_new_file(struct fs_file_t *file);
32 static int del_oldest_log(void);
33 static int get_log_file_id(struct fs_dirent *ent);
34 #ifndef CONFIG_LOG_BACKEND_FS_TESTSUITE
35 static uint32_t log_format_current = CONFIG_LOG_BACKEND_FS_OUTPUT_DEFAULT;
36 #endif
37 
check_log_volume_available(void)38 static int check_log_volume_available(void)
39 {
40 	int index = 0;
41 	char const *name;
42 	int rc = 0;
43 
44 	while (rc == 0) {
45 		rc = fs_readmount(&index, &name);
46 		if (rc == 0) {
47 			if (strncmp(CONFIG_LOG_BACKEND_FS_DIR,
48 				    name,
49 				    strlen(name))
50 			    == 0) {
51 				return 0;
52 			}
53 		}
54 	}
55 
56 	return -ENOENT;
57 }
58 
create_log_dir(const char * path)59 static int create_log_dir(const char *path)
60 {
61 	const char *next;
62 	const char *last = path + (strlen(path) - 1);
63 	char w_path[MAX_PATH_LEN];
64 	int rc, len;
65 	struct fs_dir_t dir;
66 
67 	fs_dir_t_init(&dir);
68 
69 	/* the fist directory name is the mount point*/
70 	/* the firs path's letter might be meaningless `/`, let's skip it */
71 	next = strchr(path + 1, '/');
72 	if (!next) {
73 		return 0;
74 	}
75 
76 	while (true) {
77 		next++;
78 		if (next > last) {
79 			return 0;
80 		}
81 		next = strchr(next, '/');
82 		if (!next) {
83 			next = last;
84 			len = last - path + 1;
85 		} else {
86 			len = next - path;
87 		}
88 
89 		memcpy(w_path, path, len);
90 		w_path[len] = 0;
91 
92 		rc = fs_opendir(&dir, w_path);
93 		if (rc) {
94 			/* assume directory doesn't exist */
95 			rc = fs_mkdir(w_path);
96 			if (rc) {
97 				break;
98 			}
99 		}
100 		rc = fs_closedir(&dir);
101 		if (rc) {
102 			break;
103 		}
104 	}
105 
106 	return rc;
107 
108 }
109 
check_log_file_exist(int num)110 static int check_log_file_exist(int num)
111 {
112 	struct fs_dir_t dir;
113 	struct fs_dirent ent;
114 	int rc;
115 
116 	fs_dir_t_init(&dir);
117 
118 	rc = fs_opendir(&dir, CONFIG_LOG_BACKEND_FS_DIR);
119 	if (rc) {
120 		return -EIO;
121 	}
122 
123 	while (true) {
124 		rc = fs_readdir(&dir, &ent);
125 		if (rc < 0) {
126 			rc = -EIO;
127 			goto close_dir;
128 		}
129 		if (ent.name[0] == 0) {
130 			break;
131 		}
132 
133 		rc = get_log_file_id(&ent);
134 
135 		if (rc == num) {
136 			rc = 1;
137 			goto close_dir;
138 		}
139 	}
140 
141 	rc = 0;
142 
143 close_dir:
144 	(void) fs_closedir(&dir);
145 
146 	return rc;
147 }
148 
write_log_to_file(uint8_t * data,size_t length,void * ctx)149 int write_log_to_file(uint8_t *data, size_t length, void *ctx)
150 {
151 	int rc;
152 	struct fs_file_t *f = &fs_file;
153 
154 	if (backend_state == BACKEND_FS_NOT_INITIALIZED) {
155 		if (check_log_volume_available()) {
156 			return length;
157 		}
158 		rc = create_log_dir(CONFIG_LOG_BACKEND_FS_DIR);
159 		if (!rc) {
160 			rc = allocate_new_file(&fs_file);
161 		}
162 		backend_state = (rc ? BACKEND_FS_CORRUPTED : BACKEND_FS_OK);
163 	}
164 
165 	if (backend_state == BACKEND_FS_OK) {
166 
167 		/* Check if new data overwrites max file size.
168 		 * If so, create new log file.
169 		 */
170 		int size = fs_tell(f);
171 
172 		if (size < 0) {
173 			backend_state = BACKEND_FS_CORRUPTED;
174 
175 			return length;
176 		} else if ((size + length) > CONFIG_LOG_BACKEND_FS_FILE_SIZE) {
177 			rc = allocate_new_file(f);
178 
179 			if (rc < 0) {
180 				goto on_error;
181 			}
182 		}
183 
184 		rc = fs_write(f, data, length);
185 		if (rc >= 0) {
186 			if (IS_ENABLED(CONFIG_LOG_BACKEND_FS_OVERWRITE) &&
187 			    (rc != length)) {
188 				del_oldest_log();
189 
190 				return 0;
191 			}
192 			/* If overwrite is disabled, full memory
193 			 * cause the log record abandonment.
194 			 */
195 			length = rc;
196 		} else {
197 			rc = check_log_file_exist(newest);
198 			if (rc == 0) {
199 				/* file was lost somehow
200 				 * try to get a new one
201 				 */
202 				file_ctr--;
203 				rc = allocate_new_file(f);
204 				if (rc < 0) {
205 					goto on_error;
206 				}
207 			} else if (rc < 0) {
208 				/* fs is corrupted*/
209 				goto on_error;
210 			}
211 			length = 0;
212 		}
213 
214 		rc = fs_sync(f);
215 		if (rc < 0) {
216 			/* Something is wrong */
217 			goto on_error;
218 		}
219 	}
220 
221 	return length;
222 
223 on_error:
224 	backend_state = BACKEND_FS_CORRUPTED;
225 	return length;
226 }
227 
get_log_file_id(struct fs_dirent * ent)228 static int get_log_file_id(struct fs_dirent *ent)
229 {
230 	size_t len;
231 	int num;
232 
233 	if (ent->type != FS_DIR_ENTRY_FILE) {
234 		return -1;
235 	}
236 
237 	len = strlen(ent->name);
238 
239 	if (len != LOG_PREFIX_LEN + FILE_NUMERAL_LEN) {
240 		return -1;
241 	}
242 
243 	if (memcmp(ent->name, CONFIG_LOG_BACKEND_FS_FILE_PREFIX, LOG_PREFIX_LEN) != 0) {
244 		return -1;
245 	}
246 
247 	num = atoi(ent->name + LOG_PREFIX_LEN);
248 
249 	if (num <= MAX_FILE_NUMERAL && num >= 0) {
250 		return num;
251 	}
252 
253 	return -1;
254 }
255 
allocate_new_file(struct fs_file_t * file)256 static int allocate_new_file(struct fs_file_t *file)
257 {
258 	/* In case of no log file or current file fills up
259 	 * create new log file.
260 	 */
261 	int rc;
262 	struct fs_statvfs stat;
263 	int curr_file_num;
264 	struct fs_dirent ent;
265 	char fname[MAX_PATH_LEN];
266 	off_t file_size;
267 
268 	assert(file);
269 
270 	if (backend_state == BACKEND_FS_NOT_INITIALIZED) {
271 		/* Search for the last used log number. */
272 		struct fs_dir_t dir;
273 		int file_num = 0;
274 
275 		fs_dir_t_init(&dir);
276 		curr_file_num = 0;
277 		int max = 0, min = MAX_FILE_NUMERAL;
278 
279 		rc = fs_opendir(&dir, CONFIG_LOG_BACKEND_FS_DIR);
280 
281 		while (rc >= 0) {
282 			rc = fs_readdir(&dir, &ent);
283 			if ((rc < 0) || (ent.name[0] == 0)) {
284 				break;
285 			}
286 
287 			file_num = get_log_file_id(&ent);
288 			if (file_num >= 0) {
289 
290 				if (file_num > max) {
291 					max = file_num;
292 				}
293 
294 				if (file_num < min) {
295 					min = file_num;
296 				}
297 				++file_ctr;
298 			}
299 		}
300 
301 		oldest = min;
302 
303 		if ((file_ctr > 1) &&
304 		    ((max - min) >
305 		     2 * CONFIG_LOG_BACKEND_FS_FILES_LIMIT)) {
306 			/* oldest log is in the range around the min */
307 			newest = min;
308 			oldest = max;
309 			(void)fs_closedir(&dir);
310 			rc = fs_opendir(&dir, CONFIG_LOG_BACKEND_FS_DIR);
311 
312 			while (rc == 0) {
313 				rc = fs_readdir(&dir, &ent);
314 				if ((rc < 0) || (ent.name[0] == 0)) {
315 					break;
316 				}
317 
318 				file_num = get_log_file_id(&ent);
319 				if (file_num < min + CONFIG_LOG_BACKEND_FS_FILES_LIMIT) {
320 					if (newest < file_num) {
321 						newest = file_num;
322 					}
323 				}
324 
325 				if (file_num > max - CONFIG_LOG_BACKEND_FS_FILES_LIMIT) {
326 					if (oldest > file_num) {
327 						oldest = file_num;
328 					}
329 				}
330 			}
331 		} else {
332 			newest = max;
333 			oldest = min;
334 		}
335 
336 		(void)fs_closedir(&dir);
337 		if (rc < 0) {
338 			goto out;
339 		}
340 
341 		curr_file_num = newest;
342 
343 		/* Is there space left in the newest file? */
344 		snprintf(fname, sizeof(fname), "%s/%s%04d", CONFIG_LOG_BACKEND_FS_DIR,
345 			 CONFIG_LOG_BACKEND_FS_FILE_PREFIX, curr_file_num);
346 		rc = fs_open(file, fname, FS_O_CREATE | FS_O_WRITE | FS_O_APPEND);
347 		if (rc < 0) {
348 			goto out;
349 		}
350 		file_size = fs_tell(file);
351 		if (IS_ENABLED(CONFIG_LOG_BACKEND_FS_APPEND_TO_NEWEST_FILE) &&
352 		    file_size < CONFIG_LOG_BACKEND_FS_FILE_SIZE) {
353 			/* There is space left to log to the latest file, no need to create
354 			 * a new one or delete old ones at this point.
355 			 */
356 			if (file_ctr == 0) {
357 				++file_ctr;
358 			}
359 			backend_state = BACKEND_FS_OK;
360 			goto out;
361 		} else {
362 			fs_close(file);
363 			if (file_ctr >= 1) {
364 				curr_file_num++;
365 				if (curr_file_num > MAX_FILE_NUMERAL) {
366 					curr_file_num = 0;
367 				}
368 			}
369 			backend_state = BACKEND_FS_OK;
370 		}
371 	} else {
372 		fs_close(file);
373 
374 		curr_file_num = newest;
375 		curr_file_num++;
376 		if (curr_file_num > MAX_FILE_NUMERAL) {
377 			curr_file_num = 0;
378 		}
379 	}
380 
381 	rc = fs_statvfs(CONFIG_LOG_BACKEND_FS_DIR, &stat);
382 
383 	/* Check if there is enough space to write file or max files number
384 	 * is not exceeded.
385 	 */
386 	while ((file_ctr >= CONFIG_LOG_BACKEND_FS_FILES_LIMIT) ||
387 	       ((stat.f_bfree * stat.f_frsize) <=
388 		CONFIG_LOG_BACKEND_FS_FILE_SIZE)) {
389 
390 		if (IS_ENABLED(CONFIG_LOG_BACKEND_FS_OVERWRITE)) {
391 			rc = del_oldest_log();
392 			if (rc < 0) {
393 				goto out;
394 			}
395 
396 			rc = fs_statvfs(CONFIG_LOG_BACKEND_FS_DIR,
397 					&stat);
398 			if (rc < 0) {
399 				goto out;
400 			}
401 		} else {
402 			return -ENOSPC;
403 		}
404 	}
405 
406 	snprintf(fname, sizeof(fname), "%s/%s%04d",
407 		CONFIG_LOG_BACKEND_FS_DIR,
408 		CONFIG_LOG_BACKEND_FS_FILE_PREFIX, curr_file_num);
409 
410 	rc = fs_open(file, fname, FS_O_CREATE | FS_O_WRITE);
411 	if (rc < 0) {
412 		goto out;
413 	}
414 	++file_ctr;
415 	newest = curr_file_num;
416 
417 out:
418 	return rc;
419 }
420 
del_oldest_log(void)421 static int del_oldest_log(void)
422 {
423 	int rc;
424 	static char dellname[MAX_PATH_LEN];
425 
426 	while (true) {
427 		snprintf(dellname, sizeof(dellname), "%s/%s%04d",
428 			 CONFIG_LOG_BACKEND_FS_DIR,
429 			 CONFIG_LOG_BACKEND_FS_FILE_PREFIX, oldest);
430 		rc = fs_unlink(dellname);
431 
432 		if ((rc == 0) || (rc == -ENOENT)) {
433 			oldest++;
434 			if (oldest > MAX_FILE_NUMERAL) {
435 				oldest = 0;
436 			}
437 
438 			if (rc == 0) {
439 				--file_ctr;
440 				break;
441 			}
442 		} else {
443 			break;
444 		}
445 	}
446 
447 	return rc;
448 }
449 
450 BUILD_ASSERT(!IS_ENABLED(CONFIG_LOG_MODE_IMMEDIATE),
451 	     "Immediate logging is not supported by LOG FS backend.");
452 
453 #ifndef CONFIG_LOG_BACKEND_FS_TESTSUITE
454 
455 static uint8_t __aligned(4) buf[MAX_FLASH_WRITE_SIZE];
456 LOG_OUTPUT_DEFINE(log_output, write_log_to_file, buf, MAX_FLASH_WRITE_SIZE);
457 
log_backend_fs_init(const struct log_backend * const backend)458 static void log_backend_fs_init(const struct log_backend *const backend)
459 {
460 }
461 
panic(struct log_backend const * const backend)462 static void panic(struct log_backend const *const backend)
463 {
464 	/* In case of panic deinitialize backend. It is better to keep
465 	 * current data rather than log new and risk of failure.
466 	 */
467 	log_backend_deactivate(backend);
468 }
469 
dropped(const struct log_backend * const backend,uint32_t cnt)470 static void dropped(const struct log_backend *const backend, uint32_t cnt)
471 {
472 	ARG_UNUSED(backend);
473 
474 	if (IS_ENABLED(CONFIG_LOG_BACKEND_FS_OUTPUT_DICTIONARY)) {
475 		log_dict_output_dropped_process(&log_output, cnt);
476 	} else {
477 		log_backend_std_dropped(&log_output, cnt);
478 	}
479 }
480 
process(const struct log_backend * const backend,union log_msg_generic * msg)481 static void process(const struct log_backend *const backend,
482 		union log_msg_generic *msg)
483 {
484 	uint32_t flags = log_backend_std_get_flags();
485 
486 	log_format_func_t log_output_func = log_format_func_t_get(log_format_current);
487 
488 	log_output_func(&log_output, &msg->log, flags);
489 }
490 
format_set(const struct log_backend * const backend,uint32_t log_type)491 static int format_set(const struct log_backend *const backend, uint32_t log_type)
492 {
493 	log_format_current = log_type;
494 	return 0;
495 }
496 
497 static const struct log_backend_api log_backend_fs_api = {
498 	.process = process,
499 	.panic = panic,
500 	.init = log_backend_fs_init,
501 	.dropped = dropped,
502 	.format_set = format_set,
503 };
504 
505 LOG_BACKEND_DEFINE(log_backend_fs, log_backend_fs_api,
506 		   IS_ENABLED(CONFIG_LOG_BACKEND_FS_AUTOSTART));
507 #endif
508