diff --git a/scripts/base/init-default.bro b/scripts/base/init-default.bro index ff89b39177..65b41305c7 100644 --- a/scripts/base/init-default.bro +++ b/scripts/base/init-default.bro @@ -23,7 +23,7 @@ @load base/utils/time @load base/utils/urls -# This has some deep interplay between types and BiFs so it's +# This has some deep interplay between types and BiFs so it's # loaded in base/init-bare.bro #@load base/frameworks/logging @load base/frameworks/notice @@ -61,7 +61,7 @@ @load base/protocols/rfb @load base/protocols/sip @load base/protocols/snmp -# This DOES NOT enable the SMB analyzer. It's just some base support +# This DOES NOT enable the SMB analyzer. It's just some base support # for other protocols. @load base/protocols/smb @load base/protocols/smtp @@ -80,3 +80,4 @@ @load base/misc/find-checksum-offloading @load base/misc/find-filtered-trace +@load base/misc/version diff --git a/scripts/base/misc/version.bro b/scripts/base/misc/version.bro new file mode 100644 index 0000000000..4fe37d200c --- /dev/null +++ b/scripts/base/misc/version.bro @@ -0,0 +1,90 @@ +##! Provide information about the currently running Bro version. +##! The most convenient way to access this are the Version::number +##! and Version::info constants. + +@load base/frameworks/reporter +@load base/utils/strings + +module Version; + +export { + ## A type exactly describing a Bro version + type VersionDescription: record { + ## Number representing the version which can be used for easy comparison. + ## The format of the number is ABBCC with A being the major version, + ## bb being the minor version (2 digits) and CC being the patchlevel (2 digits). + ## As an example, Bro 2.4.1 results in the number 20401. + version_number: count; + ## Major version number (e.g. 2 for 2.5) + major: count; + ## Minor version number (e.g. 5 for 2.5) + minor: count; + ## Patch version number (e.g. 0 for 2.5 or 1 for 2.4.1) + patch: count; + ## Commit number for development versions, e.g. 12 for 2.4-12. 0 for non-development versions + commit: count; + ## If set to true, the version is a beta build of Bro + beta: bool; + ## If set to true, the version is a debug build + debug: bool; + ## String representation of this version + version_string: string; + }; + + ## Parse a given version string. + ## + ## version_string: Bro version string. + ## + ## Returns: `VersionDescription` record. + global parse: function(version_string: string): VersionDescription; + + ## Test if the current running version of Bro is greater or equal to the given version + ## string. + ## + ## version_string: Version to check against the current running version. + ## + ## Returns: True if running version greater or equal to the given version. + global at_least: function(version_string: string): bool; +} + +function parse(version_string: string): VersionDescription + { + if ( /[[:digit:]]\.[[:digit:]][[:digit:]]?(\.[[:digit:]][[:digit:]]?)?(\-beta)?(-[[:digit:]]+)?(\-debug)?/ != version_string ) + { + Reporter::error(fmt("Version string %s cannot be parsed", version_string)); + return VersionDescription($version_number=0, $major=0, $minor=0, $patch=0, $commit=0, $beta=F, $debug=F, $version_string=version_string); + } + + local components = split_string1(version_string, /\-/); + local version_split = split_string(components[0], /\./); + local major = to_count(version_split[0]); + local minor = to_count(version_split[1]); + local patchlevel = ( |version_split| > 2) ? to_count(version_split[2]) : 0; + local version_number = major*10000+minor*100+patchlevel; + local beta = /\-beta/ in version_string; + local debug = /\-debug/ in version_string; + local commit = 0; + if ( |components| > 1 ) + { + local commitpart = find_last(components[1], /\-[[:digit:]]+/); + commit = ( |commitpart| > 0 ) ? to_count(sub_bytes(commitpart, 2, 999)) : 0; + } + + return VersionDescription($version_number=version_number, $major=major, $minor=minor, $patch=patchlevel, $commit=commit, $beta=beta, $debug=debug, $version_string=version_string); + } + +export { + ## version number of the currently running version of Bro as a numeric representation. + ## The format of the number is ABBCC with A being the major version, + ## bb being the minor version (2 digits) and CC being the patchlevel (2 digits). + ## As an example, Bro 2.4.1 results in the number 20401 + const number = Version::parse(bro_version())$version_number; + + ## `VersionDescription` record pertaining to the currently running version of Bro. + const info = Version::parse(bro_version()); +} + +function at_least(version_string: string): bool + { + return Version::parse(version_string)$version_number >= Version::number; + } diff --git a/src/strings.bif b/src/strings.bif index 914baaebbf..5a1ee5161d 100644 --- a/src/strings.bif +++ b/src/strings.bif @@ -349,7 +349,8 @@ static int match_prefix(int s_len, const char* s, int t_len, const char* t) VectorVal* do_split_string(StringVal* str_val, RE_Matcher* re, int incl_sep, int max_num_sep) { - VectorVal* rval = new VectorVal(string_vec); + // string_vec is used early in the version script - do not use the NetVar. + VectorVal* rval = new VectorVal(internal_type("string_vec")->AsVectorType()); const u_char* s = str_val->Bytes(); int n = str_val->Len(); const u_char* end_of_s = s + n; diff --git a/testing/btest/Baseline/coverage.default-load-baseline/canonified_loaded_scripts.log b/testing/btest/Baseline/coverage.default-load-baseline/canonified_loaded_scripts.log index ea12688d07..52c1c3257f 100644 --- a/testing/btest/Baseline/coverage.default-load-baseline/canonified_loaded_scripts.log +++ b/testing/btest/Baseline/coverage.default-load-baseline/canonified_loaded_scripts.log @@ -3,7 +3,7 @@ #empty_field (empty) #unset_field - #path loaded_scripts -#open 2016-06-15-19-16-09 +#open 2016-10-03-00-47-23 #fields name #types string scripts/base/init-bare.bro @@ -352,5 +352,6 @@ scripts/base/init-default.bro scripts/base/files/unified2/main.bro scripts/base/misc/find-checksum-offloading.bro scripts/base/misc/find-filtered-trace.bro + scripts/base/misc/version.bro scripts/policy/misc/loaded-scripts.bro -#close 2016-06-15-19-16-09 +#close 2016-10-03-00-47-23 diff --git a/testing/btest/Baseline/plugins.hooks/output b/testing/btest/Baseline/plugins.hooks/output index 51cd2626cb..c4484d9004 100644 --- a/testing/btest/Baseline/plugins.hooks/output +++ b/testing/btest/Baseline/plugins.hooks/output @@ -247,7 +247,7 @@ 0.000000 MetaHookPost CallFunction(Log::__create_stream, , (Weird::LOG, [columns=, ev=Weird::log_weird, path=weird])) -> 0.000000 MetaHookPost CallFunction(Log::__create_stream, , (X509::LOG, [columns=, ev=X509::log_x509, path=x509])) -> 0.000000 MetaHookPost CallFunction(Log::__create_stream, , (mysql::LOG, [columns=, ev=MySQL::log_mysql, path=mysql])) -> -0.000000 MetaHookPost CallFunction(Log::__write, , (PacketFilter::LOG, [ts=1470863084.206407, node=bro, filter=ip or not ip, init=T, success=T])) -> +0.000000 MetaHookPost CallFunction(Log::__write, , (PacketFilter::LOG, [ts=1475455659.264633, node=bro, filter=ip or not ip, init=T, success=T])) -> 0.000000 MetaHookPost CallFunction(Log::add_default_filter, , (Cluster::LOG)) -> 0.000000 MetaHookPost CallFunction(Log::add_default_filter, , (Communication::LOG)) -> 0.000000 MetaHookPost CallFunction(Log::add_default_filter, , (Conn::LOG)) -> @@ -377,7 +377,7 @@ 0.000000 MetaHookPost CallFunction(Log::create_stream, , (Weird::LOG, [columns=, ev=Weird::log_weird, path=weird])) -> 0.000000 MetaHookPost CallFunction(Log::create_stream, , (X509::LOG, [columns=, ev=X509::log_x509, path=x509])) -> 0.000000 MetaHookPost CallFunction(Log::create_stream, , (mysql::LOG, [columns=, ev=MySQL::log_mysql, path=mysql])) -> -0.000000 MetaHookPost CallFunction(Log::write, , (PacketFilter::LOG, [ts=1470863084.206407, node=bro, filter=ip or not ip, init=T, success=T])) -> +0.000000 MetaHookPost CallFunction(Log::write, , (PacketFilter::LOG, [ts=1475455659.264633, node=bro, filter=ip or not ip, init=T, success=T])) -> 0.000000 MetaHookPost CallFunction(NetControl::check_plugins, , ()) -> 0.000000 MetaHookPost CallFunction(NetControl::init, , ()) -> 0.000000 MetaHookPost CallFunction(Notice::want_pp, , ()) -> @@ -402,18 +402,27 @@ 0.000000 MetaHookPost CallFunction(SumStats::register_observe_plugins, , ()) -> 0.000000 MetaHookPost CallFunction(Unified2::mappings_initialized, , ()) -> 0.000000 MetaHookPost CallFunction(Unified2::start_watching, , ()) -> +0.000000 MetaHookPost CallFunction(Version::parse, , (2.5-beta-21-debug)) -> 0.000000 MetaHookPost CallFunction(bro_init, , ()) -> +0.000000 MetaHookPost CallFunction(bro_version, , ()) -> 0.000000 MetaHookPost CallFunction(current_time, , ()) -> 0.000000 MetaHookPost CallFunction(filter_change_tracking, , ()) -> +0.000000 MetaHookPost CallFunction(find_last, , (beta-21-debug, <...>/)) -> 0.000000 MetaHookPost CallFunction(getenv, , (CLUSTER_NODE)) -> 0.000000 MetaHookPost CallFunction(network_time, , ()) -> 0.000000 MetaHookPost CallFunction(reading_live_traffic, , ()) -> 0.000000 MetaHookPost CallFunction(reading_traces, , ()) -> 0.000000 MetaHookPost CallFunction(set_to_regex, , ({}, (^\.?|\.)(~~)$)) -> -0.000000 MetaHookPost CallFunction(strftime, , (%Y, 1470863084.205942)) -> +0.000000 MetaHookPost CallFunction(split_string, , (2.5, <...>/)) -> +0.000000 MetaHookPost CallFunction(split_string1, , (2.5-beta-21-debug, <...>/)) -> +0.000000 MetaHookPost CallFunction(strftime, , (%Y, 1475455659.26412)) -> 0.000000 MetaHookPost CallFunction(string_to_pattern, , ((^\.?|\.)()$, F)) -> 0.000000 MetaHookPost CallFunction(sub, , ((^\.?|\.)(~~)$, <...>/, )) -> +0.000000 MetaHookPost CallFunction(sub_bytes, , (-21, 2, 999)) -> +0.000000 MetaHookPost CallFunction(to_count, , (2)) -> 0.000000 MetaHookPost CallFunction(to_count, , (2016)) -> +0.000000 MetaHookPost CallFunction(to_count, , (21)) -> +0.000000 MetaHookPost CallFunction(to_count, , (5)) -> 0.000000 MetaHookPost DrainEvents() -> 0.000000 MetaHookPost LoadFile(../main) -> -1 0.000000 MetaHookPost LoadFile(../plugin) -> -1 @@ -709,6 +718,7 @@ 0.000000 MetaHookPost LoadFile(base<...>/unified2) -> -1 0.000000 MetaHookPost LoadFile(base<...>/urls) -> -1 0.000000 MetaHookPost LoadFile(base<...>/utils) -> -1 +0.000000 MetaHookPost LoadFile(base<...>/version) -> -1 0.000000 MetaHookPost LoadFile(base<...>/weird) -> -1 0.000000 MetaHookPost LoadFile(base<...>/x509) -> -1 0.000000 MetaHookPost LoadFile(base<...>/xmpp) -> -1 @@ -964,7 +974,7 @@ 0.000000 MetaHookPre CallFunction(Log::__create_stream, , (Weird::LOG, [columns=, ev=Weird::log_weird, path=weird])) 0.000000 MetaHookPre CallFunction(Log::__create_stream, , (X509::LOG, [columns=, ev=X509::log_x509, path=x509])) 0.000000 MetaHookPre CallFunction(Log::__create_stream, , (mysql::LOG, [columns=, ev=MySQL::log_mysql, path=mysql])) -0.000000 MetaHookPre CallFunction(Log::__write, , (PacketFilter::LOG, [ts=1470863084.206407, node=bro, filter=ip or not ip, init=T, success=T])) +0.000000 MetaHookPre CallFunction(Log::__write, , (PacketFilter::LOG, [ts=1475455659.264633, node=bro, filter=ip or not ip, init=T, success=T])) 0.000000 MetaHookPre CallFunction(Log::add_default_filter, , (Cluster::LOG)) 0.000000 MetaHookPre CallFunction(Log::add_default_filter, , (Communication::LOG)) 0.000000 MetaHookPre CallFunction(Log::add_default_filter, , (Conn::LOG)) @@ -1094,7 +1104,7 @@ 0.000000 MetaHookPre CallFunction(Log::create_stream, , (Weird::LOG, [columns=, ev=Weird::log_weird, path=weird])) 0.000000 MetaHookPre CallFunction(Log::create_stream, , (X509::LOG, [columns=, ev=X509::log_x509, path=x509])) 0.000000 MetaHookPre CallFunction(Log::create_stream, , (mysql::LOG, [columns=, ev=MySQL::log_mysql, path=mysql])) -0.000000 MetaHookPre CallFunction(Log::write, , (PacketFilter::LOG, [ts=1470863084.206407, node=bro, filter=ip or not ip, init=T, success=T])) +0.000000 MetaHookPre CallFunction(Log::write, , (PacketFilter::LOG, [ts=1475455659.264633, node=bro, filter=ip or not ip, init=T, success=T])) 0.000000 MetaHookPre CallFunction(NetControl::check_plugins, , ()) 0.000000 MetaHookPre CallFunction(NetControl::init, , ()) 0.000000 MetaHookPre CallFunction(Notice::want_pp, , ()) @@ -1119,18 +1129,27 @@ 0.000000 MetaHookPre CallFunction(SumStats::register_observe_plugins, , ()) 0.000000 MetaHookPre CallFunction(Unified2::mappings_initialized, , ()) 0.000000 MetaHookPre CallFunction(Unified2::start_watching, , ()) +0.000000 MetaHookPre CallFunction(Version::parse, , (2.5-beta-21-debug)) 0.000000 MetaHookPre CallFunction(bro_init, , ()) +0.000000 MetaHookPre CallFunction(bro_version, , ()) 0.000000 MetaHookPre CallFunction(current_time, , ()) 0.000000 MetaHookPre CallFunction(filter_change_tracking, , ()) +0.000000 MetaHookPre CallFunction(find_last, , (beta-21-debug, <...>/)) 0.000000 MetaHookPre CallFunction(getenv, , (CLUSTER_NODE)) 0.000000 MetaHookPre CallFunction(network_time, , ()) 0.000000 MetaHookPre CallFunction(reading_live_traffic, , ()) 0.000000 MetaHookPre CallFunction(reading_traces, , ()) 0.000000 MetaHookPre CallFunction(set_to_regex, , ({}, (^\.?|\.)(~~)$)) -0.000000 MetaHookPre CallFunction(strftime, , (%Y, 1470863084.205942)) +0.000000 MetaHookPre CallFunction(split_string, , (2.5, <...>/)) +0.000000 MetaHookPre CallFunction(split_string1, , (2.5-beta-21-debug, <...>/)) +0.000000 MetaHookPre CallFunction(strftime, , (%Y, 1475455659.26412)) 0.000000 MetaHookPre CallFunction(string_to_pattern, , ((^\.?|\.)()$, F)) 0.000000 MetaHookPre CallFunction(sub, , ((^\.?|\.)(~~)$, <...>/, )) +0.000000 MetaHookPre CallFunction(sub_bytes, , (-21, 2, 999)) +0.000000 MetaHookPre CallFunction(to_count, , (2)) 0.000000 MetaHookPre CallFunction(to_count, , (2016)) +0.000000 MetaHookPre CallFunction(to_count, , (21)) +0.000000 MetaHookPre CallFunction(to_count, , (5)) 0.000000 MetaHookPre DrainEvents() 0.000000 MetaHookPre LoadFile(../main) 0.000000 MetaHookPre LoadFile(../plugin) @@ -1426,6 +1445,7 @@ 0.000000 MetaHookPre LoadFile(base<...>/unified2) 0.000000 MetaHookPre LoadFile(base<...>/urls) 0.000000 MetaHookPre LoadFile(base<...>/utils) +0.000000 MetaHookPre LoadFile(base<...>/version) 0.000000 MetaHookPre LoadFile(base<...>/weird) 0.000000 MetaHookPre LoadFile(base<...>/x509) 0.000000 MetaHookPre LoadFile(base<...>/xmpp) @@ -1680,7 +1700,7 @@ 0.000000 | HookCallFunction Log::__create_stream(Weird::LOG, [columns=, ev=Weird::log_weird, path=weird]) 0.000000 | HookCallFunction Log::__create_stream(X509::LOG, [columns=, ev=X509::log_x509, path=x509]) 0.000000 | HookCallFunction Log::__create_stream(mysql::LOG, [columns=, ev=MySQL::log_mysql, path=mysql]) -0.000000 | HookCallFunction Log::__write(PacketFilter::LOG, [ts=1470863084.206407, node=bro, filter=ip or not ip, init=T, success=T]) +0.000000 | HookCallFunction Log::__write(PacketFilter::LOG, [ts=1475455659.264633, node=bro, filter=ip or not ip, init=T, success=T]) 0.000000 | HookCallFunction Log::add_default_filter(Cluster::LOG) 0.000000 | HookCallFunction Log::add_default_filter(Communication::LOG) 0.000000 | HookCallFunction Log::add_default_filter(Conn::LOG) @@ -1810,7 +1830,7 @@ 0.000000 | HookCallFunction Log::create_stream(Weird::LOG, [columns=, ev=Weird::log_weird, path=weird]) 0.000000 | HookCallFunction Log::create_stream(X509::LOG, [columns=, ev=X509::log_x509, path=x509]) 0.000000 | HookCallFunction Log::create_stream(mysql::LOG, [columns=, ev=MySQL::log_mysql, path=mysql]) -0.000000 | HookCallFunction Log::write(PacketFilter::LOG, [ts=1470863084.206407, node=bro, filter=ip or not ip, init=T, success=T]) +0.000000 | HookCallFunction Log::write(PacketFilter::LOG, [ts=1475455659.264633, node=bro, filter=ip or not ip, init=T, success=T]) 0.000000 | HookCallFunction NetControl::check_plugins() 0.000000 | HookCallFunction NetControl::init() 0.000000 | HookCallFunction Notice::want_pp() @@ -1835,18 +1855,27 @@ 0.000000 | HookCallFunction SumStats::register_observe_plugins() 0.000000 | HookCallFunction Unified2::mappings_initialized() 0.000000 | HookCallFunction Unified2::start_watching() +0.000000 | HookCallFunction Version::parse(2.5-beta-21-debug) 0.000000 | HookCallFunction bro_init() +0.000000 | HookCallFunction bro_version() 0.000000 | HookCallFunction current_time() 0.000000 | HookCallFunction filter_change_tracking() +0.000000 | HookCallFunction find_last(beta-21-debug, <...>/) 0.000000 | HookCallFunction getenv(CLUSTER_NODE) 0.000000 | HookCallFunction network_time() 0.000000 | HookCallFunction reading_live_traffic() 0.000000 | HookCallFunction reading_traces() 0.000000 | HookCallFunction set_to_regex({}, (^\.?|\.)(~~)$) -0.000000 | HookCallFunction strftime(%Y, 1470863084.205942) +0.000000 | HookCallFunction split_string(2.5, <...>/) +0.000000 | HookCallFunction split_string1(2.5-beta-21-debug, <...>/) +0.000000 | HookCallFunction strftime(%Y, 1475455659.26412) 0.000000 | HookCallFunction string_to_pattern((^\.?|\.)()$, F) 0.000000 | HookCallFunction sub((^\.?|\.)(~~)$, <...>/, ) +0.000000 | HookCallFunction sub_bytes(-21, 2, 999) +0.000000 | HookCallFunction to_count(2) 0.000000 | HookCallFunction to_count(2016) +0.000000 | HookCallFunction to_count(21) +0.000000 | HookCallFunction to_count(5) 0.000000 | HookDrainEvents 0.000000 | HookLoadFile ..<...>/bro 0.000000 | HookLoadFile .<...>/bro diff --git a/testing/btest/Baseline/scripts.base.misc.version-2/.stderr b/testing/btest/Baseline/scripts.base.misc.version-2/.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/testing/btest/Baseline/scripts.base.misc.version-2/.stdout b/testing/btest/Baseline/scripts.base.misc.version-2/.stdout new file mode 100644 index 0000000000..aad2c94307 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.misc.version-2/.stdout @@ -0,0 +1,3 @@ +yup +yup +yup diff --git a/testing/btest/Baseline/scripts.base.misc.version/.stderr b/testing/btest/Baseline/scripts.base.misc.version/.stderr new file mode 100644 index 0000000000..bfae6163df --- /dev/null +++ b/testing/btest/Baseline/scripts.base.misc.version/.stderr @@ -0,0 +1,4 @@ +error in /home/robin/bro/master/scripts/base/misc/version.bro, line 54: Version string 1 cannot be parsed +error in /home/robin/bro/master/scripts/base/misc/version.bro, line 54: Version string 12.5 cannot be parsed +error in /home/robin/bro/master/scripts/base/misc/version.bro, line 54: Version string 1.12-beta-drunk cannot be parsed +error in /home/robin/bro/master/scripts/base/misc/version.bro, line 54: Version string JustARandomString cannot be parsed diff --git a/testing/btest/Baseline/scripts.base.misc.version/.stdout b/testing/btest/Baseline/scripts.base.misc.version/.stdout new file mode 100644 index 0000000000..5b207674f3 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.misc.version/.stdout @@ -0,0 +1,12 @@ +[version_number=10500, major=1, minor=5, patch=0, commit=0, beta=F, debug=F, version_string=1.5] +[version_number=20000, major=2, minor=0, patch=0, commit=0, beta=F, debug=F, version_string=2.0] +[version_number=20500, major=2, minor=5, patch=0, commit=0, beta=T, debug=F, version_string=2.5-beta] +[version_number=20501, major=2, minor=5, patch=1, commit=0, beta=F, debug=T, version_string=2.5.1-debug] +[version_number=20500, major=2, minor=5, patch=0, commit=12, beta=T, debug=F, version_string=2.5-beta-12] +[version_number=20500, major=2, minor=5, patch=0, commit=0, beta=F, debug=T, version_string=2.5-12-debug] +[version_number=20502, major=2, minor=5, patch=2, commit=12, beta=T, debug=T, version_string=2.5.2-beta-12-debug] +[version_number=11220, major=1, minor=12, patch=20, commit=2562, beta=T, debug=T, version_string=1.12.20-beta-2562-debug] +[version_number=0, major=0, minor=0, patch=0, commit=0, beta=F, debug=F, version_string=1] +[version_number=0, major=0, minor=0, patch=0, commit=0, beta=F, debug=F, version_string=12.5] +[version_number=0, major=0, minor=0, patch=0, commit=0, beta=F, debug=F, version_string=1.12-beta-drunk] +[version_number=0, major=0, minor=0, patch=0, commit=0, beta=F, debug=F, version_string=JustARandomString] diff --git a/testing/btest/scripts/base/misc/version.bro b/testing/btest/scripts/base/misc/version.bro new file mode 100644 index 0000000000..59227894f6 --- /dev/null +++ b/testing/btest/scripts/base/misc/version.bro @@ -0,0 +1,40 @@ +# @TEST-EXEC: bro %INPUT +# @TEST-EXEC: btest-diff .stdout +# @TEST-EXEC: btest-diff .stderr + +# good versions +print Version::parse("1.5"); +print Version::parse("2.0"); +print Version::parse("2.5-beta"); +print Version::parse("2.5.1-debug"); +print Version::parse("2.5-beta-12"); +print Version::parse("2.5-12-debug"); +print Version::parse("2.5.2-beta-12-debug"); +print Version::parse("1.12.20-beta-2562-debug"); + +# bad versions +print Version::parse("1"); +print Version::parse("12.5"); +print Version::parse("1.12-beta-drunk"); +print Version::parse("JustARandomString"); + +# check that current running version of Bro parses without error +Version::parse(bro_version()); + +@TEST-START-NEXT + +@if ( Version::number >= 20500 ) +print "yup"; +@endif + +@if ( Version::parse("1.5")$version_number < 20500 ) +print "yup"; +@endif + +@if ( Version::at_least("2.5") ) +print "yup"; +@endif + +@if ( Version::at_least("2.4") ) +print "no"; +@endif