Coverage Report

Created: 2026-04-11 06:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/util-linux/lib/cpuset.c
Line
Count
Source
1
/*
2
 * SPDX-License-Identifier: LGPL-2.1-or-later
3
 *
4
 * Terminology:
5
 *
6
 *  cpuset  - (libc) cpu_set_t data structure represents set of CPUs
7
 *  cpumask - string with hex mask (e.g. "0x00000001")
8
 *  cpulist - string with CPU ranges (e.g. "0-3,5,7,8")
9
 *
10
 * Based on code from taskset.c and Linux kernel.
11
 *
12
 * This file may be redistributed under the terms of the
13
 * GNU Lesser General Public License.
14
 *
15
 * Copyright (C) 2010 Karel Zak <kzak@redhat.com>
16
 */
17
18
#include <stdio.h>
19
#include <stdlib.h>
20
#include <unistd.h>
21
#include <sched.h>
22
#include <errno.h>
23
#include <string.h>
24
#include <ctype.h>
25
#ifdef HAVE_SYS_SYSCALL_H
26
#include <sys/syscall.h>
27
#endif
28
29
#include "cpuset.h"
30
#include "c.h"
31
32
static inline int val_to_char(int v)
33
0
{
34
0
  if (v >= 0 && v < 10)
35
0
    return '0' + v;
36
0
  if (v >= 10 && v < 16)
37
0
    return ('a' - 10) + v;
38
0
  return -1;
39
0
}
40
41
static inline int char_to_val(int c)
42
0
{
43
0
  int cl;
44
45
0
  if (c >= '0' && c <= '9')
46
0
    return c - '0';
47
0
  cl = tolower(c);
48
0
  if (cl >= 'a' && cl <= 'f')
49
0
    return cl + (10 - 'a');
50
0
  return -1;
51
0
}
52
53
static const char *nexttoken(const char *q,  int sep)
54
0
{
55
0
  if (q)
56
0
    q = strchr(q, sep);
57
0
  if (q)
58
0
    q++;
59
0
  return q;
60
0
}
61
62
/*
63
 * Number of bits in a CPU bitmask on current system
64
 */
65
int get_max_number_of_cpus(void)
66
0
{
67
0
#ifdef SYS_sched_getaffinity
68
0
  int n, cpus = 2048;
69
0
  size_t setsize;
70
0
  cpu_set_t *set = cpuset_alloc(cpus, &setsize, NULL);
71
72
0
  if (!set)
73
0
    return -1; /* error */
74
75
0
  for (;;) {
76
0
    CPU_ZERO_S(setsize, set);
77
78
    /* the library version does not return size of cpumask_t */
79
0
    n = syscall(SYS_sched_getaffinity, 0, setsize, set);
80
81
0
    if (n < 0 && errno == EINVAL && cpus < 1024 * 1024) {
82
0
      cpuset_free(set);
83
0
      cpus *= 2;
84
0
      set = cpuset_alloc(cpus, &setsize, NULL);
85
0
      if (!set)
86
0
        return -1; /* error */
87
0
      continue;
88
0
    }
89
0
    cpuset_free(set);
90
0
    return n * 8;
91
0
  }
92
0
#endif
93
0
  return -1;
94
0
}
95
96
/*
97
 * Allocates a new set for ncpus and returns size in bytes and size in bits
98
 */
