mirror of
https://gitlab.nic.cz/labs/bird.git
synced 2024-12-22 17:51:53 +00:00
MRT: buildable and running again for BIRD 3
Tests for MRT are scarce and not automated for now, so it may behave weirdly in corner cases.
This commit is contained in:
parent
eb6918e4db
commit
dc69284f61
@ -320,8 +320,7 @@ else
|
|||||||
AC_DEFINE([HAVE_CLOCK_MONOTONIC_COARSE], [1], [Define to 1 if coarse clock is available])
|
AC_DEFINE([HAVE_CLOCK_MONOTONIC_COARSE], [1], [Define to 1 if coarse clock is available])
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# temporarily removed "mrt" from all_protocols to speed up 3.0-alpha1 release
|
all_protocols="aggregator bfd babel bgp l3vpn ospf pipe radv rip rpki static mrt"
|
||||||
all_protocols="aggregator bfd babel bgp l3vpn ospf pipe radv rip rpki static"
|
|
||||||
all_protocols=`echo $all_protocols | sed 's/ /,/g'`
|
all_protocols=`echo $all_protocols | sed 's/ /,/g'`
|
||||||
|
|
||||||
if test "$with_protocols" = all ; then
|
if test "$with_protocols" = all ; then
|
||||||
|
@ -125,7 +125,7 @@ struct rt_export_request {
|
|||||||
/* Table feeding contraption */
|
/* Table feeding contraption */
|
||||||
struct rt_export_feeder {
|
struct rt_export_feeder {
|
||||||
/* Formal name */
|
/* Formal name */
|
||||||
char *name;
|
const char *name;
|
||||||
|
|
||||||
/* Enlisting */
|
/* Enlisting */
|
||||||
struct rt_exporter * _Atomic exporter;
|
struct rt_exporter * _Atomic exporter;
|
||||||
|
@ -1433,7 +1433,10 @@ static inline int
|
|||||||
bgp_encode_attr(struct bgp_write_state *s, eattr *a, byte *buf, uint size)
|
bgp_encode_attr(struct bgp_write_state *s, eattr *a, byte *buf, uint size)
|
||||||
{
|
{
|
||||||
const union bgp_attr_desc *desc = bgp_find_attr_desc(a);
|
const union bgp_attr_desc *desc = bgp_find_attr_desc(a);
|
||||||
|
if (s->ignore_non_bgp_attrs == 0)
|
||||||
ASSERT_DIE(desc);
|
ASSERT_DIE(desc);
|
||||||
|
else if (desc == NULL)
|
||||||
|
return 0;
|
||||||
return desc->encode(s, a, buf, size);
|
return desc->encode(s, a, buf, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -513,6 +513,7 @@ struct bgp_write_state {
|
|||||||
int as4_session;
|
int as4_session;
|
||||||
int add_path;
|
int add_path;
|
||||||
int mpls;
|
int mpls;
|
||||||
|
int ignore_non_bgp_attrs;
|
||||||
|
|
||||||
eattr *mp_next_hop;
|
eattr *mp_next_hop;
|
||||||
const adata *mpls_labels;
|
const adata *mpls_labels;
|
||||||
|
@ -145,7 +145,7 @@ bgp_dump_message(struct bgp_conn *conn, byte *pkt, uint len)
|
|||||||
d.msg_len = len;
|
d.msg_len = len;
|
||||||
d.add_path = bgp_estimate_add_path(conn->bgp, pkt, len);
|
d.add_path = bgp_estimate_add_path(conn->bgp, pkt, len);
|
||||||
|
|
||||||
mrt_dump_bgp_message(&d);
|
mrt_dump_bgp_message(&d, conn->bgp->p.pool);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@ -157,7 +157,7 @@ bgp_dump_state_change(struct bgp_conn *conn, uint old, uint new)
|
|||||||
d.old_state = old;
|
d.old_state = old;
|
||||||
d.new_state = new;
|
d.new_state = new;
|
||||||
|
|
||||||
mrt_dump_bgp_state_change(&d);
|
mrt_dump_bgp_state_change(&d, conn->bgp->p.pool);
|
||||||
}
|
}
|
||||||
|
|
||||||
static byte *
|
static byte *
|
||||||
|
@ -28,6 +28,7 @@ proto: mrt_proto ;
|
|||||||
mrt_proto_start: proto_start MRT
|
mrt_proto_start: proto_start MRT
|
||||||
{
|
{
|
||||||
this_proto = proto_config_new(&proto_mrt, $1);
|
this_proto = proto_config_new(&proto_mrt, $1);
|
||||||
|
this_proto->loop_order = DOMAIN_ORDER(proto);
|
||||||
};
|
};
|
||||||
|
|
||||||
mrt_proto_item:
|
mrt_proto_item:
|
||||||
|
370
proto/mrt/mrt.c
370
proto/mrt/mrt.c
@ -38,6 +38,7 @@
|
|||||||
* - RFC 6396 - MRT format standard
|
* - RFC 6396 - MRT format standard
|
||||||
* - RFC 8050 - ADD_PATH extension
|
* - RFC 8050 - ADD_PATH extension
|
||||||
*/
|
*/
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
@ -45,10 +46,13 @@
|
|||||||
|
|
||||||
#include "mrt.h"
|
#include "mrt.h"
|
||||||
|
|
||||||
|
#include "lib/io-loop.h"
|
||||||
#include "nest/cli.h"
|
#include "nest/cli.h"
|
||||||
#include "filter/filter.h"
|
#include "filter/filter.h"
|
||||||
#include "proto/bgp/bgp.h"
|
#include "proto/bgp/bgp.h"
|
||||||
#include "sysdep/unix/unix.h"
|
#include "sysdep/unix/unix.h"
|
||||||
|
#include "sysdep/unix/io-loop.h"
|
||||||
|
#include "conf/conf.h"
|
||||||
|
|
||||||
|
|
||||||
#ifdef PATH_MAX
|
#ifdef PATH_MAX
|
||||||
@ -65,6 +69,7 @@
|
|||||||
log(L_ERR "%s: " msg, s->proto->p.name, ## args); \
|
log(L_ERR "%s: " msg, s->proto->p.name, ## args); \
|
||||||
})
|
})
|
||||||
|
|
||||||
|
extern proto_state_table proto_state_table_pub;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* MRT buffer code
|
* MRT buffer code
|
||||||
@ -78,6 +83,13 @@ mrt_buffer_init(buffer *b, pool *pool, size_t n)
|
|||||||
b->end = b->start + n;
|
b->end = b->start + n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
mrt_buffer_free(buffer *b)
|
||||||
|
{
|
||||||
|
mb_free(b->start);
|
||||||
|
b->start = b->pos = b->end = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
mrt_buffer_grow(buffer *b, size_t n)
|
mrt_buffer_grow(buffer *b, size_t n)
|
||||||
{
|
{
|
||||||
@ -216,46 +228,6 @@ bstrsub(char *dst, size_t n, const char *src, const char *key, const char *val)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline rtable *
|
|
||||||
mrt_next_table_(rtable *tab, rtable *tab_ptr, const char *pattern)
|
|
||||||
{
|
|
||||||
/* Handle explicit table, return it in the first pass */
|
|
||||||
if (tab_ptr)
|
|
||||||
return !tab ? tab_ptr : NULL;
|
|
||||||
|
|
||||||
/* Walk routing_tables list, starting after tab (if non-NULL) */
|
|
||||||
for (node *tn = tab ? tab->n.next : HEAD(routing_tables);
|
|
||||||
NODE_VALID(tn);
|
|
||||||
tn = tn->next)
|
|
||||||
{
|
|
||||||
tab = SKIP_BACK(rtable, n, tn);
|
|
||||||
if (patmatch(pattern, tab->name) &&
|
|
||||||
((tab->addr_type == NET_IP4) || (tab->addr_type == NET_IP6)))
|
|
||||||
return tab;
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static rtable *
|
|
||||||
mrt_next_table(struct mrt_table_dump_state *s)
|
|
||||||
{
|
|
||||||
rtable *tab = mrt_next_table_(s->table, s->table_ptr, s->table_expr);
|
|
||||||
|
|
||||||
if (s->table)
|
|
||||||
RT_LOCKED(s->table, tab)
|
|
||||||
rt_unlock_table(tab);
|
|
||||||
|
|
||||||
s->table = tab;
|
|
||||||
s->ipv4 = tab ? (tab->addr_type == NET_IP4) : 0;
|
|
||||||
|
|
||||||
if (s->table)
|
|
||||||
RT_LOCKED(s->table, tab)
|
|
||||||
rt_lock_table(tab);
|
|
||||||
|
|
||||||
return s->table;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
mrt_open_file(struct mrt_table_dump_state *s)
|
mrt_open_file(struct mrt_table_dump_state *s)
|
||||||
{
|
{
|
||||||
@ -263,15 +235,16 @@ mrt_open_file(struct mrt_table_dump_state *s)
|
|||||||
char name[BIRD_PATH_MAX];
|
char name[BIRD_PATH_MAX];
|
||||||
btime now = current_time();
|
btime now = current_time();
|
||||||
btime now_real = current_real_time();
|
btime now_real = current_real_time();
|
||||||
|
rtable *tab = s->table_list[s->table_cur];
|
||||||
|
|
||||||
if (!bstrsub(fmt1, sizeof(fmt1), s->filename, "%N", s->table->name) ||
|
if (!bstrsub(fmt1, sizeof(fmt1), s->filename, "%N", tab->name) ||
|
||||||
!tm_format_real_time(name, sizeof(name), fmt1, now_real))
|
!tm_format_real_time(name, sizeof(name), fmt1, now_real))
|
||||||
{
|
{
|
||||||
mrt_log(s, "Invalid filename '%s'", s->filename);
|
mrt_log(s, "Invalid filename '%s'", s->filename);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
s->file = rf_open(s->pool, name, "a");
|
s->file = rf_open(s->pool, name, RF_APPEND, 0);
|
||||||
if (!s->file)
|
if (!s->file)
|
||||||
{
|
{
|
||||||
mrt_log(s, "Unable to open MRT file '%s': %m", name);
|
mrt_log(s, "Unable to open MRT file '%s': %m", name);
|
||||||
@ -354,18 +327,32 @@ static void
|
|||||||
mrt_peer_table_dump(struct mrt_table_dump_state *s)
|
mrt_peer_table_dump(struct mrt_table_dump_state *s)
|
||||||
{
|
{
|
||||||
mrt_init_message(&s->buf, MRT_TABLE_DUMP_V2, MRT_PEER_INDEX_TABLE);
|
mrt_init_message(&s->buf, MRT_TABLE_DUMP_V2, MRT_PEER_INDEX_TABLE);
|
||||||
mrt_peer_table_header(s, config->router_id, s->table->name);
|
mrt_peer_table_header(s, OBSREF_GET(config)->router_id, s->table_open->name);
|
||||||
|
|
||||||
/* 0 is fake peer for non-BGP routes */
|
/* 0 is fake peer for non-BGP routes */
|
||||||
mrt_peer_table_entry(s, 0, 0, IPA_NONE);
|
mrt_peer_table_entry(s, 0, 0, IPA_NONE);
|
||||||
|
|
||||||
#ifdef CONFIG_BGP
|
#ifdef CONFIG_BGP
|
||||||
struct proto *P;
|
PST_LOCKED(ts)
|
||||||
WALK_LIST(P, proto_list)
|
|
||||||
if ((P->proto == &proto_bgp) && (P->proto_state != PS_DOWN))
|
|
||||||
{
|
{
|
||||||
struct bgp_proto *p = (void *) P;
|
for(u32 i = 0; i < ts->length_states; i++)
|
||||||
mrt_peer_table_entry(s, p->remote_id, p->remote_as, p->remote_ip);
|
{
|
||||||
|
ea_list *eal = ts->states[i];
|
||||||
|
if (eal)
|
||||||
|
ea_free_later(ea_ref(eal));
|
||||||
|
else
|
||||||
|
continue;
|
||||||
|
|
||||||
|
struct protocol *type = (struct protocol *)ea_get_ptr(eal, &ea_protocol_type, 0);
|
||||||
|
int state = ea_get_int(eal, &ea_state, 0);
|
||||||
|
if ((type == &proto_bgp) && (state != PS_DOWN))
|
||||||
|
{
|
||||||
|
int rem_id = ea_get_int(eal, &ea_bgp_rem_id, 0);
|
||||||
|
int rem_as = ea_get_int(eal, &ea_bgp_rem_as, 0);
|
||||||
|
ip_addr *rem_ip = (ip_addr *)ea_get_adata(eal, &ea_bgp_rem_ip)->data;
|
||||||
|
mrt_peer_table_entry(s, rem_id, rem_as, *rem_ip);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -388,7 +375,7 @@ mrt_peer_table_flush(struct mrt_table_dump_state *s)
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
static void
|
static void
|
||||||
mrt_rib_table_header(struct mrt_table_dump_state *s, net_addr *n)
|
mrt_rib_table_header(struct mrt_table_dump_state *s, const net_addr *n)
|
||||||
{
|
{
|
||||||
buffer *b = &s->buf;
|
buffer *b = &s->buf;
|
||||||
|
|
||||||
@ -432,7 +419,7 @@ mrt_rib_table_entry_bgp_attrs(struct mrt_table_dump_state *s, rte *r)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
/* Attribute list must be normalized for bgp_encode_attrs() */
|
/* Attribute list must be normalized for bgp_encode_attrs() */
|
||||||
if (!rta_is_cached(r->attrs))
|
if (!r->attrs->stored)
|
||||||
eattrs = ea_normalize(eattrs, 0);
|
eattrs = ea_normalize(eattrs, 0);
|
||||||
|
|
||||||
mrt_buffer_need(b, MRT_ATTR_BUFFER_SIZE);
|
mrt_buffer_need(b, MRT_ATTR_BUFFER_SIZE);
|
||||||
@ -440,6 +427,7 @@ mrt_rib_table_entry_bgp_attrs(struct mrt_table_dump_state *s, rte *r)
|
|||||||
|
|
||||||
s->bws->mp_reach = !s->ipv4;
|
s->bws->mp_reach = !s->ipv4;
|
||||||
s->bws->mp_next_hop = NULL;
|
s->bws->mp_next_hop = NULL;
|
||||||
|
s->bws->ignore_non_bgp_attrs = 1;
|
||||||
|
|
||||||
/* Encode BGP attributes */
|
/* Encode BGP attributes */
|
||||||
int len = bgp_encode_attrs(s->bws, eattrs, pos, b->end);
|
int len = bgp_encode_attrs(s->bws, eattrs, pos, b->end);
|
||||||
@ -503,7 +491,7 @@ mrt_rib_table_entry(struct mrt_table_dump_state *s, rte *r)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
mrt_rib_table_dump(struct mrt_table_dump_state *s, net *n, int add_path)
|
mrt_rib_table_dump(struct mrt_table_dump_state *s, const struct rt_export_feed *feed, int add_path)
|
||||||
{
|
{
|
||||||
s->add_path = s->bws->add_path = add_path;
|
s->add_path = s->bws->add_path = add_path;
|
||||||
|
|
||||||
@ -512,23 +500,23 @@ mrt_rib_table_dump(struct mrt_table_dump_state *s, net *n, int add_path)
|
|||||||
(!add_path ? MRT_RIB_IPV6_UNICAST : MRT_RIB_IPV6_UNICAST_ADDPATH);
|
(!add_path ? MRT_RIB_IPV6_UNICAST : MRT_RIB_IPV6_UNICAST_ADDPATH);
|
||||||
|
|
||||||
mrt_init_message(&s->buf, MRT_TABLE_DUMP_V2, subtype);
|
mrt_init_message(&s->buf, MRT_TABLE_DUMP_V2, subtype);
|
||||||
mrt_rib_table_header(s, n->n.addr);
|
mrt_rib_table_header(s, feed->block[0].net);
|
||||||
|
|
||||||
for (struct rte_storage *rt, *rt0 = n->routes; rt = rt0; rt0 = rt0->next)
|
for (uint i = 0; i < feed->count_routes; i++)
|
||||||
{
|
{
|
||||||
if (rte_is_filtered(&rt->rte))
|
rte *rte = &feed->block[i];
|
||||||
|
if (rte_is_filtered(rte))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/* Skip routes that should be reported in the other phase */
|
/* Skip routes that should be reported in the other phase */
|
||||||
if (!s->always_add_path && (!rt->rte.src->private_id != !s->add_path))
|
if (!s->always_add_path && (!rte->src->private_id != !s->add_path))
|
||||||
{
|
{
|
||||||
s->want_add_path = 1;
|
s->want_add_path = 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
rte e = rt->rte;
|
if (f_run(s->filter, rte, 0) <= F_ACCEPT)
|
||||||
if (f_run(s->filter, &e, 0) <= F_ACCEPT)
|
mrt_rib_table_entry(s, rte);
|
||||||
mrt_rib_table_entry(s, &e);
|
|
||||||
|
|
||||||
lp_flush(s->linpool);
|
lp_flush(s->linpool);
|
||||||
}
|
}
|
||||||
@ -547,25 +535,80 @@ mrt_rib_table_dump(struct mrt_table_dump_state *s, net *n, int add_path)
|
|||||||
mrt_dump_message(&s->buf, s->fd);
|
mrt_dump_message(&s->buf, s->fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* MRT Table Dump: table lists
|
||||||
|
*/
|
||||||
|
|
||||||
|
static uint
|
||||||
|
mrt_get_table_list(pool *p, const char *pattern, rtable ***tl)
|
||||||
|
{
|
||||||
|
ASSERT_DIE(the_bird_locked());
|
||||||
|
|
||||||
|
rtable *tab;
|
||||||
|
node *n;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CAVEAT directly accessing tab->priv.deleted; this is safe due to
|
||||||
|
* the_bird_locked(), and this value changes only from the main loop.
|
||||||
|
* But this is a fragile piece of code which may need some rethinking
|
||||||
|
* later on.
|
||||||
|
*/
|
||||||
|
|
||||||
|
uint count = 0;
|
||||||
|
WALK_LIST2(tab, n, routing_tables, n)
|
||||||
|
if (!OBSREF_GET(tab->priv.deleted) &&
|
||||||
|
patmatch(pattern, tab->name) &&
|
||||||
|
((tab->addr_type == NET_IP4) || (tab->addr_type == NET_IP6)))
|
||||||
|
count++;
|
||||||
|
|
||||||
|
*tl = mb_alloc(p, sizeof **tl * count);
|
||||||
|
|
||||||
|
uint pos = 0;
|
||||||
|
WALK_LIST2(tab, n, routing_tables, n)
|
||||||
|
if (!OBSREF_GET(tab->priv.deleted) &&
|
||||||
|
patmatch(pattern, tab->name) &&
|
||||||
|
((tab->addr_type == NET_IP4) || (tab->addr_type == NET_IP6)))
|
||||||
|
{
|
||||||
|
(*tl)[pos++] = tab;
|
||||||
|
rt_lock_table(tab);
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_DIE(pos == count);
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
mrt_free_table_list(rtable **tl, int len)
|
||||||
|
{
|
||||||
|
for (int i=0; i<len; i++)
|
||||||
|
rt_unlock_table(tl[i]);
|
||||||
|
|
||||||
|
mb_free(tl);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* MRT Table Dump: main logic
|
* MRT Table Dump: main logic
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static struct mrt_table_dump_state *
|
static struct mrt_table_dump_state *
|
||||||
mrt_table_dump_init(pool *pp)
|
mrt_table_dump_init(pool *pp, const char *name)
|
||||||
{
|
{
|
||||||
pool *pool = rp_new(pp, "MRT Table Dump");
|
pool *pool = rp_new(pp, pp->domain, "MRT Table Dump");
|
||||||
struct mrt_table_dump_state *s = mb_allocz(pool, sizeof(struct mrt_table_dump_state));
|
struct mrt_table_dump_state *s = mb_allocz(pool, sizeof *s);
|
||||||
|
|
||||||
s->pool = pool;
|
s->pool = pool;
|
||||||
s->linpool = lp_new(pool);
|
s->linpool = lp_new(pool);
|
||||||
s->peer_lp = lp_new(pool);
|
s->peer_lp = lp_new(pool);
|
||||||
mrt_buffer_init(&s->buf, pool, 2 * MRT_ATTR_BUFFER_SIZE);
|
mrt_buffer_init(&s->buf, pool, 2 * MRT_ATTR_BUFFER_SIZE);
|
||||||
|
|
||||||
|
s->feeder = (struct rt_export_feeder) {
|
||||||
|
.name = name,
|
||||||
|
};
|
||||||
|
|
||||||
/* We lock the current config as we may reference it indirectly by filter */
|
/* We lock the current config as we may reference it indirectly by filter */
|
||||||
s->config = config;
|
|
||||||
config_add_obstacle(s->config);
|
OBSREF_SET(s->config, OBSREF_GET(config));
|
||||||
|
|
||||||
s->fd = -1;
|
s->fd = -1;
|
||||||
|
|
||||||
@ -575,28 +618,20 @@ mrt_table_dump_init(pool *pp)
|
|||||||
static void
|
static void
|
||||||
mrt_table_dump_free(struct mrt_table_dump_state *s)
|
mrt_table_dump_free(struct mrt_table_dump_state *s)
|
||||||
{
|
{
|
||||||
if (s->table)
|
OBSREF_CLEAR(s->config);
|
||||||
RT_LOCKED(s->table, tab)
|
|
||||||
{
|
|
||||||
if (s->table_open)
|
if (s->table_open)
|
||||||
FIB_ITERATE_UNLINK(&s->fit, &tab->fib);
|
RT_LOCKED(s->table_open, tab)
|
||||||
|
rt_feeder_unsubscribe(&s->feeder);
|
||||||
rt_unlock_table(tab);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (s->table_ptr)
|
|
||||||
RT_LOCKED(s->table_ptr, tab)
|
|
||||||
rt_unlock_table(tab);
|
|
||||||
|
|
||||||
config_del_obstacle(s->config);
|
|
||||||
|
|
||||||
rp_free(s->pool);
|
rp_free(s->pool);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static int
|
static void
|
||||||
mrt_table_dump_step(struct mrt_table_dump_state *s)
|
mrt_table_dump_step(void *_s)
|
||||||
{
|
{
|
||||||
|
struct mrt_table_dump_state *s = _s;
|
||||||
struct bgp_write_state bws = { .as4_session = 1 };
|
struct bgp_write_state bws = { .as4_session = 1 };
|
||||||
|
|
||||||
s->max = 2048;
|
s->max = 2048;
|
||||||
@ -605,49 +640,49 @@ mrt_table_dump_step(struct mrt_table_dump_state *s)
|
|||||||
if (s->table_open)
|
if (s->table_open)
|
||||||
goto step;
|
goto step;
|
||||||
|
|
||||||
while (mrt_next_table(s))
|
while (++s->table_cur < s->table_end)
|
||||||
{
|
{
|
||||||
if (!mrt_open_file(s))
|
if (!mrt_open_file(s))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
s->table_open = s->table_list[s->table_cur];
|
||||||
|
s->ipv4 = (s->table_open->addr_type == NET_IP4);
|
||||||
|
|
||||||
mrt_peer_table_dump(s);
|
mrt_peer_table_dump(s);
|
||||||
|
|
||||||
RT_LOCKED(s->table, tab)
|
RT_LOCKED(s->table_open, tab)
|
||||||
{
|
rt_feeder_subscribe(&tab->export_all, &s->feeder);
|
||||||
|
|
||||||
FIB_ITERATE_INIT(&s->fit, &tab->fib);
|
step: ;
|
||||||
s->table_open = 1;
|
RT_FEED_WALK(&s->feeder, route_feed)
|
||||||
|
|
||||||
step:
|
|
||||||
FIB_ITERATE_START(&tab->fib, &s->fit, net, n)
|
|
||||||
{
|
{
|
||||||
if (s->max < 0)
|
|
||||||
{
|
|
||||||
FIB_ITERATE_PUT(&s->fit);
|
|
||||||
RT_RETURN(tab, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* With Always ADD_PATH option, we jump directly to second phase */
|
/* With Always ADD_PATH option, we jump directly to second phase */
|
||||||
s->want_add_path = s->always_add_path;
|
s->want_add_path = s->always_add_path;
|
||||||
|
|
||||||
if (s->want_add_path == 0)
|
if (s->want_add_path == 0)
|
||||||
mrt_rib_table_dump(s, n, 0);
|
mrt_rib_table_dump(s, route_feed, 0);
|
||||||
|
|
||||||
if (s->want_add_path == 1)
|
if (s->want_add_path == 1)
|
||||||
mrt_rib_table_dump(s, n, 1);
|
mrt_rib_table_dump(s, route_feed, 1);
|
||||||
}
|
|
||||||
FIB_ITERATE_END;
|
|
||||||
s->table_open = 0;
|
|
||||||
|
|
||||||
|
MAYBE_DEFER_TASK(s->target, s->event,
|
||||||
|
"MRT dump %s", s->feeder.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RT_LOCKED(s->table_open, tab)
|
||||||
|
rt_feeder_unsubscribe(&s->feeder);
|
||||||
|
|
||||||
|
s->table_open = NULL;
|
||||||
|
|
||||||
mrt_close_file(s);
|
mrt_close_file(s);
|
||||||
mrt_peer_table_flush(s);
|
mrt_peer_table_flush(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
CALL(s->cleanup, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void mrt_proto_dump_done(struct mrt_table_dump_state *s);
|
||||||
|
|
||||||
static void
|
static void
|
||||||
mrt_timer(timer *t)
|
mrt_timer(timer *t)
|
||||||
{
|
{
|
||||||
@ -662,45 +697,41 @@ mrt_timer(timer *t)
|
|||||||
|
|
||||||
TRACE(D_EVENTS, "RIB table dump started");
|
TRACE(D_EVENTS, "RIB table dump started");
|
||||||
|
|
||||||
struct mrt_table_dump_state *s = mrt_table_dump_init(p->p.pool);
|
struct mrt_table_dump_state *s = mrt_table_dump_init(p->p.pool, p->p.name);
|
||||||
|
|
||||||
s->proto = p;
|
s->proto = p;
|
||||||
s->table_expr = cf->table_expr;
|
s->table_list = p->table_list;
|
||||||
s->table_ptr = cf->table_cf ? cf->table_cf->table : NULL;
|
s->table_cur = -1;
|
||||||
|
s->table_end = p->table_list_len;
|
||||||
s->filter = cf->filter;
|
s->filter = cf->filter;
|
||||||
s->filename = cf->filename;
|
s->filename = cf->filename;
|
||||||
s->always_add_path = cf->always_add_path;
|
s->always_add_path = cf->always_add_path;
|
||||||
|
s->cleanup = mrt_proto_dump_done;
|
||||||
|
|
||||||
if (s->table_ptr)
|
s->event = ev_new_init(s->pool, mrt_table_dump_step, s);
|
||||||
RT_LOCKED(s->table_ptr, tab)
|
s->target = proto_work_list(&p->p);
|
||||||
rt_lock_table(tab);
|
|
||||||
|
|
||||||
p->table_dump = s;
|
p->table_dump = s;
|
||||||
ev_schedule(p->event);
|
ev_send_loop(p->p.loop, s->event);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
mrt_event(void *P)
|
mrt_proto_dump_done(struct mrt_table_dump_state *s)
|
||||||
{
|
{
|
||||||
struct mrt_proto *p = P;
|
struct mrt_proto *p = s->proto;
|
||||||
|
|
||||||
if (!p->table_dump)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!mrt_table_dump_step(p->table_dump))
|
|
||||||
{
|
|
||||||
ev_schedule(p->event);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
ASSERT_DIE(p->table_dump == s);
|
||||||
mrt_table_dump_free(p->table_dump);
|
mrt_table_dump_free(p->table_dump);
|
||||||
p->table_dump = NULL;
|
p->table_dump = NULL;
|
||||||
|
|
||||||
TRACE(D_EVENTS, "RIB table dump done");
|
TRACE(D_EVENTS, "RIB table dump done");
|
||||||
|
|
||||||
if (p->p.proto_state == PS_STOP)
|
if (p->p.proto_state == PS_STOP)
|
||||||
|
{
|
||||||
|
mrt_free_table_list(p->table_list, p->table_list_len);
|
||||||
proto_notify_state(&p->p, PS_DOWN);
|
proto_notify_state(&p->p, PS_DOWN);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -708,24 +739,29 @@ mrt_event(void *P)
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
static void
|
static void
|
||||||
mrt_dump_cont(struct cli *c)
|
mrt_cli_dump_done(struct mrt_table_dump_state *s)
|
||||||
{
|
{
|
||||||
if (!mrt_table_dump_step(c->rover))
|
struct cli *c = s->cli;
|
||||||
return;
|
ASSERT_DIE(c->rover == c);
|
||||||
|
|
||||||
cli_printf(c, 0, "");
|
cli_printf(c, 0, "");
|
||||||
mrt_table_dump_free(c->rover);
|
mrt_table_dump_free(s);
|
||||||
c->cont = NULL;
|
c->cont = NULL;
|
||||||
c->cleanup = NULL;
|
c->cleanup = NULL;
|
||||||
c->rover = NULL;
|
c->rover = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static void
|
||||||
|
mrt_dump_cont(struct cli *c)
|
||||||
|
{
|
||||||
|
return mrt_table_dump_step(c->rover);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
mrt_dump_cleanup(struct cli *c)
|
mrt_dump_cleanup(struct cli *c)
|
||||||
{
|
{
|
||||||
mrt_table_dump_free(c->rover);
|
mrt_table_dump_free(c->rover);
|
||||||
c->rover = NULL;
|
c->rover = NULL;
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@ -740,17 +776,33 @@ mrt_dump_cmd(struct mrt_dump_data *d)
|
|||||||
if (!d->filename)
|
if (!d->filename)
|
||||||
cf_error("File not specified");
|
cf_error("File not specified");
|
||||||
|
|
||||||
struct mrt_table_dump_state *s = mrt_table_dump_init(this_cli->pool);
|
struct mrt_table_dump_state *s = mrt_table_dump_init(this_cli->pool, "cli-mrt");
|
||||||
|
|
||||||
s->cli = this_cli;
|
s->cli = this_cli;
|
||||||
s->table_expr = d->table_expr;
|
|
||||||
s->table_ptr = d->table_ptr;
|
/* Either allocate single-pointer block or a full table list.
|
||||||
|
* We rather allocate the block anyway to make simpler cleanup,
|
||||||
|
* than to resolve corner cases in the end.
|
||||||
|
*
|
||||||
|
* This is the version for MRT requested from CLI */
|
||||||
|
if (d->table_ptr)
|
||||||
|
{
|
||||||
|
s->table_end = 1;
|
||||||
|
s->table_list = mb_allocz(s->pool, sizeof *s->table_list);
|
||||||
|
s->table_list[0] = d->table_ptr;
|
||||||
|
rt_lock_table(s->table_list[0]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
s->table_end = mrt_get_table_list(s->pool, d->table_expr, &s->table_list);
|
||||||
|
|
||||||
|
s->table_cur = -1;
|
||||||
|
|
||||||
s->filter = d->filter;
|
s->filter = d->filter;
|
||||||
s->filename = d->filename;
|
s->filename = d->filename;
|
||||||
|
|
||||||
if (s->table_ptr)
|
s->event = this_cli->event;
|
||||||
RT_LOCKED(s->table_ptr, tab)
|
s->target = &global_work_list;
|
||||||
rt_lock_table(tab);
|
s->cleanup = mrt_cli_dump_done;
|
||||||
|
|
||||||
this_cli->cont = mrt_dump_cont;
|
this_cli->cont = mrt_dump_cont;
|
||||||
this_cli->cleanup = mrt_dump_cleanup;
|
this_cli->cleanup = mrt_dump_cleanup;
|
||||||
@ -763,14 +815,12 @@ mrt_dump_cmd(struct mrt_dump_data *d)
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
static buffer *
|
static buffer *
|
||||||
mrt_bgp_buffer(void)
|
mrt_bgp_buffer(pool *p)
|
||||||
{
|
{
|
||||||
/* Static buffer for BGP4MP dump, TODO: change to use MRT protocol */
|
/* Static buffer for BGP4MP dump, TODO: change to use MRT protocol */
|
||||||
static buffer b;
|
static _Thread_local buffer b;
|
||||||
|
|
||||||
if (!b.start)
|
|
||||||
mrt_buffer_init(&b, &root_pool, 1024);
|
|
||||||
|
|
||||||
|
mrt_buffer_init(&b, p, 1024);
|
||||||
return &b;
|
return &b;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -804,7 +854,7 @@ mrt_bgp_header(buffer *b, struct mrt_bgp_data *d)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
mrt_dump_bgp_message(struct mrt_bgp_data *d)
|
mrt_dump_bgp_message(struct mrt_bgp_data *d, pool *p)
|
||||||
{
|
{
|
||||||
const u16 subtypes[] = {
|
const u16 subtypes[] = {
|
||||||
MRT_BGP4MP_MESSAGE, MRT_BGP4MP_MESSAGE_AS4,
|
MRT_BGP4MP_MESSAGE, MRT_BGP4MP_MESSAGE_AS4,
|
||||||
@ -813,15 +863,16 @@ mrt_dump_bgp_message(struct mrt_bgp_data *d)
|
|||||||
MRT_BGP4MP_MESSAGE_LOCAL_ADDPATH, MRT_BGP4MP_MESSAGE_AS4_LOCAL_ADDPATH,
|
MRT_BGP4MP_MESSAGE_LOCAL_ADDPATH, MRT_BGP4MP_MESSAGE_AS4_LOCAL_ADDPATH,
|
||||||
};
|
};
|
||||||
|
|
||||||
buffer *b = mrt_bgp_buffer();
|
buffer *b = mrt_bgp_buffer(p);
|
||||||
mrt_init_message(b, MRT_BGP4MP, subtypes[d->as4 + 4*d->add_path]);
|
mrt_init_message(b, MRT_BGP4MP, subtypes[d->as4 + 4*d->add_path]);
|
||||||
mrt_bgp_header(b, d);
|
mrt_bgp_header(b, d);
|
||||||
mrt_put_data(b, d->message, d->msg_len);
|
mrt_put_data(b, d->message, d->msg_len);
|
||||||
mrt_dump_message(b, config->mrtdump_file);
|
mrt_dump_message(b, rf_fileno(OBSREF_GET(config)->mrtdump_file));
|
||||||
|
mrt_buffer_free(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
mrt_dump_bgp_state_change(struct mrt_bgp_data *d)
|
mrt_dump_bgp_state_change(struct mrt_bgp_data *d, pool *p)
|
||||||
{
|
{
|
||||||
/* Convert state from our BS_* values to values used in MRTDump */
|
/* Convert state from our BS_* values to values used in MRTDump */
|
||||||
const u16 states[BS_MAX] = {1, 2, 3, 4, 5, 6, 1};
|
const u16 states[BS_MAX] = {1, 2, 3, 4, 5, 6, 1};
|
||||||
@ -832,12 +883,13 @@ mrt_dump_bgp_state_change(struct mrt_bgp_data *d)
|
|||||||
/* Always use AS4 mode for STATE_CHANGE */
|
/* Always use AS4 mode for STATE_CHANGE */
|
||||||
d->as4 = 1;
|
d->as4 = 1;
|
||||||
|
|
||||||
buffer *b = mrt_bgp_buffer();
|
buffer *b = mrt_bgp_buffer(p);
|
||||||
mrt_init_message(b, MRT_BGP4MP, MRT_BGP4MP_STATE_CHANGE_AS4);
|
mrt_init_message(b, MRT_BGP4MP, MRT_BGP4MP_STATE_CHANGE_AS4);
|
||||||
mrt_bgp_header(b, d);
|
mrt_bgp_header(b, d);
|
||||||
mrt_put_u16(b, states[d->old_state]);
|
mrt_put_u16(b, states[d->old_state]);
|
||||||
mrt_put_u16(b, states[d->new_state]);
|
mrt_put_u16(b, states[d->new_state]);
|
||||||
mrt_dump_message(b, config->mrtdump_file);
|
mrt_dump_message(b, rf_fileno(OBSREF_GET(config)->mrtdump_file));
|
||||||
|
mrt_buffer_free(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -853,6 +905,9 @@ mrt_check_config(struct proto_config *CF)
|
|||||||
if (!cf->table_expr && !cf->table_cf)
|
if (!cf->table_expr && !cf->table_cf)
|
||||||
cf_error("Table not specified");
|
cf_error("Table not specified");
|
||||||
|
|
||||||
|
if (cf->table_expr && cf->table_cf)
|
||||||
|
cf_error("Clashing table specifiers");
|
||||||
|
|
||||||
if (!cf->filename)
|
if (!cf->filename)
|
||||||
cf_error("File not specified");
|
cf_error("File not specified");
|
||||||
|
|
||||||
@ -860,6 +915,25 @@ mrt_check_config(struct proto_config *CF)
|
|||||||
cf_error("Period not specified");
|
cf_error("Period not specified");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
mrt_proto_get_table_list(struct mrt_proto *p, struct mrt_config *cf)
|
||||||
|
{
|
||||||
|
/* Either allocate single-pointer block or a full table list.
|
||||||
|
* We rather allocate the block anyway to make simpler cleanup,
|
||||||
|
* than to resolve corner cases in the end.
|
||||||
|
*
|
||||||
|
* This is the version for MRT (pseudo)protocol */
|
||||||
|
if (cf->table_cf)
|
||||||
|
{
|
||||||
|
p->table_list_len = 1;
|
||||||
|
p->table_list = mb_allocz(p->p.pool, sizeof *p->table_list);
|
||||||
|
p->table_list[0] = cf->table_cf->table;
|
||||||
|
rt_lock_table(p->table_list[0]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
p->table_list_len = mrt_get_table_list(p->p.pool, cf->table_expr, &p->table_list);
|
||||||
|
}
|
||||||
|
|
||||||
static struct proto *
|
static struct proto *
|
||||||
mrt_init(struct proto_config *CF)
|
mrt_init(struct proto_config *CF)
|
||||||
{
|
{
|
||||||
@ -875,9 +949,10 @@ mrt_start(struct proto *P)
|
|||||||
struct mrt_config *cf = (void *) (P->cf);
|
struct mrt_config *cf = (void *) (P->cf);
|
||||||
|
|
||||||
p->timer = tm_new_init(P->pool, mrt_timer, p, cf->period S, 0);
|
p->timer = tm_new_init(P->pool, mrt_timer, p, cf->period S, 0);
|
||||||
p->event = ev_new_init(P->pool, mrt_event, p);
|
|
||||||
|
|
||||||
tm_start(p->timer, cf->period S);
|
tm_start_in(p->timer, cf->period S, p->p.loop);
|
||||||
|
|
||||||
|
mrt_proto_get_table_list(p, cf);
|
||||||
|
|
||||||
return PS_UP;
|
return PS_UP;
|
||||||
}
|
}
|
||||||
@ -887,7 +962,11 @@ mrt_shutdown(struct proto *P)
|
|||||||
{
|
{
|
||||||
struct mrt_proto *p = (void *) P;
|
struct mrt_proto *p = (void *) P;
|
||||||
|
|
||||||
return p->table_dump ? PS_STOP : PS_DOWN;
|
if (p->table_dump)
|
||||||
|
return PS_STOP;
|
||||||
|
|
||||||
|
mrt_free_table_list(p->table_list, p->table_list_len);
|
||||||
|
return PS_DOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -904,9 +983,14 @@ mrt_reconfigure(struct proto *P, struct proto_config *CF)
|
|||||||
btime now = current_time();
|
btime now = current_time();
|
||||||
btime new_time = p->timer->expires - (old->period S) + (new->period S);
|
btime new_time = p->timer->expires - (old->period S) + (new->period S);
|
||||||
p->timer->recurrent = new->period S;
|
p->timer->recurrent = new->period S;
|
||||||
tm_set(p->timer, MAX(now, new_time));
|
tm_set_in(p->timer, MAX(now, new_time), p->p.loop);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!p->table_dump)
|
||||||
|
mrt_free_table_list(p->table_list, p->table_list_len);
|
||||||
|
|
||||||
|
mrt_proto_get_table_list(p, new);
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,6 +36,9 @@ struct mrt_proto {
|
|||||||
|
|
||||||
struct mrt_target *file;
|
struct mrt_target *file;
|
||||||
struct mrt_table_dump_state *table_dump;
|
struct mrt_table_dump_state *table_dump;
|
||||||
|
|
||||||
|
rtable **table_list;
|
||||||
|
int table_list_len;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct mrt_dump_data {
|
struct mrt_dump_data {
|
||||||
@ -56,11 +59,10 @@ struct mrt_peer_entry {
|
|||||||
struct mrt_table_dump_state {
|
struct mrt_table_dump_state {
|
||||||
struct mrt_proto *proto; /* Protocol for regular MRT dumps (or NULL) */
|
struct mrt_proto *proto; /* Protocol for regular MRT dumps (or NULL) */
|
||||||
struct cli *cli; /* CLI for irregular MRT dumps (or NULL) */
|
struct cli *cli; /* CLI for irregular MRT dumps (or NULL) */
|
||||||
struct config *config; /* Config valid during start of dump, locked */
|
config_ref config; /* Config valid during start of dump, locked */
|
||||||
|
|
||||||
/* Configuration information */
|
/* Configuration information */
|
||||||
const char *table_expr; /* Wildcard for table name (or NULL) */
|
struct rt_export_feeder feeder; /* Table feeder */
|
||||||
rtable *table_ptr; /* Explicit table (or NULL) */
|
|
||||||
const struct filter *filter; /* Optional filter */
|
const struct filter *filter; /* Optional filter */
|
||||||
const char *filename; /* Filename pattern */
|
const char *filename; /* Filename pattern */
|
||||||
int always_add_path; /* Always use *_ADDPATH message subtypes */
|
int always_add_path; /* Always use *_ADDPATH message subtypes */
|
||||||
@ -73,9 +75,9 @@ struct mrt_table_dump_state {
|
|||||||
|
|
||||||
HASH(struct mrt_peer_entry) peer_hash; /* Hash for peers to find the index */
|
HASH(struct mrt_peer_entry) peer_hash; /* Hash for peers to find the index */
|
||||||
|
|
||||||
rtable *table; /* Processed table, NULL initially */
|
rtable **table_list; /* List of tables to process */
|
||||||
struct fib_iterator fit; /* Iterator in processed table */
|
int table_cur, table_end; /* Table list iterator */
|
||||||
int table_open; /* Whether iterator is linked */
|
rtable *table_open; /* Table to which the iterator is linked */
|
||||||
|
|
||||||
int ipv4; /* Processed table is IPv4 */
|
int ipv4; /* Processed table is IPv4 */
|
||||||
int add_path; /* Current message subtype is *_ADDPATH */
|
int add_path; /* Current message subtype is *_ADDPATH */
|
||||||
@ -92,6 +94,11 @@ struct mrt_table_dump_state {
|
|||||||
|
|
||||||
struct rfile *file; /* tracking for mrt table dump file */
|
struct rfile *file; /* tracking for mrt table dump file */
|
||||||
int fd;
|
int fd;
|
||||||
|
|
||||||
|
event *event; /* defer event */
|
||||||
|
event_list *target; /* defer target */
|
||||||
|
|
||||||
|
void (*cleanup)(struct mrt_table_dump_state *); /* call when done */
|
||||||
};
|
};
|
||||||
|
|
||||||
struct mrt_bgp_data {
|
struct mrt_bgp_data {
|
||||||
@ -147,12 +154,12 @@ struct mrt_bgp_data {
|
|||||||
|
|
||||||
#ifdef CONFIG_MRT
|
#ifdef CONFIG_MRT
|
||||||
void mrt_dump_cmd(struct mrt_dump_data *d);
|
void mrt_dump_cmd(struct mrt_dump_data *d);
|
||||||
void mrt_dump_bgp_message(struct mrt_bgp_data *d);
|
void mrt_dump_bgp_message(struct mrt_bgp_data *d, pool *p);
|
||||||
void mrt_dump_bgp_state_change(struct mrt_bgp_data *d);
|
void mrt_dump_bgp_state_change(struct mrt_bgp_data *d, pool *p);
|
||||||
void mrt_check_config(struct proto_config *C);
|
void mrt_check_config(struct proto_config *C);
|
||||||
#else
|
#else
|
||||||
static inline void mrt_dump_bgp_message(struct mrt_bgp_data *d UNUSED) { }
|
static inline void mrt_dump_bgp_message(struct mrt_bgp_data *d UNUSED, pool *p UNUSED) { }
|
||||||
static inline void mrt_dump_bgp_state_change(struct mrt_bgp_data *d UNUSED) { }
|
static inline void mrt_dump_bgp_state_change(struct mrt_bgp_data *d UNUSED, pool *p UNUSED) { }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif /* _BIRD_MRT_H_ */
|
#endif /* _BIRD_MRT_H_ */
|
||||||
|
@ -106,6 +106,12 @@ static struct resclass rf_class = {
|
|||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
|
int
|
||||||
|
rf_fileno(struct rfile *f)
|
||||||
|
{
|
||||||
|
return f->fd;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
rf_open_get_fd(const char *name, enum rf_mode mode)
|
rf_open_get_fd(const char *name, enum rf_mode mode)
|
||||||
{
|
{
|
||||||
|
@ -126,6 +126,7 @@ off_t rf_size(struct rfile *);
|
|||||||
int rf_same(struct rfile *, struct rfile *);
|
int rf_same(struct rfile *, struct rfile *);
|
||||||
int rf_writev(struct rfile *, struct iovec *, int);
|
int rf_writev(struct rfile *, struct iovec *, int);
|
||||||
void rf_write_crude(struct rfile *, const char *, int);
|
void rf_write_crude(struct rfile *, const char *, int);
|
||||||
|
int rf_fileno(struct rfile *f);
|
||||||
|
|
||||||
extern struct rfile rf_stderr;
|
extern struct rfile rf_stderr;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user