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