1 /*
2  * Copyright (c) 2006-2021, RT-Thread Development Team
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  *
6  * Change Logs:
7  * Date           Author       Notes
8  * 2021-01-07     ChenYong     first version
9  * 2021-12-20     armink       add multi-instance version
10  */
11 
12 #include <rtthread.h>
13 #include <dfs_file.h>
14 #include <unistd.h>
15 
16 #include <ulog.h>
17 #include <ulog_be.h>
18 
19 #ifdef ULOG_BACKEND_USING_FILE
20 
21 #if defined(ULOG_ASYNC_OUTPUT_THREAD_STACK) && (ULOG_ASYNC_OUTPUT_THREAD_STACK < 2048)
22 #error "The value of ULOG_ASYNC_OUTPUT_THREAD_STACK must be greater than 2048."
23 #endif
24 
25 /* rotate the log file xxx_n-1.log => xxx_n.log, and xxx.log => xxx_0.log */
ulog_file_rotate(struct ulog_file_be * be)26 static rt_bool_t ulog_file_rotate(struct ulog_file_be *be)
27 {
28 #define SUFFIX_LEN          10
29     /* mv xxx_n-1.log => xxx_n.log, and xxx.log => xxx_0.log */
30     static char old_path[ULOG_FILE_PATH_LEN], new_path[ULOG_FILE_PATH_LEN];
31     int index = 0, err = 0, file_fd = 0;
32     rt_bool_t result = RT_FALSE;
33     size_t base_len = 0;
34 
35     rt_snprintf(old_path, ULOG_FILE_PATH_LEN, "%s/%s", be->cur_log_dir_path, be->parent.name);
36     rt_snprintf(new_path, ULOG_FILE_PATH_LEN, "%s/%s", be->cur_log_dir_path, be->parent.name);
37     base_len = rt_strlen(be->cur_log_dir_path) + rt_strlen(be->parent.name) + 1;
38 
39     if (be->cur_log_file_fd >= 0)
40     {
41         close(be->cur_log_file_fd);
42     }
43 
44     for (index = be->file_max_num - 2; index >= 0; --index)
45     {
46         rt_snprintf(old_path + base_len, SUFFIX_LEN, index ? "_%d.log" : ".log", index - 1);
47         rt_snprintf(new_path + base_len, SUFFIX_LEN, "_%d.log", index);
48         /* remove the old file */
49         if ((file_fd = open(new_path, O_RDONLY)) >= 0)
50         {
51             close(file_fd);
52             unlink(new_path);
53         }
54         /* change the new log file to old file name */
55         if ((file_fd = open(old_path , O_RDONLY)) >= 0)
56         {
57             close(file_fd);
58             err = dfs_file_rename(old_path, new_path);
59         }
60 
61         if (err < 0)
62         {
63             result = RT_FALSE;
64             goto __exit;
65         }
66 
67         result = RT_TRUE;
68     }
69 
70 __exit:
71     /* reopen the file */
72     be->cur_log_file_fd = open(be->cur_log_file_path, O_CREAT | O_RDWR | O_APPEND);
73 
74     return result;
75 }
76 
ulog_file_backend_flush_with_buf(struct ulog_backend * backend)77 static void ulog_file_backend_flush_with_buf(struct ulog_backend *backend)
78 {
79     struct ulog_file_be *be = (struct ulog_file_be *) backend;
80     rt_size_t file_size = 0, write_size = 0;
81 
82     if (be->enable == RT_FALSE || be->buf_ptr_now == be->file_buf)
83     {
84         return;
85     }
86     if (be->cur_log_file_fd < 0)
87     {
88         /* check log file directory  */
89         if (access(be->cur_log_dir_path, F_OK) < 0)
90         {
91             mkdir(be->cur_log_dir_path, 0);
92         }
93         /* open file */
94         rt_snprintf(be->cur_log_file_path, ULOG_FILE_PATH_LEN, "%s/%s.log", be->cur_log_dir_path, be->parent.name);
95         be->cur_log_file_fd = open(be->cur_log_file_path, O_CREAT | O_RDWR | O_APPEND);
96         if (be->cur_log_file_fd < 0)
97         {
98             rt_kprintf("ulog file(%s) open failed.", be->cur_log_file_path);
99             return;
100         }
101     }
102 
103     file_size = lseek(be->cur_log_file_fd, 0, SEEK_END);
104     if (file_size >= (be->file_max_size - be->buf_size * 2))
105     {
106         if (!ulog_file_rotate(be))
107         {
108             return;
109         }
110     }
111 
112     write_size = (rt_size_t)(be->buf_ptr_now - be->file_buf);
113     /* write to the file */
114     if (write(be->cur_log_file_fd, be->file_buf, write_size) != write_size)
115     {
116         return;
117     }
118     /* flush file cache */
119     fsync(be->cur_log_file_fd);
120 
121     /* point be->buf_ptr_now at the head of be->file_buf[be->buf_size] */
122     be->buf_ptr_now = be->file_buf;
123 }
124 
ulog_file_backend_output_with_buf(struct ulog_backend * backend,rt_uint32_t level,const char * tag,rt_bool_t is_raw,const char * log,rt_size_t len)125 static void ulog_file_backend_output_with_buf(struct ulog_backend *backend, rt_uint32_t level,
126             const char *tag, rt_bool_t is_raw, const char *log, rt_size_t len)
127 {
128     struct ulog_file_be *be = (struct ulog_file_be *)backend;
129     rt_size_t copy_len = 0, free_len = 0;
130     const unsigned char *buf_ptr_end = be->file_buf + be->buf_size;
131 
132     while (len)
133     {
134         /* free space length */
135         free_len = buf_ptr_end - be->buf_ptr_now;
136         /* copy the log to the mem buffer */
137         if (len > free_len)
138         {
139             copy_len = free_len;
140         }
141         else
142         {
143             copy_len = len;
144         }
145         rt_memcpy(be->buf_ptr_now, log, copy_len);
146         /* update data pos */
147         be->buf_ptr_now += copy_len;
148         len -= copy_len;
149         log += copy_len;
150 
151         RT_ASSERT(be->buf_ptr_now <= buf_ptr_end);
152         /* check the log buffer remain size */
153         if (buf_ptr_end == be->buf_ptr_now)
154         {
155             ulog_file_backend_flush_with_buf(backend);
156             if (buf_ptr_end == be->buf_ptr_now)
157             {
158                 /* There is no space, indicating that the data cannot be refreshed
159                    to the back end of the file Discard data and exit directly */
160                 break;
161             }
162         }
163     }
164 }
165 
166 /* initialize the ulog file backend */
ulog_file_backend_init(struct ulog_file_be * be,const char * name,const char * dir_path,rt_size_t max_num,rt_size_t max_size,rt_size_t buf_size)167 int ulog_file_backend_init(struct ulog_file_be *be, const char *name, const char *dir_path, rt_size_t max_num,
168         rt_size_t max_size, rt_size_t buf_size)
169 {
170     be->file_buf = rt_calloc(1, buf_size);
171     if (!be->file_buf)
172     {
173         rt_kprintf("Warning: NO MEMORY for %s file backend\n", name);
174         return -RT_ENOMEM;
175     }
176     /* temporarily store the start address of the ulog file buffer */
177     be->buf_ptr_now = be->file_buf;
178     be->cur_log_file_fd = -1;
179     be->file_max_num = max_num;
180     be->file_max_size = max_size;
181     be->buf_size = buf_size;
182     be->enable = RT_FALSE;
183     rt_strncpy(be->cur_log_dir_path, dir_path, ULOG_FILE_PATH_LEN);
184     /* the buffer length MUST less than file size */
185     RT_ASSERT(be->buf_size < be->file_max_size);
186 
187     be->parent.output = ulog_file_backend_output_with_buf;
188     be->parent.flush = ulog_file_backend_flush_with_buf;
189     ulog_backend_register((ulog_backend_t) be, name, RT_FALSE);
190 
191     return 0;
192 }
193 
194 /* uninitialize the ulog file backend */
ulog_file_backend_deinit(struct ulog_file_be * be)195 int ulog_file_backend_deinit(struct ulog_file_be *be)
196 {
197     if (be->cur_log_file_fd >= 0)
198     {
199         /* flush log to file */
200         ulog_file_backend_flush_with_buf((ulog_backend_t)be);
201         /* close */
202         close(be->cur_log_file_fd);
203         be->cur_log_file_fd = -1;
204     }
205 
206     if (be->file_buf)
207     {
208         rt_free(be->file_buf);
209         be->file_buf = RT_NULL;
210     }
211 
212     ulog_backend_unregister((ulog_backend_t)be);
213     return 0;
214 }
215 
ulog_file_backend_enable(struct ulog_file_be * be)216 void ulog_file_backend_enable(struct ulog_file_be *be)
217 {
218     be->enable = RT_TRUE;
219 }
220 
ulog_file_backend_disable(struct ulog_file_be * be)221 void ulog_file_backend_disable(struct ulog_file_be *be)
222 {
223     be->enable = RT_FALSE;
224 }
225 
226 #endif /* ULOG_BACKEND_USING_FILE */
227