Skip to content

Commit

Permalink
scripts/bpf: Add syscall commands printer
Browse files Browse the repository at this point in the history
Add a new target to bpf_doc.py to support generating the list of syscall
commands directly from the UAPI headers. Assuming that developer
submissions keep the main header up to date, this should allow the man
pages to be automatically generated based on the latest API changes
rather than requiring someone to separately go back through the API and
describe each command.

Signed-off-by: Joe Stringer <joe@cilium.io>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Reviewed-by: Quentin Monnet <quentin@isovalent.com>
Acked-by: Toke Høiland-Jørgensen <toke@redhat.com>
Link: https://lore.kernel.org/bpf/20210302171947.2268128-11-joe@cilium.io
  • Loading branch information
Joe Stringer authored and Alexei Starovoitov committed Mar 5, 2021
1 parent 923a932 commit a67882a
Showing 1 changed file with 91 additions and 9 deletions.
100 changes: 91 additions & 9 deletions scripts/bpf_doc.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
class NoHelperFound(BaseException):
pass

class NoSyscallCommandFound(BaseException):
pass

class ParsingError(BaseException):
def __init__(self, line='<line not provided>', reader=None):
if reader:
Expand All @@ -23,18 +26,27 @@ def __init__(self, line='<line not provided>', reader=None):
else:
BaseException.__init__(self, 'Error parsing line: %s' % line)

class Helper(object):

class APIElement(object):
"""
An object representing the description of an eBPF helper function.
@proto: function prototype of the helper function
@desc: textual description of the helper function
@ret: description of the return value of the helper function
An object representing the description of an aspect of the eBPF API.
@proto: prototype of the API symbol
@desc: textual description of the symbol
@ret: (optional) description of any associated return value
"""
def __init__(self, proto='', desc='', ret=''):
self.proto = proto
self.desc = desc
self.ret = ret


class Helper(APIElement):
"""
An object representing the description of an eBPF helper function.
@proto: function prototype of the helper function
@desc: textual description of the helper function
@ret: description of the return value of the helper function
"""
def proto_break_down(self):
"""
Break down helper function protocol into smaller chunks: return type,
Expand All @@ -61,6 +73,7 @@ def proto_break_down(self):

return res


class HeaderParser(object):
"""
An object used to parse a file in order to extract the documentation of a
Expand All @@ -73,13 +86,32 @@ def __init__(self, filename):
self.reader = open(filename, 'r')
self.line = ''
self.helpers = []
self.commands = []

def parse_element(self):
proto = self.parse_symbol()
desc = self.parse_desc()
ret = self.parse_ret()
return APIElement(proto=proto, desc=desc, ret=ret)

def parse_helper(self):
proto = self.parse_proto()
desc = self.parse_desc()
ret = self.parse_ret()
return Helper(proto=proto, desc=desc, ret=ret)

def parse_symbol(self):
p = re.compile(' \* ?(.+)$')
capture = p.match(self.line)
if not capture:
raise NoSyscallCommandFound
end_re = re.compile(' \* ?NOTES$')
end = end_re.match(self.line)
if end:
raise NoSyscallCommandFound
self.line = self.reader.readline()
return capture.group(1)

def parse_proto(self):
# Argument can be of shape:
# - "void"
Expand Down Expand Up @@ -141,23 +173,39 @@ def parse_ret(self):
break
return ret

def run(self):
# Advance to start of helper function descriptions.
offset = self.reader.read().find('* Start of BPF helper function descriptions:')
def seek_to(self, target, help_message):
self.reader.seek(0)
offset = self.reader.read().find(target)
if offset == -1:
raise Exception('Could not find start of eBPF helper descriptions list')
raise Exception(help_message)
self.reader.seek(offset)
self.reader.readline()
self.reader.readline()
self.line = self.reader.readline()

def parse_syscall(self):
self.seek_to('* DOC: eBPF Syscall Commands',
'Could not find start of eBPF syscall descriptions list')
while True:
try:
command = self.parse_element()
self.commands.append(command)
except NoSyscallCommandFound:
break

def parse_helpers(self):
self.seek_to('* Start of BPF helper function descriptions:',
'Could not find start of eBPF helper descriptions list')
while True:
try:
helper = self.parse_helper()
self.helpers.append(helper)
except NoHelperFound:
break

def run(self):
self.parse_syscall()
self.parse_helpers()
self.reader.close()

###############################################################################
Expand Down Expand Up @@ -420,6 +468,37 @@ def print_one(self, helper):
self.print_elem(helper)


class PrinterSyscallRST(PrinterRST):
"""
A printer for dumping collected information about the syscall API as a
ReStructured Text page compatible with the rst2man program, which can be
used to generate a manual page for the syscall.
@parser: A HeaderParser with APIElement objects to print to standard
output
"""
def __init__(self, parser):
self.elements = parser.commands

def print_header(self):
header = '''\
===
bpf
===
-------------------------------------------------------------------------------
Perform a command on an extended BPF object
-------------------------------------------------------------------------------
:Manual section: 2
COMMANDS
========
'''
PrinterRST.print_license(self)
print(header)

def print_one(self, command):
print('**%s**' % (command.proto))
self.print_elem(command)


class PrinterHelpers(Printer):
Expand Down Expand Up @@ -620,6 +699,7 @@ def print_one(self, helper):

printers = {
'helpers': PrinterHelpersRST,
'syscall': PrinterSyscallRST,
}

argParser = argparse.ArgumentParser(description="""
Expand All @@ -644,6 +724,8 @@ def print_one(self, helper):

# Print formatted output to standard output.
if args.header:
if args.target != 'helpers':
raise NotImplementedError('Only helpers header generation is supported')
printer = PrinterHelpers(headerParser)
else:
printer = printers[args.target](headerParser)
Expand Down

0 comments on commit a67882a

Please sign in to comment.