fix: removed accidental VLA in _get_num_possible_cpus()
[lttng-ust.git] / src / common / smp.c
1 /*
2 * SPDX-License-Identifier: LGPL-2.1-only
3 *
4 * Copyright (C) 2011-2012 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
5 * Copyright (C) 2019 Michael Jeanson <mjeanson@efficios.com>
6 */
7
8 #define _LGPL_SOURCE
9 #include <assert.h>
10 #include <ctype.h>
11 #include <errno.h>
12 #include <fcntl.h>
13 #include <limits.h>
14 #include <unistd.h>
15 #include <pthread.h>
16 #include <stdlib.h>
17
18 #include <urcu/compiler.h>
19
20 #include "common/logging.h"
21 #include "common/smp.h"
22
23 static int num_possible_cpus_cache;
24
25 #if (defined(__GLIBC__) || defined( __UCLIBC__))
26 int get_num_possible_cpus_fallback(void)
27 {
28 /* On Linux, when some processors are offline
29 * _SC_NPROCESSORS_CONF counts the offline
30 * processors, whereas _SC_NPROCESSORS_ONLN
31 * does not. If we used _SC_NPROCESSORS_ONLN,
32 * getcpu() could return a value greater than
33 * this sysconf, in which case the arrays
34 * indexed by processor would overflow.
35 */
36 return sysconf(_SC_NPROCESSORS_CONF);
37 }
38
39 #else
40
41 /*
42 * The MUSL libc implementation of the _SC_NPROCESSORS_CONF sysconf does not
43 * return the number of configured CPUs in the system but relies on the cpu
44 * affinity mask of the current task.
45 *
46 * So instead we use a strategy similar to GLIBC's, counting the cpu
47 * directories in "/sys/devices/system/cpu" and fallback on the value from
48 * sysconf if it fails.
49 */
50
51 #include <dirent.h>
52 #include <limits.h>
53 #include <stdlib.h>
54 #include <string.h>
55 #include <sys/types.h>
56
57 #define __max(a,b) ((a)>(b)?(a):(b))
58
59 int get_num_possible_cpus_fallback(void)
60 {
61 int count = 0;
62 DIR *cpudir;
63 struct dirent *entry;
64
65 cpudir = opendir("/sys/devices/system/cpu");
66 if (cpudir == NULL)
67 goto end;
68
69 /*
70 * Count the number of directories named "cpu" followed by and
71 * integer. This is the same strategy as glibc uses.
72 */
73 while ((entry = readdir(cpudir))) {
74 if (entry->d_type == DT_DIR &&
75 strncmp(entry->d_name, "cpu", 3) == 0) {
76
77 char *endptr;
78 unsigned long cpu_num;
79
80 cpu_num = strtoul(entry->d_name + 3, &endptr, 10);
81 if ((cpu_num < ULONG_MAX) && (endptr != entry->d_name + 3)
82 && (*endptr == '\0')) {
83 count++;
84 }
85 }
86 }
87
88 end:
89 /*
90 * Get the sysconf value as a fallback. Keep the highest number.
91 */
92 return __max(sysconf(_SC_NPROCESSORS_CONF), count);
93 }
94 #endif
95
96 /*
97 * Get the CPU possible mask string from sysfs.
98 *
99 * buf: the buffer where the mask will be read.
100 * max_bytes: the maximum number of bytes to write in the buffer.
101 *
102 * Returns the number of bytes read or -1 on error.
103 */
104 int get_possible_cpu_mask_from_sysfs(char *buf, size_t max_bytes)
105 {
106 ssize_t bytes_read = 0;
107 size_t total_bytes_read = 0;
108 int fd = -1, ret = -1;
109
110 if (buf == NULL)
111 goto end;
112
113 fd = open("/sys/devices/system/cpu/possible", O_RDONLY);
114 if (fd < 0)
115 goto end;
116
117 do {
118 bytes_read = read(fd, buf + total_bytes_read,
119 max_bytes - total_bytes_read);
120
121 if (bytes_read < 0) {
122 if (errno == EINTR) {
123 continue; /* retry operation */
124 } else {
125 goto end;
126 }
127 }
128
129 total_bytes_read += bytes_read;
130 assert(total_bytes_read <= max_bytes);
131 } while (max_bytes > total_bytes_read && bytes_read > 0);
132
133 /*
134 * Make sure the mask read is a null terminated string.
135 */
136 if (total_bytes_read < max_bytes)
137 buf[total_bytes_read] = '\0';
138 else
139 buf[max_bytes - 1] = '\0';
140
141 if (total_bytes_read > INT_MAX)
142 goto end;
143 ret = (int) total_bytes_read;
144 end:
145 if (fd >= 0 && close(fd) < 0)
146 PERROR("close");
147 return ret;
148 }
149
150 /*
151 * Get the number of CPUs from the possible cpu mask.
152 *
153 * pmask: the mask to parse.
154 * len: the len of the mask excluding '\0'.
155 *
156 * Returns the number of possible CPUs from the mask or 0 on error.
157 */
158 int get_num_possible_cpus_from_mask(const char *pmask, size_t len)
159 {
160 ssize_t i;
161 unsigned long cpu_index;
162 char *endptr;
163
164 /* We need at least one char to read */
165 if (len < 1)
166 goto error;
167
168 /* Start from the end to read the last CPU index. */
169 for (i = len - 1; i > 0; i--) {
170 /* Break when we hit the first separator. */
171 if ((pmask[i] == ',') || (pmask[i] == '-')) {
172 i++;
173 break;
174 }
175 }
176
177 cpu_index = strtoul(&pmask[i], &endptr, 10);
178
179 /*
180 * If we read a CPU index, increment it by one to return a number of
181 * CPUs.
182 */
183 if ((&pmask[i] != endptr) && (cpu_index < INT_MAX))
184 return (int) cpu_index + 1;
185
186 error:
187 return 0;
188 }
189
190 static void _get_num_possible_cpus(void)
191 {
192 int ret;
193 char buf[LTTNG_UST_CPUMASK_SIZE];
194
195 /* Get the possible cpu mask from sysfs, fallback to sysconf. */
196 ret = get_possible_cpu_mask_from_sysfs((char *) &buf, LTTNG_UST_CPUMASK_SIZE);
197 if (ret <= 0)
198 goto fallback;
199
200 /* Parse the possible cpu mask, on failure fallback to sysconf. */
201 ret = get_num_possible_cpus_from_mask((char *) &buf, ret);
202 if (ret > 0)
203 goto end;
204
205 fallback:
206 /* Fallback to sysconf. */
207 ret = get_num_possible_cpus_fallback();
208
209 end:
210 /* If all methods failed, don't store the value. */
211 if (ret < 1)
212 return;
213
214 num_possible_cpus_cache = ret;
215 }
216
217 /*
218 * Returns the total number of CPUs in the system. If the cache is not yet
219 * initialized, get the value from "/sys/devices/system/cpu/possible" or
220 * fallback to sysconf and cache it.
221 *
222 * If all methods fail, don't populate the cache and return 0.
223 */
224 int num_possible_cpus(void)
225 {
226 if (caa_unlikely(!num_possible_cpus_cache))
227 _get_num_possible_cpus();
228
229 return num_possible_cpus_cache;
230 }
This page took 0.032797 seconds and 4 git commands to generate.