Dynamic indexing support

This commit is contained in:
Howard Chu 2005-04-21 19:04:31 +00:00
parent eac3fc3737
commit 5de908e7e1
7 changed files with 327 additions and 133 deletions

View File

@ -25,14 +25,6 @@
#include "back-bdb.h"
#include "lutil.h"
/* for the cache of attribute information (which are indexed, etc.) */
typedef struct bdb_attrinfo {
AttributeDescription *ai_desc; /* attribute description cn;lang-en */
slap_mask_t ai_indexmask; /* how the attr is indexed */
#ifdef LDAP_COMP_MATCH
ComponentReference* ai_cr; /*component indexing*/
#endif
} AttrInfo;
static int
ainfo_type_cmp(
@ -55,50 +47,13 @@ ainfo_cmp(
return SLAP_PTRCMP(a->ai_desc, b->ai_desc);
}
#ifdef LDAP_COMP_MATCH
void
bdb_attr_comp_ref(
struct bdb_info *bdb,
AttributeDescription *desc,
ComponentReference** cr )
{
AttrInfo *a;
a = (AttrInfo *) avl_find( bdb->bi_attrs, desc, ainfo_type_cmp );
*cr = a != NULL ? a->ai_cr : 0 ;
}
void
bdb_attr_mask_cr(
struct bdb_info *bdb,
AttributeDescription *desc,
slap_mask_t *indexmask,
ComponentReference** cr )
{
AttrInfo *a;
a = (AttrInfo *) avl_find( bdb->bi_attrs, desc, ainfo_type_cmp );
if ( a ) {
*indexmask = a->ai_indexmask;
*cr = a->ai_cr;
} else {
*indexmask = 0;
*cr = NULL;
}
}
#endif
void
AttrInfo *
bdb_attr_mask(
struct bdb_info *bdb,
AttributeDescription *desc,
slap_mask_t *indexmask )
AttributeDescription *desc )
{
AttrInfo *a;
a = (AttrInfo *) avl_find( bdb->bi_attrs, desc, ainfo_type_cmp );
*indexmask = a != NULL ? a->ai_indexmask : 0;
return avl_find( bdb->bi_attrs, desc, ainfo_type_cmp );
}
int
@ -254,7 +209,15 @@ bdb_attr_index_config(
ad->ad_cname.bv_val, mask, 0 );
a->ai_desc = ad;
if ( bdb->bi_flags & BDB_IS_OPEN ) {
a->ai_indexmask = 0;
a->ai_newmask = mask;
} else {
a->ai_indexmask = mask;
a->ai_newmask = 0;
}
#ifdef LDAP_COMP_MATCH
if ( cr ) {
a_cr = avl_find( bdb->bi_attrs, ad, ainfo_type_cmp );
@ -283,6 +246,17 @@ bdb_attr_index_config(
ainfo_cmp, avl_dup_error );
if( rc ) {
if ( bdb->bi_flags & BDB_IS_OPEN ) {
AttrInfo *b = avl_find( bdb->bi_attrs, ad, ainfo_type_cmp );
/* If we were editing this attr, reset it */
b->ai_indexmask &= ~BDB_INDEX_DELETING;
/* If this is leftover from a previous add, commit it */
if ( b->ai_newmask )
b->ai_indexmask = b->ai_newmask;
b->ai_newmask = a->ai_newmask;
ch_free( a );
continue;
}
fprintf( stderr, "%s: line %d: duplicate index definition "
"for attr \"%s\" (ignored)\n",
fname, lineno, attrs[i] );
@ -353,3 +327,38 @@ void bdb_attr_index_free( struct bdb_info *bdb, AttributeDescription *ad )
if ( ai )
bdb_attrinfo_free( ai );
}
/* Get a list of AttrInfo's to delete */
typedef struct Alist {
struct Alist *next;
AttrInfo *ptr;
} Alist;
static int
bdb_attrinfo_flush( void *v1, void *arg )
{
AttrInfo *ai = v1;
if ( ai->ai_indexmask & BDB_INDEX_DELETING ) {
Alist **al = arg;
Alist *a = ch_malloc( sizeof( Alist ));
a->ptr = ai;
a->next = *al;
*al = a;
}
return 0;
}
void bdb_attr_flush( struct bdb_info *bdb )
{
Alist *al = NULL, *a2;
avl_apply( bdb->bi_attrs, bdb_attrinfo_flush, &al, -1, AVL_INORDER );
while (( a2 = al )) {
al = al->next;
avl_delete( &bdb->bi_attrs, a2->ptr, ainfo_cmp );
ch_free( a2 );
}
}

View File

@ -187,8 +187,11 @@ struct bdb_info {
alock_info_t bi_alock_info;
char *bi_db_config_path;
BerVarray bi_db_config;
int bi_db_is_open;
int bi_db_has_config;
int bi_flags;
#define BDB_IS_OPEN 0x01
#define BDB_HAS_CONFIG 0x02
#define BDB_UPD_CONFIG 0x04
#define BDB_DEL_INDEX 0x08
};
#define bi_id2entry bi_databases[BDB_ID2ENTRY]
@ -272,6 +275,20 @@ struct bdb_op_info {
LDAP_END_DECL
/* for the cache of attribute information (which are indexed, etc.) */
typedef struct bdb_attrinfo {
AttributeDescription *ai_desc; /* attribute description cn;lang-en */
slap_mask_t ai_indexmask; /* how the attr is indexed */
slap_mask_t ai_newmask; /* new settings to replace old mask */
#ifdef LDAP_COMP_MATCH
ComponentReference* ai_cr; /*component indexing*/
#endif
} AttrInfo;
/* These flags must not clash with SLAP_INDEX flags or ops in slap.h! */
#define BDB_INDEX_DELETING 0x8000U /* index is being modified */
#define BDB_INDEX_UPDATE_OP 0x03 /* performing an index update */
#include "proto-bdb.h"
#endif /* _BACK_BDB_H_ */

View File

@ -24,6 +24,7 @@
#include "config.h"
#include "lutil.h"
#include "ldap_rq.h"
#ifdef DB_DIRTY_READ
# define SLAP_BDB_ALLOW_DIRTY_READ
@ -86,6 +87,7 @@ static ConfigTable bdbcfg[] = {
{ "index", "attr> <[pres,eq,approx,sub]", 2, 3, 0, ARG_MAGIC|BDB_INDEX,
bdb_cf_gen, "( OLcfgDbAt:0.2 NAME 'olcDbIndex' "
"DESC 'Attribute index parameters' "
"EQUALITY caseIgnoreMatch "
"SYNTAX OMsDirectoryString )", NULL, NULL },
{ "linearindex", NULL, 1, 2, 0, ARG_ON_OFF|ARG_OFFSET,
(void *)offsetof(struct bdb_info, bi_linear_index),
@ -147,6 +149,141 @@ static slap_verbmasks bdb_lockd[] = {
{ BER_BVNULL, 0 }
};
/* reindex entries on the fly */
static void *
bdb_online_index( void *ctx, void *arg )
{
struct re_s *rtask = arg;
BackendDB *be = rtask->arg;
struct bdb_info *bdb = be->be_private;
Connection conn = {0};
char opbuf[OPERATION_BUFFER_SIZE];
Operation *op = (Operation *)opbuf;
DBC *curs;
DBT key, data;
DB_TXN *txn;
DB_LOCK lock;
u_int32_t locker;
ID id, nid;
EntryInfo *ei;
int rc, getnext = 1;
connection_fake_init( &conn, op, ctx );
op->o_bd = be;
DBTzero( &key );
DBTzero( &data );
id = 1;
key.data = &nid;
key.size = key.ulen = sizeof(ID);
key.flags = DB_DBT_USERMEM;
data.flags = DB_DBT_USERMEM | DB_DBT_PARTIAL;
data.dlen = data.ulen = 0;
while ( 1 ) {
rc = TXN_BEGIN( bdb->bi_dbenv, NULL, &txn, bdb->bi_db_opflags );
if ( rc )
break;
locker = TXN_ID( txn );
if ( getnext ) {
getnext = 0;
BDB_ID2DISK( id, &nid );
rc = bdb->bi_id2entry->bdi_db->cursor(
bdb->bi_id2entry->bdi_db, txn, &curs, bdb->bi_db_opflags );
if ( rc ) {
TXN_ABORT( txn );
break;
}
rc = curs->c_get( curs, &key, &data, DB_SET_RANGE );
curs->c_close( curs );
if ( rc ) {
TXN_ABORT( txn );
if ( rc == DB_NOTFOUND )
rc = 0;
if ( rc == DB_LOCK_DEADLOCK ) {
ldap_pvt_thread_yield();
continue;
}
break;
}
BDB_DISK2ID( &nid, &id );
}
ei = NULL;
rc = bdb_cache_find_id( op, txn, id, &ei, 0, locker, &lock );
if ( rc ) {
TXN_ABORT( txn );
if ( rc == DB_LOCK_DEADLOCK ) {
ldap_pvt_thread_yield();
continue;
}
if ( rc == DB_NOTFOUND ) {
id++
getnext = 1;
continue;
}
break;
}
if ( ei->bei_e ) {
rc = bdb_index_entry( op, txn, BDB_INDEX_UPDATE_OP, ei->bei_e );
if ( rc == DB_LOCK_DEADLOCK ) {
TXN_ABORT( txn );
ldap_pvt_thread_yield();
continue;
}
if ( rc == 0 ) {
rc = TXN_COMMIT( txn, 0 );
txn = NULL;
}
if ( rc )
break;
}
id++;
getnext = 1;
}
out:
ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
ldap_pvt_runqueue_stoptask( &slapd_rq, rtask );
ldap_pvt_runqueue_remove( &slapd_rq, rtask );
ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
return NULL;
}
/* Cleanup loose ends after Modify completes */
static int
bdb_cf_cleanup( ConfigArgs *c )
{
struct bdb_info *bdb = c->be->be_private;
if ( bdb->bi_flags & BDB_UPD_CONFIG ) {
if ( bdb->bi_db_config ) {
int i;
FILE *f = fopen( bdb->bi_db_config_path, "w" );
if ( f ) {
for (i=0; bdb->bi_db_config[i].bv_val; i++)
fprintf( f, "%s\n", bdb->bi_db_config[i].bv_val );
fclose( f );
}
} else {
unlink( bdb->bi_db_config_path );
}
bdb->bi_flags ^= BDB_UPD_CONFIG;
}
if ( bdb->bi_flags & BDB_DEL_INDEX ) {
bdb_attr_flush( bdb );
bdb->bi_flags ^= BDB_DEL_INDEX;
}
return 0;
}
static int
bdb_cf_gen(ConfigArgs *c)
{
@ -234,8 +371,17 @@ bdb_cf_gen(ConfigArgs *c)
bdb->bi_txn_cp = 0;
break;
case BDB_CONFIG:
rc = 1;
/* FIXME: delete values or the whole file? */
if ( c->valx < 0 ) {
ber_bvarray_free( bdb->bi_db_config );
bdb->bi_db_config = NULL;
} else {
int i = c->valx;
ch_free( bdb->bi_db_config[i].bv_val );
for (; bdb->bi_db_config[i].bv_val; i++)
bdb->bi_db_config[i] = bdb->bi_db_config[i+1];
}
bdb->bi_flags |= BDB_UPD_CONFIG;
c->cleanup = bdb_cf_cleanup;
break;
case BDB_DIRECTORY:
rc = 1;
@ -252,12 +398,16 @@ bdb_cf_gen(ConfigArgs *c)
for (ptr = c->line; !isspace( *ptr ); ptr++);
bv.bv_val = c->line;
bv.bv_len = ptr - bv.bv_val;
if ( ber_bvmatch( &bv, &defbv )) {
if ( bvmatch( &bv, &def )) {
bdb->bi_defaultmask = 0;
} else {
slap_bv2ad( &bv, &ad, &text );
if ( ad )
bdb_attr_index_free( bdb, ad );
if ( ad ) {
AttrInfo *ai = bdb_attr_mask( bdb, ad );
ai->ai_indexmask |= BDB_INDEX_DELETING;
bdb->bi_flags |= BDB_DEL_INDEX;
c->cleanup = bdb_cf_cleanup;
}
}
}
break;
@ -278,14 +428,17 @@ bdb_cf_gen(ConfigArgs *c)
while (!isspace(*ptr)) ptr++;
while (isspace(*ptr)) ptr++;
if ( bdb->bi_flags & BDB_IS_OPEN ) {
bdb->bi_flags |= BDB_UPD_CONFIG;
c->cleanup = bdb_cf_cleanup;
} else {
/* If we're just starting up...
*/
if ( !bdb->bi_db_is_open ) {
FILE *f;
/* If a DB_CONFIG file exists, or we don't know the path
* to the DB_CONFIG file, ignore these directives
*/
if ( bdb->bi_db_has_config || !bdb->bi_db_config_path )
if (( bdb->bi_flags & BDB_HAS_CONFIG ) || !bdb->bi_db_config_path )
break;
f = fopen( bdb->bi_db_config_path, "a" );
if ( f ) {
@ -314,7 +467,7 @@ bdb_cf_gen(ConfigArgs *c)
f = fopen( bdb->bi_db_config_path, "r" );
if ( f ) {
bdb->bi_db_has_config = 1;
bdb->bi_flags |= BDB_HAS_CONFIG;
fclose(f);
}
}
@ -325,7 +478,7 @@ bdb_cf_gen(ConfigArgs *c)
bdb->bi_dbenv_xflags |= DB_TXN_NOSYNC;
else
bdb->bi_dbenv_xflags &= ~DB_TXN_NOSYNC;
if ( bdb->bi_db_is_open ) {
if ( bdb->bi_flags & BDB_IS_OPEN ) {
bdb->bi_dbenv->set_flags( bdb->bi_dbenv, DB_TXN_NOSYNC,
c->value_int );
}
@ -336,8 +489,10 @@ bdb_cf_gen(ConfigArgs *c)
c->argc - 1, &c->argv[1] );
if( rc != LDAP_SUCCESS ) return 1;
/* FIXME: must run slapindex on the new attributes */
if ( bdb->bi_db_is_open ) {
if ( bdb->bi_flags & BDB_IS_OPEN ) {
/* Start the task as soon as we finish here */
ldap_pvt_runqueue_insert( &slapd_rq, 60,
bdb_online_index, c->be );
}
break;

View File

@ -297,12 +297,16 @@ comp_equality_candidates (
MatchingRule *mr = mra->ma_rule;
Syntax *sat_syntax;
ComponentReference* cr_list, *cr;
AttrInfo *ai;
BDB_IDL_ALL( bdb, ids );
bdb_attr_comp_ref ( op->o_bd->be_private, mra->ma_desc, &cr_list );
if( !cr_list || !ca->ca_comp_ref )
if ( !ca->ca_comp_ref )
return 0;
ai = bdb_attr_mask( op->o_bd->be_private, mra->ma_desc );
if( ai )
cr_list = ai->ai_cr;
/* find a component reference to be indexed */
sat_syntax = ca->ca_ma_rule->smr_syntax;
for ( cr = cr_list ; cr ; cr = cr->cr_next ) {

View File

@ -28,19 +28,17 @@
static char presence_keyval[LUTIL_HASH_BYTES] = {0,0,0,1};
static struct berval presence_key = {LUTIL_HASH_BYTES, presence_keyval};
static slap_mask_t index_mask(
static AttrInfo *index_mask(
Backend *be,
AttributeDescription *desc,
struct berval *atname )
{
AttributeType *at;
slap_mask_t mask = 0;
AttrInfo *ai = bdb_attr_mask( be->be_private, desc );
bdb_attr_mask( be->be_private, desc, &mask );
if( mask ) {
if( ai ) {
*atname = desc->ad_cname;
return mask;
return ai;
}
/* If there is a tagging option, did we ever index the base
@ -48,11 +46,11 @@ static slap_mask_t index_mask(
*/
if( slap_ad_is_tagged( desc ) && desc != desc->ad_type->sat_ad ) {
/* has tagging option */
bdb_attr_mask( be->be_private, desc->ad_type->sat_ad, &mask );
ai = bdb_attr_mask( be->be_private, desc->ad_type->sat_ad );
if ( mask && ( mask ^ SLAP_INDEX_NOTAGS ) ) {
if ( ai && ( ai->ai_indexmask ^ SLAP_INDEX_NOTAGS ) ) {
*atname = desc->ad_type->sat_cname;
return mask;
return ai;
}
}
@ -61,11 +59,11 @@ static slap_mask_t index_mask(
/* If no AD, we've never indexed this type */
if ( !at->sat_ad ) continue;
bdb_attr_mask( be->be_private, at->sat_ad, &mask );
ai = bdb_attr_mask( be->be_private, at->sat_ad );
if ( mask && ( mask ^ SLAP_INDEX_NOSUBTYPES ) ) {
if ( ai && ( ai->ai_indexmask ^ SLAP_INDEX_NOSUBTYPES ) ) {
*atname = at->sat_cname;
return mask;
return ai;
}
}
@ -76,18 +74,19 @@ int bdb_index_is_indexed(
Backend *be,
AttributeDescription *desc )
{
slap_mask_t mask;
AttrInfo *ai;
struct berval prefix;
mask = index_mask( be, desc, &prefix );
ai = index_mask( be, desc, &prefix );
if( mask == 0 ) {
if( !ai )
return LDAP_INAPPROPRIATE_MATCHING;
}
return LDAP_SUCCESS;
}
/* This function is only called when evaluating search filters.
*/
int bdb_index_param(
Backend *be,
AttributeDescription *desc,
@ -96,15 +95,17 @@ int bdb_index_param(
slap_mask_t *maskp,
struct berval *prefixp )
{
AttrInfo *ai;
int rc;
slap_mask_t mask;
DB *db;
mask = index_mask( be, desc, prefixp );
ai = index_mask( be, desc, prefixp );
if( mask == 0 ) {
if( !ai ) {
return LDAP_INAPPROPRIATE_MATCHING;
}
mask = ai->ai_indexmask;
rc = bdb_db_cache( be, prefixp->bv_val, &db );
@ -268,6 +269,11 @@ static int index_at_values(
{
int rc;
slap_mask_t mask = 0;
int ixop = opid;
AttrInfo *ai = NULL;
if ( opid == BDB_INDEX_UPDATE_OP )
ixop = SLAP_INDEX_ADD_OP;
if( type->sat_sup ) {
/* recurse */
@ -280,52 +286,62 @@ static int index_at_values(
/* If this type has no AD, we've never used it before */
if( type->sat_ad ) {
ai = bdb_attr_mask( op->o_bd->be_private, type->sat_ad );
if ( ai ) {
#ifdef LDAP_COMP_MATCH
/* component indexing */
ComponentReference* cr_list, *cr;
bdb_attr_mask_cr( op->o_bd->be_private, type->sat_ad, &mask, &cr_list );
if ( cr_list ) {
for( cr = cr_list ; cr ; cr = cr->cr_next ) {
if ( ai->ai_cr ) {
ComponentReference *cr;
for( cr = ai->ai_cr ; cr ; cr = cr->cr_next ) {
rc = indexer( op, txn, cr->cr_ad, &type->sat_cname,
cr->cr_nvals, id, opid,
cr->cr_indexmask );
}
}
#else
bdb_attr_mask( op->o_bd->be_private, type->sat_ad, &mask );
#endif
ad = type->sat_ad;
}
/* If we're updating the index, just set the new bits that aren't
* already in the old mask.
*/
if ( opid == BDB_INDEX_UPDATE_OP )
mask = ai->ai_newmask & ~ai->ai_indexmask;
else
/* For regular updates, if there is a newmask use it. Otherwise
* just use the old mask.
*/
mask = ai->ai_newmask ? ai->ai_newmask : ai->ai_indexmask;
if( mask ) {
rc = indexer( op, txn, ad, &type->sat_cname,
vals, id, opid,
mask );
vals, id, ixop, mask );
if( rc ) return rc;
}
}
}
if( tags->bv_len ) {
AttributeDescription *desc;
mask = 0;
desc = ad_find_tags( type, tags );
if( desc ) {
bdb_attr_mask( op->o_bd->be_private, desc, &mask );
}
ai = bdb_attr_mask( op->o_bd->be_private, desc );
if( mask ) {
if( ai ) {
if ( opid == BDB_INDEX_UPDATE_OP )
mask = ai->ai_newmask & ~ai->ai_indexmask;
else
mask = ai->ai_newmask ? ai->ai_newmask : ai->ai_indexmask;
if ( mask ) {
rc = indexer( op, txn, desc, &desc->ad_cname,
vals, id, opid,
mask );
vals, id, ixop, mask );
if( rc ) {
return rc;
}
}
}
}
}
return LDAP_SUCCESS;
}
@ -373,14 +389,17 @@ bdb_index_entry(
#endif
Debug( LDAP_DEBUG_TRACE, "=> index_entry_%s( %ld, \"%s\" )\n",
opid == SLAP_INDEX_ADD_OP ? "add" : "del",
opid == SLAP_INDEX_DELETE_OP ? "del" : "add",
(long) e->e_id, e->e_dn );
/* add each attribute to the indexes */
for ( ; ap != NULL; ap = ap->a_next ) {
#ifdef LDAP_COMP_MATCH
AttrInfo *ai;
/* see if attribute has components to be indexed */
bdb_attr_comp_ref( op->o_bd->be_private, ap->a_desc->ad_type->sat_ad, &cr_list );
ai = bdb_attr_mask( op->o_bd->be_private, ap->a_desc->ad_type->sat_ad );
if ( ai ) cr_list = ai->ai_cr;
else cr_list = NULL;
if ( attr_converter && cr_list ) {
syn = ap->a_desc->ad_type->sat_syntax;
ap->a_comp_data = op->o_tmpalloc( sizeof( ComponentData ), op->o_tmpmemctx );
@ -443,7 +462,7 @@ bdb_index_entry(
}
Debug( LDAP_DEBUG_TRACE, "<= index_entry_%s( %ld, \"%s\" ) success\n",
opid == SLAP_INDEX_ADD_OP ? "add" : "del",
opid == SLAP_INDEX_DELETE_OP ? "del" : "add",
(long) e->e_id, e->e_dn );
return LDAP_SUCCESS;

View File

@ -546,7 +546,7 @@ bdb_db_open( BackendDB *be )
ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
}
if ( slapMode & SLAP_SERVER_MODE && bdb->bi_db_has_config ) {
if (( slapMode&SLAP_SERVER_MODE ) && ( bdb->bi_flags&BDB_HAS_CONFIG )) {
char buf[SLAP_TEXT_BUFLEN];
FILE *f = fopen( bdb->bi_db_config_path, "r" );
struct berval bv;
@ -568,11 +568,11 @@ bdb_db_open( BackendDB *be )
fclose( f );
} else {
/* Eh? It disappeared between config and open?? */
bdb->bi_db_has_config = 0;
bdb->bi_flags &= ~BDB_HAS_CONFIG;
}
}
bdb->bi_db_is_open = 1;
bdb->bi_flags |= BDB_IS_OPEN;
return 0;
}
@ -585,7 +585,7 @@ bdb_db_close( BackendDB *be )
struct bdb_db_info *db;
bdb_idl_cache_entry_t *entry, *next_entry;
bdb->bi_db_is_open = 0;
bdb->bi_flags &= ~BDB_IS_OPEN;
ber_bvarray_free( bdb->bi_db_config );

View File

@ -31,26 +31,16 @@ LDAP_BEGIN_DECL
*/
#define bdb_attr_mask BDB_SYMBOL(attr_mask)
#define bdb_attr_flush BDB_SYMBOL(attr_flush)
#define bdb_attr_index_config BDB_SYMBOL(attr_index_config)
#define bdb_attr_index_destroy BDB_SYMBOL(attr_index_destroy)
#define bdb_attr_index_free BDB_SYMBOL(attr_index_free)
#define bdb_attr_index_unparse BDB_SYMBOL(attr_index_unparse)
#ifdef LDAP_COMP_MATCH
#define bdb_attr_comp_ref BDB_SYMBOL(attr_comp_ref)
#define bdb_attr_mask_cr BDB_SYMBOL(attr_mask_cr)
void bdb_attr_comp_ref( struct bdb_info *bdb,
AttributeDescription *desc,
ComponentReference **cr );
void bdb_attr_mask_cr( struct bdb_info *bdb,
AttributeDescription *desc,
slap_mask_t *indexmask,
ComponentReference **cr );
#endif
AttrInfo *bdb_attr_mask( struct bdb_info *bdb,
AttributeDescription *desc );
void bdb_attr_mask( struct bdb_info *bdb,
AttributeDescription *desc,
slap_mask_t *indexmask );
void bdb_attr_flush( struct bdb_info *bdb );
int bdb_attr_index_config LDAP_P(( struct bdb_info *bdb,
const char *fname, int lineno,