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