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