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