99
cpu_set_t *cpuset_alloc(int ncpus, size_t *setsize, size_t *nbits)
100
0
{
101
0
  cpu_set_t *set = NULL;
102
103
0
  if (ncpus)
104
0
    set = CPU_ALLOC(ncpus);
105
0
  if (!set)
106
0
    return NULL;
107
0
  if (setsize)
108
0
    *setsize = CPU_ALLOC_SIZE(ncpus);
109
0
  if (nbits)
110
0
    *nbits = cpuset_nbits(CPU_ALLOC_SIZE(ncpus));
111
0
  return set;
112
0
}
113
114
void cpuset_free(cpu_set_t *set)
115
0
{
116
0
  CPU_FREE(set);
117
0
}
118
119
#if !HAVE_DECL_CPU_ALLOC
120
/* Please, use CPU_COUNT_S() macro. This is fallback */
121
int __cpuset_count_s(size_t setsize, const cpu_set_t *set)
122
{
123
  int s = 0;
124
  const __cpu_mask *p = set->__bits;
125
  const __cpu_mask *end = &set->__bits[setsize / sizeof (__cpu_mask)];
126
127
  while (p < end) {
128
    __cpu_mask l = *p++;
129
130
    if (l == 0)
131
      continue;
132
# if LONG_BIT > 32
133
    l = (l & 0x5555555555555555ul) + ((l >> 1) & 0x5555555555555555ul);
134
    l = (l & 0x3333333333333333ul) + ((l >> 2) & 0x3333333333333333ul);
135
    l = (l & 0x0f0f0f0f0f0f0f0ful) + ((l >> 4) & 0x0f0f0f0f0f0f0f0ful);
136
    l = (l & 0x00ff00ff00ff00fful) + ((l >> 8) & 0x00ff00ff00ff00fful);
137
    l = (l & 0x0000ffff0000fffful) + ((l >> 16) & 0x0000ffff0000fffful);
138
    l = (l & 0x00000000fffffffful) + ((l >> 32) & 0x00000000fffffffful);
139
# else
140
    l = (l & 0x55555555ul) + ((l >> 1) & 0x55555555ul);
141
    l = (l & 0x33333333ul) + ((l >> 2) & 0x33333333ul);
142
    l = (l & 0x0f0f0f0ful) + ((l >> 4) & 0x0f0f0f0ful);
143
    l = (l & 0x00ff00fful) + ((l >> 8) & 0x00ff00fful);
144
    l = (l & 0x0000fffful) + ((l >> 16) & 0x0000fffful);
145
# endif
146
    s += l;
147
  }
148
  return s;
149
}
150
#endif
151
152
/*
153
 * Finds the first CPU present after the specified index.
154
 *
155
 * start: starting index, inclusive.
156
 * setsize: size of the set in *bytes*.
157
 * set: CPU set to search.
158
 *
159
 * Return: the index of the first CPU present in `set`, starting at `start`.
160
 * If no such CPU exists, returns the size of the set in *bits*.
161
 */
162
static size_t find_next_cpu(size_t start, size_t setsize, cpu_set_t *set)
163
0
{
164
0
  size_t nbits = cpuset_nbits(setsize);
165
0
  for (; start < nbits; start++)
166
0
    if (CPU_ISSET_S(start, setsize, set))
167
0
      return start;
168
0
  return start;
169
0
}
170
171
/*
172
 * Returns human readable representation of the cpuset. The output format is
173
 * a list of CPUs with ranges (for example, "0,1,3-9:3").
174
 */
175
char *cpulist_create(char *str, size_t len,
176
      cpu_set_t *set, size_t setsize)
177
0
{
178
0
  char *ptr = str;
179
0
  int entry_made = 0;
180
0
  size_t max = cpuset_nbits(setsize);
181
0
  size_t a = 0;  /* min for cpu range */
182
0
  size_t next = 0;  /* where to start looking for next cpu */
183
184
0
  while ((a = find_next_cpu(next, setsize, set)) < max) {
185
0
    int rlen;
186
0
    next = find_next_cpu(a + 1, setsize, set);
187
0
    if (next == max) {
188
0
      rlen = snprintf(ptr, len, "%zu,", a);
189
0
    } else {
190
      /* Extend range as long as we have the same stride. */
191
0
      size_t b = next;
192
0
      size_t s = b - a;
193
0
      while (((next = find_next_cpu(b + 1, setsize, set)) <
194
0
        max) && next - b == s) {
195
0
        b = next;
196
0
      }
197
0
      if (b - a == s) {
198
        /*
199
         * Only print one CPU.  Hope the next one can
200
         * be put in the next range.
201
         */
202
0
        rlen = snprintf(ptr, len, "%zu,", a);
203
0
        next = b;
204
0
      } else if (s == 1) {
205
0
        rlen = snprintf(ptr, len, "%zu-%zu,", a, b);
206
0
      } else {
207
0
        rlen = snprintf(ptr, len, "%zu-%zu:%zu,",
208
0
            a, b, s);
209
0
      }
210
0
    }
211
0
    if (rlen < 0 || (size_t) rlen >= len)
212
0
      return NULL;
213
0
    ptr += rlen;
214
0
    len -= rlen;
215
0
    entry_made = 1;
216
0
  }
217
0
  ptr -= entry_made;
218
0
  *ptr = '\0';
219
220
0
  return str;
221
0
}
222
223
/*
224
 * Returns string with CPU mask.
225
 */
