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