#include #include #include #include #include #include #include #include #include #include #if CPUINFO_MOCK #include #endif #include #include /* * Size, in chars, of the on-stack buffer used for parsing cpu lists. * This is also the limit on the length of a single entry * ( or -) * in the cpu list. */ #define BUFFER_SIZE 256 /* Locale-independent */ inline static bool is_whitespace(char c) { switch (c) { case ' ': case '\t': case '\n': case '\r': return true; default: return false; } } inline static const char* parse_number(const char* string, const char* end, uint32_t number_ptr[restrict static 1]) { uint32_t number = 0; while (string != end) { const uint32_t digit = (uint32_t) (*string) - (uint32_t) '0'; if (digit >= 10) { break; } number = number * UINT32_C(10) + digit; string += 1; } *number_ptr = number; return string; } inline static bool parse_entry(const char* entry_start, const char* entry_end, cpuinfo_cpulist_callback callback, void* context) { /* Skip whitespace at the beginning of an entry */ for (; entry_start != entry_end; entry_start++) { if (!is_whitespace(*entry_start)) { break; } } /* Skip whitespace at the end of an entry */ for (; entry_end != entry_start; entry_end--) { if (!is_whitespace(entry_end[-1])) { break; } } const size_t entry_length = (size_t) (entry_end - entry_start); if (entry_length == 0) { cpuinfo_log_warning("unexpected zero-length cpu list entry ignored"); return false; } #if CPUINFO_LOG_DEBUG_PARSERS cpuinfo_log_debug("parse cpu list entry \"%.*s\" (%zu chars)", (int) entry_length, entry_start, entry_length); #endif uint32_t first_cpu, last_cpu; const char* number_end = parse_number(entry_start, entry_end, &first_cpu); if (number_end == entry_start) { /* Failed to parse the number; ignore the entry */ cpuinfo_log_warning("invalid character '%c' in the cpu list entry \"%.*s\": entry is ignored", entry_start[0], (int) entry_length, entry_start); return false; } else if (number_end == entry_end) { /* Completely parsed the entry */ #if CPUINFO_LOG_DEBUG_PARSERS cpuinfo_log_debug("cpulist: call callback with list_start = %"PRIu32", list_end = %"PRIu32, first_cpu, first_cpu + 1); #endif return callback(first_cpu, first_cpu + 1, context); } /* Parse the second part of the entry */ if (*number_end != '-') { cpuinfo_log_warning("invalid character '%c' in the cpu list entry \"%.*s\": entry is ignored", *number_end, (int) entry_length, entry_start); return false; } const char* number_start = number_end + 1; number_end = parse_number(number_start, entry_end, &last_cpu); if (number_end == number_start) { /* Failed to parse the second number; ignore the entry */ cpuinfo_log_warning("invalid character '%c' in the cpu list entry \"%.*s\": entry is ignored", *number_start, (int) entry_length, entry_start); return false; } if (number_end != entry_end) { /* Partially parsed the entry; ignore unparsed characters and continue with the parsed part */ cpuinfo_log_warning("ignored invalid characters \"%.*s\" at the end of cpu list entry \"%.*s\"", (int) (entry_end - number_end), number_start, (int) entry_length, entry_start); } if (last_cpu < first_cpu) { cpuinfo_log_warning("ignored cpu list entry \"%.*s\": invalid range %"PRIu32"-%"PRIu32, (int) entry_length, entry_start, first_cpu, last_cpu); return false; } /* Parsed both parts of the entry; update CPU set */ #if CPUINFO_LOG_DEBUG_PARSERS cpuinfo_log_debug("cpulist: call callback with list_start = %"PRIu32", list_end = %"PRIu32, first_cpu, last_cpu + 1); #endif return callback(first_cpu, last_cpu + 1, context); } bool cpuinfo_linux_parse_cpulist(const char* filename, cpuinfo_cpulist_callback callback, void* context) { bool status = true; int file = -1; char buffer[BUFFER_SIZE]; #if CPUINFO_LOG_DEBUG_PARSERS cpuinfo_log_debug("parsing cpu list from file %s", filename); #endif #if CPUINFO_MOCK file = cpuinfo_mock_open(filename, O_RDONLY); #else file = open(filename, O_RDONLY); #endif if (file == -1) { cpuinfo_log_info("failed to open %s: %s", filename, strerror(errno)); status = false; goto cleanup; } size_t position = 0; const char* buffer_end = &buffer[BUFFER_SIZE]; char* data_start = buffer; ssize_t bytes_read; do { #if CPUINFO_MOCK bytes_read = cpuinfo_mock_read(file, data_start, (size_t) (buffer_end - data_start)); #else bytes_read = read(file, data_start, (size_t) (buffer_end - data_start)); #endif if (bytes_read < 0) { cpuinfo_log_info("failed to read file %s at position %zu: %s", filename, position, strerror(errno)); status = false; goto cleanup; } position += (size_t) bytes_read; const char* data_end = data_start + (size_t) bytes_read; const char* entry_start = buffer; if (bytes_read == 0) { /* No more data in the file: process the remaining text in the buffer as a single entry */ const char* entry_end = data_end; const bool entry_status = parse_entry(entry_start, entry_end, callback, context); status &= entry_status; } else { const char* entry_end; do { /* Find the end of the entry, as indicated by a comma (',') */ for (entry_end = entry_start; entry_end != data_end; entry_end++) { if (*entry_end == ',') { break; } } /* * If we located separator at the end of the entry, parse it. * Otherwise, there may be more data at the end; read the file once again. */ if (entry_end != data_end) { const bool entry_status = parse_entry(entry_start, entry_end, callback, context); status &= entry_status; entry_start = entry_end + 1; } } while (entry_end != data_end); /* Move remaining partial entry data at the end to the beginning of the buffer */ const size_t entry_length = (size_t) (entry_end - entry_start); memmove(buffer, entry_start, entry_length); data_start = &buffer[entry_length]; } } while (bytes_read != 0); cleanup: if (file != -1) { #if CPUINFO_MOCK cpuinfo_mock_close(file); #else close(file); #endif file = -1; } return status; }