]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
text-utils: add bits command
authorRobin Jarry <robin@jarry.cc>
Mon, 6 May 2024 21:45:21 +0000 (23:45 +0200)
committerRobin Jarry <robin@jarry.cc>
Thu, 24 Oct 2024 21:55:40 +0000 (23:55 +0200)
Add a new text utility to convert bit masks in various formats.

This can be handy to avoid parsing affinity masks in one's head and/or
to interact with the kernel in a more human friendly way. It is
a rewrite in C of the bits command from my linux-tools python package so
that it can be more widely available.

Here is an example:

 ~# cat /sys/kernel/debug/tracing/tracing_cpumask
 ffffffff,ffffffff,ffffffff,ffffffff
 ~# bits -l ,$(cat /sys/kernel/debug/tracing/tracing_cpumask)
 0-128
 ~# bits -g 58,59,120,123
 9000000,00000000,0c000000,00000000
 ~# bits -g 58,59,120,123 > /sys/kernel/debug/tracing/tracing_cpumask
 ~# echo 1 > /sys/kernel/debug/tracing/tracing_on

Add man page and basic tests.

Link: https://git.sr.ht/~rjarry/linux-tools#bits
Signed-off-by: Robin Jarry <robin@jarry.cc>
28 files changed:
.gitignore
bash-completion/bits [new file with mode: 0644]
configure.ac
meson.build
meson_options.txt
tests/commands.sh
tests/expected/misc/bits [new file with mode: 0644]
tests/expected/misc/bits-and [new file with mode: 0644]
tests/expected/misc/bits-binary [new file with mode: 0644]
tests/expected/misc/bits-default [new file with mode: 0644]
tests/expected/misc/bits-grouped-mask [new file with mode: 0644]
tests/expected/misc/bits-list [new file with mode: 0644]
tests/expected/misc/bits-mask [new file with mode: 0644]
tests/expected/misc/bits-not [new file with mode: 0644]
tests/expected/misc/bits-or [new file with mode: 0644]
tests/expected/misc/bits-parse-grouped-mask [new file with mode: 0644]
tests/expected/misc/bits-parse-mask [new file with mode: 0644]
tests/expected/misc/bits-parse-range [new file with mode: 0644]
tests/expected/misc/bits-stdin [new file with mode: 0644]
tests/expected/misc/bits-truncate [new file with mode: 0644]
tests/expected/misc/bits-width [new file with mode: 0644]
tests/expected/misc/bits-width-truncate [new file with mode: 0644]
tests/expected/misc/bits-xor [new file with mode: 0644]
tests/ts/misc/bits [new file with mode: 0755]
text-utils/Makemodule.am
text-utils/bits.1.adoc [new file with mode: 0644]
text-utils/bits.c [new file with mode: 0644]
text-utils/meson.build

index 316f3cdcc76c15abd67b85296d77630c3dd98cc8..af5a1184472e82342f70cb7a1407b74e5ee1ffb6 100644 (file)
@@ -76,6 +76,7 @@ ylwrap
 #
 /addpart
 /agetty
+/bits
 /build*/
 /blkdiscard
 /blkid
diff --git a/bash-completion/bits b/bash-completion/bits
new file mode 100644 (file)
index 0000000..786be4d
--- /dev/null
@@ -0,0 +1,21 @@
+_bits_module()
+{
+       local cur prev OPTS
+       COMPREPLY=()
+       cur="${COMP_WORDS[COMP_CWORD]}"
+       prev="${COMP_WORDS[COMP_CWORD-1]}"
+       case $prev in
+       '-h'|'--help'|'-V'|'--version')
+               return 0
+               ;;
+       esac
+       case $cur in
+       -*)
+               OPTS="--version --help --width --mask --grouped-mask --bit --list"
+               COMPREPLY=( $(compgen -W "${OPTS[*]}" -- $cur) )
+               return 0
+               ;;
+       esac
+       return 0
+}
+complete -F _bits_module bits
index 96519e08820438ce4edd427a47bd86aaf713712a..6bfdc62d2bbaaa2c0756d5dea5a1ba932c1cd834 100644 (file)
@@ -2251,6 +2251,9 @@ UL_REQUIRES_HAVE([scriptlive], [pty], [openpty function (libutil)])
 AM_CONDITIONAL([BUILD_SCRIPTLIVE], [test "x$build_scriptlive" = xyes])
 
 
