|
@@ -0,0 +1,389 @@
|
|
|
+#!/usr/bin/perl
|
|
|
+
|
|
|
+#
|
|
|
+# Generate docs from ser/sip-router RPCs descriptions
|
|
|
+# (run on files generated by gcc -fdump-translation-unit -c file.c,
|
|
|
+# try -h for help)
|
|
|
+# E.g.: dump_rpcs.pl --file cfg_core.c --defs="-DUSE_SCTP ..."
|
|
|
+#
|
|
|
+# History:
|
|
|
+# =======
|
|
|
+# 2009-10-18 initial version (Andrei Pelinescu-Onciul <[email protected]>)
|
|
|
+#
|
|
|
+
|
|
|
+# Note: uses GCC::TranslationUnit (see cpan) with the following patch:
|
|
|
+#@@ -251,6 +251,8 @@
|
|
|
+# $node->{vector}[$key] = $value;
|
|
|
+# } elsif($key =~ /^op (\d+)$/) {
|
|
|
+# $node->{operand}[$1] = $value;
|
|
|
+#+ } elsif ($key eq "val") {
|
|
|
+#+ push @{$node->{$key}}, ($value) ;
|
|
|
+# } else {
|
|
|
+# $node->{$key} = $value;
|
|
|
+# }
|
|
|
+#
|
|
|
+#
|
|
|
+# Assumptions:
|
|
|
+# - the first array of type rpc_export_t with an initializer is the array
|
|
|
+# with the rpc definitions (name, doc, flags a.s.o.). Only
|
|
|
+# one rpc_export_t array per file is supported.
|
|
|
+# - all the documentation arrays referenced in the rpc export array are
|
|
|
+# defined and intialized in the same file.
|
|
|
+#
|
|
|
+# Output notes:
|
|
|
+# - doc strings are not printed if they cannot be found
|
|
|
+
|
|
|
+
|
|
|
+use strict;
|
|
|
+use warnings;
|
|
|
+use Getopt::Long;
|
|
|
+use File::Temp qw(:mktemp);
|
|
|
+use File::Basename;
|
|
|
+use lib "/home/andrei/perl/modules/share/perl/5.10.1";
|
|
|
+use GCC::TranslationUnit;
|
|
|
+
|
|
|
+# text printed if we discover that GCC::TranslationUnit is unpatched
|
|
|
+my $patch_required="$0 requires a patched GCC:TranslationUnit, see the " .
|
|
|
+ "comments at the beginning of the file or try --patch\n";
|
|
|
+# gcc name
|
|
|
+my $gcc="gcc";
|
|
|
+# default defines
|
|
|
+my $c_defs="-D__CPU_i386 -D__OS_linux -DSER_VER=2099099 -DPKG_MALLOC -DSHM_MEM -DSHM_MMAP -DDNS_IP_HACK -DUSE_IPV6 -DUSE_MCAST -DUSE_TCP -DUSE_DNS_CACHE -DUSE_DNS_FAILOVER -DUSE_DST_BLACKLIST -DUSE_NAPTR -DUSE_TLS -DTLS_HOOKS -DFAST_LOCK -DCC_GCC_LIKE_ASM -DHAVE_GETHOSTBYNAME2 -DHAVE_UNION_SEMUN -DHAVE_SCHED_YIELD -DHAVE_MSG_NOSIGNAL -DHAVE_MSGHDR_MSG_CONTROL -DHAVE_ALLOCA_H -DHAVE_SCHED_SETSCHEDULER -DHAVE_EPOLL -DUSE_SCTP -DNAME='\"ser\"' -DVERSION='\"2.99.99-pre3\"' -DARCH='\"i386\"' -DOS_QUOTED='\"linux\"' -DSER_MOD_INTERFACE";
|
|
|
+
|
|
|
+# file with gcc syntax tree
|
|
|
+my $file;
|
|
|
+my $tmp_file;
|
|
|
+my $src_fname;
|
|
|
+
|
|
|
+# type to look for
|
|
|
+my $var_type="rpc_export_t";
|
|
|
+
|
|
|
+my $tu;
|
|
|
+my $node;
|
|
|
+my $i;
|
|
|
+my @rpc_exports; # filled with rpc definitions (rpc_export_t)
|
|
|
+my %rpc_docs; # hash containing rpc_doc_varname -> doc_string mappings
|
|
|
+my ($rpc_grp_name, $rpc_var_name);
|
|
|
+
|
|
|
+my ($opt_help, $opt_txt, $opt_is_tu, $dbg, $opt_grp_name, $opt_patch);
|
|
|
+my $opt_force_grp_name;
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+sub show_patch
|
|
|
+{
|
|
|
+my $patch='
|
|
|
+--- GCC/TranslationUnit.pm.orig 2009-10-16 17:57:51.275963053 +0200
|
|
|
++++ GCC/TranslationUnit.pm 2009-10-16 20:17:28.128455959 +0200
|
|
|
+@@ -251,6 +251,8 @@
|
|
|
+ $node->{vector}[$key] = $value;
|
|
|
+ } elsif($key =~ /^op (\d+)$/) {
|
|
|
+ $node->{operand}[$1] = $value;
|
|
|
++ } elsif ($key eq "val") {
|
|
|
++ push @{$node->{$key}}, ($value) ;
|
|
|
+ } else {
|
|
|
+ $node->{$key} = $value;
|
|
|
+ }
|
|
|
+';
|
|
|
+
|
|
|
+print $patch;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+sub help
|
|
|
+{
|
|
|
+ $~ = "USAGE";
|
|
|
+ write;
|
|
|
+
|
|
|
+format USAGE =
|
|
|
+Usage @* -f filename | --file filename [options...]
|
|
|
+ $0
|
|
|
+Options:
|
|
|
+ -f filename - use filename for input (see also -T/--tu).
|
|
|
+ --file filename - same as -f.
|
|
|
+ -h | -? | --help - this help message.
|
|
|
+ -D | --dbg | --debug - enable debugging messages.
|
|
|
+ -d | --defs - defines to be passed on gcc's command line
|
|
|
+ (e.g. --defs="-DUSE_SCTP -DUSE_TCP").
|
|
|
+ -g | --grp name
|
|
|
+ --group name - rpc group name used if one cannot be
|
|
|
+ autodetected (e.g. no default value
|
|
|
+ intializer present in the file).
|
|
|
+ -G | --force-grp name
|
|
|
+ --force-group name - force using a rpc group name, even if one
|
|
|
+ is autodetected (see also -g).
|
|
|
+ --gcc gcc_name - run gcc_name instead of gcc.
|
|
|
+ -t | --txt - text mode output.
|
|
|
+ -T | --tu - the input file is in raw gcc translation
|
|
|
+ unit format (as produced by
|
|
|
+ gcc -fdump-translation-unit -c ). If not
|
|
|
+ present it's assumed that the file contains
|
|
|
+ C code.
|
|
|
+ -s | --src | --source - name of the source file, needed only if
|
|
|
+ the input file is in "raw" translation
|
|
|
+ unit format (--tu) and usefull to restrict
|
|
|
+ and speed-up the search.
|
|
|
+ --patch - show patches needed for the
|
|
|
+ GCC::TranslationUnit package.
|
|
|
+.
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+# eliminate casts and expressions.
|
|
|
+# (always go on the first operand)
|
|
|
+# params: node (GCC::Node)
|
|
|
+# result: if node is an expression it will walk on operand(0) until first non
|
|
|
+# expression element is found
|
|
|
+sub expr_op0{
|
|
|
+ my $n=shift;
|
|
|
+
|
|
|
+ while(($n->isa('GCC::Node::Expression') || $n->isa('GCC::Node::Unary')) &&
|
|
|
+ defined $n->operand(0)) {
|
|
|
+ $n=$n->operand(0);
|
|
|
+ }
|
|
|
+ return $n;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+# read command line args
|
|
|
+if ($#ARGV < 0 || ! GetOptions( 'help|h|?' => \$opt_help,
|
|
|
+ 'file|f=s' => \$file,
|
|
|
+ 'txt|t' => \$opt_txt,
|
|
|
+ 'tu|T' => \$opt_is_tu,
|
|
|
+ 'source|src|s=s' => \$src_fname,
|
|
|
+ 'defs|d=s'=>\$c_defs,
|
|
|
+ 'group|grp|g=s'=>\$opt_grp_name,
|
|
|
+ 'force-group|force-grp|G=s' =>
|
|
|
+ \$opt_force_grp_name,
|
|
|
+ 'dbg|debug|D'=>\$dbg,
|
|
|
+ 'gcc=s' => \$gcc,
|
|
|
+ 'patch' => \$opt_patch) ||
|
|
|
+ defined $opt_help) {
|
|
|
+ do { show_patch(); exit 0; } if (defined $opt_patch);
|
|
|
+ select(STDERR) if ! defined $opt_help;
|
|
|
+ help();
|
|
|
+ exit((defined $opt_help)?0:1);
|
|
|
+}
|
|
|
+
|
|
|
+do { show_patch(); exit 0; } if (defined $opt_patch);
|
|
|
+do { select(STDERR); help(); exit 1 } if (!defined $file);
|
|
|
+
|
|
|
+if (! defined $opt_is_tu){
|
|
|
+ # file is not a gcc translation-unit dump
|
|
|
+ # => we have to create one
|
|
|
+ $src_fname=basename($file);
|
|
|
+ $tmp_file = "/tmp/" . mktemp ("dump_translation_unit_XXXXXX");
|
|
|
+ system("$gcc -fdump-translation-unit $c_defs -c $file -o $tmp_file && \
|
|
|
+ mv \"$src_fname\".001t.tu $tmp_file") == 0 or
|
|
|
+ die "$gcc failed to generate a translation unit dump from $file";
|
|
|
+ $tu=GCC::TranslationUnit::Parser->parsefile($tmp_file);
|
|
|
+ print(STDERR "src name $src_fname\n") if $dbg;
|
|
|
+ unlink($tmp_file, "$tmp_file.o");
|
|
|
+}else{
|
|
|
+ $tu=GCC::TranslationUnit::Parser->parsefile($file);
|
|
|
+}
|
|
|
+
|
|
|
+print(STDERR "Parsing file $file...\n") if $dbg;
|
|
|
+#
|
|
|
+# function_decl: name, type, srcp (source), chan?, body?, link (e.g. extern)
|
|
|
+# parm_decl: name, type, scpe, srcp, chan, argt, size, algn, used
|
|
|
+# field_decl: name, type, scpe (scope), srcp, size, algn, bpos (bit pos?)
|
|
|
+#
|
|
|
+# array_type: size, algn, elts (elements?), domn ?
|
|
|
+#
|
|
|
+#
|
|
|
+# name as string $node->name->identifier
|
|
|
+#
|
|
|
+# E.g.: static cfg_def_t tcp_cfg_def[]= {...}
|
|
|
+# ^^^^^^^^^^^
|
|
|
+#
|
|
|
+# @7695 var_decl: name: @7705 type: @7706 srcp: tcp_options.c:51
|
|
|
+# chan: @7707 init: @7708 size: @7709
|
|
|
+# algn: 256 used: 1
|
|
|
+#
|
|
|
+# @7705 (var name) identifier_node strg: tcp_cfg_def lngt 11
|
|
|
+# @7706 (var type) array_type: size:@7709 algn: 32 elts: @2265 domn: @7718
|
|
|
+# @7707 (? next ? ) function_decl: ....
|
|
|
+# @7708 (intializer) constructor: lngt: 25
|
|
|
+# idx : @20 val : @7723 [...]
|
|
|
+# @7709 interget_cst: type: @11 low: 5600
|
|
|
+#
|
|
|
+# @2265 (array type) record_type: name: @2256 unql: @2255 size: @2002
|
|
|
+# algn: 32 tag : struct flds: @2263
|
|
|
+# @2256 (type) type_decl: name: @2264 type: @2265 srcp: cfg.h:73
|
|
|
+# chan: @2266
|
|
|
+# @2264 (name) identifier_node: strg: cfg_def_t
|
|
|
+
|
|
|
+print(STDERR "Searching...\n") if $dbg;
|
|
|
+$i=0;
|
|
|
+# iterate on the entire nodes array (returned by gcc), but skipping node 0
|
|
|
+SEARCH: for $node (@{$tu}[1..$#{$tu}]) {
|
|
|
+ $i++;
|
|
|
+ while($node) {
|
|
|
+ if (
|
|
|
+ @rpc_exports == 0 && # parse it only once
|
|
|
+ $node->isa('GCC::Node::var_decl') &&
|
|
|
+ $node->type->isa('GCC::Node::array_type') &&
|
|
|
+ (! defined $src_fname || $src_fname eq "" ||
|
|
|
+ $node->source=~"$src_fname")
|
|
|
+ ){
|
|
|
+ # found a var decl. that it's an array
|
|
|
+ # check if it's a valid array type
|
|
|
+ next if (!( $node->type->can('elements') &&
|
|
|
+ defined $node->type->elements &&
|
|
|
+ $node->type->elements->can('name') &&
|
|
|
+ defined $node->type->elements->name &&
|
|
|
+ $node->type->elements->name->can('name') &&
|
|
|
+ defined $node->type->elements->name->name)
|
|
|
+ );
|
|
|
+ my $type_name= $node->type->elements->name->name->identifier;
|
|
|
+ if ($type_name eq $var_type) {
|
|
|
+ if ($node->can('initial') && defined $node->initial) {
|
|
|
+ my %c1=%{$node->initial};
|
|
|
+ $rpc_var_name=$node->name->identifier;
|
|
|
+ if (defined $c1{val}){
|
|
|
+ my $c1_el;
|
|
|
+ die $patch_required if (ref($c1{val}) ne "ARRAY");
|
|
|
+ # iterate on array elem., level 1( top {} )
|
|
|
+ # each element is a constructor.
|
|
|
+ # { name, callback, doc_var, flags }
|
|
|
+ for $c1_el (@{$c1{val}}) {
|
|
|
+ # finally we are a the lower {} initializer:
|
|
|
+ # { name, callback, doc_var, flags }
|
|
|
+ my %c2=%{$c1_el};
|
|
|
+ my @el=@{$c2{val}};
|
|
|
+ my ($name_n, $callback_n, $docvar_n, $flags_n)=@el;
|
|
|
+ my ($name, $docvar, $flags);
|
|
|
+ if ($name_n->isa('GCC::Node::integer_cst')){
|
|
|
+ printf(" ERROR: integer non-0 name (%d)\n",
|
|
|
+ $name_n->low) if ($name_n->low!=0);
|
|
|
+ last SEARCH;
|
|
|
+ }
|
|
|
+ $name_n=expr_op0($name_n);
|
|
|
+ $name= $name_n->string;
|
|
|
+ $flags=$flags_n->low;
|
|
|
+ # eliminate casts and expressions
|
|
|
+ # (always go on the first operand)
|
|
|
+ $docvar_n=expr_op0($docvar_n);
|
|
|
+ $docvar=$docvar_n->name->identifier;
|
|
|
+ push @rpc_exports, [$name, $docvar, $flags];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } continue {
|
|
|
+ if ($node->can('chain')){
|
|
|
+ $node = $node->chain;
|
|
|
+ }else{
|
|
|
+ last;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+print(STDERR "Searching doc vars...\n") if $dbg;
|
|
|
+# look for docvars
|
|
|
+# re-iterate on the entire nodes array (returned by gcc), but skipping node 0
|
|
|
+DOC: for $node (@{$tu}[1..$#{$tu}]) {
|
|
|
+ while(@rpc_exports>0 && $node) {
|
|
|
+ if (
|
|
|
+ $node->isa('GCC::Node::var_decl') &&
|
|
|
+ $node->type->isa('GCC::Node::array_type') &&
|
|
|
+ (! defined $src_fname || $src_fname eq "" ||
|
|
|
+ $node->source=~"$src_fname") &&
|
|
|
+ # var name is among the one we look for
|
|
|
+ grep(${$_}[1] eq $node->name->identifier, @rpc_exports) > 0
|
|
|
+ ){
|
|
|
+ print(STDERR "found a candidate:", $node->name->identifier, "\n")
|
|
|
+ if $dbg;
|
|
|
+ # found a var decl. that it's an array
|
|
|
+ # check if it's a valid array type
|
|
|
+ next if (!( $node->type->can('elements') &&
|
|
|
+ defined $node->type->elements)
|
|
|
+ );
|
|
|
+ if ($node->can('initial') && defined $node->initial){
|
|
|
+ my %c1=%{$node->initial};
|
|
|
+ my $doc_n = ${$c1{val}}[0];
|
|
|
+ if (defined $doc_n){
|
|
|
+ my $doc=expr_op0($doc_n)->string;
|
|
|
+ $rpc_docs{$node->name->identifier}=$doc;
|
|
|
+ last DOC if ( @rpc_exports == keys %rpc_docs );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } continue {
|
|
|
+ if ($node->can('chain')){
|
|
|
+ $node = $node->chain;
|
|
|
+ }else{
|
|
|
+ last;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+print(STDERR "Done.\n") if $dbg;
|
|
|
+
|
|
|
+my ($name, $flags, $desc);
|
|
|
+my $extra_txt;
|
|
|
+
|
|
|
+if (@rpc_exports > 0){
|
|
|
+ my $l;
|
|
|
+ $i=0;
|
|
|
+ if (@rpc_exports != keys %rpc_docs){
|
|
|
+ print STDERR "Warning: missing ", @rpc_exports - keys %rpc_docs,
|
|
|
+ " doc variables definitions\n";
|
|
|
+ }
|
|
|
+ # dump the configuration in txt mode
|
|
|
+ if (defined $opt_force_grp_name) {
|
|
|
+ $rpc_grp_name=$opt_force_grp_name;
|
|
|
+ }elsif (!defined $rpc_grp_name && defined $opt_grp_name) {
|
|
|
+ $rpc_grp_name=$opt_grp_name;
|
|
|
+ }
|
|
|
+ $~ = "HEADER"; write;
|
|
|
+ $~ = "RPCLINE" ;
|
|
|
+ for $l (@rpc_exports){
|
|
|
+ ($name, $desc, $flags)=@{$l};
|
|
|
+ $extra_txt="";
|
|
|
+ $desc=(defined $rpc_docs{$desc} && $rpc_docs{$desc} ne "")?
|
|
|
+ $rpc_docs{$desc}:
|
|
|
+ "Documentation missing ($desc).";
|
|
|
+ $i++;
|
|
|
+ $extra_txt.="Returns an array." if ($flags & 1 );
|
|
|
+ # generate txt description
|
|
|
+ write;
|
|
|
+ }
|
|
|
+}else{
|
|
|
+ die "no rpc exports found in $file\n";
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+sub valid_grp_name
|
|
|
+{
|
|
|
+ my $name=shift;
|
|
|
+ return defined $name && $name ne "";
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+format HEADER =
|
|
|
+RPC Exports@*
|
|
|
+(valid_grp_name $rpc_grp_name) ? " for " . $rpc_grp_name : ""
|
|
|
+===========@*
|
|
|
+"=" x length((valid_grp_name $rpc_grp_name)?" for " . $rpc_grp_name : "")
|
|
|
+
|
|
|
+@||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|
+"[ this file is autogenerated, do not edit ]"
|
|
|
+
|
|
|
+
|
|
|
+.
|
|
|
+
|
|
|
+format RPCLINE =
|
|
|
+@>. @*
|
|
|
+$i, $name
|
|
|
+~~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
|
|
+ $desc
|
|
|
+~~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
|
|
+ $extra_txt
|
|
|
+
|
|
|
+.
|