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