static int bdb_tool_index_add( Operation *op, DB_TXN *txn, Entry *e ) { struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; if ( !bdb->bi_nattrs ) return 0; if ( bdb_tool_threads > 1 ) { IndexRec *ir; int i, rc; Attribute *a; ir = bdb_tool_index_rec; memset(ir, 0, bdb->bi_nattrs * sizeof( IndexRec )); for ( a = e->e_attrs; a != NULL; a = a->a_next ) { rc = bdb_index_recset( bdb, a, a->a_desc->ad_type, &a->a_desc->ad_tags, ir ); if ( rc ) return rc; } bdb_tool_ix_id = e->e_id; bdb_tool_ix_op = op; ldap_pvt_thread_mutex_lock( &bdb_tool_index_mutex ); /* Wait for all threads to be ready */ while ( bdb_tool_index_tcount > 0 ) { ldap_pvt_thread_cond_wait( &bdb_tool_index_cond_main, &bdb_tool_index_mutex ); } for ( i=1; i<bdb_tool_threads; i++ ) bdb_tool_index_threads[i] = LDAP_BUSY; bdb_tool_index_tcount = bdb_tool_threads - 1; ldap_pvt_thread_cond_broadcast( &bdb_tool_index_cond_work ); ldap_pvt_thread_mutex_unlock( &bdb_tool_index_mutex ); rc = bdb_index_recrun( op, bdb, ir, e->e_id, 0 ); if ( rc ) return rc; ldap_pvt_thread_mutex_lock( &bdb_tool_index_mutex ); for ( i=1; i<bdb_tool_threads; i++ ) { if ( bdb_tool_index_threads[i] == LDAP_BUSY ) { ldap_pvt_thread_cond_wait( &bdb_tool_index_cond_main, &bdb_tool_index_mutex ); i--; continue; } if ( bdb_tool_index_threads[i] ) { rc = bdb_tool_index_threads[i]; break; } } ldap_pvt_thread_mutex_unlock( &bdb_tool_index_mutex ); return rc; } else { return bdb_index_entry_add( op, txn, e ); } }
int mdb_tool_entry_close( BackendDB *be ) { if ( mdb_tool_info ) { slapd_shutdown = 1; ldap_pvt_thread_mutex_lock( &mdb_tool_index_mutex ); /* There might still be some threads starting */ while ( mdb_tool_index_tcount > 0 ) { ldap_pvt_thread_cond_wait( &mdb_tool_index_cond_main, &mdb_tool_index_mutex ); } mdb_tool_index_tcount = mdb_tool_threads - 1; ldap_pvt_thread_cond_broadcast( &mdb_tool_index_cond_work ); /* Make sure all threads are stopped */ while ( mdb_tool_index_tcount > 0 ) { ldap_pvt_thread_cond_wait( &mdb_tool_index_cond_main, &mdb_tool_index_mutex ); } ldap_pvt_thread_mutex_unlock( &mdb_tool_index_mutex ); mdb_tool_info = NULL; slapd_shutdown = 0; ch_free( mdb_tool_index_rec ); mdb_tool_index_tcount = mdb_tool_threads - 1; } if( idcursor ) { mdb_cursor_close( idcursor ); idcursor = NULL; } if( cursor ) { mdb_cursor_close( cursor ); cursor = NULL; } if( txn ) { MDB_TOOL_IDL_FLUSH( be, txn ); if ( mdb_txn_commit( txn )) return -1; txn = NULL; } if( nholes ) { unsigned i; fprintf( stderr, "Error, entries missing!\n"); for (i=0; i<nholes; i++) { fprintf(stderr, " entry %ld: %s\n", holes[i].id, holes[i].dn.bv_val); } nholes = 0; return -1; } return 0; }
static void * bdb_tool_index_task( void *ctx, void *ptr ) { int base = *(int *)ptr; free( ptr ); while ( 1 ) { ldap_pvt_thread_mutex_lock( &bdb_tool_index_mutex ); bdb_tool_index_tcount--; if ( !bdb_tool_index_tcount ) ldap_pvt_thread_cond_signal( &bdb_tool_index_cond_main ); ldap_pvt_thread_cond_wait( &bdb_tool_index_cond_work, &bdb_tool_index_mutex ); if ( slapd_shutdown ) { bdb_tool_index_tcount--; if ( !bdb_tool_index_tcount ) ldap_pvt_thread_cond_signal( &bdb_tool_index_cond_main ); ldap_pvt_thread_mutex_unlock( &bdb_tool_index_mutex ); break; } ldap_pvt_thread_mutex_unlock( &bdb_tool_index_mutex ); bdb_tool_index_threads[base] = bdb_index_recrun( bdb_tool_ix_op, bdb_tool_info, bdb_tool_index_rec, bdb_tool_ix_id, base ); } return NULL; }
static void * mdb_tool_index_task( void *ctx, void *ptr ) { int base = *(int *)ptr; free( ptr ); while ( 1 ) { ldap_pvt_thread_mutex_lock( &mdb_tool_index_mutex ); mdb_tool_index_tcount--; if ( !mdb_tool_index_tcount ) ldap_pvt_thread_cond_signal( &mdb_tool_index_cond_main ); ldap_pvt_thread_cond_wait( &mdb_tool_index_cond_work, &mdb_tool_index_mutex ); if ( slapd_shutdown ) { mdb_tool_index_tcount--; if ( !mdb_tool_index_tcount ) ldap_pvt_thread_cond_signal( &mdb_tool_index_cond_main ); ldap_pvt_thread_mutex_unlock( &mdb_tool_index_mutex ); break; } ldap_pvt_thread_mutex_unlock( &mdb_tool_index_mutex ); mdb_tool_index_rec[base].ir_i = mdb_index_recrun( mdb_tool_ix_op, mdb_tool_ix_txn, mdb_tool_info, mdb_tool_index_rec, mdb_tool_ix_id, base ); } return NULL; }
int ldap_pvt_thread_rdwr_rlock( ldap_pvt_thread_rdwr_t *rwlock ) { struct ldap_int_thread_rdwr_s *rw; assert( rwlock != NULL ); rw = *rwlock; assert( rw != NULL ); assert( rw->ltrw_valid == LDAP_PVT_THREAD_RDWR_VALID ); if( rw->ltrw_valid != LDAP_PVT_THREAD_RDWR_VALID ) return LDAP_PVT_THREAD_EINVAL; ldap_pvt_thread_mutex_lock( &rw->ltrw_mutex ); assert( rw->ltrw_w_active >= 0 ); assert( rw->ltrw_w_wait >= 0 ); assert( rw->ltrw_r_active >= 0 ); assert( rw->ltrw_r_wait >= 0 ); if( rw->ltrw_w_active > 0 ) { /* writer is active */ rw->ltrw_r_wait++; do { ldap_pvt_thread_cond_wait( &rw->ltrw_read, &rw->ltrw_mutex ); } while( rw->ltrw_w_active > 0 ); rw->ltrw_r_wait--; assert( rw->ltrw_r_wait >= 0 ); } #ifdef LDAP_RDWR_DEBUG if( rw->ltrw_r_active < MAX_READERS ) rw->ltrw_readers[rw->ltrw_r_active] = ldap_pvt_thread_self(); else rw->ltrw_more_readers = 1; #endif rw->ltrw_r_active++; ldap_pvt_thread_mutex_unlock( &rw->ltrw_mutex ); return 0; }
static void * bdb_tool_trickle_task( void *ctx, void *ptr ) { DB_ENV *env = ptr; int wrote; ldap_pvt_thread_mutex_lock( &bdb_tool_trickle_mutex ); while ( 1 ) { ldap_pvt_thread_cond_wait( &bdb_tool_trickle_cond, &bdb_tool_trickle_mutex ); if ( slapd_shutdown ) break; env->memp_trickle( env, 30, &wrote ); } ldap_pvt_thread_mutex_unlock( &bdb_tool_trickle_mutex ); return NULL; }
int ldap_pvt_thread_rmutex_lock( ldap_pvt_thread_rmutex_t *rmutex, ldap_pvt_thread_t owner ) { struct ldap_int_thread_rmutex_s *rm; assert( rmutex != NULL ); rm = *rmutex; assert( rm != NULL ); assert( rm->ltrm_valid == LDAP_PVT_THREAD_RMUTEX_VALID ); if( rm->ltrm_valid != LDAP_PVT_THREAD_RMUTEX_VALID ) return LDAP_PVT_THREAD_EINVAL; ldap_pvt_thread_mutex_lock( &rm->ltrm_mutex ); assert( rm->ltrm_depth >= 0 ); assert( rm->ltrm_waits >= 0 ); if( rm->ltrm_depth > 0 ) { /* already locked */ if ( !ldap_pvt_thread_equal( rm->ltrm_owner, owner )) { rm->ltrm_waits++; do { ldap_pvt_thread_cond_wait( &rm->ltrm_cond, &rm->ltrm_mutex ); } while( rm->ltrm_depth > 0 ); rm->ltrm_waits--; assert( rm->ltrm_waits >= 0 ); rm->ltrm_owner = owner; } } else { rm->ltrm_owner = owner; } rm->ltrm_depth++; ldap_pvt_thread_mutex_unlock( &rm->ltrm_mutex ); return 0; }
int ldap_pvt_thread_rdwr_wlock( ldap_pvt_thread_rdwr_t *rwlock ) { struct ldap_int_thread_rdwr_s *rw; assert( rwlock != NULL ); rw = *rwlock; assert( rw != NULL ); assert( rw->ltrw_valid == LDAP_PVT_THREAD_RDWR_VALID ); if( rw->ltrw_valid != LDAP_PVT_THREAD_RDWR_VALID ) return LDAP_PVT_THREAD_EINVAL; ldap_pvt_thread_mutex_lock( &rw->ltrw_mutex ); assert( rw->ltrw_w_active >= 0 ); assert( rw->ltrw_w_wait >= 0 ); assert( rw->ltrw_r_active >= 0 ); assert( rw->ltrw_r_wait >= 0 ); if ( rw->ltrw_w_active > 0 || rw->ltrw_r_active > 0 ) { rw->ltrw_w_wait++; do { ldap_pvt_thread_cond_wait( &rw->ltrw_write, &rw->ltrw_mutex ); } while ( rw->ltrw_w_active > 0 || rw->ltrw_r_active > 0 ); rw->ltrw_w_wait--; assert( rw->ltrw_w_wait >= 0 ); } #ifdef LDAP_RDWR_DEBUG rw->ltrw_writer = ldap_pvt_thread_self(); #endif rw->ltrw_w_active++; ldap_pvt_thread_mutex_unlock( &rw->ltrw_mutex ); return 0; }
/* * Process any unhandled replication entries in the queue. */ static int Ri_process( Ri *ri ) { Rq *rq = sglob->rq; Re *re = NULL, *new_re = NULL; int rc ; char *errmsg; (void) SIGNAL( LDAP_SIGUSR1, do_nothing ); #ifdef SIGPIPE (void) SIGNAL( SIGPIPE, SIG_IGN ); #endif if ( ri == NULL ) { #ifdef NEW_LOGGING LDAP_LOG ( SLURPD, ERR, "Ri_process: " "Error: ri == NULL!\n", 0, 0, 0 ); #else Debug( LDAP_DEBUG_ANY, "Error: Ri_process: ri == NULL!\n", 0, 0, 0 ); #endif return -1; } /* * Startup code. See if there's any work to do. If not, wait on the * rq->rq_more condition variable. */ rq->rq_lock( rq ); while ( !sglob->slurpd_shutdown && (( re = rq->rq_gethead( rq )) == NULL )) { /* No work */ if ( sglob->one_shot_mode ) { /* give up if in one shot mode */ rq->rq_unlock( rq ); return 0; } /* wait on condition variable */ ldap_pvt_thread_cond_wait( &rq->rq_more, &rq->rq_mutex ); } /* * When we get here, there's work in the queue, and we have the * queue locked. re should be pointing to the head of the queue. */ rq->rq_unlock( rq ); while ( !sglob->slurpd_shutdown ) { if ( re != NULL ) { if ( !ismine( ri, re )) { /* The Re doesn't list my host:port */ #ifdef NEW_LOGGING LDAP_LOG ( SLURPD, DETAIL1, "Ri_process: " "Replica %s:%d, skip repl record for %s (not mine)\n", ri->ri_hostname, ri->ri_port, re->re_dn ); #else Debug( LDAP_DEBUG_TRACE, "Replica %s:%d, skip repl record for %s (not mine)\n", ri->ri_hostname, ri->ri_port, re->re_dn ); #endif } else if ( !isnew( ri, re )) { /* This Re is older than my saved status information */ #ifdef NEW_LOGGING LDAP_LOG ( SLURPD, DETAIL1, "Ri_process: " "Replica %s:%d, skip repl record for %s (old)\n", ri->ri_hostname, ri->ri_port, re->re_dn ); #else Debug( LDAP_DEBUG_TRACE, "Replica %s:%d, skip repl record for %s (old)\n", ri->ri_hostname, ri->ri_port, re->re_dn ); #endif } else { rc = do_ldap( ri, re, &errmsg ); switch ( rc ) { case DO_LDAP_ERR_RETRYABLE: ldap_pvt_thread_sleep( RETRY_SLEEP_TIME ); #ifdef NEW_LOGGING LDAP_LOG ( SLURPD, DETAIL1, "Ri_process: " "Retrying operation for DN %s on replica %s:%d\n", re->re_dn, ri->ri_hostname, ri->ri_port ); #else Debug( LDAP_DEBUG_ANY, "Retrying operation for DN %s on replica %s:%d\n", re->re_dn, ri->ri_hostname, ri->ri_port ); #endif continue; break; case DO_LDAP_ERR_FATAL: { /* Non-retryable error. Write rejection log. */ int ld_errno = 0; ldap_get_option(ri->ri_ldp, LDAP_OPT_ERROR_NUMBER, &ld_errno); write_reject( ri, re, ld_errno, errmsg ); /* Update status ... */ (void) sglob->st->st_update( sglob->st, ri->ri_stel, re ); /* ... and write to disk */ (void) sglob->st->st_write( sglob->st ); } break; default: /* LDAP op completed ok - update status... */ (void) sglob->st->st_update( sglob->st, ri->ri_stel, re ); /* ... and write to disk */ (void) sglob->st->st_write( sglob->st ); break; } } } else { #ifdef NEW_LOGGING LDAP_LOG ( SLURPD, ERR, "Ri_process: " "Error: re is null in Ri_process\n", 0, 0, 0 ); #else Debug( LDAP_DEBUG_ANY, "Error: re is null in Ri_process\n", 0, 0, 0 ); #endif } rq->rq_lock( rq ); while ( !sglob->slurpd_shutdown && ((new_re = re->re_getnext( re )) == NULL )) { if ( sglob->one_shot_mode ) { rq->rq_unlock( rq ); return 0; } /* No work - wait on condition variable */ ldap_pvt_thread_cond_wait( &rq->rq_more, &rq->rq_mutex ); } re->re_decrefcnt( re ); re = new_re; rq->rq_unlock( rq ); if ( sglob->slurpd_shutdown ) { return 0; } } return 0; }
int bdb_tool_entry_close( BackendDB *be ) { if ( bdb_tool_info ) { slapd_shutdown = 1; #ifdef USE_TRICKLE ldap_pvt_thread_mutex_lock( &bdb_tool_trickle_mutex ); /* trickle thread may not have started yet */ while ( !bdb_tool_trickle_active ) ldap_pvt_thread_cond_wait( &bdb_tool_trickle_cond_end, &bdb_tool_trickle_mutex ); ldap_pvt_thread_cond_signal( &bdb_tool_trickle_cond ); while ( bdb_tool_trickle_active ) ldap_pvt_thread_cond_wait( &bdb_tool_trickle_cond_end, &bdb_tool_trickle_mutex ); ldap_pvt_thread_mutex_unlock( &bdb_tool_trickle_mutex ); #endif if ( bdb_tool_threads > 1 ) { ldap_pvt_thread_mutex_lock( &bdb_tool_index_mutex ); /* There might still be some threads starting */ while ( bdb_tool_index_tcount > 0 ) { ldap_pvt_thread_cond_wait( &bdb_tool_index_cond_main, &bdb_tool_index_mutex ); } bdb_tool_index_tcount = bdb_tool_threads - 1; ldap_pvt_thread_cond_broadcast( &bdb_tool_index_cond_work ); /* Make sure all threads are stopped */ while ( bdb_tool_index_tcount > 0 ) { ldap_pvt_thread_cond_wait( &bdb_tool_index_cond_main, &bdb_tool_index_mutex ); } ldap_pvt_thread_mutex_unlock( &bdb_tool_index_mutex ); ch_free( bdb_tool_index_threads ); ch_free( bdb_tool_index_rec ); bdb_tool_index_tcount = bdb_tool_threads - 1; } bdb_tool_info = NULL; slapd_shutdown = 0; } if( eh.bv.bv_val ) { ch_free( eh.bv.bv_val ); eh.bv.bv_val = NULL; } if( cursor ) { cursor->c_close( cursor ); cursor = NULL; } #ifdef BDB_TOOL_IDL_CACHING bdb_tool_idl_flush( be ); #endif if( nholes ) { unsigned i; fprintf( stderr, "Error, entries missing!\n"); for (i=0; i<nholes; i++) { fprintf(stderr, " entry %ld: %s\n", holes[i].id, holes[i].dn.bv_val); } return -1; } return 0; }
static long send_ldap_ber( Operation *op, BerElement *ber ) { Connection *conn = op->o_conn; ber_len_t bytes; long ret = 0; ber_get_option( ber, LBER_OPT_BER_BYTES_TO_WRITE, &bytes ); /* write only one pdu at a time - wait til it's our turn */ ldap_pvt_thread_mutex_lock( &conn->c_write1_mutex ); if (( op->o_abandon && !op->o_cancel ) || !connection_valid( conn ) || conn->c_writers < 0 ) { ldap_pvt_thread_mutex_unlock( &conn->c_write1_mutex ); return 0; } conn->c_writers++; while ( conn->c_writers > 0 && conn->c_writing ) { ldap_pvt_thread_cond_wait( &conn->c_write1_cv, &conn->c_write1_mutex ); } /* connection was closed under us */ if ( conn->c_writers < 0 ) { /* we're the last waiter, let the closer continue */ if ( conn->c_writers == -1 ) ldap_pvt_thread_cond_signal( &conn->c_write1_cv ); conn->c_writers++; ldap_pvt_thread_mutex_unlock( &conn->c_write1_mutex ); return 0; } /* Our turn */ conn->c_writing = 1; /* write the pdu */ while( 1 ) { int err; if ( ber_flush2( conn->c_sb, ber, LBER_FLUSH_FREE_NEVER ) == 0 ) { ret = bytes; break; } err = sock_errno(); /* * we got an error. if it's ewouldblock, we need to * wait on the socket being writable. otherwise, figure * it's a hard error and return. */ Debug( LDAP_DEBUG_CONNS, "ber_flush2 failed errno=%d reason=\"%s\"\n", err, sock_errstr(err) ); if ( err != EWOULDBLOCK && err != EAGAIN ) { conn->c_writers--; conn->c_writing = 0; ldap_pvt_thread_mutex_unlock( &conn->c_write1_mutex ); ldap_pvt_thread_mutex_lock( &conn->c_mutex ); connection_closing( conn, "connection lost on write" ); ldap_pvt_thread_mutex_unlock( &conn->c_mutex ); return -1; } /* wait for socket to be write-ready */ slap_writewait_play( op ); ldap_pvt_thread_mutex_lock( &conn->c_write2_mutex ); conn->c_writewaiter = 1; slapd_set_write( conn->c_sd, 2 ); ldap_pvt_thread_mutex_unlock( &conn->c_write1_mutex ); ldap_pvt_thread_pool_idle( &connection_pool ); ldap_pvt_thread_cond_wait( &conn->c_write2_cv, &conn->c_write2_mutex ); conn->c_writewaiter = 0; ldap_pvt_thread_mutex_unlock( &conn->c_write2_mutex ); ldap_pvt_thread_pool_unidle( &connection_pool ); ldap_pvt_thread_mutex_lock( &conn->c_write1_mutex ); if ( conn->c_writers < 0 ) { ret = 0; break; } } conn->c_writing = 0; if ( conn->c_writers < 0 ) { conn->c_writers++; if ( !conn->c_writers ) ldap_pvt_thread_cond_signal( &conn->c_write1_cv ); } else { conn->c_writers--; ldap_pvt_thread_cond_signal( &conn->c_write1_cv ); } ldap_pvt_thread_mutex_unlock( &conn->c_write1_mutex ); return ret; }
static int mdb_tool_index_add( Operation *op, MDB_txn *txn, Entry *e ) { struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; if ( !mdb->mi_nattrs ) return 0; if ( mdb_tool_threads > 1 ) { IndexRec *ir; int i, rc; Attribute *a; ir = mdb_tool_index_rec; for (i=0; i<mdb->mi_nattrs; i++) ir[i].ir_attrs = NULL; for ( a = e->e_attrs; a != NULL; a = a->a_next ) { rc = mdb_index_recset( mdb, a, a->a_desc->ad_type, &a->a_desc->ad_tags, ir ); if ( rc ) return rc; } for (i=0; i<mdb->mi_nattrs; i++) { if ( !ir[i].ir_ai ) break; rc = mdb_cursor_open( txn, ir[i].ir_ai->ai_dbi, &ir[i].ir_ai->ai_cursor ); if ( rc ) return rc; } mdb_tool_ix_id = e->e_id; mdb_tool_ix_op = op; mdb_tool_ix_txn = txn; ldap_pvt_thread_mutex_lock( &mdb_tool_index_mutex ); /* Wait for all threads to be ready */ while ( mdb_tool_index_tcount ) { ldap_pvt_thread_cond_wait( &mdb_tool_index_cond_main, &mdb_tool_index_mutex ); } for ( i=1; i<mdb_tool_threads; i++ ) mdb_tool_index_rec[i].ir_i = LDAP_BUSY; mdb_tool_index_tcount = mdb_tool_threads - 1; ldap_pvt_thread_mutex_unlock( &mdb_tool_index_mutex ); ldap_pvt_thread_cond_broadcast( &mdb_tool_index_cond_work ); rc = mdb_index_recrun( op, txn, mdb, ir, e->e_id, 0 ); if ( rc ) return rc; ldap_pvt_thread_mutex_lock( &mdb_tool_index_mutex ); for ( i=1; i<mdb_tool_threads; i++ ) { if ( mdb_tool_index_rec[i].ir_i == LDAP_BUSY ) { ldap_pvt_thread_cond_wait( &mdb_tool_index_cond_main, &mdb_tool_index_mutex ); i--; continue; } if ( mdb_tool_index_rec[i].ir_i ) { rc = mdb_tool_index_rec[i].ir_i; break; } } ldap_pvt_thread_mutex_unlock( &mdb_tool_index_mutex ); return rc; } else { return mdb_index_entry_add( op, txn, e ); } }
int mdb_tool_entry_close( BackendDB *be ) { if ( mdb_tool_info ) { slapd_shutdown = 1; ldap_pvt_thread_mutex_lock( &mdb_tool_index_mutex ); /* There might still be some threads starting */ while ( mdb_tool_index_tcount > 0 ) { ldap_pvt_thread_cond_wait( &mdb_tool_index_cond_main, &mdb_tool_index_mutex ); } mdb_tool_index_tcount = mdb_tool_threads - 1; ldap_pvt_thread_cond_broadcast( &mdb_tool_index_cond_work ); /* Make sure all threads are stopped */ while ( mdb_tool_index_tcount > 0 ) { ldap_pvt_thread_cond_wait( &mdb_tool_index_cond_main, &mdb_tool_index_mutex ); } ldap_pvt_thread_mutex_unlock( &mdb_tool_index_mutex ); mdb_tool_info = NULL; slapd_shutdown = 0; ch_free( mdb_tool_index_rec ); mdb_tool_index_tcount = mdb_tool_threads - 1; } if( idcursor ) { mdb_cursor_close( idcursor ); idcursor = NULL; } if( cursor ) { mdb_cursor_close( cursor ); cursor = NULL; } if( mdb_tool_txn ) { int rc; MDB_TOOL_IDL_FLUSH( be, mdb_tool_txn ); if (( rc = mdb_txn_commit( mdb_tool_txn ))) { Debug( LDAP_DEBUG_ANY, LDAP_XSTRING(mdb_tool_entry_close) ": database %s: " "txn_commit failed: %s (%d)\n", be->be_suffix[0].bv_val, mdb_strerror(rc), rc ); return -1; } mdb_tool_txn = NULL; } if( nholes ) { unsigned i; fprintf( stderr, "Error, entries missing!\n"); for (i=0; i<nholes; i++) { fprintf(stderr, " entry %ld: %s\n", holes[i].id, holes[i].dn.bv_val); } nholes = 0; return -1; } return 0; }