aboutsummaryrefslogtreecommitdiff
path: root/src/linux/cpulist.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/linux/cpulist.c')
-rw-r--r--src/linux/cpulist.c199
1 files changed, 199 insertions, 0 deletions
diff --git a/src/linux/cpulist.c b/src/linux/cpulist.c
new file mode 100644
index 0000000..9b9543d
--- /dev/null
+++ b/src/linux/cpulist.c
@@ -0,0 +1,199 @@
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sched.h>
+
+#include <linux/api.h>
+#include <log.h>
+
+
+/*
+ * 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
+ * (<cpu-number> or <cpu-number-start>-<cpu-number-end>)
+ * 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) {
+ return string;
+ }
+ number = number * UINT32_C(10) + digit;
+ string += 1;
+ }
+ *number_ptr = number;
+ return end;
+}
+
+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
+
+ file = open(filename, O_RDONLY);
+ if (file == -1) {
+ cpuinfo_log_error("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 {
+ bytes_read = read(file, data_start, (size_t) (buffer_end - data_start));
+ if (bytes_read < 0) {
+ cpuinfo_log_error("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) {
+ close(file);
+ file = -1;
+ }
+ return status;
+}