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