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.
Showing the changes to scripts (1 of 1 file). Show all
Unified · Split
| @@ -14,6 +14,9 @@ import sys, os · 13 unmodified lines | ||
| 14 | 14 | class NoHelperFound(BaseException): |
| 15 | 15 | pass |
| 16 | 16 | |
| 17 | +class NoSyscallCommandFound(BaseException): | |
| 18 | + pass | |
| 19 | + | |
| 17 | 20 | class ParsingError(BaseException): |
| 18 | 21 | def __init__(self, line='<line not provided>', reader=None): |
| 19 | 22 | if reader: |
| @@ -23,18 +26,27 @@ class ParsingError(BaseException): · 3 unmodified lines | ||
| 23 | 26 | else: |
| 24 | 27 | BaseException.__init__(self, 'Error parsing line: %s' % line) |
| 25 | 28 | |
| 26 | −class Helper(object): | |
| 29 | + | |
| 30 | +class APIElement(object): | |
| 27 | 31 | """ |
| 28 | − An object representing the description of an eBPF helper function. | |
| 29 | − @proto: function prototype of the helper function | |
| 30 | − @desc: textual description of the helper function | |
| 31 | − @ret: description of the return value of the helper function | |
| 32 | + An object representing the description of an aspect of the eBPF API. | |
| 33 | + @proto: prototype of the API symbol | |
| 34 | + @desc: textual description of the symbol | |
| 35 | + @ret: (optional) description of any associated return value | |
| 32 | 36 | """ |
| 33 | 37 | def __init__(self, proto='', desc='', ret=''): |
| 34 | 38 | self.proto = proto |
| 35 | 39 | self.desc = desc |
| 36 | 40 | self.ret = ret |
| 37 | 41 | |
| 42 | + | |
| 43 | +class 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 | 50 | def proto_break_down(self): |
| 39 | 51 | """ |
| 40 | 52 | Break down helper function protocol into smaller chunks: return type, |
| @@ -61,6 +73,7 @@ class Helper(object): · 20 unmodified lines | ||
| 61 | 73 | |
| 62 | 74 | return res |
| 63 | 75 | |
| 76 | + | |
| 64 | 77 | class HeaderParser(object): |
| 65 | 78 | """ |
| 66 | 79 | 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 | 86 | self.reader = open(filename, 'r') |
| 74 | 87 | self.line = '' |
| 75 | 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 | 97 | def parse_helper(self): |
| 78 | 98 | proto = self.parse_proto() |
| @@ -80,6 +100,18 @@ class HeaderParser(object): · 1 unmodified line | ||
| 80 | 100 | ret = self.parse_ret() |
| 81 | 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 | 115 | def parse_proto(self): |
| 84 | 116 | # Argument can be of shape: |
| 85 | 117 | # - "void" |
| @@ -141,16 +173,29 @@ class HeaderParser(object): · 55 unmodified lines | ||
| 141 | 173 | break |
| 142 | 174 | return ret |
| 143 | 175 | |
| 144 | − def run(self): | |
| 145 | − # Advance to start of helper function descriptions. | |
| 146 | − offset = self.reader.read().find('* Start of BPF helper function descriptions:') | |
| 176 | + def seek_to(self, target, help_message): | |
| 177 | + self.reader.seek(0) | |
| 178 | + offset = self.reader.read().find(target) | |
| 147 | 179 | if offset == -1: |
| 148 | − raise Exception('Could not find start of eBPF helper descriptions list') | |
| 180 | + raise Exception(help_message) | |
| 149 | 181 | self.reader.seek(offset) |
| 150 | 182 | self.reader.readline() |
| 151 | 183 | self.reader.readline() |
| 152 | 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 | 199 | while True: |
| 155 | 200 | try: |
| 156 | 201 | helper = self.parse_helper() |
| @@ -158,6 +203,9 @@ class HeaderParser(object): · 1 unmodified line | ||
| 158 | 203 | except NoHelperFound: |
| 159 | 204 | break |
| 160 | 205 | |
| 206 | + def run(self): | |
| 207 | + self.parse_syscall() | |
| 208 | + self.parse_helpers() | |
| 161 | 209 | self.reader.close() |
| 162 | 210 | |
| 163 | 211 | ############################################################################### |
| @@ -420,6 +468,37 @@ SEE ALSO · 256 unmodified lines | ||
| 420 | 468 | self.print_elem(helper) |
| 421 | 469 | |
| 422 | 470 | |
| 471 | +class 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 | +=== | |
| 485 | +bpf | |
| 486 | +=== | |
| 487 | +------------------------------------------------------------------------------- | |
| 488 | +Perform a command on an extended BPF object | |
| 489 | +------------------------------------------------------------------------------- | |
| 490 | + | |
| 491 | +:Manual section: 2 | |
| 492 | + | |
| 493 | +COMMANDS | |
| 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 | |
| 425 | 504 | class PrinterHelpers(Printer): |
| @@ -620,6 +699,7 @@ bpfh = os.path.join(linuxRoot, 'include/uapi/linux/bpf.h') · 194 unmodified lines | ||
| 620 | 699 | |
| 621 | 700 | printers = { |
| 622 | 701 | 'helpers': PrinterHelpersRST, |
| 702 | + 'syscall': PrinterSyscallRST, | |
| 623 | 703 | } |
| 624 | 704 | |
| 625 | 705 | argParser = argparse.ArgumentParser(description=""" |
| @@ -644,6 +724,8 @@ headerParser.run() · 18 unmodified lines | ||
| 644 | 724 | |
| 645 | 725 | # Print formatted output to standard output. |
| 646 | 726 | if args.header: |
| 727 | + if args.target != 'helpers': | |
| 728 | + raise NotImplementedError('Only helpers header generation is supported') | |
| 647 | 729 | printer = PrinterHelpers(headerParser) |
| 648 | 730 | else: |
| 649 | 731 | printer = printers[args.target](headerParser) |