1#!/usr/bin/env perl 2# SPDX-License-Identifier: GPL-2.0 3 4use strict; 5use warnings; 6use utf8; 7use Pod::Usage; 8use Getopt::Long; 9use File::Find; 10use Fcntl ':mode'; 11 12my $help = 0; 13my $man = 0; 14my $debug = 0; 15my $enable_lineno = 0; 16my $prefix="Documentation/ABI"; 17 18# 19# If true, assumes that the description is formatted with ReST 20# 21my $description_is_rst = 1; 22 23GetOptions( 24 "debug|d+" => \$debug, 25 "enable-lineno" => \$enable_lineno, 26 "rst-source!" => \$description_is_rst, 27 "dir=s" => \$prefix, 28 'help|?' => \$help, 29 man => \$man 30) or pod2usage(2); 31 32pod2usage(1) if $help; 33pod2usage(-exitstatus => 0, -verbose => 2) if $man; 34 35pod2usage(2) if (scalar @ARGV < 1 || @ARGV > 2); 36 37my ($cmd, $arg) = @ARGV; 38 39pod2usage(2) if ($cmd ne "search" && $cmd ne "rest" && $cmd ne "validate"); 40pod2usage(2) if ($cmd eq "search" && !$arg); 41 42require Data::Dumper if ($debug); 43 44my %data; 45my %symbols; 46 47# 48# Displays an error message, printing file name and line 49# 50sub parse_error($$$$) { 51 my ($file, $ln, $msg, $data) = @_; 52 53 $data =~ s/\s+$/\n/; 54 55 print STDERR "Warning: file $file#$ln:\n\t$msg"; 56 57 if ($data ne "") { 58 print STDERR ". Line\n\t\t$data"; 59 } else { 60 print STDERR "\n"; 61 } 62} 63 64# 65# Parse an ABI file, storing its contents at %data 66# 67sub parse_abi { 68 my $file = $File::Find::name; 69 70 my $mode = (stat($file))[2]; 71 return if ($mode & S_IFDIR); 72 return if ($file =~ m,/README,); 73 74 my $name = $file; 75 $name =~ s,.*/,,; 76 77 my $fn = $file; 78 $fn =~ s,Documentation/ABI/,,; 79 80 my $nametag = "File $fn"; 81 $data{$nametag}->{what} = "File $name"; 82 $data{$nametag}->{type} = "File"; 83 $data{$nametag}->{file} = $name; 84 $data{$nametag}->{filepath} = $file; 85 $data{$nametag}->{is_file} = 1; 86 $data{$nametag}->{line_no} = 1; 87 88 my $type = $file; 89 $type =~ s,.*/(.*)/.*,$1,; 90 91 my $what; 92 my $new_what; 93 my $tag = ""; 94 my $ln; 95 my $xrefs; 96 my $space; 97 my @labels; 98 my $label = ""; 99 100 print STDERR "Opening $file\n" if ($debug > 1); 101 open IN, $file; 102 while(<IN>) { 103 $ln++; 104 if (m/^(\S+)(:\s*)(.*)/i) { 105 my $new_tag = lc($1); 106 my $sep = $2; 107 my $content = $3; 108 109 if (!($new_tag =~ m/(what|where|date|kernelversion|contact|description|users)/)) { 110 if ($tag eq "description") { 111 # New "tag" is actually part of 112 # description. Don't consider it a tag 113 $new_tag = ""; 114 } elsif ($tag ne "") { 115 parse_error($file, $ln, "tag '$tag' is invalid", $_); 116 } 117 } 118 119 # Invalid, but it is a common mistake 120 if ($new_tag eq "where") { 121 parse_error($file, $ln, "tag 'Where' is invalid. Should be 'What:' instead", ""); 122 $new_tag = "what"; 123 } 124 125 if ($new_tag =~ m/what/) { 126 $space = ""; 127 $content =~ s/[,.;]$//; 128 129 push @{$symbols{$content}->{file}}, " $file:" . ($ln - 1); 130 131 if ($tag =~ m/what/) { 132 $what .= ", " . $content; 133 } else { 134 if ($what) { 135 parse_error($file, $ln, "What '$what' doesn't have a description", "") if (!$data{$what}->{description}); 136 137 foreach my $w(split /, /, $what) { 138 $symbols{$w}->{xref} = $what; 139 }; 140 } 141 142 $what = $content; 143 $label = $content; 144 $new_what = 1; 145 } 146 push @labels, [($content, $label)]; 147 $tag = $new_tag; 148 149 push @{$data{$nametag}->{symbols}}, $content if ($data{$nametag}->{what}); 150 next; 151 } 152 153 if ($tag ne "" && $new_tag) { 154 $tag = $new_tag; 155 156 if ($new_what) { 157 @{$data{$what}->{label_list}} = @labels if ($data{$nametag}->{what}); 158 @labels = (); 159 $label = ""; 160 $new_what = 0; 161 162 $data{$what}->{type} = $type; 163 if (!defined($data{$what}->{file})) { 164 $data{$what}->{file} = $name; 165 $data{$what}->{filepath} = $file; 166 } else { 167 if ($name ne $data{$what}->{file}) { 168 $data{$what}->{file} .= " " . $name; 169 $data{$what}->{filepath} .= " " . $file; 170 } 171 } 172 print STDERR "\twhat: $what\n" if ($debug > 1); 173 $data{$what}->{line_no} = $ln; 174 } else { 175 $data{$what}->{line_no} = $ln if (!defined($data{$what}->{line_no})); 176 } 177 178 if (!$what) { 179 parse_error($file, $ln, "'What:' should come first:", $_); 180 next; 181 } 182 if ($new_tag eq "description") { 183 $sep =~ s,:, ,; 184 $content = ' ' x length($new_tag) . $sep . $content; 185 while ($content =~ s/\t+/' ' x (length($&) * 8 - length($`) % 8)/e) {} 186 if ($content =~ m/^(\s*)(\S.*)$/) { 187 # Preserve initial spaces for the first line 188 $space = $1; 189 $content = "$2\n"; 190 $data{$what}->{$tag} .= $content; 191 } else { 192 undef($space); 193 } 194 195 } else { 196 $data{$what}->{$tag} = $content; 197 } 198 next; 199 } 200 } 201 202 # Store any contents before tags at the database 203 if (!$tag && $data{$nametag}->{what}) { 204 $data{$nametag}->{description} .= $_; 205 next; 206 } 207 208 if ($tag eq "description") { 209 my $content = $_; 210 while ($content =~ s/\t+/' ' x (length($&) * 8 - length($`) % 8)/e) {} 211 if (m/^\s*\n/) { 212 $data{$what}->{$tag} .= "\n"; 213 next; 214 } 215 216 if (!defined($space)) { 217 # Preserve initial spaces for the first line 218 if ($content =~ m/^(\s*)(\S.*)$/) { 219 $space = $1; 220 $content = "$2\n"; 221 } 222 } else { 223 $space = "" if (!($content =~ s/^($space)//)); 224 } 225 $data{$what}->{$tag} .= $content; 226 227 next; 228 } 229 if (m/^\s*(.*)/) { 230 $data{$what}->{$tag} .= "\n$1"; 231 $data{$what}->{$tag} =~ s/\n+$//; 232 next; 233 } 234 235 # Everything else is error 236 parse_error($file, $ln, "Unexpected content", $_); 237 } 238 $data{$nametag}->{description} =~ s/^\n+// if ($data{$nametag}->{description}); 239 if ($what) { 240 parse_error($file, $ln, "What '$what' doesn't have a description", "") if (!$data{$what}->{description}); 241 242 foreach my $w(split /, /,$what) { 243 $symbols{$w}->{xref} = $what; 244 }; 245 } 246 close IN; 247} 248 249sub create_labels { 250 my %labels; 251 252 foreach my $what (keys %data) { 253 next if ($data{$what}->{file} eq "File"); 254 255 foreach my $p (@{$data{$what}->{label_list}}) { 256 my ($content, $label) = @{$p}; 257 $label = "abi_" . $label . " "; 258 $label =~ tr/A-Z/a-z/; 259 260 # Convert special chars to "_" 261 $label =~s/([\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xff])/_/g; 262 $label =~ s,_+,_,g; 263 $label =~ s,_$,,; 264 265 # Avoid duplicated labels 266 while (defined($labels{$label})) { 267 my @chars = ("A".."Z", "a".."z"); 268 $label .= $chars[rand @chars]; 269 } 270 $labels{$label} = 1; 271 272 $data{$what}->{label} = $label; 273 274 # only one label is enough 275 last; 276 } 277 } 278} 279 280# 281# Outputs the book on ReST format 282# 283 284# \b doesn't work well with paths. So, we need to define something else: 285# Boundaries are punct characters, spaces and end-of-line 286my $start = qr {(^|\s|\() }x; 287my $bondary = qr { ([,.:;\)\s]|\z) }x; 288my $xref_match = qr { $start(\/(sys|config|proc|dev|kvd)\/[^,.:;\)\s]+)$bondary }x; 289my $symbols = qr { ([\x01-\x08\x0e-\x1f\x21-\x2f\x3a-\x40\x7b-\xff]) }x; 290 291sub output_rest { 292 create_labels(); 293 294 my $part = ""; 295 296 foreach my $what (sort { 297 ($data{$a}->{type} eq "File") cmp ($data{$b}->{type} eq "File") || 298 $a cmp $b 299 } keys %data) { 300 my $type = $data{$what}->{type}; 301 302 my @file = split / /, $data{$what}->{file}; 303 my @filepath = split / /, $data{$what}->{filepath}; 304 305 if ($enable_lineno) { 306 printf "#define LINENO %s%s#%s\n\n", 307 $prefix, $file[0], 308 $data{$what}->{line_no}; 309 } 310 311 my $w = $what; 312 313 if ($type ne "File") { 314 my $cur_part = $what; 315 if ($what =~ '/') { 316 if ($what =~ m#^(\/?(?:[\w\-]+\/?){1,2})#) { 317 $cur_part = "Symbols under $1"; 318 $cur_part =~ s,/$,,; 319 } 320 } 321 322 if ($cur_part ne "" && $part ne $cur_part) { 323 $part = $cur_part; 324 my $bar = $part; 325 $bar =~ s/./-/g; 326 print "$part\n$bar\n\n"; 327 } 328 329 printf ".. _%s:\n\n", $data{$what}->{label}; 330 331 my @names = split /, /,$w; 332 my $len = 0; 333 334 foreach my $name (@names) { 335 $name =~ s/$symbols/\\$1/g; 336 $name = "**$name**"; 337 $len = length($name) if (length($name) > $len); 338 } 339 340 print "+-" . "-" x $len . "-+\n"; 341 foreach my $name (@names) { 342 printf "| %s", $name . " " x ($len - length($name)) . " |\n"; 343 print "+-" . "-" x $len . "-+\n"; 344 } 345 346 print "\n"; 347 } 348 349 for (my $i = 0; $i < scalar(@filepath); $i++) { 350 my $path = $filepath[$i]; 351 my $f = $file[$i]; 352 353 $path =~ s,.*/(.*/.*),$1,;; 354 $path =~ s,[/\-],_,g;; 355 my $fileref = "abi_file_".$path; 356 357 if ($type eq "File") { 358 print ".. _$fileref:\n\n"; 359 } else { 360 print "Defined on file :ref:`$f <$fileref>`\n\n"; 361 } 362 } 363 364 if ($type eq "File") { 365 my $bar = $w; 366 $bar =~ s/./-/g; 367 print "$w\n$bar\n\n"; 368 } 369 370 my $desc = ""; 371 $desc = $data{$what}->{description} if (defined($data{$what}->{description})); 372 $desc =~ s/\s+$/\n/; 373 374 if (!($desc =~ /^\s*$/)) { 375 if ($description_is_rst) { 376 # Remove title markups from the description 377 # Having titles inside ABI files will only work if extra 378 # care would be taken in order to strictly follow the same 379 # level order for each markup. 380 $desc =~ s/\n[\-\*\=\^\~]+\n/\n\n/g; 381 382 # Enrich text by creating cross-references 383 384 my $new_desc = ""; 385 my $init_indent = -1; 386 my $literal_indent = -1; 387 388 open(my $fh, "+<", \$desc); 389 while (my $d = <$fh>) { 390 my $indent = $d =~ m/^(\s+)/; 391 my $spaces = length($indent); 392 $init_indent = $indent if ($init_indent < 0); 393 if ($literal_indent >= 0) { 394 if ($spaces > $literal_indent) { 395 $new_desc .= $d; 396 next; 397 } else { 398 $literal_indent = -1; 399 } 400 } else { 401 if ($d =~ /()::$/ && !($d =~ /^\s*\.\./)) { 402 $literal_indent = $spaces; 403 } 404 } 405 406 $d =~ s,Documentation/(?!devicetree)(\S+)\.rst,:doc:`/$1`,g; 407 408 my @matches = $d =~ m,Documentation/ABI/([\w\/\-]+),g; 409 foreach my $f (@matches) { 410 my $xref = $f; 411 my $path = $f; 412 $path =~ s,.*/(.*/.*),$1,;; 413 $path =~ s,[/\-],_,g;; 414 $xref .= " <abi_file_" . $path . ">"; 415 $d =~ s,\bDocumentation/ABI/$f\b,:ref:`$xref`,g; 416 } 417 418 # Seek for cross reference symbols like /sys/... 419 @matches = $d =~ m/$xref_match/g; 420 421 foreach my $s (@matches) { 422 next if (!($s =~ m,/,)); 423 if (defined($data{$s}) && defined($data{$s}->{label})) { 424 my $xref = $s; 425 426 $xref =~ s/$symbols/\\$1/g; 427 $xref = ":ref:`$xref <" . $data{$s}->{label} . ">`"; 428 429 $d =~ s,$start$s$bondary,$1$xref$2,g; 430 } 431 } 432 $new_desc .= $d; 433 } 434 close $fh; 435 436 437 print "$new_desc\n\n"; 438 } else { 439 $desc =~ s/^\s+//; 440 441 # Remove title markups from the description, as they won't work 442 $desc =~ s/\n[\-\*\=\^\~]+\n/\n\n/g; 443 444 if ($desc =~ m/\:\n/ || $desc =~ m/\n[\t ]+/ || $desc =~ m/[\x00-\x08\x0b-\x1f\x7b-\xff]/) { 445 # put everything inside a code block 446 $desc =~ s/\n/\n /g; 447 448 print "::\n\n"; 449 print " $desc\n\n"; 450 } else { 451 # Escape any special chars from description 452 $desc =~s/([\x00-\x08\x0b-\x1f\x21-\x2a\x2d\x2f\x3c-\x40\x5c\x5e-\x60\x7b-\xff])/\\$1/g; 453 print "$desc\n\n"; 454 } 455 } 456 } else { 457 print "DESCRIPTION MISSING for $what\n\n" if (!$data{$what}->{is_file}); 458 } 459 460 if ($data{$what}->{symbols}) { 461 printf "Has the following ABI:\n\n"; 462 463 foreach my $content(@{$data{$what}->{symbols}}) { 464 my $label = $data{$symbols{$content}->{xref}}->{label}; 465 466 # Escape special chars from content 467 $content =~s/([\x00-\x1f\x21-\x2f\x3a-\x40\x7b-\xff])/\\$1/g; 468 469 print "- :ref:`$content <$label>`\n\n"; 470 } 471 } 472 473 if (defined($data{$what}->{users})) { 474 my $users = $data{$what}->{users}; 475 476 $users =~ s/\n/\n\t/g; 477 printf "Users:\n\t%s\n\n", $users if ($users ne ""); 478 } 479 480 } 481} 482 483# 484# Searches for ABI symbols 485# 486sub search_symbols { 487 foreach my $what (sort keys %data) { 488 next if (!($what =~ m/($arg)/)); 489 490 my $type = $data{$what}->{type}; 491 next if ($type eq "File"); 492 493 my $file = $data{$what}->{filepath}; 494 495 my $bar = $what; 496 $bar =~ s/./-/g; 497 498 print "\n$what\n$bar\n\n"; 499 500 my $kernelversion = $data{$what}->{kernelversion} if (defined($data{$what}->{kernelversion})); 501 my $contact = $data{$what}->{contact} if (defined($data{$what}->{contact})); 502 my $users = $data{$what}->{users} if (defined($data{$what}->{users})); 503 my $date = $data{$what}->{date} if (defined($data{$what}->{date})); 504 my $desc = $data{$what}->{description} if (defined($data{$what}->{description})); 505 506 $kernelversion =~ s/^\s+// if ($kernelversion); 507 $contact =~ s/^\s+// if ($contact); 508 if ($users) { 509 $users =~ s/^\s+//; 510 $users =~ s/\n//g; 511 } 512 $date =~ s/^\s+// if ($date); 513 $desc =~ s/^\s+// if ($desc); 514 515 printf "Kernel version:\t\t%s\n", $kernelversion if ($kernelversion); 516 printf "Date:\t\t\t%s\n", $date if ($date); 517 printf "Contact:\t\t%s\n", $contact if ($contact); 518 printf "Users:\t\t\t%s\n", $users if ($users); 519 print "Defined on file(s):\t$file\n\n"; 520 print "Description:\n\n$desc"; 521 } 522} 523 524# Ensure that the prefix will always end with a slash 525# While this is not needed for find, it makes the patch nicer 526# with --enable-lineno 527$prefix =~ s,/?$,/,; 528 529# 530# Parses all ABI files located at $prefix dir 531# 532find({wanted =>\&parse_abi, no_chdir => 1}, $prefix); 533 534print STDERR Data::Dumper->Dump([\%data], [qw(*data)]) if ($debug); 535 536# 537# Handles the command 538# 539if ($cmd eq "search") { 540 search_symbols; 541} else { 542 if ($cmd eq "rest") { 543 output_rest; 544 } 545 546 # Warn about duplicated ABI entries 547 foreach my $what(sort keys %symbols) { 548 my @files = @{$symbols{$what}->{file}}; 549 550 next if (scalar(@files) == 1); 551 552 printf STDERR "Warning: $what is defined %d times: @files\n", 553 scalar(@files); 554 } 555} 556 557__END__ 558 559=head1 NAME 560 561abi_book.pl - parse the Linux ABI files and produce a ReST book. 562 563=head1 SYNOPSIS 564 565B<abi_book.pl> [--debug] [--enable-lineno] [--man] [--help] 566 [--(no-)rst-source] [--dir=<dir>] <COMAND> [<ARGUMENT>] 567 568Where <COMMAND> can be: 569 570=over 8 571 572B<search> [SEARCH_REGEX] - search for [SEARCH_REGEX] inside ABI 573 574B<rest> - output the ABI in ReST markup language 575 576B<validate> - validate the ABI contents 577 578=back 579 580=head1 OPTIONS 581 582=over 8 583 584=item B<--dir> 585 586Changes the location of the ABI search. By default, it uses 587the Documentation/ABI directory. 588 589=item B<--rst-source> and B<--no-rst-source> 590 591The input file may be using ReST syntax or not. Those two options allow 592selecting between a rst-compliant source ABI (--rst-source), or a 593plain text that may be violating ReST spec, so it requres some escaping 594logic (--no-rst-source). 595 596=item B<--enable-lineno> 597 598Enable output of #define LINENO lines. 599 600=item B<--debug> 601 602Put the script in verbose mode, useful for debugging. Can be called multiple 603times, to increase verbosity. 604 605=item B<--help> 606 607Prints a brief help message and exits. 608 609=item B<--man> 610 611Prints the manual page and exits. 612 613=back 614 615=head1 DESCRIPTION 616 617Parse the Linux ABI files from ABI DIR (usually located at Documentation/ABI), 618allowing to search for ABI symbols or to produce a ReST book containing 619the Linux ABI documentation. 620 621=head1 EXAMPLES 622 623Search for all stable symbols with the word "usb": 624 625=over 8 626 627$ scripts/get_abi.pl search usb --dir Documentation/ABI/stable 628 629=back 630 631Search for all symbols that match the regex expression "usb.*cap": 632 633=over 8 634 635$ scripts/get_abi.pl search usb.*cap 636 637=back 638 639Output all obsoleted symbols in ReST format 640 641=over 8 642 643$ scripts/get_abi.pl rest --dir Documentation/ABI/obsolete 644 645=back 646 647=head1 BUGS 648 649Report bugs to Mauro Carvalho Chehab <mchehab+samsung@kernel.org> 650 651=head1 COPYRIGHT 652 653Copyright (c) 2016-2019 by Mauro Carvalho Chehab <mchehab+samsung@kernel.org>. 654 655License GPLv2: GNU GPL version 2 <http://gnu.org/licenses/gpl.html>. 656 657This is free software: you are free to change and redistribute it. 658There is NO WARRANTY, to the extent permitted by law. 659 660=cut 661