dump_rpcs.pl 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  1. #!/usr/bin/perl
  2. #
  3. # Generate docs from ser/sip-router RPCs descriptions
  4. # (run on files generated by gcc -fdump-translation-unit -c file.c,
  5. # try -h for help)
  6. # E.g.: dump_rpcs.pl --file cfg_core.c --defs="-DUSE_SCTP ..."
  7. #
  8. # History:
  9. # =======
  10. # 2009-10-18 initial version (Andrei Pelinescu-Onciul <[email protected]>)
  11. #
  12. # Note: uses GCC::TranslationUnit (see cpan) with the following patch:
  13. #@@ -251,6 +251,8 @@
  14. # $node->{vector}[$key] = $value;
  15. # } elsif($key =~ /^op (\d+)$/) {
  16. # $node->{operand}[$1] = $value;
  17. #+ } elsif ($key eq "val") {
  18. #+ push @{$node->{$key}}, ($value) ;
  19. # } else {
  20. # $node->{$key} = $value;
  21. # }
  22. #
  23. #
  24. # Assumptions:
  25. # - the first array of type rpc_export_t with an initializer is the array
  26. # with the rpc definitions (name, doc, flags a.s.o.). Only
  27. # one rpc_export_t array per file is supported.
  28. # - all the documentation arrays referenced in the rpc export array are
  29. # defined and initialized in the same file.
  30. #
  31. # Output notes:
  32. # - doc strings are not printed if they cannot be found
  33. use strict;
  34. use warnings;
  35. use Getopt::Long;
  36. use File::Temp qw(:mktemp);
  37. use File::Basename;
  38. use lib "/home/andrei/perl/modules/share/perl/5.10.1";
  39. use GCC::TranslationUnit;
  40. # text printed if we discover that GCC::TranslationUnit is unpatched
  41. my $patch_required="$0 requires a patched GCC:TranslationUnit, see the " .
  42. "comments at the beginning of the file or try --patch\n";
  43. # gcc name
  44. my $gcc="gcc";
  45. # default defines
  46. 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";
  47. # file with gcc syntax tree
  48. my $file;
  49. my $tmp_file;
  50. my $src_fname;
  51. # type to look for
  52. my $var_type="rpc_export_t";
  53. my $tu;
  54. my $node;
  55. my $i;
  56. my @rpc_exports; # filled with rpc definitions (rpc_export_t)
  57. my %rpc_docs; # hash containing rpc_doc_varname -> doc_string mappings
  58. my ($rpc_grp_name, $rpc_var_name);
  59. my ($opt_help, $opt_txt, $opt_is_tu, $dbg, $opt_grp_name, $opt_patch);
  60. my ($opt_force_grp_name, $opt_docbook);
  61. # default output formats
  62. my $output_format_header="HEADER";
  63. my $output_format_footer="FOOTER";
  64. my $output_format_rpcline="RPCLINE";
  65. sub show_patch
  66. {
  67. my $patch='
  68. --- GCC/TranslationUnit.pm.orig 2009-10-16 17:57:51.275963053 +0200
  69. +++ GCC/TranslationUnit.pm 2009-10-16 20:17:28.128455959 +0200
  70. @@ -251,6 +251,8 @@
  71. $node->{vector}[$key] = $value;
  72. } elsif($key =~ /^op (\d+)$/) {
  73. $node->{operand}[$1] = $value;
  74. + } elsif ($key eq "val") {
  75. + push @{$node->{$key}}, ($value) ;
  76. } else {
  77. $node->{$key} = $value;
  78. }
  79. ';
  80. print $patch;
  81. }
  82. sub help
  83. {
  84. $~ = "USAGE";
  85. write;
  86. format USAGE =
  87. Usage @* -f filename | --file filename [options...]
  88. $0
  89. Options:
  90. -f filename - use filename for input (see also -T/--tu).
  91. --file filename - same as -f.
  92. -h | -? | --help - this help message.
  93. -D | --dbg | --debug - enable debugging messages.
  94. -d | --defs - defines to be passed on gcc's command line
  95. (e.g. --defs="-DUSE_SCTP -DUSE_TCP").
  96. -g | --grp name
  97. --group name - rpc group name used if one cannot be
  98. autodetected (e.g. no default value
  99. initializer present in the file).
  100. -G | --force-grp name
  101. --force-group name - force using a rpc group name, even if one
  102. is autodetected (see also -g).
  103. --gcc gcc_name - run gcc_name instead of gcc.
  104. -t | --txt - text mode output.
  105. --docbook | --xml - docbook output (xml).
  106. -T | --tu - the input file is in raw gcc translation
  107. unit format (as produced by
  108. gcc -fdump-translation-unit -c ). If not
  109. present it's assumed that the file contains
  110. C code.
  111. -s | --src | --source - name of the source file, needed only if
  112. the input file is in "raw" translation
  113. unit format (--tu) and usefull to restrict
  114. and speed-up the search.
  115. --patch - show patches needed for the
  116. GCC::TranslationUnit package.
  117. .
  118. }
  119. # escape a string for xml use
  120. # params: string to be escaped
  121. # return: escaped string
  122. sub xml_escape{
  123. my $s=shift;
  124. my %escapes = (
  125. '"' => '&quot;',
  126. "'" => '&apos;',
  127. '&' => '&amp;',
  128. '<' => '&lt;',
  129. '>' => '&gt;'
  130. );
  131. $s=~s/(["'&<>])/$escapes{$1}/g;
  132. return $s;
  133. }
  134. # escape a string according with the output requirements
  135. # params: string to be escaped
  136. # return: escaped string
  137. sub output_esc{
  138. return xml_escape(shift) if defined $opt_docbook;
  139. return shift;
  140. }
  141. # eliminate casts and expressions.
  142. # (always go on the first operand)
  143. # params: node (GCC::Node)
  144. # result: if node is an expression it will walk on operand(0) until first non
  145. # expression element is found
  146. sub expr_op0{
  147. my $n=shift;
  148. while(($n->isa('GCC::Node::Expression') || $n->isa('GCC::Node::Unary')) &&
  149. defined $n->operand(0)) {
  150. $n=$n->operand(0);
  151. }
  152. return $n;
  153. }
  154. # read command line args
  155. if ($#ARGV < 0 || ! GetOptions( 'help|h|?' => \$opt_help,
  156. 'file|f=s' => \$file,
  157. 'txt|t' => \$opt_txt,
  158. 'docbook|xml' => \$opt_docbook,
  159. 'tu|T' => \$opt_is_tu,
  160. 'source|src|s=s' => \$src_fname,
  161. 'defs|d=s'=>\$c_defs,
  162. 'group|grp|g=s'=>\$opt_grp_name,
  163. 'force-group|force-grp|G=s' =>
  164. \$opt_force_grp_name,
  165. 'dbg|debug|D'=>\$dbg,
  166. 'gcc=s' => \$gcc,
  167. 'patch' => \$opt_patch) ||
  168. defined $opt_help) {
  169. do { show_patch(); exit 0; } if (defined $opt_patch);
  170. select(STDERR) if ! defined $opt_help;
  171. help();
  172. exit((defined $opt_help)?0:1);
  173. }
  174. do { show_patch(); exit 0; } if (defined $opt_patch);
  175. do { select(STDERR); help(); exit 1 } if (!defined $file);
  176. if (defined $opt_txt){
  177. $output_format_header="HEADER";
  178. $output_format_footer="FOOTER";
  179. $output_format_rpcline="RPCLINE";
  180. }elsif (defined $opt_docbook){
  181. $output_format_header="DOCBOOK_HEADER";
  182. $output_format_footer="DOCBOOK_FOOTER";
  183. $output_format_rpcline="DOCBOOK_RPCLINE";
  184. }
  185. if (! defined $opt_is_tu){
  186. # file is not a gcc translation-unit dump
  187. # => we have to create one
  188. $src_fname=basename($file);
  189. $tmp_file = "/tmp/" . mktemp ("dump_translation_unit_XXXXXX");
  190. # Note: gcc < 4.5 will produce the translation unit dump in a file in
  191. # the current directory. gcc 4.5 will write it in the same directory as
  192. # the output file.
  193. system("$gcc -fdump-translation-unit $c_defs -c $file -o $tmp_file") == 0
  194. or die "$gcc -fdump-translation-unit $c_defs -c $file -o $tmp_file" .
  195. " failed to generate a translation unit dump from $file";
  196. if (system("if [ -f \"$src_fname\".001t.tu ]; then \
  197. mv \"$src_fname\".001t.tu $tmp_file; \
  198. else mv /tmp/\"$src_fname\".001t.tu $tmp_file; fi ") != 0) {
  199. unlink($tmp_file, "$tmp_file.o");
  200. die "could not find the gcc translation unit dump file" .
  201. " ($src_fname.001t.tu) neither in the current directory" .
  202. " or /tmp";
  203. };
  204. $tu=GCC::TranslationUnit::Parser->parsefile($tmp_file);
  205. print(STDERR "src name $src_fname\n") if $dbg;
  206. unlink($tmp_file, "$tmp_file.o");
  207. }else{
  208. $tu=GCC::TranslationUnit::Parser->parsefile($file);
  209. }
  210. print(STDERR "Parsing file $file...\n") if $dbg;
  211. #
  212. # function_decl: name, type, srcp (source), chan?, body?, link (e.g. extern)
  213. # parm_decl: name, type, scpe, srcp, chan, argt, size, algn, used
  214. # field_decl: name, type, scpe (scope), srcp, size, algn, bpos (bit pos?)
  215. #
  216. # array_type: size, algn, elts (elements?), domn ?
  217. #
  218. #
  219. # name as string $node->name->identifier
  220. #
  221. # E.g.: static cfg_def_t tcp_cfg_def[]= {...}
  222. # ^^^^^^^^^^^
  223. #
  224. # @7695 var_decl: name: @7705 type: @7706 srcp: tcp_options.c:51
  225. # chan: @7707 init: @7708 size: @7709
  226. # algn: 256 used: 1
  227. #
  228. # @7705 (var name) identifier_node strg: tcp_cfg_def lngt 11
  229. # @7706 (var type) array_type: size:@7709 algn: 32 elts: @2265 domn: @7718
  230. # @7707 (? next ? ) function_decl: ....
  231. # @7708 (initializer) constructor: lngt: 25
  232. # idx : @20 val : @7723 [...]
  233. # @7709 interget_cst: type: @11 low: 5600
  234. #
  235. # @2265 (array type) record_type: name: @2256 unql: @2255 size: @2002
  236. # algn: 32 tag : struct flds: @2263
  237. # @2256 (type) type_decl: name: @2264 type: @2265 srcp: cfg.h:73
  238. # chan: @2266
  239. # @2264 (name) identifier_node: strg: cfg_def_t
  240. print(STDERR "Searching...\n") if $dbg;
  241. $i=0;
  242. # iterate on the entire nodes array (returned by gcc), but skipping node 0
  243. SEARCH: for $node (@{$tu}[1..$#{$tu}]) {
  244. $i++;
  245. while($node) {
  246. if (
  247. @rpc_exports == 0 && # parse it only once
  248. $node->isa('GCC::Node::var_decl') &&
  249. $node->type->isa('GCC::Node::array_type') &&
  250. (! defined $src_fname || $src_fname eq "" ||
  251. $node->source=~"$src_fname")
  252. ){
  253. # found a var decl. that it's an array
  254. # check if it's a valid array type
  255. next if (!( $node->type->can('elements') &&
  256. defined $node->type->elements &&
  257. $node->type->elements->can('name') &&
  258. defined $node->type->elements->name &&
  259. $node->type->elements->name->can('name') &&
  260. defined $node->type->elements->name->name)
  261. );
  262. my $type_name= $node->type->elements->name->name->identifier;
  263. if ($type_name eq $var_type) {
  264. if ($node->can('initial') && defined $node->initial) {
  265. my %c1=%{$node->initial};
  266. $rpc_var_name=$node->name->identifier;
  267. if (defined $c1{val}){
  268. my $c1_el;
  269. die $patch_required if (ref($c1{val}) ne "ARRAY");
  270. # iterate on array elem., level 1( top {} )
  271. # each element is a constructor.
  272. # { name, callback, doc_var, flags }
  273. for $c1_el (@{$c1{val}}) {
  274. # finally we are a the lower {} initializer:
  275. # { name, callback, doc_var, flags }
  276. my %c2=%{$c1_el};
  277. my @el=@{$c2{val}};
  278. my ($name_n, $callback_n, $docvar_n, $flags_n)=@el;
  279. my ($name, $docvar, $flags);
  280. if ($name_n->isa('GCC::Node::integer_cst')){
  281. printf(" ERROR: integer non-0 name (%d)\n",
  282. $name_n->low) if ($name_n->low!=0);
  283. last SEARCH;
  284. }
  285. $name_n=expr_op0($name_n);
  286. $name= $name_n->string;
  287. $flags=$flags_n->low;
  288. # eliminate casts and expressions
  289. # (always go on the first operand)
  290. $docvar_n=expr_op0($docvar_n);
  291. $docvar=$docvar_n->name->identifier;
  292. push @rpc_exports, [$name, $docvar, $flags];
  293. }
  294. }
  295. }
  296. }
  297. }
  298. } continue {
  299. if ($node->can('chain')){
  300. $node = $node->chain;
  301. }else{
  302. last;
  303. }
  304. }
  305. }
  306. print(STDERR "Searching doc vars...\n") if $dbg;
  307. # look for docvars
  308. # re-iterate on the entire nodes array (returned by gcc), but skipping node 0
  309. DOC: for $node (@{$tu}[1..$#{$tu}]) {
  310. while(@rpc_exports>0 && $node) {
  311. if (
  312. $node->isa('GCC::Node::var_decl') &&
  313. $node->type->isa('GCC::Node::array_type') &&
  314. (! defined $src_fname || $src_fname eq "" ||
  315. $node->source=~"$src_fname") &&
  316. # var name is among the one we look for
  317. grep(${$_}[1] eq $node->name->identifier, @rpc_exports) > 0
  318. ){
  319. print(STDERR "found a candidate:", $node->name->identifier, "\n")
  320. if $dbg;
  321. # found a var decl. that it's an array
  322. # check if it's a valid array type
  323. next if (!( $node->type->can('elements') &&
  324. defined $node->type->elements)
  325. );
  326. if ($node->can('initial') && defined $node->initial){
  327. my %c1=%{$node->initial};
  328. my $doc_n = ${$c1{val}}[0];
  329. if (defined $doc_n){
  330. my $doc=expr_op0($doc_n)->string;
  331. $rpc_docs{$node->name->identifier}=$doc;
  332. last DOC if ( @rpc_exports == keys %rpc_docs );
  333. }
  334. }
  335. }
  336. } continue {
  337. if ($node->can('chain')){
  338. $node = $node->chain;
  339. }else{
  340. last;
  341. }
  342. }
  343. }
  344. print(STDERR "Done.\n") if $dbg;
  345. my ($name, $flags, $desc);
  346. my $extra_txt;
  347. if (@rpc_exports > 0){
  348. my $l;
  349. $i=0;
  350. if (@rpc_exports != keys %rpc_docs){
  351. print STDERR "Warning: missing ", @rpc_exports - keys %rpc_docs,
  352. " doc variables definitions\n";
  353. }
  354. # dump the configuration in txt mode
  355. if (defined $opt_force_grp_name) {
  356. $rpc_grp_name=output_esc($opt_force_grp_name);
  357. }elsif (!defined $rpc_grp_name && defined $opt_grp_name) {
  358. $rpc_grp_name=output_esc($opt_grp_name);
  359. }
  360. $~ = $output_format_header; write;
  361. $~ = $output_format_rpcline ;
  362. for $l (@rpc_exports){
  363. ($name, $desc, $flags)=@{$l};
  364. $extra_txt="";
  365. $desc=(defined $rpc_docs{$desc} && $rpc_docs{$desc} ne "")?
  366. output_esc($rpc_docs{$desc}):
  367. output_esc("Documentation missing ($desc).");
  368. $i++;
  369. $extra_txt.=output_esc("Returns an array.") if ($flags & 1 );
  370. $name=output_esc($name);
  371. # generate txt description
  372. write;
  373. }
  374. $~ = $output_format_footer; write;
  375. }else{
  376. die "no rpc exports found in $file\n";
  377. }
  378. sub valid_grp_name
  379. {
  380. my $name=shift;
  381. return defined $name && $name ne "";
  382. }
  383. format HEADER =
  384. RPC Exports@*
  385. (valid_grp_name $rpc_grp_name) ? " for " . $rpc_grp_name : ""
  386. ===========@*
  387. "=" x length((valid_grp_name $rpc_grp_name)?" for " . $rpc_grp_name : "")
  388. @||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
  389. "[ this file is autogenerated, do not edit ]"
  390. .
  391. format FOOTER =
  392. .
  393. format RPCLINE =
  394. @>. @*
  395. $i, $name
  396. ~~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  397. $desc
  398. ~~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  399. $extra_txt
  400. .
  401. format DOCBOOK_HEADER =
  402. <?xml version="1.0" encoding="UTF-8"?>
  403. <!-- this file is autogenerated, do not edit! -->
  404. <!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
  405. "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
  406. <chapter id="rpc_exports@*">
  407. (valid_grp_name $rpc_grp_name) ? "." . $rpc_grp_name : ""
  408. <title>
  409. RPC Exports@*
  410. (valid_grp_name $rpc_grp_name) ? " for " . $rpc_grp_name : ""
  411. </title>
  412. .
  413. format DOCBOOK_FOOTER =
  414. </chapter>
  415. .
  416. format DOCBOOK_RPCLINE =
  417. <section id="@*"><title>@*</title>
  418. $name, $name
  419. <para>
  420. ~~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  421. $desc
  422. </para>
  423. <para>
  424. ~~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  425. $extra_txt
  426. </para>
  427. </section>
  428. .