226
char *cpumask_create(char *str, size_t len,
227
      cpu_set_t *set, size_t setsize)
228
0
{
229
0
  char *ptr = str;
230
0
  char *ret = NULL;
231
0
  int cpu;
232
233
0
  for (cpu = cpuset_nbits(setsize) - 4; cpu >= 0; cpu -= 4) {
234
0
    char val = 0;
235
236
0
    if (len == (size_t) (ptr - str))
237
0
      break;
238
239
0
    if (CPU_ISSET_S(cpu, setsize, set))
240
0
      val |= 1;
241
0
    if (CPU_ISSET_S(cpu + 1, setsize, set))
242
0
      val |= 2;
243
0
    if (CPU_ISSET_S(cpu + 2, setsize, set))
244
0
      val |= 4;
245
0
    if (CPU_ISSET_S(cpu + 3, setsize, set))
246
0
      val |= 8;
247
248
0
    if (!ret && val)
249
0
      ret = ptr;
250
0
    *ptr++ = val_to_char(val);
251
0
  }
252
0
  *ptr = '\0';
253
0
  return ret ? ret : ptr - 1;
254
0
}
255
256
/*
257
 * Parses string with CPUs mask.
258
 */
259
int cpumask_parse(const char *str, cpu_set_t *set, size_t setsize)
260
0
{
261
0
  int len = strlen(str);
262
0
  const char *ptr = str + len - 1;
263
0
  int cpu = 0;
264
265
  /* skip 0x, it's all hex anyway */
266
0
  if (len > 1 && !memcmp(str, "0x", 2L))
267
0
    str += 2;
268
269
0
  CPU_ZERO_S(setsize, set);
270
271
0
  while (ptr >= str) {
272
0
    char val;
273
274
    /* cpu masks in /sys uses comma as a separator */
275
0
    if (*ptr == ',')
276
0
      ptr--;
277
278
0
    val = char_to_val(*ptr);
279
0
    if (val == (char) -1)
280
0
      return -1;
281
0
    if (val & 1)
282
0
      CPU_SET_S(cpu, setsize, set);
283
0
    if (val & 2)
284
0
      CPU_SET_S(cpu + 1, setsize, set);
285
0
    if (val & 4)
286
0
      CPU_SET_S(cpu + 2, setsize, set);
287
0
    if (val & 8)
288
0
      CPU_SET_S(cpu + 3, setsize, set);
289
0
    ptr--;
290
0
    cpu += 4;
291
0
  }
292
293
0
  return 0;
294
0
}
295
296
static int nextnumber(const char *str, char **end, unsigned int *result)
297
0
{
298
0
  errno = 0;
299
0
  if (str == NULL || *str == '\0' || !isdigit(*str))
300
0
    return -EINVAL;
301
0
  *result = (unsigned int) strtoul(str, end, 10);
302
0
  if (errno)
303
0
    return -errno;
304
0
  if (str == *end)
305
0
    return -EINVAL;
306
0
  return 0;
307
0
}
308
309
/*
310
 * Parses string with list of CPU ranges.
311
 * Returns 0 on success.
312
 * Returns 1 on error.
313
 * Returns 2 if fail is set and a cpu number passed in the list doesn't fit
314
 * into the cpu_set. If fail is not set cpu numbers that do not fit are
315
 * ignored and 0 is returned instead.
316
 */
