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