History

scripts/bpf: Add syscall commands printer

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.

Joe Stringer committed 5 years ago

Commit a67882a1 parent 923a9321 file · +91 −9Browse files

Unified · Split

scripts/bpf_doc.py+91−9
@@ -14,6 +14,9 @@ import sys, os · 13 unmodified lines
14class NoHelperFound(BaseException):14class NoHelperFound(BaseException):
15 pass15 pass
16 16
17class NoSyscallCommandFound(BaseException):
18 pass
19
17class ParsingError(BaseException):20class ParsingError(BaseException):
18 def __init__(self, line='<line not provided>', reader=None):21 def __init__(self, line='<line not provided>', reader=None):
19 if reader:22 if reader:
@@ -23,18 +26,27 @@ class ParsingError(BaseException): · 3 unmodified lines
23 else:26 else:
24 BaseException.__init__(self, 'Error parsing line: %s' % line)27 BaseException.__init__(self, 'Error parsing line: %s' % line)
25 28
26class Helper(object):29
30class APIElement(object):
27 """31 """
28 An object representing the description of an eBPF helper function.32 An object representing the description of an aspect of the eBPF API.
29 @proto: function prototype of the helper function33 @proto: prototype of the API symbol
30 @desc: textual description of the helper function34 @desc: textual description of the symbol
31 @ret: description of the return value of the helper function35 @ret: (optional) description of any associated return value
32 """36 """
33 def __init__(self, proto='', desc='', ret=''):37 def __init__(self, proto='', desc='', ret=''):
34 self.proto = proto38 self.proto = proto
35 self.desc = desc39 self.desc = desc
36 self.ret = ret40 self.ret = ret
37 41
42
43class Helper(APIElement):
44 """
45 An object representing the description of an eBPF helper function.
46 @proto: function prototype of the helper function
47 @desc: textual description of the helper function
48 @ret: description of the return value of the helper function
49 """
38 def proto_break_down(self):50 def proto_break_down(self):
39 """51 """
40 Break down helper function protocol into smaller chunks: return type,52 Break down helper function protocol into smaller chunks: return type,
@@ -61,6 +73,7 @@ class Helper(object): · 20 unmodified lines
61 73
62 return res74 return res
63 75
76
64class HeaderParser(object):77class HeaderParser(object):
65 """78 """
66 An object used to parse a file in order to extract the documentation of a79 An object used to parse a file in order to extract the documentation of a
@@ -73,6 +86,13 @@ class HeaderParser(object): · 6 unmodified lines
73 self.reader = open(filename, 'r')86 self.reader = open(filename, 'r')
74 self.line = ''87 self.line = ''
75 self.helpers = []88 self.helpers = []
89 self.commands = []
90
91 def parse_element(self):
92 proto = self.parse_symbol()
93 desc = self.parse_desc()
94 ret = self.parse_ret()
95 return APIElement(proto=proto, desc=desc, ret=ret)
76 96
77 def parse_helper(self):97 def parse_helper(self):
78 proto = self.parse_proto()98 proto = self.parse_proto()
@@ -80,6 +100,18 @@ class HeaderParser(object): · 1 unmodified line
80 ret = self.parse_ret()100 ret = self.parse_ret()
81 return Helper(proto=proto, desc=desc, ret=ret)101 return Helper(proto=proto, desc=desc, ret=ret)
82 102
103 def parse_symbol(self):
104 p = re.compile(' \* ?(.+)$')
105 capture = p.match(self.line)
106 if not capture:
107 raise NoSyscallCommandFound
108 end_re = re.compile(' \* ?NOTES$')
109 end = end_re.match(self.line)
110 if end:
111 raise NoSyscallCommandFound
112 self.line = self.reader.readline()
113 return capture.group(1)
114
83 def parse_proto(self):115 def parse_proto(self):
84 # Argument can be of shape:116 # Argument can be of shape:
85 # - "void"117 # - "void"
@@ -141,16 +173,29 @@ class HeaderParser(object): · 55 unmodified lines
141 break173 break
142 return ret174 return ret
143 175
144 def run(self):176 def seek_to(self, target, help_message):
145 # Advance to start of helper function descriptions.177 self.reader.seek(0)
146 offset = self.reader.read().find('* Start of BPF helper function descriptions:')178 offset = self.reader.read().find(target)
147 if offset == -1:179 if offset == -1:
148 raise Exception('Could not find start of eBPF helper descriptions list')180 raise Exception(help_message)
149 self.reader.seek(offset)181 self.reader.seek(offset)
150 self.reader.readline()182 self.reader.readline()
151 self.reader.readline()183 self.reader.readline()
152 self.line = self.reader.readline()184 self.line = self.reader.readline()
153 185
186 def parse_syscall(self):
187 self.seek_to('* DOC: eBPF Syscall Commands',
188 'Could not find start of eBPF syscall descriptions list')
189 while True:
190 try:
191 command = self.parse_element()
192 self.commands.append(command)
193 except NoSyscallCommandFound:
194 break
195
196 def parse_helpers(self):
197 self.seek_to('* Start of BPF helper function descriptions:',
198 'Could not find start of eBPF helper descriptions list')
154 while True:199 while True:
155 try:200 try:
156 helper = self.parse_helper()201 helper = self.parse_helper()
@@ -158,6 +203,9 @@ class HeaderParser(object): · 1 unmodified line
158 except NoHelperFound:203 except NoHelperFound:
159 break204 break
160 205
206 def run(self):
207 self.parse_syscall()
208 self.parse_helpers()
161 self.reader.close()209 self.reader.close()
162 210
163###############################################################################211###############################################################################
@@ -420,6 +468,37 @@ SEE ALSO · 256 unmodified lines
420 self.print_elem(helper)468 self.print_elem(helper)
421 469
422 470
471class PrinterSyscallRST(PrinterRST):
472 """
473 A printer for dumping collected information about the syscall API as a
474 ReStructured Text page compatible with the rst2man program, which can be
475 used to generate a manual page for the syscall.
476 @parser: A HeaderParser with APIElement objects to print to standard
477 output
478 """
479 def __init__(self, parser):
480 self.elements = parser.commands
481
482 def print_header(self):
483 header = '''\
484===
485bpf
486===
487-------------------------------------------------------------------------------
488Perform a command on an extended BPF object
489-------------------------------------------------------------------------------
490
491:Manual section: 2
492
493COMMANDS
494========
495'''
496 PrinterRST.print_license(self)
497 print(header)
498
499 def print_one(self, command):
500 print('**%s**' % (command.proto))
501 self.print_elem(command)
423 502
424 503
425class PrinterHelpers(Printer):504class PrinterHelpers(Printer):
@@ -620,6 +699,7 @@ bpfh = os.path.join(linuxRoot, 'include/uapi/linux/bpf.h') · 194 unmodified lines
620 699
621printers = {700printers = {
622 'helpers': PrinterHelpersRST,701 'helpers': PrinterHelpersRST,
702 'syscall': PrinterSyscallRST,
623}703}
624 704
625argParser = argparse.ArgumentParser(description="""705argParser = argparse.ArgumentParser(description="""
@@ -644,6 +724,8 @@ headerParser.run() · 18 unmodified lines
644 724
645# Print formatted output to standard output.725# Print formatted output to standard output.
646if args.header:726if args.header:
727 if args.target != 'helpers':
728 raise NotImplementedError('Only helpers header generation is supported')
647 printer = PrinterHelpers(headerParser)729 printer = PrinterHelpers(headerParser)
648else:730else:
649 printer = printers[args.target](headerParser)731 printer = printers[args.target](headerParser)