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