+UL_BUILD_INIT([bits], [yes])
+AM_CONDITIONAL([BUILD_BITS], [test "x$build_bits" = xyes])
+
 UL_BUILD_INIT([col], [check])
 UL_REQUIRES_COMPILE([col], [#include <limits.h>], [__GLIBC__], [building for glibc])
 AM_CONDITIONAL([BUILD_COL], [test "x$build_col" = xyes])
index 71f88a163aa26bbdcfebc80f85e3ee0b6638bc1c..4667544a2a01e145d04d57fd1dccde54405d7379 100644 (file)
@@ -1199,6 +1199,20 @@ endif
 
 ############################################################
 
+opt = not get_option('build-bits').disabled()
+exe = executable(
+  'bits',
+  bits_sources,
+  include_directories : includes,
+  link_with : lib_common,
+  install_dir : usrbin_exec_dir,
+  install : true)
+if opt and not is_disabler(exe)
+exes += exe
+manadocs += ['text-utils/bits.1.adoc']
+bashcompletions += ['bits']
+endif
+
 opt = not get_option('build-col').require(is_glibc).disabled()
 exe = executable(
   'col',
index df2d13eaa40db57fefa5298d8ea085eadbb4eb9c..2c7b2d1c08ab83ba6c79e452a3c8854cecc16e37 100644 (file)
@@ -75,6 +75,8 @@ option('build-script', type : 'feature',
        description : 'build script')
 option('build-scriptutils', type : 'feature',
        description : 'build scriptutils')
+option('build-bits', type : 'feature',
+       description : 'build bits')
 option('build-col', type : 'feature',
        description : 'build col')
 option('build-colcrt', type : 'feature',
index b30551f0154d196fb8011775e556bffc34d12971..b79d7c650a8b9717aae821973e0d0ee55cf5004c 100644 (file)
@@ -66,6 +66,7 @@ TS_HELPER_TIMEUTILS="${ts_helpersdir}test_timeutils"
 # paths to commands
 TS_CMD_ADDPART=${TS_CMD_ADDPART:-"${ts_commandsdir}addpart"}
 TS_CMD_DELPART=${TS_CMD_DELPART:-"${ts_commandsdir}delpart"}
+TS_CMD_BITS=${TS_CMD_BITS-"${ts_commandsdir}bits"}
 TS_CMD_BLKDISCARD=${TS_CMD_BLKID-"${ts_commandsdir}blkdiscard"}
 TS_CMD_BLKID=${TS_CMD_BLKID-"${ts_commandsdir}blkid"}
 TS_CMD_CAL=${TS_CMD_CAL-"${ts_commandsdir}cal"}
diff --git a/tests/expected/misc/bits b/tests/expected/misc/bits
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/expected/misc/bits-and b/tests/expected/misc/bits-and
new file mode 100644 (file)
index 0000000..1d8ffee
--- /dev/null
@@ -0,0 +1 @@
+75-100
diff --git a/tests/expected/misc/bits-binary b/tests/expected/misc/bits-binary
new file mode 100644 (file)
index 0000000..ba7b220
--- /dev/null
@@ -0,0 +1 @@
+0b1_0000_0000_0010_0000_0000_0100_0000_0000_1000_0000_0000
diff --git a/tests/expected/misc/bits-default b/tests/expected/misc/bits-default
new file mode 100644 (file)
index 0000000..a56502d
--- /dev/null
@@ -0,0 +1 @@
+0x100200400800
diff --git a/tests/expected/misc/bits-grouped-mask b/tests/expected/misc/bits-grouped-mask
new file mode 100644 (file)
index 0000000..427fc5c
--- /dev/null
@@ -0,0 +1 @@
+1002,00400800
diff --git a/tests/expected/misc/bits-list b/tests/expected/misc/bits-list
new file mode 100644 (file)
index 0000000..7511e53
--- /dev/null
@@ -0,0 +1 @@
+11,22,33,44
diff --git a/tests/expected/misc/bits-mask b/tests/expected/misc/bits-mask
new file mode 100644 (file)
index 0000000..a56502d
--- /dev/null
@@ -0,0 +1 @@
+0x100200400800
diff --git a/tests/expected/misc/bits-not b/tests/expected/misc/bits-not
new file mode 100644 (file)
index 0000000..2487fcf
--- /dev/null
@@ -0,0 +1 @@
+50-74
diff --git a/tests/expected/misc/bits-or b/tests/expected/misc/bits-or
new file mode 100644 (file)
index 0000000..753370a
--- /dev/null
@@ -0,0 +1 @@
+50-150
diff --git a/tests/expected/misc/bits-parse-grouped-mask b/tests/expected/misc/bits-parse-grouped-mask
new file mode 100644 (file)
index 0000000..3ed39af
--- /dev/null
@@ -0,0 +1 @@
+58,59,120,123
diff --git a/tests/expected/misc/bits-parse-mask b/tests/expected/misc/bits-parse-mask
new file mode 100644 (file)
index 0000000..59dd4b4
--- /dev/null
@@ -0,0 +1 @@
+1,3,6,7,9,11,14-16,18,19,21,23-25,27
diff --git a/tests/expected/misc/bits-parse-range b/tests/expected/misc/bits-parse-range
new file mode 100644 (file)
index 0000000..5afeb03
--- /dev/null
@@ -0,0 +1 @@
+7fffff,ffffffff,ffffffff,fffc0000,00000000
diff --git a/tests/expected/misc/bits-stdin b/tests/expected/misc/bits-stdin
new file mode 100644 (file)
index 0000000..00ff99b
--- /dev/null
@@ -0,0 +1 @@
+11,33,44
diff --git a/tests/expected/misc/bits-truncate b/tests/expected/misc/bits-truncate
new file mode 100644 (file)
index 0000000..d00491f
--- /dev/null
@@ -0,0 +1 @@
+1
diff --git a/tests/expected/misc/bits-width b/tests/expected/misc/bits-width
new file mode 100644 (file)
index 0000000..5caff40
--- /dev/null
@@ -0,0 +1 @@
+10000
diff --git a/tests/expected/misc/bits-width-truncate b/tests/expected/misc/bits-width-truncate
new file mode 100644 (file)
index 0000000..268a4e1
--- /dev/null
@@ -0,0 +1 @@
+11,22
diff --git a/tests/expected/misc/bits-xor b/tests/expected/misc/bits-xor
new file mode 100644 (file)
index 0000000..5528943
--- /dev/null
@@ -0,0 +1 @@
+50-74,101-150
diff --git a/tests/ts/misc/bits b/tests/ts/misc/bits
new file mode 100755 (executable)
index 0000000..57db84c
--- /dev/null
@@ -0,0 +1,89 @@
+#!/bin/bash
+
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# Copyright (c) 2024 Robin Jarry
+#
+TS_TOPDIR="${0%/*}/../.."
+TS_DESC="bits"
+
+. "$TS_TOPDIR"/functions.sh
+ts_init "$*"
+
+ts_check_test_command "$TS_CMD_BITS"
+ts_cd "$TS_OUTDIR"
+
+ts_init_subtest "default"
+$TS_CMD_BITS 11,22,33,44 >> $TS_OUTPUT 2>> $TS_ERRLOG
+ts_finalize_subtest
+
+ts_init_subtest "mask"
+$TS_CMD_BITS --mask 11,22,33,44 >> $TS_OUTPUT 2>> $TS_ERRLOG
+ts_finalize_subtest
+
+ts_init_subtest "grouped-mask"
+$TS_CMD_BITS --grouped-mask 11,22,33,44 >> $TS_OUTPUT 2>> $TS_ERRLOG
+ts_finalize_subtest
+
+ts_init_subtest "list"
+$TS_CMD_BITS --list 11,22,33,44 >> $TS_OUTPUT 2>> $TS_ERRLOG
+ts_finalize_subtest
+
+ts_init_subtest "binary"
+$TS_CMD_BITS --binary 11,22,33,44 >> $TS_OUTPUT 2>> $TS_ERRLOG
+ts_finalize_subtest
+
+ts_init_subtest "truncate"
+$TS_CMD_BITS -l 1,10000 >> $TS_OUTPUT 2>> $TS_ERRLOG
+ts_finalize_subtest
+
+ts_init_subtest "width"
+$TS_CMD_BITS -w 16384 -l 10000  >> $TS_OUTPUT 2>> $TS_ERRLOG
+ts_finalize_subtest
+
+ts_init_subtest "width-truncate"
+$TS_CMD_BITS -w 32 -l 11,22,33,44 >> $TS_OUTPUT 2>> $TS_ERRLOG
+ts_finalize_subtest
+
+ts_init_subtest "parse-mask"
+$TS_CMD_BITS -l 0x0badcaca >> $TS_OUTPUT 2>> $TS_ERRLOG
+ts_finalize_subtest
+
+ts_init_subtest "parse-range"
+$TS_CMD_BITS -g 50-100 75-150 >> $TS_OUTPUT 2>> $TS_ERRLOG
+ts_finalize_subtest
+
+ts_init_subtest "parse-grouped-mask"
+$TS_CMD_BITS -l ,9000000,00000000,0c000000,00000000 >> $TS_OUTPUT 2>> $TS_ERRLOG
+ts_finalize_subtest
+
+ts_init_subtest "or"
+$TS_CMD_BITS -l 50-100 '|75-150' >> $TS_OUTPUT 2>> $TS_ERRLOG
+ts_finalize_subtest
+
+ts_init_subtest "and"
+$TS_CMD_BITS -l 50-100 '&75-150' >> $TS_OUTPUT 2>> $TS_ERRLOG
+ts_finalize_subtest
+
+ts_init_subtest "xor"
+$TS_CMD_BITS -l 50-100 '^75-150' >> $TS_OUTPUT 2>> $TS_ERRLOG
+ts_finalize_subtest
+
+ts_init_subtest "not"
+$TS_CMD_BITS -l 50-100 '~75-150' >> $TS_OUTPUT 2>> $TS_ERRLOG
+ts_finalize_subtest
+
+ts_init_subtest "stdin"
+{
+       echo 11,22,33,44
+       echo ^22
+} | $TS_CMD_BITS --list >> $TS_OUTPUT 2>> $TS_ERRLOG
+ts_finalize_subtest
+
+ts_finalize
index af1bf02386647ef2046e84c06a98df83d72caf9a..b135e3a028199929255f54c116d0226e5dcbc1dd 100644 (file)
@@ -1,3 +1,11 @@
+if BUILD_BITS
+usrbin_exec_PROGRAMS += bits
+MANPAGES += text-utils/bits.1
+dist_noinst_DATA += text-utils/bits.1.adoc
+bits_SOURCES = text-utils/bits.c
+bits_LDADD = $(LDADD) libcommon.la
+endif
+
 if BUILD_COL
 usrbin_exec_PROGRAMS += col
 MANPAGES += text-utils/col.1
@@ -105,4 +113,3 @@ test_more_CFLAGS = -DTEST_PROGRAM $(more_CFLAGS)
 test_more_LDADD = $(more_LDADD)
 
 endif # BUILD_MORE
-
diff --git a/text-utils/bits.1.adoc b/text-utils/bits.1.adoc
new file mode 100644 (file)
index 0000000..8501bd1
--- /dev/null
@@ -0,0 +1,133 @@
+//po4a: entry man manual
+////
+
+SPDX-License-Identifier: GPL-2.0-or-later
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+Copyright (c) 2024 Robin Jarry
+
+////
+= bits(1)
+:doctype: manpage
+:man manual: User Commands
+:man source: util-linux {release-version}
+:page-layout: base
+:command: bits
+
+== NAME
+
+bits - convert bit masks from/to various formats
+
+== SYNOPSIS
+
+*bits* [*-h*] [*-V*] [*-w* _<NUM>_] [_<MODE>_] [_<MASK_OR_LIST>_...]
+
+== DESCRIPTION
+
+The *bits* utility converts bit masks into various formats. It supports
+combining multiple masks together using bitwise operations.
+
+== POSITIONAL ARGUMENTS
+
+_<MASK_OR_LIST>_::
+A set of bits specified as a hexadecimal mask value (e.g. _0xeec2_) or as
+a comma-separated list of bit IDs.
+
+If no argument is specified, the sets of bits will be read from standard input;
+one group per line.
+
+Consecutive ids can be compressed as ranges (e.g. _5,6,7,8,9,10_ -> _5-10_).
+
+Optionally, if an argument starts with a comma, it will be parsed as a single
+hexadecimal mask split in 32bit groups (e.g. _,00014000,00000000,00020000_ ->
+_17,78,80_).
+
+By default all groups will be OR'ed together. If a group has one of the
+following prefixes, it will be combined with the resulting mask using
+a different binary operation:
+
+**&**__<MASK_OR_LIST>__::
+The group will be combined with a binary AND operation. I.e. all bits that are
+set to 1 in the group AND the combined groups so far will be preserved to 1.
+All other bits will be reset to 0.
+
+**^**__<MASK_OR_LIST>__::
+The group will be combined with a binary XOR operation. I.e. all bits that are
+set to 1 in the group AND to 0 the combined groups so far (or the other way
+around) will be set to 1. Bits that are both to 1 or both to 0 will be reset to
+0.
+
+**~**__<MASK_OR_LIST>__::
+All bits set to 1 in the group will be cleared (reset to 0) in the combined
+groups so far.
+
+== OPTIONS
+
+include::man-common/help-version.adoc[]
+
+*-w* __<NUM>__, *--width* __<NUM>__::
+Maximum number of bits in the masks handled by *bits* (default __8192__). Any
+bit larger than this number will be truncated.
+
+== CONVERSION MODE
+
+One of the following conversion modes can be specified. If not specified, it
+defaults to *-m*, *--mask*.
+
+*-m*, *--mask*::
+Print the combined args as a hexadecimal mask value (default).
+
+*-g*, *--grouped-mask*::
+Print the combined args as a hexadecimal mask value in 32bit comma separated
+groups.
+
+*-b*, *--binary*::
+Print the combined args as a binary mask value.
+
+*-l*, *--list*::
+Print the combined args as a list of bit IDs. Consecutive IDs are compressed as
+ranges.
+
+== EXAMPLES
+
+....
+~$ bits --mask 4,5-8 16,30
+0x400101f0
+
+~$ bits --list 0xeec2
+1,6,7,9-11,13-15
+
+~$ bits --binary 4,5-8 16,30
+0b100_0000_0000_0001_0000_0001_1111_0000
+
+~$ bits --list ,00300000,03000000,30000003
+0,1,28,29,56,57,84,85
+
+~$ bits --list 1,2,3,4 ~3-10
+1,2
+
+~$ bits --list 1,2,3,4 ^3-10
+1,2,5-10
+
+~$ bits --grouped-mask 2,22,74,79
+8400,00000000,00400004
+
+~$ bits --width 64 --list 2,22,74,79
+2,22
+....
+
+== AUTHORS
+
+Robin Jarry.
+
+include::man-common/bugreports.adoc[]
+
+include::man-common/footer.adoc[]
+
+ifdef::translation[]
+include::man-common/translation.adoc[]
+endif::[]
diff --git a/text-utils/bits.c b/text-utils/bits.c
new file mode 100644 (file)
index 0000000..62f3913
--- /dev/null
@@ -0,0 +1,330 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Copyright (c) 2024 Robin Jarry
+ *
+ * bits - convert bit masks from/to various formats
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <sched.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "c.h"
+#include "closestream.h"
+#include "cpuset.h"
+#include "nls.h"
+#include "strutils.h"
+#include "strv.h"
+#include "xalloc.h"
+
+static void parse_mask_or_list(const char *cmdline_arg,
+               cpu_set_t *all_bits, size_t width)
+{
+       cpu_set_t *bits, *copy;
+       char bitwise_op = '|';
+       const char *arg;
+       size_t size, n;
+
+       arg = cmdline_arg;
+
+       /* strip optional operator first */
+       if (startswith(arg, "&")) {
+               bitwise_op = '&';
+               arg++;
+       } else if (startswith(arg, "^")) {
+               bitwise_op = '^';
+               arg++;
+       } else if (startswith(arg, "~")) {
+               bitwise_op = '~';
+               arg++;
+       } else if (startswith(arg, "|")) {
+               arg++;
+       }
+
+       bits = cpuset_alloc(width, &size, NULL);
+       if (bits == NULL)
+               errx(EXIT_FAILURE, _("error: cannot allocate bit mask"));
+
+       if (startswith(arg, ",") || startswith(arg, "0x")) {
+               if (startswith(arg, ","))
+                       arg++;
+               if (cpumask_parse(arg, bits, size) < 0)
+                       errx(EXIT_FAILURE, _("error: invalid bit mask: %s"), cmdline_arg);
+       } else {
+               if (cpulist_parse(arg, bits, size, 1) < 0)
+                       errx(EXIT_FAILURE, _("error: invalid bit list: %s"), cmdline_arg);
+       }
+
+       /* truncate all bits beyond the requested mask size */
+       for (n = cpuset_nbits(size) - 1; n >= width; n--)
+               CPU_CLR_S(n, size, bits);
+
+       copy = cpuset_alloc(width, &size, NULL);
+       if (copy == NULL)
+               errx(EXIT_FAILURE, _("error: cannot allocate bit mask"));
+       memcpy(copy, all_bits, size);
+
+       switch (bitwise_op) {
+       case '&':
+               CPU_AND_S(size, all_bits, copy, bits);
+               break;
+       case '|':
+               CPU_OR_S(size, all_bits, copy, bits);
+               break;
+       case '^':
+               CPU_XOR_S(size, all_bits, copy, bits);
+               break;
+       case '~':
+               for (n = 0; n < width; n++) {
+                       if (CPU_ISSET_S(n, size, bits))
+                               CPU_CLR_S(n, size, all_bits);
+               }
+               break;
+       }
+
+       cpuset_free(bits);
+       cpuset_free(copy);
+}
+
+static size_t num_digits(size_t value)
+{
+       size_t digits = 1;
+       while (value > 9) {
+               digits++;
+               value /= 10;
+       }
+       return digits;
+}
+
+enum output_mode {
+       MODE_BINARY,
+       MODE_GROUPED_MASK,
+       MODE_LIST,
+       MODE_MASK,
+};
+
+static void print_bits(cpu_set_t *bits, size_t width, enum output_mode mode)
+{
+       const size_t size = CPU_ALLOC_SIZE(width);
+       bool started = false;
+       char *buf = NULL;
+       size_t buf_size;
+       ssize_t n = 0;
+
+       if (CPU_COUNT_S(size, bits) == 0) {
+               switch (mode) {
+               case MODE_MASK:
+                       printf("0x0\n");
+                       break;
+               case MODE_GROUPED_MASK:
+                       printf("0\n");
+                       break;
+               case MODE_BINARY:
+                       printf("0b0\n");
+                       break;
+               case MODE_LIST:
+                       break;
+               }
+               return;
+       }
+
+       switch (mode) {
+       case MODE_MASK:
+               /* fit 4 bits per character plus terminating nul byte */
+               buf_size = ((cpuset_nbits(size) + 3) / 4) + 1;
+               buf = xmalloc(buf_size);
+               cpumask_create(buf, buf_size, bits, size);
+
+               /* strip leading zeroes */
+               while (buf[n] == '0')
+                       n++;
+               printf("0x%s\n", buf + n);
+               break;
+
+       case MODE_GROUPED_MASK:
+               /* fit 4 bits per character plus terminating nul byte */
+               buf_size = ((cpuset_nbits(size) + 3) / 4) + 1;
+               buf = xmalloc(buf_size);
+               cpumask_create(buf, buf_size, bits, size);
+
+               /* strip leading zeroes */
+               while (buf[n] == '0')
+                       n++;
+
+               while (buf[n] != '\0') {
+                       if (started && (n % 8) == 0)
+                               printf(",");
+                       if (buf[n] != '0')
+                               started = true;
+                       printf("%c", buf[n]);
+                       n++;
+               }
+               printf("\n");
+               break;
+
+       case MODE_BINARY:
+               printf("0b");
+               for (n = width - 1; n >= 0; n--) {
+                       if (started && ((n + 1) % 4) == 0)
+                               printf("_");
+                       if (CPU_ISSET_S(n, size, bits)) {
+                               started = true;
+                               printf("1");
+                       } else if (started) {
+                               printf("0");
+                       }
+               }
+               printf("\n");
+               break;
+
+       case MODE_LIST:
+               /* Maximum number of digits (larger bit number) plus 1
+                * to account for a separating comma, times the number of bits
+                * set to 1. */
+               buf_size = (num_digits(width - 1) + 1) * CPU_COUNT_S(size, bits);
+               buf = xmalloc(buf_size);
+               cpulist_create(buf, buf_size, bits, size);
+               printf("%s\n", buf);
+               break;
+       }
+
+       free(buf);
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+       fputs(USAGE_HEADER, stdout);
+       fprintf(stdout, _(" %s [options] [<mask_or_list>...]\n"),
+               program_invocation_short_name);
+
+       fputs(USAGE_SEPARATOR, stdout);
+       fputsln(_("Convert bit masks from/to various formats."), stdout);
+
+       fputs(USAGE_ARGUMENTS, stdout);
+       fputsln(_(" <mask_or_list>      A set of bits specified as a hex mask value (e.g. 0xeec2)\n"
+                 "                     or as a comma-separated list of bit IDs."),
+               stdout);
+       fputs(USAGE_SEPARATOR, stdout);
+       fputsln(_("                     If not specified, arguments will be read from stdin."),
+               stdout);
+
+       fputs(USAGE_OPTIONS, stdout);
+       fprintf(stdout, USAGE_HELP_OPTIONS(21));
+       fputsln(_(" -w <num>, --width <num>\n"
+                 "                     Maximum width of bit masks handled by this tool (default 8192)."),
+               stdout);
+
+       fputs(_("\nMode:\n"), stdout);
+       fputsln(_(" -m, --mask          Print the combined args as a hex mask value (default)."),
+               stdout);
+       fputsln(_(" -g, --grouped-mask  Print the combined args as a hex mask value in 32bit\n"
+                 "                     comma separated groups."), stdout);
+       fputsln(_(" -b, --binary        Print the combined args as a binary mask value."),
+               stdout);
+       fputsln(_(" -l, --list          Print the combined args as a compressed list of bit IDs."),
+               stdout);
+
+       fprintf(stdout, USAGE_MAN_TAIL("bits(1)"));
+       exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char **argv)
+{
+       enum output_mode mode = MODE_MASK;
+       char **stdin_lines = NULL;
+       cpu_set_t *bits = NULL;
+       size_t width = 8192;
+       size_t alloc_size;
+       int c;
+
+#define FLAGS "Vhw:mgbl"
+       static const struct option longopts[] = {
+               { "version",      no_argument,       NULL, 'V' },
+               { "help",         no_argument,       NULL, 'h' },
+               { "width",        required_argument, NULL, 'w' },
+               { "mask",         no_argument,       NULL, 'm' },
+               { "grouped-mask", no_argument,       NULL, 'g' },
+               { "binary",       no_argument,       NULL, 'b' },
+               { "list",         no_argument,       NULL, 'l' },
+               { NULL,           0,                 NULL,  0  }
+       };
+
+       setlocale(LC_ALL, "");
+       bindtextdomain(PACKAGE, LOCALEDIR);
+       textdomain(PACKAGE);
+       close_stdout_atexit();
+
+       while ((c = getopt_long(argc, argv, FLAGS, longopts, NULL)) != -1) {
+               switch (c) {
+               case 'm':
+                       mode = MODE_MASK;
+                       break;
+               case 'g':
+                       mode = MODE_GROUPED_MASK;
+                       break;
+               case 'b':
+                       mode = MODE_BINARY;
+                       break;
+               case 'l':
+                       mode = MODE_LIST;
+                       break;
+               case 'w':
+                       /* allow up to 128k masks */
+                       width = str2unum_or_err(optarg,
+                               10, "invalid --width", 128 * 1024);
+                       break;
+               case 'V':
+                       print_version(EXIT_SUCCESS);
+               case 'h':
+                       usage();
+               default:
+                       errtryhelp(EXIT_FAILURE);
+               }
+       }
+
+       argc -= optind;
+       argv += optind;
+       if (argc == 0) {
+               /* no arguments provided, read lines from stdin */
+               char buf[LINE_MAX];
+
+               while (fgets(buf, sizeof(buf), stdin)) {
+                       /* strip LF, CR, CRLF, LFCR */
+                       rtrim_whitespace((unsigned char *)buf);
+                       if (strv_push(&stdin_lines, xstrdup(buf)) < 0)
+                               errx(EXIT_FAILURE, _("cannot allocate memory"));
+               }
+
+               argc = strv_length(stdin_lines);
+               argv = stdin_lines;
+       }
+
+       bits = cpuset_alloc(width, &alloc_size, NULL);
+       if (bits == NULL)
+               errx(EXIT_FAILURE, _("cannot allocate memory"));
+
+       /* start off with all bits set to 0 */
+       memset(bits, 0, alloc_size);
+
+       for (; argc > 0; argc--, argv++)
+               parse_mask_or_list(*argv, bits, width);
+
+       strv_free(stdin_lines);
+
+       print_bits(bits, width, mode);
+
+       cpuset_free(bits);
+
+       return EXIT_SUCCESS;
+}
index f3b25d382160b3a85d22095bbaf330a91f38a786..4a25fa4783281653b60ee0296fefcd6f25bf5a51 100644 (file)
@@ -1,3 +1,7 @@
+bits_sources = files(
+  'bits.c',
+)
+
 col_sources = files(
   'col.c',
 )