On 2026-10-08 19:00, Sohil Mehta wrote:
> 
> Note, the CPUID bit does not reflect whether the kernel enabled LASS;
> only the cpuinfo flag does. But parsing cpuinfo is cumbersome, and the
> kernel enables LASS whenever the CPU supports it unless the command line
> opts out (vsyscall=emulate or clearcpuid=lass). To keep it simple, rely
> on the CPUID bit and accept a failure in these unusual cases.
> 
Cumbersome, really?

Here is a set of parsing functions -- tested, even! -- that you can make
available as library routines. Integrating them into the selftests build
framework has been left as an exercise for the reader ;-)

Signed-off-by: H. Peter Anvin (Rosaic) <[email protected]>

        -hpa
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define likely(x)	__builtin_expect(!!(x), 1)
#define unlikely(x)	__builtin_expect(!!(x), 0)

static char *read_cpuinfo(void)
{
	const size_t bufstep = 64*1024;
	size_t bufsize = 0;
	size_t len = 0;
	ssize_t rlen;
	char *buf = NULL;
	char *newbuf;
	int fd;
	int err;
	char *p;

	fd = open("/proc/cpuinfo", O_RDONLY);
	if (fd < 0)
		return NULL;

	do {
		if (len >= bufsize) {
			bufsize += bufstep;
			newbuf = realloc(buf, bufsize);
			if (!newbuf)
				goto error;
			buf = newbuf;
		}

		rlen = read(fd, buf + len, bufsize - len);
		if (rlen < 0) {
			if (errno == EINTR || errno == EAGAIN)
				continue;
			else
				goto error;
		}

		len += rlen;
	} while (rlen);

	/*
	 * There should always be a blank line after each CPU,
	 * including at the end.
	 */
	if (len < 2 || memcmp(buf + len - 2, "\n\n", 2)) {
		errno = EPROTO;
		goto error;
	}

	close(fd);

	/*
	 * buf will have at least one free byte at the end. As a result:
	 * - Each processor data block is terminated by an empty string.
	 * - The entire file is terminated by two empty strings in a row.
	 */
	buf[len] = '\0';

	/* Make each line a separate string */
	for (p = buf; p < buf + len; p++) {
		if (*p == '\n')
			*p = '\0';
	}

	newbuf = realloc(buf, len+1);
	if (newbuf)
		buf = newbuf;

	return buf;

error:
	err = errno;
	close(fd);
	free(buf);
	errno = err;
	return NULL;
}

const char *get_cpuinfo(void)
{
	static char *cpuinfo;

	if (unlikely(!cpuinfo))
		cpuinfo = read_cpuinfo();

	return cpuinfo;
}

/*
 * Sets *value to the contents of the field for a specific CPU or NULL.
 * Returns 0 on success or:
 *	-errno	- any file I/O error
 *	-EPROTO	- parsing error
 *	-ESRCH	- no such CPU
 *	-ENOMSG	- no such tag
 */
int cpuinfo_get_field(const char **value, const char *field, unsigned long cpu)
{
	static const char processor_prefix[] = "processor\t: ";
	const size_t pfxlen = sizeof processor_prefix - 1;
	size_t fieldlen;
	const char *p;

	*value = NULL;
	p = get_cpuinfo();
	if (!p)
		return -errno;

	while (1) {
		/* Blank line at start -> no more processors */
		if (!*p)
			return -ESRCH;

		/* First line must be processor */
		if (strncmp(p, processor_prefix, pfxlen))
			return -EPROTO;

		if (strtoul(p + pfxlen, NULL, 10) == cpu)
			break;	/* Found our CPU */

		/*
		 * Skip to the next processor.
		 * Each processor block is terminated by a blank line.
		 */
		do {
			p = strchr(p, '\0') + 1;
		} while (*p);
		p++;		/* Skip the blank line */
	}

	/* Look for the requested field inside the processor block. */
	fieldlen = strlen(field);

	while (*p) {
		if (!strncmp(p, field, fieldlen)) {
			p += fieldlen;
			if (!*p)
				return -EPROTO; /* No field data present */

			if (*p == '\t' || *p == ':') {
				/* Field matches */

				while (*p == '\t')
					p++;
				if (*p != ':')
					return -EPROTO; /* Colon missing */
				p++;

				/* Skip space after colon */
				if (*p == ' ')
					p++;
				*value = p;
				return 0;		/* Field found */
			}
		}
		p = strchr(p, '\0') + 1;	/* Next field */
	}

	return -ENOMSG;		/* CPU found but no such field */
}

/*
 * Look for a specific flag inside a specific multi-token field;
 * this currently applies to the "flags" or "bugs" fields.
 *
 * Return 1 if the given flag is present, 0 if it is not, or
 * a negative error code returned from cpuinfo_get_field().
 */
int cpuinfo_has_flag(const char *flag, const char *field, unsigned long cpu)
{
	const char *p;
	size_t flaglen;
	int err;

	err = cpuinfo_get_field(&p, field, cpu);
	if (err)
		return err;

	flaglen = strlen(flag);
	while (*p) {
		if (!strncmp(p, flag, flaglen) &&
		    (!p[flaglen] || p[flaglen] == ' '))
		    return 1;	/* Flag found */

		/* Skip to the next flag */
		p = strchr(p, ' ');
		if (!p)
			break;
		p++;
	}

	return 0;		/* Not found */
}

#ifdef TEST_CPUINFO

#include <stdio.h>
#include <sysexits.h>

int main(int argc, char **argv)
{
	const char *field, *flag;
	unsigned long cpu;
	int rv;

	if (argc < 3) {
		fprintf(stderr, "Usage: %s cpu field\n", argv[0]);
		exit(EX_USAGE);
	}

	cpu = strtoul(argv[1], NULL, 10);
	field = argv[2];
	flag = argv[3];

	if (flag) {
		rv = cpuinfo_has_flag(flag, field, cpu);
		if (rv >= 0) {
			printf("CPU %lu %s(%s) = %d\n", cpu, field, flag, rv);
			exit(rv);
		}
	} else {
		const char *data;
		rv = cpuinfo_get_field(&data, field, cpu);
		if (!rv) {
			printf("CPU %lu %s = %s\n", cpu, field, data);
			exit(0);
		}
	}

	/*
	 * On error. Errors that should never happen no matter what the
	 * input are sent to stderr.
	 */
	switch (rv) {
	case -ESRCH:
		printf("CPU %lu does not exist\n", cpu);
		exit(EX_NOUSER);
	case -ENOMSG:
		printf("CPU %lu %s : field does not exist\n", cpu, field);
		exit(EX_NOINPUT);
	case -EPROTO:
		fprintf(stderr, "%s: could not parse /proc/cpuinfo\n", argv[0]);
		exit(EX_PROTOCOL);
	default:
		fprintf(stderr, "/proc/cpuinfo: %s\n", strerror(-rv));
		exit(EX_OSFILE);
	}
}

#endif /* TEST_CPUINFO */

Reply via email to