1#!/usr/bin/perl 2# SPDX-License-Identifier: GPL-2.0 3 4use strict; 5use Pod::Usage; 6use Getopt::Long; 7use File::Find; 8use Fcntl ':mode'; 9use Cwd 'abs_path'; 10 11my $help; 12my $man; 13my $debug; 14my $arch; 15my $feat; 16 17my $basename = abs_path($0); 18$basename =~ s,/[^/]+$,/,; 19 20my $prefix=$basename . "../Documentation/features"; 21 22GetOptions( 23 "debug|d+" => \$debug, 24 "dir=s" => \$prefix, 25 'help|?' => \$help, 26 'arch=s' => \$arch, 27 'feat=s' => \$feat, 28 'feature=s' => \$feat, 29 man => \$man 30) or pod2usage(2); 31 32pod2usage(1) if $help; 33pod2usage(-exitstatus => 0, -verbose => 2) if $man; 34 35pod2usage(1) if (scalar @ARGV < 1 || @ARGV > 2); 36 37my ($cmd, $arg) = @ARGV; 38 39pod2usage(2) if ($cmd ne "current" && $cmd ne "rest" && $cmd ne "validate" 40 && $cmd ne "ls" && $cmd ne "list"); 41 42require Data::Dumper if ($debug); 43 44my %data; 45my %archs; 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 a features file, storing its contents at %data 66# 67 68my $h_name = "Feature"; 69my $h_kconfig = "Kconfig"; 70my $h_description = "Description"; 71my $h_subsys = "Subsystem"; 72my $h_status = "Status"; 73my $h_arch = "Architecture"; 74 75my $max_size_name = length($h_name); 76my $max_size_kconfig = length($h_kconfig); 77my $max_size_description = length($h_description); 78my $max_size_subsys = length($h_subsys); 79my $max_size_status = length($h_status); 80my $max_size_arch = length($h_arch); 81 82sub parse_feat { 83 my $file = $File::Find::name; 84 85 my $mode = (stat($file))[2]; 86 return if ($mode & S_IFDIR); 87 return if ($file =~ m,($prefix)/arch-support.txt,); 88 return if (!($file =~ m,arch-support.txt$,)); 89 90 my $subsys = ""; 91 $subsys = $2 if ( m,.*($prefix)/([^/]+).*,); 92 93 if (length($subsys) > $max_size_subsys) { 94 $max_size_subsys = length($subsys); 95 } 96 97 my $name; 98 my $kconfig; 99 my $description; 100 my $comments = ""; 101 my $last_status; 102 my $ln; 103 my %arch_table; 104 105 print STDERR "Opening $file\n" if ($debug > 1); 106 open IN, $file; 107 108 while(<IN>) { 109 $ln++; 110 111 if (m/^\#\s+Feature\s+name:\s*(.*\S)/) { 112 $name = $1; 113 if (length($name) > $max_size_name) { 114 $max_size_name = length($name); 115 } 116 next; 117 } 118 if (m/^\#\s+Kconfig:\s*(.*\S)/) { 119 $kconfig = $1; 120 if (length($kconfig) > $max_size_kconfig) { 121 $max_size_kconfig = length($kconfig); 122 } 123 next; 124 } 125 if (m/^\#\s+description:\s*(.*\S)/) { 126 $description = $1; 127 if (length($description) > $max_size_description) { 128 $max_size_description = length($description); 129 } 130 next; 131 } 132 next if (m/^\\s*$/); 133 next if (m/^\s*\-+\s*$/); 134 next if (m/^\s*\|\s*arch\s*\|\s*status\s*\|\s*$/); 135 136 if (m/^\#\s*(.*)/) { 137 $comments .= "$1\n"; 138 next; 139 } 140 if (m/^\s*\|\s*(\S+):\s*\|\s*(\S+)\s*\|\s*$/) { 141 my $a = $1; 142 my $status = $2; 143 144 if (length($status) > $max_size_status) { 145 $max_size_status = length($status); 146 } 147 if (length($a) > $max_size_arch) { 148 $max_size_arch = length($a); 149 } 150 151 $status = "---" if ($status =~ m/^\.\.$/); 152 153 $archs{$a} = 1; 154 $arch_table{$a} = $status; 155 next; 156 } 157 158 #Everything else is an error 159 parse_error($file, $ln, "line is invalid", $_); 160 } 161 close IN; 162 163 if (!$name) { 164 parse_error($file, $ln, "Feature name not found", ""); 165 return; 166 } 167 168 parse_error($file, $ln, "Subsystem not found", "") if (!$subsys); 169 parse_error($file, $ln, "Kconfig not found", "") if (!$kconfig); 170 parse_error($file, $ln, "Description not found", "") if (!$description); 171 172 if (!%arch_table) { 173 parse_error($file, $ln, "Architecture table not found", ""); 174 return; 175 } 176 177 $data{$name}->{where} = $file; 178 $data{$name}->{subsys} = $subsys; 179 $data{$name}->{kconfig} = $kconfig; 180 $data{$name}->{description} = $description; 181 $data{$name}->{comments} = $comments; 182 $data{$name}->{table} = \%arch_table; 183} 184 185# 186# Output feature(s) for a given architecture 187# 188sub output_arch_table { 189 my $title = "Feature status on $arch architecture"; 190 191 print "=" x length($title) . "\n"; 192 print "$title\n"; 193 print "=" x length($title) . "\n\n"; 194 195 print "=" x $max_size_subsys; 196 print " "; 197 print "=" x $max_size_name; 198 print " "; 199 print "=" x $max_size_kconfig; 200 print " "; 201 print "=" x $max_size_status; 202 print " "; 203 print "=" x $max_size_description; 204 print "\n"; 205 printf "%-${max_size_subsys}s ", $h_subsys; 206 printf "%-${max_size_name}s ", $h_name; 207 printf "%-${max_size_kconfig}s ", $h_kconfig; 208 printf "%-${max_size_status}s ", $h_status; 209 printf "%-${max_size_description}s\n", $h_description; 210 print "=" x $max_size_subsys; 211 print " "; 212 print "=" x $max_size_name; 213 print " "; 214 print "=" x $max_size_kconfig; 215 print " "; 216 print "=" x $max_size_status; 217 print " "; 218 print "=" x $max_size_description; 219 print "\n"; 220 221 foreach my $name (sort { 222 ($data{$a}->{subsys} cmp $data{$b}->{subsys}) || 223 ("\L$a" cmp "\L$b") 224 } keys %data) { 225 next if ($feat && $name ne $feat); 226 227 my %arch_table = %{$data{$name}->{table}}; 228 printf "%-${max_size_subsys}s ", $data{$name}->{subsys}; 229 printf "%-${max_size_name}s ", $name; 230 printf "%-${max_size_kconfig}s ", $data{$name}->{kconfig}; 231 printf "%-${max_size_status}s ", $arch_table{$arch}; 232 printf "%-s\n", $data{$name}->{description}; 233 } 234 235 print "=" x $max_size_subsys; 236 print " "; 237 print "=" x $max_size_name; 238 print " "; 239 print "=" x $max_size_kconfig; 240 print " "; 241 print "=" x $max_size_status; 242 print " "; 243 print "=" x $max_size_description; 244 print "\n"; 245} 246 247# 248# list feature(s) for a given architecture 249# 250sub list_arch_features { 251 print "#\n# Kernel feature support matrix of the '$arch' architecture:\n#\n"; 252 253 foreach my $name (sort { 254 ($data{$a}->{subsys} cmp $data{$b}->{subsys}) || 255 ("\L$a" cmp "\L$b") 256 } keys %data) { 257 next if ($feat && $name ne $feat); 258 259 my %arch_table = %{$data{$name}->{table}}; 260 261 my $status = $arch_table{$arch}; 262 $status = " " x ((4 - length($status)) / 2) . $status; 263 264 printf " %${max_size_subsys}s/ ", $data{$name}->{subsys}; 265 printf "%-${max_size_name}s: ", $name; 266 printf "%-5s| ", $status; 267 printf "%${max_size_kconfig}s # ", $data{$name}->{kconfig}; 268 printf " %s\n", $data{$name}->{description}; 269 } 270} 271 272# 273# Output a feature on all architectures 274# 275sub output_feature { 276 my $title = "Feature $feat"; 277 278 print "=" x length($title) . "\n"; 279 print "$title\n"; 280 print "=" x length($title) . "\n\n"; 281 282 print ":Subsystem: $data{$feat}->{subsys} \n" if ($data{$feat}->{subsys}); 283 print ":Kconfig: $data{$feat}->{kconfig} \n" if ($data{$feat}->{kconfig}); 284 285 my $desc = $data{$feat}->{description}; 286 $desc =~ s/^([a-z])/\U$1/; 287 $desc =~ s/\.?\s*//; 288 print "\n$desc.\n\n"; 289 290 my $com = $data{$feat}->{comments}; 291 $com =~ s/^\s+//; 292 $com =~ s/\s+$//; 293 if ($com) { 294 print "Comments\n"; 295 print "--------\n\n"; 296 print "$com\n\n"; 297 } 298 299 print "=" x $max_size_arch; 300 print " "; 301 print "=" x $max_size_status; 302 print "\n"; 303 304 printf "%-${max_size_arch}s ", $h_arch; 305 printf "%-${max_size_status}s", $h_status . "\n"; 306 307 print "=" x $max_size_arch; 308 print " "; 309 print "=" x $max_size_status; 310 print "\n"; 311 312 my %arch_table = %{$data{$feat}->{table}}; 313 foreach my $arch (sort keys %arch_table) { 314 printf "%-${max_size_arch}s ", $arch; 315 printf "%-${max_size_status}s\n", $arch_table{$arch}; 316 } 317 318 print "=" x $max_size_arch; 319 print " "; 320 print "=" x $max_size_status; 321 print "\n"; 322} 323 324# 325# Output all features for all architectures 326# 327 328sub matrix_lines($$) { 329 my $partial = shift; 330 my $header = shift; 331 my $split; 332 my $fill; 333 my $ln_marker; 334 335 if ($header) { 336 $ln_marker = "="; 337 } else { 338 $ln_marker = "-"; 339 } 340 341 if ($partial) { 342 $split = "|"; 343 $fill = " "; 344 } else { 345 $split = "+"; 346 $fill = $ln_marker; 347 } 348 349 print $split; 350 print $fill x $max_size_name; 351 print $split; 352 print $fill x $max_size_kconfig; 353 print $split; 354 print $fill x $max_size_description; 355 print "+"; 356 print $ln_marker x $max_size_arch; 357 print "+"; 358 print $ln_marker x $max_size_status; 359 print "+\n"; 360} 361 362sub output_matrix { 363 my $title = "Feature status on all architectures"; 364 365 print "=" x length($title) . "\n"; 366 print "$title\n"; 367 print "=" x length($title) . "\n\n"; 368 369 my $cur_subsys = ""; 370 foreach my $name (sort { 371 ($data{$a}->{subsys} cmp $data{$b}->{subsys}) or 372 ("\L$a" cmp "\L$b") 373 } keys %data) { 374 375 if ($cur_subsys ne $data{$name}->{subsys}) { 376 if ($cur_subsys ne "") { 377 printf "\n"; 378 } 379 380 $cur_subsys = $data{$name}->{subsys}; 381 382 my $title = "Subsystem: $cur_subsys"; 383 print "$title\n"; 384 print "=" x length($title) . "\n\n"; 385 386 matrix_lines(0, 0); 387 printf "|%-${max_size_name}s", $h_name; 388 printf "|%-${max_size_kconfig}s", $h_kconfig; 389 printf "|%-${max_size_description}s", $h_description; 390 391 printf "|%-${max_size_arch}s", $h_arch; 392 printf "|%-${max_size_status}s|\n", $h_status; 393 394 matrix_lines(0, 1); 395 } 396 397 my %arch_table = %{$data{$name}->{table}}; 398 my $first = 1; 399 foreach my $arch (sort keys %arch_table) { 400 if ($first) { 401 printf "|%-${max_size_name}s", $name; 402 printf "|%-${max_size_kconfig}s", $data{$name}->{kconfig}; 403 printf "|%-${max_size_description}s", $data{$name}->{description}; 404 $first = 0; 405 } else { 406 matrix_lines(1, 0); 407 408 printf "|%-${max_size_name}s", ""; 409 printf "|%-${max_size_kconfig}s", ""; 410 printf "|%-${max_size_description}s", ""; 411 } 412 printf "|%-${max_size_arch}s", $arch; 413 printf "|%-${max_size_status}s|\n", $arch_table{$arch}; 414 } 415 matrix_lines(0, 0); 416 } 417} 418 419 420# 421# Parses all feature files located at $prefix dir 422# 423find({wanted =>\&parse_feat, no_chdir => 1}, $prefix); 424 425print STDERR Data::Dumper->Dump([\%data], [qw(*data)]) if ($debug); 426 427# 428# Handles the command 429# 430if ($cmd eq "current") { 431 $arch = qx(uname -m | sed 's/x86_64/x86/' | sed 's/i386/x86/'); 432 $arch =~s/\s+$//; 433} 434 435if ($cmd eq "ls" or $cmd eq "list") { 436 if (!$arch) { 437 $arch = qx(uname -m | sed 's/x86_64/x86/' | sed 's/i386/x86/'); 438 $arch =~s/\s+$//; 439 } 440 441 list_arch_features; 442 443 exit; 444} 445 446if ($cmd ne "validate") { 447 if ($arch) { 448 output_arch_table; 449 } elsif ($feat) { 450 output_feature; 451 } else { 452 output_matrix; 453 } 454} 455 456__END__ 457 458=head1 NAME 459 460get_feat.pl - parse the Linux Feature files and produce a ReST book. 461 462=head1 SYNOPSIS 463 464B<get_feat.pl> [--debug] [--man] [--help] [--dir=<dir>] [--arch=<arch>] 465 [--feature=<feature>|--feat=<feature>] <COMAND> [<ARGUMENT>] 466 467Where <COMMAND> can be: 468 469=over 8 470 471B<current> - output table in ReST compatible ASCII format 472 with features for this machine's architecture 473 474B<rest> - output table(s) in ReST compatible ASCII format 475 with features in ReST markup language. The output 476 is affected by --arch or --feat/--feature flags. 477 478B<validate> - validate the contents of the files under 479 Documentation/features. 480 481B<ls> or B<list> - list features for this machine's architecture, 482 using an easier to parse format. 483 The output is affected by --arch flag. 484 485=back 486 487=head1 OPTIONS 488 489=over 8 490 491=item B<--arch> 492 493Output features for an specific architecture, optionally filtering for 494a single specific feature. 495 496=item B<--feat> or B<--feature> 497 498Output features for a single specific feature. 499 500=item B<--dir> 501 502Changes the location of the Feature files. By default, it uses 503the Documentation/features directory. 504 505=item B<--debug> 506 507Put the script in verbose mode, useful for debugging. Can be called multiple 508times, to increase verbosity. 509 510=item B<--help> 511 512Prints a brief help message and exits. 513 514=item B<--man> 515 516Prints the manual page and exits. 517 518=back 519 520=head1 DESCRIPTION 521 522Parse the Linux feature files from Documentation/features (by default), 523optionally producing results at ReST format. 524 525It supports output data per architecture, per feature or a 526feature x arch matrix. 527 528When used with B<rest> command, it will use either one of the tree formats: 529 530If neither B<--arch> or B<--feature> arguments are used, it will output a 531matrix with features per architecture. 532 533If B<--arch> argument is used, it will output the features availability for 534a given architecture. 535 536If B<--feat> argument is used, it will output the content of the feature 537file using ReStructured Text markup. 538 539=head1 BUGS 540 541Report bugs to Mauro Carvalho Chehab <[email protected]> 542 543=head1 COPYRIGHT 544 545Copyright (c) 2019 by Mauro Carvalho Chehab <[email protected]>. 546 547License GPLv2: GNU GPL version 2 <http://gnu.org/licenses/gpl.html>. 548 549This is free software: you are free to change and redistribute it. 550There is NO WARRANTY, to the extent permitted by law. 551 552=cut 553