1 /*
2  * Copyright (C) 2018-2022 Intel Corporation.
3  *
4  * SPDX-License-Identifier: BSD-3-Clause
5  */
6 
7 #include <sys/param.h>
8 #include <sys/stat.h>
9 #include <errno.h>
10 #include <paths.h>
11 #include <stdarg.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <unistd.h>
16 #include <stdbool.h>
17 #include <fcntl.h>
18 #include <dirent.h>
19 #include <time.h>
20 
21 #include "dm.h"
22 #include "log.h"
23 
24 #define DISK_PREFIX  "disk_log: "
25 
26 #define LOG_PATH_NODE "/var/log/acrn-dm/"
27 #define LOG_NAME_PREFIX  "%s_log_"
28 #define LOG_NAME_FMT  "%s%s_log_%d" /* %s-->vm1/vm2..., %d-->1/2/3/4... */
29 #define LOG_DELIMITER "\n\n----------------new vm instance------------------\n\n\0"
30 
31 #define FILE_NAME_LENGTH  96
32 
33 #define LOG_SIZE_LIMIT    0x200000 /* one log file size limit */
34 #define LOG_FILES_COUNT   8
35 
36 static int disk_fd = -1;
37 static uint32_t cur_log_size;
38 static uint16_t cur_file_index;
39 
40 static uint8_t disk_log_level = LOG_DEBUG;
41 static bool disk_log_enabled = false;
42 
43 #define DISK_LOG_MAX_LEN    (MAX_ONE_LOG_SIZE + 32)
44 #define INDEX_AFTER(a, b) ((short int)b - (short int)a < 0)
45 
is_disk_log_enabled(void)46 static bool is_disk_log_enabled(void)
47 {
48 	return disk_log_enabled;
49 }
50 
get_disk_log_level(void)51 static uint8_t get_disk_log_level(void)
52 {
53 	return disk_log_level;
54 }
55 
probe_disk_log_file(void)56 static int probe_disk_log_file(void)
57 {
58 	char file_name[FILE_NAME_LENGTH];
59 	struct dirent *pdir;
60 	struct stat st;
61 	int length;
62 	uint16_t index = 0, tmp;
63 	bool is_first_file = true;
64 	DIR *dir;
65 
66 	if (stat(LOG_PATH_NODE, &st)) {
67 		if (system("mkdir -p " LOG_PATH_NODE) < 0) {
68 			printf(DISK_PREFIX"create path: %s failed! Error: %s\n",
69 				LOG_PATH_NODE, strerror(errno));
70 			return -1;
71 		}
72 	}
73 
74 	dir = opendir(LOG_PATH_NODE);
75 	if (!dir) {
76 		printf(DISK_PREFIX" open %s failed! Error: %s\n",
77 			LOG_PATH_NODE, strerror(errno));
78 		return -1;
79 	}
80 
81 	snprintf(file_name, FILE_NAME_LENGTH - 1, LOG_NAME_PREFIX, vmname);
82 	length = strlen(file_name);
83 
84 	while ((pdir = readdir(dir)) != NULL) {
85 		if (!(pdir->d_type & DT_REG))
86 			continue;
87 
88 		if (strncmp(pdir->d_name, file_name, length) != 0)
89 			continue;
90 
91 		tmp = (uint16_t)atoi(pdir->d_name + length);
92 		if (is_first_file) {
93 			is_first_file = false;
94 			index = tmp;
95 		} else if (INDEX_AFTER(tmp, index)) {
96 			index = tmp;
97 		}
98 	}
99 	closedir(dir);
100 
101 	snprintf(file_name, FILE_NAME_LENGTH - 1, LOG_NAME_FMT, LOG_PATH_NODE, vmname, index);
102 	disk_fd = open(file_name, O_RDWR | O_CREAT | O_APPEND, 0644);
103 	if (disk_fd < 0) {
104 		printf(DISK_PREFIX" open %s failed! Error: %s\n", file_name, strerror(errno));
105 		return -1;
106 	}
107 
108 	if (write(disk_fd, LOG_DELIMITER, strlen(LOG_DELIMITER)) < 0) {
109 		printf(DISK_PREFIX" write %s failed! Error: %s\n", file_name, strerror(errno));
110 		return -1;
111 	}
112 
113 	fstat(disk_fd, &st);
114 	cur_log_size = st.st_size;
115 	cur_file_index = index;
116 
117 	return 0;
118 }
119 
init_disk_logger(bool enable,uint8_t log_level)120 static int init_disk_logger(bool enable, uint8_t log_level)
121 {
122 	disk_log_enabled = enable;
123 	disk_log_level = log_level;
124 
125 	return 1;
126 }
127 
deinit_disk_logger(void)128 static void deinit_disk_logger(void)
129 {
130 	if (disk_fd > 0) {
131 		disk_log_enabled = false;
132 
133 		fsync(disk_fd);
134 		close(disk_fd);
135 		disk_fd = -1;
136 	}
137 }
138 
write_to_disk(const char * fmt,va_list args)139 static void write_to_disk(const char *fmt, va_list args)
140 {
141 	char buffer[DISK_LOG_MAX_LEN];
142 	char *file_name = buffer;
143 	char *buf;
144 	int len;
145 	int write_cnt;
146 	struct timespec times = {0, 0};
147 	struct tm *lt;
148 	time_t tt;
149 
150 
151 	if ((disk_fd < 0) && disk_log_enabled) {
152 		/**
153 		 * usually this probe just be called once in DM whole life; but we need use vmname in
154 		 * probe_disk_log_file, it can't be called in init_disk_logger for vmname not inited then,
155 		 * so call it here.
156 		 */
157 		if (probe_disk_log_file() < 0) {
158 			disk_log_enabled = false;
159 			return;
160 		}
161 	}
162 
163 	len = vasprintf(&buf, fmt, args);
164 	if (len < 0)
165 		return;
166 
167 	time(&tt);
168 	lt = localtime(&tt);
169 	clock_gettime(CLOCK_MONOTONIC, &times);
170 
171 	len = snprintf(buffer, DISK_LOG_MAX_LEN, "[%4d-%02d-%02d %02d:%02d:%02d][%5lu.%06lu] ",
172 		lt->tm_year + 1900, lt->tm_mon + 1, lt->tm_mday, lt->tm_hour, lt->tm_min, lt->tm_sec,
173 		times.tv_sec, times.tv_nsec / 1000);
174 	if (len < 0 || len >= DISK_LOG_MAX_LEN) {
175 		free(buf);
176 		return;
177 	}
178 	len = strnlen(buffer, DISK_LOG_MAX_LEN);
179 
180 	strncpy(buffer + len, buf, DISK_LOG_MAX_LEN - len);
181 	buffer[DISK_LOG_MAX_LEN - 1] = '\0';
182 	free(buf);
183 
184 	write_cnt = write(disk_fd, buffer, strnlen(buffer, DISK_LOG_MAX_LEN));
185 	if (write_cnt < 0) {
186 		printf(DISK_PREFIX"write disk failed");
187 		close(disk_fd);
188 		disk_fd = -1;
189 		return;
190 	}
191 
192 	cur_log_size += write_cnt;
193 	if (cur_log_size > LOG_SIZE_LIMIT) {
194 
195 		cur_file_index++;
196 
197 		/* remove the first old log file, to add a new one */
198 		snprintf(file_name, FILE_NAME_LENGTH - 1, LOG_NAME_FMT,
199 			LOG_PATH_NODE, vmname, (uint16_t)(cur_file_index - LOG_FILES_COUNT));
200 		remove(file_name);
201 
202 		snprintf(file_name, FILE_NAME_LENGTH - 1, LOG_NAME_FMT,
203 			LOG_PATH_NODE, vmname, cur_file_index);
204 
205 		close(disk_fd);
206 		disk_fd = open(file_name, O_RDWR | O_CREAT, 0644);
207 		if (disk_fd < 0) {
208 			printf(DISK_PREFIX" open %s failed! Error: %s\n", file_name, strerror(errno));
209 			return;
210 		}
211 		cur_log_size = 0;
212 	}
213 }
214 
215 static struct logger_ops logger_disk = {
216 	.name = "disk",
217 	.is_enabled = is_disk_log_enabled,
218 	.get_log_level = get_disk_log_level,
219 	.init = init_disk_logger,
220 	.deinit = deinit_disk_logger,
221 	.output = write_to_disk,
222 };
223 
224 DEFINE_LOGGER_DEVICE(logger_disk);
225