get_abi.pl (25923B)
1#!/usr/bin/env perl 2# SPDX-License-Identifier: GPL-2.0 3 4BEGIN { $Pod::Usage::Formatter = 'Pod::Text::Termcap'; } 5 6use strict; 7use warnings; 8use utf8; 9use Pod::Usage qw(pod2usage); 10use Getopt::Long; 11use File::Find; 12use IO::Handle; 13use Fcntl ':mode'; 14use Cwd 'abs_path'; 15use Data::Dumper; 16 17my $help = 0; 18my $hint = 0; 19my $man = 0; 20my $debug = 0; 21my $enable_lineno = 0; 22my $show_warnings = 1; 23my $prefix="Documentation/ABI"; 24my $sysfs_prefix="/sys"; 25my $search_string; 26 27# Debug options 28my $dbg_what_parsing = 1; 29my $dbg_what_open = 2; 30my $dbg_dump_abi_structs = 4; 31my $dbg_undefined = 8; 32 33$Data::Dumper::Indent = 1; 34$Data::Dumper::Terse = 1; 35 36# 37# If true, assumes that the description is formatted with ReST 38# 39my $description_is_rst = 1; 40 41GetOptions( 42 "debug=i" => \$debug, 43 "enable-lineno" => \$enable_lineno, 44 "rst-source!" => \$description_is_rst, 45 "dir=s" => \$prefix, 46 'help|?' => \$help, 47 "show-hints" => \$hint, 48 "search-string=s" => \$search_string, 49 man => \$man 50) or pod2usage(2); 51 52pod2usage(1) if $help; 53pod2usage(-exitstatus => 0, -noperldoc, -verbose => 2) if $man; 54 55pod2usage(2) if (scalar @ARGV < 1 || @ARGV > 2); 56 57my ($cmd, $arg) = @ARGV; 58 59pod2usage(2) if ($cmd ne "search" && $cmd ne "rest" && $cmd ne "validate" && $cmd ne "undefined"); 60pod2usage(2) if ($cmd eq "search" && !$arg); 61 62require Data::Dumper if ($debug & $dbg_dump_abi_structs); 63 64my %data; 65my %symbols; 66 67# 68# Displays an error message, printing file name and line 69# 70sub parse_error($$$$) { 71 my ($file, $ln, $msg, $data) = @_; 72 73 return if (!$show_warnings); 74 75 $data =~ s/\s+$/\n/; 76 77 print STDERR "Warning: file $file#$ln:\n\t$msg"; 78 79 if ($data ne "") { 80 print STDERR ". Line\n\t\t$data"; 81 } else { 82 print STDERR "\n"; 83 } 84} 85 86# 87# Parse an ABI file, storing its contents at %data 88# 89sub parse_abi { 90 my $file = $File::Find::name; 91 92 my $mode = (stat($file))[2]; 93 return if ($mode & S_IFDIR); 94 return if ($file =~ m,/README,); 95 return if ($file =~ m,/\.,); 96 97 my $name = $file; 98 $name =~ s,.*/,,; 99 100 my $fn = $file; 101 $fn =~ s,Documentation/ABI/,,; 102 103 my $nametag = "File $fn"; 104 $data{$nametag}->{what} = "File $name"; 105 $data{$nametag}->{type} = "File"; 106 $data{$nametag}->{file} = $name; 107 $data{$nametag}->{filepath} = $file; 108 $data{$nametag}->{is_file} = 1; 109 $data{$nametag}->{line_no} = 1; 110 111 my $type = $file; 112 $type =~ s,.*/(.*)/.*,$1,; 113 114 my $what; 115 my $new_what; 116 my $tag = ""; 117 my $ln; 118 my $xrefs; 119 my $space; 120 my @labels; 121 my $label = ""; 122 123 print STDERR "Opening $file\n" if ($debug & $dbg_what_open); 124 open IN, $file; 125 while(<IN>) { 126 $ln++; 127 if (m/^(\S+)(:\s*)(.*)/i) { 128 my $new_tag = lc($1); 129 my $sep = $2; 130 my $content = $3; 131 132 if (!($new_tag =~ m/(what|where|date|kernelversion|contact|description|users)/)) { 133 if ($tag eq "description") { 134 # New "tag" is actually part of 135 # description. Don't consider it a tag 136 $new_tag = ""; 137 } elsif ($tag ne "") { 138 parse_error($file, $ln, "tag '$tag' is invalid", $_); 139 } 140 } 141 142 # Invalid, but it is a common mistake 143 if ($new_tag eq "where") { 144 parse_error($file, $ln, "tag 'Where' is invalid. Should be 'What:' instead", ""); 145 $new_tag = "what"; 146 } 147 148 if ($new_tag =~ m/what/) { 149 $space = ""; 150 $content =~ s/[,.;]$//; 151 152 push @{$symbols{$content}->{file}}, " $file:" . ($ln - 1); 153 154 if ($tag =~ m/what/) { 155 $what .= "\xac" . $content; 156 } else { 157 if ($what) { 158 parse_error($file, $ln, "What '$what' doesn't have a description", "") if (!$data{$what}->{description}); 159 160 foreach my $w(split /\xac/, $what) { 161 $symbols{$w}->{xref} = $what; 162 }; 163 } 164 165 $what = $content; 166 $label = $content; 167 $new_what = 1; 168 } 169 push @labels, [($content, $label)]; 170 $tag = $new_tag; 171 172 push @{$data{$nametag}->{symbols}}, $content if ($data{$nametag}->{what}); 173 next; 174 } 175 176 if ($tag ne "" && $new_tag) { 177 $tag = $new_tag; 178 179 if ($new_what) { 180 @{$data{$what}->{label_list}} = @labels if ($data{$nametag}->{what}); 181 @labels = (); 182 $label = ""; 183 $new_what = 0; 184 185 $data{$what}->{type} = $type; 186 if (!defined($data{$what}->{file})) { 187 $data{$what}->{file} = $name; 188 $data{$what}->{filepath} = $file; 189 } else { 190 $data{$what}->{description} .= "\n\n" if (defined($data{$what}->{description})); 191 if ($name ne $data{$what}->{file}) { 192 $data{$what}->{file} .= " " . $name; 193 $data{$what}->{filepath} .= " " . $file; 194 } 195 } 196 print STDERR "\twhat: $what\n" if ($debug & $dbg_what_parsing); 197 $data{$what}->{line_no} = $ln; 198 } else { 199 $data{$what}->{line_no} = $ln if (!defined($data{$what}->{line_no})); 200 } 201 202 if (!$what) { 203 parse_error($file, $ln, "'What:' should come first:", $_); 204 next; 205 } 206 if ($new_tag eq "description") { 207 $sep =~ s,:, ,; 208 $content = ' ' x length($new_tag) . $sep . $content; 209 while ($content =~ s/\t+/' ' x (length($&) * 8 - length($`) % 8)/e) {} 210 if ($content =~ m/^(\s*)(\S.*)$/) { 211 # Preserve initial spaces for the first line 212 $space = $1; 213 $content = "$2\n"; 214 $data{$what}->{$tag} .= $content; 215 } else { 216 undef($space); 217 } 218 219 } else { 220 $data{$what}->{$tag} = $content; 221 } 222 next; 223 } 224 } 225 226 # Store any contents before tags at the database 227 if (!$tag && $data{$nametag}->{what}) { 228 $data{$nametag}->{description} .= $_; 229 next; 230 } 231 232 if ($tag eq "description") { 233 my $content = $_; 234 while ($content =~ s/\t+/' ' x (length($&) * 8 - length($`) % 8)/e) {} 235 if (m/^\s*\n/) { 236 $data{$what}->{$tag} .= "\n"; 237 next; 238 } 239 240 if (!defined($space)) { 241 # Preserve initial spaces for the first line 242 if ($content =~ m/^(\s*)(\S.*)$/) { 243 $space = $1; 244 $content = "$2\n"; 245 } 246 } else { 247 $space = "" if (!($content =~ s/^($space)//)); 248 } 249 $data{$what}->{$tag} .= $content; 250 251 next; 252 } 253 if (m/^\s*(.*)/) { 254 $data{$what}->{$tag} .= "\n$1"; 255 $data{$what}->{$tag} =~ s/\n+$//; 256 next; 257 } 258 259 # Everything else is error 260 parse_error($file, $ln, "Unexpected content", $_); 261 } 262 $data{$nametag}->{description} =~ s/^\n+// if ($data{$nametag}->{description}); 263 if ($what) { 264 parse_error($file, $ln, "What '$what' doesn't have a description", "") if (!$data{$what}->{description}); 265 266 foreach my $w(split /\xac/,$what) { 267 $symbols{$w}->{xref} = $what; 268 }; 269 } 270 close IN; 271} 272 273sub create_labels { 274 my %labels; 275 276 foreach my $what (keys %data) { 277 next if ($data{$what}->{file} eq "File"); 278 279 foreach my $p (@{$data{$what}->{label_list}}) { 280 my ($content, $label) = @{$p}; 281 $label = "abi_" . $label . " "; 282 $label =~ tr/A-Z/a-z/; 283 284 # Convert special chars to "_" 285 $label =~s/([\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xff])/_/g; 286 $label =~ s,_+,_,g; 287 $label =~ s,_$,,; 288 289 # Avoid duplicated labels 290 while (defined($labels{$label})) { 291 my @chars = ("A".."Z", "a".."z"); 292 $label .= $chars[rand @chars]; 293 } 294 $labels{$label} = 1; 295 296 $data{$what}->{label} = $label; 297 298 # only one label is enough 299 last; 300 } 301 } 302} 303 304# 305# Outputs the book on ReST format 306# 307 308# \b doesn't work well with paths. So, we need to define something else: 309# Boundaries are punct characters, spaces and end-of-line 310my $start = qr {(^|\s|\() }x; 311my $bondary = qr { ([,.:;\)\s]|\z) }x; 312my $xref_match = qr { $start(\/(sys|config|proc|dev|kvd)\/[^,.:;\)\s]+)$bondary }x; 313my $symbols = qr { ([\x01-\x08\x0e-\x1f\x21-\x2f\x3a-\x40\x7b-\xff]) }x; 314 315sub output_rest { 316 create_labels(); 317 318 my $part = ""; 319 320 foreach my $what (sort { 321 ($data{$a}->{type} eq "File") cmp ($data{$b}->{type} eq "File") || 322 $a cmp $b 323 } keys %data) { 324 my $type = $data{$what}->{type}; 325 326 my @file = split / /, $data{$what}->{file}; 327 my @filepath = split / /, $data{$what}->{filepath}; 328 329 if ($enable_lineno) { 330 printf ".. LINENO %s%s#%s\n\n", 331 $prefix, $file[0], 332 $data{$what}->{line_no}; 333 } 334 335 my $w = $what; 336 337 if ($type ne "File") { 338 my $cur_part = $what; 339 if ($what =~ '/') { 340 if ($what =~ m#^(\/?(?:[\w\-]+\/?){1,2})#) { 341 $cur_part = "Symbols under $1"; 342 $cur_part =~ s,/$,,; 343 } 344 } 345 346 if ($cur_part ne "" && $part ne $cur_part) { 347 $part = $cur_part; 348 my $bar = $part; 349 $bar =~ s/./-/g; 350 print "$part\n$bar\n\n"; 351 } 352 353 printf ".. _%s:\n\n", $data{$what}->{label}; 354 355 my @names = split /\xac/,$w; 356 my $len = 0; 357 358 foreach my $name (@names) { 359 $name =~ s/$symbols/\\$1/g; 360 $name = "**$name**"; 361 $len = length($name) if (length($name) > $len); 362 } 363 364 print "+-" . "-" x $len . "-+\n"; 365 foreach my $name (@names) { 366 printf "| %s", $name . " " x ($len - length($name)) . " |\n"; 367 print "+-" . "-" x $len . "-+\n"; 368 } 369 370 print "\n"; 371 } 372 373 for (my $i = 0; $i < scalar(@filepath); $i++) { 374 my $path = $filepath[$i]; 375 my $f = $file[$i]; 376 377 $path =~ s,.*/(.*/.*),$1,;; 378 $path =~ s,[/\-],_,g;; 379 my $fileref = "abi_file_".$path; 380 381 if ($type eq "File") { 382 print ".. _$fileref:\n\n"; 383 } else { 384 print "Defined on file :ref:`$f <$fileref>`\n\n"; 385 } 386 } 387 388 if ($type eq "File") { 389 my $bar = $w; 390 $bar =~ s/./-/g; 391 print "$w\n$bar\n\n"; 392 } 393 394 my $desc = ""; 395 $desc = $data{$what}->{description} if (defined($data{$what}->{description})); 396 $desc =~ s/\s+$/\n/; 397 398 if (!($desc =~ /^\s*$/)) { 399 if ($description_is_rst) { 400 # Remove title markups from the description 401 # Having titles inside ABI files will only work if extra 402 # care would be taken in order to strictly follow the same 403 # level order for each markup. 404 $desc =~ s/\n[\-\*\=\^\~]+\n/\n\n/g; 405 406 # Enrich text by creating cross-references 407 408 my $new_desc = ""; 409 my $init_indent = -1; 410 my $literal_indent = -1; 411 412 open(my $fh, "+<", \$desc); 413 while (my $d = <$fh>) { 414 my $indent = $d =~ m/^(\s+)/; 415 my $spaces = length($indent); 416 $init_indent = $indent if ($init_indent < 0); 417 if ($literal_indent >= 0) { 418 if ($spaces > $literal_indent) { 419 $new_desc .= $d; 420 next; 421 } else { 422 $literal_indent = -1; 423 } 424 } else { 425 if ($d =~ /()::$/ && !($d =~ /^\s*\.\./)) { 426 $literal_indent = $spaces; 427 } 428 } 429 430 $d =~ s,Documentation/(?!devicetree)(\S+)\.rst,:doc:`/$1`,g; 431 432 my @matches = $d =~ m,Documentation/ABI/([\w\/\-]+),g; 433 foreach my $f (@matches) { 434 my $xref = $f; 435 my $path = $f; 436 $path =~ s,.*/(.*/.*),$1,;; 437 $path =~ s,[/\-],_,g;; 438 $xref .= " <abi_file_" . $path . ">"; 439 $d =~ s,\bDocumentation/ABI/$f\b,:ref:`$xref`,g; 440 } 441 442 # Seek for cross reference symbols like /sys/... 443 @matches = $d =~ m/$xref_match/g; 444 445 foreach my $s (@matches) { 446 next if (!($s =~ m,/,)); 447 if (defined($data{$s}) && defined($data{$s}->{label})) { 448 my $xref = $s; 449 450 $xref =~ s/$symbols/\\$1/g; 451 $xref = ":ref:`$xref <" . $data{$s}->{label} . ">`"; 452 453 $d =~ s,$start$s$bondary,$1$xref$2,g; 454 } 455 } 456 $new_desc .= $d; 457 } 458 close $fh; 459 460 461 print "$new_desc\n\n"; 462 } else { 463 $desc =~ s/^\s+//; 464 465 # Remove title markups from the description, as they won't work 466 $desc =~ s/\n[\-\*\=\^\~]+\n/\n\n/g; 467 468 if ($desc =~ m/\:\n/ || $desc =~ m/\n[\t ]+/ || $desc =~ m/[\x00-\x08\x0b-\x1f\x7b-\xff]/) { 469 # put everything inside a code block 470 $desc =~ s/\n/\n /g; 471 472 print "::\n\n"; 473 print " $desc\n\n"; 474 } else { 475 # Escape any special chars from description 476 $desc =~s/([\x00-\x08\x0b-\x1f\x21-\x2a\x2d\x2f\x3c-\x40\x5c\x5e-\x60\x7b-\xff])/\\$1/g; 477 print "$desc\n\n"; 478 } 479 } 480 } else { 481 print "DESCRIPTION MISSING for $what\n\n" if (!$data{$what}->{is_file}); 482 } 483 484 if ($data{$what}->{symbols}) { 485 printf "Has the following ABI:\n\n"; 486 487 foreach my $content(@{$data{$what}->{symbols}}) { 488 my $label = $data{$symbols{$content}->{xref}}->{label}; 489 490 # Escape special chars from content 491 $content =~s/([\x00-\x1f\x21-\x2f\x3a-\x40\x7b-\xff])/\\$1/g; 492 493 print "- :ref:`$content <$label>`\n\n"; 494 } 495 } 496 497 if (defined($data{$what}->{users})) { 498 my $users = $data{$what}->{users}; 499 500 $users =~ s/\n/\n\t/g; 501 printf "Users:\n\t%s\n\n", $users if ($users ne ""); 502 } 503 504 } 505} 506 507# 508# Searches for ABI symbols 509# 510sub search_symbols { 511 foreach my $what (sort keys %data) { 512 next if (!($what =~ m/($arg)/)); 513 514 my $type = $data{$what}->{type}; 515 next if ($type eq "File"); 516 517 my $file = $data{$what}->{filepath}; 518 519 $what =~ s/\xac/, /g; 520 my $bar = $what; 521 $bar =~ s/./-/g; 522 523 print "\n$what\n$bar\n\n"; 524 525 my $kernelversion = $data{$what}->{kernelversion} if (defined($data{$what}->{kernelversion})); 526 my $contact = $data{$what}->{contact} if (defined($data{$what}->{contact})); 527 my $users = $data{$what}->{users} if (defined($data{$what}->{users})); 528 my $date = $data{$what}->{date} if (defined($data{$what}->{date})); 529 my $desc = $data{$what}->{description} if (defined($data{$what}->{description})); 530 531 $kernelversion =~ s/^\s+// if ($kernelversion); 532 $contact =~ s/^\s+// if ($contact); 533 if ($users) { 534 $users =~ s/^\s+//; 535 $users =~ s/\n//g; 536 } 537 $date =~ s/^\s+// if ($date); 538 $desc =~ s/^\s+// if ($desc); 539 540 printf "Kernel version:\t\t%s\n", $kernelversion if ($kernelversion); 541 printf "Date:\t\t\t%s\n", $date if ($date); 542 printf "Contact:\t\t%s\n", $contact if ($contact); 543 printf "Users:\t\t\t%s\n", $users if ($users); 544 print "Defined on file(s):\t$file\n\n"; 545 print "Description:\n\n$desc"; 546 } 547} 548 549# Exclude /sys/kernel/debug and /sys/kernel/tracing from the search path 550sub dont_parse_special_attributes { 551 if (($File::Find::dir =~ m,^/sys/kernel,)) { 552 return grep {!/(debug|tracing)/ } @_; 553 } 554 555 if (($File::Find::dir =~ m,^/sys/fs,)) { 556 return grep {!/(pstore|bpf|fuse)/ } @_; 557 } 558 559 return @_ 560} 561 562my %leaf; 563my %aliases; 564my @files; 565my %root; 566 567sub graph_add_file { 568 my $file = shift; 569 my $type = shift; 570 571 my $dir = $file; 572 $dir =~ s,^(.*/).*,$1,; 573 $file =~ s,.*/,,; 574 575 my $name; 576 my $file_ref = \%root; 577 foreach my $edge(split "/", $dir) { 578 $name .= "$edge/"; 579 if (!defined ${$file_ref}{$edge}) { 580 ${$file_ref}{$edge} = { }; 581 } 582 $file_ref = \%{$$file_ref{$edge}}; 583 ${$file_ref}{"__name"} = [ $name ]; 584 } 585 $name .= "$file"; 586 ${$file_ref}{$file} = { 587 "__name" => [ $name ] 588 }; 589 590 return \%{$$file_ref{$file}}; 591} 592 593sub graph_add_link { 594 my $file = shift; 595 my $link = shift; 596 597 # Traverse graph to find the reference 598 my $file_ref = \%root; 599 foreach my $edge(split "/", $file) { 600 $file_ref = \%{$$file_ref{$edge}} || die "Missing node!"; 601 } 602 603 # do a BFS 604 605 my @queue; 606 my %seen; 607 my $st; 608 609 push @queue, $file_ref; 610 $seen{$start}++; 611 612 while (@queue) { 613 my $v = shift @queue; 614 my @child = keys(%{$v}); 615 616 foreach my $c(@child) { 617 next if $seen{$$v{$c}}; 618 next if ($c eq "__name"); 619 620 if (!defined($$v{$c}{"__name"})) { 621 printf STDERR "Error: Couldn't find a non-empty name on a children of $file/.*: "; 622 print STDERR Dumper(%{$v}); 623 exit; 624 } 625 626 # Add new name 627 my $name = @{$$v{$c}{"__name"}}[0]; 628 if ($name =~ s#^$file/#$link/#) { 629 push @{$$v{$c}{"__name"}}, $name; 630 } 631 # Add child to the queue and mark as seen 632 push @queue, $$v{$c}; 633 $seen{$c}++; 634 } 635 } 636} 637 638my $escape_symbols = qr { ([\x01-\x08\x0e-\x1f\x21-\x29\x2b-\x2d\x3a-\x40\x7b-\xfe]) }x; 639sub parse_existing_sysfs { 640 my $file = $File::Find::name; 641 642 my $mode = (lstat($file))[2]; 643 my $abs_file = abs_path($file); 644 645 my @tmp; 646 push @tmp, $file; 647 push @tmp, $abs_file if ($abs_file ne $file); 648 649 foreach my $f(@tmp) { 650 # Ignore cgroup, as this is big and has zero docs under ABI 651 return if ($f =~ m#^/sys/fs/cgroup/#); 652 653 # Ignore firmware as it is documented elsewhere 654 # Either ACPI or under Documentation/devicetree/bindings/ 655 return if ($f =~ m#^/sys/firmware/#); 656 657 # Ignore some sysfs nodes that aren't actually part of ABI 658 return if ($f =~ m#/sections|notes/#); 659 660 # Would need to check at 661 # Documentation/admin-guide/kernel-parameters.txt, but this 662 # is not easily parseable. 663 return if ($f =~ m#/parameters/#); 664 } 665 666 if (S_ISLNK($mode)) { 667 $aliases{$file} = $abs_file; 668 return; 669 } 670 671 return if (S_ISDIR($mode)); 672 673 # Trivial: file is defined exactly the same way at ABI What: 674 return if (defined($data{$file})); 675 return if (defined($data{$abs_file})); 676 677 push @files, graph_add_file($abs_file, "file"); 678} 679 680sub get_leave($) 681{ 682 my $what = shift; 683 my $leave; 684 685 my $l = $what; 686 my $stop = 1; 687 688 $leave = $l; 689 $leave =~ s,/$,,; 690 $leave =~ s,.*/,,; 691 $leave =~ s/[\(\)]//g; 692 693 # $leave is used to improve search performance at 694 # check_undefined_symbols, as the algorithm there can seek 695 # for a small number of "what". It also allows giving a 696 # hint about a leave with the same name somewhere else. 697 # However, there are a few occurences where the leave is 698 # either a wildcard or a number. Just group such cases 699 # altogether. 700 if ($leave =~ m/\.\*/ || $leave eq "" || $leave =~ /\\d/) { 701 $leave = "others"; 702 } 703 704 return $leave; 705} 706 707my @not_found; 708 709sub check_file($$) 710{ 711 my $file_ref = shift; 712 my $names_ref = shift; 713 my @names = @{$names_ref}; 714 my $file = $names[0]; 715 716 my $found_string; 717 718 my $leave = get_leave($file); 719 if (!defined($leaf{$leave})) { 720 $leave = "others"; 721 } 722 my @expr = @{$leaf{$leave}->{expr}}; 723 die ("\rmissing rules for $leave") if (!defined($leaf{$leave})); 724 725 my $path = $file; 726 $path =~ s,(.*/).*,$1,; 727 728 if ($search_string) { 729 return if (!($file =~ m#$search_string#)); 730 $found_string = 1; 731 } 732 733 for (my $i = 0; $i < @names; $i++) { 734 if ($found_string && $hint) { 735 if (!$i) { 736 print STDERR "--> $names[$i]\n"; 737 } else { 738 print STDERR " $names[$i]\n"; 739 } 740 } 741 foreach my $re (@expr) { 742 print STDERR "$names[$i] =~ /^$re\$/\n" if ($debug && $dbg_undefined); 743 if ($names[$i] =~ $re) { 744 return; 745 } 746 } 747 } 748 749 if ($leave ne "others") { 750 my @expr = @{$leaf{"others"}->{expr}}; 751 for (my $i = 0; $i < @names; $i++) { 752 foreach my $re (@expr) { 753 print STDERR "$names[$i] =~ /^$re\$/\n" if ($debug && $dbg_undefined); 754 if ($names[$i] =~ $re) { 755 return; 756 } 757 } 758 } 759 } 760 761 push @not_found, $file if (!$search_string || $found_string); 762 763 if ($hint && (!$search_string || $found_string)) { 764 my $what = $leaf{$leave}->{what}; 765 $what =~ s/\xac/\n\t/g; 766 if ($leave ne "others") { 767 print STDERR "\r more likely regexes:\n\t$what\n"; 768 } else { 769 print STDERR "\r tested regexes:\n\t$what\n"; 770 } 771 } 772} 773 774sub check_undefined_symbols { 775 my $num_files = scalar @files; 776 my $next_i = 0; 777 my $start_time = times; 778 779 @files = sort @files; 780 781 my $last_time = $start_time; 782 783 # When either debug or hint is enabled, there's no sense showing 784 # progress, as the progress will be overriden. 785 if ($hint || ($debug && $dbg_undefined)) { 786 $next_i = $num_files; 787 } 788 789 my $is_console; 790 $is_console = 1 if (-t STDERR); 791 792 for (my $i = 0; $i < $num_files; $i++) { 793 my $file_ref = $files[$i]; 794 my @names = @{$$file_ref{"__name"}}; 795 796 check_file($file_ref, \@names); 797 798 my $cur_time = times; 799 800 if ($i == $next_i || $cur_time > $last_time + 1) { 801 my $percent = $i * 100 / $num_files; 802 803 my $tm = $cur_time - $start_time; 804 my $time = sprintf "%d:%02d", int($tm), 60 * ($tm - int($tm)); 805 806 printf STDERR "\33[2K\r", if ($is_console); 807 printf STDERR "%s: processing sysfs files... %i%%: $names[0]", $time, $percent; 808 printf STDERR "\n", if (!$is_console); 809 STDERR->flush(); 810 811 $next_i = int (($percent + 1) * $num_files / 100); 812 $last_time = $cur_time; 813 } 814 } 815 816 my $cur_time = times; 817 my $tm = $cur_time - $start_time; 818 my $time = sprintf "%d:%02d", int($tm), 60 * ($tm - int($tm)); 819 820 printf STDERR "\33[2K\r", if ($is_console); 821 printf STDERR "%s: processing sysfs files... done\n", $time; 822 823 foreach my $file (@not_found) { 824 print "$file not found.\n"; 825 } 826} 827 828sub undefined_symbols { 829 print STDERR "Reading $sysfs_prefix directory contents..."; 830 find({ 831 wanted =>\&parse_existing_sysfs, 832 preprocess =>\&dont_parse_special_attributes, 833 no_chdir => 1 834 }, $sysfs_prefix); 835 print STDERR "done.\n"; 836 837 $leaf{"others"}->{what} = ""; 838 839 print STDERR "Converting ABI What fields into regexes..."; 840 foreach my $w (sort keys %data) { 841 foreach my $what (split /\xac/,$w) { 842 next if (!($what =~ m/^$sysfs_prefix/)); 843 844 # Convert what into regular expressions 845 846 # Escape dot characters 847 $what =~ s/\./\xf6/g; 848 849 # Temporarily change [0-9]+ type of patterns 850 $what =~ s/\[0\-9\]\+/\xff/g; 851 852 # Temporarily change [\d+-\d+] type of patterns 853 $what =~ s/\[0\-\d+\]/\xff/g; 854 $what =~ s/\[(\d+)\]/\xf4$1\xf5/g; 855 856 # Temporarily change [0-9] type of patterns 857 $what =~ s/\[(\d)\-(\d)\]/\xf4$1-$2\xf5/g; 858 859 # Handle multiple option patterns 860 $what =~ s/[\{\<\[]([\w_]+)(?:[,|]+([\w_]+)){1,}[\}\>\]]/($1|$2)/g; 861 862 # Handle wildcards 863 $what =~ s,\*,.*,g; 864 $what =~ s,/\xf6..,/.*,g; 865 $what =~ s/\<[^\>]+\>/.*/g; 866 $what =~ s/\{[^\}]+\}/.*/g; 867 $what =~ s/\[[^\]]+\]/.*/g; 868 869 $what =~ s/[XYZ]/.*/g; 870 871 # Recover [0-9] type of patterns 872 $what =~ s/\xf4/[/g; 873 $what =~ s/\xf5/]/g; 874 875 # Remove duplicated spaces 876 $what =~ s/\s+/ /g; 877 878 # Special case: this ABI has a parenthesis on it 879 $what =~ s/sqrt\(x^2\+y^2\+z^2\)/sqrt\(x^2\+y^2\+z^2\)/; 880 881 # Special case: drop comparition as in: 882 # What: foo = <something> 883 # (this happens on a few IIO definitions) 884 $what =~ s,\s*\=.*$,,; 885 886 # Escape all other symbols 887 $what =~ s/$escape_symbols/\\$1/g; 888 $what =~ s/\\\\/\\/g; 889 $what =~ s/\\([\[\]\(\)\|])/$1/g; 890 $what =~ s/(\d+)\\(-\d+)/$1$2/g; 891 892 $what =~ s/\xff/\\d+/g; 893 894 # Special case: IIO ABI which a parenthesis. 895 $what =~ s/sqrt(.*)/sqrt\(.*\)/; 896 897 # Simplify regexes with multiple .* 898 $what =~ s#(?:\.\*){2,}##g; 899# $what =~ s#\.\*/\.\*#.*#g; 900 901 # Recover dot characters 902 $what =~ s/\xf6/\./g; 903 904 my $leave = get_leave($what); 905 906 my $added = 0; 907 foreach my $l (split /\|/, $leave) { 908 if (defined($leaf{$l})) { 909 next if ($leaf{$l}->{what} =~ m/\b$what\b/); 910 $leaf{$l}->{what} .= "\xac" . $what; 911 $added = 1; 912 } else { 913 $leaf{$l}->{what} = $what; 914 $added = 1; 915 } 916 } 917 if ($search_string && $added) { 918 print STDERR "What: $what\n" if ($what =~ m#$search_string#); 919 } 920 921 } 922 } 923 # Compile regexes 924 foreach my $l (sort keys %leaf) { 925 my @expr; 926 foreach my $w(sort split /\xac/, $leaf{$l}->{what}) { 927 push @expr, qr /^$w$/; 928 } 929 $leaf{$l}->{expr} = \@expr; 930 } 931 932 # Take links into account 933 foreach my $link (sort keys %aliases) { 934 my $abs_file = $aliases{$link}; 935 graph_add_link($abs_file, $link); 936 } 937 print STDERR "done.\n"; 938 939 check_undefined_symbols; 940} 941 942# Ensure that the prefix will always end with a slash 943# While this is not needed for find, it makes the patch nicer 944# with --enable-lineno 945$prefix =~ s,/?$,/,; 946 947if ($cmd eq "undefined" || $cmd eq "search") { 948 $show_warnings = 0; 949} 950# 951# Parses all ABI files located at $prefix dir 952# 953find({wanted =>\&parse_abi, no_chdir => 1}, $prefix); 954 955print STDERR Data::Dumper->Dump([\%data], [qw(*data)]) if ($debug & $dbg_dump_abi_structs); 956 957# 958# Handles the command 959# 960if ($cmd eq "undefined") { 961 undefined_symbols; 962} elsif ($cmd eq "search") { 963 search_symbols; 964} else { 965 if ($cmd eq "rest") { 966 output_rest; 967 } 968 969 # Warn about duplicated ABI entries 970 foreach my $what(sort keys %symbols) { 971 my @files = @{$symbols{$what}->{file}}; 972 973 next if (scalar(@files) == 1); 974 975 printf STDERR "Warning: $what is defined %d times: @files\n", 976 scalar(@files); 977 } 978} 979 980__END__ 981 982=head1 NAME 983 984get_abi.pl - parse the Linux ABI files and produce a ReST book. 985 986=head1 SYNOPSIS 987 988B<get_abi.pl> [--debug <level>] [--enable-lineno] [--man] [--help] 989 [--(no-)rst-source] [--dir=<dir>] [--show-hints] 990 [--search-string <regex>] 991 <COMMAND> [<ARGUMENT>] 992 993Where B<COMMAND> can be: 994 995=over 8 996 997B<search> I<SEARCH_REGEX> - search for I<SEARCH_REGEX> inside ABI 998 999B<rest> - output the ABI in ReST markup language 1000 1001B<validate> - validate the ABI contents 1002 1003B<undefined> - existing symbols at the system that aren't 1004 defined at Documentation/ABI 1005 1006=back 1007 1008=head1 OPTIONS 1009 1010=over 8 1011 1012=item B<--dir> 1013 1014Changes the location of the ABI search. By default, it uses 1015the Documentation/ABI directory. 1016 1017=item B<--rst-source> and B<--no-rst-source> 1018 1019The input file may be using ReST syntax or not. Those two options allow 1020selecting between a rst-compliant source ABI (B<--rst-source>), or a 1021plain text that may be violating ReST spec, so it requres some escaping 1022logic (B<--no-rst-source>). 1023 1024=item B<--enable-lineno> 1025 1026Enable output of .. LINENO lines. 1027 1028=item B<--debug> I<debug level> 1029 1030Print debug information according with the level, which is given by the 1031following bitmask: 1032 1033 - 1: Debug parsing What entries from ABI files; 1034 - 2: Shows what files are opened from ABI files; 1035 - 4: Dump the structs used to store the contents of the ABI files. 1036 1037=item B<--show-hints> 1038 1039Show hints about possible definitions for the missing ABI symbols. 1040Used only when B<undefined>. 1041 1042=item B<--search-string> I<regex string> 1043 1044Show only occurences that match a search string. 1045Used only when B<undefined>. 1046 1047=item B<--help> 1048 1049Prints a brief help message and exits. 1050 1051=item B<--man> 1052 1053Prints the manual page and exits. 1054 1055=back 1056 1057=head1 DESCRIPTION 1058 1059Parse the Linux ABI files from ABI DIR (usually located at Documentation/ABI), 1060allowing to search for ABI symbols or to produce a ReST book containing 1061the Linux ABI documentation. 1062 1063=head1 EXAMPLES 1064 1065Search for all stable symbols with the word "usb": 1066 1067=over 8 1068 1069$ scripts/get_abi.pl search usb --dir Documentation/ABI/stable 1070 1071=back 1072 1073Search for all symbols that match the regex expression "usb.*cap": 1074 1075=over 8 1076 1077$ scripts/get_abi.pl search usb.*cap 1078 1079=back 1080 1081Output all obsoleted symbols in ReST format 1082 1083=over 8 1084 1085$ scripts/get_abi.pl rest --dir Documentation/ABI/obsolete 1086 1087=back 1088 1089=head1 BUGS 1090 1091Report bugs to Mauro Carvalho Chehab <mchehab+huawei@kernel.org> 1092 1093=head1 COPYRIGHT 1094 1095Copyright (c) 2016-2021 by Mauro Carvalho Chehab <mchehab+huawei@kernel.org>. 1096 1097License GPLv2: GNU GPL version 2 <http://gnu.org/licenses/gpl.html>. 1098 1099This is free software: you are free to change and redistribute it. 1100There is NO WARRANTY, to the extent permitted by law. 1101 1102=cut