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 285my $bondary = qr { (?<![\w\/\`\{])(?=[\w\/\`\{])|(?<=[\w\/\`\{])(?![\w\/\`\{]) }x; 286 287sub output_rest { 288 create_labels(); 289 290 my $part = ""; 291 292 foreach my $what (sort { 293 ($data{$a}->{type} eq "File") cmp ($data{$b}->{type} eq "File") || 294 $a cmp $b 295 } keys %data) { 296 my $type = $data{$what}->{type}; 297 298 my @file = split / /, $data{$what}->{file}; 299 my @filepath = split / /, $data{$what}->{filepath}; 300 301 if ($enable_lineno) { 302 printf "#define LINENO %s%s#%s\n\n", 303 $prefix, $file[0], 304 $data{$what}->{line_no}; 305 } 306 307 my $w = $what; 308 $w =~ s/([\(\)\_\-\*\=\^\~\\])/\\$1/g; 309 310 if ($type ne "File") { 311 my $cur_part = $what; 312 if ($what =~ '/') { 313 if ($what =~ m#^(\/?(?:[\w\-]+\/?){1,2})#) { 314 $cur_part = "Symbols under $1"; 315 $cur_part =~ s,/$,,; 316 } 317 } 318 319 if ($cur_part ne "" && $part ne $cur_part) { 320 $part = $cur_part; 321 my $bar = $part; 322 $bar =~ s/./-/g; 323 print "$part\n$bar\n\n"; 324 } 325 326 printf ".. _%s:\n\n", $data{$what}->{label}; 327 328 my @names = split /, /,$w; 329 my $len = 0; 330 331 foreach my $name (@names) { 332 $name = "**$name**"; 333 $len = length($name) if (length($name) > $len); 334 } 335 336 print "+-" . "-" x $len . "-+\n"; 337 foreach my $name (@names) { 338 printf "| %s", $name . " " x ($len - length($name)) . " |\n"; 339 print "+-" . "-" x $len . "-+\n"; 340 } 341 342 print "\n"; 343 } 344 345 for (my $i = 0; $i < scalar(@filepath); $i++) { 346 my $path = $filepath[$i]; 347 my $f = $file[$i]; 348 349 $path =~ s,.*/(.*/.*),$1,;; 350 $path =~ s,[/\-],_,g;; 351 my $fileref = "abi_file_".$path; 352 353 if ($type eq "File") { 354 print ".. _$fileref:\n\n"; 355 } else { 356 print "Defined on file :ref:`$f <$fileref>`\n\n"; 357 } 358 } 359 360 if ($type eq "File") { 361 my $bar = $w; 362 $bar =~ s/./-/g; 363 print "$w\n$bar\n\n"; 364 } 365 366 my $desc = ""; 367 $desc = $data{$what}->{description} if (defined($data{$what}->{description})); 368 $desc =~ s/\s+$/\n/; 369 370 if (!($desc =~ /^\s*$/)) { 371 if ($description_is_rst) { 372 # Remove title markups from the description 373 # Having titles inside ABI files will only work if extra 374 # care would be taken in order to strictly follow the same 375 # level order for each markup. 376 $desc =~ s/\n[\-\*\=\^\~]+\n/\n\n/g; 377 378 # Enrich text by creating cross-references 379 380 $desc =~ s,Documentation/(?!devicetree)(\S+)\.rst,:doc:`/$1`,g; 381 382 my @matches = $desc =~ m,Documentation/ABI/([\w\/\-]+),; 383 foreach my $f (@matches) { 384 my $xref = $f; 385 my $path = $f; 386 $path =~ s,.*/(.*/.*),$1,;; 387 $path =~ s,[/\-],_,g;; 388 $xref .= " <abi_file_" . $path . ">"; 389 $desc =~ s,\bDocumentation/ABI/$f\b,:ref:`$xref`,g; 390 } 391 392 @matches = $desc =~ m,$bondary(/sys/[^\s\.\,\;\:\*\s\`\'\(\)]+)$bondary,; 393 394 foreach my $s (@matches) { 395 if (defined($data{$s}) && defined($data{$s}->{label})) { 396 my $xref = $s; 397 398 $xref =~ s/([\x00-\x1f\x21-\x2f\x3a-\x40\x7b-\xff])/\\$1/g; 399 $xref = ":ref:`$xref <" . $data{$s}->{label} . ">`"; 400 401 $desc =~ s,$bondary$s$bondary,$xref,g; 402 } 403 } 404 405 print "$desc\n\n"; 406 } else { 407 $desc =~ s/^\s+//; 408 409 # Remove title markups from the description, as they won't work 410 $desc =~ s/\n[\-\*\=\^\~]+\n/\n\n/g; 411 412 if ($desc =~ m/\:\n/ || $desc =~ m/\n[\t ]+/ || $desc =~ m/[\x00-\x08\x0b-\x1f\x7b-\xff]/) { 413 # put everything inside a code block 414 $desc =~ s/\n/\n /g; 415 416 print "::\n\n"; 417 print " $desc\n\n"; 418 } else { 419 # Escape any special chars from description 420 $desc =~s/([\x00-\x08\x0b-\x1f\x21-\x2a\x2d\x2f\x3c-\x40\x5c\x5e-\x60\x7b-\xff])/\\$1/g; 421 print "$desc\n\n"; 422 } 423 } 424 } else { 425 print "DESCRIPTION MISSING for $what\n\n" if (!$data{$what}->{is_file}); 426 } 427 428 if ($data{$what}->{symbols}) { 429 printf "Has the following ABI:\n\n"; 430 431 foreach my $content(@{$data{$what}->{symbols}}) { 432 my $label = $data{$symbols{$content}->{xref}}->{label}; 433 434 # Escape special chars from content 435 $content =~s/([\x00-\x1f\x21-\x2f\x3a-\x40\x7b-\xff])/\\$1/g; 436 437 print "- :ref:`$content <$label>`\n\n"; 438 } 439 } 440 441 if (defined($data{$what}->{users})) { 442 my $users = $data{$what}->{users}; 443 444 $users =~ s/\n/\n\t/g; 445 printf "Users:\n\t%s\n\n", $users if ($users ne ""); 446 } 447 448 } 449} 450 451# 452# Searches for ABI symbols 453# 454sub search_symbols { 455 foreach my $what (sort keys %data) { 456 next if (!($what =~ m/($arg)/)); 457 458 my $type = $data{$what}->{type}; 459 next if ($type eq "File"); 460 461 my $file = $data{$what}->{filepath}; 462 463 my $bar = $what; 464 $bar =~ s/./-/g; 465 466 print "\n$what\n$bar\n\n"; 467 468 my $kernelversion = $data{$what}->{kernelversion} if (defined($data{$what}->{kernelversion})); 469 my $contact = $data{$what}->{contact} if (defined($data{$what}->{contact})); 470 my $users = $data{$what}->{users} if (defined($data{$what}->{users})); 471 my $date = $data{$what}->{date} if (defined($data{$what}->{date})); 472 my $desc = $data{$what}->{description} if (defined($data{$what}->{description})); 473 474 $kernelversion =~ s/^\s+// if ($kernelversion); 475 $contact =~ s/^\s+// if ($contact); 476 if ($users) { 477 $users =~ s/^\s+//; 478 $users =~ s/\n//g; 479 } 480 $date =~ s/^\s+// if ($date); 481 $desc =~ s/^\s+// if ($desc); 482 483 printf "Kernel version:\t\t%s\n", $kernelversion if ($kernelversion); 484 printf "Date:\t\t\t%s\n", $date if ($date); 485 printf "Contact:\t\t%s\n", $contact if ($contact); 486 printf "Users:\t\t\t%s\n", $users if ($users); 487 print "Defined on file(s):\t$file\n\n"; 488 print "Description:\n\n$desc"; 489 } 490} 491 492# Ensure that the prefix will always end with a slash 493# While this is not needed for find, it makes the patch nicer 494# with --enable-lineno 495$prefix =~ s,/?$,/,; 496 497# 498# Parses all ABI files located at $prefix dir 499# 500find({wanted =>\&parse_abi, no_chdir => 1}, $prefix); 501 502print STDERR Data::Dumper->Dump([\%data], [qw(*data)]) if ($debug); 503 504# 505# Handles the command 506# 507if ($cmd eq "search") { 508 search_symbols; 509} else { 510 if ($cmd eq "rest") { 511 output_rest; 512 } 513 514 # Warn about duplicated ABI entries 515 foreach my $what(sort keys %symbols) { 516 my @files = @{$symbols{$what}->{file}}; 517 518 next if (scalar(@files) == 1); 519 520 printf STDERR "Warning: $what is defined %d times: @files\n", 521 scalar(@files); 522 } 523} 524 525__END__ 526 527=head1 NAME 528 529abi_book.pl - parse the Linux ABI files and produce a ReST book. 530 531=head1 SYNOPSIS 532 533B<abi_book.pl> [--debug] [--enable-lineno] [--man] [--help] 534 [--(no-)rst-source] [--dir=<dir>] <COMAND> [<ARGUMENT>] 535 536Where <COMMAND> can be: 537 538=over 8 539 540B<search> [SEARCH_REGEX] - search for [SEARCH_REGEX] inside ABI 541 542B<rest> - output the ABI in ReST markup language 543 544B<validate> - validate the ABI contents 545 546=back 547 548=head1 OPTIONS 549 550=over 8 551 552=item B<--dir> 553 554Changes the location of the ABI search. By default, it uses 555the Documentation/ABI directory. 556 557=item B<--rst-source> and B<--no-rst-source> 558 559The input file may be using ReST syntax or not. Those two options allow 560selecting between a rst-compliant source ABI (--rst-source), or a 561plain text that may be violating ReST spec, so it requres some escaping 562logic (--no-rst-source). 563 564=item B<--enable-lineno> 565 566Enable output of #define LINENO lines. 567 568=item B<--debug> 569 570Put the script in verbose mode, useful for debugging. Can be called multiple 571times, to increase verbosity. 572 573=item B<--help> 574 575Prints a brief help message and exits. 576 577=item B<--man> 578 579Prints the manual page and exits. 580 581=back 582 583=head1 DESCRIPTION 584 585Parse the Linux ABI files from ABI DIR (usually located at Documentation/ABI), 586allowing to search for ABI symbols or to produce a ReST book containing 587the Linux ABI documentation. 588 589=head1 EXAMPLES 590 591Search for all stable symbols with the word "usb": 592 593=over 8 594 595$ scripts/get_abi.pl search usb --dir Documentation/ABI/stable 596 597=back 598 599Search for all symbols that match the regex expression "usb.*cap": 600 601=over 8 602 603$ scripts/get_abi.pl search usb.*cap 604 605=back 606 607Output all obsoleted symbols in ReST format 608 609=over 8 610 611$ scripts/get_abi.pl rest --dir Documentation/ABI/obsolete 612 613=back 614 615=head1 BUGS 616 617Report bugs to Mauro Carvalho Chehab <mchehab+samsung@kernel.org> 618 619=head1 COPYRIGHT 620 621Copyright (c) 2016-2019 by Mauro Carvalho Chehab <mchehab+samsung@kernel.org>. 622 623License GPLv2: GNU GPL version 2 <http://gnu.org/licenses/gpl.html>. 624 625This is free software: you are free to change and redistribute it. 626There is NO WARRANTY, to the extent permitted by law. 627 628=cut 629