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 (file_size < CONFIG_LOG_BACKEND_FS_FILE_SIZE) {
352 			/* There is space left to log to the latest file, no need to create
353 			 * a new one or delete old ones at this point.
354 			 */
355 			if (file_ctr == 0) {
356 				++file_ctr;
357 			}
358 			backend_state = BACKEND_FS_OK;
359 			goto out;
360 		} else {
361 			fs_close(file);
362 			if (file_ctr >= 1) {
363 				curr_file_num++;
364 				if (curr_file_num > MAX_FILE_NUMERAL) {
365 					curr_file_num = 0;
366 				}
367 			}
368 			backend_state = BACKEND_FS_OK;
369 		}
370 	} else {
371 		fs_close(file);
372 
373 		curr_file_num = newest;
374 		curr_file_num++;
375 		if (curr_file_num > MAX_FILE_NUMERAL) {
376 			curr_file_num = 0;
377 		}
378 	}
379 
380 	rc = fs_statvfs(CONFIG_LOG_BACKEND_FS_DIR, &stat);
381 
382 	/* Check if there is enough space to write file or max files number
383 	 * is not exceeded.
384 	 */
385 	while ((file_ctr >= CONFIG_LOG_BACKEND_FS_FILES_LIMIT) ||
386 	       ((stat.f_bfree * stat.f_frsize) <=
387 		CONFIG_LOG_BACKEND_FS_FILE_SIZE)) {
388 
389 		if (IS_ENABLED(CONFIG_LOG_BACKEND_FS_OVERWRITE)) {
390 			rc = del_oldest_log();
391 			if (rc < 0) {
392 				goto out;
393 			}
394 
395 			rc = fs_statvfs(CONFIG_LOG_BACKEND_FS_DIR,
396 					&stat);
397 			if (rc < 0) {
398 				goto out;
399 			}
400 		} else {
401 			return -ENOSPC;
402 		}
403 	}
404 
405 	snprintf(fname, sizeof(fname), "%s/%s%04d",
406 		CONFIG_LOG_BACKEND_FS_DIR,
407 		CONFIG_LOG_BACKEND_FS_FILE_PREFIX, curr_file_num);
408 
409 	rc = fs_open(file, fname, FS_O_CREATE | FS_O_WRITE);
410 	if (rc < 0) {
411 		goto out;
412 	}
413 	++file_ctr;
414 	newest = curr_file_num;
415 
416 out:
417 	return rc;
418 }
419 
del_oldest_log(void)420 static int del_oldest_log(void)
421 {
422 	int rc;
423 	static char dellname[MAX_PATH_LEN];
424 
425 	while (true) {
426 		snprintf(dellname, sizeof(dellname), "%s/%s%04d",
427 			 CONFIG_LOG_BACKEND_FS_DIR,
428 			 CONFIG_LOG_BACKEND_FS_FILE_PREFIX, oldest);
429 		rc = fs_unlink(dellname);
430 
431 		if ((rc == 0) || (rc == -ENOENT)) {
432 			oldest++;
433 			if (oldest > MAX_FILE_NUMERAL) {
434 				oldest = 0;
435 			}
436 
437 			if (rc == 0) {
438 				--file_ctr;
439 				break;
440 			}
441 		} else {
442 			break;
443 		}
444 	}
445 
446 	return rc;
447 }
448 
449 BUILD_ASSERT(!IS_ENABLED(CONFIG_LOG_MODE_IMMEDIATE),
450 	     "Immediate logging is not supported by LOG FS backend.");
451 
452 #ifndef CONFIG_LOG_BACKEND_FS_TESTSUITE
453 
454 static uint8_t __aligned(4) buf[MAX_FLASH_WRITE_SIZE];
455 LOG_OUTPUT_DEFINE(log_output, write_log_to_file, buf, MAX_FLASH_WRITE_SIZE);
456 
log_backend_fs_init(const struct log_backend * const backend)457 static void log_backend_fs_init(const struct log_backend *const backend)
458 {
459 }
460 
panic(struct log_backend const * const backend)461 static void panic(struct log_backend const *const backend)
462 {
463 	/* In case of panic deinitialize backend. It is better to keep
464 	 * current data rather than log new and risk of failure.
465 	 */
466 	log_backend_deactivate(backend);
467 }
468 
dropped(const struct log_backend * const backend,uint32_t cnt)469 static void dropped(const struct log_backend *const backend, uint32_t cnt)
470 {
471 	ARG_UNUSED(backend);
472 
473 	if (IS_ENABLED(CONFIG_LOG_BACKEND_FS_OUTPUT_DICTIONARY)) {
474 		log_dict_output_dropped_process(&log_output, cnt);
475 	} else {
476 		log_backend_std_dropped(&log_output, cnt);
477 	}
478 }
479 
process(const struct log_backend * const backend,union log_msg_generic * msg)480 static void process(const struct log_backend *const backend,
481 		union log_msg_generic *msg)
482 {
483 	uint32_t flags = log_backend_std_get_flags();
484 
485 	log_format_func_t log_output_func = log_format_func_t_get(log_format_current);
486 
487 	log_output_func(&log_output, &msg->log, flags);
488 }
489 
format_set(const struct log_backend * const backend,uint32_t log_type)490 static int format_set(const struct log_backend *const backend, uint32_t log_type)
491 {
492 	log_format_current = log_type;
493 	return 0;
494 }
495 
496 static const struct log_backend_api log_backend_fs_api = {
497 	.process = process,
498 	.panic = panic,
499 	.init = log_backend_fs_init,
500 	.dropped = dropped,
501 	.format_set = format_set,
502 };
503 
504 LOG_BACKEND_DEFINE(log_backend_fs, log_backend_fs_api,
505 		   IS_ENABLED(CONFIG_LOG_BACKEND_FS_AUTOSTART));
506 #endif
507