• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #include <errno.h>
2 #include <stdbool.h>
3 #include <stdint.h>
4 #include <stdlib.h>
5 #include <string.h>
6 
7 #include <fcntl.h>
8 #include <sched.h>
9 #include <sys/stat.h>
10 #include <sys/types.h>
11 #include <unistd.h>
12 
13 #if CPUINFO_MOCK
14 #include <cpuinfo-mock.h>
15 #endif
16 #include <cpuinfo/log.h>
17 #include <linux/api.h>
18 
19 /*
20  * Size, in chars, of the on-stack buffer used for parsing cpu lists.
21  * This is also the limit on the length of a single entry
22  * (<cpu-number> or <cpu-number-start>-<cpu-number-end>)
23  * in the cpu list.
24  */
25 #define BUFFER_SIZE 256
26 
27 /* Locale-independent */
is_whitespace(char c)28 inline static bool is_whitespace(char c) {
29 	switch (c) {
30 		case ' ':
31 		case '\t':
32 		case '\n':
33 		case '\r':
34 			return true;
35 		default:
36 			return false;
37 	}
38 }
39 
parse_number(const char * string,const char * end,uint32_t number_ptr[restrict static1])40 inline static const char* parse_number(const char* string, const char* end, uint32_t number_ptr[restrict static 1]) {
41 	uint32_t number = 0;
42 	while (string != end) {
43 		const uint32_t digit = (uint32_t)(*string) - (uint32_t)'0';
44 		if (digit >= 10) {
45 			break;
46 		}
47 		number = number * UINT32_C(10) + digit;
48 		string += 1;
49 	}
50 	*number_ptr = number;
51 	return string;
52 }
53 
parse_entry(const char * entry_start,const char * entry_end,cpuinfo_cpulist_callback callback,void * context)54 inline static bool parse_entry(
55 	const char* entry_start,
56 	const char* entry_end,
57 	cpuinfo_cpulist_callback callback,
58 	void* context) {
59 	/* Skip whitespace at the beginning of an entry */
60 	for (; entry_start != entry_end; entry_start++) {
61 		if (!is_whitespace(*entry_start)) {
62 			break;
63 		}
64 	}
65 	/* Skip whitespace at the end of an entry */
66 	for (; entry_end != entry_start; entry_end--) {
67 		if (!is_whitespace(entry_end[-1])) {
68 			break;
69 		}
70 	}
71 
72 	const size_t entry_length = (size_t)(entry_end - entry_start);
73 	if (entry_length == 0) {
74 		cpuinfo_log_warning("unexpected zero-length cpu list entry ignored");
75 		return false;
76 	}
77 
78 #if CPUINFO_LOG_DEBUG_PARSERS
79 	cpuinfo_log_debug("parse cpu list entry \"%.*s\" (%zu chars)", (int)entry_length, entry_start, entry_length);
80 #endif
81 	uint32_t first_cpu, last_cpu;
82 
83 	const char* number_end = parse_number(entry_start, entry_end, &first_cpu);
84 	if (number_end == entry_start) {
85 		/* Failed to parse the number; ignore the entry */
86 		cpuinfo_log_warning(
87 			"invalid character '%c' in the cpu list entry \"%.*s\": entry is ignored",
88 			entry_start[0],
89 			(int)entry_length,
90 			entry_start);
91 		return false;
92 	} else if (number_end == entry_end) {
93 /* Completely parsed the entry */
94 #if CPUINFO_LOG_DEBUG_PARSERS
95 		cpuinfo_log_debug(
96 			"cpulist: call callback with list_start = %" PRIu32 ", list_end = %" PRIu32,
97 			first_cpu,
98 			first_cpu + 1);
99 #endif
100 		return callback(first_cpu, first_cpu + 1, context);
101 	}
102 
103 	/* Parse the second part of the entry */
104 	if (*number_end != '-') {
105 		cpuinfo_log_warning(
106 			"invalid character '%c' in the cpu list entry \"%.*s\": entry is ignored",
107 			*number_end,
108 			(int)entry_length,
109 			entry_start);
110 		return false;
111 	}
112 
113 	const char* number_start = number_end + 1;
114 	number_end = parse_number(number_start, entry_end, &last_cpu);
115 	if (number_end == number_start) {
116 		/* Failed to parse the second number; ignore the entry */
117 		cpuinfo_log_warning(
118 			"invalid character '%c' in the cpu list entry \"%.*s\": entry is ignored",
119 			*number_start,
120 			(int)entry_length,
121 			entry_start);
122 		return false;
123 	}
124 
125 	if (number_end != entry_end) {
126 		/* Partially parsed the entry; ignore unparsed characters and
127 		 * continue with the parsed part */
128 		cpuinfo_log_warning(
129 			"ignored invalid characters \"%.*s\" at the end of cpu list entry \"%.*s\"",
130 			(int)(entry_end - number_end),
131 			number_start,
132 			(int)entry_length,
133 			entry_start);
134 	}
135 
136 	if (last_cpu < first_cpu) {
137 		cpuinfo_log_warning(
138 			"ignored cpu list entry \"%.*s\": invalid range %" PRIu32 "-%" PRIu32,
139 			(int)entry_length,
140 			entry_start,
141 			first_cpu,
142 			last_cpu);
143 		return false;
144 	}
145 
146 /* Parsed both parts of the entry; update CPU set */
147 #if CPUINFO_LOG_DEBUG_PARSERS
148 	cpuinfo_log_debug(
149 		"cpulist: call callback with list_start = %" PRIu32 ", list_end = %" PRIu32, first_cpu, last_cpu + 1);
150 #endif
151 	return callback(first_cpu, last_cpu + 1, context);
152 }
153 
cpuinfo_linux_parse_cpulist(const char * filename,cpuinfo_cpulist_callback callback,void * context)154 bool cpuinfo_linux_parse_cpulist(const char* filename, cpuinfo_cpulist_callback callback, void* context) {
155 	bool status = true;
156 	int file = -1;
157 	char buffer[BUFFER_SIZE];
158 #if CPUINFO_LOG_DEBUG_PARSERS
159 	cpuinfo_log_debug("parsing cpu list from file %s", filename);
160 #endif
161 
162 #if CPUINFO_MOCK
163 	file = cpuinfo_mock_open(filename, O_RDONLY);
164 #else
165 	file = open(filename, O_RDONLY);
166 #endif
167 	if (file == -1) {
168 		cpuinfo_log_info("failed to open %s: %s", filename, strerror(errno));
169 		status = false;
170 		goto cleanup;
171 	}
172 
173 	size_t position = 0;
174 	const char* buffer_end = &buffer[BUFFER_SIZE];
175 	char* data_start = buffer;
176 	ssize_t bytes_read;
177 	do {
178 #if CPUINFO_MOCK
179 		bytes_read = cpuinfo_mock_read(file, data_start, (size_t)(buffer_end - data_start));
180 #else
181 		bytes_read = read(file, data_start, (size_t)(buffer_end - data_start));
182 #endif
183 		if (bytes_read < 0) {
184 			cpuinfo_log_info(
185 				"failed to read file %s at position %zu: %s", filename, position, strerror(errno));
186 			status = false;
187 			goto cleanup;
188 		}
189 
190 		position += (size_t)bytes_read;
191 		const char* data_end = data_start + (size_t)bytes_read;
192 		const char* entry_start = buffer;
193 
194 		if (bytes_read == 0) {
195 			/* No more data in the file: process the remaining text
196 			 * in the buffer as a single entry */
197 			const char* entry_end = data_end;
198 			const bool entry_status = parse_entry(entry_start, entry_end, callback, context);
199 			status &= entry_status;
200 		} else {
201 			const char* entry_end;
202 			do {
203 				/* Find the end of the entry, as indicated by a
204 				 * comma (',') */
205 				for (entry_end = entry_start; entry_end != data_end; entry_end++) {
206 					if (*entry_end == ',') {
207 						break;
208 					}
209 				}
210 
211 				/*
212 				 * If we located separator at the end of the
213 				 * entry, parse it. Otherwise, there may be more
214 				 * data at the end; read the file once again.
215 				 */
216 				if (entry_end != data_end) {
217 					const bool entry_status =
218 						parse_entry(entry_start, entry_end, callback, context);
219 					status &= entry_status;
220 					entry_start = entry_end + 1;
221 				}
222 			} while (entry_end != data_end);
223 
224 			/* Move remaining partial entry data at the end to the
225 			 * beginning of the buffer */
226 			const size_t entry_length = (size_t)(entry_end - entry_start);
227 			memmove(buffer, entry_start, entry_length);
228 			data_start = &buffer[entry_length];
229 		}
230 	} while (bytes_read != 0);
231 
232 cleanup:
233 	if (file != -1) {
234 #if CPUINFO_MOCK
235 		cpuinfo_mock_close(file);
236 #else
237 		close(file);
238 #endif
239 		file = -1;
240 	}
241 	return status;
242 }
243