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