mirror of
https://github.com/mmueller41/genode.git
synced 2026-01-21 12:32:56 +01:00
Merge branch 'master' into ealan to sync with upstream.
This commit is contained in:
@@ -189,10 +189,10 @@ class Genode::Affinity
|
||||
Affinity::Space space { };
|
||||
Affinity::Location location { };
|
||||
|
||||
node.with_sub_node("affinity", [&] (Xml_node const &node) {
|
||||
node.with_sub_node("space", [&] (Xml_node const &node) {
|
||||
node.with_optional_sub_node("affinity", [&] (Xml_node const &node) {
|
||||
node.with_optional_sub_node("space", [&] (Xml_node const &node) {
|
||||
space = Space::from_xml(node); });
|
||||
node.with_sub_node("location", [&] (Xml_node const &node) {
|
||||
node.with_optional_sub_node("location", [&] (Xml_node const &node) {
|
||||
location = Location::from_xml(space, node); });
|
||||
});
|
||||
|
||||
|
||||
@@ -481,6 +481,17 @@ class Genode::Rpc_entrypoint : Thread, public Object_pool<Rpc_object_base>
|
||||
* This method is solely needed on Linux.
|
||||
*/
|
||||
bool is_myself() const;
|
||||
|
||||
/**
|
||||
* Check whether given stack info matches stack of the entrypoint.
|
||||
*
|
||||
* \noapi
|
||||
*
|
||||
*/
|
||||
bool myself(addr_t const ptr) const
|
||||
{
|
||||
return addr_t(stack_base()) <= ptr && ptr <= addr_t(stack_top());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -257,6 +257,9 @@ class Genode::Trace::Simple_buffer
|
||||
size_t length() const { return _entry->len; }
|
||||
char const *data() const { return _entry->data; }
|
||||
|
||||
template <typename T>
|
||||
T const &object() const { return *reinterpret_cast<const T*>(data()); }
|
||||
|
||||
/* return whether entry is valid, i.e. length field is present */
|
||||
bool last() const { return _entry == 0; }
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@ namespace Genode { namespace Trace {
|
||||
struct Rpc_reply;
|
||||
struct Signal_submit;
|
||||
struct Signal_received;
|
||||
struct Checkpoint;
|
||||
struct Ethernet_packet;
|
||||
} }
|
||||
|
||||
|
||||
@@ -121,4 +123,62 @@ struct Genode::Trace::Signal_received
|
||||
};
|
||||
|
||||
|
||||
struct Genode::Trace::Checkpoint
|
||||
{
|
||||
enum Type : unsigned char {
|
||||
UNDEF = 0x0,
|
||||
START = 0x1,
|
||||
END = 0x2,
|
||||
OBJ_NEW = 0x10,
|
||||
OBJ_DEL = 0x11,
|
||||
OBJ_STATE = 0x12,
|
||||
EXCEPTION = 0xfe,
|
||||
FAILURE = 0xff
|
||||
};
|
||||
|
||||
char const *name;
|
||||
unsigned long const data;
|
||||
Type const type;
|
||||
void *addr;
|
||||
|
||||
Checkpoint(char const *name, unsigned long data, void *addr, Type type=Type::UNDEF)
|
||||
: name(name), data(data), type(type), addr(addr)
|
||||
{
|
||||
Thread::trace(this);
|
||||
}
|
||||
|
||||
size_t generate(Policy_module &policy, char *dst) const {
|
||||
return policy.checkpoint(dst, name, data, addr, type); }
|
||||
};
|
||||
|
||||
|
||||
struct Genode::Trace::Ethernet_packet
|
||||
{
|
||||
enum Direction : char {
|
||||
RECV = 0x0,
|
||||
SENT = 0x1
|
||||
};
|
||||
|
||||
char const *name;
|
||||
Direction direction;
|
||||
char *data;
|
||||
size_t length;
|
||||
|
||||
Ethernet_packet(char const *name, Direction direction, char *data, size_t len)
|
||||
: name(name), direction(direction), data(data), length(len)
|
||||
{
|
||||
Thread::trace(this);
|
||||
}
|
||||
|
||||
Ethernet_packet(char const *name, Direction direction, void *data, size_t len)
|
||||
: name(name), direction(direction), data((char*)data), length(len)
|
||||
{
|
||||
Thread::trace(this);
|
||||
}
|
||||
|
||||
size_t generate(Policy_module &policy, char *dst) const {
|
||||
return policy.trace_eth_packet(dst, name, direction == Direction::SENT, data, length); }
|
||||
};
|
||||
|
||||
|
||||
#endif /* _INCLUDE__BASE__TRACE__EVENTS_H_ */
|
||||
|
||||
@@ -31,14 +31,16 @@ namespace Genode {
|
||||
*/
|
||||
struct Genode::Trace::Policy_module
|
||||
{
|
||||
size_t (*max_event_size) ();
|
||||
size_t (*log_output) (char *, char const *, size_t);
|
||||
size_t (*rpc_call) (char *, char const *, Msgbuf_base const &);
|
||||
size_t (*rpc_returned) (char *, char const *, Msgbuf_base const &);
|
||||
size_t (*rpc_dispatch) (char *, char const *);
|
||||
size_t (*rpc_reply) (char *, char const *);
|
||||
size_t (*signal_submit) (char *, unsigned const);
|
||||
size_t (*signal_received) (char *, Signal_context const &, unsigned const);
|
||||
size_t (*max_event_size) ();
|
||||
size_t (*trace_eth_packet) (char *, char const *, bool, char *, size_t);
|
||||
size_t (*checkpoint) (char *, char const *, unsigned long, void *, unsigned char);
|
||||
size_t (*log_output) (char *, char const *, size_t);
|
||||
size_t (*rpc_call) (char *, char const *, Msgbuf_base const &);
|
||||
size_t (*rpc_returned) (char *, char const *, Msgbuf_base const &);
|
||||
size_t (*rpc_dispatch) (char *, char const *);
|
||||
size_t (*rpc_reply) (char *, char const *);
|
||||
size_t (*signal_submit) (char *, unsigned const);
|
||||
size_t (*signal_received) (char *, Signal_context const &, unsigned const);
|
||||
};
|
||||
|
||||
#endif /* _INCLUDE__BASE__TRACE__POLICY_H_ */
|
||||
|
||||
@@ -42,6 +42,26 @@ struct Genode::Irq_connection : Connection<Irq_session>, Irq_session_client
|
||||
irq, trigger, polarity, device_config_phys)),
|
||||
Irq_session_client(cap())
|
||||
{ }
|
||||
|
||||
/**
|
||||
* Constructor for label-based configuration (used by pin driver)
|
||||
*
|
||||
* \param label session label
|
||||
*/
|
||||
Irq_connection(Env &env,
|
||||
char const *label)
|
||||
:
|
||||
Connection<Irq_session>(env, session(env.parent(),
|
||||
"ram_quota=%u, cap_quota=%u, "
|
||||
"irq_number=%u, irq_trigger=%u, "
|
||||
"irq_polarity=%u, device_config_phys=0x%lx, "
|
||||
"label=\"%s\"",
|
||||
RAM_QUOTA, CAP_QUOTA, 0,
|
||||
Irq_session::TRIGGER_UNCHANGED,
|
||||
Irq_session::POLARITY_UNCHANGED,
|
||||
0, label)),
|
||||
Irq_session_client(cap())
|
||||
{ }
|
||||
};
|
||||
|
||||
#endif /* _INCLUDE__IRQ_SESSION__CONNECTION_H_ */
|
||||
|
||||
@@ -123,6 +123,8 @@ class Genode::Timeout : private Noncopyable,
|
||||
void discard();
|
||||
|
||||
bool scheduled();
|
||||
|
||||
Microseconds deadline() const { return _deadline; }
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -117,6 +117,8 @@ class Timer::One_shot_timeout : private Genode::Noncopyable,
|
||||
void discard() { _timeout.discard(); }
|
||||
|
||||
bool scheduled() { return _timeout.scheduled(); }
|
||||
|
||||
Microseconds deadline() const { return _timeout.deadline(); }
|
||||
};
|
||||
|
||||
|
||||
|
||||
85
repos/base/include/trace/probe.h
Normal file
85
repos/base/include/trace/probe.h
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* \brief Trace probes
|
||||
* \author Johannes Schlatow
|
||||
* \date 2021-12-01
|
||||
*
|
||||
* Convenience macros for creating user-defined trace checkpoints.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2021 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU Affero General Public License version 3.
|
||||
*/
|
||||
|
||||
#ifndef _INCLUDE__TRACE__PROBE_H_
|
||||
#define _INCLUDE__TRACE__PROBE_H_
|
||||
|
||||
#include <base/trace/events.h>
|
||||
|
||||
namespace Genode { namespace Trace {
|
||||
|
||||
class Duration
|
||||
{
|
||||
private:
|
||||
|
||||
char const *_name;
|
||||
unsigned long const _data;
|
||||
|
||||
Duration(Duration const &) = delete;
|
||||
|
||||
Duration & operator = (Duration const &) = delete;
|
||||
|
||||
public:
|
||||
|
||||
Duration(char const * name, unsigned long data)
|
||||
: _name(name), _data(data)
|
||||
{ Checkpoint(_name, _data, nullptr, Checkpoint::Type::START); }
|
||||
|
||||
~Duration()
|
||||
{ Checkpoint(_name, _data, nullptr, Checkpoint::Type::END); }
|
||||
};
|
||||
|
||||
} }
|
||||
|
||||
|
||||
/**
|
||||
* Trace a single checkpoint named after the current function.
|
||||
*
|
||||
* The argument 'data' specifies the payload as an unsigned value.
|
||||
*/
|
||||
#define GENODE_TRACE_CHECKPOINT(data) \
|
||||
Genode::Trace::Checkpoint(__PRETTY_FUNCTION__, (unsigned long)data, nullptr);
|
||||
|
||||
|
||||
/**
|
||||
* Variant of 'GENODE_TRACE_CHECKPOINT' that accepts the name of the checkpoint as argument.
|
||||
*
|
||||
* The argument 'data' specifies the payload as an unsigned value.
|
||||
* The argument 'name' specifies the name of the checkpoint.
|
||||
*/
|
||||
#define GENODE_TRACE_CHECKPOINT_NAMED(data, name) \
|
||||
Genode::Trace::Checkpoint(name, (unsigned long)data, nullptr);
|
||||
|
||||
|
||||
/**
|
||||
* Trace a pair of checkpoints when entering and leaving the current scope.
|
||||
*
|
||||
* The argument 'data' specifies the payload as an unsigned value.
|
||||
*/
|
||||
#define GENODE_TRACE_DURATION(data) \
|
||||
Genode::Trace::Duration duration(__PRETTY_FUNCTION__, (unsigned long)data);
|
||||
|
||||
|
||||
/**
|
||||
* Variant of 'GENODE_TRACE_DURATION' that accepts the name of the checkpoints as argument.
|
||||
*
|
||||
* The argument 'data' specifies the payload as an unsigned value.
|
||||
* The argument 'name' specifies the names of the checkpoints
|
||||
*/
|
||||
#define GENODE_TRACE_DURATION_NAMED(data, name) \
|
||||
Genode::Trace::Duration duration(name, (unsigned long)data);
|
||||
|
||||
|
||||
#endif /* _INCLUDE__TRACE__PROBE_H_ */
|
||||
@@ -116,10 +116,6 @@ struct Genode::Trace::Session_client : Genode::Rpc_client<Genode::Trace::Session
|
||||
void trace(Subject_id s, Policy_id p, size_t buffer_size) override {
|
||||
call<Rpc_trace>(s, p, buffer_size); }
|
||||
|
||||
void rule(Session_label const &label, Thread_name const &thread,
|
||||
Policy_id policy, size_t buffer_size) override {
|
||||
call<Rpc_rule>(label, thread, policy, buffer_size); }
|
||||
|
||||
void pause(Subject_id subject) override {
|
||||
call<Rpc_pause>(subject); }
|
||||
|
||||
|
||||
@@ -64,12 +64,6 @@ struct Genode::Trace::Session : Genode::Session
|
||||
*/
|
||||
virtual void trace(Subject_id, Policy_id, size_t buffer_size) = 0;
|
||||
|
||||
/**
|
||||
* Install a matching rule for automatically tracing new threads
|
||||
*/
|
||||
virtual void rule(Session_label const &, Thread_name const &,
|
||||
Policy_id, size_t buffer_size) = 0;
|
||||
|
||||
/**
|
||||
* Pause generation of tracing data
|
||||
*
|
||||
@@ -124,10 +118,6 @@ struct Genode::Trace::Session : Genode::Session
|
||||
Nonexistent_policy,
|
||||
Traced_by_other_session),
|
||||
Subject_id, Policy_id, size_t);
|
||||
GENODE_RPC_THROW(Rpc_rule, void, rule,
|
||||
GENODE_TYPE_LIST(Out_of_ram, Out_of_caps),
|
||||
Session_label const &, Thread_name const &,
|
||||
Policy_id, size_t);
|
||||
GENODE_RPC_THROW(Rpc_pause, void, pause,
|
||||
GENODE_TYPE_LIST(Nonexistent_subject), Subject_id);
|
||||
GENODE_RPC_THROW(Rpc_resume, void, resume,
|
||||
@@ -144,7 +134,7 @@ struct Genode::Trace::Session : Genode::Session
|
||||
GENODE_TYPE_LIST(Nonexistent_subject), Subject_id);
|
||||
|
||||
GENODE_RPC_INTERFACE(Rpc_dataspace, Rpc_alloc_policy, Rpc_policy,
|
||||
Rpc_unload_policy, Rpc_trace, Rpc_rule, Rpc_pause,
|
||||
Rpc_unload_policy, Rpc_trace, Rpc_pause,
|
||||
Rpc_resume, Rpc_subjects, Rpc_buffer,
|
||||
Rpc_free, Rpc_subject_infos);
|
||||
};
|
||||
|
||||
148
repos/base/include/util/dictionary.h
Normal file
148
repos/base/include/util/dictionary.h
Normal file
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* \brief Utility for accessing objects by name
|
||||
* \author Norman Feske
|
||||
* \date 2022-09-14
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2022 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU Affero General Public License version 3.
|
||||
*/
|
||||
|
||||
#ifndef _INCLUDE__UTIL__DICTIONARY_H_
|
||||
#define _INCLUDE__UTIL__DICTIONARY_H_
|
||||
|
||||
#include <util/meta.h>
|
||||
#include <util/string.h>
|
||||
#include <util/avl_tree.h>
|
||||
#include <util/noncopyable.h>
|
||||
#include <base/log.h>
|
||||
|
||||
namespace Genode { template <typename, typename> class Dictionary; }
|
||||
|
||||
|
||||
template <typename T, typename NAME>
|
||||
class Genode::Dictionary : Noncopyable
|
||||
{
|
||||
private:
|
||||
|
||||
Avl_tree<T> _tree { };
|
||||
|
||||
public:
|
||||
|
||||
class Element : private Avl_node<T>
|
||||
{
|
||||
public:
|
||||
|
||||
NAME const name;
|
||||
|
||||
private:
|
||||
|
||||
using This = Dictionary<T, NAME>::Element;
|
||||
|
||||
Dictionary<T, NAME> &_dictionary;
|
||||
|
||||
bool higher(T const *other) const { return name > other->This::name; }
|
||||
|
||||
friend class Avl_tree<T>;
|
||||
friend class Avl_node<T>;
|
||||
friend class Dictionary<T, NAME>;
|
||||
|
||||
static T *_matching_sub_tree(T &curr, NAME const &name)
|
||||
{
|
||||
typename Avl_node<T>::Side side = (curr.This::name > name);
|
||||
return curr.Avl_node<T>::child(side);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
Element(Dictionary &dictionary, NAME const &name)
|
||||
:
|
||||
name(name), _dictionary(dictionary)
|
||||
{
|
||||
if (_dictionary.exists(name))
|
||||
warning("dictionary entry '", name, "' is not unique");
|
||||
|
||||
_dictionary._tree.insert(this);
|
||||
}
|
||||
|
||||
~Element()
|
||||
{
|
||||
_dictionary._tree.remove(this);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Call 'match_fn' with named constant dictionary element
|
||||
*
|
||||
* The 'match_fn' functor is called with a const reference to the
|
||||
* matching dictionary element. If no maching element exists,
|
||||
* 'no_match_fn' is called without argument.
|
||||
*/
|
||||
template <typename FN1, typename FN2>
|
||||
auto with_element(NAME const &name, FN1 const &match_fn, FN2 const &no_match_fn)
|
||||
-> typename Trait::Functor<decltype(&FN1::operator())>::Return_type
|
||||
{
|
||||
T *curr_ptr = _tree.first();
|
||||
for (;;) {
|
||||
if (!curr_ptr)
|
||||
break;
|
||||
|
||||
if (curr_ptr->Element::name == name) {
|
||||
return match_fn(*curr_ptr);
|
||||
}
|
||||
|
||||
curr_ptr = Element::_matching_sub_tree(*curr_ptr, name);
|
||||
}
|
||||
return no_match_fn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Call 'match_fn' with named mutable dictionary element
|
||||
*
|
||||
* The 'match_fn' functor is called with a non-const reference to the
|
||||
* matching dictionary element. If no maching element exists,
|
||||
* 'no_match_fn' is called without argument.
|
||||
*/
|
||||
template <typename FN1, typename FN2>
|
||||
auto with_element(NAME const &name, FN1 const &match_fn, FN2 const &no_match_fn) const
|
||||
-> typename Trait::Functor<decltype(&FN1::operator())>::Return_type
|
||||
{
|
||||
auto const_match_fn = [&] (T const &e) { return match_fn(e); };
|
||||
auto non_const_this = const_cast<Dictionary *>(this);
|
||||
return non_const_this->with_element(name, const_match_fn, no_match_fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call 'fn' with a non-const reference to any dictionary element
|
||||
*
|
||||
* \return true if 'fn' was called, or
|
||||
* false if the dictionary is empty
|
||||
*
|
||||
* This method is intended for the orderly destruction of a dictionary.
|
||||
* It allows for the consecutive destruction of all elements.
|
||||
*/
|
||||
template <typename FUNC>
|
||||
bool with_any_element(FUNC const &fn)
|
||||
{
|
||||
T *curr_ptr = _tree.first();
|
||||
if (!curr_ptr)
|
||||
return false;
|
||||
|
||||
fn(*curr_ptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename FN>
|
||||
void for_each(FN const &fn) const { _tree.for_each(fn); }
|
||||
|
||||
bool exists(NAME const &name) const
|
||||
{
|
||||
return with_element(name, [] (T const &) { return true; },
|
||||
[] { return false; });
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _INCLUDE__UTIL__DICTIONARY_H_ */
|
||||
@@ -459,20 +459,21 @@ namespace Genode {
|
||||
|
||||
|
||||
/**
|
||||
* Read signed long value from string
|
||||
* Read signed value from string
|
||||
*
|
||||
* \return number of consumed characters
|
||||
*/
|
||||
inline size_t ascii_to(const char *s, long &result)
|
||||
template <typename T>
|
||||
inline size_t ascii_to_signed(const char *s, T &result)
|
||||
{
|
||||
int i = 0;
|
||||
size_t i = 0;
|
||||
|
||||
/* read sign */
|
||||
int sign = (*s == '-') ? -1 : 1;
|
||||
|
||||
if (*s == '-' || *s == '+') { s++; i++; }
|
||||
|
||||
unsigned long value = 0;
|
||||
T value = 0;
|
||||
|
||||
size_t const j = ascii_to_unsigned(s, value, 0);
|
||||
|
||||
@@ -484,6 +485,28 @@ namespace Genode {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read signed long value from string
|
||||
*
|
||||
* \return number of consumed characters
|
||||
*/
|
||||
inline size_t ascii_to(const char *s, long &result)
|
||||
{
|
||||
return ascii_to_signed<long>(s, result);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read signed integer value from string
|
||||
*
|
||||
* \return number of consumed characters
|
||||
*/
|
||||
inline size_t ascii_to(const char *s, int &result)
|
||||
{
|
||||
return ascii_to_signed<int>(s, result);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read 'Number_of_bytes' value from string and handle the size suffixes
|
||||
*
|
||||
@@ -785,6 +808,12 @@ class Genode::String
|
||||
return strcmp(string(), other.string()) != 0;
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
bool operator > (String<N> const &other) const
|
||||
{
|
||||
return strcmp(string(), other.string()) > 0;
|
||||
}
|
||||
|
||||
void print(Output &out) const { Genode::print(out, string()); }
|
||||
};
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ class Genode::Token
|
||||
/**
|
||||
* Access single characters of token
|
||||
*/
|
||||
char operator [] (int idx)
|
||||
char operator [] (int idx) const
|
||||
{
|
||||
return ((idx >= 0) && ((unsigned)idx < _len)) ? _start[idx] : 0;
|
||||
}
|
||||
|
||||
@@ -55,17 +55,16 @@ class Genode::Xml_attribute
|
||||
struct Tokens
|
||||
{
|
||||
Token name;
|
||||
Token value;
|
||||
Token equals { name .next().eat_whitespace() };
|
||||
Token value { equals.next().eat_whitespace() };
|
||||
|
||||
Tokens(Token t)
|
||||
: name(t.eat_whitespace()), value(name.next().next()) { };
|
||||
Tokens(Token t) : name(t.eat_whitespace()) { };
|
||||
|
||||
bool valid() const
|
||||
{
|
||||
bool const tag_present = (name.type() == Token::IDENT);
|
||||
bool const value_present = (name.next()[0] == '=' &&
|
||||
value.type() == Token::STRING);
|
||||
return tag_present && value_present;
|
||||
return (name.type() == Token::IDENT)
|
||||
&& (equals[0] == '=')
|
||||
&& (value.type() == Token::STRING);
|
||||
}
|
||||
} _tokens;
|
||||
|
||||
@@ -103,7 +102,7 @@ class Genode::Xml_attribute
|
||||
/**
|
||||
* Return token following the attribute declaration
|
||||
*/
|
||||
Token _next_token() const { return _tokens.name.next().next().next(); }
|
||||
Token _next_token() const { return _tokens.value.next(); }
|
||||
|
||||
public:
|
||||
|
||||
@@ -355,7 +354,7 @@ class Genode::Xml_node
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if tag as at least one attribute
|
||||
* Return true if tag has at least one attribute
|
||||
*/
|
||||
bool has_attribute() const { return Xml_attribute::_valid(_name.next()); }
|
||||
|
||||
@@ -846,12 +845,27 @@ class Genode::Xml_node
|
||||
* If no matching sub node exists, the functor is not called.
|
||||
*/
|
||||
template <typename FN>
|
||||
void with_sub_node(char const *type, FN const &fn) const
|
||||
void with_optional_sub_node(char const *type, FN const &fn) const
|
||||
{
|
||||
if (has_sub_node(type))
|
||||
fn(sub_node(type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply functor 'fn' to first sub node of specified type
|
||||
*
|
||||
* The functor is called with the sub node as argument.
|
||||
* If no matching sub node exists, the functor 'fn_nexists' is called.
|
||||
*/
|
||||
template <typename FN, typename FN_NEXISTS>
|
||||
void with_sub_node(char const *type, FN const &fn, FN_NEXISTS const &fn_nexists) const
|
||||
{
|
||||
if (has_sub_node(type))
|
||||
fn(sub_node(type));
|
||||
else
|
||||
fn_nexists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute functor 'fn' for each sub node of specified type
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user