317
int cpulist_parse(const char *str, cpu_set_t *set, size_t setsize, int fail)
318
0
{
319
0
  const size_t max = cpuset_nbits(setsize);
320
0
  const char *p, *q;
321
0
  char *end = NULL;
322
323
0
  q = str;
324
0
  CPU_ZERO_S(setsize, set);
325
326
0
  while (p = q, q = nexttoken(q, ','), p) {
327
0
    unsigned int a; /* beginning of range */
328
0
    unsigned int b; /* end of range */
329
0
    unsigned int s; /* stride */
330
0
    const char *c1, *c2;
331
332
0
    if (nextnumber(p, &end, &a) != 0)
333
0
      return 1;
334
0
    b = a;
335
0
    s = 1;
336
0
    p = end;
337
338
0
    c1 = nexttoken(p, '-');
339
0
    c2 = nexttoken(p, ',');
340
341
0
    if (c1 != NULL && (c2 == NULL || c1 < c2)) {
342
0
      if (nextnumber(c1, &end, &b) != 0)
343
0
        return 1;
344
345
0
      c1 = end && *end ? nexttoken(end, ':') : NULL;
346
347
0
      if (c1 != NULL && (c2 == NULL || c1 < c2)) {
348
0
        if (nextnumber(c1, &end, &s) != 0)
349
0
          return 1;
350
0
        if (s == 0)
351
0
          return 1;
352
0
      }
353
0
    }
354
355
0
    if (!(a <= b))
356
0
      return 1;
357
0
    while (a <= b) {
358
0
      if (a >= max) {
359
0
        if (fail)
360
0
          return 2;
361
0
        else
362
0
          break;
363
0
      }
364
0
      CPU_SET_S(a, setsize, set);
365
0
      a += s;
366
0
    }
367
0
  }
368
369
0
  if (end && *end)
370
0
    return 1;
371
0
  return 0;
372
0
}
373
374
#ifdef TEST_PROGRAM_CPUSET
375
376
#include <getopt.h>
377
378
int main(int argc, char *argv[])
379
{
380
  cpu_set_t *set;
381
  size_t setsize, buflen, nbits;
382
  char *buf, *mask = NULL, *range = NULL;
383
  int ncpus = 2048, rc, c;
384
385
  static const struct option longopts[] = {
386
      { "ncpus", 1, NULL, 'n' },
387
      { "mask",  1, NULL, 'm' },
388
      { "range", 1, NULL, 'r' },
389
      { NULL,    0, NULL, 0 }
390
  };
391
392
  while ((c = getopt_long(argc, argv, "n:m:r:", longopts, NULL)) != -1) {
393
    switch(c) {
394
    case 'n':
395
      ncpus = atoi(optarg);
396
      break;
397
    case 'm':
398
      mask = strdup(optarg);
399
      break;
400
    case 'r':
401
      range = strdup(optarg);
402
      break;
403
    default:
404
      goto usage_err;
405
    }
406
  }
407
408
  if (!mask && !range)
409
    goto usage_err;
410
411
  set = cpuset_alloc(ncpus, &setsize, &nbits);
412
  if (!set)
413
    err(EXIT_FAILURE, "failed to allocate cpu set");
414
415
  /*
416
  fprintf(stderr, "ncpus: %d, cpuset bits: %zd, cpuset bytes: %zd\n",
417
      ncpus, nbits, setsize);
418
  */
419
420
  buflen = 7 * nbits;
421
  buf = malloc(buflen);
422
  if (!buf)
423
    err(EXIT_FAILURE, "failed to allocate cpu set buffer");
424
425
  if (mask)
426
    rc = cpumask_parse(mask, set, setsize);
427
  else
428
    rc = cpulist_parse(range, set, setsize, 0);
429
430
  if (rc)
431
    errx(EXIT_FAILURE, "failed to parse string: %s", mask ? : range);
432
433
  printf("%-15s = %15s ", mask ? : range,
434
        cpumask_create(buf, buflen, set, setsize));
435
  printf("[%s]\n", cpulist_create(buf, buflen, set, setsize));
436
437
  free(buf);
438
  free(mask);
439
  free(range);
440
  cpuset_free(set);
441
442
  return EXIT_SUCCESS;
443
444
usage_err:
445
  fprintf(stderr,
446
    "usage: %s [--ncpus <num>] --mask <mask> | --range <list>\n",
447
    program_invocation_short_name);
448
  exit(EXIT_FAILURE);
449
}
450
#endif