mirror of
https://github.com/samba-team/samba.git
synced 2025-12-17 04:23:50 +03:00
604 lines
13 KiB
Perl
604 lines
13 KiB
Perl
###################################################
|
|
# Samba3 NDR parser generator for IDL structures
|
|
# Copyright jelmer@samba.org 2005
|
|
# released under the GNU GPL
|
|
|
|
package Parse::Pidl::Samba3::Parser;
|
|
|
|
use strict;
|
|
use Parse::Pidl::Typelist qw(hasType getType mapType);
|
|
use Parse::Pidl::Util qw(has_property ParseExpr);
|
|
use Parse::Pidl::NDR qw(GetPrevLevel GetNextLevel ContainsDeferred);
|
|
use Parse::Pidl::Samba3::Types qw(DeclShort DeclLong InitType DissectType StringType);
|
|
|
|
use vars qw($VERSION);
|
|
$VERSION = '0.01';
|
|
|
|
use constant PRIMITIVES => 1;
|
|
use constant DEFERRED => 2;
|
|
|
|
my $res = "";
|
|
my $tabs = "";
|
|
sub indent() { $tabs.="\t"; }
|
|
sub deindent() { $tabs = substr($tabs, 1); }
|
|
sub pidl($) { $res .= $tabs.(shift)."\n"; }
|
|
sub fatal($$) { my ($e,$s) = @_; die("$e->{ORIGINAL}->{FILE}:$e->{ORIGINAL}->{LINE}: $s\n"); }
|
|
|
|
#TODO:
|
|
# - Add some security checks (array sizes, memory alloc == NULL, etc)
|
|
# - Don't add seperate _p and _d functions if there is no deferred data
|
|
# - [string] with non-varying arrays and "surrounding" strings
|
|
# - subcontext()
|
|
# - DATA_BLOB
|
|
|
|
sub Align($$)
|
|
{
|
|
my ($a,$b) = @_;
|
|
|
|
# Only align if previous element was smaller than current one
|
|
if ($$a < $b) {
|
|
pidl "if (!prs_align_custom(ps, $b))";
|
|
pidl "\treturn False;";
|
|
pidl "";
|
|
}
|
|
|
|
$$a = $b;
|
|
}
|
|
|
|
sub DeclareArrayVariables
|
|
{
|
|
my $es = shift;
|
|
my $what = shift;
|
|
|
|
my $output = 0;
|
|
|
|
foreach my $e (@$es) {
|
|
foreach my $l (@{$e->{LEVELS}}) {
|
|
if ($what) {
|
|
next if ($l->{IS_DEFERRED} and $what == PRIMITIVES);
|
|
next if (not $l->{IS_DEFERRED} and $what == DEFERRED);
|
|
}
|
|
if ($l->{TYPE} eq "ARRAY" and not $l->{IS_ZERO_TERMINATED}) {
|
|
pidl "uint32 i_$e->{NAME}_$l->{LEVEL_INDEX};";
|
|
$output = 1;
|
|
}
|
|
}
|
|
}
|
|
pidl "" if $output;
|
|
}
|
|
|
|
sub ParseElementLevelData($$$$$$$)
|
|
{
|
|
my ($e,$l,$nl,$env,$varname,$what,$align) = @_;
|
|
|
|
my $c = DissectType($e,$l,$varname,$what,$align);
|
|
return if not $c;
|
|
|
|
if (defined($e->{ALIGN})) {
|
|
Align($align, $e->{ALIGN});
|
|
} else {
|
|
# Default to 4
|
|
Align($align, 4);
|
|
}
|
|
|
|
pidl "if (!$c)";
|
|
pidl "\treturn False;";
|
|
}
|
|
|
|
sub ParseElementLevelArray($$$$$$$)
|
|
{
|
|
my ($e,$l,$nl,$env,$varname,$what,$align) = @_;
|
|
|
|
if ($l->{IS_ZERO_TERMINATED}) {
|
|
return if ($what == DEFERRED);
|
|
|
|
my ($t,$f) = StringType($e,$l);
|
|
|
|
Align($align, 4);
|
|
pidl "if (!smb_io_$t(\"$e->{NAME}\", &$varname, 1, ps, depth))";
|
|
pidl "\treturn False;";
|
|
|
|
$$align = 0;
|
|
return;
|
|
}
|
|
|
|
my $len = ParseExpr($l->{LENGTH_IS}, $env);
|
|
my $size = ParseExpr($l->{SIZE_IS}, $env);
|
|
|
|
if ($what == PRIMITIVES) {
|
|
# Fetch headers
|
|
if ($l->{IS_CONFORMANT} and not $l->{IS_SURROUNDING}) {
|
|
Align($align, 4);
|
|
pidl "if (!prs_uint32(\"size_$e->{NAME}\", ps, depth, &" . ParseExpr("size_$e->{NAME}", $env) . "))";
|
|
pidl "\treturn False;";
|
|
pidl "";
|
|
}
|
|
|
|
if ($l->{IS_VARYING}) {
|
|
Align($align, 4);
|
|
pidl "if (!prs_uint32(\"offset_$e->{NAME}\", ps, depth, &" . ParseExpr("offset_$e->{NAME}", $env) . "))";
|
|
pidl "\treturn False;";
|
|
pidl "";
|
|
|
|
pidl "if (!prs_uint32(\"length_$e->{NAME}\", ps, depth, &" . ParseExpr("length_$e->{NAME}", $env) . "))";
|
|
pidl "\treturn False;";
|
|
pidl "";
|
|
}
|
|
}
|
|
|
|
# Everything but fixed arrays have to be allocated
|
|
if (!$l->{IS_FIXED} and $what == PRIMITIVES) {
|
|
pidl "if (UNMARSHALLING(ps)) {";
|
|
indent;
|
|
pidl "$varname = (void *)PRS_ALLOC_MEM_VOID(ps,sizeof(*$varname)*$size);";
|
|
deindent;
|
|
pidl "}";
|
|
}
|
|
|
|
return if ($what == DEFERRED and not ContainsDeferred($e,$l));
|
|
|
|
my $i = "i_$e->{NAME}_$l->{LEVEL_INDEX}";
|
|
pidl "for ($i=0; $i<$len;$i++) {";
|
|
indent;
|
|
ParseElementLevel($e,$nl,$env,$varname."[$i]",$what,$align);
|
|
deindent;
|
|
pidl "}";
|
|
}
|
|
|
|
sub ParseElementLevelSwitch($$$$$$$)
|
|
{
|
|
my ($e,$l,$nl,$env,$varname,$what,$align) = @_;
|
|
|
|
ParseElementLevel($e,$nl,$env,$varname,$what,$align);
|
|
}
|
|
|
|
sub ParseElementLevelPtr($$$$$$$)
|
|
{
|
|
my ($e,$l,$nl,$env,$varname,$what,$align) = @_;
|
|
|
|
if ($what == PRIMITIVES) {
|
|
if (($l->{POINTER_TYPE} eq "ref") and ($l->{LEVEL} eq "EMBEDDED")) {
|
|
# Ref pointers always have to be non-NULL
|
|
pidl "if (MARSHALLING(ps) && !" . ParseExpr("ptr$l->{POINTER_INDEX}_$e->{NAME}", $env) . ")";
|
|
pidl "\treturn False;";
|
|
pidl "";
|
|
}
|
|
|
|
unless ($l->{POINTER_TYPE} eq "ref" and $l->{LEVEL} eq "TOP") {
|
|
Align($align, 4);
|
|
pidl "if (!prs_uint32(\"ptr$l->{POINTER_INDEX}_$e->{NAME}\", ps, depth, &" . ParseExpr("ptr$l->{POINTER_INDEX}_$e->{NAME}", $env) . "))";
|
|
pidl "\treturn False;";
|
|
pidl "";
|
|
}
|
|
}
|
|
|
|
if ($l->{POINTER_TYPE} eq "relative") {
|
|
fatal($e, "relative pointers not supported for Samba 3");
|
|
#FIXME
|
|
}
|
|
|
|
if ($what == DEFERRED) {
|
|
if ($l->{POINTER_TYPE} ne "ref") {
|
|
pidl "if (" . ParseExpr("ptr$l->{POINTER_INDEX}_$e->{NAME}", $env) . ") {";
|
|
indent;
|
|
}
|
|
ParseElementLevel($e,$nl,$env,$varname,PRIMITIVES,$align);
|
|
ParseElementLevel($e,$nl,$env,$varname,DEFERRED,$align);
|
|
if ($l->{POINTER_TYPE} ne "ref") {
|
|
deindent;
|
|
pidl "}";
|
|
}
|
|
$$align = 0;
|
|
}
|
|
}
|
|
|
|
sub ParseElementLevelSubcontext($$$$$$$)
|
|
{
|
|
my ($e,$l,$nl,$env,$varname,$what,$align) = @_;
|
|
|
|
fatal($e, "subcontext() not supported for Samba 3");
|
|
#FIXME
|
|
}
|
|
|
|
sub ParseElementLevel($$$$$$)
|
|
{
|
|
my ($e,$l,$env,$varname,$what,$align) = @_;
|
|
|
|
{
|
|
DATA => \&ParseElementLevelData,
|
|
SUBCONTEXT => \&ParseElementLevelSubcontext,
|
|
POINTER => \&ParseElementLevelPtr,
|
|
SWITCH => \&ParseElementLevelSwitch,
|
|
ARRAY => \&ParseElementLevelArray
|
|
}->{$l->{TYPE}}->($e,$l,GetNextLevel($e,$l),$env,$varname,$what,$align);
|
|
}
|
|
|
|
sub ParseElement($$$$)
|
|
{
|
|
my ($e,$env,$what,$align) = @_;
|
|
|
|
ParseElementLevel($e, $e->{LEVELS}[0], $env, ParseExpr($e->{NAME}, $env), $what, $align);
|
|
}
|
|
|
|
sub InitLevel($$$$)
|
|
{
|
|
sub InitLevel($$$$);
|
|
my ($e,$l,$varname,$env) = @_;
|
|
|
|
if ($l->{TYPE} eq "POINTER") {
|
|
if ($l->{POINTER_TYPE} eq "ref") {
|
|
pidl "if (!$varname)";
|
|
pidl "\treturn False;";
|
|
pidl "";
|
|
} else {
|
|
pidl "if ($varname) {";
|
|
indent;
|
|
}
|
|
|
|
unless ($l->{POINTER_TYPE} eq "ref" and $l->{LEVEL} eq "TOP") {
|
|
pidl ParseExpr("ptr$l->{POINTER_INDEX}_$e->{NAME}", $env) . " = 1;";
|
|
}
|
|
InitLevel($e, GetNextLevel($e,$l), "*$varname", $env);
|
|
|
|
if ($l->{POINTER_TYPE} ne "ref") {
|
|
deindent;
|
|
pidl "} else {";
|
|
pidl "\t" . ParseExpr("ptr$l->{POINTER_INDEX}_$e->{NAME}", $env) . " = 0;";
|
|
pidl "}";
|
|
}
|
|
} elsif ($l->{TYPE} eq "ARRAY" and $l->{IS_ZERO_TERMINATED}) {
|
|
my ($t,$f) = StringType($e,$l);
|
|
pidl "init_$t(&" . ParseExpr($e->{NAME}, $env) . ", ".substr($varname, 1) . ", $f);";
|
|
} elsif ($l->{TYPE} eq "ARRAY") {
|
|
pidl ParseExpr($e->{NAME}, $env) . " = $varname;";
|
|
} elsif ($l->{TYPE} eq "DATA") {
|
|
pidl InitType($e, $l, ParseExpr($e->{NAME}, $env), $varname);
|
|
} elsif ($l->{TYPE} eq "SWITCH") {
|
|
InitLevel($e, GetNextLevel($e,$l), $varname, $env);
|
|
pidl ParseExpr($e->{NAME}, $env) . ".switch_value = " . ParseExpr($l->{SWITCH_IS}, $env) . ";";
|
|
}
|
|
}
|
|
|
|
sub GenerateEnvElement($$)
|
|
{
|
|
my ($e,$env) = @_;
|
|
foreach my $l (@{$e->{LEVELS}}) {
|
|
if ($l->{TYPE} eq "DATA") {
|
|
$env->{$e->{NAME}} = "v->$e->{NAME}";
|
|
} elsif ($l->{TYPE} eq "POINTER") {
|
|
$env->{"ptr$l->{POINTER_INDEX}_$e->{NAME}"} = "v->ptr$l->{POINTER_INDEX}_$e->{NAME}";
|
|
} elsif ($l->{TYPE} eq "SWITCH") {
|
|
} elsif ($l->{TYPE} eq "ARRAY" and not $l->{IS_ZERO_TERMINATED}) {
|
|
$env->{"length_$e->{NAME}"} = "v->length_$e->{NAME}";
|
|
$env->{"size_$e->{NAME}"} = "v->size_$e->{NAME}";
|
|
$env->{"offset_$e->{NAME}"} = "v->offset_$e->{NAME}";
|
|
}
|
|
}
|
|
}
|
|
|
|
sub ParseStruct($$$)
|
|
{
|
|
my ($if,$s,$n) = @_;
|
|
|
|
my $fn = "$if->{NAME}_io_$n";
|
|
my $sn = uc("$if->{NAME}_$n");
|
|
my $ifn = "init_$if->{NAME}_$n";
|
|
|
|
my $args = "";
|
|
foreach (@{$s->{ELEMENTS}}) {
|
|
$args .= ", " . DeclLong($_);
|
|
}
|
|
|
|
my $env = { "this" => "v" };
|
|
GenerateEnvElement($_, $env) foreach (@{$s->{ELEMENTS}});
|
|
|
|
pidl "BOOL $ifn($sn *v$args)";
|
|
pidl "{";
|
|
indent;
|
|
pidl "DEBUG(5,(\"$ifn\\n\"));";
|
|
pidl "";
|
|
# Call init for all arguments
|
|
foreach (@{$s->{ELEMENTS}}) {
|
|
InitLevel($_, $_->{LEVELS}[0], $_->{NAME}, $env);
|
|
pidl "";
|
|
}
|
|
pidl "return True;";
|
|
deindent;
|
|
pidl "}";
|
|
pidl "";
|
|
|
|
my $pfn = "$fn\_p";
|
|
my $dfn = "$fn\_d";
|
|
|
|
pidl "BOOL $pfn(const char *desc, $sn *v, prs_struct *ps, int depth)";
|
|
pidl "{";
|
|
indent;
|
|
DeclareArrayVariables($s->{ELEMENTS}, PRIMITIVES);
|
|
pidl "if (v == NULL)";
|
|
pidl "\treturn False;";
|
|
pidl "";
|
|
pidl "prs_debug(ps, depth, desc, \"$pfn\");";
|
|
pidl "depth++;";
|
|
|
|
my $align = 8;
|
|
if ($s->{SURROUNDING_ELEMENT}) {
|
|
pidl "if (!prs_uint32(\"size_$s->{SURROUNDING_ELEMENT}->{NAME}\", ps, depth, &" . ParseExpr("size_$s->{SURROUNDING_ELEMENT}->{NAME}", $env) . "))";
|
|
pidl "\treturn False;";
|
|
pidl "";
|
|
$align = 4;
|
|
|
|
}
|
|
|
|
foreach (@{$s->{ELEMENTS}}) {
|
|
ParseElement($_, $env, PRIMITIVES, \$align);
|
|
pidl "";
|
|
}
|
|
|
|
pidl "return True;";
|
|
deindent;
|
|
pidl "}";
|
|
pidl "";
|
|
|
|
pidl "BOOL $dfn(const char *desc, $sn *v, prs_struct *ps, int depth)";
|
|
pidl "{";
|
|
indent;
|
|
DeclareArrayVariables($s->{ELEMENTS}, DEFERRED);
|
|
pidl "if (v == NULL)";
|
|
pidl "\treturn False;";
|
|
pidl "";
|
|
pidl "prs_debug(ps, depth, desc, \"$dfn\");";
|
|
pidl "depth++;";
|
|
|
|
$align = 0;
|
|
foreach (@{$s->{ELEMENTS}}) {
|
|
ParseElement($_, $env, DEFERRED, \$align);
|
|
pidl "";
|
|
}
|
|
|
|
pidl "return True;";
|
|
deindent;
|
|
pidl "}";
|
|
pidl "";
|
|
}
|
|
|
|
sub UnionGenerateEnvElement($)
|
|
{
|
|
my $e = shift;
|
|
my $env = {};
|
|
|
|
foreach my $l (@{$e->{LEVELS}}) {
|
|
if ($l->{TYPE} eq "DATA") {
|
|
$env->{$e->{NAME}} = "v->u.$e->{NAME}";
|
|
} elsif ($l->{TYPE} eq "POINTER") {
|
|
$env->{"ptr$l->{POINTER_INDEX}_$e->{NAME}"} = "v->ptr$l->{POINTER_INDEX}";
|
|
} elsif ($l->{TYPE} eq "SWITCH") {
|
|
} elsif ($l->{TYPE} eq "ARRAY" and not $l->{IS_ZERO_TERMINATED}) {
|
|
$env->{"length_$e->{NAME}"} = "v->length";
|
|
$env->{"size_$e->{NAME}"} = "v->size";
|
|
$env->{"offset_$e->{NAME}"} = "v->offset";
|
|
}
|
|
}
|
|
|
|
return $env;
|
|
}
|
|
|
|
sub ParseUnion($$$)
|
|
{
|
|
my ($if,$u,$n) = @_;
|
|
|
|
my $fn = "$if->{NAME}_io_$n";
|
|
my $sn = uc("$if->{NAME}_$n\_ctr");
|
|
|
|
my $pfn = "$fn\_p";
|
|
my $dfn = "$fn\_d";
|
|
|
|
pidl "BOOL $pfn(const char *desc, $sn* v, prs_struct *ps, int depth)";
|
|
pidl "{";
|
|
indent;
|
|
DeclareArrayVariables($u->{ELEMENTS});
|
|
|
|
if (defined ($u->{SWITCH_TYPE})) {
|
|
pidl "if (!prs_$u->{SWITCH_TYPE}(\"switch_value\", ps, depth, &v->switch_value))";
|
|
pidl "\treturn False;";
|
|
pidl "";
|
|
}
|
|
|
|
# Maybe check here that level and v->switch_value are equal?
|
|
|
|
pidl "switch (v->switch_value) {";
|
|
indent;
|
|
|
|
foreach (@{$u->{ELEMENTS}}) {
|
|
pidl "$_->{CASE}:";
|
|
indent;
|
|
if ($_->{TYPE} ne "EMPTY") {
|
|
pidl "depth++;";
|
|
my $env = UnionGenerateEnvElement($_);
|
|
my $align = 8;
|
|
ParseElement($_, $env, PRIMITIVES, \$align);
|
|
pidl "depth--;";
|
|
}
|
|
pidl "break;";
|
|
deindent;
|
|
pidl "";
|
|
}
|
|
|
|
unless ($u->{HAS_DEFAULT}) {
|
|
pidl "default:";
|
|
pidl "\treturn False;";
|
|
pidl "";
|
|
}
|
|
|
|
deindent;
|
|
pidl "}";
|
|
pidl "";
|
|
pidl "return True;";
|
|
deindent;
|
|
pidl "}";
|
|
pidl "";
|
|
|
|
pidl "BOOL $dfn(const char *desc, $sn* v, prs_struct *ps, int depth)";
|
|
pidl "{";
|
|
indent;
|
|
DeclareArrayVariables($u->{ELEMENTS});
|
|
|
|
if (defined($u->{SWITCH_TYPE})) {
|
|
pidl "switch (v->switch_value) {";
|
|
} else {
|
|
pidl "switch (level) {";
|
|
}
|
|
indent;
|
|
|
|
foreach (@{$u->{ELEMENTS}}) {
|
|
pidl "$_->{CASE}:";
|
|
indent;
|
|
if ($_->{TYPE} ne "EMPTY") {
|
|
pidl "depth++;";
|
|
my $env = UnionGenerateEnvElement($_);
|
|
my $align = 0;
|
|
ParseElement($_, $env, DEFERRED, \$align);
|
|
pidl "depth--;";
|
|
}
|
|
pidl "break;";
|
|
deindent;
|
|
pidl "";
|
|
}
|
|
|
|
deindent;
|
|
pidl "}";
|
|
pidl "";
|
|
pidl "return True;";
|
|
deindent;
|
|
pidl "}";
|
|
|
|
}
|
|
|
|
sub CreateFnDirection($$$$$)
|
|
{
|
|
my ($fn,$ifn,$s,$all,$es) = @_;
|
|
|
|
my $args = "";
|
|
foreach (@$all) { $args .= ", " . DeclLong($_); }
|
|
|
|
my $env = { };
|
|
GenerateEnvElement($_, $env) foreach (@$es);
|
|
|
|
pidl "BOOL $ifn($s *v$args)";
|
|
pidl "{";
|
|
indent;
|
|
pidl "DEBUG(5,(\"$ifn\\n\"));";
|
|
pidl "";
|
|
# Call init for all arguments
|
|
foreach (@$es) {
|
|
InitLevel($_, $_->{LEVELS}[0], $_->{NAME}, $env);
|
|
pidl "";
|
|
}
|
|
pidl "return True;";
|
|
deindent;
|
|
pidl "}";
|
|
pidl "";
|
|
|
|
pidl "BOOL $fn(const char *desc, $s *v, prs_struct *ps, int depth)";
|
|
pidl "{";
|
|
indent;
|
|
DeclareArrayVariables($es);
|
|
pidl "if (v == NULL)";
|
|
pidl "\treturn False;";
|
|
pidl "";
|
|
pidl "prs_debug(ps, depth, desc, \"$fn\");";
|
|
pidl "depth++;";
|
|
|
|
my $align = 8;
|
|
foreach (@$es) {
|
|
ParseElement($_, $env, PRIMITIVES, \$align);
|
|
ParseElement($_, $env, DEFERRED, \$align);
|
|
pidl "";
|
|
}
|
|
|
|
pidl "return True;";
|
|
deindent;
|
|
pidl "}";
|
|
pidl "";
|
|
}
|
|
|
|
sub ParseFunction($$)
|
|
{
|
|
my ($if,$fn) = @_;
|
|
|
|
my @in = ();
|
|
my @out = ();
|
|
my @all = @{$fn->{ELEMENTS}};
|
|
|
|
foreach (@{$fn->{ELEMENTS}}) {
|
|
push (@in, $_) if (grep(/in/, @{$_->{DIRECTION}}));
|
|
push (@out, $_) if (grep(/out/, @{$_->{DIRECTION}}));
|
|
}
|
|
|
|
if (defined($fn->{RETURN_TYPE})) {
|
|
my $status = {
|
|
NAME => "status",
|
|
TYPE => $fn->{RETURN_TYPE},
|
|
LEVELS => [
|
|
{
|
|
TYPE => "DATA",
|
|
DATA_TYPE => $fn->{RETURN_TYPE}
|
|
}
|
|
]
|
|
};
|
|
|
|
push (@out, $status);
|
|
push (@all, $status);
|
|
}
|
|
|
|
CreateFnDirection("$if->{NAME}_io_q_$fn->{NAME}",
|
|
"init_$if->{NAME}_q_$fn->{NAME}",
|
|
uc("$if->{NAME}_q_$fn->{NAME}"),
|
|
\@in, \@in);
|
|
CreateFnDirection("$if->{NAME}_io_r_$fn->{NAME}",
|
|
"init_$if->{NAME}_r_$fn->{NAME}",
|
|
uc("$if->{NAME}_r_$fn->{NAME}"),
|
|
\@all, \@out);
|
|
}
|
|
|
|
sub ParseInterface($)
|
|
{
|
|
my $if = shift;
|
|
|
|
# Structures first
|
|
pidl "/* $if->{NAME} structures */";
|
|
foreach (@{$if->{TYPES}}) {
|
|
ParseStruct($if, $_->{DATA}, $_->{NAME}) if ($_->{DATA}->{TYPE} eq "STRUCT");
|
|
ParseUnion($if, $_->{DATA}, $_->{NAME}) if ($_->{DATA}->{TYPE} eq "UNION");
|
|
}
|
|
|
|
pidl "/* $if->{NAME} functions */";
|
|
ParseFunction($if, $_) foreach (@{$if->{FUNCTIONS}});
|
|
}
|
|
|
|
sub Parse($$)
|
|
{
|
|
my($ndr,$filename) = @_;
|
|
|
|
$tabs = "";
|
|
$res = "";
|
|
|
|
pidl "/*";
|
|
pidl " * Unix SMB/CIFS implementation.";
|
|
pidl " * parser auto-generated by pidl. DO NOT MODIFY!";
|
|
pidl " */";
|
|
pidl "";
|
|
pidl "#include \"includes.h\"";
|
|
pidl "";
|
|
pidl "#undef DBGC_CLASS";
|
|
pidl "#define DBGC_CLASS DBGC_RPC_PARSE";
|
|
pidl "";
|
|
|
|
foreach (@$ndr) {
|
|
ParseInterface($_) if ($_->{TYPE} eq "INTERFACE");
|
|
}
|
|
|
|
return $res;
|
|
}
|
|
|
|
1;
|