Merge QEMU v10.2.0

This commit is contained in:
Matt Borgerson
2026-01-18 16:36:55 -07:00
5172 changed files with 276005 additions and 130634 deletions
-1
View File
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Analyse lock events and compute statistics
#
+1 -1
View File
@@ -53,7 +53,7 @@ echo $(grep_include -F 'trace/generated-tracers.h') files include generated-trac
echo $(grep_include -F 'qapi/error.h') files include qapi/error.h
echo $(grep_include -F 'qom/object.h') files include qom/object.h
echo $(grep_include -F 'block/aio.h') files include block/aio.h
echo $(grep_include -F 'exec/memory.h') files include exec/memory.h
echo $(grep_include -F 'system/memory.h') files include system/memory.h
echo $(grep_include -F 'fpu/softfloat.h') files include fpu/softfloat.h
echo $(grep_include -F 'qemu/bswap.h') files include qemu/bswap.h
echo
+11
View File
@@ -620,7 +620,9 @@ class MigrationDump(object):
QEMU_VM_SUBSECTION = 0x05
QEMU_VM_VMDESCRIPTION = 0x06
QEMU_VM_CONFIGURATION = 0x07
QEMU_VM_COMMAND = 0x08
QEMU_VM_SECTION_FOOTER= 0x7e
QEMU_MIG_CMD_SWITCHOVER_START = 0x0b
def __init__(self, filename):
self.section_classes = {
@@ -685,6 +687,15 @@ class MigrationDump(object):
elif section_type == self.QEMU_VM_SECTION_PART or section_type == self.QEMU_VM_SECTION_END:
section_id = file.read32()
self.sections[section_id].read()
elif section_type == self.QEMU_VM_COMMAND:
command_type = file.read16()
command_data_len = file.read16()
if command_type != self.QEMU_MIG_CMD_SWITCHOVER_START:
raise Exception("Unknown QEMU_VM_COMMAND: %x" %
(command_type))
if command_data_len != 0:
raise Exception("Invalid SWITCHOVER_START length: %x" %
(command_data_len))
elif section_type == self.QEMU_VM_SECTION_FOOTER:
read_section_id = file.read32()
if read_section_id != section_id:
+41 -12
View File
@@ -29,17 +29,47 @@ sub_file="${sub_tdir}/submodule.tar"
# independent of what the developer currently has initialized
# in their checkout, because the build environment is completely
# different to the host OS.
subprojects="keycodemapdb libvfio-user berkeley-softfloat-3
berkeley-testfloat-3 arbitrary-int-1-rs bilge-0.2-rs
bilge-impl-0.2-rs either-1-rs itertools-0.11-rs proc-macro2-1-rs
proc-macro-error-1-rs proc-macro-error-attr-1-rs quote-1-rs
syn-2-rs unicode-ident-1-rs"
subprojects=(
anyhow-1-rs
arbitrary-int-1-rs
attrs-0.2-rs
berkeley-softfloat-3
berkeley-testfloat-3
bilge-0.2-rs
bilge-impl-0.2-rs
either-1-rs
foreign-0.3-rs
glib-sys-0.21-rs
itertools-0.11-rs
keycodemapdb
libc-0.2-rs
libvfio-user
proc-macro-error-1-rs
proc-macro-error-attr-1-rs
proc-macro2-1-rs
quote-1-rs
syn-2-rs
unicode-ident-1-rs
)
sub_deinit=""
# xemu only
subprojects="keycodemapdb berkeley-softfloat-3 berkeley-testfloat-3
glslang SPIRV-Reflect volk VulkanMemoryAllocator nv2a_vsh_cpu
tomlplusplus xxhash imgui implot genconfig json"
subprojects=(
berkeley-softfloat-3
berkeley-testfloat-3
genconfig
glslang
imgui
implot
json
keycodemapdb
nv2a_vsh_cpu
SPIRV-Reflect
tomlplusplus
volk
VulkanMemoryAllocator
xxhash
)
function cleanup() {
local status=$?
@@ -84,12 +114,11 @@ function subproject_dir() {
git archive --format tar "$(tree_ish)" --prefix="$tar_prefix" > "$tar_file"
test $? -ne 0 && error "failed to archive qemu"
meson subprojects download -j8 $subprojects
# test $? -ne 0 && error "failed to download subprojects"
meson subprojects download -j8 ${subprojects[@]} >/dev/null
# test $? -ne 0 && error "failed to download subprojects $subprojects"
for sp in $subprojects; do
for sp in "${subprojects[@]}"; do
tar --append --file "$tar_file" --exclude=.git --transform "s,^./,$tar_prefix," ./subprojects/"$(subproject_dir $sp)"
test $? -ne 0 && error "failed to append subproject $sp to $tar_file"
done
exit 0
+476
View File
@@ -0,0 +1,476 @@
#!/usr/bin/env python3
#
# pylint: disable=C0301,C0114,R0903,R0912,R0913,R0914,R0915,W0511
# SPDX-License-Identifier: GPL-2.0-or-later
#
# Copyright (C) 2024-2025 Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
# TODO: current implementation has dummy defaults.
#
# For a better implementation, a QMP addition/call is needed to
# retrieve some data for ARM Processor Error injection:
#
# - ARM registers: power_state, mpidr.
"""
Generate an ARM processor error CPER, compatible with
UEFI 2.9A Errata.
Injecting such errors can be done using:
$ ./scripts/ghes_inject.py arm
Error injected.
Produces a simple CPER register, as detected on a Linux guest:
[Hardware Error]: Hardware error from APEI Generic Hardware Error Source: 1
[Hardware Error]: event severity: recoverable
[Hardware Error]: Error 0, type: recoverable
[Hardware Error]: section_type: ARM processor error
[Hardware Error]: MIDR: 0x0000000000000000
[Hardware Error]: running state: 0x0
[Hardware Error]: Power State Coordination Interface state: 0
[Hardware Error]: Error info structure 0:
[Hardware Error]: num errors: 2
[Hardware Error]: error_type: 0x02: cache error
[Hardware Error]: error_info: 0x000000000091000f
[Hardware Error]: transaction type: Data Access
[Hardware Error]: cache error, operation type: Data write
[Hardware Error]: cache level: 2
[Hardware Error]: processor context not corrupted
[Firmware Warn]: GHES: Unhandled processor error type 0x02: cache error
The ARM Processor Error message can be customized via command line
parameters. For instance:
$ ./scripts/ghes_inject.py arm --mpidr 0x444 --running --affinity 1 \
--error-info 12345678 --vendor 0x13,123,4,5,1 --ctx-array 0,1,2,3,4,5 \
-t cache tlb bus micro-arch tlb,micro-arch
Error injected.
Injects this error, as detected on a Linux guest:
[Hardware Error]: Hardware error from APEI Generic Hardware Error Source: 1
[Hardware Error]: event severity: recoverable
[Hardware Error]: Error 0, type: recoverable
[Hardware Error]: section_type: ARM processor error
[Hardware Error]: MIDR: 0x0000000000000000
[Hardware Error]: Multiprocessor Affinity Register (MPIDR): 0x0000000000000000
[Hardware Error]: error affinity level: 0
[Hardware Error]: running state: 0x1
[Hardware Error]: Power State Coordination Interface state: 0
[Hardware Error]: Error info structure 0:
[Hardware Error]: num errors: 2
[Hardware Error]: error_type: 0x02: cache error
[Hardware Error]: error_info: 0x0000000000bc614e
[Hardware Error]: cache level: 2
[Hardware Error]: processor context not corrupted
[Hardware Error]: Error info structure 1:
[Hardware Error]: num errors: 2
[Hardware Error]: error_type: 0x04: TLB error
[Hardware Error]: error_info: 0x000000000054007f
[Hardware Error]: transaction type: Instruction
[Hardware Error]: TLB error, operation type: Instruction fetch
[Hardware Error]: TLB level: 1
[Hardware Error]: processor context not corrupted
[Hardware Error]: the error has not been corrected
[Hardware Error]: PC is imprecise
[Hardware Error]: Error info structure 2:
[Hardware Error]: num errors: 2
[Hardware Error]: error_type: 0x08: bus error
[Hardware Error]: error_info: 0x00000080d6460fff
[Hardware Error]: transaction type: Generic
[Hardware Error]: bus error, operation type: Generic read (type of instruction or data request cannot be determined)
[Hardware Error]: affinity level at which the bus error occurred: 1
[Hardware Error]: processor context corrupted
[Hardware Error]: the error has been corrected
[Hardware Error]: PC is imprecise
[Hardware Error]: Program execution can be restarted reliably at the PC associated with the error.
[Hardware Error]: participation type: Local processor observed
[Hardware Error]: request timed out
[Hardware Error]: address space: External Memory Access
[Hardware Error]: memory access attributes:0x20
[Hardware Error]: access mode: secure
[Hardware Error]: Error info structure 3:
[Hardware Error]: num errors: 2
[Hardware Error]: error_type: 0x10: micro-architectural error
[Hardware Error]: error_info: 0x0000000078da03ff
[Hardware Error]: Error info structure 4:
[Hardware Error]: num errors: 2
[Hardware Error]: error_type: 0x14: TLB error|micro-architectural error
[Hardware Error]: Context info structure 0:
[Hardware Error]: register context type: AArch64 EL1 context registers
[Hardware Error]: 00000000: 00000000 00000000
[Hardware Error]: Vendor specific error info has 5 bytes:
[Hardware Error]: 00000000: 13 7b 04 05 01 .{...
[Firmware Warn]: GHES: Unhandled processor error type 0x02: cache error
[Firmware Warn]: GHES: Unhandled processor error type 0x04: TLB error
[Firmware Warn]: GHES: Unhandled processor error type 0x08: bus error
[Firmware Warn]: GHES: Unhandled processor error type 0x10: micro-architectural error
[Firmware Warn]: GHES: Unhandled processor error type 0x14: TLB error|micro-architectural error
"""
import argparse
import re
from qmp_helper import qmp, util, cper_guid
class ArmProcessorEinj:
"""
Implements ARM Processor Error injection via GHES
"""
DESC = """
Generates an ARM processor error CPER, compatible with
UEFI 2.9A Errata.
"""
ACPI_GHES_ARM_CPER_LENGTH = 40
ACPI_GHES_ARM_CPER_PEI_LENGTH = 32
# Context types
CONTEXT_AARCH32_EL1 = 1
CONTEXT_AARCH64_EL1 = 5
CONTEXT_MISC_REG = 8
def __init__(self, subparsers):
"""Initialize the error injection class and add subparser"""
# Valid choice values
self.arm_valid_bits = {
"mpidr": util.bit(0),
"affinity": util.bit(1),
"running": util.bit(2),
"vendor": util.bit(3),
}
self.pei_flags = {
"first": util.bit(0),
"last": util.bit(1),
"propagated": util.bit(2),
"overflow": util.bit(3),
}
self.pei_error_types = {
"cache": util.bit(1),
"tlb": util.bit(2),
"bus": util.bit(3),
"micro-arch": util.bit(4),
}
self.pei_valid_bits = {
"multiple-error": util.bit(0),
"flags": util.bit(1),
"error-info": util.bit(2),
"virt-addr": util.bit(3),
"phy-addr": util.bit(4),
}
self.data = bytearray()
parser = subparsers.add_parser("arm", description=self.DESC)
arm_valid_bits = ",".join(self.arm_valid_bits.keys())
flags = ",".join(self.pei_flags.keys())
error_types = ",".join(self.pei_error_types.keys())
pei_valid_bits = ",".join(self.pei_valid_bits.keys())
# UEFI N.16 ARM Validation bits
g_arm = parser.add_argument_group("ARM processor")
g_arm.add_argument("--arm", "--arm-valid",
help=f"ARM valid bits: {arm_valid_bits}")
g_arm.add_argument("-a", "--affinity", "--level", "--affinity-level",
type=lambda x: int(x, 0),
help="Affinity level (when multiple levels apply)")
g_arm.add_argument("-l", "--mpidr", type=lambda x: int(x, 0),
help="Multiprocessor Affinity Register")
g_arm.add_argument("-i", "--midr", type=lambda x: int(x, 0),
help="Main ID Register")
g_arm.add_argument("-r", "--running",
action=argparse.BooleanOptionalAction,
default=None,
help="Indicates if the processor is running or not")
g_arm.add_argument("--psci", "--psci-state",
type=lambda x: int(x, 0),
help="Power State Coordination Interface - PSCI state")
# TODO: Add vendor-specific support
# UEFI N.17 bitmaps (type and flags)
g_pei = parser.add_argument_group("ARM Processor Error Info (PEI)")
g_pei.add_argument("-t", "--type", nargs="+",
help=f"one or more error types: {error_types}")
g_pei.add_argument("-f", "--flags", nargs="*",
help=f"zero or more error flags: {flags}")
g_pei.add_argument("-V", "--pei-valid", "--error-valid", nargs="*",
help=f"zero or more PEI valid bits: {pei_valid_bits}")
# UEFI N.17 Integer values
g_pei.add_argument("-m", "--multiple-error", nargs="+",
help="Number of errors: 0: Single error, 1: Multiple errors, 2-65535: Error count if known")
g_pei.add_argument("-e", "--error-info", nargs="+",
help="Error information (UEFI 2.10 tables N.18 to N.20)")
g_pei.add_argument("-p", "--physical-address", nargs="+",
help="Physical address")
g_pei.add_argument("-v", "--virtual-address", nargs="+",
help="Virtual address")
# UEFI N.21 Context
g_ctx = parser.add_argument_group("Processor Context")
g_ctx.add_argument("--ctx-type", "--context-type", nargs="*",
help="Type of the context (0=ARM32 GPR, 5=ARM64 EL1, other values supported)")
g_ctx.add_argument("--ctx-size", "--context-size", nargs="*",
help="Minimal size of the context")
g_ctx.add_argument("--ctx-array", "--context-array", nargs="*",
help="Comma-separated arrays for each context")
# Vendor-specific data
g_vendor = parser.add_argument_group("Vendor-specific data")
g_vendor.add_argument("--vendor", "--vendor-specific", nargs="+",
help="Vendor-specific byte arrays of data")
# Add arguments for Generic Error Data
qmp.argparse(parser)
parser.set_defaults(func=self.send_cper)
def send_cper(self, args):
"""Parse subcommand arguments and send a CPER via QMP"""
qmp_cmd = qmp(args.host, args.port, args.debug)
# Handle Generic Error Data arguments if any
qmp_cmd.set_args(args)
is_cpu_type = re.compile(r"^([\w+]+\-)?arm\-cpu$")
cpus = qmp_cmd.search_qom("/machine/unattached/device",
"type", is_cpu_type)
cper = {}
pei = {}
ctx = {}
vendor = {}
arg = vars(args)
# Handle global parameters
if args.arm:
arm_valid_init = False
cper["valid"] = util.get_choice(name="valid",
value=args.arm,
choices=self.arm_valid_bits,
suffixes=["-error", "-err"])
else:
cper["valid"] = 0
arm_valid_init = True
if "running" in arg:
if args.running:
cper["running-state"] = util.bit(0)
else:
cper["running-state"] = 0
else:
cper["running-state"] = 0
if arm_valid_init:
if args.affinity:
cper["valid"] |= self.arm_valid_bits["affinity"]
if args.mpidr:
cper["valid"] |= self.arm_valid_bits["mpidr"]
if "running-state" in cper:
cper["valid"] |= self.arm_valid_bits["running"]
if args.psci:
cper["valid"] |= self.arm_valid_bits["running"]
# Handle PEI
if not args.type:
args.type = ["cache-error"]
util.get_mult_choices(
pei,
name="valid",
values=args.pei_valid,
choices=self.pei_valid_bits,
suffixes=["-valid", "--addr"],
)
util.get_mult_choices(
pei,
name="type",
values=args.type,
choices=self.pei_error_types,
suffixes=["-error", "-err"],
)
util.get_mult_choices(
pei,
name="flags",
values=args.flags,
choices=self.pei_flags,
suffixes=["-error", "-cap"],
)
util.get_mult_int(pei, "error-info", args.error_info)
util.get_mult_int(pei, "multiple-error", args.multiple_error)
util.get_mult_int(pei, "phy-addr", args.physical_address)
util.get_mult_int(pei, "virt-addr", args.virtual_address)
# Handle context
util.get_mult_int(ctx, "type", args.ctx_type, allow_zero=True)
util.get_mult_int(ctx, "minimal-size", args.ctx_size, allow_zero=True)
util.get_mult_array(ctx, "register", args.ctx_array, allow_zero=True)
util.get_mult_array(vendor, "bytes", args.vendor, max_val=255)
# Store PEI
pei_data = bytearray()
default_flags = self.pei_flags["first"]
default_flags |= self.pei_flags["last"]
error_info_num = 0
for i, p in pei.items(): # pylint: disable=W0612
error_info_num += 1
# UEFI 2.10 doesn't define how to encode error information
# when multiple types are raised. So, provide a default only
# if a single type is there
if "error-info" not in p:
if p["type"] == util.bit(1):
p["error-info"] = 0x0091000F
if p["type"] == util.bit(2):
p["error-info"] = 0x0054007F
if p["type"] == util.bit(3):
p["error-info"] = 0x80D6460FFF
if p["type"] == util.bit(4):
p["error-info"] = 0x78DA03FF
if "valid" not in p:
p["valid"] = 0
if "multiple-error" in p:
p["valid"] |= self.pei_valid_bits["multiple-error"]
if "flags" in p:
p["valid"] |= self.pei_valid_bits["flags"]
if "error-info" in p:
p["valid"] |= self.pei_valid_bits["error-info"]
if "phy-addr" in p:
p["valid"] |= self.pei_valid_bits["phy-addr"]
if "virt-addr" in p:
p["valid"] |= self.pei_valid_bits["virt-addr"]
# Version
util.data_add(pei_data, 0, 1)
util.data_add(pei_data,
self.ACPI_GHES_ARM_CPER_PEI_LENGTH, 1)
util.data_add(pei_data, p["valid"], 2)
util.data_add(pei_data, p["type"], 1)
util.data_add(pei_data, p.get("multiple-error", 1), 2)
util.data_add(pei_data, p.get("flags", default_flags), 1)
util.data_add(pei_data, p.get("error-info", 0), 8)
util.data_add(pei_data, p.get("virt-addr", 0xDEADBEEF), 8)
util.data_add(pei_data, p.get("phy-addr", 0xABBA0BAD), 8)
# Store Context
ctx_data = bytearray()
context_info_num = 0
if ctx:
ret = qmp_cmd.send_cmd("query-target", may_open=True)
default_ctx = self.CONTEXT_MISC_REG
if "arch" in ret:
if ret["arch"] == "aarch64":
default_ctx = self.CONTEXT_AARCH64_EL1
elif ret["arch"] == "arm":
default_ctx = self.CONTEXT_AARCH32_EL1
for k in sorted(ctx.keys()):
context_info_num += 1
if "type" not in ctx[k]:
ctx[k]["type"] = default_ctx
if "register" not in ctx[k]:
ctx[k]["register"] = []
reg_size = len(ctx[k]["register"])
size = 0
if "minimal-size" in ctx:
size = ctx[k]["minimal-size"]
size = max(size, reg_size)
size = (size + 1) % 0xFFFE
# Version
util.data_add(ctx_data, 0, 2)
util.data_add(ctx_data, ctx[k]["type"], 2)
util.data_add(ctx_data, 8 * size, 4)
for r in ctx[k]["register"]:
util.data_add(ctx_data, r, 8)
for i in range(reg_size, size): # pylint: disable=W0612
util.data_add(ctx_data, 0, 8)
# Vendor-specific bytes are not grouped
vendor_data = bytearray()
if vendor:
for k in sorted(vendor.keys()):
for b in vendor[k]["bytes"]:
util.data_add(vendor_data, b, 1)
# Encode ARM Processor Error
data = bytearray()
util.data_add(data, cper["valid"], 4)
util.data_add(data, error_info_num, 2)
util.data_add(data, context_info_num, 2)
# Calculate the length of the CPER data
cper_length = self.ACPI_GHES_ARM_CPER_LENGTH
cper_length += len(pei_data)
cper_length += len(vendor_data)
cper_length += len(ctx_data)
util.data_add(data, cper_length, 4)
util.data_add(data, arg.get("affinity-level", 0), 1)
# Reserved
util.data_add(data, 0, 3)
if "midr-el1" not in arg:
if cpus:
cmd_arg = {
'path': cpus[0],
'property': "midr"
}
ret = qmp_cmd.send_cmd("qom-get", cmd_arg, may_open=True)
if isinstance(ret, int):
arg["midr-el1"] = ret
util.data_add(data, arg.get("mpidr-el1", 0), 8)
util.data_add(data, arg.get("midr-el1", 0), 8)
util.data_add(data, cper["running-state"], 4)
util.data_add(data, arg.get("psci-state", 0), 4)
# Add PEI
data.extend(pei_data)
data.extend(ctx_data)
data.extend(vendor_data)
self.data = data
qmp_cmd.send_cper(cper_guid.CPER_PROC_ARM, self.data)
+326 -97
View File
@@ -365,6 +365,18 @@ our @typeList = (
qr{guintptr},
);
# Match text found in common license boilerplate comments:
# for new files the SPDX-License-Identifier line is sufficient.
our @LICENSE_BOILERPLATE = (
"licensed under the terms of the GNU GPL",
"under the terms of the GNU General Public License",
"under the terms of the GNU Lesser General Public",
"Permission is hereby granted, free of charge",
"GNU GPL, version 2 or later",
"See the COPYING file"
);
our $LICENSE_BOILERPLATE_RE = join("|", @LICENSE_BOILERPLATE);
# Load common spelling mistakes and build regular expression list.
my $misspellings;
my %spelling_fix;
@@ -1330,26 +1342,182 @@ sub WARN {
}
}
# According to tests/qtest/bios-tables-test.c: do not
# change expected file in the same commit with adding test
sub checkfilename {
my ($name, $acpi_testexpected, $acpi_nontestexpected) = @_;
sub checkspdx {
my ($file, $expr) = @_;
# Note: shell script that rebuilds the expected files is in the same
# directory as files themselves.
# Note: allowed diff list can be changed both when changing expected
# files and when changing tests.
if ($name =~ m#^tests/data/acpi/# and not $name =~ m#^\.sh$#) {
$$acpi_testexpected = $name;
} elsif ($name !~ m#^tests/qtest/bios-tables-test-allowed-diff.h$#) {
$$acpi_nontestexpected = $name;
# Imported Linux headers probably have SPDX tags, but if they
# don't we're not requiring contributors to fix this, as these
# files are not expected to be modified locally in QEMU.
# Also don't accidentally detect own checking code.
if ($file =~ m,include/standard-headers, ||
$file =~ m,linux-headers, ||
$file =~ m,checkpatch.pl,) {
return;
}
my $origexpr = $expr;
# Flatten sub-expressions
$expr =~ s/\(|\)/ /g;
$expr =~ s/OR|AND/ /g;
# Merge WITH exceptions to the license
$expr =~ s/\s+WITH\s+/-WITH-/g;
# Cull more leading/trailing whitespace
$expr =~ s/^\s*//g;
$expr =~ s/\s*$//g;
# Cull C comment end
$expr =~ s/\*\/.*//;
my @bits = split / +/, $expr;
my $prefer = "GPL-2.0-or-later";
my @valid = qw(
GPL-2.0-only
LGPL-2.1-only
LGPL-2.1-or-later
BSD-2-Clause
BSD-3-Clause
MIT
);
my $nonpreferred = 0;
my @unknown = ();
foreach my $bit (@bits) {
if ($bit eq $prefer) {
next;
}
if (defined $$acpi_testexpected and defined $$acpi_nontestexpected) {
if (grep /^$bit$/, @valid) {
$nonpreferred = 1;
} else {
push @unknown, $bit;
}
}
if (@unknown) {
ERROR("Saw unacceptable licenses '" . join(',', @unknown) .
"', valid choices for QEMU are:\n" . join("\n", $prefer, @valid));
}
if ($nonpreferred) {
WARN("Saw acceptable license '$origexpr' but note '$prefer' is " .
"preferred for new files unless the code is derived from a " .
"source file with an existing declared license that must be " .
"retained. Please explain the license choice in the commit " .
"message.");
}
}
# All three of the methods below take a 'file info' record
# which is a hash ref containing
#
# 'isgit': 1 if an enhanced git diff or 0 for a plain diff
# 'githeader': 1 if still parsing git patch header, 0 otherwise
# 'linestart': line number of start of file diff
# 'lineend': line number of end of file diff
# 'filenew': the new filename
# 'fileold': the old filename (same as 'new filename' except
# for renames in git diffs)
# 'action': one of 'modified' (always) or 'new' or 'deleted' or
# 'renamed' (git diffs only)
# 'mode': file mode for new/deleted files (git diffs only)
# 'similarity': file similarity when renamed (git diffs only)
# 'facts': hash ref for storing any metadata related to checks
#
# Called at the end of each patch, with the list of
# real filenames that were seen in the patch
sub process_file_list {
my @fileinfos = @_;
# According to tests/qtest/bios-tables-test.c: do not
# change expected file in the same commit with adding test
my @acpi_testexpected;
my @acpi_nontestexpected;
foreach my $fileinfo (@fileinfos) {
# Note: shell script that rebuilds the expected files is in
# the same directory as files themselves.
# Note: allowed diff list can be changed both when changing
# expected files and when changing tests.
if ($fileinfo->{filenew} =~ m#^tests/data/acpi/# &&
$fileinfo->{filenew} !~ m#^\.sh$#) {
push @acpi_testexpected, $fileinfo->{filenew};
} elsif ($fileinfo->{filenew} !~
m#^tests/qtest/bios-tables-test-allowed-diff.h$#) {
push @acpi_nontestexpected, $fileinfo->{filenew};
}
}
if (int(@acpi_testexpected) > 0 and int(@acpi_nontestexpected) > 0) {
ERROR("Do not add expected files together with tests, " .
"follow instructions in " .
"tests/qtest/bios-tables-test.c: both " .
$$acpi_testexpected . " and " .
$$acpi_nontestexpected . " found\n");
"tests/qtest/bios-tables-test.c. Files\n\n " .
join("\n ", @acpi_testexpected) .
"\n\nand\n\n " .
join("\n ", @acpi_nontestexpected) .
"\n\nfound in the same patch\n");
}
my $sawmaintainers = 0;
my @maybemaintainers;
foreach my $fileinfo (@fileinfos) {
if ($fileinfo->{action} ne "modified" &&
$fileinfo->{filenew} !~ m#^tests/data/acpi/#) {
push @maybemaintainers, $fileinfo->{filenew};
}
if ($fileinfo->{filenew} eq "MAINTAINERS") {
$sawmaintainers = 1;
}
}
# If we don't see a MAINTAINERS update, prod the user to check
if (int(@maybemaintainers) > 0 && !$sawmaintainers) {
WARN("added, moved or deleted file(s):\n\n " .
join("\n ", @maybemaintainers) .
"\n\nDoes MAINTAINERS need updating?\n");
}
}
# Called at the start of processing a diff hunk for a file
sub process_start_of_file {
my $fileinfo = shift;
# Check for incorrect file permissions
if ($fileinfo->{action} eq "new" && ($fileinfo->{mode} & 0111)) {
my $permhere = $fileinfo->{linestart} . "FILE: " .
$fileinfo->{filenew} . "\n";
if ($fileinfo->{filenew} =~
/(\bMakefile.*|\.(c|cc|cpp|h|mak|s|S))$/) {
ERROR("do not set execute permissions for source " .
"files\n" . $permhere);
}
}
}
# Called at the end of processing a diff hunk for a file
sub process_end_of_file {
my $fileinfo = shift;
if ($fileinfo->{action} eq "new" &&
!exists $fileinfo->{facts}->{sawspdx}) {
if ($fileinfo->{filenew} =~
/(\.(c|h|py|pl|sh|json|inc|rs)|Makefile.*)$/) {
# source code files MUST have SPDX license declared
ERROR("New file '" . $fileinfo->{filenew} .
"' requires 'SPDX-License-Identifier'");
} else {
# Other files MAY have SPDX license if appropriate
WARN("Does new file '" . $fileinfo->{filenew} .
"' need 'SPDX-License-Identifier'?");
}
}
if ($fileinfo->{action} eq "new" &&
exists $fileinfo->{facts}->{sawboilerplate}) {
ERROR("New file '" . $fileinfo->{filenew} . "' must " .
"not have license boilerplate header text, only " .
"the SPDX-License-Identifier, unless this file was " .
"copied from existing code already having such text.");
}
}
@@ -1373,7 +1541,6 @@ sub process {
my $in_header_lines = $file ? 0 : 1;
my $in_commit_log = 0; #Scanning lines before patch
my $reported_maintainer_file = 0;
my $reported_mixing_imported_file = 0;
my $in_imported_file = 0;
my $in_no_imported_file = 0;
@@ -1389,7 +1556,10 @@ sub process {
my $realfile = '';
my $realline = 0;
my $realcnt = 0;
my $fileinfo;
my @fileinfolist;
my $here = '';
my $oldhere = '';
my $in_comment = 0;
my $comment_edge = 0;
my $first_line = 0;
@@ -1402,9 +1572,6 @@ sub process {
my %suppress_whiletrailers;
my %suppress_export;
my $acpi_testexpected;
my $acpi_nontestexpected;
# Pre-scan the patch sanitizing the lines.
sanitise_line_reset();
@@ -1527,25 +1694,79 @@ sub process {
$prefix = "$filename:$realline: " if ($emacs && $file);
$prefix = "$filename:$linenr: " if ($emacs && !$file);
$oldhere = $here;
$here = "#$linenr: " if (!$file);
$here = "#$realline: " if ($file);
# extract the filename as it passes
if ($line =~ /^diff --git.*?(\S+)$/) {
$realfile = $1;
$realfile =~ s@^([^/]*)/@@ if (!$file);
checkfilename($realfile, \$acpi_testexpected, \$acpi_nontestexpected);
if ($line =~ /^diff --git\s+(\S+)\s+(\S+)$/) {
my $fileold = $1;
my $filenew = $2;
if (defined $fileinfo) {
$fileinfo->{lineend} = $oldhere;
process_end_of_file($fileinfo)
}
$fileold =~ s@^([^/]*)/@@ if (!$file);
$filenew =~ s@^([^/]*)/@@ if (!$file);
$realfile = $filenew;
$fileinfo = {
"isgit" => 1,
"githeader" => 1,
"linestart" => $here,
"lineend" => 0,
"fileold" => $fileold,
"filenew" => $filenew,
"action" => "modified",
"mode" => 0,
"similarity" => 0,
"facts" => {},
};
push @fileinfolist, $fileinfo;
} elsif (defined $fileinfo && $fileinfo->{githeader} &&
$line =~ /^(new|deleted) (?:file )?mode\s+([0-7]+)$/) {
$fileinfo->{action} = $1;
$fileinfo->{mode} = oct($2);
} elsif (defined $fileinfo && $fileinfo->{githeader} &&
$line =~ /^similarity index (\d+)%/) {
$fileinfo->{similarity} = int($1);
} elsif (defined $fileinfo && $fileinfo->{githeader} &&
$line =~ /^rename (from|to) [\w\/\.\-]+\s*$/) {
$fileinfo->{action} = "renamed";
# For a no-change rename, we'll never have any "+++..."
# lines, so trigger actions now
if ($1 eq "to" && $fileinfo->{similarity} == 100) {
process_start_of_file($fileinfo);
}
} elsif ($line =~ /^\+\+\+\s+(\S+)/) {
$realfile = $1;
$realfile =~ s@^([^/]*)/@@ if (!$file);
checkfilename($realfile, \$acpi_testexpected, \$acpi_nontestexpected);
$realfile =~ s@^[^/]*/@@ if (!$file);
$p1_prefix = $1;
if (!$file && $tree && $p1_prefix ne '' &&
-e "$root/$p1_prefix") {
WARN("patch prefix '$p1_prefix' exists, appears to be a -p0 patch\n");
if (defined $fileinfo && !$fileinfo->{isgit}) {
$fileinfo->{lineend} = $oldhere;
process_end_of_file($fileinfo);
}
if (!defined $fileinfo || !$fileinfo->{isgit}) {
$fileinfo = {
"isgit" => 0,
"githeader" => 0,
"linestart" => $here,
"lineend" => 0,
"fileold" => $realfile,
"filenew" => $realfile,
"action" => "modified",
"mode" => 0,
"similarity" => 0,
"facts" => {},
};
push @fileinfolist, $fileinfo;
} else {
$fileinfo->{githeader} = 0;
}
process_start_of_file($fileinfo);
next;
}
@@ -1557,14 +1778,6 @@ sub process {
$cnt_lines++ if ($realcnt != 0);
# Check for incorrect file permissions
if ($line =~ /^new (file )?mode.*[7531]\d{0,2}$/) {
my $permhere = $here . "FILE: $realfile\n";
if ($realfile =~ /(\bMakefile(?:\.objs)?|\.c|\.cc|\.cpp|\.h|\.mak|\.[sS])$/) {
ERROR("do not set execute permissions for source files\n" . $permhere);
}
}
# Only allow Python 3 interpreter
if ($realline == 1 &&
$line =~ /^\+#!\ *\/usr\/bin\/(?:env )?python$/) {
@@ -1596,23 +1809,28 @@ sub process {
}
}
# Check if MAINTAINERS is being updated. If so, there's probably no need to
# emit the "does MAINTAINERS need updating?" message on file add/move/delete
if ($line =~ /^\s*MAINTAINERS\s*\|/) {
$reported_maintainer_file = 1;
# Check SPDX-License-Identifier references a permitted license
if (($rawline =~ m,SPDX-License-Identifier: (.*?)(\*/)?\s*$,) &&
$rawline !~ /^-/) {
$fileinfo->{facts}->{sawspdx} = 1;
&checkspdx($realfile, $1);
}
# Check for added, moved or deleted files
if (!$reported_maintainer_file && !$in_commit_log &&
($line =~ /^(?:new|deleted) file mode\s*\d+\s*$/ ||
$line =~ /^rename (?:from|to) [\w\/\.\-]+\s*$/ ||
($line =~ /\{\s*([\w\/\.\-]*)\s*\=\>\s*([\w\/\.\-]*)\s*\}/ &&
(defined($1) || defined($2)))) &&
!(($realfile ne '') &&
defined($acpi_testexpected) &&
($realfile eq $acpi_testexpected))) {
$reported_maintainer_file = 1;
WARN("added, moved or deleted file(s), does MAINTAINERS need updating?\n" . $herecurr);
if ($rawline =~ /$LICENSE_BOILERPLATE_RE/) {
$fileinfo->{facts}->{sawboilerplate} = 1;
}
if ($rawline =~ m,(SPDX-[a-zA-Z0-9-_]+):,) {
my $tag = $1;
my @permitted = qw(
SPDX-License-Identifier
);
unless (grep { /^$tag$/ } @permitted) {
ERROR("Tag $tag not permitted in QEMU code, " .
"valid choices are: " .
join(", ", @permitted));
}
}
# Check for wrappage within a valid hunk of the file
@@ -2193,7 +2411,7 @@ sub process {
# missing space after union, struct or enum definition
if ($line =~ /^.\s*(?:typedef\s+)?(enum|union|struct)(?:\s+$Ident)?(?:\s+$Ident)?[=\{]/) {
ERROR("missing space after $1 definition\n" . $herecurr);
ERROR("missing space after $1 definition\n" . $herecurr);
}
# check for spacing round square brackets; allowed:
@@ -2488,7 +2706,7 @@ sub process {
if ($line =~ /^.\s*(Q(?:S?LIST|SIMPLEQ|TAILQ)_HEAD)\s*\(\s*[^,]/ &&
$line !~ /^.typedef/) {
ERROR("named $1 should be typedefed separately\n" . $herecurr);
ERROR("named $1 should be typedefed separately\n" . $herecurr);
}
# Need a space before open parenthesis after if, while etc
@@ -2974,14 +3192,18 @@ sub process {
if ($line =~ /\bsignal\s*\(/ && !($line =~ /SIG_(?:IGN|DFL)/)) {
ERROR("use sigaction to establish signal handlers; signal is not portable\n" . $herecurr);
}
# recommend qemu_bh_new_guarded instead of qemu_bh_new
if ($realfile =~ /.*\/hw\/.*/ && $line =~ /\bqemu_bh_new\s*\(/) {
ERROR("use qemu_bh_new_guarded() instead of qemu_bh_new() to avoid reentrancy problems\n" . $herecurr);
# recommend aio_bh_new_guarded instead of legacy qemu_bh_new / qemu_bh_new_guarded
if ($realfile =~ /.*\/hw\/.*/ && $line =~ /\bqemu_bh_new(_guarded)?\s*\(/) {
ERROR("use aio_bh_new_guarded() instead of qemu_bh_new*() to avoid reentrancy problems\n" . $herecurr);
}
# recommend aio_bh_new_guarded instead of aio_bh_new
if ($realfile =~ /.*\/hw\/.*/ && $line =~ /\baio_bh_new\s*\(/) {
ERROR("use aio_bh_new_guarded() instead of aio_bh_new() to avoid reentrancy problems\n" . $herecurr);
}
# check for DEVICE_NATIVE_ENDIAN, use explicit endianness instead
if ($line =~ /\bDEVICE_NATIVE_ENDIAN\b/) {
ERROR("DEVICE_NATIVE_ENDIAN is not allowed, use DEVICE_LITTLE_ENDIAN or DEVICE_BIG_ENDIAN instead\n" . $herecurr);
}
# check for module_init(), use category-specific init macros explicitly please
if ($line =~ /^module_init\s*\(/) {
ERROR("please use block_init(), type_init() etc. instead of module_init()\n" . $herecurr);
@@ -3037,48 +3259,50 @@ sub process {
# Qemu error function tests
# Find newlines in error messages
my $qemu_error_funcs = qr{error_setg|
error_setg_errno|
error_setg_win32|
error_setg_file_open|
error_set|
error_prepend|
warn_reportf_err|
error_reportf_err|
error_vreport|
warn_vreport|
info_vreport|
error_report|
warn_report|
info_report|
g_test_message}x;
# Find newlines in error messages
my $qemu_error_funcs = qr{error_setg|
error_setg_errno|
error_setg_win32|
error_setg_file_open|
error_set|
error_prepend|
warn_reportf_err|
error_reportf_err|
error_vreport|
warn_vreport|
info_vreport|
error_report|
warn_report|
info_report|
g_test_message}x;
if ($rawline =~ /\b(?:$qemu_error_funcs)\s*\(.*\".*\\n/) {
ERROR("Error messages should not contain newlines\n" . $herecurr);
}
# Continue checking for error messages that contains newlines. This
# check handles cases where string literals are spread over multiple lines.
# Example:
# error_report("Error msg line #1"
# "Error msg line #2\n");
my $quoted_newline_regex = qr{\+\s*\".*\\n.*\"};
my $continued_str_literal = qr{\+\s*\".*\"};
if ($rawline =~ /$quoted_newline_regex/) {
# Backtrack to first line that does not contain only a quoted literal
# and assume that it is the start of the statement.
my $i = $linenr - 2;
while (($i >= 0) & $rawlines[$i] =~ /$continued_str_literal/) {
$i--;
}
if ($rawlines[$i] =~ /\b(?:$qemu_error_funcs)\s*\(/) {
if ($rawline =~ /\b(?:$qemu_error_funcs)\s*\(.*\".*\\n/) {
ERROR("Error messages should not contain newlines\n" . $herecurr);
}
}
# Continue checking for error messages that contains newlines.
# This check handles cases where string literals are spread
# over multiple lines.
# Example:
# error_report("Error msg line #1"
# "Error msg line #2\n");
my $quoted_newline_regex = qr{\+\s*\".*\\n.*\"};
my $continued_str_literal = qr{\+\s*\".*\"};
if ($rawline =~ /$quoted_newline_regex/) {
# Backtrack to first line that does not contain only
# a quoted literal and assume that it is the start
# of the statement.
my $i = $linenr - 2;
while (($i >= 0) & $rawlines[$i] =~ /$continued_str_literal/) {
$i--;
}
if ($rawlines[$i] =~ /\b(?:$qemu_error_funcs)\s*\(/) {
ERROR("Error messages should not contain newlines\n" . $herecurr);
}
}
# check for non-portable libc calls that have portable alternatives in QEMU
if ($line =~ /\bffs\(/) {
@@ -3132,6 +3356,11 @@ sub process {
}
}
if (defined $fileinfo) {
process_end_of_file($fileinfo);
}
process_file_list(@fileinfolist);
if ($is_patch && $chk_signoff && $signoff == 0) {
ERROR("Missing Signed-off-by: line(s)\n");
}
+117
View File
@@ -0,0 +1,117 @@
#!/usr/bin/env python3
#
# A script to analyse failures in the gitlab pipelines. It requires an
# API key from gitlab with the following permissions:
# - api
# - read_repository
# - read_user
#
import argparse
import gitlab
import os
#
# Arguments
#
class NoneForEmptyStringAction(argparse.Action):
def __call__(self, parser, namespace, value, option_string=None):
if value == '':
setattr(namespace, self.dest, None)
else:
setattr(namespace, self.dest, value)
parser = argparse.ArgumentParser(description="Analyse failed GitLab CI runs.")
parser.add_argument("--gitlab",
default="https://gitlab.com",
help="GitLab instance URL (default: https://gitlab.com).")
parser.add_argument("--id", default=11167699,
type=int,
help="GitLab project id (default: 11167699 for qemu-project/qemu)")
parser.add_argument("--token",
default=os.getenv("GITLAB_TOKEN"),
help="Your personal access token with 'api' scope.")
parser.add_argument("--branch",
type=str,
default="staging",
action=NoneForEmptyStringAction,
help="The name of the branch (default: 'staging')")
parser.add_argument("--status",
type=str,
action=NoneForEmptyStringAction,
default="failed",
help="Filter by branch status (default: 'failed')")
parser.add_argument("--count", type=int,
default=3,
help="The number of failed runs to fetch.")
parser.add_argument("--skip-jobs",
default=False,
action='store_true',
help="Skip dumping the job info")
parser.add_argument("--pipeline", type=int,
nargs="+",
default=None,
help="Explicit pipeline ID(s) to fetch.")
if __name__ == "__main__":
args = parser.parse_args()
gl = gitlab.Gitlab(url=args.gitlab, private_token=args.token)
project = gl.projects.get(args.id)
pipelines_to_process = []
# Use explicit pipeline IDs if provided, otherwise fetch a list
if args.pipeline:
args.count = len(args.pipeline)
for p_id in args.pipeline:
pipelines_to_process.append(project.pipelines.get(p_id))
else:
# Use an iterator to fetch the pipelines
pipe_iter = project.pipelines.list(iterator=True,
status=args.status,
ref=args.branch)
# Check each failed pipeline
pipelines_to_process = [next(pipe_iter) for _ in range(args.count)]
# Check each pipeline
for p in pipelines_to_process:
jobs = p.jobs.list(get_all=True)
failed_jobs = [j for j in jobs if j.status == "failed"]
skipped_jobs = [j for j in jobs if j.status == "skipped"]
manual_jobs = [j for j in jobs if j.status == "manual"]
trs = p.test_report_summary.get()
total = trs.total["count"]
skipped = trs.total["skipped"]
failed = trs.total["failed"]
print(f"{p.status} pipeline {p.id}, total jobs {len(jobs)}, "
f"skipped {len(skipped_jobs)}, "
f"failed {len(failed_jobs)}, ",
f"{total} tests, "
f"{skipped} skipped tests, "
f"{failed} failed tests")
if not args.skip_jobs:
for j in failed_jobs:
print(f" Failed job {j.id}, {j.name}, {j.web_url}")
# It seems we can only extract failing tests from the full
# test report, maybe there is some way to filter it.
if failed > 0:
ftr = p.test_report.get()
failed_suites = [s for s in ftr.test_suites if
s["failed_count"] > 0]
for fs in failed_suites:
name = fs["name"]
tests = fs["test_cases"]
failed_tests = [t for t in tests if t["status"] == 'failed']
for t in failed_tests:
print(f" Failed test {t["classname"]}, {name}, {t["name"]}")
+56
View File
@@ -0,0 +1,56 @@
# Copyright (c) 2021 Red Hat, Inc.
#
# Author:
# Cleber Rosa <crosa@redhat.com>
#
# This work is licensed under the terms of the GNU GPL, version 2 or
# later. See the COPYING file in the top-level directory.
#
# This is an ansible playbook file. Run it to set up systems with the
# environment needed to build QEMU.
---
- name: Installation of basic packages to build QEMU
hosts: all
tasks:
- name: Check for suitable ansible version
delegate_to: localhost
assert:
that:
- '((ansible_version.major == 2) and (ansible_version.minor >= 8)) or (ansible_version.major >= 3)'
msg: "Unsuitable ansible version, please use version 2.8.0 or later"
- name: Update apt cache / upgrade packages via apt
apt:
update_cache: yes
upgrade: yes
when:
- ansible_facts['distribution'] in ['Ubuntu', 'Debian']
# the package lists are updated by "make lcitool-refresh"
- name: Define package list file path for Ubuntu
set_fact:
package_file: "ubuntu/ubuntu-2404-{{ ansible_facts['architecture'] }}.yaml"
when:
- ansible_facts['distribution'] == 'Ubuntu'
- ansible_facts['distribution_version'] == '24.04'
- name: Define package list file path for Debian
set_fact:
package_file: "debian/debian-{{ ansible_facts['distribution_major_version'] }}-{{ ansible_facts['architecture'] }}.yaml"
when:
- ansible_facts['distribution'] == 'Debian'
- name: Include package lists based on OS and architecture
include_vars:
file: "{{ package_file }}"
when:
- package_file is exists
- name: Install packages for QEMU on Ubuntu/Debian
package:
name: "{{ packages }}"
when:
- package_file is exists
- packages is defined
@@ -0,0 +1,135 @@
# THIS FILE WAS AUTO-GENERATED
#
# $ lcitool variables --host-arch ppc64le debian-13 qemu
#
# https://gitlab.com/libvirt/libvirt-ci
packages:
- bash
- bc
- bindgen
- bison
- bsdextrautils
- bzip2
- ca-certificates
- ccache
- clang
- coreutils
- dbus
- debianutils
- diffutils
- exuberant-ctags
- findutils
- flex
- gcc
- gcovr
- gettext
- git
- hostname
- libaio-dev
- libasan8
- libasound2-dev
- libattr1-dev
- libbpf-dev
- libbrlapi-dev
- libbz2-dev
- libc6-dev
- libcacard-dev
- libcap-ng-dev
- libcapstone-dev
- libcbor-dev
- libclang-rt-dev
- libcmocka-dev
- libcurl4-gnutls-dev
- libdaxctl-dev
- libdrm-dev
- libepoxy-dev
- libfdt-dev
- libffi-dev
- libfuse3-dev
- libgbm-dev
- libgcrypt20-dev
- libglib2.0-dev
- libglusterfs-dev
- libgnutls28-dev
- libgtk-3-dev
- libgtk-vnc-2.0-dev
- libibverbs-dev
- libiscsi-dev
- libjemalloc-dev
- libjpeg62-turbo-dev
- libjson-c-dev
- liblttng-ust-dev
- liblzo2-dev
- libncursesw5-dev
- libnfs-dev
- libnuma-dev
- libpam0g-dev
- libpcre2-dev
- libpipewire-0.3-dev
- libpixman-1-dev
- libpng-dev
- libpulse-dev
- librbd-dev
- librdmacm-dev
- libsasl2-dev
- libsdl2-dev
- libsdl2-image-dev
- libseccomp-dev
- libselinux1-dev
- libslirp-dev
- libsnappy-dev
- libsndio-dev
- libspice-protocol-dev
- libspice-server-dev
- libssh-dev
- libstd-rust-dev
- libsystemd-dev
- libtasn1-6-dev
- libubsan1
- libudev-dev
- liburing-dev
- libusb-1.0-0-dev
- libusbredirhost-dev
- libvdeplug-dev
- libvirglrenderer-dev
- libvte-2.91-dev
- libxdp-dev
- libzstd-dev
- llvm
- locales
- make
- mtools
- multipath-tools
- ncat
- nettle-dev
- ninja-build
- openssh-client
- pkgconf
- python3
- python3-numpy
- python3-opencv
- python3-pillow
- python3-pip
- python3-setuptools
- python3-sphinx
- python3-sphinx-rtd-theme
- python3-tomli
- python3-venv
- python3-wheel
- python3-yaml
- rpm2cpio
- rustc
- sed
- socat
- sparse
- swtpm
- systemtap-sdt-dev
- tar
- tesseract-ocr
- tesseract-ocr-eng
- vulkan-tools
- xorriso
- zlib1g-dev
- zstd
+5 -13
View File
@@ -16,7 +16,7 @@
tasks:
- debug:
msg: 'Checking for a valid GitLab registration token'
failed_when: "gitlab_runner_registration_token == 'PLEASE_PROVIDE_A_VALID_TOKEN'"
failed_when: "gitlab_runner_authentication_token == 'PLEASE_PROVIDE_A_VALID_TOKEN'"
- name: Create a group for the gitlab-runner service
group:
@@ -56,12 +56,12 @@
url: "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh"
mode: 0755
when:
- ansible_facts['distribution'] == 'Ubuntu'
- ansible_facts['distribution'] in ['Ubuntu', 'Debian']
- name: Run gitlab-runner repo setup script (DEB)
shell: "/root/script.deb.sh"
when:
- ansible_facts['distribution'] == 'Ubuntu'
- ansible_facts['distribution'] in ['Ubuntu', 'Debian']
- name: Install gitlab-runner (DEB)
ansible.builtin.apt:
@@ -69,7 +69,7 @@
update_cache: yes
state: present
when:
- ansible_facts['distribution'] == 'Ubuntu'
- ansible_facts['distribution'] in ['Ubuntu', 'Debian']
# RPM setup
- name: Get gitlab-runner repo setup script (RPM)
@@ -95,15 +95,7 @@
# Register Runners
- name: Register the gitlab-runner
command: "/usr/bin/gitlab-runner register --non-interactive --url {{ gitlab_runner_server_url }} --registration-token {{ gitlab_runner_registration_token }} --executor shell --tag-list {{ ansible_facts[\"architecture\"] }},{{ ansible_facts[\"distribution\"]|lower }}_{{ ansible_facts[\"distribution_version\"] }} --description '{{ ansible_facts[\"distribution\"] }} {{ ansible_facts[\"distribution_version\"] }} {{ ansible_facts[\"architecture\"] }} ({{ ansible_facts[\"os_family\"] }})'"
# The secondary runner will still run under the single gitlab-runner service
- name: Register secondary gitlab-runner
command: "/usr/bin/gitlab-runner register --non-interactive --url {{ gitlab_runner_server_url }} --registration-token {{ gitlab_runner_registration_token }} --executor shell --tag-list aarch32,{{ ansible_facts[\"distribution\"]|lower }}_{{ ansible_facts[\"distribution_version\"] }} --description '{{ ansible_facts[\"distribution\"] }} {{ ansible_facts[\"distribution_version\"] }} {{ ansible_facts[\"architecture\"] }} ({{ ansible_facts[\"os_family\"] }})'"
when:
- ansible_facts['distribution'] == 'Ubuntu'
- ansible_facts['architecture'] == 'aarch64'
- ansible_facts['distribution_version'] == '22.04'
command: "/usr/bin/gitlab-runner register --non-interactive --url {{ gitlab_runner_server_url }} --token {{ gitlab_runner_authentication_token }} --executor shell"
- name: Install the gitlab-runner service using its own functionality
command: "/usr/bin/gitlab-runner install --user gitlab-runner --working-directory /home/gitlab-runner"
@@ -1,67 +0,0 @@
# Copyright (c) 2021 Red Hat, Inc.
#
# Author:
# Cleber Rosa <crosa@redhat.com>
#
# This work is licensed under the terms of the GNU GPL, version 2 or
# later. See the COPYING file in the top-level directory.
#
# This is an ansible playbook file. Run it to set up systems with the
# environment needed to build QEMU.
---
- name: Installation of basic packages to build QEMU
hosts: all
tasks:
- name: Check for suitable ansible version
delegate_to: localhost
assert:
that:
- '((ansible_version.major == 2) and (ansible_version.minor >= 8)) or (ansible_version.major >= 3)'
msg: "Unsuitable ansible version, please use version 2.8.0 or later"
- name: Add armhf foreign architecture to aarch64 hosts
command: dpkg --add-architecture armhf
when:
- ansible_facts['distribution'] == 'Ubuntu'
- ansible_facts['architecture'] == 'aarch64'
- name: Update apt cache / upgrade packages via apt
apt:
update_cache: yes
upgrade: yes
when:
- ansible_facts['distribution'] == 'Ubuntu'
# the package lists are updated by "make lcitool-refresh"
- name: Include package lists based on OS and architecture
include_vars:
file: "ubuntu-2204-{{ ansible_facts['architecture'] }}.yaml"
when:
- ansible_facts['distribution'] == 'Ubuntu'
- ansible_facts['distribution_version'] == '22.04'
- name: Install packages for QEMU on Ubuntu 22.04
package:
name: "{{ packages }}"
when:
- ansible_facts['distribution'] == 'Ubuntu'
- ansible_facts['distribution_version'] == '22.04'
- name: Install armhf cross-compile packages to build QEMU on AArch64 Ubuntu 22.04
package:
name:
- binutils-arm-linux-gnueabihf
- gcc-arm-linux-gnueabihf
- libblkid-dev:armhf
- libc6-dev:armhf
- libffi-dev:armhf
- libglib2.0-dev:armhf
- libmount-dev:armhf
- libpcre2-dev:armhf
- libpixman-1-dev:armhf
- zlib1g-dev:armhf
when:
- ansible_facts['distribution'] == 'Ubuntu'
- ansible_facts['distribution_version'] == '22.04'
- ansible_facts['architecture'] == 'aarch64'
@@ -1,127 +0,0 @@
# THIS FILE WAS AUTO-GENERATED
#
# $ lcitool variables --cross-arch armv7l ubuntu-2204 qemu
#
# https://gitlab.com/libvirt/libvirt-ci
packages:
- bash
- bc
- bison
- bsdextrautils
- bzip2
- ca-certificates
- ccache
- dbus
- debianutils
- diffutils
- exuberant-ctags
- findutils
- flex
- gcc
- gcovr
- gettext
- git
- hostname
- libglib2.0-dev
- libpcre2-dev
- libsndio-dev
- libspice-protocol-dev
- llvm
- locales
- make
- meson
- mtools
- ncat
- ninja-build
- openssh-client
- pkgconf
- python3
- python3-numpy
- python3-opencv
- python3-pillow
- python3-pip
- python3-sphinx
- python3-sphinx-rtd-theme
- python3-tomli
- python3-venv
- python3-yaml
- rpm2cpio
- sed
- socat
- sparse
- swtpm
- tar
- tesseract-ocr
- tesseract-ocr-eng
- xorriso
- zstd
- gcc-arm-linux-gnueabihf
- libaio-dev:armhf
- libasan6:armhf
- libasound2-dev:armhf
- libattr1-dev:armhf
- libbpf-dev:armhf
- libbrlapi-dev:armhf
- libbz2-dev:armhf
- libc6-dev:armhf
- libcacard-dev:armhf
- libcap-ng-dev:armhf
- libcapstone-dev:armhf
- libcmocka-dev:armhf
- libcurl4-gnutls-dev:armhf
- libdaxctl-dev:armhf
- libdrm-dev:armhf
- libepoxy-dev:armhf
- libfdt-dev:armhf
- libffi-dev:armhf
- libfuse3-dev:armhf
- libgbm-dev:armhf
- libgcrypt20-dev:armhf
- libglib2.0-dev:armhf
- libglusterfs-dev:armhf
- libgnutls28-dev:armhf
- libgtk-3-dev:armhf
- libibumad-dev:armhf
- libibverbs-dev:armhf
- libiscsi-dev:armhf
- libjemalloc-dev:armhf
- libjpeg-turbo8-dev:armhf
- libjson-c-dev:armhf
- liblttng-ust-dev:armhf
- liblzo2-dev:armhf
- libncursesw5-dev:armhf
- libnfs-dev:armhf
- libnuma-dev:armhf
- libpam0g-dev:armhf
- libpipewire-0.3-dev:armhf
- libpixman-1-dev:armhf
- libpng-dev:armhf
- libpulse-dev:armhf
- librbd-dev:armhf
- librdmacm-dev:armhf
- libsasl2-dev:armhf
- libsdl2-dev:armhf
- libsdl2-image-dev:armhf
- libseccomp-dev:armhf
- libselinux1-dev:armhf
- libslirp-dev:armhf
- libsnappy-dev:armhf
- libspice-server-dev:armhf
- libssh-dev:armhf
- libsystemd-dev:armhf
- libtasn1-6-dev:armhf
- libubsan1:armhf
- libudev-dev:armhf
- liburing-dev:armhf
- libusb-1.0-0-dev:armhf
- libusbredirhost-dev:armhf
- libvdeplug-dev:armhf
- libvirglrenderer-dev:armhf
- libvte-2.91-dev:armhf
- libxen-dev:armhf
- libzstd-dev:armhf
- nettle-dev:armhf
- systemtap-sdt-dev:armhf
- zlib1g-dev:armhf
@@ -1,18 +1,20 @@
# THIS FILE WAS AUTO-GENERATED
#
# $ lcitool variables --host-arch aarch64 ubuntu-2204 qemu
# $ lcitool variables --host-arch aarch64 ubuntu-2404 qemu
#
# https://gitlab.com/libvirt/libvirt-ci
packages:
- bash
- bc
- bindgen
- bison
- bsdextrautils
- bzip2
- ca-certificates
- ccache
- clang
- coreutils
- dbus
- debianutils
- diffutils
@@ -25,7 +27,7 @@ packages:
- git
- hostname
- libaio-dev
- libasan6
- libasan8
- libasound2-dev
- libattr1-dev
- libbpf-dev
@@ -36,6 +38,7 @@ packages:
- libcap-ng-dev
- libcapstone-dev
- libcbor-dev
- libclang-rt-dev
- libcmocka-dev
- libcurl4-gnutls-dev
- libdaxctl-dev
@@ -80,6 +83,7 @@ packages:
- libspice-protocol-dev
- libspice-server-dev
- libssh-dev
- libstd-rust-dev
- libsystemd-dev
- libtasn1-6-dev
- libubsan1
@@ -90,12 +94,12 @@ packages:
- libvdeplug-dev
- libvirglrenderer-dev
- libvte-2.91-dev
- libxdp-dev
- libxen-dev
- libzstd-dev
- llvm
- locales
- make
- meson
- mtools
- multipath-tools
- ncat
@@ -108,13 +112,15 @@ packages:
- python3-opencv
- python3-pillow
- python3-pip
- python3-setuptools
- python3-sphinx
- python3-sphinx-rtd-theme
- python3-tomli
- python3-venv
- python3-wheel
- python3-yaml
- rpm2cpio
- rustc
- rustc-1.83
- sed
- socat
- sparse
@@ -123,6 +129,7 @@ packages:
- tar
- tesseract-ocr
- tesseract-ocr-eng
- vulkan-tools
- xorriso
- zlib1g-dev
- zstd
@@ -1,18 +1,20 @@
# THIS FILE WAS AUTO-GENERATED
#
# $ lcitool variables --host-arch s390x ubuntu-2204 qemu
# $ lcitool variables --host-arch s390x ubuntu-2404 qemu
#
# https://gitlab.com/libvirt/libvirt-ci
packages:
- bash
- bc
- bindgen
- bison
- bsdextrautils
- bzip2
- ca-certificates
- ccache
- clang
- coreutils
- dbus
- debianutils
- diffutils
@@ -25,7 +27,7 @@ packages:
- git
- hostname
- libaio-dev
- libasan6
- libasan8
- libasound2-dev
- libattr1-dev
- libbpf-dev
@@ -36,6 +38,7 @@ packages:
- libcap-ng-dev
- libcapstone-dev
- libcbor-dev
- libclang-rt-dev
- libcmocka-dev
- libcurl4-gnutls-dev
- libdaxctl-dev
@@ -79,6 +82,7 @@ packages:
- libsndio-dev
- libspice-protocol-dev
- libssh-dev
- libstd-rust-dev
- libsystemd-dev
- libtasn1-6-dev
- libubsan1
@@ -89,11 +93,11 @@ packages:
- libvdeplug-dev
- libvirglrenderer-dev
- libvte-2.91-dev
- libxdp-dev
- libzstd-dev
- llvm
- locales
- make
- meson
- mtools
- multipath-tools
- ncat
@@ -106,13 +110,15 @@ packages:
- python3-opencv
- python3-pillow
- python3-pip
- python3-setuptools
- python3-sphinx
- python3-sphinx-rtd-theme
- python3-tomli
- python3-venv
- python3-wheel
- python3-yaml
- rpm2cpio
- rustc
- rustc-1.83
- sed
- socat
- sparse
@@ -121,6 +127,7 @@ packages:
- tar
- tesseract-ocr
- tesseract-ocr-eng
- vulkan-tools
- xorriso
- zlib1g-dev
- zstd
+3 -2
View File
@@ -6,5 +6,6 @@ ansible_to_gitlab_arch:
x86_64: amd64
aarch64: arm64
s390x: s390x
# A unique token made available by GitLab to your project for registering runners
gitlab_runner_registration_token: PLEASE_PROVIDE_A_VALID_TOKEN
# A unique token made obtained from GitLab for each runner
# see: https://gitlab.com/PROJECT/REPO/-/runners/new
gitlab_runner_authentication_token: PLEASE_PROVIDE_A_VALID_TOKEN
+3 -3
View File
@@ -130,8 +130,8 @@ for f in "$@"; do
*include/qemu/compiler.h | \
*include/qemu/qemu-plugin.h | \
*include/glib-compat.h | \
*include/sysemu/os-posix.h | \
*include/sysemu/os-win32.h | \
*include/system/os-posix.h | \
*include/system/os-win32.h | \
*include/standard-headers/ )
# Removing include lines from osdep.h itself would be counterproductive.
echo "SKIPPING $f (special case header)"
@@ -174,7 +174,7 @@ for f in "$@"; do
<limits.h> <unistd.h> <time.h> <ctype.h> <errno.h> <fcntl.h>
<sys/stat.h> <sys/time.h> <assert.h> <signal.h> <glib.h>
<sys/stat.h> <sys/time.h> <assert.h> <signal.h> <glib.h> <sys/mman.h>
"sysemu/os-posix.h, sysemu/os-win32.h "glib-compat.h"
"system/os-posix.h, system/os-win32.h "glib-compat.h"
"qemu/typedefs.h"
))' "$f"
+45
View File
@@ -0,0 +1,45 @@
#!/usr/bin/env python3
#
# SPDX-License-Identifier: GPL-2.0-or-later
#
"""Delete stale assets from the download cache of the functional tests"""
import os
import stat
import sys
import time
from pathlib import Path
cache_dir_env = os.getenv('QEMU_TEST_CACHE_DIR')
if cache_dir_env:
cache_dir = Path(cache_dir_env, "download")
else:
cache_dir = Path(Path("~").expanduser(), ".cache", "qemu", "download")
if not cache_dir.exists():
print(f"Cache dir {cache_dir} does not exist!", file=sys.stderr)
sys.exit(1)
os.chdir(cache_dir)
for file in cache_dir.iterdir():
# Only consider the files that use a sha256 as filename:
if len(file.name) != 64:
continue
try:
timestamp = int(file.with_suffix(".stamp").read_text())
except FileNotFoundError:
# Assume it's an old file that was already in the cache before we
# added the code for evicting stale assets. Use the release date
# of QEMU v10.1 as a default timestamp.
timestamp = time.mktime((2025, 8, 26, 0, 0, 0, 0, 0, 0))
age = time.time() - timestamp
# Delete files older than half of a year (183 days * 24h * 60m * 60s)
if age > 15811200:
print(f"Removing {cache_dir}/{file.name}.")
file.chmod(stat.S_IWRITE)
file.unlink()
+1 -5
View File
@@ -23,11 +23,7 @@
#define G_GNUC_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
#define G_GNUC_NULL_TERMINATED __attribute__((sentinel))
#if defined(_WIN32) && (defined(__x86_64__) || defined(__i386__))
# define QEMU_PACKED __attribute__((gcc_struct, packed))
#else
# define QEMU_PACKED __attribute__((packed))
#endif
#define QEMU_PACKED __attribute__((packed))
#define cat(x,y) x ## y
#define cat2(x,y) cat(x,y)
-22
View File
@@ -21,13 +21,6 @@ expression E1, E2, E3, E4, E5;
+ address_space_rw(E1, E2, E3, E4, E5, true)
|
- cpu_physical_memory_rw(E1, E2, E3, 0)
+ cpu_physical_memory_rw(E1, E2, E3, false)
|
- cpu_physical_memory_rw(E1, E2, E3, 1)
+ cpu_physical_memory_rw(E1, E2, E3, true)
|
- cpu_physical_memory_map(E1, E2, 0)
+ cpu_physical_memory_map(E1, E2, false)
|
@@ -62,18 +55,6 @@ symbol true, false;
+ address_space_write(E1, E2, E3, E4, E5)
)
// Avoid uses of cpu_physical_memory_rw() with a constant is_write argument.
@@
expression E1, E2, E3;
@@
(
- cpu_physical_memory_rw(E1, E2, E3, false)
+ cpu_physical_memory_read(E1, E2, E3)
|
- cpu_physical_memory_rw(E1, E2, E3, true)
+ cpu_physical_memory_write(E1, E2, E3)
)
// Remove useless cast
@@
expression E1, E2, E3, E4, E5, E6;
@@ -93,9 +74,6 @@ type T;
+ address_space_write_rom(E1, E2, E3, E4, E5)
|
- cpu_physical_memory_rw(E1, (T *)(E2), E3, E4)
+ cpu_physical_memory_rw(E1, E2, E3, E4)
|
- cpu_physical_memory_read(E1, (T *)(E2), E3)
+ cpu_physical_memory_read(E1, E2, E3)
|
@@ -798,7 +798,8 @@ class RedundantTypeSizes(TypeInfoVar):
#
#
# if 'class_init' not in fields:
# yield self.prepend(('static void %s_class_init(ObjectClass *oc, void *data)\n'
# yield self.prepend(('static void %s_class_init(ObjectClass *oc,\n'
# 'const void *data)\n'
# '{\n'
# '}\n\n') % (ids.lowercase))
# yield self.append_field('class_init', ids.lowercase+'_class_init')
@@ -901,26 +902,6 @@ class TypeRegisterCall(FileMatch):
regexp = S(r'^[ \t]*', NAMED('func_name', 'type_register'),
r'\s*\(&\s*', NAMED('name', RE_IDENTIFIER), r'\s*\);[ \t]*\n')
class MakeTypeRegisterStatic(TypeRegisterCall):
"""Make type_register() call static if variable is static const"""
def gen_patches(self):
var = self.file.find_match(TypeInfoVar, self.name)
if var is None:
self.warn("can't find TypeInfo var declaration for %s", self.name)
return
if var.is_static() and var.is_const():
yield self.group_match('func_name').make_patch('type_register_static')
class MakeTypeRegisterNotStatic(TypeRegisterStaticCall):
"""Make type_register() call static if variable is static const"""
def gen_patches(self):
var = self.file.find_match(TypeInfoVar, self.name)
if var is None:
self.warn("can't find TypeInfo var declaration for %s", self.name)
return
if not var.is_static() or not var.is_const():
yield self.group_match('func_name').make_patch('type_register')
class TypeInfoMacro(FileMatch):
"""TYPE_INFO macro usage"""
regexp = S(r'^[ \t]*TYPE_INFO\s*\(\s*', NAMED('name', RE_IDENTIFIER), r'\s*\)[ \t]*;?[ \t]*\n')
@@ -70,15 +70,15 @@ static const TypeInfo char_file_type_info = {
.name = armsse_variants[i].name,
.parent = TYPE_ARMSSE,
.class_init = armsse_class_init,
.class_data = (void *)&armsse_variants[i],
.class_data = &armsse_variants[i],
};''', re.MULTILINE)
print(RE_ARRAY_ITEM)
assert fullmatch(RE_ARRAY_ITEM, '{ TYPE_HOTPLUG_HANDLER },')
assert fullmatch(RE_ARRAY_ITEM, '{ TYPE_ACPI_DEVICE_IF },')
assert fullmatch(RE_ARRAY_ITEM, '{ }')
assert fullmatch(RE_ARRAY_CAST, '(InterfaceInfo[])')
assert fullmatch(RE_ARRAY, '''(InterfaceInfo[]) {
assert fullmatch(RE_ARRAY_CAST, '(const InterfaceInfo[])')
assert fullmatch(RE_ARRAY, '''(const InterfaceInfo[]) {
{ TYPE_HOTPLUG_HANDLER },
{ TYPE_ACPI_DEVICE_IF },
{ }
@@ -98,7 +98,7 @@ static const TypeInfo char_file_type_info = {
.parent = TYPE_DEVICE,
.instance_size = sizeof(CRBState),
.class_init = tpm_crb_class_init,
.interfaces = (InterfaceInfo[]) {
.interfaces = (const InterfaceInfo[]) {
{ TYPE_TPM_IF },
{ }
}
@@ -134,7 +134,7 @@ static const TypeInfo char_file_type_info = {
.instance_size = sizeof(AcpiGedState),
.instance_init = acpi_ged_initfn,
.class_init = acpi_ged_class_init,
.interfaces = (InterfaceInfo[]) {
.interfaces = (const InterfaceInfo[]) {
{ TYPE_HOTPLUG_HANDLER },
{ TYPE_ACPI_DEVICE_IF },
{ }
@@ -164,7 +164,7 @@ static const TypeInfo char_file_type_info = {
.parent = TYPE_DEVICE,
.instance_size = sizeof(CRBState),
.class_init = tpm_crb_class_init,
.interfaces = (InterfaceInfo[]) {
.interfaces = (const InterfaceInfo[]) {
{ TYPE_TPM_IF },
{ }
}
@@ -264,12 +264,12 @@ def test_initial_includes():
#define SILENT_ES1370
#include "qemu/osdep.h"
#include "hw/audio/soundhw.h"
#include "audio/audio.h"
#include "hw/audio/model.h"
#include "qemu/audio.h"
#include "hw/pci/pci.h"
#include "migration/vmstate.h"
#include "qemu/module.h"
#include "sysemu/dma.h"
#include "system/dma.h"
/* Missing stuff:
SCTRL_P[12](END|ST)INC
@@ -278,5 +278,5 @@ def test_initial_includes():
m = InitialIncludes.domatch(c)
assert m
print(repr(m.group(0)))
assert m.group(0).endswith('#include "sysemu/dma.h"\n')
assert m.group(0).endswith('#include "system/dma.h"\n')
+5 -2
View File
@@ -76,7 +76,7 @@ chardev
~ .*/qemu((/include)?/chardev/.*)
crypto
~ .*/qemu((/include)?/crypto/.*|/hw/.*/.*crypto.*|(/include/sysemu|/backends)/cryptodev.*|/host/include/.*/host/crypto/.*)
~ .*/qemu((/include)?/crypto/.*|/hw/.*/.*crypto.*|(/include/system|/backends)/cryptodev.*|/host/include/.*/host/crypto/.*)
disas
~ .*/qemu((/include)?/disas.*)
@@ -144,9 +144,12 @@ kvm
tcg
~ .*/qemu(/accel/tcg|/replay|/tcg)/.*
sysemu
system
~ .*/qemu(/system/.*|/accel/.*)
plugins
~ .*/qemu(/contrib|/tests/tcg)?/plugins/.*
(headers)
~ .*/qemu(/include/.*)
+4 -3
View File
@@ -1016,9 +1016,12 @@ def infer_format(arg, fieldmask, flds, width):
else:
var_flds[n] = c
if not arg:
arg = infer_argument_set(flds)
# Look for an existing format with the same argument set and fields
for fmt in formats.values():
if arg and fmt.base != arg:
if fmt.base != arg:
continue
if fieldmask != fmt.fieldmask:
continue
@@ -1029,8 +1032,6 @@ def infer_format(arg, fieldmask, flds, width):
return (fmt, const_flds)
name = decode_function + '_Fmt_' + str(len(formats))
if not arg:
arg = infer_argument_set(flds)
fmt = Format(name, 0, arg, 0, 0, 0, fieldmask, var_flds, width)
formats[name] = fmt
+2 -3
View File
@@ -16,8 +16,7 @@
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
# with this program; if not, see <https://www.gnu.org/licenses/>.
"""
Run QEMU with all combinations of -machine and -device types,
@@ -528,7 +527,7 @@ def main():
# Async QMP, when in use, is chatty about connection failures.
# This script knowingly generates a ton of connection errors.
# Silence this logger.
logging.getLogger('qemu.qmp.qmp_client').setLevel(logging.CRITICAL)
logging.getLogger('qemu.qmp.protocol').setLevel(logging.CRITICAL)
fatal_failures = []
wl_stats = {}
+190
View File
@@ -0,0 +1,190 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-or-later
"""
get-wraps-from-cargo-registry.py - Update Meson subprojects from a global registry
"""
# Copyright (C) 2025 Red Hat, Inc.
#
# Author: Paolo Bonzini <pbonzini@redhat.com>
import argparse
import configparser
import filecmp
import glob
import os
import subprocess
import sys
def get_name_and_semver(namever: str) -> tuple[str, str]:
"""Split a subproject name into its name and semantic version parts"""
parts = namever.rsplit("-", 1)
if len(parts) != 2:
return namever, ""
return parts[0], parts[1]
class UpdateSubprojects:
cargo_registry: str
top_srcdir: str
dry_run: bool
changes: int = 0
def find_installed_crate(self, namever: str) -> str | None:
"""Find installed crate matching name and semver prefix"""
name, semver = get_name_and_semver(namever)
# exact version match
path = os.path.join(self.cargo_registry, f"{name}-{semver}")
if os.path.exists(path):
return f"{name}-{semver}"
# semver match
matches = sorted(glob.glob(f"{path}.*"))
return os.path.basename(matches[0]) if matches else None
def compare_build_rs(self, orig_dir: str, registry_namever: str) -> None:
"""Warn if the build.rs in the original directory differs from the registry version."""
orig_build_rs = os.path.join(orig_dir, "build.rs")
new_build_rs = os.path.join(self.cargo_registry, registry_namever, "build.rs")
msg = None
if os.path.isfile(orig_build_rs) != os.path.isfile(new_build_rs):
if os.path.isfile(orig_build_rs):
msg = f"build.rs removed in {registry_namever}"
if os.path.isfile(new_build_rs):
msg = f"build.rs added in {registry_namever}"
elif os.path.isfile(orig_build_rs) and not filecmp.cmp(orig_build_rs, new_build_rs):
msg = f"build.rs changed from {orig_dir} to {registry_namever}"
if msg:
print(f"⚠️ Warning: {msg}")
print(" This may affect the build process - please review the differences.")
def update_subproject(self, wrap_file: str, registry_namever: str) -> None:
"""Modify [wrap-file] section to point to self.cargo_registry."""
assert wrap_file.endswith("-rs.wrap")
wrap_name = wrap_file[:-5]
env = os.environ.copy()
env["MESON_PACKAGE_CACHE_DIR"] = self.cargo_registry
config = configparser.ConfigParser()
config.read(wrap_file)
if "wrap-file" not in config:
return
# do not download the wrap, always use the local copy
orig_dir = config["wrap-file"]["directory"]
if os.path.exists(orig_dir) and orig_dir != registry_namever:
self.compare_build_rs(orig_dir, registry_namever)
if self.dry_run:
if orig_dir == registry_namever:
print(f"Will install {orig_dir} from registry.")
else:
print(f"Will replace {orig_dir} with {registry_namever}.")
self.changes += 1
return
config["wrap-file"]["directory"] = registry_namever
for key in list(config["wrap-file"].keys()):
if key.startswith("source"):
del config["wrap-file"][key]
# replace existing directory with installed version
if os.path.exists(orig_dir):
subprocess.run(
["meson", "subprojects", "purge", "--confirm", wrap_name],
cwd=self.top_srcdir,
env=env,
check=True,
)
with open(wrap_file, "w") as f:
config.write(f)
if orig_dir == registry_namever:
print(f"Installing {orig_dir} from registry.")
else:
print(f"Replacing {orig_dir} with {registry_namever}.")
patch_dir = config["wrap-file"]["patch_directory"]
patch_dir = os.path.join("packagefiles", patch_dir)
_, ver = registry_namever.rsplit("-", 1)
subprocess.run(
["meson", "rewrite", "kwargs", "set", "project", "/", "version", ver],
cwd=patch_dir,
env=env,
check=True,
)
subprocess.run(
["meson", "subprojects", "download", wrap_name],
cwd=self.top_srcdir,
env=env,
check=True,
)
self.changes += 1
@staticmethod
def parse_cmdline() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Replace Meson subprojects with packages in a Cargo registry"
)
parser.add_argument(
"--cargo-registry",
default=os.environ.get("CARGO_REGISTRY"),
help="Path to Cargo registry (default: CARGO_REGISTRY env var)",
)
parser.add_argument(
"--dry-run",
action="store_true",
default=False,
help="Do not actually replace anything",
)
args = parser.parse_args()
if not args.cargo_registry:
print("error: CARGO_REGISTRY environment variable not set and --cargo-registry not provided")
sys.exit(1)
return args
def __init__(self, args: argparse.Namespace):
self.cargo_registry = args.cargo_registry
self.dry_run = args.dry_run
self.top_srcdir = os.getcwd()
def main(self) -> None:
if not os.path.exists("subprojects"):
print("'subprojects' directory not found, nothing to do.")
return
os.chdir("subprojects")
for wrap_file in sorted(glob.glob("*-rs.wrap")):
namever = wrap_file[:-8] # Remove '-rs.wrap'
registry_namever = self.find_installed_crate(namever)
if not registry_namever:
print(f"No installed crate found for {wrap_file}")
continue
self.update_subproject(wrap_file, registry_namever)
if self.changes:
if self.dry_run:
print("Rerun without --dry-run to apply changes.")
else:
print(f"{self.changes} subproject(s) updated!")
else:
print("No changes.")
if __name__ == "__main__":
args = UpdateSubprojects.parse_cmdline()
UpdateSubprojects(args).main()
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env python3
#
# SPDX-License-Identifier: GPL-2.0-or-later
#
# Copyright (C) 2024-2025 Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
"""
Handle ACPI GHESv2 error injection logic QEMU QMP interface.
"""
import argparse
import sys
from arm_processor_error import ArmProcessorEinj
EINJ_DESC = """
Handle ACPI GHESv2 error injection logic QEMU QMP interface.
It allows using UEFI BIOS EINJ features to generate GHES records.
It helps testing CPER and GHES drivers at the guest OS and how
userspace applications at the guest handle them.
"""
def main():
"""Main program"""
# Main parser - handle generic args like QEMU QMP TCP socket options
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter,
usage="%(prog)s [options]",
description=EINJ_DESC)
g_options = parser.add_argument_group("QEMU QMP socket options")
g_options.add_argument("-H", "--host", default="localhost", type=str,
help="host name")
g_options.add_argument("-P", "--port", default=4445, type=int,
help="TCP port number")
g_options.add_argument('-d', '--debug', action='store_true')
subparsers = parser.add_subparsers()
ArmProcessorEinj(subparsers)
args = parser.parse_args()
if "func" in args:
args.func(args)
else:
sys.exit(f"Please specify a valid command for {sys.argv[0]}")
if __name__ == "__main__":
main()
-2442
View File
File diff suppressed because it is too large Load Diff
+325
View File
@@ -0,0 +1,325 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>.
#
# pylint: disable=C0103,R0915
#
# Converted from the kernel-doc script originally written in Perl
# under GPLv2, copyrighted since 1998 by the following authors:
#
# Aditya Srivastava <yashsri421@gmail.com>
# Akira Yokosawa <akiyks@gmail.com>
# Alexander A. Klimov <grandmaster@al2klimov.de>
# Alexander Lobakin <aleksander.lobakin@intel.com>
# André Almeida <andrealmeid@igalia.com>
# Andy Shevchenko <andriy.shevchenko@linux.intel.com>
# Anna-Maria Behnsen <anna-maria@linutronix.de>
# Armin Kuster <akuster@mvista.com>
# Bart Van Assche <bart.vanassche@sandisk.com>
# Ben Hutchings <ben@decadent.org.uk>
# Borislav Petkov <bbpetkov@yahoo.de>
# Chen-Yu Tsai <wenst@chromium.org>
# Coco Li <lixiaoyan@google.com>
# Conchúr Navid <conchur@web.de>
# Daniel Santos <daniel.santos@pobox.com>
# Danilo Cesar Lemes de Paula <danilo.cesar@collabora.co.uk>
# Dan Luedtke <mail@danrl.de>
# Donald Hunter <donald.hunter@gmail.com>
# Gabriel Krisman Bertazi <krisman@collabora.co.uk>
# Greg Kroah-Hartman <gregkh@linuxfoundation.org>
# Harvey Harrison <harvey.harrison@gmail.com>
# Horia Geanta <horia.geanta@freescale.com>
# Ilya Dryomov <idryomov@gmail.com>
# Jakub Kicinski <kuba@kernel.org>
# Jani Nikula <jani.nikula@intel.com>
# Jason Baron <jbaron@redhat.com>
# Jason Gunthorpe <jgg@nvidia.com>
# Jérémy Bobbio <lunar@debian.org>
# Johannes Berg <johannes.berg@intel.com>
# Johannes Weiner <hannes@cmpxchg.org>
# Jonathan Cameron <Jonathan.Cameron@huawei.com>
# Jonathan Corbet <corbet@lwn.net>
# Jonathan Neuschäfer <j.neuschaefer@gmx.net>
# Kamil Rytarowski <n54@gmx.com>
# Kees Cook <kees@kernel.org>
# Laurent Pinchart <laurent.pinchart@ideasonboard.com>
# Levin, Alexander (Sasha Levin) <alexander.levin@verizon.com>
# Linus Torvalds <torvalds@linux-foundation.org>
# Lucas De Marchi <lucas.demarchi@profusion.mobi>
# Mark Rutland <mark.rutland@arm.com>
# Markus Heiser <markus.heiser@darmarit.de>
# Martin Waitz <tali@admingilde.org>
# Masahiro Yamada <masahiroy@kernel.org>
# Matthew Wilcox <willy@infradead.org>
# Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
# Michal Wajdeczko <michal.wajdeczko@intel.com>
# Michael Zucchi
# Mike Rapoport <rppt@linux.ibm.com>
# Niklas Söderlund <niklas.soderlund@corigine.com>
# Nishanth Menon <nm@ti.com>
# Paolo Bonzini <pbonzini@redhat.com>
# Pavan Kumar Linga <pavan.kumar.linga@intel.com>
# Pavel Pisa <pisa@cmp.felk.cvut.cz>
# Peter Maydell <peter.maydell@linaro.org>
# Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
# Randy Dunlap <rdunlap@infradead.org>
# Richard Kennedy <richard@rsk.demon.co.uk>
# Rich Walker <rw@shadow.org.uk>
# Rolf Eike Beer <eike-kernel@sf-tec.de>
# Sakari Ailus <sakari.ailus@linux.intel.com>
# Silvio Fricke <silvio.fricke@gmail.com>
# Simon Huggins
# Tim Waugh <twaugh@redhat.com>
# Tomasz Warniełło <tomasz.warniello@gmail.com>
# Utkarsh Tripathi <utripathi2002@gmail.com>
# valdis.kletnieks@vt.edu <valdis.kletnieks@vt.edu>
# Vegard Nossum <vegard.nossum@oracle.com>
# Will Deacon <will.deacon@arm.com>
# Yacine Belkadi <yacine.belkadi.1@gmail.com>
# Yujie Liu <yujie.liu@intel.com>
"""
kernel_doc
==========
Print formatted kernel documentation to stdout
Read C language source or header FILEs, extract embedded
documentation comments, and print formatted documentation
to standard output.
The documentation comments are identified by the "/**"
opening comment mark.
See Documentation/doc-guide/kernel-doc.rst for the
documentation comment syntax.
"""
import argparse
import logging
import os
import sys
# Import Python modules
LIB_DIR = "lib/kdoc"
SRC_DIR = os.path.dirname(os.path.realpath(__file__))
sys.path.insert(0, os.path.join(SRC_DIR, LIB_DIR))
from kdoc_files import KernelFiles # pylint: disable=C0413
from kdoc_output import RestFormat, ManFormat # pylint: disable=C0413
DESC = """
Read C language source or header FILEs, extract embedded documentation comments,
and print formatted documentation to standard output.
The documentation comments are identified by the "/**" opening comment mark.
See Documentation/doc-guide/kernel-doc.rst for the documentation comment syntax.
"""
EXPORT_FILE_DESC = """
Specify an additional FILE in which to look for EXPORT_SYMBOL information.
May be used multiple times.
"""
EXPORT_DESC = """
Only output documentation for the symbols that have been
exported using EXPORT_SYMBOL() and related macros in any input
FILE or -export-file FILE.
"""
INTERNAL_DESC = """
Only output documentation for the symbols that have NOT been
exported using EXPORT_SYMBOL() and related macros in any input
FILE or -export-file FILE.
"""
FUNCTION_DESC = """
Only output documentation for the given function or DOC: section
title. All other functions and DOC: sections are ignored.
May be used multiple times.
"""
NOSYMBOL_DESC = """
Exclude the specified symbol from the output documentation.
May be used multiple times.
"""
FILES_DESC = """
Header and C source files to be parsed.
"""
WARN_CONTENTS_BEFORE_SECTIONS_DESC = """
Warns if there are contents before sections (deprecated).
This option is kept just for backward-compatibility, but it does nothing,
neither here nor at the original Perl script.
"""
class MsgFormatter(logging.Formatter):
"""Helper class to format warnings on a similar way to kernel-doc.pl"""
def format(self, record):
record.levelname = record.levelname.capitalize()
return logging.Formatter.format(self, record)
def main():
"""Main program"""
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter,
description=DESC)
# Normal arguments
parser.add_argument("-v", "-verbose", "--verbose", action="store_true",
help="Verbose output, more warnings and other information.")
parser.add_argument("-d", "-debug", "--debug", action="store_true",
help="Enable debug messages")
parser.add_argument("-M", "-modulename", "--modulename",
default="Kernel API",
help="Allow setting a module name at the output.")
parser.add_argument("-l", "-enable-lineno", "--enable_lineno",
action="store_true",
help="Enable line number output (only in ReST mode)")
# Arguments to control the warning behavior
parser.add_argument("-Wreturn", "--wreturn", action="store_true",
help="Warns about the lack of a return markup on functions.")
parser.add_argument("-Wshort-desc", "-Wshort-description", "--wshort-desc",
action="store_true",
help="Warns if initial short description is missing")
parser.add_argument("-Wcontents-before-sections",
"--wcontents-before-sections", action="store_true",
help=WARN_CONTENTS_BEFORE_SECTIONS_DESC)
parser.add_argument("-Wall", "--wall", action="store_true",
help="Enable all types of warnings")
parser.add_argument("-Werror", "--werror", action="store_true",
help="Treat warnings as errors.")
parser.add_argument("-export-file", "--export-file", action='append',
help=EXPORT_FILE_DESC)
# Output format mutually-exclusive group
out_group = parser.add_argument_group("Output format selection (mutually exclusive)")
out_fmt = out_group.add_mutually_exclusive_group()
out_fmt.add_argument("-m", "-man", "--man", action="store_true",
help="Output troff manual page format.")
out_fmt.add_argument("-r", "-rst", "--rst", action="store_true",
help="Output reStructuredText format (default).")
out_fmt.add_argument("-N", "-none", "--none", action="store_true",
help="Do not output documentation, only warnings.")
# Output selection mutually-exclusive group
sel_group = parser.add_argument_group("Output selection (mutually exclusive)")
sel_mut = sel_group.add_mutually_exclusive_group()
sel_mut.add_argument("-e", "-export", "--export", action='store_true',
help=EXPORT_DESC)
sel_mut.add_argument("-i", "-internal", "--internal", action='store_true',
help=INTERNAL_DESC)
sel_mut.add_argument("-s", "-function", "--symbol", action='append',
help=FUNCTION_DESC)
# Those are valid for all 3 types of filter
parser.add_argument("-n", "-nosymbol", "--nosymbol", action='append',
help=NOSYMBOL_DESC)
parser.add_argument("-D", "-no-doc-sections", "--no-doc-sections",
action='store_true', help="Don't outputt DOC sections")
parser.add_argument("files", metavar="FILE",
nargs="+", help=FILES_DESC)
args = parser.parse_args()
if args.wall:
args.wreturn = True
args.wshort_desc = True
args.wcontents_before_sections = True
logger = logging.getLogger()
if not args.debug:
logger.setLevel(logging.INFO)
else:
logger.setLevel(logging.DEBUG)
formatter = MsgFormatter('%(levelname)s: %(message)s')
handler = logging.StreamHandler()
handler.setFormatter(formatter)
logger.addHandler(handler)
python_ver = sys.version_info[:2]
if python_ver < (3,6):
logger.warning("Python 3.6 or later is required by kernel-doc")
# Return 0 here to avoid breaking compilation
sys.exit(0)
if python_ver < (3,7):
logger.warning("Python 3.7 or later is required for correct results")
if args.man:
out_style = ManFormat(modulename=args.modulename)
elif args.none:
out_style = None
else:
out_style = RestFormat()
kfiles = KernelFiles(verbose=args.verbose,
out_style=out_style, werror=args.werror,
wreturn=args.wreturn, wshort_desc=args.wshort_desc,
wcontents_before_sections=args.wcontents_before_sections)
kfiles.parse(args.files, export_file=args.export_file)
for t in kfiles.msg(enable_lineno=args.enable_lineno, export=args.export,
internal=args.internal, symbol=args.symbol,
nosymbol=args.nosymbol, export_file=args.export_file,
no_doc_sections=args.no_doc_sections):
msg = t[1]
if msg:
print(msg)
error_count = kfiles.errors
if not error_count:
sys.exit(0)
if args.werror:
print(f"{error_count} warnings as errors")
sys.exit(error_count)
if args.verbose:
print(f"{error_count} errors")
if args.none:
sys.exit(0)
sys.exit(error_count)
# Call main method
if __name__ == "__main__":
main()
+291
View File
@@ -0,0 +1,291 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>.
#
# pylint: disable=R0903,R0913,R0914,R0917
"""
Parse lernel-doc tags on multiple kernel source files.
"""
import argparse
import logging
import os
import re
from kdoc_parser import KernelDoc
from kdoc_output import OutputFormat
class GlobSourceFiles:
"""
Parse C source code file names and directories via an Interactor.
"""
def __init__(self, srctree=None, valid_extensions=None):
"""
Initialize valid extensions with a tuple.
If not defined, assume default C extensions (.c and .h)
It would be possible to use python's glob function, but it is
very slow, and it is not interactive. So, it would wait to read all
directories before actually do something.
So, let's use our own implementation.
"""
if not valid_extensions:
self.extensions = (".c", ".h")
else:
self.extensions = valid_extensions
self.srctree = srctree
def _parse_dir(self, dirname):
"""Internal function to parse files recursively"""
with os.scandir(dirname) as obj:
for entry in obj:
name = os.path.join(dirname, entry.name)
if entry.is_dir():
yield from self._parse_dir(name)
if not entry.is_file():
continue
basename = os.path.basename(name)
if not basename.endswith(self.extensions):
continue
yield name
def parse_files(self, file_list, file_not_found_cb):
"""
Define an interator to parse all source files from file_list,
handling directories if any
"""
if not file_list:
return
for fname in file_list:
if self.srctree:
f = os.path.join(self.srctree, fname)
else:
f = fname
if os.path.isdir(f):
yield from self._parse_dir(f)
elif os.path.isfile(f):
yield f
elif file_not_found_cb:
file_not_found_cb(fname)
class KernelFiles():
"""
Parse kernel-doc tags on multiple kernel source files.
There are two type of parsers defined here:
- self.parse_file(): parses both kernel-doc markups and
EXPORT_SYMBOL* macros;
- self.process_export_file(): parses only EXPORT_SYMBOL* macros.
"""
def warning(self, msg):
"""Ancillary routine to output a warning and increment error count"""
self.config.log.warning(msg)
self.errors += 1
def error(self, msg):
"""Ancillary routine to output an error and increment error count"""
self.config.log.error(msg)
self.errors += 1
def parse_file(self, fname):
"""
Parse a single Kernel source.
"""
# Prevent parsing the same file twice if results are cached
if fname in self.files:
return
doc = KernelDoc(self.config, fname)
export_table, entries = doc.parse_kdoc()
self.export_table[fname] = export_table
self.files.add(fname)
self.export_files.add(fname) # parse_kdoc() already check exports
self.results[fname] = entries
def process_export_file(self, fname):
"""
Parses EXPORT_SYMBOL* macros from a single Kernel source file.
"""
# Prevent parsing the same file twice if results are cached
if fname in self.export_files:
return
doc = KernelDoc(self.config, fname)
export_table = doc.parse_export()
if not export_table:
self.error(f"Error: Cannot check EXPORT_SYMBOL* on {fname}")
export_table = set()
self.export_table[fname] = export_table
self.export_files.add(fname)
def file_not_found_cb(self, fname):
"""
Callback to warn if a file was not found.
"""
self.error(f"Cannot find file {fname}")
def __init__(self, verbose=False, out_style=None,
werror=False, wreturn=False, wshort_desc=False,
wcontents_before_sections=False,
logger=None):
"""
Initialize startup variables and parse all files
"""
if not verbose:
verbose = bool(os.environ.get("KBUILD_VERBOSE", 0))
if out_style is None:
out_style = OutputFormat()
if not werror:
kcflags = os.environ.get("KCFLAGS", None)
if kcflags:
match = re.search(r"(\s|^)-Werror(\s|$)/", kcflags)
if match:
werror = True
# reading this variable is for backwards compat just in case
# someone was calling it with the variable from outside the
# kernel's build system
kdoc_werror = os.environ.get("KDOC_WERROR", None)
if kdoc_werror:
werror = kdoc_werror
# Some variables are global to the parser logic as a whole as they are
# used to send control configuration to KernelDoc class. As such,
# those variables are read-only inside the KernelDoc.
self.config = argparse.Namespace
self.config.verbose = verbose
self.config.werror = werror
self.config.wreturn = wreturn
self.config.wshort_desc = wshort_desc
self.config.wcontents_before_sections = wcontents_before_sections
if not logger:
self.config.log = logging.getLogger("kernel-doc")
else:
self.config.log = logger
self.config.warning = self.warning
self.config.src_tree = os.environ.get("SRCTREE", None)
# Initialize variables that are internal to KernelFiles
self.out_style = out_style
self.errors = 0
self.results = {}
self.files = set()
self.export_files = set()
self.export_table = {}
def parse(self, file_list, export_file=None):
"""
Parse all files
"""
glob = GlobSourceFiles(srctree=self.config.src_tree)
for fname in glob.parse_files(file_list, self.file_not_found_cb):
self.parse_file(fname)
for fname in glob.parse_files(export_file, self.file_not_found_cb):
self.process_export_file(fname)
def out_msg(self, fname, name, arg):
"""
Return output messages from a file name using the output style
filtering.
If output type was not handled by the syler, return None.
"""
# NOTE: we can add rules here to filter out unwanted parts,
# although OutputFormat.msg already does that.
return self.out_style.msg(fname, name, arg)
def msg(self, enable_lineno=False, export=False, internal=False,
symbol=None, nosymbol=None, no_doc_sections=False,
filenames=None, export_file=None):
"""
Interacts over the kernel-doc results and output messages,
returning kernel-doc markups on each interaction
"""
self.out_style.set_config(self.config)
if not filenames:
filenames = sorted(self.results.keys())
glob = GlobSourceFiles(srctree=self.config.src_tree)
for fname in filenames:
function_table = set()
if internal or export:
if not export_file:
export_file = [fname]
for f in glob.parse_files(export_file, self.file_not_found_cb):
function_table |= self.export_table[f]
if symbol:
for s in symbol:
function_table.add(s)
self.out_style.set_filter(export, internal, symbol, nosymbol,
function_table, enable_lineno,
no_doc_sections)
msg = ""
if fname not in self.results:
self.config.log.warning("No kernel-doc for file %s", fname)
continue
for arg in self.results[fname]:
m = self.out_msg(fname, arg.name, arg)
if m is None:
ln = arg.get("ln", 0)
dtype = arg.get('type', "")
self.config.log.warning("%s:%d Can't handle %s",
fname, ln, dtype)
else:
msg += m
if msg:
yield fname, msg
+42
View File
@@ -0,0 +1,42 @@
# SPDX-License-Identifier: GPL-2.0
#
# A class that will, eventually, encapsulate all of the parsed data that we
# then pass into the output modules.
#
class KdocItem:
def __init__(self, name, type, start_line, **other_stuff):
self.name = name
self.type = type
self.declaration_start_line = start_line
self.sections = {}
self.sections_start_lines = {}
self.parameterlist = []
self.parameterdesc_start_lines = []
self.parameterdescs = {}
self.parametertypes = {}
#
# Just save everything else into our own dict so that the output
# side can grab it directly as before. As we move things into more
# structured data, this will, hopefully, fade away.
#
self.other_stuff = other_stuff
def get(self, key, default = None):
return self.other_stuff.get(key, default)
def __getitem__(self, key):
return self.get(key)
#
# Tracking of section and parameter information.
#
def set_sections(self, sections, start_lines):
self.sections = sections
self.section_start_lines = start_lines
def set_params(self, names, descs, types, starts):
self.parameterlist = names
self.parameterdescs = descs
self.parametertypes = types
self.parameterdesc_start_lines = starts
+749
View File
@@ -0,0 +1,749 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>.
#
# pylint: disable=C0301,R0902,R0911,R0912,R0913,R0914,R0915,R0917
"""
Implement output filters to print kernel-doc documentation.
The implementation uses a virtual base class (OutputFormat) which
contains a dispatches to virtual methods, and some code to filter
out output messages.
The actual implementation is done on one separate class per each type
of output. Currently, there are output classes for ReST and man/troff.
"""
import os
import re
from datetime import datetime
from kdoc_parser import KernelDoc, type_param
from kdoc_re import KernRe
function_pointer = KernRe(r"([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)", cache=False)
# match expressions used to find embedded type information
type_constant = KernRe(r"\b``([^\`]+)``\b", cache=False)
type_constant2 = KernRe(r"\%([-_*\w]+)", cache=False)
type_func = KernRe(r"(\w+)\(\)", cache=False)
type_param_ref = KernRe(r"([\!~\*]?)\@(\w*((\.\w+)|(->\w+))*(\.\.\.)?)", cache=False)
# Special RST handling for func ptr params
type_fp_param = KernRe(r"\@(\w+)\(\)", cache=False)
# Special RST handling for structs with func ptr params
type_fp_param2 = KernRe(r"\@(\w+->\S+)\(\)", cache=False)
type_env = KernRe(r"(\$\w+)", cache=False)
type_enum = KernRe(r"#(enum\s*([_\w]+))", cache=False)
type_struct = KernRe(r"#(struct\s*([_\w]+))", cache=False)
type_typedef = KernRe(r"#(([A-Z][_\w]*))", cache=False)
type_union = KernRe(r"#(union\s*([_\w]+))", cache=False)
type_member = KernRe(r"#([_\w]+)(\.|->)([_\w]+)", cache=False)
type_fallback = KernRe(r"((?!))", cache=False) # this never matches
type_member_func = type_member + KernRe(r"\(\)", cache=False)
class OutputFormat:
"""
Base class for OutputFormat. If used as-is, it means that only
warnings will be displayed.
"""
# output mode.
OUTPUT_ALL = 0 # output all symbols and doc sections
OUTPUT_INCLUDE = 1 # output only specified symbols
OUTPUT_EXPORTED = 2 # output exported symbols
OUTPUT_INTERNAL = 3 # output non-exported symbols
# Virtual member to be overriden at the inherited classes
highlights = []
def __init__(self):
"""Declare internal vars and set mode to OUTPUT_ALL"""
self.out_mode = self.OUTPUT_ALL
self.enable_lineno = None
self.nosymbol = {}
self.symbol = None
self.function_table = None
self.config = None
self.no_doc_sections = False
self.data = ""
def set_config(self, config):
"""
Setup global config variables used by both parser and output.
"""
self.config = config
def set_filter(self, export, internal, symbol, nosymbol, function_table,
enable_lineno, no_doc_sections):
"""
Initialize filter variables according with the requested mode.
Only one choice is valid between export, internal and symbol.
The nosymbol filter can be used on all modes.
"""
self.enable_lineno = enable_lineno
self.no_doc_sections = no_doc_sections
self.function_table = function_table
if symbol:
self.out_mode = self.OUTPUT_INCLUDE
elif export:
self.out_mode = self.OUTPUT_EXPORTED
elif internal:
self.out_mode = self.OUTPUT_INTERNAL
else:
self.out_mode = self.OUTPUT_ALL
if nosymbol:
self.nosymbol = set(nosymbol)
def highlight_block(self, block):
"""
Apply the RST highlights to a sub-block of text.
"""
for r, sub in self.highlights:
block = r.sub(sub, block)
return block
def out_warnings(self, args):
"""
Output warnings for identifiers that will be displayed.
"""
for log_msg in args.warnings:
self.config.warning(log_msg)
def check_doc(self, name, args):
"""Check if DOC should be output"""
if self.no_doc_sections:
return False
if name in self.nosymbol:
return False
if self.out_mode == self.OUTPUT_ALL:
self.out_warnings(args)
return True
if self.out_mode == self.OUTPUT_INCLUDE:
if name in self.function_table:
self.out_warnings(args)
return True
return False
def check_declaration(self, dtype, name, args):
"""
Checks if a declaration should be output or not based on the
filtering criteria.
"""
if name in self.nosymbol:
return False
if self.out_mode == self.OUTPUT_ALL:
self.out_warnings(args)
return True
if self.out_mode in [self.OUTPUT_INCLUDE, self.OUTPUT_EXPORTED]:
if name in self.function_table:
return True
if self.out_mode == self.OUTPUT_INTERNAL:
if dtype != "function":
self.out_warnings(args)
return True
if name not in self.function_table:
self.out_warnings(args)
return True
return False
def msg(self, fname, name, args):
"""
Handles a single entry from kernel-doc parser
"""
self.data = ""
dtype = args.type
if dtype == "doc":
self.out_doc(fname, name, args)
return self.data
if not self.check_declaration(dtype, name, args):
return self.data
if dtype == "function":
self.out_function(fname, name, args)
return self.data
if dtype == "enum":
self.out_enum(fname, name, args)
return self.data
if dtype == "typedef":
self.out_typedef(fname, name, args)
return self.data
if dtype in ["struct", "union"]:
self.out_struct(fname, name, args)
return self.data
# Warn if some type requires an output logic
self.config.log.warning("doesn't now how to output '%s' block",
dtype)
return None
# Virtual methods to be overridden by inherited classes
# At the base class, those do nothing.
def out_doc(self, fname, name, args):
"""Outputs a DOC block"""
def out_function(self, fname, name, args):
"""Outputs a function"""
def out_enum(self, fname, name, args):
"""Outputs an enum"""
def out_typedef(self, fname, name, args):
"""Outputs a typedef"""
def out_struct(self, fname, name, args):
"""Outputs a struct"""
class RestFormat(OutputFormat):
"""Consts and functions used by ReST output"""
highlights = [
(type_constant, r"``\1``"),
(type_constant2, r"``\1``"),
# Note: need to escape () to avoid func matching later
(type_member_func, r":c:type:`\1\2\3\\(\\) <\1>`"),
(type_member, r":c:type:`\1\2\3 <\1>`"),
(type_fp_param, r"**\1\\(\\)**"),
(type_fp_param2, r"**\1\\(\\)**"),
(type_func, r"\1()"),
(type_enum, r":c:type:`\1 <\2>`"),
(type_struct, r":c:type:`\1 <\2>`"),
(type_typedef, r":c:type:`\1 <\2>`"),
(type_union, r":c:type:`\1 <\2>`"),
# in rst this can refer to any type
(type_fallback, r":c:type:`\1`"),
(type_param_ref, r"**\1\2**")
]
blankline = "\n"
sphinx_literal = KernRe(r'^[^.].*::$', cache=False)
sphinx_cblock = KernRe(r'^\.\.\ +code-block::', cache=False)
def __init__(self):
"""
Creates class variables.
Not really mandatory, but it is a good coding style and makes
pylint happy.
"""
super().__init__()
self.lineprefix = ""
def print_lineno(self, ln):
"""Outputs a line number"""
if self.enable_lineno and ln is not None:
ln += 1
self.data += f".. LINENO {ln}\n"
def output_highlight(self, args):
"""
Outputs a C symbol that may require being converted to ReST using
the self.highlights variable
"""
input_text = args
output = ""
in_literal = False
litprefix = ""
block = ""
for line in input_text.strip("\n").split("\n"):
# If we're in a literal block, see if we should drop out of it.
# Otherwise, pass the line straight through unmunged.
if in_literal:
if line.strip(): # If the line is not blank
# If this is the first non-blank line in a literal block,
# figure out the proper indent.
if not litprefix:
r = KernRe(r'^(\s*)')
if r.match(line):
litprefix = '^' + r.group(1)
else:
litprefix = ""
output += line + "\n"
elif not KernRe(litprefix).match(line):
in_literal = False
else:
output += line + "\n"
else:
output += line + "\n"
# Not in a literal block (or just dropped out)
if not in_literal:
block += line + "\n"
if self.sphinx_literal.match(line) or self.sphinx_cblock.match(line):
in_literal = True
litprefix = ""
output += self.highlight_block(block)
block = ""
# Handle any remaining block
if block:
output += self.highlight_block(block)
# Print the output with the line prefix
for line in output.strip("\n").split("\n"):
self.data += self.lineprefix + line + "\n"
def out_section(self, args, out_docblock=False):
"""
Outputs a block section.
This could use some work; it's used to output the DOC: sections, and
starts by putting out the name of the doc section itself, but that
tends to duplicate a header already in the template file.
"""
for section, text in args.sections.items():
# Skip sections that are in the nosymbol_table
if section in self.nosymbol:
continue
if out_docblock:
if not self.out_mode == self.OUTPUT_INCLUDE:
self.data += f".. _{section}:\n\n"
self.data += f'{self.lineprefix}**{section}**\n\n'
else:
self.data += f'{self.lineprefix}**{section}**\n\n'
self.print_lineno(args.section_start_lines.get(section, 0))
self.output_highlight(text)
self.data += "\n"
self.data += "\n"
def out_doc(self, fname, name, args):
if not self.check_doc(name, args):
return
self.out_section(args, out_docblock=True)
def out_function(self, fname, name, args):
oldprefix = self.lineprefix
signature = ""
func_macro = args.get('func_macro', False)
if func_macro:
signature = name
else:
if args.get('functiontype'):
signature = args['functiontype'] + " "
signature += name + " ("
ln = args.declaration_start_line
count = 0
for parameter in args.parameterlist:
if count != 0:
signature += ", "
count += 1
dtype = args.parametertypes.get(parameter, "")
if function_pointer.search(dtype):
signature += function_pointer.group(1) + parameter + function_pointer.group(3)
else:
signature += dtype
if not func_macro:
signature += ")"
self.print_lineno(ln)
if args.get('typedef') or not args.get('functiontype'):
self.data += f".. c:macro:: {name}\n\n"
if args.get('typedef'):
self.data += " **Typedef**: "
self.lineprefix = ""
self.output_highlight(args.get('purpose', ""))
self.data += "\n\n**Syntax**\n\n"
self.data += f" ``{signature}``\n\n"
else:
self.data += f"``{signature}``\n\n"
else:
self.data += f".. c:function:: {signature}\n\n"
if not args.get('typedef'):
self.print_lineno(ln)
self.lineprefix = " "
self.output_highlight(args.get('purpose', ""))
self.data += "\n"
# Put descriptive text into a container (HTML <div>) to help set
# function prototypes apart
self.lineprefix = " "
if args.parameterlist:
self.data += ".. container:: kernelindent\n\n"
self.data += f"{self.lineprefix}**Parameters**\n\n"
for parameter in args.parameterlist:
parameter_name = KernRe(r'\[.*').sub('', parameter)
dtype = args.parametertypes.get(parameter, "")
if dtype:
self.data += f"{self.lineprefix}``{dtype}``\n"
else:
self.data += f"{self.lineprefix}``{parameter}``\n"
self.print_lineno(args.parameterdesc_start_lines.get(parameter_name, 0))
self.lineprefix = " "
if parameter_name in args.parameterdescs and \
args.parameterdescs[parameter_name] != KernelDoc.undescribed:
self.output_highlight(args.parameterdescs[parameter_name])
self.data += "\n"
else:
self.data += f"{self.lineprefix}*undescribed*\n\n"
self.lineprefix = " "
self.out_section(args)
self.lineprefix = oldprefix
def out_enum(self, fname, name, args):
oldprefix = self.lineprefix
ln = args.declaration_start_line
self.data += f"\n\n.. c:enum:: {name}\n\n"
self.print_lineno(ln)
self.lineprefix = " "
self.output_highlight(args.get('purpose', ''))
self.data += "\n"
self.data += ".. container:: kernelindent\n\n"
outer = self.lineprefix + " "
self.lineprefix = outer + " "
self.data += f"{outer}**Constants**\n\n"
for parameter in args.parameterlist:
self.data += f"{outer}``{parameter}``\n"
if args.parameterdescs.get(parameter, '') != KernelDoc.undescribed:
self.output_highlight(args.parameterdescs[parameter])
else:
self.data += f"{self.lineprefix}*undescribed*\n\n"
self.data += "\n"
self.lineprefix = oldprefix
self.out_section(args)
def out_typedef(self, fname, name, args):
oldprefix = self.lineprefix
ln = args.declaration_start_line
self.data += f"\n\n.. c:type:: {name}\n\n"
self.print_lineno(ln)
self.lineprefix = " "
self.output_highlight(args.get('purpose', ''))
self.data += "\n"
self.lineprefix = oldprefix
self.out_section(args)
def out_struct(self, fname, name, args):
purpose = args.get('purpose', "")
declaration = args.get('definition', "")
dtype = args.type
ln = args.declaration_start_line
self.data += f"\n\n.. c:{dtype}:: {name}\n\n"
self.print_lineno(ln)
oldprefix = self.lineprefix
self.lineprefix += " "
self.output_highlight(purpose)
self.data += "\n"
self.data += ".. container:: kernelindent\n\n"
self.data += f"{self.lineprefix}**Definition**::\n\n"
self.lineprefix = self.lineprefix + " "
declaration = declaration.replace("\t", self.lineprefix)
self.data += f"{self.lineprefix}{dtype} {name}" + ' {' + "\n"
self.data += f"{declaration}{self.lineprefix}" + "};\n\n"
self.lineprefix = " "
self.data += f"{self.lineprefix}**Members**\n\n"
for parameter in args.parameterlist:
if not parameter or parameter.startswith("#"):
continue
parameter_name = parameter.split("[", maxsplit=1)[0]
if args.parameterdescs.get(parameter_name) == KernelDoc.undescribed:
continue
self.print_lineno(args.parameterdesc_start_lines.get(parameter_name, 0))
self.data += f"{self.lineprefix}``{parameter}``\n"
self.lineprefix = " "
self.output_highlight(args.parameterdescs[parameter_name])
self.lineprefix = " "
self.data += "\n"
self.data += "\n"
self.lineprefix = oldprefix
self.out_section(args)
class ManFormat(OutputFormat):
"""Consts and functions used by man pages output"""
highlights = (
(type_constant, r"\1"),
(type_constant2, r"\1"),
(type_func, r"\\fB\1\\fP"),
(type_enum, r"\\fI\1\\fP"),
(type_struct, r"\\fI\1\\fP"),
(type_typedef, r"\\fI\1\\fP"),
(type_union, r"\\fI\1\\fP"),
(type_param, r"\\fI\1\\fP"),
(type_param_ref, r"\\fI\1\2\\fP"),
(type_member, r"\\fI\1\2\3\\fP"),
(type_fallback, r"\\fI\1\\fP")
)
blankline = ""
date_formats = [
"%a %b %d %H:%M:%S %Z %Y",
"%a %b %d %H:%M:%S %Y",
"%Y-%m-%d",
"%b %d %Y",
"%B %d %Y",
"%m %d %Y",
]
def __init__(self, modulename):
"""
Creates class variables.
Not really mandatory, but it is a good coding style and makes
pylint happy.
"""
super().__init__()
self.modulename = modulename
dt = None
tstamp = os.environ.get("KBUILD_BUILD_TIMESTAMP")
if tstamp:
for fmt in self.date_formats:
try:
dt = datetime.strptime(tstamp, fmt)
break
except ValueError:
pass
if not dt:
dt = datetime.now()
self.man_date = dt.strftime("%B %Y")
def output_highlight(self, block):
"""
Outputs a C symbol that may require being highlighted with
self.highlights variable using troff syntax
"""
contents = self.highlight_block(block)
if isinstance(contents, list):
contents = "\n".join(contents)
for line in contents.strip("\n").split("\n"):
line = KernRe(r"^\s*").sub("", line)
if not line:
continue
if line[0] == ".":
self.data += "\\&" + line + "\n"
else:
self.data += line + "\n"
def out_doc(self, fname, name, args):
if not self.check_doc(name, args):
return
self.data += f'.TH "{self.modulename}" 9 "{self.modulename}" "{self.man_date}" "API Manual" LINUX' + "\n"
for section, text in args.sections.items():
self.data += f'.SH "{section}"' + "\n"
self.output_highlight(text)
def out_function(self, fname, name, args):
"""output function in man"""
self.data += f'.TH "{name}" 9 "{name}" "{self.man_date}" "Kernel Hacker\'s Manual" LINUX' + "\n"
self.data += ".SH NAME\n"
self.data += f"{name} \\- {args['purpose']}\n"
self.data += ".SH SYNOPSIS\n"
if args.get('functiontype', ''):
self.data += f'.B "{args["functiontype"]}" {name}' + "\n"
else:
self.data += f'.B "{name}' + "\n"
count = 0
parenth = "("
post = ","
for parameter in args.parameterlist:
if count == len(args.parameterlist) - 1:
post = ");"
dtype = args.parametertypes.get(parameter, "")
if function_pointer.match(dtype):
# Pointer-to-function
self.data += f'".BI "{parenth}{function_pointer.group(1)}" " ") ({function_pointer.group(2)}){post}"' + "\n"
else:
dtype = KernRe(r'([^\*])$').sub(r'\1 ', dtype)
self.data += f'.BI "{parenth}{dtype}" "{post}"' + "\n"
count += 1
parenth = ""
if args.parameterlist:
self.data += ".SH ARGUMENTS\n"
for parameter in args.parameterlist:
parameter_name = re.sub(r'\[.*', '', parameter)
self.data += f'.IP "{parameter}" 12' + "\n"
self.output_highlight(args.parameterdescs.get(parameter_name, ""))
for section, text in args.sections.items():
self.data += f'.SH "{section.upper()}"' + "\n"
self.output_highlight(text)
def out_enum(self, fname, name, args):
self.data += f'.TH "{self.modulename}" 9 "enum {name}" "{self.man_date}" "API Manual" LINUX' + "\n"
self.data += ".SH NAME\n"
self.data += f"enum {name} \\- {args['purpose']}\n"
self.data += ".SH SYNOPSIS\n"
self.data += f"enum {name}" + " {\n"
count = 0
for parameter in args.parameterlist:
self.data += f'.br\n.BI " {parameter}"' + "\n"
if count == len(args.parameterlist) - 1:
self.data += "\n};\n"
else:
self.data += ", \n.br\n"
count += 1
self.data += ".SH Constants\n"
for parameter in args.parameterlist:
parameter_name = KernRe(r'\[.*').sub('', parameter)
self.data += f'.IP "{parameter}" 12' + "\n"
self.output_highlight(args.parameterdescs.get(parameter_name, ""))
for section, text in args.sections.items():
self.data += f'.SH "{section}"' + "\n"
self.output_highlight(text)
def out_typedef(self, fname, name, args):
module = self.modulename
purpose = args.get('purpose')
self.data += f'.TH "{module}" 9 "{name}" "{self.man_date}" "API Manual" LINUX' + "\n"
self.data += ".SH NAME\n"
self.data += f"typedef {name} \\- {purpose}\n"
for section, text in args.sections.items():
self.data += f'.SH "{section}"' + "\n"
self.output_highlight(text)
def out_struct(self, fname, name, args):
module = self.modulename
purpose = args.get('purpose')
definition = args.get('definition')
self.data += f'.TH "{module}" 9 "{args.type} {name}" "{self.man_date}" "API Manual" LINUX' + "\n"
self.data += ".SH NAME\n"
self.data += f"{args.type} {name} \\- {purpose}\n"
# Replace tabs with two spaces and handle newlines
declaration = definition.replace("\t", " ")
declaration = KernRe(r"\n").sub('"\n.br\n.BI "', declaration)
self.data += ".SH SYNOPSIS\n"
self.data += f"{args.type} {name} " + "{" + "\n.br\n"
self.data += f'.BI "{declaration}\n' + "};\n.br\n\n"
self.data += ".SH Members\n"
for parameter in args.parameterlist:
if parameter.startswith("#"):
continue
parameter_name = re.sub(r"\[.*", "", parameter)
if args.parameterdescs.get(parameter_name) == KernelDoc.undescribed:
continue
self.data += f'.IP "{parameter}" 12' + "\n"
self.output_highlight(args.parameterdescs.get(parameter_name))
for section, text in args.sections.items():
self.data += f'.SH "{section}"' + "\n"
self.output_highlight(text)
File diff suppressed because it is too large Load Diff
+270
View File
@@ -0,0 +1,270 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>.
"""
Regular expression ancillary classes.
Those help caching regular expressions and do matching for kernel-doc.
"""
import re
# Local cache for regular expressions
re_cache = {}
class KernRe:
"""
Helper class to simplify regex declaration and usage,
It calls re.compile for a given pattern. It also allows adding
regular expressions and define sub at class init time.
Regular expressions can be cached via an argument, helping to speedup
searches.
"""
def _add_regex(self, string, flags):
"""
Adds a new regex or re-use it from the cache.
"""
self.regex = re_cache.get(string, None)
if not self.regex:
self.regex = re.compile(string, flags=flags)
if self.cache:
re_cache[string] = self.regex
def __init__(self, string, cache=True, flags=0):
"""
Compile a regular expression and initialize internal vars.
"""
self.cache = cache
self.last_match = None
self._add_regex(string, flags)
def __str__(self):
"""
Return the regular expression pattern.
"""
return self.regex.pattern
def __add__(self, other):
"""
Allows adding two regular expressions into one.
"""
return KernRe(str(self) + str(other), cache=self.cache or other.cache,
flags=self.regex.flags | other.regex.flags)
def match(self, string):
"""
Handles a re.match storing its results
"""
self.last_match = self.regex.match(string)
return self.last_match
def search(self, string):
"""
Handles a re.search storing its results
"""
self.last_match = self.regex.search(string)
return self.last_match
def findall(self, string):
"""
Alias to re.findall
"""
return self.regex.findall(string)
def split(self, string):
"""
Alias to re.split
"""
return self.regex.split(string)
def sub(self, sub, string, count=0):
"""
Alias to re.sub
"""
return self.regex.sub(sub, string, count=count)
def group(self, num):
"""
Returns the group results of the last match
"""
return self.last_match.group(num)
class NestedMatch:
"""
Finding nested delimiters is hard with regular expressions. It is
even harder on Python with its normal re module, as there are several
advanced regular expressions that are missing.
This is the case of this pattern:
'\\bSTRUCT_GROUP(\\(((?:(?>[^)(]+)|(?1))*)\\))[^;]*;'
which is used to properly match open/close parenthesis of the
string search STRUCT_GROUP(),
Add a class that counts pairs of delimiters, using it to match and
replace nested expressions.
The original approach was suggested by:
https://stackoverflow.com/questions/5454322/python-how-to-match-nested-parentheses-with-regex
Although I re-implemented it to make it more generic and match 3 types
of delimiters. The logic checks if delimiters are paired. If not, it
will ignore the search string.
"""
# TODO: make NestedMatch handle multiple match groups
#
# Right now, regular expressions to match it are defined only up to
# the start delimiter, e.g.:
#
# \bSTRUCT_GROUP\(
#
# is similar to: STRUCT_GROUP\((.*)\)
# except that the content inside the match group is delimiter's aligned.
#
# The content inside parenthesis are converted into a single replace
# group (e.g. r`\1').
#
# It would be nice to change such definition to support multiple
# match groups, allowing a regex equivalent to.
#
# FOO\((.*), (.*), (.*)\)
#
# it is probably easier to define it not as a regular expression, but
# with some lexical definition like:
#
# FOO(arg1, arg2, arg3)
DELIMITER_PAIRS = {
'{': '}',
'(': ')',
'[': ']',
}
RE_DELIM = re.compile(r'[\{\}\[\]\(\)]')
def _search(self, regex, line):
"""
Finds paired blocks for a regex that ends with a delimiter.
The suggestion of using finditer to match pairs came from:
https://stackoverflow.com/questions/5454322/python-how-to-match-nested-parentheses-with-regex
but I ended using a different implementation to align all three types
of delimiters and seek for an initial regular expression.
The algorithm seeks for open/close paired delimiters and place them
into a stack, yielding a start/stop position of each match when the
stack is zeroed.
The algorithm shoud work fine for properly paired lines, but will
silently ignore end delimiters that preceeds an start delimiter.
This should be OK for kernel-doc parser, as unaligned delimiters
would cause compilation errors. So, we don't need to rise exceptions
to cover such issues.
"""
stack = []
for match_re in regex.finditer(line):
start = match_re.start()
offset = match_re.end()
d = line[offset - 1]
if d not in self.DELIMITER_PAIRS:
continue
end = self.DELIMITER_PAIRS[d]
stack.append(end)
for match in self.RE_DELIM.finditer(line[offset:]):
pos = match.start() + offset
d = line[pos]
if d in self.DELIMITER_PAIRS:
end = self.DELIMITER_PAIRS[d]
stack.append(end)
continue
# Does the end delimiter match what it is expected?
if stack and d == stack[-1]:
stack.pop()
if not stack:
yield start, offset, pos + 1
break
def search(self, regex, line):
"""
This is similar to re.search:
It matches a regex that it is followed by a delimiter,
returning occurrences only if all delimiters are paired.
"""
for t in self._search(regex, line):
yield line[t[0]:t[2]]
def sub(self, regex, sub, line, count=0):
"""
This is similar to re.sub:
It matches a regex that it is followed by a delimiter,
replacing occurrences only if all delimiters are paired.
if r'\1' is used, it works just like re: it places there the
matched paired data with the delimiter stripped.
If count is different than zero, it will replace at most count
items.
"""
out = ""
cur_pos = 0
n = 0
for start, end, pos in self._search(regex, line):
out += line[cur_pos:start]
# Value, ignoring start/end delimiters
value = line[end:pos - 1]
# replaces \1 at the sub string, if \1 is used there
new_sub = sub
new_sub = new_sub.replace(r'\1', value)
out += new_sub
# Drop end ';' if any
if line[pos] == ';':
pos += 1
cur_pos = pos
n += 1
if count and count >= n:
break
# Append the remaining string
l = len(line)
out += line[cur_pos:l]
return out
+17 -17
View File
@@ -11,8 +11,9 @@
# See the COPYING file in the top-level directory.
function subproject_dir() {
if test ! -f "subprojects/$1.wrap"; then
error "scripts/archive-source.sh should only process wrap subprojects"
if test ! -f "$src/subprojects/$1.wrap"; then
echo "scripts/archive-source.sh should only process wrap subprojects"
exit 1
fi
# Print the directory key of the wrap file, defaulting to the
@@ -26,7 +27,7 @@ function subproject_dir() {
-e 's///p' \
-e 'q' \
-e '}' \
"subprojects/$1.wrap")
"$src/subprojects/$1.wrap")
echo "${dir:-$1}"
}
@@ -39,8 +40,9 @@ fi
# Only include wraps that are invoked with subproject()
SUBPROJECTS="libvfio-user keycodemapdb berkeley-softfloat-3
berkeley-testfloat-3 arbitrary-int-1-rs bilge-0.2-rs
bilge-impl-0.2-rs either-1-rs itertools-0.11-rs proc-macro2-1-rs
berkeley-testfloat-3 anyhow-1-rs arbitrary-int-1-rs attrs-0.2-rs bilge-0.2-rs
bilge-impl-0.2-rs either-1-rs foreign-0.3-rs itertools-0.11-rs
libc-0.2-rs proc-macro2-1-rs
proc-macro-error-1-rs proc-macro-error-attr-1-rs quote-1-rs
syn-2-rs unicode-ident-1-rs"
@@ -60,23 +62,21 @@ meson subprojects download $SUBPROJECTS
(cd roms/skiboot && ./make_version.sh > .version)
# Fetch edk2 submodule's submodules, since it won't have access to them via
# the tarball later.
#
# A more uniform way to handle this sort of situation would be nice, but we
# don't necessarily have much control over how a submodule handles its
# submodule dependencies, so we continue to handle these on a case-by-case
# basis for now.
(cd roms/edk2 && \
git submodule update --init --depth 1 -- \
ArmPkg/Library/ArmSoftFloatLib/berkeley-softfloat-3 \
BaseTools/Source/C/BrotliCompress/brotli \
CryptoPkg/Library/OpensslLib/openssl \
MdeModulePkg/Library/BrotliCustomDecompressLib/brotli)
# As recommended by the EDK2 readme, we don't use --recursive here.
# EDK2 won't use any code or feature from a submodule of a submodule,
# so we don't need to add them to the tarball.
# Although we don't necessarily need all of the submodules that EDK2
# has, we clone them all, to avoid running into problems where EDK2
# adds a new submodule or changes its use of an existing one and
# the sources we ship in the tarball then fail to build.
(cd roms/edk2 && git submodule update --init --depth 1)
popd
exclude=(--exclude=.git)
# include the tarballs in subprojects/packagecache but not their expansion
for sp in $SUBPROJECTS; do
if grep -xqF "[wrap-file]" subprojects/$sp.wrap; then
if grep -xqF "[wrap-file]" $src/subprojects/$sp.wrap; then
exclude+=(--exclude=subprojects/"$(subproject_dir $sp)")
fi
done
+27 -16
View File
@@ -111,13 +111,13 @@ def help_line(left, opt, indent, long):
right = f'{opt["description"]}'
if long:
value = get_help(opt)
if value != "auto" and value != "":
if value not in {"", "auto"}:
right += f" [{value}]"
if "choices" in opt and long:
choices = "/".join(sorted(opt["choices"]))
right += f" (choices: {choices})"
for x in wrap(" " + left, right, indent):
sh_print(x)
for line in wrap(" " + left, right, indent):
sh_print(line)
# Return whether the option (a dictionary) can be used with
@@ -144,18 +144,18 @@ def require_arg(opt):
return not ({"enabled", "disabled"}.intersection(opt["choices"]))
def filter_options(json):
if ":" in json["name"]:
def filter_options(opt):
if ":" in opt["name"]:
return False
if json["section"] == "user":
return json["name"] not in SKIP_OPTIONS
if opt["section"] == "user":
return opt["name"] not in SKIP_OPTIONS
else:
return json["name"] in BUILTIN_OPTIONS
return opt["name"] in BUILTIN_OPTIONS
def load_options(json):
json = [x for x in json if filter_options(x)]
return sorted(json, key=lambda x: x["name"])
def load_options(opts):
opts = [opt for opt in opts if filter_options(opt)]
return sorted(opts, key=lambda opt: opt["name"])
def cli_option(opt):
@@ -223,7 +223,7 @@ def print_parse(options):
key = cli_option(opt)
name = opt["name"]
if require_arg(opt):
if opt["type"] == "array" and not "choices" in opt:
if opt["type"] == "array" and "choices" not in opt:
print(f' --{key}=*) quote_sh "-D{name}=$(meson_option_build_array $2)" ;;')
else:
print(f' --{key}=*) quote_sh "-D{name}=$2" ;;')
@@ -242,7 +242,18 @@ def print_parse(options):
print("}")
options = load_options(json.load(sys.stdin))
print("# This file is generated by meson-buildoptions.py, do not edit!")
print_help(options)
print_parse(options)
def main():
json_data = sys.stdin.read()
try:
options = load_options(json.loads(json_data))
except:
print("Failure in scripts/meson-buildoptions.py parsing stdin as json",
file=sys.stderr)
print(json_data, file=sys.stderr)
sys.exit(1)
print("# This file is generated by meson-buildoptions.py, do not edit!")
print_help(options)
print_parse(options)
sys.exit(main())
+18 -7
View File
@@ -58,6 +58,7 @@ meson_options_help() {
printf "%s\n" ' --enable-ubsan enable undefined behaviour sanitizer'
printf "%s\n" ' --firmwarepath=VALUES search PATH for firmware files [share/qemu-'
printf "%s\n" ' firmware]'
printf "%s\n" ' --gdb=VALUE Path to GDB'
printf "%s\n" ' --iasl=VALUE Path to ACPI disassembler'
printf "%s\n" ' --includedir=VALUE Header file directory [include]'
printf "%s\n" ' --interp-prefix=VALUE where to find shared libraries etc., use %M for'
@@ -81,7 +82,7 @@ meson_options_help() {
printf "%s\n" ' [NORMAL]'
printf "%s\n" ' --vtune=VALUE Path to VTune directory for profiling'
printf "%s\n" ' --with-coroutine=CHOICE coroutine backend to use (choices:'
printf "%s\n" ' auto/sigaltstack/ucontext/windows)'
printf "%s\n" ' auto/sigaltstack/ucontext/wasm/windows)'
printf "%s\n" ' --with-pkgversion=VALUE use specified string as sub-version of the'
printf "%s\n" ' package'
printf "%s\n" ' --with-suffix=VALUE Suffix for QEMU data/modules/config directories'
@@ -98,8 +99,6 @@ meson_options_help() {
printf "%s\n" ' alsa ALSA sound support'
printf "%s\n" ' attr attr/xattr support'
printf "%s\n" ' auth-pam PAM access control'
printf "%s\n" ' avx2 AVX2 optimizations'
printf "%s\n" ' avx512bw AVX512BW optimizations'
printf "%s\n" ' blkio libblkio block device driver'
printf "%s\n" ' bochs bochs image format support'
printf "%s\n" ' bpf eBPF support'
@@ -132,6 +131,7 @@ meson_options_help() {
printf "%s\n" ' hv-balloon hv-balloon driver (requires Glib 2.68+ GTree API)'
printf "%s\n" ' hvf HVF acceleration support'
printf "%s\n" ' iconv Font glyph conversion support'
printf "%s\n" ' igvm Independent Guest Virtual Machine (IGVM) file support'
printf "%s\n" ' jack JACK sound support'
printf "%s\n" ' keyring Linux keyring support'
printf "%s\n" ' kvm KVM acceleration support'
@@ -155,6 +155,7 @@ meson_options_help() {
printf "%s\n" ' membarrier membarrier system call (for Linux 4.14+ or Windows'
printf "%s\n" ' modules modules support (non Windows)'
printf "%s\n" ' mpath Multipath persistent reservation passthrough'
printf "%s\n" ' mshv MSHV acceleration support'
printf "%s\n" ' multiprocess Out of process device emulation support'
printf "%s\n" ' netmap netmap network backend support'
printf "%s\n" ' nettle nettle cryptography support'
@@ -164,10 +165,12 @@ meson_options_help() {
printf "%s\n" ' oss OSS sound support'
printf "%s\n" ' pa PulseAudio sound support'
printf "%s\n" ' parallels parallels image format support'
printf "%s\n" ' passt passt network backend support'
printf "%s\n" ' pipewire PipeWire sound support'
printf "%s\n" ' pixman pixman support'
printf "%s\n" ' plugins TCG plugins via shared library loading'
printf "%s\n" ' png PNG support with libpng'
printf "%s\n" ' pvg macOS paravirtualized graphics support'
printf "%s\n" ' qatzip QATzip compression support'
printf "%s\n" ' qcow1 qcow1 image format support'
printf "%s\n" ' qed qed image format support'
@@ -198,6 +201,7 @@ meson_options_help() {
printf "%s\n" ' u2f U2F emulation support'
printf "%s\n" ' uadk UADK Library support'
printf "%s\n" ' usb-redir libusbredir support'
printf "%s\n" ' valgrind valgrind debug support for coroutine stacks'
printf "%s\n" ' vde vde network backend support'
printf "%s\n" ' vdi vdi image format support'
printf "%s\n" ' vduse-blk-export'
@@ -243,10 +247,6 @@ _meson_option_parse() {
--audio-drv-list=*) quote_sh "-Daudio_drv_list=$2" ;;
--enable-auth-pam) printf "%s" -Dauth_pam=enabled ;;
--disable-auth-pam) printf "%s" -Dauth_pam=disabled ;;
--enable-avx2) printf "%s" -Davx2=enabled ;;
--disable-avx2) printf "%s" -Davx2=disabled ;;
--enable-avx512bw) printf "%s" -Davx512bw=enabled ;;
--disable-avx512bw) printf "%s" -Davx512bw=disabled ;;
--enable-gcov) printf "%s" -Db_coverage=true ;;
--disable-gcov) printf "%s" -Db_coverage=false ;;
--enable-lto) printf "%s" -Db_lto=true ;;
@@ -326,6 +326,7 @@ _meson_option_parse() {
--disable-fuzzing) printf "%s" -Dfuzzing=false ;;
--enable-gcrypt) printf "%s" -Dgcrypt=enabled ;;
--disable-gcrypt) printf "%s" -Dgcrypt=disabled ;;
--gdb=*) quote_sh "-Dgdb=$2" ;;
--enable-gettext) printf "%s" -Dgettext=enabled ;;
--disable-gettext) printf "%s" -Dgettext=disabled ;;
--enable-gio) printf "%s" -Dgio=enabled ;;
@@ -349,6 +350,8 @@ _meson_option_parse() {
--iasl=*) quote_sh "-Diasl=$2" ;;
--enable-iconv) printf "%s" -Diconv=enabled ;;
--disable-iconv) printf "%s" -Diconv=disabled ;;
--enable-igvm) printf "%s" -Digvm=enabled ;;
--disable-igvm) printf "%s" -Digvm=disabled ;;
--includedir=*) quote_sh "-Dincludedir=$2" ;;
--enable-install-blobs) printf "%s" -Dinstall_blobs=true ;;
--disable-install-blobs) printf "%s" -Dinstall_blobs=false ;;
@@ -407,6 +410,8 @@ _meson_option_parse() {
--disable-modules) printf "%s" -Dmodules=disabled ;;
--enable-mpath) printf "%s" -Dmpath=enabled ;;
--disable-mpath) printf "%s" -Dmpath=disabled ;;
--enable-mshv) printf "%s" -Dmshv=enabled ;;
--disable-mshv) printf "%s" -Dmshv=disabled ;;
--enable-multiprocess) printf "%s" -Dmultiprocess=enabled ;;
--disable-multiprocess) printf "%s" -Dmultiprocess=disabled ;;
--enable-netmap) printf "%s" -Dnetmap=enabled ;;
@@ -425,6 +430,8 @@ _meson_option_parse() {
--disable-pa) printf "%s" -Dpa=disabled ;;
--enable-parallels) printf "%s" -Dparallels=enabled ;;
--disable-parallels) printf "%s" -Dparallels=disabled ;;
--enable-passt) printf "%s" -Dpasst=enabled ;;
--disable-passt) printf "%s" -Dpasst=disabled ;;
--enable-pipewire) printf "%s" -Dpipewire=enabled ;;
--disable-pipewire) printf "%s" -Dpipewire=disabled ;;
--enable-pixman) printf "%s" -Dpixman=enabled ;;
@@ -435,6 +442,8 @@ _meson_option_parse() {
--enable-png) printf "%s" -Dpng=enabled ;;
--disable-png) printf "%s" -Dpng=disabled ;;
--prefix=*) quote_sh "-Dprefix=$2" ;;
--enable-pvg) printf "%s" -Dpvg=enabled ;;
--disable-pvg) printf "%s" -Dpvg=disabled ;;
--enable-qatzip) printf "%s" -Dqatzip=enabled ;;
--disable-qatzip) printf "%s" -Dqatzip=disabled ;;
--enable-qcow1) printf "%s" -Dqcow1=enabled ;;
@@ -524,6 +533,8 @@ _meson_option_parse() {
--disable-ubsan) printf "%s" -Dubsan=false ;;
--enable-usb-redir) printf "%s" -Dusb_redir=enabled ;;
--disable-usb-redir) printf "%s" -Dusb_redir=disabled ;;
--enable-valgrind) printf "%s" -Dvalgrind=enabled ;;
--disable-valgrind) printf "%s" -Dvalgrind=disabled ;;
--enable-vde) printf "%s" -Dvde=enabled ;;
--disable-vde) printf "%s" -Dvde=disabled ;;
--enable-vdi) printf "%s" -Dvdi=enabled ;;
+4 -2
View File
@@ -340,7 +340,7 @@ class KconfigParser:
@classmethod
def parse(self, fp, mode=None):
data = KconfigData(mode or KconfigParser.defconfig)
data = KconfigData(mode or defconfig)
parser = KconfigParser(data)
parser.parse_file(fp)
return data
@@ -363,7 +363,9 @@ class KconfigParser:
def do_assignment(self, var, val):
if not var.startswith("CONFIG_"):
raise Error('assigned variable should start with CONFIG_')
raise KconfigParserError(
self, "assigned variable should start with CONFIG_"
)
var = self.data.do_var(var[7:])
self.data.do_assignment(var, val)
+216
View File
@@ -0,0 +1,216 @@
#!/bin/sh -e
# SPDX-License-Identifier: GPL-2.0-only
#
# Create eMMC block device image from boot, RPMB and user data images
#
# Copyright (c) Siemens, 2025
#
# Authors:
# Jan Kiszka <jan.kiszka@siemens.com>
#
usage() {
echo "$0 [OPTIONS] USER_IMG[:SIZE] OUTPUT_IMG"
echo ""
echo "SIZE must be a power of 2 up to 2G and multiples of 512 byte from there on."
echo "If no SIZE is specified, the size of USER_ING will be used (rounded up)."
echo ""
echo "Supported options:"
echo " -b BOOT1_IMG[:SIZE] Add boot partitions. SIZE must be multiples of 128K. If"
echo " no SIZE is specified, the size of BOOT1_IMG will be"
echo " used (rounded up). BOOT1_IMG will be stored in boot"
echo " partition 1, and a boot partition 2 of the same size"
echo " will be created as empty (all zeros) unless -B is"
echo " specified as well."
echo " -B BOOT2_IMG Fill boot partition 2 with BOOT2_IMG. Must be combined"
echo " with -b which is also defining the partition size."
echo " -r RPMB_IMG[:SIZE] Add RPMB partition. SIZE must be multiples of 128K. If"
echo " no SIZE is specified, the size of RPMB_IMG will be"
echo " used (rounded up)."
echo " -h, --help This help"
echo ""
echo "All SIZE parameters support the units K, M, G. If SIZE is smaller than the"
echo "associated image, it will be truncated in the output image."
exit "$1"
}
process_size() {
name=$1
image_file=$2
alignment=$3
image_arg=$4
if [ "${image_arg#*:}" = "$image_arg" ]; then
if ! size=$(wc -c < "$image_file" 2>/dev/null); then
echo "Missing $name image '$image_file'." >&2
exit 1
fi
if [ "$alignment" = 128 ]; then
size=$(( (size + 128 * 1024 - 1) & ~(128 * 1024 - 1) ))
elif [ $size -gt $((2 * 1024 * 1024 * 1024)) ]; then
size=$(( (size + 511) & ~511 ))
elif [ $(( size & (size - 1) )) -gt 0 ]; then
n=0
while [ "$size" -gt 0 ]; do
size=$((size >> 1))
n=$((n + 1))
done
size=$((1 << n))
fi
else
value="${image_arg#*:}"
if [ "${value%K}" != "$value" ]; then
size=${value%K}
multiplier=1024
elif [ "${value%M}" != "$value" ]; then
size=${value%M}
multiplier=$((1024 * 1024))
elif [ "${value%G}" != "$value" ]; then
size=${value%G}
multiplier=$((1024 * 1024 * 1024))
else
size=$value
multiplier=1
fi
# check if "$size" is a valid integer by doing a self-comparison
if [ "$size" -eq "$size" ] 2>/dev/null; then
size=$((size * multiplier))
else
echo "Invalid value '$value' specified for $image_file image size." >&2
exit 1
fi
if [ "$alignment" = 128 ]; then
if [ $(( size & (128 * 1024 - 1) )) -ne 0 ]; then
echo "The $name image size must be multiples of 128K." >&2
exit 1
fi
elif [ $size -gt $((2 * 1024 * 1024 * 1024)) ]; then
if [ $(( size & 511)) -ne 0 ]; then
echo "The $name image size must be multiples of 512 (if >2G)." >&2
exit 1
fi
elif [ $(( size & (size - 1) )) -gt 0 ]; then
echo "The $name image size must be power of 2 (up to 2G)." >&2
exit 1
fi
fi
echo $size
}
check_truncation() {
image_file=$1
output_size=$2
if [ "$image_file" = "/dev/zero" ]; then
return
fi
if ! actual_size=$(wc -c < "$image_file" 2>/dev/null); then
echo "Missing image '$image_file'." >&2
exit 1
fi
if [ "$actual_size" -gt "$output_size" ]; then
echo "Warning: image '$image_file' will be truncated on output."
fi
}
userimg=
outimg=
bootimg1=
bootimg2=/dev/zero
bootsz=0
rpmbimg=
rpmbsz=0
while [ $# -gt 0 ]; do
case "$1" in
-b)
shift
[ $# -ge 1 ] || usage 1
bootimg1=${1%%:*}
bootsz=$(process_size boot "$bootimg1" 128 "$1")
shift
;;
-B)
shift
[ $# -ge 1 ] || usage 1
bootimg2=$1
shift
;;
-r)
shift
[ $# -ge 1 ] || usage 1
rpmbimg=${1%%:*}
rpmbsz=$(process_size RPMB "$rpmbimg" 128 "$1")
shift
;;
-h|--help)
usage 0
;;
*)
if [ -z "$userimg" ]; then
userimg=${1%%:*}
usersz=$(process_size user "$userimg" U "$1")
elif [ -z "$outimg" ]; then
outimg=$1
else
usage 1
fi
shift
;;
esac
done
[ -n "$outimg" ] || usage 1
if [ "$bootsz" -gt $((32640 * 1024)) ]; then
echo "Boot image size is larger than 32640K." >&2
exit 1
fi
if [ "$rpmbsz" -gt $((16384 * 1024)) ]; then
echo "RPMB image size is larger than 16384K." >&2
exit 1
fi
echo "Creating eMMC image"
truncate -s 0 "$outimg"
pos=0
if [ "$bootsz" -gt 0 ]; then
echo " Boot partition 1 and 2: $((bootsz / 1024))K each"
blocks=$(( bootsz / (128 * 1024) ))
check_truncation "$bootimg1" "$bootsz"
dd if="$bootimg1" of="$outimg" conv=sparse bs=128K count=$blocks \
status=none
check_truncation "$bootimg2" "$bootsz"
dd if="$bootimg2" of="$outimg" conv=sparse bs=128K count=$blocks \
seek=$blocks status=none
pos=$((2 * bootsz))
fi
if [ "$rpmbsz" -gt 0 ]; then
echo " RPMB partition: $((rpmbsz / 1024))K"
blocks=$(( rpmbsz / (128 * 1024) ))
check_truncation "$rpmbimg" "$rpmbsz"
dd if="$rpmbimg" of="$outimg" conv=sparse bs=128K count=$blocks \
seek=$(( pos / (128 * 1024) )) status=none
pos=$((pos + rpmbsz))
fi
if [ "$usersz" -lt 1024 ]; then
echo " User data: $usersz bytes"
elif [ "$usersz" -lt $((1024 * 1024)) ]; then
echo " User data: $(( (usersz + 1023) / 1024 ))K ($usersz)"
elif [ "$usersz" -lt $((1024 * 1024 * 1024)) ]; then
echo " User data: $(( (usersz + 1048575) / 1048576))M ($usersz)"
else
echo " User data: $(( (usersz + 1073741823) / 1073741824))G ($usersz)"
fi
check_truncation "$userimg" "$usersz"
dd if="$userimg" of="$outimg" conv=sparse bs=128K seek=$(( pos / (128 * 1024) )) \
count=$(( (usersz + 128 * 1024 - 1) / (128 * 1024) )) status=none
pos=$((pos + usersz))
truncate -s $pos "$outimg"
echo ""
echo "Instantiate by appending to the qemu command line:"
echo " -drive file=$outimg,if=none,format=raw,id=emmc-img"
echo " -device emmc,boot-partition-size=$bootsz,rpmb-partition-size=$rpmbsz,drive=emmc-img"
+11 -13
View File
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import sys
@@ -7,15 +6,6 @@ import json
import shlex
import subprocess
def find_command(src, target, compile_commands):
for command in compile_commands:
if command['file'] != src:
continue
if target != '' and command['command'].find(target) == -1:
continue
return command['command']
return 'false'
def process_command(src, command):
skip = False
out = []
@@ -43,14 +33,22 @@ def main(args):
print("MODINFO_DEBUG target %s" % target)
arch = target[:-8] # cut '-softmmu'
print("MODINFO_START arch \"%s\" MODINFO_END" % arch)
with open('compile_commands.json') as f:
compile_commands = json.load(f)
for src in args:
compile_commands_json = json.load(f)
compile_commands = { x['output']: x for x in compile_commands_json }
for obj in args:
entry = compile_commands.get(obj, None)
if not entry:
sys.stderr.write(f'modinfo: Could not find object file {obj}')
sys.exit(1)
src = entry['file']
if not src.endswith('.c'):
print("MODINFO_DEBUG skip %s" % src)
continue
command = entry['command']
print("MODINFO_DEBUG src %s" % src)
command = find_command(src, target, compile_commands)
cmdline = process_command(src, command)
print("MODINFO_DEBUG cmd", cmdline)
result = subprocess.run(cmdline, stdout = subprocess.PIPE,
-1
View File
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import sys
+23 -25
View File
@@ -8,34 +8,35 @@ from collections import defaultdict
import itertools
import json
import os
import shlex
import sys
class Suite(object):
def __init__(self):
self.deps = set()
self.speeds = ['quick']
self.speeds = set()
def names(self, base):
return [base if speed == 'quick' else f'{base}-{speed}' for speed in self.speeds]
return [f'{base}-{speed}' for speed in self.speeds]
print('''
print(r'''
SPEED = quick
.speed.quick = $(foreach s,$(sort $(filter-out %-slow %-thorough, $1)), --suite $s)
.speed.slow = $(foreach s,$(sort $(filter-out %-thorough, $1)), --suite $s)
.speed.thorough = $(foreach s,$(sort $1), --suite $s)
.speed.quick = $(sort $(filter-out %-slow %-thorough, $1))
.speed.slow = $(sort $(filter-out %-thorough, $1))
.speed.thorough = $(sort $1)
TIMEOUT_MULTIPLIER = 1
TIMEOUT_MULTIPLIER ?= 1
.mtestargs = --no-rebuild -t $(TIMEOUT_MULTIPLIER)
ifneq ($(SPEED), quick)
.mtestargs += --setup $(SPEED)
endif
.mtestargs += $(subst -j,--num-processes , $(filter-out -j, $(lastword -j1 $(filter -j%, $(MAKEFLAGS)))))
.check.mtestargs = $(MTESTARGS) $(.mtestargs) $(if $(V),--verbose,--print-errorlogs)
.bench.mtestargs = $(MTESTARGS) $(.mtestargs) --benchmark --verbose''')
.check.mtestargs = $(MTESTARGS) $(.mtestargs) $(if $(V),--verbose,--print-errorlogs) \
$(foreach s, $(sort $(.check.mtest-suites)), --suite $s)
.bench.mtestargs = $(MTESTARGS) $(.mtestargs) --benchmark --verbose \
$(foreach s, $(sort $(.bench.mtest-suites)), --suite $s)''')
introspect = json.load(sys.stdin)
@@ -57,13 +58,13 @@ def process_tests(test, targets, suites):
s = s.split(':')[1]
if s == 'slow' or s == 'thorough':
continue
suites[s].deps.update(deps)
if s.endswith('-slow'):
s = s[:-5]
suites[s].speeds.append('slow')
suites[s].speeds.add('slow')
if s.endswith('-thorough'):
s = s[:-9]
suites[s].speeds.append('thorough')
suites[s].deps.update(deps)
suites[s].speeds.add('thorough')
def emit_prolog(suites, prefix):
all_targets = ' '.join((f'{prefix}-{k}' for k in suites.keys()))
@@ -72,29 +73,26 @@ def emit_prolog(suites, prefix):
print(f'all-{prefix}-targets = {all_targets}')
print(f'all-{prefix}-xml = {all_xml}')
print(f'.PHONY: {prefix} do-meson-{prefix} {prefix}-report.junit.xml $(all-{prefix}-targets) $(all-{prefix}-xml)')
print(f'ifeq ($(filter {prefix}, $(MAKECMDGOALS)),)')
print(f'.{prefix}.mtestargs += $(call .speed.$(SPEED), $(.{prefix}.mtest-suites))')
print(f'endif')
print(f'ninja-cmd-goals += $(foreach s, $(.{prefix}.mtest-suites), $(.{prefix}-$s.deps))')
print(f'{prefix}-build: run-ninja')
print(f'{prefix} $(all-{prefix}-targets): do-meson-{prefix}')
print(f'do-meson-{prefix}: run-ninja; $(if $(MAKE.n),,+)$(MESON) test $(.{prefix}.mtestargs)')
print(f'{prefix}-report.junit.xml $(all-{prefix}-xml): {prefix}-report%.junit.xml: run-ninja')
print(f'\t$(MAKE) {prefix}$* MTESTARGS="$(MTESTARGS) --logbase {prefix}-report$*" && ln -f meson-logs/$@ .')
def emit_suite_deps(name, suite, prefix):
def emit_suite(name, suite, prefix):
deps = ' '.join(suite.deps)
targets = [f'{prefix}-{name}', f'{prefix}-report-{name}.junit.xml', f'{prefix}', f'{prefix}-report.junit.xml',
f'{prefix}-build']
print()
print(f'.{prefix}-{name}.deps = {deps}')
for t in targets:
print(f'.ninja-goals.{t} += $(.{prefix}-{name}.deps)')
print(f'.ninja-goals.check-build += $(.{prefix}-{name}.deps)')
def emit_suite(name, suite, prefix):
emit_suite_deps(name, suite, prefix)
targets = f'{prefix}-{name} {prefix}-report-{name}.junit.xml {prefix} {prefix}-report.junit.xml'
names = ' '.join(suite.names(name))
targets = f'{prefix}-{name} {prefix}-report-{name}.junit.xml'
if not name.endswith('-slow') and not name.endswith('-thorough'):
targets += f' {prefix} {prefix}-report.junit.xml'
print(f'ifneq ($(filter {targets}, $(MAKECMDGOALS)),)')
print(f'.{prefix}.mtest-suites += ' + ' '.join(suite.names(name)))
# for the "base" suite possibly add FOO-slow and FOO-thorough
print(f".{prefix}.mtest-suites += {name} $(call .speed.$(SPEED), {names})")
print(f'endif')
targets = {t['id']: [os.path.relpath(f) for f in t['filename']]
+7 -7
View File
@@ -23,7 +23,7 @@ def find_deps(exe_or_dll, search_path, analyzed_deps):
output = subprocess.check_output(["objdump", "-p", exe_or_dll], text=True)
output = output.split("\n")
for line in output:
if not line.startswith("\tDLL Name: "):
if not line.lstrip().startswith("DLL Name: "):
continue
dep = line.split("DLL Name: ")[1].strip()
@@ -37,10 +37,10 @@ def find_deps(exe_or_dll, search_path, analyzed_deps):
analyzed_deps.add(dep)
# locate the dll dependencies recursively
rdeps = find_deps(dll, search_path, analyzed_deps)
analyzed_deps, rdeps = find_deps(dll, search_path, analyzed_deps)
deps.extend(rdeps)
return deps
return analyzed_deps, deps
def main():
parser = argparse.ArgumentParser(description="QEMU NSIS build helper.")
@@ -92,18 +92,18 @@ def main():
dlldir = os.path.join(destdir + prefix, "dll")
os.mkdir(dlldir)
analyzed_deps = set()
for exe in glob.glob(os.path.join(destdir + prefix, "*.exe")):
signcode(exe)
# find all dll dependencies
deps = set(find_deps(exe, search_path, set()))
analyzed_deps, deps = find_deps(exe, search_path, analyzed_deps)
deps = set(deps)
deps.remove(exe)
# copy all dlls to the DLLDIR
for dep in deps:
dllfile = os.path.join(dlldir, os.path.basename(dep))
if (os.path.exists(dllfile)):
continue
print("Copying '%s' to '%s'" % (dep, dllfile))
shutil.copy(dep, dllfile)
@@ -114,7 +114,7 @@ def main():
"-DSRCDIR=" + args.srcdir,
"-DBINDIR=" + destdir + prefix,
]
if args.cpu == "x86_64":
if args.cpu == "aarch64" or args.cpu == "x86_64":
makensis += ["-DW64"]
makensis += ["-DDLLDIR=" + dlldir]
-1
View File
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
This takes a crashing qtest trace and tries to remove superfluous operations
-1
View File
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Convert plain qtest traces to C or Bash reproducers
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Use this to convert qtest log info from a generic fuzzer input into a qtest
-1
View File
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
# coding: utf-8
#
# Probe gdb for supported architectures.
#
-3
View File
@@ -1,3 +0,0 @@
[flake8]
# Prefer pylint's bare-except checks to flake8's
extend-ignore = E722
-7
View File
@@ -1,7 +0,0 @@
[settings]
force_grid_wrap=4
force_sort_within_sections=True
include_trailing_comma=True
line_length=72
lines_after_imports=2
multi_line_output=3
+65
View File
@@ -0,0 +1,65 @@
# This work is licensed under the terms of the GNU GPL, version 2 or later.
# See the COPYING file in the top-level directory.
from abc import ABC, abstractmethod
from .commands import gen_commands
from .events import gen_events
from .features import gen_features
from .introspect import gen_introspect
from .schema import QAPISchema
from .types import gen_types
from .visit import gen_visit
class QAPIBackend(ABC):
# pylint: disable=too-few-public-methods
@abstractmethod
def generate(self,
schema: QAPISchema,
output_dir: str,
prefix: str,
unmask: bool,
builtins: bool,
gen_tracing: bool) -> None:
"""
Generate code for the given schema into the target directory.
:param schema: The primary QAPI schema object.
:param output_dir: The output directory to store generated code.
:param prefix: Optional C-code prefix for symbol names.
:param unmask: Expose non-ABI names through introspection?
:param builtins: Generate code for built-in types?
:raise QAPIError: On failures.
"""
class QAPICBackend(QAPIBackend):
# pylint: disable=too-few-public-methods
def generate(self,
schema: QAPISchema,
output_dir: str,
prefix: str,
unmask: bool,
builtins: bool,
gen_tracing: bool) -> None:
"""
Generate C code for the given schema into the target directory.
:param schema_file: The primary QAPI schema file.
:param output_dir: The output directory to store generated code.
:param prefix: Optional C-code prefix for symbol names.
:param unmask: Expose non-ABI names through introspection?
:param builtins: Generate code for built-in types?
:raise QAPIError: On failures.
"""
gen_types(schema, output_dir, prefix, builtins)
gen_features(schema, output_dir, prefix)
gen_visit(schema, output_dir, prefix, builtins)
gen_commands(schema, output_dir, prefix, gen_tracing)
gen_events(schema, output_dir, prefix)
gen_introspect(schema, output_dir, prefix, unmask)
+17 -43
View File
@@ -13,19 +13,13 @@ This work is licensed under the terms of the GNU GPL, version 2.
See the COPYING file in the top-level directory.
"""
from typing import (
Dict,
List,
Optional,
Set,
)
from typing import List, Optional
from .common import c_name, mcgen
from .gen import (
QAPIGenC,
QAPISchemaModularCVisitor,
build_params,
gen_special_features,
gen_features,
ifcontext,
)
from .schema import (
@@ -112,11 +106,7 @@ def gen_call(name: str,
''')
if ret_type:
ret += mcgen('''
qmp_marshal_output_%(c_name)s(retval, ret, errp);
''',
c_name=ret_type.c_name())
ret += gen_marshal_output(ret_type)
if gen_tracing:
if ret_type:
@@ -142,22 +132,16 @@ def gen_call(name: str,
def gen_marshal_output(ret_type: QAPISchemaType) -> str:
return mcgen('''
static void qmp_marshal_output_%(c_name)s(%(c_type)s ret_in,
QObject **ret_out, Error **errp)
{
Visitor *v;
v = qobject_output_visitor_new_qmp(ret_out);
if (visit_type_%(c_name)s(v, "unused", &ret_in, errp)) {
visit_complete(v, ret_out);
ov = qobject_output_visitor_new_qmp(ret);
if (visit_type_%(c_name)s(ov, "unused", &retval, errp)) {
visit_complete(ov, ret);
}
visit_free(v);
v = qapi_dealloc_visitor_new();
visit_type_%(c_name)s(v, "unused", &ret_in, NULL);
visit_free(v);
}
visit_free(ov);
ov = qapi_dealloc_visitor_new();
visit_type_%(c_name)s(ov, "unused", &retval, NULL);
visit_free(ov);
''',
c_type=ret_type.c_type(), c_name=ret_type.c_name())
c_name=ret_type.c_name())
def build_marshal_proto(name: str,
@@ -209,6 +193,7 @@ def gen_marshal(name: str,
if ret_type:
ret += mcgen('''
%(c_type)s retval;
Visitor *ov;
''',
c_type=ret_type.c_type())
@@ -298,7 +283,7 @@ def gen_register_command(name: str,
''',
name=name, c_name=c_name(name),
opts=' | '.join(options) or 0,
feats=gen_special_features(features))
feats=gen_features(features))
return ret
@@ -308,11 +293,9 @@ class QAPISchemaGenCommandVisitor(QAPISchemaModularCVisitor):
prefix, 'qapi-commands',
' * Schema-defined QAPI/QMP commands', None, __doc__,
gen_tracing=gen_tracing)
self._visited_ret_types: Dict[QAPIGenC, Set[QAPISchemaType]] = {}
self._gen_tracing = gen_tracing
def _begin_user_module(self, name: str) -> None:
self._visited_ret_types[self._genc] = set()
commands = self._module_basename('qapi-commands', name)
types = self._module_basename('qapi-types', name)
visit = self._module_basename('qapi-visit', name)
@@ -320,7 +303,7 @@ class QAPISchemaGenCommandVisitor(QAPISchemaModularCVisitor):
#include "qemu/osdep.h"
#include "qapi/compat-policy.h"
#include "qapi/visitor.h"
#include "qapi/qmp/qdict.h"
#include "qobject/qdict.h"
#include "qapi/dealloc-visitor.h"
#include "qapi/error.h"
#include "%(visit)s.h"
@@ -330,7 +313,7 @@ class QAPISchemaGenCommandVisitor(QAPISchemaModularCVisitor):
if self._gen_tracing and commands != 'qapi-commands':
self._genc.add(mcgen('''
#include "qapi/qmp/qjson.h"
#include "qobject/qjson.h"
#include "trace/trace-%(nm)s_trace_events.h"
''',
nm=c_name(commands, protect=False)))
@@ -346,7 +329,7 @@ class QAPISchemaGenCommandVisitor(QAPISchemaModularCVisitor):
def visit_begin(self, schema: QAPISchema) -> None:
self._add_module('./init', ' * QAPI Commands initialization')
self._genh.add(mcgen('''
#include "qapi/qmp/dispatch.h"
#include "qapi/qmp-registry.h"
void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds);
''',
@@ -355,6 +338,7 @@ void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds);
#include "qemu/osdep.h"
#include "%(prefix)sqapi-commands.h"
#include "%(prefix)sqapi-init-commands.h"
#include "%(prefix)sqapi-features.h"
void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds)
{
@@ -385,16 +369,6 @@ void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds)
coroutine: bool) -> None:
if not gen:
return
# FIXME: If T is a user-defined type, the user is responsible
# for making this work, i.e. to make T's condition the
# conjunction of the T-returning commands' conditions. If T
# is a built-in type, this isn't possible: the
# qmp_marshal_output_T() will be generated unconditionally.
if ret_type and ret_type not in self._visited_ret_types[self._genc]:
self._visited_ret_types[self._genc].add(ret_type)
with ifcontext(ret_type.ifcond,
self._genh, self._genc):
self._genc.add(gen_marshal_output(ret_type))
with ifcontext(ifcond, self._genh, self._genc):
self._genh.add(gen_command_decl(name, arg_type, boxed,
ret_type, coroutine))
-2
View File
@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2017-2019 Red Hat Inc.
#
# Authors:
+1 -1
View File
@@ -194,7 +194,7 @@ class QAPISchemaGenEventVisitor(QAPISchemaModularCVisitor):
#include "%(visit)s.h"
#include "qapi/compat-policy.h"
#include "qapi/error.h"
#include "qapi/qmp/qdict.h"
#include "qobject/qdict.h"
#include "qapi/qmp-event.h"
''',
events=events, visit=visit,
-2
View File
@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
#
# Copyright IBM, Corp. 2011
# Copyright (c) 2013-2021 Red Hat Inc.
#
+48
View File
@@ -0,0 +1,48 @@
"""
QAPI features generator
Copyright 2024 Red Hat
This work is licensed under the terms of the GNU GPL, version 2.
# See the COPYING file in the top-level directory.
"""
from typing import ValuesView
from .common import c_enum_const, c_name
from .gen import QAPISchemaMonolithicCVisitor
from .schema import QAPISchema, QAPISchemaFeature
class QAPISchemaGenFeatureVisitor(QAPISchemaMonolithicCVisitor):
def __init__(self, prefix: str):
super().__init__(
prefix, 'qapi-features',
' * Schema-defined QAPI features',
__doc__)
self.features: ValuesView[QAPISchemaFeature]
def visit_begin(self, schema: QAPISchema) -> None:
self.features = schema.features()
self._genh.add("#include \"qapi/util.h\"\n\n")
def visit_end(self) -> None:
self._genh.add("typedef enum {\n")
for f in self.features:
self._genh.add(f" {c_enum_const('qapi_feature', f.name)}")
if f.name in QAPISchemaFeature.SPECIAL_NAMES:
self._genh.add(f" = {c_enum_const('qapi', f.name)},\n")
else:
self._genh.add(",\n")
self._genh.add("} " + c_name('QapiFeature') + ";\n")
def gen_features(schema: QAPISchema,
output_dir: str,
prefix: str) -> None:
vis = QAPISchemaGenFeatureVisitor(prefix)
schema.visit(vis)
vis.write(output_dir)
+5 -6
View File
@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
#
# QAPI code generation
#
# Copyright (c) 2015-2019 Red Hat Inc.
@@ -24,6 +22,7 @@ from typing import (
)
from .common import (
c_enum_const,
c_fname,
c_name,
guardend,
@@ -40,10 +39,10 @@ from .schema import (
from .source import QAPISourceInfo
def gen_special_features(features: Sequence[QAPISchemaFeature]) -> str:
special_features = [f"1u << QAPI_{feat.name.upper()}"
for feat in features if feat.is_special()]
return ' | '.join(special_features) or '0'
def gen_features(features: Sequence[QAPISchemaFeature]) -> str:
feats = [f"1u << {c_enum_const('qapi_feature', feat.name)}"
for feat in features]
return ' | '.join(feats) or '0'
class QAPIGen:
+7 -9
View File
@@ -11,6 +11,7 @@ This work is licensed under the terms of the GNU GPL, version 2.
See the COPYING file in the top-level directory.
"""
from dataclasses import dataclass
from typing import (
Any,
Dict,
@@ -58,7 +59,7 @@ from .source import QAPISourceInfo
#
# Sadly, mypy does not support recursive types; so the _Stub alias is used to
# mark the imprecision in the type model where we'd otherwise use JSONValue.
_Stub = Any
_Stub = Any # pylint: disable=invalid-name
_Scalar = Union[str, bool, None]
_NonScalar = Union[Dict[str, _Stub], List[_Stub]]
_Value = Union[_Scalar, _NonScalar]
@@ -79,19 +80,16 @@ SchemaInfoCommand = Dict[str, object]
_ValueT = TypeVar('_ValueT', bound=_Value)
@dataclass
class Annotated(Generic[_ValueT]):
"""
Annotated generally contains a SchemaInfo-like type (as a dict),
But it also used to wrap comments/ifconds around scalar leaf values,
for the benefit of features and enums.
"""
# TODO: Remove after Python 3.7 adds @dataclass:
# pylint: disable=too-few-public-methods
def __init__(self, value: _ValueT, ifcond: QAPISchemaIfCond,
comment: Optional[str] = None):
self.value = value
self.comment: Optional[str] = comment
self.ifcond = ifcond
value: _ValueT
ifcond: QAPISchemaIfCond
comment: Optional[str] = None
def _tree_to_qlit(obj: JSONValue,
@@ -197,7 +195,7 @@ class QAPISchemaGenIntrospectVisitor(QAPISchemaMonolithicCVisitor):
# generate C
name = c_name(self._prefix, protect=False) + 'qmp_schema_qlit'
self._genh.add(mcgen('''
#include "qapi/qmp/qlit.h"
#include "qobject/qlit.h"
extern const QLitObject %(c_name)s;
''',
+39 -33
View File
@@ -8,17 +8,14 @@ This is the main entry point for generating C code from the QAPI schema.
"""
import argparse
from importlib import import_module
import sys
from typing import Optional
from .commands import gen_commands
from .backend import QAPIBackend, QAPICBackend
from .common import must_match
from .error import QAPIError
from .events import gen_events
from .introspect import gen_introspect
from .schema import QAPISchema
from .types import gen_types
from .visit import gen_visit
def invalid_prefix_char(prefix: str) -> Optional[str]:
@@ -28,31 +25,36 @@ def invalid_prefix_char(prefix: str) -> Optional[str]:
return None
def generate(schema_file: str,
output_dir: str,
prefix: str,
unmask: bool = False,
builtins: bool = False,
gen_tracing: bool = False) -> None:
"""
Generate C code for the given schema into the target directory.
def create_backend(path: str) -> QAPIBackend:
if path is None:
return QAPICBackend()
:param schema_file: The primary QAPI schema file.
:param output_dir: The output directory to store generated code.
:param prefix: Optional C-code prefix for symbol names.
:param unmask: Expose non-ABI names through introspection?
:param builtins: Generate code for built-in types?
module_path, dot, class_name = path.rpartition('.')
if not dot:
raise QAPIError("argument of -B must be of the form MODULE.CLASS")
:raise QAPIError: On failures.
"""
assert invalid_prefix_char(prefix) is None
try:
mod = import_module(module_path)
except Exception as ex:
raise QAPIError(f"unable to import '{module_path}': {ex}") from ex
schema = QAPISchema(schema_file)
gen_types(schema, output_dir, prefix, builtins)
gen_visit(schema, output_dir, prefix, builtins)
gen_commands(schema, output_dir, prefix, gen_tracing)
gen_events(schema, output_dir, prefix)
gen_introspect(schema, output_dir, prefix, unmask)
try:
klass = getattr(mod, class_name)
except AttributeError as ex:
raise QAPIError(
f"module '{module_path}' has no class '{class_name}'") from ex
try:
backend = klass()
except Exception as ex:
raise QAPIError(
f"backend '{path}' cannot be instantiated: {ex}") from ex
if not isinstance(backend, QAPIBackend):
raise QAPIError(
f"backend '{path}' must be an instance of QAPIBackend")
return backend
def main() -> int:
@@ -75,6 +77,8 @@ def main() -> int:
parser.add_argument('-u', '--unmask-non-abi-names', action='store_true',
dest='unmask',
help="expose non-ABI names in introspection")
parser.add_argument('-B', '--backend', default=None,
help="Python module name for code generator")
# Option --suppress-tracing exists so we can avoid solving build system
# problems. TODO Drop it when we no longer need it.
@@ -91,12 +95,14 @@ def main() -> int:
return 1
try:
generate(args.schema,
output_dir=args.output_dir,
prefix=args.prefix,
unmask=args.unmask,
builtins=args.builtins,
gen_tracing=not args.suppress_tracing)
schema = QAPISchema(args.schema)
backend = create_backend(args.backend)
backend.generate(schema,
output_dir=args.output_dir,
prefix=args.prefix,
unmask=args.unmask,
builtins=args.builtins,
gen_tracing=not args.suppress_tracing)
except QAPIError as err:
print(err, file=sys.stderr)
return 1
-4
View File
@@ -1,4 +0,0 @@
[mypy]
strict = True
disallow_untyped_calls = False
python_version = 3.8
+182 -46
View File
@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
#
# QAPI schema parser
#
# Copyright IBM, Corp. 2011
@@ -14,7 +12,7 @@
# This work is licensed under the terms of the GNU GPL, version 2.
# See the COPYING file in the top-level directory.
from collections import OrderedDict
import enum
import os
import re
from typing import (
@@ -110,6 +108,11 @@ class QAPISchemaParser:
self.exprs: List[QAPIExpression] = []
self.docs: List[QAPIDoc] = []
# State for tracking qmp-example blocks and simple
# :: literal blocks.
self._literal_mode = False
self._literal_mode_indent = 0
# Showtime!
self._parse()
@@ -154,7 +157,7 @@ class QAPISchemaParser:
"value of 'include' must be a string")
incl_fname = os.path.join(os.path.dirname(self._fname),
include)
self._add_expr(OrderedDict({'include': incl_fname}), info)
self._add_expr({'include': incl_fname}, info)
exprs_include = self._include(include, info, incl_fname,
self._included)
if exprs_include:
@@ -355,7 +358,7 @@ class QAPISchemaParser:
raise QAPIParseError(self, "stray '%s'" % match.group(0))
def get_members(self) -> Dict[str, object]:
expr: Dict[str, object] = OrderedDict()
expr: Dict[str, object] = {}
if self.tok == '}':
self.accept()
return expr
@@ -425,12 +428,55 @@ class QAPISchemaParser:
if self.val != '##':
raise QAPIParseError(
self, "junk after '##' at end of documentation comment")
self._literal_mode = False
return None
if self.val == '#':
return ''
if self.val[1] != ' ':
raise QAPIParseError(self, "missing space after #")
return self.val[2:].rstrip()
line = self.val[2:].rstrip()
if re.match(r'(\.\. +qmp-example)? *::$', line):
self._literal_mode = True
self._literal_mode_indent = 0
elif self._literal_mode and line:
indent = must_match(r'\s*', line).end()
if self._literal_mode_indent == 0:
self._literal_mode_indent = indent
elif indent < self._literal_mode_indent:
# ReST directives stop at decreasing indentation
self._literal_mode = False
if not self._literal_mode:
self._validate_doc_line_format(line)
return line
def _validate_doc_line_format(self, line: str) -> None:
"""
Validate documentation format rules for a single line:
1. Lines should not exceed 70 characters
2. Sentences should be separated by two spaces
"""
full_line_length = len(line) + 2 # "# " = 2 characters
if full_line_length > 70:
# Skip URL lines - they can't be broken
if re.match(r' *(https?|ftp)://[^ ]*$', line):
pass
else:
raise QAPIParseError(
self, "documentation line longer than 70 characters")
single_space_pattern = r'(\be\.g\.|^ *\d\.|([.!?])) [A-Z0-9(]'
for m in list(re.finditer(single_space_pattern, line)):
if not m.group(2):
continue
# HACK so the error message points to the offending spot
self.pos = self.line_pos + 2 + m.start(2) + 1
raise QAPIParseError(
self, "Use two spaces between sentences\n"
"If this not the end of a sentence, please report a bug.")
@staticmethod
def _match_at_name_colon(string: str) -> Optional[Match[str]]:
@@ -575,18 +621,17 @@ class QAPISchemaParser:
)
raise QAPIParseError(self, emsg)
doc.new_tagged_section(self.info, match.group(1))
doc.new_tagged_section(
self.info,
QAPIDoc.Kind.from_string(match.group(1))
)
text = line[match.end():]
if text:
doc.append_line(text)
line = self.get_doc_indented(doc)
no_more_args = True
elif line.startswith('='):
raise QAPIParseError(
self,
"unexpected '=' markup in definition documentation")
else:
# tag-less paragraph
# plain paragraph
doc.ensure_untagged_section(self.info)
doc.append_line(line)
line = self.get_doc_paragraph(doc)
@@ -594,22 +639,15 @@ class QAPISchemaParser:
# Free-form documentation
doc = QAPIDoc(info)
doc.ensure_untagged_section(self.info)
first = True
while line is not None:
if match := self._match_at_name_colon(line):
raise QAPIParseError(
self,
"'@%s:' not allowed in free-form documentation"
% match.group(1))
if line.startswith('='):
if not first:
raise QAPIParseError(
self,
"'=' heading must come first in a comment block")
doc.append_line(line)
self.accept(False)
line = self.get_doc_line()
first = False
self.accept()
doc.end()
@@ -635,23 +673,51 @@ class QAPIDoc:
Free-form documentation blocks consist only of a body section.
"""
class Kind(enum.Enum):
PLAIN = 0
MEMBER = 1
FEATURE = 2
RETURNS = 3
ERRORS = 4
SINCE = 5
TODO = 6
@staticmethod
def from_string(kind: str) -> 'QAPIDoc.Kind':
return QAPIDoc.Kind[kind.upper()]
def __str__(self) -> str:
return self.name.title()
class Section:
# pylint: disable=too-few-public-methods
def __init__(self, info: QAPISourceInfo,
tag: Optional[str] = None):
def __init__(
self,
info: QAPISourceInfo,
kind: 'QAPIDoc.Kind',
):
# section source info, i.e. where it begins
self.info = info
# section tag, if any ('Returns', '@name', ...)
self.tag = tag
# section kind
self.kind = kind
# section text without tag
self.text = ''
def __repr__(self) -> str:
return f"<QAPIDoc.Section kind={self.kind!r} text={self.text!r}>"
def append_line(self, line: str) -> None:
self.text += line + '\n'
class ArgSection(Section):
def __init__(self, info: QAPISourceInfo, tag: str):
super().__init__(info, tag)
def __init__(
self,
info: QAPISourceInfo,
kind: 'QAPIDoc.Kind',
name: str
):
super().__init__(info, kind)
self.name = name
self.member: Optional['QAPISchemaMember'] = None
def connect(self, member: 'QAPISchemaMember') -> None:
@@ -663,7 +729,9 @@ class QAPIDoc:
# definition doc's symbol, None for free-form doc
self.symbol: Optional[str] = symbol
# the sections in textual order
self.all_sections: List[QAPIDoc.Section] = [QAPIDoc.Section(info)]
self.all_sections: List[QAPIDoc.Section] = [
QAPIDoc.Section(info, QAPIDoc.Kind.PLAIN)
]
# the body section
self.body: Optional[QAPIDoc.Section] = self.all_sections[0]
# dicts mapping parameter/feature names to their description
@@ -680,55 +748,71 @@ class QAPIDoc:
def end(self) -> None:
for section in self.all_sections:
section.text = section.text.strip('\n')
if section.tag is not None and section.text == '':
if section.kind != QAPIDoc.Kind.PLAIN and section.text == '':
raise QAPISemError(
section.info, "text required after '%s:'" % section.tag)
section.info, "text required after '%s:'" % section.kind)
def ensure_untagged_section(self, info: QAPISourceInfo) -> None:
if self.all_sections and not self.all_sections[-1].tag:
kind = QAPIDoc.Kind.PLAIN
if self.all_sections and self.all_sections[-1].kind == kind:
# extend current section
self.all_sections[-1].text += '\n'
section = self.all_sections[-1]
if not section.text:
# Section is empty so far; update info to start *here*.
section.info = info
section.text += '\n'
return
# start new section
section = self.Section(info)
section = self.Section(info, kind)
self.sections.append(section)
self.all_sections.append(section)
def new_tagged_section(self, info: QAPISourceInfo, tag: str) -> None:
section = self.Section(info, tag)
if tag == 'Returns':
def new_tagged_section(
self,
info: QAPISourceInfo,
kind: 'QAPIDoc.Kind',
) -> None:
section = self.Section(info, kind)
if kind == QAPIDoc.Kind.RETURNS:
if self.returns:
raise QAPISemError(
info, "duplicated '%s' section" % tag)
info, "duplicated '%s' section" % kind)
self.returns = section
elif tag == 'Errors':
elif kind == QAPIDoc.Kind.ERRORS:
if self.errors:
raise QAPISemError(
info, "duplicated '%s' section" % tag)
info, "duplicated '%s' section" % kind)
self.errors = section
elif tag == 'Since':
elif kind == QAPIDoc.Kind.SINCE:
if self.since:
raise QAPISemError(
info, "duplicated '%s' section" % tag)
info, "duplicated '%s' section" % kind)
self.since = section
self.sections.append(section)
self.all_sections.append(section)
def _new_description(self, info: QAPISourceInfo, name: str,
desc: Dict[str, ArgSection]) -> None:
def _new_description(
self,
info: QAPISourceInfo,
name: str,
kind: 'QAPIDoc.Kind',
desc: Dict[str, ArgSection]
) -> None:
if not name:
raise QAPISemError(info, "invalid parameter name")
if name in desc:
raise QAPISemError(info, "'%s' parameter name duplicated" % name)
section = self.ArgSection(info, '@' + name)
section = self.ArgSection(info, kind, name)
self.all_sections.append(section)
desc[name] = section
def new_argument(self, info: QAPISourceInfo, name: str) -> None:
self._new_description(info, name, self.args)
self._new_description(info, name, QAPIDoc.Kind.MEMBER, self.args)
def new_feature(self, info: QAPISourceInfo, name: str) -> None:
self._new_description(info, name, self.features)
self._new_description(info, name, QAPIDoc.Kind.FEATURE, self.features)
def append_line(self, line: str) -> None:
self.all_sections[-1].append_line(line)
@@ -740,8 +824,23 @@ class QAPIDoc:
raise QAPISemError(member.info,
"%s '%s' lacks documentation"
% (member.role, member.name))
self.args[member.name] = QAPIDoc.ArgSection(
self.info, '@' + member.name)
# Insert stub documentation section for missing member docs.
# TODO: drop when undocumented members are outlawed
section = QAPIDoc.ArgSection(
self.info, QAPIDoc.Kind.MEMBER, member.name)
self.args[member.name] = section
# Determine where to insert stub doc - it should go at the
# end of the members section(s), if any. Note that index 0
# is assumed to be an untagged intro section, even if it is
# empty.
index = 1
if len(self.all_sections) > 1:
while self.all_sections[index].kind == QAPIDoc.Kind.MEMBER:
index += 1
self.all_sections.insert(index, section)
self.args[member.name].connect(member)
def connect_feature(self, feature: 'QAPISchemaFeature') -> None:
@@ -751,6 +850,43 @@ class QAPIDoc:
% feature.name)
self.features[feature.name].connect(feature)
def ensure_returns(self, info: QAPISourceInfo) -> None:
def _insert_near_kind(
kind: QAPIDoc.Kind,
new_sect: QAPIDoc.Section,
after: bool = False,
) -> bool:
for idx, sect in enumerate(reversed(self.all_sections)):
if sect.kind == kind:
pos = len(self.all_sections) - idx - 1
if after:
pos += 1
self.all_sections.insert(pos, new_sect)
return True
return False
if any(s.kind == QAPIDoc.Kind.RETURNS for s in self.all_sections):
return
# Stub "Returns" section for undocumented returns value
stub = QAPIDoc.Section(info, QAPIDoc.Kind.RETURNS)
if any(_insert_near_kind(kind, stub, after) for kind, after in (
# 1. If arguments, right after those.
(QAPIDoc.Kind.MEMBER, True),
# 2. Elif errors, right *before* those.
(QAPIDoc.Kind.ERRORS, False),
# 3. Elif features, right *before* those.
(QAPIDoc.Kind.FEATURE, False),
)):
return
# Otherwise, it should go right after the intro. The intro
# is always the first section and is always present (even
# when empty), so we can insert directly at index=1 blindly.
self.all_sections.insert(1, stub)
def check_expr(self, expr: QAPIExpression) -> None:
if 'command' in expr:
if self.returns and 'returns' not in expr:
+2
View File
@@ -17,7 +17,9 @@ disable=consider-using-f-string,
too-many-arguments,
too-many-branches,
too-many-instance-attributes,
too-many-positional-arguments,
too-many-statements,
unknown-option-value,
useless-option-value,
[REPORTS]
+37 -8
View File
@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
#
# QAPI schema internal representation
#
# Copyright (c) 2015-2019 Red Hat Inc.
@@ -19,7 +17,6 @@
from __future__ import annotations
from abc import ABC, abstractmethod
from collections import OrderedDict
import os
import re
from typing import (
@@ -29,6 +26,7 @@ from typing import (
List,
Optional,
Union,
ValuesView,
cast,
)
@@ -556,7 +554,7 @@ class QAPISchemaObjectType(QAPISchemaType):
super().check(schema)
assert self._checked and not self._check_complete
seen = OrderedDict()
seen = {}
if self._base_name:
self.base = schema.resolve_type(self._base_name, self.info,
"'base'")
@@ -933,8 +931,11 @@ class QAPISchemaEnumMember(QAPISchemaMember):
class QAPISchemaFeature(QAPISchemaMember):
role = 'feature'
# Features which are standardized across all schemas
SPECIAL_NAMES = ['deprecated', 'unstable']
def is_special(self) -> bool:
return self.name in ('deprecated', 'unstable')
return self.name in QAPISchemaFeature.SPECIAL_NAMES
class QAPISchemaObjectTypeMember(QAPISchemaMember):
@@ -1059,6 +1060,9 @@ class QAPISchemaCommand(QAPISchemaDefinition):
if self.arg_type and self.arg_type.is_implicit():
self.arg_type.connect_doc(doc)
if self.ret_type and self.info:
doc.ensure_returns(self.info)
def visit(self, visitor: QAPISchemaVisitor) -> None:
super().visit(visitor)
visitor.visit_command(
@@ -1137,7 +1141,17 @@ class QAPISchema:
self.docs = parser.docs
self._entity_list: List[QAPISchemaEntity] = []
self._entity_dict: Dict[str, QAPISchemaDefinition] = {}
self._module_dict: Dict[str, QAPISchemaModule] = OrderedDict()
self._module_dict: Dict[str, QAPISchemaModule] = {}
# NB, values in the dict will identify the first encountered
# usage of a named feature only
self._feature_dict: Dict[str, QAPISchemaFeature] = {}
# All schemas get the names defined in the QapiSpecialFeature enum.
# Rely on dict iteration order matching insertion order so that
# the special names are emitted first when generating code.
for f in QAPISchemaFeature.SPECIAL_NAMES:
self._feature_dict[f] = QAPISchemaFeature(f, None)
self._schema_dir = os.path.dirname(fname)
self._make_module(QAPISchemaModule.BUILTIN_MODULE_NAME)
self._make_module(fname)
@@ -1147,6 +1161,9 @@ class QAPISchema:
self._def_exprs(exprs)
self.check()
def features(self) -> ValuesView[QAPISchemaFeature]:
return self._feature_dict.values()
def _def_entity(self, ent: QAPISchemaEntity) -> None:
self._entity_list.append(ent)
@@ -1258,6 +1275,12 @@ class QAPISchema:
) -> List[QAPISchemaFeature]:
if features is None:
return []
for f in features:
feat = QAPISchemaFeature(f['name'], info)
if feat.name not in self._feature_dict:
self._feature_dict[feat.name] = feat
return [QAPISchemaFeature(f['name'], info,
QAPISchemaIfCond(f.get('if')))
for f in features]
@@ -1431,7 +1454,7 @@ class QAPISchema:
ifcond = QAPISchemaIfCond(expr.get('if'))
info = expr.info
features = self._make_features(expr.get('features'), info)
if isinstance(data, OrderedDict):
if isinstance(data, dict):
data = self._make_implicit_object_type(
name, info, ifcond,
'arg', self._make_members(data, info))
@@ -1450,7 +1473,7 @@ class QAPISchema:
ifcond = QAPISchemaIfCond(expr.get('if'))
info = expr.info
features = self._make_features(expr.get('features'), info)
if isinstance(data, OrderedDict):
if isinstance(data, dict):
data = self._make_implicit_object_type(
name, info, ifcond,
'arg', self._make_members(data, info))
@@ -1485,6 +1508,12 @@ class QAPISchema:
for doc in self.docs:
doc.check()
features = list(self._feature_dict.values())
if len(features) > 64:
raise QAPISemError(
features[64].info,
"Maximum of 64 schema features is permitted")
def visit(self, visitor: QAPISchemaVisitor) -> None:
visitor.visit_begin(self)
for mod in self._module_dict.values():
+2 -2
View File
@@ -47,9 +47,9 @@ class QAPISourceInfo:
self.defn_meta = meta
self.defn_name = name
def next_line(self: T) -> T:
def next_line(self: T, n: int = 1) -> T:
info = copy.copy(self)
info.line += 1
info.line += n
return info
def loc(self) -> str:
+11 -12
View File
@@ -16,11 +16,7 @@ This work is licensed under the terms of the GNU GPL, version 2.
from typing import List, Optional
from .common import c_enum_const, c_name, mcgen
from .gen import (
QAPISchemaModularCVisitor,
gen_special_features,
ifcontext,
)
from .gen import QAPISchemaModularCVisitor, gen_features, ifcontext
from .schema import (
QAPISchema,
QAPISchemaAlternatives,
@@ -61,17 +57,17 @@ const QEnumLookup %(c_name)s_lookup = {
index=index, name=memb.name)
ret += memb.ifcond.gen_endif()
special_features = gen_special_features(memb.features)
if special_features != '0':
features = gen_features(memb.features)
if features != '0':
feats += mcgen('''
[%(index)s] = %(special_features)s,
[%(index)s] = %(features)s,
''',
index=index, special_features=special_features)
index=index, features=features)
if feats:
ret += mcgen('''
},
.special_features = (const unsigned char[%(max_index)s]) {
.features = (const uint64_t[%(max_index)s]) {
''',
max_index=max_index)
ret += feats
@@ -308,11 +304,14 @@ class QAPISchemaGenTypeVisitor(QAPISchemaModularCVisitor):
#include "qapi/dealloc-visitor.h"
#include "%(types)s.h"
#include "%(visit)s.h"
#include "%(prefix)sqapi-features.h"
''',
types=types, visit=visit))
types=types, visit=visit,
prefix=self._prefix))
self._genh.preamble_add(mcgen('''
#include "qapi/qapi-builtin-types.h"
'''))
''',
prefix=self._prefix))
def visit_begin(self, schema: QAPISchema) -> None:
# gen_object() is recursive, ensure it doesn't visit the empty type
+9 -12
View File
@@ -21,11 +21,7 @@ from .common import (
indent,
mcgen,
)
from .gen import (
QAPISchemaModularCVisitor,
gen_special_features,
ifcontext,
)
from .gen import QAPISchemaModularCVisitor, gen_features, ifcontext
from .schema import (
QAPISchema,
QAPISchemaAlternatives,
@@ -103,15 +99,15 @@ bool visit_type_%(c_name)s_members(Visitor *v, %(c_name)s *obj, Error **errp)
''',
name=memb.name, has=has)
indent.increase()
special_features = gen_special_features(memb.features)
if special_features != '0':
features = gen_features(memb.features)
if features != '0':
ret += mcgen('''
if (visit_policy_reject(v, "%(name)s", %(special_features)s, errp)) {
if (visit_policy_reject(v, "%(name)s", %(features)s, errp)) {
return false;
}
if (!visit_policy_skip(v, "%(name)s", %(special_features)s)) {
if (!visit_policy_skip(v, "%(name)s", %(features)s)) {
''',
name=memb.name, special_features=special_features)
name=memb.name, features=features)
indent.increase()
ret += mcgen('''
if (!visit_type_%(c_type)s(v, "%(name)s", &obj->%(c_name)s, errp)) {
@@ -120,7 +116,7 @@ bool visit_type_%(c_name)s_members(Visitor *v, %(c_name)s *obj, Error **errp)
''',
c_type=memb.type.c_name(), name=memb.name,
c_name=c_name(memb.name))
if special_features != '0':
if features != '0':
indent.decrease()
ret += mcgen('''
}
@@ -360,8 +356,9 @@ class QAPISchemaGenVisitVisitor(QAPISchemaModularCVisitor):
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "%(visit)s.h"
#include "%(prefix)sqapi-features.h"
''',
visit=visit))
visit=visit, prefix=self._prefix))
self._genh.preamble_add(mcgen('''
#include "qapi/qapi-builtin-visit.h"
#include "%(types)s.h"
+449
View File
@@ -0,0 +1,449 @@
#!/usr/bin/env python3
# This tool reads a disk image in any format and converts it to qcow2,
# writing the result directly to stdout.
#
# Copyright (C) 2024 Igalia, S.L.
#
# Authors: Alberto Garcia <berto@igalia.com>
# Madeeha Javed <javed@igalia.com>
#
# SPDX-License-Identifier: GPL-2.0-or-later
#
# qcow2 files produced by this script are always arranged like this:
#
# - qcow2 header
# - refcount table
# - refcount blocks
# - L1 table
# - L2 tables
# - Data clusters
#
# A note about variable names: in qcow2 there is one refcount table
# and one (active) L1 table, although each can occupy several
# clusters. For the sake of simplicity the code sometimes talks about
# refcount tables and L1 tables when referring to those clusters.
import argparse
import errno
import math
import os
import signal
import struct
import subprocess
import sys
import tempfile
import time
from contextlib import contextmanager
QCOW2_DEFAULT_CLUSTER_SIZE = 65536
QCOW2_DEFAULT_REFCOUNT_BITS = 16
QCOW2_FEATURE_NAME_TABLE = 0x6803F857
QCOW2_DATA_FILE_NAME_STRING = 0x44415441
QCOW2_V3_HEADER_LENGTH = 112 # Header length in QEMU 9.0. Must be a multiple of 8
QCOW2_INCOMPAT_DATA_FILE_BIT = 2
QCOW2_AUTOCLEAR_DATA_FILE_RAW_BIT = 1
QCOW_OFLAG_COPIED = 1 << 63
QEMU_STORAGE_DAEMON = "qemu-storage-daemon"
def bitmap_set(bitmap, idx):
bitmap[idx // 8] |= 1 << (idx % 8)
def bitmap_is_set(bitmap, idx):
return (bitmap[idx // 8] & (1 << (idx % 8))) != 0
def bitmap_iterator(bitmap, length):
for idx in range(length):
if bitmap_is_set(bitmap, idx):
yield idx
def align_up(num, d):
return d * math.ceil(num / d)
# Holes in the input file contain only zeroes so we can skip them and
# save time. This function returns the indexes of the clusters that
# are known to contain data. Those are the ones that we need to read.
def clusters_with_data(fd, cluster_size):
data_to = 0
while True:
try:
data_from = os.lseek(fd, data_to, os.SEEK_DATA)
data_to = align_up(os.lseek(fd, data_from, os.SEEK_HOLE), cluster_size)
for idx in range(data_from // cluster_size, data_to // cluster_size):
yield idx
except OSError as err:
if err.errno == errno.ENXIO: # End of file reached
break
raise err
# write_qcow2_content() expects a raw input file. If we have a different
# format we can use qemu-storage-daemon to make it appear as raw.
@contextmanager
def get_input_as_raw_file(input_file, input_format):
if input_format == "raw":
yield input_file
return
try:
temp_dir = tempfile.mkdtemp()
pid_file = os.path.join(temp_dir, "pid")
raw_file = os.path.join(temp_dir, "raw")
open(raw_file, "wb").close()
ret = subprocess.run(
[
QEMU_STORAGE_DAEMON,
"--daemonize",
"--pidfile", pid_file,
"--blockdev", f"driver=file,node-name=file0,driver=file,filename={input_file},read-only=on",
"--blockdev", f"driver={input_format},node-name=disk0,file=file0,read-only=on",
"--export", f"type=fuse,id=export0,node-name=disk0,mountpoint={raw_file},writable=off",
],
capture_output=True,
)
if ret.returncode != 0:
sys.exit("[Error] Could not start the qemu-storage-daemon:\n" +
ret.stderr.decode().rstrip('\n'))
yield raw_file
finally:
# Kill the storage daemon on exit
# and remove all temporary files
if os.path.exists(pid_file):
with open(pid_file, "r") as f:
pid = int(f.readline())
os.kill(pid, signal.SIGTERM)
while os.path.exists(pid_file):
time.sleep(0.1)
os.unlink(raw_file)
os.rmdir(temp_dir)
def write_features(cluster, offset, data_file_name):
if data_file_name is not None:
encoded_name = data_file_name.encode("utf-8")
padded_name_len = align_up(len(encoded_name), 8)
struct.pack_into(f">II{padded_name_len}s", cluster, offset,
QCOW2_DATA_FILE_NAME_STRING,
len(encoded_name),
encoded_name)
offset += 8 + padded_name_len
qcow2_features = [
# Incompatible
(0, 0, "dirty bit"),
(0, 1, "corrupt bit"),
(0, 2, "external data file"),
(0, 3, "compression type"),
(0, 4, "extended L2 entries"),
# Compatible
(1, 0, "lazy refcounts"),
# Autoclear
(2, 0, "bitmaps"),
(2, 1, "raw external data"),
]
struct.pack_into(">I", cluster, offset, QCOW2_FEATURE_NAME_TABLE)
struct.pack_into(">I", cluster, offset + 4, len(qcow2_features) * 48)
offset += 8
for feature_type, feature_bit, feature_name in qcow2_features:
struct.pack_into(">BB46s", cluster, offset,
feature_type, feature_bit, feature_name.encode("ascii"))
offset += 48
def write_qcow2_content(input_file, cluster_size, refcount_bits, data_file_name, data_file_raw):
# Some basic values
l1_entries_per_table = cluster_size // 8
l2_entries_per_table = cluster_size // 8
refcounts_per_table = cluster_size // 8
refcounts_per_block = cluster_size * 8 // refcount_bits
# Virtual disk size, number of data clusters and L1 entries
disk_size = align_up(os.path.getsize(input_file), 512)
total_data_clusters = math.ceil(disk_size / cluster_size)
l1_entries = math.ceil(total_data_clusters / l2_entries_per_table)
allocated_l1_tables = math.ceil(l1_entries / l1_entries_per_table)
# Max L1 table size is 32 MB (QCOW_MAX_L1_SIZE in block/qcow2.h)
if (l1_entries * 8) > (32 * 1024 * 1024):
sys.exit("[Error] The image size is too large. Try using a larger cluster size.")
# Two bitmaps indicating which L1 and L2 entries are set
l1_bitmap = bytearray(allocated_l1_tables * l1_entries_per_table // 8)
l2_bitmap = bytearray(l1_entries * l2_entries_per_table // 8)
allocated_l2_tables = 0
allocated_data_clusters = 0
if data_file_raw:
# If data_file_raw is set then all clusters are allocated and
# we don't need to read the input file at all.
allocated_l2_tables = l1_entries
for idx in range(l1_entries):
bitmap_set(l1_bitmap, idx)
for idx in range(total_data_clusters):
bitmap_set(l2_bitmap, idx)
else:
# Open the input file for reading
fd = os.open(input_file, os.O_RDONLY)
zero_cluster = bytes(cluster_size)
# Read all the clusters that contain data
for idx in clusters_with_data(fd, cluster_size):
cluster = os.pread(fd, cluster_size, cluster_size * idx)
# If the last cluster is smaller than cluster_size pad it with zeroes
if len(cluster) < cluster_size:
cluster += bytes(cluster_size - len(cluster))
# If a cluster has non-zero data then it must be allocated
# in the output file and its L2 entry must be set
if cluster != zero_cluster:
bitmap_set(l2_bitmap, idx)
allocated_data_clusters += 1
# Allocated data clusters also need their corresponding L1 entry and L2 table
l1_idx = math.floor(idx / l2_entries_per_table)
if not bitmap_is_set(l1_bitmap, l1_idx):
bitmap_set(l1_bitmap, l1_idx)
allocated_l2_tables += 1
# Total amount of allocated clusters excluding the refcount blocks and table
total_allocated_clusters = 1 + allocated_l1_tables + allocated_l2_tables
if data_file_name is None:
total_allocated_clusters += allocated_data_clusters
# Clusters allocated for the refcount blocks and table
allocated_refcount_blocks = math.ceil(total_allocated_clusters / refcounts_per_block)
allocated_refcount_tables = math.ceil(allocated_refcount_blocks / refcounts_per_table)
# Now we have a problem because allocated_refcount_blocks and allocated_refcount_tables...
# (a) increase total_allocated_clusters, and
# (b) need to be recalculated when total_allocated_clusters is increased
# So we need to repeat the calculation as long as the numbers change
while True:
new_total_allocated_clusters = total_allocated_clusters + allocated_refcount_tables + allocated_refcount_blocks
new_allocated_refcount_blocks = math.ceil(new_total_allocated_clusters / refcounts_per_block)
if new_allocated_refcount_blocks > allocated_refcount_blocks:
allocated_refcount_blocks = new_allocated_refcount_blocks
allocated_refcount_tables = math.ceil(allocated_refcount_blocks / refcounts_per_table)
else:
break
# Now that we have the final numbers we can update total_allocated_clusters
total_allocated_clusters += allocated_refcount_tables + allocated_refcount_blocks
# At this point we have the exact number of clusters that the output
# image is going to use so we can calculate all the offsets.
current_cluster_idx = 1
refcount_table_offset = current_cluster_idx * cluster_size
current_cluster_idx += allocated_refcount_tables
refcount_block_offset = current_cluster_idx * cluster_size
current_cluster_idx += allocated_refcount_blocks
l1_table_offset = current_cluster_idx * cluster_size
current_cluster_idx += allocated_l1_tables
l2_table_offset = current_cluster_idx * cluster_size
current_cluster_idx += allocated_l2_tables
data_clusters_offset = current_cluster_idx * cluster_size
# Calculate some values used in the qcow2 header
if allocated_l1_tables == 0:
l1_table_offset = 0
hdr_cluster_bits = int(math.log2(cluster_size))
hdr_refcount_bits = int(math.log2(refcount_bits))
hdr_length = QCOW2_V3_HEADER_LENGTH
hdr_incompat_features = 0
if data_file_name is not None:
hdr_incompat_features |= 1 << QCOW2_INCOMPAT_DATA_FILE_BIT
hdr_autoclear_features = 0
if data_file_raw:
hdr_autoclear_features |= 1 << QCOW2_AUTOCLEAR_DATA_FILE_RAW_BIT
### Write qcow2 header
cluster = bytearray(cluster_size)
struct.pack_into(">4sIQIIQIIQQIIQQQQII", cluster, 0,
b"QFI\xfb", # QCOW magic string
3, # version
0, # backing file offset
0, # backing file sizes
hdr_cluster_bits,
disk_size,
0, # encryption method
l1_entries,
l1_table_offset,
refcount_table_offset,
allocated_refcount_tables,
0, # number of snapshots
0, # snapshot table offset
hdr_incompat_features,
0, # compatible features
hdr_autoclear_features,
hdr_refcount_bits,
hdr_length,
)
write_features(cluster, hdr_length, data_file_name)
sys.stdout.buffer.write(cluster)
### Write refcount table
cur_offset = refcount_block_offset
remaining_refcount_table_entries = allocated_refcount_blocks # Each entry is a pointer to a refcount block
while remaining_refcount_table_entries > 0:
cluster = bytearray(cluster_size)
to_write = min(remaining_refcount_table_entries, refcounts_per_table)
remaining_refcount_table_entries -= to_write
for idx in range(to_write):
struct.pack_into(">Q", cluster, idx * 8, cur_offset)
cur_offset += cluster_size
sys.stdout.buffer.write(cluster)
### Write refcount blocks
remaining_refcount_block_entries = total_allocated_clusters # One entry for each allocated cluster
for tbl in range(allocated_refcount_blocks):
cluster = bytearray(cluster_size)
to_write = min(remaining_refcount_block_entries, refcounts_per_block)
remaining_refcount_block_entries -= to_write
# All refcount entries contain the number 1. The only difference
# is their bit width, defined when the image is created.
for idx in range(to_write):
if refcount_bits == 64:
struct.pack_into(">Q", cluster, idx * 8, 1)
elif refcount_bits == 32:
struct.pack_into(">L", cluster, idx * 4, 1)
elif refcount_bits == 16:
struct.pack_into(">H", cluster, idx * 2, 1)
elif refcount_bits == 8:
cluster[idx] = 1
elif refcount_bits == 4:
cluster[idx // 2] |= 1 << ((idx % 2) * 4)
elif refcount_bits == 2:
cluster[idx // 4] |= 1 << ((idx % 4) * 2)
elif refcount_bits == 1:
cluster[idx // 8] |= 1 << (idx % 8)
sys.stdout.buffer.write(cluster)
### Write L1 table
cur_offset = l2_table_offset
for tbl in range(allocated_l1_tables):
cluster = bytearray(cluster_size)
for idx in range(l1_entries_per_table):
l1_idx = tbl * l1_entries_per_table + idx
if bitmap_is_set(l1_bitmap, l1_idx):
struct.pack_into(">Q", cluster, idx * 8, cur_offset | QCOW_OFLAG_COPIED)
cur_offset += cluster_size
sys.stdout.buffer.write(cluster)
### Write L2 tables
cur_offset = data_clusters_offset
for tbl in range(l1_entries):
# Skip the empty L2 tables. We can identify them because
# there is no L1 entry pointing at them.
if bitmap_is_set(l1_bitmap, tbl):
cluster = bytearray(cluster_size)
for idx in range(l2_entries_per_table):
l2_idx = tbl * l2_entries_per_table + idx
if bitmap_is_set(l2_bitmap, l2_idx):
if data_file_name is None:
struct.pack_into(">Q", cluster, idx * 8, cur_offset | QCOW_OFLAG_COPIED)
cur_offset += cluster_size
else:
struct.pack_into(">Q", cluster, idx * 8, (l2_idx * cluster_size) | QCOW_OFLAG_COPIED)
sys.stdout.buffer.write(cluster)
### Write data clusters
if data_file_name is None:
for idx in bitmap_iterator(l2_bitmap, total_data_clusters):
cluster = os.pread(fd, cluster_size, cluster_size * idx)
# If the last cluster is smaller than cluster_size pad it with zeroes
if len(cluster) < cluster_size:
cluster += bytes(cluster_size - len(cluster))
sys.stdout.buffer.write(cluster)
if not data_file_raw:
os.close(fd)
def main():
# Command-line arguments
parser = argparse.ArgumentParser(
description="This program converts a QEMU disk image to qcow2 "
"and writes it to the standard output"
)
parser.add_argument("input_file", help="name of the input file")
parser.add_argument(
"-f",
dest="input_format",
metavar="input_format",
help="format of the input file (default: raw)",
default="raw",
)
parser.add_argument(
"-c",
dest="cluster_size",
metavar="cluster_size",
help=f"qcow2 cluster size (default: {QCOW2_DEFAULT_CLUSTER_SIZE})",
default=QCOW2_DEFAULT_CLUSTER_SIZE,
type=int,
choices=[1 << x for x in range(9, 22)],
)
parser.add_argument(
"-r",
dest="refcount_bits",
metavar="refcount_bits",
help=f"width of the reference count entries (default: {QCOW2_DEFAULT_REFCOUNT_BITS})",
default=QCOW2_DEFAULT_REFCOUNT_BITS,
type=int,
choices=[1 << x for x in range(7)],
)
parser.add_argument(
"-d",
dest="data_file",
help="create an image with input_file as an external data file",
action="store_true",
)
parser.add_argument(
"-R",
dest="data_file_raw",
help="enable data_file_raw on the generated image (implies -d)",
action="store_true",
)
args = parser.parse_args()
if args.data_file_raw:
args.data_file = True
if not os.path.isfile(args.input_file):
sys.exit(f"[Error] {args.input_file} does not exist or is not a regular file.")
if args.data_file and args.input_format != "raw":
sys.exit("[Error] External data files can only be used with raw input images")
# A 512 byte header is too small for the data file name extension
if args.data_file and args.cluster_size == 512:
sys.exit("[Error] External data files require a larger cluster size")
if sys.stdout.isatty():
sys.exit("[Error] Refusing to write to a tty. Try redirecting stdout.")
if args.data_file:
data_file_name = args.input_file
else:
data_file_name = None
with get_input_as_raw_file(args.input_file, args.input_format) as raw_file:
write_qcow2_content(
raw_file,
args.cluster_size,
args.refcount_bits,
data_file_name,
args.data_file_raw,
)
if __name__ == "__main__":
main()
+50 -28
View File
@@ -144,35 +144,35 @@ loongarch64_magic='\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x
loongarch64_mask='\xff\xff\xff\xff\xff\xff\xff\xfc\x00\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'
loongarch64_family=loongarch
qemu_get_family() {
cpu=${HOST_ARCH:-$(uname -m)}
# Converts the name of a host CPU architecture to the corresponding QEMU
# target.
#
# FIXME: This can probably be simplified a lot by dropping most entries.
# Remember that the script is only used on Linux, so we only need to
# handle the strings Linux uses to report the host CPU architecture.
qemu_normalize() {
cpu="$1"
case "$cpu" in
amd64|i386|i486|i586|i686|i86pc|BePC|x86_64)
i[3-6]86)
echo "i386"
;;
mips*)
echo "mips"
amd64)
echo "x86_64"
;;
"Power Macintosh"|ppc64|powerpc|ppc)
powerpc)
echo "ppc"
;;
ppc64el|ppc64le)
echo "ppcle"
ppc64el)
echo "ppc64le"
;;
arm|armel|armhf|arm64|armv[4-9]*l|aarch64)
armel|armhf|armv[4-9]*l)
echo "arm"
;;
armeb|armv[4-9]*b|aarch64_be)
armv[4-9]*b)
echo "armeb"
;;
sparc*)
echo "sparc"
;;
riscv*)
echo "riscv"
;;
loongarch*)
echo "loongarch"
arm64)
echo "aarch64"
;;
*)
echo "$cpu"
@@ -205,6 +205,9 @@ Usage: qemu-binfmt-conf.sh [--qemu-path PATH][--debian][--systemd CPU]
--persistent: if yes, the interpreter is loaded when binfmt is
configured and remains in memory. All future uses
are cloned from the open file.
--ignore-family: if yes, it is assumed that the host CPU (e.g. riscv64)
can't natively run programs targeting a CPU that is
part of the same family (e.g. riscv32).
--preserve-argv0 preserve argv[0]
To import templates with update-binfmts, use :
@@ -309,7 +312,13 @@ EOF
qemu_set_binfmts() {
# probe cpu type
host_family=$(qemu_get_family)
host_cpu=$(qemu_normalize ${HOST_ARCH:-$(uname -m)})
host_family=$(eval echo \$${host_cpu}_family)
if [ "$host_family" = "" ] ; then
echo "INTERNAL ERROR: unknown host cpu $host_cpu" 1>&2
exit 1
fi
# register the interpreter for each cpu except for the native one
@@ -318,20 +327,28 @@ qemu_set_binfmts() {
mask=$(eval echo \$${cpu}_mask)
family=$(eval echo \$${cpu}_family)
target="$cpu"
if [ "$cpu" = "i486" ] ; then
target="i386"
fi
qemu="$QEMU_PATH/qemu-$target$QEMU_SUFFIX"
if [ "$magic" = "" ] || [ "$mask" = "" ] || [ "$family" = "" ] ; then
echo "INTERNAL ERROR: unknown cpu $cpu" 1>&2
continue
fi
qemu="$QEMU_PATH/qemu-$cpu"
if [ "$cpu" = "i486" ] ; then
qemu="$QEMU_PATH/qemu-i386"
if [ "$host_family" = "$family" ] ; then
# When --ignore-family is used, we have to generate rules even
# for targets that are in the same family as the host CPU. The
# only exception is of course when the CPU types exactly match
if [ "$target" = "$host_cpu" ] || [ "$IGNORE_FAMILY" = "no" ] ; then
continue
fi
fi
qemu="$qemu$QEMU_SUFFIX"
if [ "$host_family" != "$family" ] ; then
$BINFMT_SET
fi
$BINFMT_SET
done
}
@@ -346,10 +363,11 @@ CREDENTIAL=no
PERSISTENT=no
PRESERVE_ARG0=no
QEMU_SUFFIX=""
IGNORE_FAMILY=no
_longopts="debian,systemd:,qemu-path:,qemu-suffix:,exportdir:,help,credential:,\
persistent:,preserve-argv0:"
options=$(getopt -o ds:Q:S:e:hc:p:g:F: -l ${_longopts} -- "$@")
persistent:,preserve-argv0:,ignore-family:"
options=$(getopt -o ds:Q:S:e:hc:p:g:F:i: -l ${_longopts} -- "$@")
eval set -- "$options"
while true ; do
@@ -409,6 +427,10 @@ while true ; do
shift
PRESERVE_ARG0="$1"
;;
-i|--ignore-family)
shift
IGNORE_FAMILY="$1"
;;
*)
break
;;
+2
View File
@@ -45,3 +45,5 @@ coroutine.CoroutineBt()
# Default to silently passing through SIGUSR1, because QEMU sends it
# to itself a lot.
gdb.execute('handle SIGUSR1 pass noprint nostop')
# Always print full stack for python errors, easier to debug and report issues
gdb.execute('set python print-stack full')
+39 -10
View File
@@ -1,11 +1,12 @@
#!/bin/sh
# This script is executed when a guest agent receives fsfreeze-freeze and
# fsfreeze-thaw command, if it is specified in --fsfreeze-hook (-F)
# option of qemu-ga or placed in default path (/etc/qemu/fsfreeze-hook).
# When the agent receives fsfreeze-freeze request, this script is issued with
# "freeze" argument before the filesystem is frozen. And for fsfreeze-thaw
# request, it is issued with "thaw" argument after filesystem is thawed.
# This script is executed when the guest agent receives fsfreeze-freeze and
# fsfreeze-thaw commands, provided that the --fsfreeze-hook (-F) option of
# qemu-ga is specified and the script is placed in /etc/qemu/fsfreeze-hook or in
# the path specified together with -F. When the agent receives fsfreeze-freeze
# requests, this script is called with "freeze" as its argument before the
# filesystem is frozen. And for fsfreeze-thaw requests, it is called with "thaw"
# as its argument after the filesystem is thawed.
LOGFILE=/var/log/qga-fsfreeze-hook.log
FSFREEZE_D=$(dirname -- "$0")/fsfreeze-hook.d
@@ -19,15 +20,43 @@ is_ignored_file() {
return 1
}
USE_SYSLOG=0
# if log file is not writable, fallback to syslog
[ ! -w "$LOGFILE" ] && USE_SYSLOG=1
# try to update log file and fallback to syslog if it fails
touch "$LOGFILE" &>/dev/null || USE_SYSLOG=1
# Ensure the log file is writable, fallback to syslog if not
log_message() {
local message="$1"
if [ "$USE_SYSLOG" -eq 0 ]; then
printf "%s: %s\n" "$(date)" "$message" >>"$LOGFILE"
else
logger -t qemu-ga-freeze-hook "$message"
fi
}
# Iterate executables in directory "fsfreeze-hook.d" with the specified args
[ ! -d "$FSFREEZE_D" ] && exit 0
for file in "$FSFREEZE_D"/* ; do
is_ignored_file "$file" && continue
[ -x "$file" ] || continue
printf "$(date): execute $file $@\n" >>$LOGFILE
"$file" "$@" >>$LOGFILE 2>&1
STATUS=$?
printf "$(date): $file finished with status=$STATUS\n" >>$LOGFILE
log_message "Executing $file $@"
if [ "$USE_SYSLOG" -eq 0 ]; then
"$file" "$@" >>"$LOGFILE" 2>&1
STATUS=$?
else
"$file" "$@" 2>&1 | logger -t qemu-ga-freeze-hook
STATUS=${PIPESTATUS[0]}
fi
if [ $STATUS -ne 0 ]; then
log_message "Error: $file finished with status=$STATUS"
else
log_message "$file finished successfully"
fi
done
exit 0
-1
View File
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Extract QEMU Plugin API symbols from a header file
#
+4 -2
View File
@@ -56,6 +56,7 @@ def tapset_dir(binary):
def cmd_run(args):
stap = which("stap")
prefix = probe_prefix(args.binary)
tapsets = tapset_dir(args.binary)
@@ -76,7 +77,7 @@ def cmd_run(args):
# We request an 8MB buffer, since the stap default 1MB buffer
# can be easily overflowed by frequently firing QEMU traces
stapargs = ["stap", "-s", "8", "-I", tapsets ]
stapargs = [stap, "-s", "8", "-I", tapsets ]
if args.pid is not None:
stapargs.extend(["-x", args.pid])
stapargs.extend(["-e", script])
@@ -84,6 +85,7 @@ def cmd_run(args):
def cmd_list(args):
stap = which("stap")
tapsets = tapset_dir(args.binary)
if args.verbose:
@@ -96,7 +98,7 @@ def cmd_list(args):
if verbose:
print("Listing probes with name '%s'" % script)
proc = subprocess.Popen(["stap", "-I", tapsets, "-l", script],
proc = subprocess.Popen([stap, "-I", tapsets, "-l", script],
stdout=subprocess.PIPE,
universal_newlines=True)
out, err = proc.communicate()
+75 -27
View File
@@ -13,28 +13,9 @@ import gdb
VOID_PTR = gdb.lookup_type('void').pointer()
def get_fs_base():
'''Fetch %fs base value using arch_prctl(ARCH_GET_FS). This is
pthread_self().'''
# %rsp - 120 is scratch space according to the SystemV ABI
old = gdb.parse_and_eval('*(uint64_t*)($rsp - 120)')
gdb.execute('call (int)arch_prctl(0x1003, $rsp - 120)', False, True)
fs_base = gdb.parse_and_eval('*(uint64_t*)($rsp - 120)')
gdb.execute('set *(uint64_t*)($rsp - 120) = %s' % old, False, True)
return fs_base
def pthread_self():
'''Fetch pthread_self() from the glibc start_thread function.'''
f = gdb.newest_frame()
while f.name() != 'start_thread':
f = f.older()
if f is None:
return get_fs_base()
try:
return f.read_var("arg")
except ValueError:
return get_fs_base()
'''Fetch the base address of TLS.'''
return gdb.parse_and_eval("$fs_base")
def get_glibc_pointer_guard():
'''Fetch glibc pointer guard value'''
@@ -65,9 +46,60 @@ def get_jmpbuf_regs(jmpbuf):
'r15': jmpbuf[JB_R15],
'rip': glibc_ptr_demangle(jmpbuf[JB_PC], pointer_guard) }
def bt_jmpbuf(jmpbuf):
'''Backtrace a jmpbuf'''
regs = get_jmpbuf_regs(jmpbuf)
def symbol_lookup(addr):
# Example: "__clone3 + 44 in section .text of /lib64/libc.so.6"
result = gdb.execute(f"info symbol {hex(addr)}", to_string=True).strip()
try:
if "+" in result:
(func, result) = result.split(" + ")
(offset, result) = result.split(" in ")
else:
offset = "0"
(func, result) = result.split(" in ")
func_str = f"{func}<+{offset}> ()"
except:
return f"??? ({result})"
# Example: Line 321 of "../util/coroutine-ucontext.c" starts at address
# 0x55cf3894d993 <qemu_coroutine_switch+99> and ends at 0x55cf3894d9ab
# <qemu_coroutine_switch+123>.
result = gdb.execute(f"info line *{hex(addr)}", to_string=True).strip()
if not result.startswith("Line "):
return func_str
result = result[5:]
try:
result = result.split(" starts ")[0]
(line, path) = result.split(" of ")
path = path.replace("\"", "")
except:
return func_str
return f"{func_str} at {path}:{line}"
def dump_backtrace(regs):
'''
Backtrace dump with raw registers, mimic GDB command 'bt'.
'''
# Here only rbp and rip that matter..
rbp = regs['rbp']
rip = regs['rip']
i = 0
while rbp:
# For all return addresses on stack, we want to look up symbol/line
# on the CALL command, because the return address is the next
# instruction instead of the CALL. Here -1 would work for any
# sized CALL instruction.
print(f"#{i} {hex(rip)} in {symbol_lookup(rip if i == 0 else rip-1)}")
rip = gdb.parse_and_eval(f"*(uint64_t *)(uint64_t)({hex(rbp)} + 8)")
rbp = gdb.parse_and_eval(f"*(uint64_t *)(uint64_t)({hex(rbp)})")
i += 1
def dump_backtrace_live(regs):
'''
Backtrace dump with gdb's 'bt' command, only usable in a live session.
'''
old = dict()
# remember current stack frame and select the topmost
@@ -88,6 +120,17 @@ def bt_jmpbuf(jmpbuf):
selected_frame.select()
def bt_jmpbuf(jmpbuf):
'''Backtrace a jmpbuf'''
regs = get_jmpbuf_regs(jmpbuf)
try:
# This reuses gdb's "bt" command, which can be slightly prettier
# but only works with live sessions.
dump_backtrace_live(regs)
except:
# If above doesn't work, fallback to poor man's unwind
dump_backtrace(regs)
def co_cast(co):
return co.cast(gdb.lookup_type('CoroutineUContext').pointer())
@@ -120,10 +163,15 @@ class CoroutineBt(gdb.Command):
gdb.execute("bt")
if gdb.parse_and_eval("qemu_in_coroutine()") == False:
return
try:
# This only works with a live session
co_ptr = gdb.parse_and_eval("qemu_coroutine_self()")
except:
# Fallback to use hard-coded ucontext vars if it's coredump
co_ptr = gdb.parse_and_eval("co_tls_current")
co_ptr = gdb.parse_and_eval("qemu_coroutine_self()")
if co_ptr == False:
return
while True:
co = co_cast(co_ptr)
-2
View File
@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
#
# GDB debugging support, TCG status
#
# Copyright 2016 Linaro Ltd
-1
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# GDB debugging support
#
# Copyright 2017 Linaro Ltd
+703
View File
@@ -0,0 +1,703 @@
#!/usr/bin/env python3
#
# pylint: disable=C0103,E0213,E1135,E1136,E1137,R0902,R0903,R0912,R0913,R0917
# SPDX-License-Identifier: GPL-2.0-or-later
#
# Copyright (C) 2024-2025 Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
"""
Helper classes to be used by ghes_inject command classes.
"""
import json
import sys
from datetime import datetime
from os import path as os_path
try:
qemu_dir = os_path.abspath(os_path.dirname(os_path.dirname(__file__)))
sys.path.append(os_path.join(qemu_dir, 'python'))
from qemu.qmp.legacy import QEMUMonitorProtocol
except ModuleNotFoundError as exc:
print(f"Module '{exc.name}' not found.")
print("Try export PYTHONPATH=top-qemu-dir/python or run from top-qemu-dir")
sys.exit(1)
from base64 import b64encode
class util:
"""
Ancillary functions to deal with bitmaps, parse arguments,
generate GUID and encode data on a bytearray buffer.
"""
#
# Helper routines to handle multiple choice arguments
#
def get_choice(name, value, choices, suffixes=None, bitmask=True):
"""Produce a list from multiple choice argument"""
new_values = 0
if not value:
return new_values
for val in value.split(","):
val = val.lower()
if suffixes:
for suffix in suffixes:
val = val.removesuffix(suffix)
if val not in choices.keys():
if suffixes:
for suffix in suffixes:
if val + suffix in choices.keys():
val += suffix
break
if val not in choices.keys():
sys.exit(f"Error on '{name}': choice '{val}' is invalid.")
val = choices[val]
if bitmask:
new_values |= val
else:
if new_values:
sys.exit(f"Error on '{name}': only one value is accepted.")
new_values = val
return new_values
def get_array(name, values, max_val=None):
"""Add numbered hashes from integer lists into an array"""
array = []
for value in values:
for val in value.split(","):
try:
val = int(val, 0)
except ValueError:
sys.exit(f"Error on '{name}': {val} is not an integer")
if val < 0:
sys.exit(f"Error on '{name}': {val} is not unsigned")
if max_val and val > max_val:
sys.exit(f"Error on '{name}': {val} is too little")
array.append(val)
return array
def get_mult_array(mult, name, values, allow_zero=False, max_val=None):
"""Add numbered hashes from integer lists"""
if not allow_zero:
if not values:
return
else:
if values is None:
return
if not values:
i = 0
if i not in mult:
mult[i] = {}
mult[i][name] = []
return
i = 0
for value in values:
for val in value.split(","):
try:
val = int(val, 0)
except ValueError:
sys.exit(f"Error on '{name}': {val} is not an integer")
if val < 0:
sys.exit(f"Error on '{name}': {val} is not unsigned")
if max_val and val > max_val:
sys.exit(f"Error on '{name}': {val} is too little")
if i not in mult:
mult[i] = {}
if name not in mult[i]:
mult[i][name] = []
mult[i][name].append(val)
i += 1
def get_mult_choices(mult, name, values, choices,
suffixes=None, allow_zero=False):
"""Add numbered hashes from multiple choice arguments"""
if not allow_zero:
if not values:
return
else:
if values is None:
return
i = 0
for val in values:
new_values = util.get_choice(name, val, choices, suffixes)
if i not in mult:
mult[i] = {}
mult[i][name] = new_values
i += 1
def get_mult_int(mult, name, values, allow_zero=False):
"""Add numbered hashes from integer arguments"""
if not allow_zero:
if not values:
return
else:
if values is None:
return
i = 0
for val in values:
try:
val = int(val, 0)
except ValueError:
sys.exit(f"Error on '{name}': {val} is not an integer")
if val < 0:
sys.exit(f"Error on '{name}': {val} is not unsigned")
if i not in mult:
mult[i] = {}
mult[i][name] = val
i += 1
#
# Data encode helper functions
#
def bit(b):
"""Simple macro to define a bit on a bitmask"""
return 1 << b
def data_add(data, value, num_bytes):
"""Adds bytes from value inside a bitarray"""
data.extend(value.to_bytes(num_bytes, byteorder="little")) # pylint: disable=E1101
def dump_bytearray(name, data):
"""Does an hexdump of a byte array, grouping in bytes"""
print(f"{name} ({len(data)} bytes):")
for ln_start in range(0, len(data), 16):
ln_end = min(ln_start + 16, len(data))
print(f" {ln_start:08x} ", end="")
for i in range(ln_start, ln_end):
print(f"{data[i]:02x} ", end="")
for i in range(ln_end, ln_start + 16):
print(" ", end="")
print(" ", end="")
for i in range(ln_start, ln_end):
if data[i] >= 32 and data[i] < 127:
print(chr(data[i]), end="")
else:
print(".", end="")
print()
print()
def time(string):
"""Handle BCD timestamps used on Generic Error Data Block"""
time = None
# Formats to be used when parsing time stamps
formats = [
"%Y-%m-%d %H:%M:%S",
]
if string == "now":
time = datetime.now()
if time is None:
for fmt in formats:
try:
time = datetime.strptime(string, fmt)
break
except ValueError:
pass
if time is None:
raise ValueError("Invalid time format")
return time
class guid:
"""
Simple class to handle GUID fields.
"""
def __init__(self, time_low, time_mid, time_high, nodes):
"""Initialize a GUID value"""
assert len(nodes) == 8
self.time_low = time_low
self.time_mid = time_mid
self.time_high = time_high
self.nodes = nodes
@classmethod
def UUID(cls, guid_str):
"""Initialize a GUID using a string on its standard format"""
if len(guid_str) != 36:
print("Size not 36")
raise ValueError('Invalid GUID size')
# It is easier to parse without separators. So, drop them
guid_str = guid_str.replace('-', '')
if len(guid_str) != 32:
print("Size not 32", guid_str, len(guid_str))
raise ValueError('Invalid GUID hex size')
time_low = 0
time_mid = 0
time_high = 0
nodes = []
for i in reversed(range(16, 32, 2)):
h = guid_str[i:i + 2]
value = int(h, 16)
nodes.insert(0, value)
time_high = int(guid_str[12:16], 16)
time_mid = int(guid_str[8:12], 16)
time_low = int(guid_str[0:8], 16)
return cls(time_low, time_mid, time_high, nodes)
def __str__(self):
"""Output a GUID value on its default string representation"""
clock = self.nodes[0] << 8 | self.nodes[1]
node = 0
for i in range(2, len(self.nodes)):
node = node << 8 | self.nodes[i]
s = f"{self.time_low:08x}-{self.time_mid:04x}-"
s += f"{self.time_high:04x}-{clock:04x}-{node:012x}"
return s
def to_bytes(self):
"""Output a GUID value in bytes"""
data = bytearray()
util.data_add(data, self.time_low, 4)
util.data_add(data, self.time_mid, 2)
util.data_add(data, self.time_high, 2)
data.extend(bytearray(self.nodes))
return data
class qmp:
"""
Opens a connection and send/receive QMP commands.
"""
def send_cmd(self, command, args=None, may_open=False, return_error=True):
"""Send a command to QMP, optinally opening a connection"""
if may_open:
self._connect()
elif not self.connected:
return False
msg = { 'execute': command }
if args:
msg['arguments'] = args
try:
obj = self.qmp_monitor.cmd_obj(msg)
# Can we use some other exception class here?
except Exception as e: # pylint: disable=W0718
print(f"Command: {command}")
print(f"Failed to inject error: {e}.")
return None
if "return" in obj:
if isinstance(obj.get("return"), dict):
if obj["return"]:
return obj["return"]
return "OK"
return obj["return"]
if isinstance(obj.get("error"), dict):
error = obj["error"]
if return_error:
print(f"Command: {msg}")
print(f'{error["class"]}: {error["desc"]}')
else:
print(json.dumps(obj))
return None
def _close(self):
"""Shutdown and close the socket, if opened"""
if not self.connected:
return
self.qmp_monitor.close()
self.connected = False
def _connect(self):
"""Connect to a QMP TCP/IP port, if not connected yet"""
if self.connected:
return True
try:
self.qmp_monitor.connect(negotiate=True)
except ConnectionError:
sys.exit(f"Can't connect to QMP host {self.host}:{self.port}")
self.connected = True
return True
BLOCK_STATUS_BITS = {
"uncorrectable": util.bit(0),
"correctable": util.bit(1),
"multi-uncorrectable": util.bit(2),
"multi-correctable": util.bit(3),
}
ERROR_SEVERITY = {
"recoverable": 0,
"fatal": 1,
"corrected": 2,
"none": 3,
}
VALIDATION_BITS = {
"fru-id": util.bit(0),
"fru-text": util.bit(1),
"timestamp": util.bit(2),
}
GEDB_FLAGS_BITS = {
"recovered": util.bit(0),
"prev-error": util.bit(1),
"simulated": util.bit(2),
}
GENERIC_DATA_SIZE = 72
def argparse(parser):
"""Prepare a parser group to query generic error data"""
block_status_bits = ",".join(qmp.BLOCK_STATUS_BITS.keys())
error_severity_enum = ",".join(qmp.ERROR_SEVERITY.keys())
validation_bits = ",".join(qmp.VALIDATION_BITS.keys())
gedb_flags_bits = ",".join(qmp.GEDB_FLAGS_BITS.keys())
g_gen = parser.add_argument_group("Generic Error Data") # pylint: disable=E1101
g_gen.add_argument("--block-status",
help=f"block status bits: {block_status_bits}")
g_gen.add_argument("--raw-data", nargs="+",
help="Raw data inside the Error Status Block")
g_gen.add_argument("--error-severity", "--severity",
help=f"error severity: {error_severity_enum}")
g_gen.add_argument("--gen-err-valid-bits",
"--generic-error-validation-bits",
help=f"validation bits: {validation_bits}")
g_gen.add_argument("--fru-id", type=guid.UUID,
help="GUID representing a physical device")
g_gen.add_argument("--fru-text",
help="ASCII string identifying the FRU hardware")
g_gen.add_argument("--timestamp", type=util.time,
help="Time when the error info was collected")
g_gen.add_argument("--precise", "--precise-timestamp",
action='store_true',
help="Marks the timestamp as precise if --timestamp is used")
g_gen.add_argument("--gedb-flags",
help=f"General Error Data Block flags: {gedb_flags_bits}")
def set_args(self, args):
"""Set the arguments optionally defined via self.argparse()"""
if args.block_status:
self.block_status = util.get_choice(name="block-status",
value=args.block_status,
choices=self.BLOCK_STATUS_BITS,
bitmask=False)
if args.raw_data:
self.raw_data = util.get_array("raw-data", args.raw_data,
max_val=255)
print(self.raw_data)
if args.error_severity:
self.error_severity = util.get_choice(name="error-severity",
value=args.error_severity,
choices=self.ERROR_SEVERITY,
bitmask=False)
if args.fru_id:
self.fru_id = args.fru_id.to_bytes()
if not args.gen_err_valid_bits:
self.validation_bits |= self.VALIDATION_BITS["fru-id"]
if args.fru_text:
text = bytearray(args.fru_text.encode('ascii'))
if len(text) > 20:
sys.exit("FRU text is too big to fit")
self.fru_text = text
if not args.gen_err_valid_bits:
self.validation_bits |= self.VALIDATION_BITS["fru-text"]
if args.timestamp:
time = args.timestamp
century = int(time.year / 100)
bcd = bytearray()
util.data_add(bcd, (time.second // 10) << 4 | (time.second % 10), 1)
util.data_add(bcd, (time.minute // 10) << 4 | (time.minute % 10), 1)
util.data_add(bcd, (time.hour // 10) << 4 | (time.hour % 10), 1)
if args.precise:
util.data_add(bcd, 1, 1)
else:
util.data_add(bcd, 0, 1)
util.data_add(bcd, (time.day // 10) << 4 | (time.day % 10), 1)
util.data_add(bcd, (time.month // 10) << 4 | (time.month % 10), 1)
util.data_add(bcd,
((time.year % 100) // 10) << 4 | (time.year % 10), 1)
util.data_add(bcd, ((century % 100) // 10) << 4 | (century % 10), 1)
self.timestamp = bcd
if not args.gen_err_valid_bits:
self.validation_bits |= self.VALIDATION_BITS["timestamp"]
if args.gen_err_valid_bits:
self.validation_bits = util.get_choice(name="validation",
value=args.gen_err_valid_bits,
choices=self.VALIDATION_BITS)
def __init__(self, host, port, debug=False):
"""Initialize variables used by the QMP send logic"""
self.connected = False
self.host = host
self.port = port
self.debug = debug
# ACPI 6.1: 18.3.2.7.1 Generic Error Data: Generic Error Status Block
self.block_status = self.BLOCK_STATUS_BITS["uncorrectable"]
self.raw_data = []
self.error_severity = self.ERROR_SEVERITY["recoverable"]
# ACPI 6.1: 18.3.2.7.1 Generic Error Data: Generic Error Data Entry
self.validation_bits = 0
self.flags = 0
self.fru_id = bytearray(16)
self.fru_text = bytearray(20)
self.timestamp = bytearray(8)
self.qmp_monitor = QEMUMonitorProtocol(address=(self.host, self.port))
#
# Socket QMP send command
#
def send_cper_raw(self, cper_data):
"""Send a raw CPER data to QEMU though QMP TCP socket"""
data = b64encode(bytes(cper_data)).decode('ascii')
cmd_arg = {
'cper': data
}
self._connect()
if self.send_cmd("inject-ghes-v2-error", cmd_arg):
print("Error injected.")
def send_cper(self, notif_type, payload):
"""Send commands to QEMU though QMP TCP socket"""
# Fill CPER record header
# NOTE: bits 4 to 13 of block status contain the number of
# data entries in the data section. This is currently unsupported.
cper_length = len(payload)
data_length = cper_length + len(self.raw_data) + self.GENERIC_DATA_SIZE
# Generic Error Data Entry
gede = bytearray()
gede.extend(notif_type.to_bytes())
util.data_add(gede, self.error_severity, 4)
util.data_add(gede, 0x300, 2)
util.data_add(gede, self.validation_bits, 1)
util.data_add(gede, self.flags, 1)
util.data_add(gede, cper_length, 4)
gede.extend(self.fru_id)
gede.extend(self.fru_text)
gede.extend(self.timestamp)
# Generic Error Status Block
gebs = bytearray()
if self.raw_data:
raw_data_offset = len(gebs)
else:
raw_data_offset = 0
util.data_add(gebs, self.block_status, 4)
util.data_add(gebs, raw_data_offset, 4)
util.data_add(gebs, len(self.raw_data), 4)
util.data_add(gebs, data_length, 4)
util.data_add(gebs, self.error_severity, 4)
cper_data = bytearray()
cper_data.extend(gebs)
cper_data.extend(gede)
cper_data.extend(bytearray(self.raw_data))
cper_data.extend(bytearray(payload))
if self.debug:
print(f"GUID: {notif_type}")
util.dump_bytearray("Generic Error Status Block", gebs)
util.dump_bytearray("Generic Error Data Entry", gede)
if self.raw_data:
util.dump_bytearray("Raw data", bytearray(self.raw_data))
util.dump_bytearray("Payload", payload)
self.send_cper_raw(cper_data)
def search_qom(self, path, prop, regex):
"""
Return a list of devices that match path array like:
/machine/unattached/device
/machine/peripheral-anon/device
...
"""
found = []
i = 0
while 1:
dev = f"{path}[{i}]"
args = {
'path': dev,
'property': prop
}
ret = self.send_cmd("qom-get", args, may_open=True,
return_error=False)
if not ret:
break
if isinstance(ret, str):
if regex.search(ret):
found.append(dev)
i += 1
if i > 10000:
print("Too many objects returned by qom-get!")
break
return found
class cper_guid:
"""
Contains CPER GUID, as per:
https://uefi.org/specs/UEFI/2.10/Apx_N_Common_Platform_Error_Record.html
"""
CPER_PROC_GENERIC = guid(0x9876CCAD, 0x47B4, 0x4bdb,
[0xB6, 0x5E, 0x16, 0xF1,
0x93, 0xC4, 0xF3, 0xDB])
CPER_PROC_X86 = guid(0xDC3EA0B0, 0xA144, 0x4797,
[0xB9, 0x5B, 0x53, 0xFA,
0x24, 0x2B, 0x6E, 0x1D])
CPER_PROC_ITANIUM = guid(0xe429faf1, 0x3cb7, 0x11d4,
[0xbc, 0xa7, 0x00, 0x80,
0xc7, 0x3c, 0x88, 0x81])
CPER_PROC_ARM = guid(0xE19E3D16, 0xBC11, 0x11E4,
[0x9C, 0xAA, 0xC2, 0x05,
0x1D, 0x5D, 0x46, 0xB0])
CPER_PLATFORM_MEM = guid(0xA5BC1114, 0x6F64, 0x4EDE,
[0xB8, 0x63, 0x3E, 0x83,
0xED, 0x7C, 0x83, 0xB1])
CPER_PLATFORM_MEM2 = guid(0x61EC04FC, 0x48E6, 0xD813,
[0x25, 0xC9, 0x8D, 0xAA,
0x44, 0x75, 0x0B, 0x12])
CPER_PCIE = guid(0xD995E954, 0xBBC1, 0x430F,
[0xAD, 0x91, 0xB4, 0x4D,
0xCB, 0x3C, 0x6F, 0x35])
CPER_PCI_BUS = guid(0xC5753963, 0x3B84, 0x4095,
[0xBF, 0x78, 0xED, 0xDA,
0xD3, 0xF9, 0xC9, 0xDD])
CPER_PCI_DEV = guid(0xEB5E4685, 0xCA66, 0x4769,
[0xB6, 0xA2, 0x26, 0x06,
0x8B, 0x00, 0x13, 0x26])
CPER_FW_ERROR = guid(0x81212A96, 0x09ED, 0x4996,
[0x94, 0x71, 0x8D, 0x72,
0x9C, 0x8E, 0x69, 0xED])
CPER_DMA_GENERIC = guid(0x5B51FEF7, 0xC79D, 0x4434,
[0x8F, 0x1B, 0xAA, 0x62,
0xDE, 0x3E, 0x2C, 0x64])
CPER_DMA_VT = guid(0x71761D37, 0x32B2, 0x45cd,
[0xA7, 0xD0, 0xB0, 0xFE,
0xDD, 0x93, 0xE8, 0xCF])
CPER_DMA_IOMMU = guid(0x036F84E1, 0x7F37, 0x428c,
[0xA7, 0x9E, 0x57, 0x5F,
0xDF, 0xAA, 0x84, 0xEC])
CPER_CCIX_PER = guid(0x91335EF6, 0xEBFB, 0x4478,
[0xA6, 0xA6, 0x88, 0xB7,
0x28, 0xCF, 0x75, 0xD7])
CPER_CXL_PROT_ERR = guid(0x80B9EFB4, 0x52B5, 0x4DE3,
[0xA7, 0x77, 0x68, 0x78,
0x4B, 0x77, 0x10, 0x48])
+5 -2
View File
@@ -13,8 +13,11 @@
# --in-place \
# --dir .
#
# SPDX-FileContributor: Philippe Mathieu-Daudé <philmd@linaro.org>
# SPDX-FileCopyrightText: 2023 Linaro Ltd.
# Copyright (c) 2023 Linaro Ltd.
#
# Authors:
# Philippe Mathieu-Daudé
#
# SPDX-License-Identifier: GPL-2.0-or-later
import re
+109
View File
@@ -0,0 +1,109 @@
#!/bin/bash
# Copied from blktests
get_ipv4_addr()
{
ip -4 -o addr show dev "$1" |
sed -n 's/.*[[:blank:]]inet[[:blank:]]*\([^[:blank:]/]*\).*/\1/p' |
head -1 | tr -d '\n'
}
get_ipv6_addr() {
ipv6=$(ip -6 -o addr show dev "$1" |
sed -n 's/.*[[:blank:]]inet6[[:blank:]]*\([^[:blank:]/]*\).*/\1/p' |
head -1 | tr -d '\n')
[ $? -eq 0 ] || return
if [[ "$ipv6" =~ ^fe80: ]]; then
echo -n "[$ipv6%$1]"
else
echo -n "[$ipv6]"
fi
}
# existing rdma interfaces
rdma_interfaces()
{
rdma link show | sed -nE 's/^link .* netdev ([^ ]+).*$/\1 /p' |
grep -Ev '^(lo|tun|tap)'
}
# existing valid ipv4 interfaces
ipv4_interfaces()
{
ip -o addr show | awk '/inet / {print $2}' | grep -Ev '^(lo|tun|tap)'
}
ipv6_interfaces()
{
ip -o addr show | awk '/inet6 / {print $2}' | grep -Ev '^(lo|tun|tap)'
}
rdma_rxe_detect()
{
family=$1
for r in $(rdma_interfaces)
do
"$family"_interfaces | grep -qw $r && get_"$family"_addr $r && return
done
return 1
}
rdma_rxe_setup()
{
family=$1
for i in $("$family"_interfaces)
do
if rdma_interfaces | grep -qw $i; then
echo "$family: Reuse the existing rdma/rxe ${i}_rxe" \
"for $i with $(get_"$family"_addr $i)"
return
fi
rdma link add "${i}_rxe" type rxe netdev "$i" && {
echo "$family: Setup new rdma/rxe ${i}_rxe" \
"for $i with $(get_"$family"_addr $i)"
return
}
done
echo "$family: Failed to setup any new rdma/rxe link" >&2
return 1
}
rdma_rxe_clean()
{
modprobe -r rdma_rxe
}
IP_FAMILY=${IP_FAMILY:-ipv4}
if [ "$IP_FAMILY" != "ipv6" ] && [ "$IP_FAMILY" != "ipv4" ]; then
echo "Unknown ip family '$IP_FAMILY', only ipv4 or ipv6 is supported." >&2
exit 1
fi
operation=${1:-detect}
command -v rdma >/dev/null || {
echo "Command 'rdma' is not available, please install it first." >&2
exit 1
}
if [ "$operation" == "setup" ] || [ "$operation" == "clean" ]; then
[ "$UID" == 0 ] || {
echo "Root privilege is required to setup/clean a rdma/rxe link" >&2
exit 1
}
if [ "$operation" == "setup" ]; then
rdma_rxe_setup ipv4
rdma_rxe_setup ipv6
else
rdma_rxe_clean
fi
elif [ "$operation" == "detect" ]; then
rdma_rxe_detect "$IP_FAMILY"
else
echo "Usage: $0 [setup | detect | clean]"
fi
-1
View File
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Dump the contents of a recorded execution stream
#
+43
View File
@@ -0,0 +1,43 @@
#!/usr/bin/env sh
#
# Copyright (C) 2025 Red Hat, Inc.
#
# Based on rust_to_clang_target() tests from rust-bindgen.
#
# SPDX-License-Identifier: GPL-2.0-or-later
scripts_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
. "$scripts_dir/rust-to-clang-target.sh"
test_case() {
input="$1"
expected="$2"
result=$(rust_to_clang_target "$input")
if [ "$result" = "$expected" ]; then
echo " OK: '$input' -> '$result'"
else
echo " FAILED: '$input'"
echo " Expected: '$expected'"
echo " Got: '$result'"
exit 1
fi
}
echo "Running tests..."
test_case "aarch64-apple-ios" "arm64-apple-ios"
test_case "riscv64gc-unknown-linux-gnu" "riscv64-unknown-linux-gnu"
test_case "riscv64imac-unknown-none-elf" "riscv64-unknown-none-elf"
test_case "riscv32imc-unknown-none-elf" "riscv32-unknown-none-elf"
test_case "riscv32imac-unknown-none-elf" "riscv32-unknown-none-elf"
test_case "riscv32imafc-unknown-none-elf" "riscv32-unknown-none-elf"
test_case "riscv32i-unknown-none-elf" "riscv32-unknown-none-elf"
test_case "riscv32imc-esp-espidf" "riscv32-esp-elf"
test_case "xtensa-esp32-espidf" "xtensa-esp32-elf"
test_case "aarch64-apple-ios-sim" "arm64-apple-ios-simulator"
test_case "aarch64-apple-tvos-sim" "arm64-apple-tvos-simulator"
test_case "aarch64-apple-watchos-sim" "arm64-apple-watchos-simulator"
echo ""
echo "All tests passed!"
+60
View File
@@ -0,0 +1,60 @@
# Copyright (C) 2025 Red Hat, Inc.
#
# Based on rust_to_clang_target() from rust-bindgen.
#
# SPDX-License-Identifier: GPL-2.0-or-later
rust_to_clang_target() {
rust_target="$1"
# Split the string by hyphens
triple_parts=""
old_IFS="$IFS"
IFS='-'
for part in $rust_target; do
triple_parts="$triple_parts $part"
done
IFS="$old_IFS"
set -- $triple_parts
# RISC-V
case "$1" in
riscv32*)
set -- "riscv32" "${2}" "${3}" "${4}"
;;
riscv64*)
set -- "riscv64" "${2}" "${3}" "${4}"
;;
esac
# Apple
if [ "$2" = "apple" ]; then
if [ "$1" = "aarch64" ]; then
set -- "arm64" "${2}" "${3}" "${4}"
fi
if [ "$4" = "sim" ]; then
set -- "${1}" "${2}" "${3}" "simulator"
fi
fi
# ESP-IDF
if [ "$3" = "espidf" ]; then
set -- "${1}" "${2}" "elf" "${4}"
fi
# Reassemble the string
new_triple=""
first=1
for part in "$@"; do
if [ -n "$part" ]; then
if [ "$first" -eq 1 ]; then
new_triple="$part"
first=0
else
new_triple="$new_triple-$part"
fi
fi
done
echo "$new_triple"
}
+1 -1
View File
@@ -4,7 +4,7 @@ set -eu
cat <<EOF
// @generated
// This file is autogenerated by scripts/rust_root_crate.sh
// This file is autogenerated by scripts/rust/rust_root_crate.sh
EOF
+164 -16
View File
@@ -25,31 +25,107 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import argparse
from dataclasses import dataclass
import logging
from pathlib import Path
from typing import Any, Iterable, List, Mapping, Optional, Set
from typing import List
try:
import tomllib
except ImportError:
import tomli as tomllib
STRICT_LINTS = {"unknown_lints", "warnings"}
def generate_cfg_flags(header: str) -> List[str]:
class CargoTOML:
tomldata: Mapping[Any, Any]
workspace_data: Mapping[Any, Any]
check_cfg: Set[str]
def __init__(self, path: Optional[str], workspace: Optional[str]):
if path is not None:
with open(path, 'rb') as f:
self.tomldata = tomllib.load(f)
else:
self.tomldata = {"lints": {"workspace": True}}
if workspace is not None:
with open(workspace, 'rb') as f:
self.workspace_data = tomllib.load(f)
if "workspace" not in self.workspace_data:
self.workspace_data["workspace"] = {}
self.check_cfg = set(self.find_check_cfg())
def find_check_cfg(self) -> Iterable[str]:
toml_lints = self.lints
rust_lints = toml_lints.get("rust", {})
cfg_lint = rust_lints.get("unexpected_cfgs", {})
return cfg_lint.get("check-cfg", [])
@property
def lints(self) -> Mapping[Any, Any]:
return self.get_table("lints", True)
def get_table(self, key: str, can_be_workspace: bool = False) -> Mapping[Any, Any]:
table = self.tomldata.get(key, {})
if can_be_workspace and table.get("workspace", False) is True:
table = self.workspace_data["workspace"].get(key, {})
return table
@dataclass
class LintFlag:
flags: List[str]
priority: int
def generate_lint_flags(cargo_toml: CargoTOML, strict_lints: bool) -> Iterable[str]:
"""Converts Cargo.toml lints to rustc -A/-D/-F/-W flags."""
toml_lints = cargo_toml.lints
lint_list = []
for k, v in toml_lints.items():
prefix = "" if k == "rust" else k + "::"
for lint, data in v.items():
level = data if isinstance(data, str) else data["level"]
priority = 0 if isinstance(data, str) else data.get("priority", 0)
if level == "deny":
flag = "-D"
elif level == "allow":
flag = "-A"
elif level == "warn":
flag = "-W"
elif level == "forbid":
flag = "-F"
else:
raise Exception(f"invalid level {level} for {prefix}{lint}")
if not (strict_lints and lint in STRICT_LINTS):
lint_list.append(LintFlag(flags=[flag, prefix + lint], priority=priority))
if strict_lints:
for lint in STRICT_LINTS:
lint_list.append(LintFlag(flags=["-D", lint], priority=1000000))
lint_list.sort(key=lambda x: x.priority)
for lint in lint_list:
yield from lint.flags
def generate_cfg_flags(header: str, cargo_toml: CargoTOML) -> Iterable[str]:
"""Converts defines from config[..].h headers to rustc --cfg flags."""
def cfg_name(name: str) -> str:
"""Filter function for C #defines"""
if (
name.startswith("CONFIG_")
or name.startswith("TARGET_")
or name.startswith("HAVE_")
):
return name
return ""
with open(header, encoding="utf-8") as cfg:
config = [l.split()[1:] for l in cfg if l.startswith("#define")]
cfg_list = []
for cfg in config:
name = cfg_name(cfg[0])
if not name:
name = cfg[0]
if f'cfg({name})' not in cargo_toml.check_cfg:
continue
if len(cfg) >= 2 and cfg[1] != "1":
continue
@@ -59,7 +135,6 @@ def generate_cfg_flags(header: str) -> List[str]:
def main() -> None:
# pylint: disable=missing-function-docstring
parser = argparse.ArgumentParser()
parser.add_argument("-v", "--verbose", action="store_true")
parser.add_argument(
@@ -71,12 +146,85 @@ def main() -> None:
required=False,
default=[],
)
parser.add_argument(
metavar="TOML_FILE",
action="store",
dest="cargo_toml",
help="path to Cargo.toml file",
nargs='?',
)
parser.add_argument(
"--workspace",
metavar="DIR",
action="store",
dest="workspace",
help="path to root of the workspace",
required=False,
default=None,
)
parser.add_argument(
"--features",
action="store_true",
dest="features",
help="generate --check-cfg arguments for features",
required=False,
default=None,
)
parser.add_argument(
"--lints",
action="store_true",
dest="lints",
help="generate arguments from [lints] table",
required=False,
default=None,
)
parser.add_argument(
"--rustc-version",
metavar="VERSION",
dest="rustc_version",
action="store",
help="version of rustc",
required=False,
default="1.0.0",
)
parser.add_argument(
"--strict-lints",
action="store_true",
dest="strict_lints",
help="apply stricter checks (for nightly Rust)",
default=False,
)
args = parser.parse_args()
if args.verbose:
logging.basicConfig(level=logging.DEBUG)
logging.debug("args: %s", args)
rustc_version = tuple((int(x) for x in args.rustc_version.split('.')[0:2]))
if args.workspace:
workspace_cargo_toml = Path(args.workspace, "Cargo.toml").resolve()
cargo_toml = CargoTOML(args.cargo_toml, str(workspace_cargo_toml))
else:
cargo_toml = CargoTOML(args.cargo_toml, None)
if args.lints:
for tok in generate_lint_flags(cargo_toml, args.strict_lints):
print(tok)
if rustc_version >= (1, 80):
if args.lints:
print("--check-cfg")
print("cfg(test)")
for cfg in sorted(cargo_toml.check_cfg):
print("--check-cfg")
print(cfg)
if args.features:
for feature in cargo_toml.get_table("features"):
if feature != "default":
print("--check-cfg")
print(f'cfg(feature,values("{feature}"))')
for header in args.config_headers:
for tok in generate_cfg_flags(header):
for tok in generate_cfg_flags(header, cargo_toml):
print(tok)
-1
View File
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Command-line wrapper for the tracetool machinery.
+182 -46
View File
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# SPDX-License-Identifier: GPL-2.0-or-later
"""
Machinery for generating tracing-related intermediate files.
@@ -12,12 +12,13 @@ __maintainer__ = "Stefan Hajnoczi"
__email__ = "stefanha@redhat.com"
import os
import re
import sys
import weakref
from pathlib import PurePath
import tracetool.format
import tracetool.backend
import tracetool.format
def error_write(*lines):
@@ -29,6 +30,49 @@ def error(*lines):
error_write(*lines)
sys.exit(1)
FMT_TOKEN = re.compile(r'''(?:
" ( (?: [^"\\] | \\[\\"abfnrt] | # a string literal
\\x[0-9a-fA-F][0-9a-fA-F]) *? ) "
| ( PRI [duixX] (?:8|16|32|64|PTR|MAX) ) # a PRIxxx macro
| \s+ # spaces (ignored)
)''', re.X)
PRI_SIZE_MAP = {
'8': 'hh',
'16': 'h',
'32': '',
'64': 'll',
'PTR': 't',
'MAX': 'j',
}
def expand_format_string(c_fmt, prefix=""):
def pri_macro_to_fmt(pri_macro):
assert pri_macro.startswith("PRI")
fmt_type = pri_macro[3] # 'd', 'i', 'u', or 'x'
fmt_size = pri_macro[4:] # '8', '16', '32', '64', 'PTR', 'MAX'
size = PRI_SIZE_MAP.get(fmt_size, None)
if size is None:
raise Exception(f"unknown macro {pri_macro}")
return size + fmt_type
result = prefix
pos = 0
while pos < len(c_fmt):
m = FMT_TOKEN.match(c_fmt, pos)
if not m:
print("No match at position", pos, ":", repr(c_fmt[pos:]), file=sys.stderr)
raise Exception("syntax error in trace file")
if m[1]:
substr = m[1]
elif m[2]:
substr = pri_macro_to_fmt(m[2])
else:
substr = ""
result += substr
pos = m.end()
return result
out_lineno = 1
out_filename = '<none>'
@@ -36,7 +80,7 @@ out_fobj = sys.stdout
def out_open(filename):
global out_filename, out_fobj
out_filename = filename
out_filename = posix_relpath(filename)
out_fobj = open(filename, 'wt')
def out(*lines, **kwargs):
@@ -88,6 +132,49 @@ ALLOWED_TYPES = [
"ptrdiff_t",
]
C_TYPE_KEYWORDS = {"char", "int", "void", "short", "long", "signed", "unsigned"}
C_TO_RUST_TYPE_MAP = {
"int": "std::ffi::c_int",
"long": "std::ffi::c_long",
"long long": "std::ffi::c_longlong",
"short": "std::ffi::c_short",
"char": "std::ffi::c_char",
"bool": "bool",
"unsigned": "std::ffi::c_uint",
# multiple keywords, keep them sorted
"long unsigned": "std::ffi::c_long",
"long long unsigned": "std::ffi::c_ulonglong",
"short unsigned": "std::ffi::c_ushort",
"char unsigned": "u8",
"int8_t": "i8",
"uint8_t": "u8",
"int16_t": "i16",
"uint16_t": "u16",
"int32_t": "i32",
"uint32_t": "u32",
"int64_t": "i64",
"uint64_t": "u64",
"void": "()",
"size_t": "usize",
"ssize_t": "isize",
"uintptr_t": "usize",
"ptrdiff_t": "isize",
}
# Rust requires manual casting of <32-bit types when passing them to
# variable-argument functions.
RUST_VARARGS_SMALL_TYPES = {
"std::ffi::c_short",
"std::ffi::c_ushort",
"std::ffi::c_char",
"i8",
"u8",
"i16",
"u16",
"bool",
}
def validate_type(name):
bits = name.split(" ")
for bit in bits:
@@ -103,6 +190,38 @@ def validate_type(name):
"other complex pointer types should be "
"declared as 'void *'" % name)
def c_type_to_rust(name):
ptr = False
const = False
name = name.rstrip()
if name[-1] == '*':
name = name[:-1].rstrip()
ptr = True
if name[-1] == '*':
# pointers to pointers are the same as void*
name = "void"
bits = name.split()
if "const" in bits:
const = True
bits.remove("const")
if bits[0] in C_TYPE_KEYWORDS:
if "signed" in bits:
bits.remove("signed")
if len(bits) > 1 and "int" in bits:
bits.remove("int")
bits.sort()
name = ' '.join(bits)
else:
if len(bits) > 1:
raise ValueError("Invalid type '%s'." % name)
name = bits[0]
ty = C_TO_RUST_TYPE_MAP[name.strip()]
if ptr:
ty = f'*{"const" if const else "mut"} {ty}'
return ty
class Arguments:
"""Event arguments description."""
@@ -120,10 +239,6 @@ class Arguments:
else:
self._args.append(arg)
def copy(self):
"""Create a new copy."""
return Arguments(list(self._args))
@staticmethod
def build(arg_str):
"""Build and Arguments instance from an argument string.
@@ -168,10 +283,16 @@ class Arguments:
def __str__(self):
"""String suitable for declaring function arguments."""
def onearg(t, n):
if t[-1] == '*':
return "".join([t, n])
else:
return " ".join([t, n])
if len(self._args) == 0:
return "void"
else:
return ", ".join([ " ".join([t, n]) for t,n in self._args ])
return ", ".join([ onearg(t, n) for t,n in self._args ])
def __repr__(self):
"""Evaluable string representation for this object."""
@@ -189,6 +310,43 @@ class Arguments:
"""List of argument names casted to their type."""
return ["(%s)%s" % (type_, name) for type_, name in self._args]
def rust_decl_extern(self):
"""Return a Rust argument list for an extern "C" function"""
return ", ".join((f"_{name}: {c_type_to_rust(type_)}"
for type_, name in self._args))
def rust_decl(self):
"""Return a Rust argument list for a tracepoint function"""
def decl_type(type_):
if type_ == "const char *":
return "&std::ffi::CStr"
return c_type_to_rust(type_)
return ", ".join((f"_{name}: {decl_type(type_)}"
for type_, name in self._args))
def rust_call_extern(self):
"""Return a Rust argument list for a call to an extern "C" function"""
def rust_cast(name, type_):
if type_ == "const char *":
return f"_{name}.as_ptr()"
return f"_{name}"
return ", ".join((rust_cast(name, type_) for type_, name in self._args))
def rust_call_varargs(self):
"""Return a Rust argument list for a call to a C varargs function"""
def rust_cast(name, type_):
if type_ == "const char *":
return f"_{name}.as_ptr()"
type_ = c_type_to_rust(type_)
if type_ in RUST_VARARGS_SMALL_TYPES:
return f"_{name} as std::ffi::c_int"
return f"_{name} /* as {type_} */"
return ", ".join((rust_cast(name, type_) for type_, name in self._args))
class Event(object):
"""Event description.
@@ -214,13 +372,12 @@ class Event(object):
r"(?P<name>\w+)"
r"\((?P<args>[^)]*)\)"
r"\s*"
r"(?:(?:(?P<fmt_trans>\".+),)?\s*(?P<fmt>\".+))?"
r"(?P<fmt>\".+)?"
r"\s*")
_VALID_PROPS = set(["disable", "vcpu"])
_VALID_PROPS = set(["disable"])
def __init__(self, name, props, fmt, args, lineno, filename, orig=None,
event_trans=None, event_exec=None):
def __init__(self, name, props, fmt, args, lineno, filename):
"""
Parameters
----------
@@ -228,20 +385,14 @@ class Event(object):
Event name.
props : list of str
Property names.
fmt : str, list of str
Event printing format string(s).
fmt : str
Event printing format string.
args : Arguments
Event arguments.
lineno : int
The line number in the input file.
filename : str
The path to the input file.
orig : Event or None
Original Event before transformation/generation.
event_trans : Event or None
Generated translation-time event ("tcg" property).
event_exec : Event or None
Generated execution-time event ("tcg" property).
"""
self.name = name
@@ -250,29 +401,16 @@ class Event(object):
self.args = args
self.lineno = int(lineno)
self.filename = str(filename)
self.event_trans = event_trans
self.event_exec = event_exec
if len(args) > 10:
raise ValueError("Event '%s' has more than maximum permitted "
"argument count" % name)
if orig is None:
self.original = weakref.ref(self)
else:
self.original = orig
unknown_props = set(self.properties) - self._VALID_PROPS
if len(unknown_props) > 0:
raise ValueError("Unknown properties: %s"
% ", ".join(unknown_props))
assert isinstance(self.fmt, str) or len(self.fmt) == 2
def copy(self):
"""Create a new copy."""
return Event(self.name, list(self.properties), self.fmt,
self.args.copy(), self.lineno, self.filename,
self, self.event_trans, self.event_exec)
@staticmethod
def build(line_str, lineno, filename):
@@ -294,8 +432,7 @@ class Event(object):
name = groups["name"]
props = groups["props"].split()
fmt = groups["fmt"]
fmt_trans = groups["fmt_trans"]
if fmt.find("%m") != -1 or fmt_trans.find("%m") != -1:
if fmt.find("%m") != -1:
raise ValueError("Event format '%m' is forbidden, pass the error "
"as an explicit trace argument")
if fmt.endswith(r'\n"'):
@@ -304,33 +441,25 @@ class Event(object):
if '\\n' in fmt:
raise ValueError("Event format must not use new line character")
if len(fmt_trans) > 0:
fmt = [fmt_trans, fmt]
args = Arguments.build(groups["args"])
return Event(name, props, fmt, args, lineno, filename)
return Event(name, props, fmt, args, lineno, posix_relpath(filename))
def __repr__(self):
"""Evaluable string representation for this object."""
if isinstance(self.fmt, str):
fmt = self.fmt
else:
fmt = "%s, %s" % (self.fmt[0], self.fmt[1])
return "Event('%s %s(%s) %s')" % (" ".join(self.properties),
self.name,
self.args,
fmt)
self.fmt)
# Star matching on PRI is dangerous as one might have multiple
# arguments with that format, hence the non-greedy version of it.
_FMT = re.compile(r"(%[\d\.]*\w+|%.*?PRI\S+)")
def formats(self):
"""List conversion specifiers in the argument print format string."""
assert not isinstance(self.fmt, list)
return self._FMT.findall(self.fmt)
QEMU_TRACE = "trace_%(name)s"
QEMU_TRACE_NOCHECK = "_nocheck__" + QEMU_TRACE
QEMU_TRACE_TCG = QEMU_TRACE + "_tcg"
QEMU_DSTATE = "_TRACE_%(NAME)s_DSTATE"
QEMU_BACKEND_DSTATE = "TRACE_%(NAME)s_BACKEND_DSTATE"
@@ -447,3 +576,10 @@ def generate(events, group, format, backends,
tracetool.backend.dtrace.PROBEPREFIX = probe_prefix
tracetool.format.generate(events, format, backend, group)
def posix_relpath(path, start=None):
try:
path = os.path.relpath(path, start)
except ValueError:
pass
return PurePath(path).as_posix()
+29 -14
View File
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# SPDX-License-Identifier: GPL-2.0-or-later
"""
Backend management.
@@ -19,11 +19,15 @@ All backends must generate their contents through the 'tracetool.out' routine.
Backend attributes
------------------
========= ====================================================================
Attribute Description
========= ====================================================================
PUBLIC If exists and is set to 'True', the backend is considered "public".
========= ====================================================================
=========================== ====================================================
Attribute Description
=========================== ====================================================
PUBLIC If exists and is set to 'True', the backend is
considered "public".
CHECK_TRACE_EVENT_GET_STATE If exists and is set to 'True', the backend-specific
code inside the tracepoint is emitted within an
``if trace_event_get_state()`` conditional.
=========================== ====================================================
Backend functions
@@ -94,29 +98,40 @@ def exists(name):
if name == "nop":
return True
name = name.replace("-", "_")
return tracetool.try_import("tracetool.backend." + name)[1]
return tracetool.try_import("tracetool.backend." + name)[0]
class Wrapper:
def __init__(self, backends, format):
self._backends = [backend.replace("-", "_") for backend in backends]
self._format = format.replace("-", "_")
self.check_trace_event_get_state = False
for backend in self._backends:
assert exists(backend)
assert tracetool.format.exists(self._format)
for backend in self.backend_modules():
check_trace_event_get_state = getattr(backend, "CHECK_TRACE_EVENT_GET_STATE", False)
self.check_trace_event_get_state = self.check_trace_event_get_state or check_trace_event_get_state
def _run_function(self, name, *args, **kwargs):
def backend_modules(self):
for backend in self._backends:
func = tracetool.try_import("tracetool.backend." + backend,
name % self._format, None)[1]
if func is not None:
func(*args, **kwargs)
module = tracetool.try_import("tracetool.backend." + backend)[1]
if module is not None:
yield module
def _run_function(self, name, *args, check_trace_event_get_state=None, **kwargs):
for backend in self.backend_modules():
func = getattr(backend, name % self._format, None)
if func is not None and \
(check_trace_event_get_state is None or
check_trace_event_get_state == getattr(backend, 'CHECK_TRACE_EVENT_GET_STATE', False)):
func(*args, **kwargs)
def generate_begin(self, events, group):
self._run_function("generate_%s_begin", events, group)
def generate(self, event, group):
self._run_function("generate_%s", event, group)
def generate(self, event, group, check_trace_event_get_state=None):
self._run_function("generate_%s", event, group, check_trace_event_get_state=check_trace_event_get_state)
def generate_backend_dstate(self, event, group):
self._run_function("generate_%s_backend_dstate", event, group)
+1 -1
View File
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# SPDX-License-Identifier: GPL-2.0-or-later
"""
DTrace/SystemTAP backend.
+12 -18
View File
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# SPDX-License-Identifier: GPL-2.0-or-later
"""
Ftrace built-in backend.
@@ -12,12 +12,11 @@ __maintainer__ = "Stefan Hajnoczi"
__email__ = "stefanha@redhat.com"
import os.path
from tracetool import out
from tracetool import out, expand_format_string
PUBLIC = True
CHECK_TRACE_EVENT_GET_STATE = True
def generate_h_begin(events, group):
@@ -30,24 +29,13 @@ def generate_h(event, group):
if len(event.args) > 0:
argnames = ", " + argnames
out(' {',
' char ftrace_buf[MAX_TRACE_STRLEN];',
' int unused __attribute__ ((unused));',
' int trlen;',
' if (trace_event_get_state(%(event_id)s)) {',
'#line %(event_lineno)d "%(event_filename)s"',
' trlen = snprintf(ftrace_buf, MAX_TRACE_STRLEN,',
' "%(name)s " %(fmt)s "\\n" %(argnames)s);',
out('#line %(event_lineno)d "%(event_filename)s"',
' ftrace_write("%(name)s " %(fmt)s "\\n" %(argnames)s);',
'#line %(out_next_lineno)d "%(out_filename)s"',
' trlen = MIN(trlen, MAX_TRACE_STRLEN - 1);',
' unused = write(trace_marker_fd, ftrace_buf, trlen);',
' }',
' }',
name=event.name,
args=event.args,
event_id="TRACE_" + event.name.upper(),
event_lineno=event.lineno,
event_filename=os.path.relpath(event.filename),
event_filename=event.filename,
fmt=event.fmt.rstrip("\n"),
argnames=argnames)
@@ -55,3 +43,9 @@ def generate_h(event, group):
def generate_h_backend_dstate(event, group):
out(' trace_event_get_state_dynamic_by_id(%(event_id)s) || \\',
event_id="TRACE_" + event.name.upper())
def generate_rs(event, group):
out(' let format_string = c"%(fmt)s";',
' unsafe {bindings::ftrace_write(format_string.as_ptr() as *const c_char, %(args)s);}',
fmt=expand_format_string(event.fmt),
args=event.args.rust_call_varargs())
+13 -25
View File
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# SPDX-License-Identifier: GPL-2.0-or-later
"""
Stderr built-in backend.
@@ -12,17 +12,15 @@ __maintainer__ = "Stefan Hajnoczi"
__email__ = "stefanha@redhat.com"
import os.path
from tracetool import out
from tracetool import out, expand_format_string
PUBLIC = True
CHECK_TRACE_EVENT_GET_STATE = True
def generate_h_begin(events, group):
out('#include "qemu/log-for-trace.h"',
'#include "qemu/error-report.h"',
'')
@@ -31,31 +29,13 @@ def generate_h(event, group):
if len(event.args) > 0:
argnames = ", " + argnames
if "vcpu" in event.properties:
# already checked on the generic format code
cond = "true"
else:
cond = "trace_event_get_state(%s)" % ("TRACE_" + event.name.upper())
out(' if (%(cond)s && qemu_loglevel_mask(LOG_TRACE)) {',
' if (message_with_timestamp) {',
' struct timeval _now;',
' gettimeofday(&_now, NULL);',
'#line %(event_lineno)d "%(event_filename)s"',
' qemu_log("%%d@%%zu.%%06zu:%(name)s " %(fmt)s "\\n",',
' qemu_get_thread_id(),',
' (size_t)_now.tv_sec, (size_t)_now.tv_usec',
' %(argnames)s);',
'#line %(out_next_lineno)d "%(out_filename)s"',
' } else {',
out(' if (qemu_loglevel_mask(LOG_TRACE)) {',
'#line %(event_lineno)d "%(event_filename)s"',
' qemu_log("%(name)s " %(fmt)s "\\n"%(argnames)s);',
'#line %(out_next_lineno)d "%(out_filename)s"',
' }',
' }',
cond=cond,
event_lineno=event.lineno,
event_filename=os.path.relpath(event.filename),
event_filename=event.filename,
name=event.name,
fmt=event.fmt.rstrip("\n"),
argnames=argnames)
@@ -64,3 +44,11 @@ def generate_h(event, group):
def generate_h_backend_dstate(event, group):
out(' trace_event_get_state_dynamic_by_id(%(event_id)s) || \\',
event_id="TRACE_" + event.name.upper())
def generate_rs(event, group):
out(' let format_string = c"%(fmt)s\\n";',
' if (unsafe { bindings::qemu_loglevel } & bindings::LOG_TRACE) != 0 {',
' unsafe { bindings::qemu_log(format_string.as_ptr() as *const c_char, %(args)s);}',
' }',
fmt=expand_format_string(event.fmt, event.name + " "),
args=event.args.rust_call_varargs())
+10 -14
View File
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# SPDX-License-Identifier: GPL-2.0-or-later
"""
Simple built-in backend.
@@ -16,6 +16,7 @@ from tracetool import out
PUBLIC = True
CHECK_TRACE_EVENT_GET_STATE = True
def is_string(arg):
@@ -36,7 +37,7 @@ def generate_h_begin(events, group):
def generate_h(event, group):
out(' _simple_%(api)s(%(args)s);',
out(' _simple_%(api)s(%(args)s);',
api=event.api(),
args=", ".join(event.args.names()))
@@ -72,22 +73,10 @@ def generate_c(event, group):
if len(event.args) == 0:
sizestr = '0'
event_id = 'TRACE_' + event.name.upper()
if "vcpu" in event.properties:
# already checked on the generic format code
cond = "true"
else:
cond = "trace_event_get_state(%s)" % event_id
out('',
' if (!%(cond)s) {',
' return;',
' }',
'',
' if (trace_record_start(&rec, %(event_obj)s.id, %(size_str)s)) {',
' return; /* Trace Buffer Full, Event Dropped ! */',
' }',
cond=cond,
event_obj=event.api(event.QEMU_EVENT),
size_str=sizestr)
@@ -109,3 +98,10 @@ def generate_c(event, group):
out(' trace_record_finish(&rec);',
'}',
'')
def generate_rs(event, group):
out(' extern "C" { fn _simple_%(api)s(%(rust_args)s); }',
' unsafe { _simple_%(api)s(%(args)s); }',
api=event.api(),
rust_args=event.args.rust_decl_extern(),
args=event.args.rust_call_extern())
+10 -15
View File
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# SPDX-License-Identifier: GPL-2.0-or-later
"""
Syslog built-in backend.
@@ -12,12 +12,11 @@ __maintainer__ = "Stefan Hajnoczi"
__email__ = "stefanha@redhat.com"
import os.path
from tracetool import out
from tracetool import out, expand_format_string
PUBLIC = True
CHECK_TRACE_EVENT_GET_STATE = True
def generate_h_begin(events, group):
@@ -30,24 +29,20 @@ def generate_h(event, group):
if len(event.args) > 0:
argnames = ", " + argnames
if "vcpu" in event.properties:
# already checked on the generic format code
cond = "true"
else:
cond = "trace_event_get_state(%s)" % ("TRACE_" + event.name.upper())
out(' if (%(cond)s) {',
'#line %(event_lineno)d "%(event_filename)s"',
out('#line %(event_lineno)d "%(event_filename)s"',
' syslog(LOG_INFO, "%(name)s " %(fmt)s %(argnames)s);',
'#line %(out_next_lineno)d "%(out_filename)s"',
' }',
cond=cond,
event_lineno=event.lineno,
event_filename=os.path.relpath(event.filename),
event_filename=event.filename,
name=event.name,
fmt=event.fmt.rstrip("\n"),
argnames=argnames)
def generate_rs(event, group):
out(' let format_string = c"%(fmt)s";',
' unsafe {::trace::syslog(::trace::LOG_INFO, format_string.as_ptr() as *const c_char, %(args)s);}',
fmt=expand_format_string(event.fmt),
args=event.args.rust_call_varargs())
def generate_h_backend_dstate(event, group):
out(' trace_event_get_state_dynamic_by_id(%(event_id)s) || \\',
+1 -1
View File
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# SPDX-License-Identifier: GPL-2.0-or-later
"""
LTTng User Space Tracing backend.
+2 -2
View File
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# SPDX-License-Identifier: GPL-2.0-or-later
"""
Format management.
@@ -70,7 +70,7 @@ def exists(name):
if len(name) == 0:
return False
name = name.replace("-", "_")
return tracetool.try_import("tracetool.format." + name)[1]
return tracetool.try_import("tracetool.format." + name)[0]
def generate(events, format, backend, group):
+3 -2
View File
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# SPDX-License-Identifier: GPL-2.0-or-later
"""
trace/generated-tracers.c
@@ -22,6 +22,7 @@ def generate(events, backend, group):
header = "trace-" + group + ".h"
out('/* This file is autogenerated by tracetool, do not edit. */',
'/* SPDX-License-Identifier: GPL-2.0-or-later */',
'',
'#include "qemu/osdep.h"',
'#include "qemu/module.h"',
@@ -36,7 +37,7 @@ def generate(events, backend, group):
' .id = 0,',
' .name = \"%(name)s\",',
' .sstate = %(sstate)s,',
' .dstate = &%(dstate)s ',
' .dstate = &%(dstate)s',
'};',
event = e.api(e.QEMU_EVENT),
name = e.name,
+3 -2
View File
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# SPDX-License-Identifier: GPL-2.0-or-later
"""
trace/generated-tracers.dtrace (DTrace only).
@@ -39,7 +39,8 @@ def generate(events, backend, group):
if not events and platform != "darwin":
return
out('/* This file is autogenerated by tracetool, do not edit. */'
out('/* This file is autogenerated by tracetool, do not edit. */',
'/* SPDX-License-Identifier: GPL-2.0-or-later */',
'',
'provider qemu {')
+11 -24
View File
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# SPDX-License-Identifier: GPL-2.0-or-later
"""
trace/generated-tracers.h
@@ -19,6 +19,7 @@ def generate(events, backend, group):
header = "trace/control.h"
out('/* This file is autogenerated by tracetool, do not edit. */',
'/* SPDX-License-Identifier: GPL-2.0-or-later */',
'',
'#ifndef TRACE_%s_GENERATED_TRACERS_H' % group.upper(),
'#define TRACE_%s_GENERATED_TRACERS_H' % group.upper(),
@@ -39,11 +40,6 @@ def generate(events, backend, group):
enabled = 0
else:
enabled = 1
if "tcg-exec" in e.properties:
# a single define for the two "sub-events"
out('#define TRACE_%(name)s_ENABLED %(enabled)d',
name=e.original.name.upper(),
enabled=enabled)
out('#define TRACE_%s_ENABLED %d' % (e.name.upper(), enabled))
backend.generate_begin(events, group)
@@ -59,33 +55,24 @@ def generate(events, backend, group):
out(' false)')
# tracer without checks
out('',
'static inline void %(api)s(%(args)s)',
'{',
api=e.api(e.QEMU_TRACE_NOCHECK),
api=e.api(),
args=e.args)
if "disable" not in e.properties:
backend.generate(e, group)
backend.generate(e, group, check_trace_event_get_state=False)
if backend.check_trace_event_get_state:
event_id = 'TRACE_' + e.name.upper()
cond = "trace_event_get_state(%s)" % event_id
out(' if (%(cond)s) {',
cond=cond)
backend.generate(e, group, check_trace_event_get_state=True)
out(' }')
out('}')
cond = "true"
out('',
'static inline void %(api)s(%(args)s)',
'{',
' if (%(cond)s) {',
' %(api_nocheck)s(%(names)s);',
' }',
'}',
api=e.api(),
api_nocheck=e.api(e.QEMU_TRACE_NOCHECK),
args=e.args,
names=", ".join(e.args.names()),
cond=cond)
backend.generate_end(events, group)
out('#endif /* TRACE_%s_GENERATED_TRACERS_H */' % group.upper())
+2 -3
View File
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# SPDX-License-Identifier: GPL-2.0-or-later
"""
Generate .stp file that printfs log messages (DTrace with SystemTAP only).
@@ -18,8 +18,6 @@ from tracetool.backend.dtrace import binary, probeprefix
from tracetool.backend.simple import is_string
from tracetool.format.stap import stap_escape
def global_var_name(name):
return probeprefix().replace(".", "_") + "_" + name
STATE_SKIP = 0
STATE_LITERAL = 1
@@ -88,6 +86,7 @@ def c_fmt_to_stap(fmt):
def generate(events, backend, group):
out('/* This file is autogenerated by tracetool, do not edit. */',
'/* SPDX-License-Identifier: GPL-2.0-or-later */',
'')
for event_id, e in enumerate(events):
+64
View File
@@ -0,0 +1,64 @@
# SPDX-License-Identifier: GPL-2.0-or-later
"""
trace-DIR.rs
"""
__author__ = "Tanish Desai <tanishdesai37@gmail.com>"
__copyright__ = "Copyright 2025, Tanish Desai <tanishdesai37@gmail.com>"
__license__ = "GPL version 2 or (at your option) any later version"
__maintainer__ = "Stefan Hajnoczi"
__email__ = "stefanha@redhat.com"
from tracetool import out
def generate(events, backend, group):
out('// SPDX-License-Identifier: GPL-2.0-or-later',
'// This file is @generated by tracetool, do not edit.',
'',
'#[allow(unused_imports)]',
'use std::ffi::c_char;',
'#[allow(unused_imports)]',
'use util::bindings;',
'',
'#[inline(always)]',
'fn trace_event_state_is_enabled(dstate: u16) -> bool {',
' (unsafe { trace_events_enabled_count }) != 0 && dstate != 0',
'}',
'',
'extern "C" {',
' static mut trace_events_enabled_count: u32;',
'}',)
out('extern "C" {')
for e in events:
out(' static mut %s: u16;' % e.api(e.QEMU_DSTATE))
out('}')
backend.generate_begin(events, group)
for e in events:
out('',
'#[inline(always)]',
'#[allow(dead_code)]',
'pub fn %(api)s(%(args)s)',
'{',
api=e.api(e.QEMU_TRACE),
args=e.args.rust_decl())
if "disable" not in e.properties:
backend.generate(e, group, check_trace_event_get_state=False)
if backend.check_trace_event_get_state:
event_id = 'TRACE_' + e.name.upper()
out(' if trace_event_state_is_enabled(unsafe { _%(event_id)s_DSTATE}) {',
event_id = event_id,
api=e.api())
backend.generate(e, group, check_trace_event_get_state=True)
out(' }')
out('}')
backend.generate_end(events, group)
+2 -3
View File
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# SPDX-License-Identifier: GPL-2.0-or-later
"""
Generate .stp file that outputs simpletrace binary traces (DTrace with SystemTAP only).
@@ -17,11 +17,10 @@ from tracetool.backend.dtrace import probeprefix
from tracetool.backend.simple import is_string
from tracetool.format.stap import stap_escape
def global_var_name(name):
return probeprefix().replace(".", "_") + "_" + name
def generate(events, backend, group):
out('/* This file is autogenerated by tracetool, do not edit. */',
'/* SPDX-License-Identifier: GPL-2.0-or-later */',
'')
for event_id, e in enumerate(events):
+2 -1
View File
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# SPDX-License-Identifier: GPL-2.0-or-later
"""
Generate .stp file (DTrace with SystemTAP only).
@@ -38,6 +38,7 @@ def generate(events, backend, group):
if "disable" not in e.properties]
out('/* This file is autogenerated by tracetool, do not edit. */',
'/* SPDX-License-Identifier: GPL-2.0-or-later */',
'')
for e in events:
+2 -1
View File
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# SPDX-License-Identifier: GPL-2.0-or-later
"""
trace/generated-ust.c
@@ -20,6 +20,7 @@ def generate(events, backend, group):
if "disabled" not in e.properties]
out('/* This file is autogenerated by tracetool, do not edit. */',
'/* SPDX-License-Identifier: GPL-2.0-or-later */',
'',
'#include "qemu/osdep.h"',
'',
+2 -1
View File
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# SPDX-License-Identifier: GPL-2.0-or-later
"""
trace/generated-ust-provider.h
@@ -25,6 +25,7 @@ def generate(events, backend, group):
include = "trace-ust.h"
out('/* This file is autogenerated by tracetool, do not edit. */',
'/* SPDX-License-Identifier: GPL-2.0-or-later */',
'',
'#undef TRACEPOINT_PROVIDER',
'#define TRACEPOINT_PROVIDER qemu',

Some files were not shown because too many files have changed in this diff Show More