OLcfgCt{Oc|At}:9 variant
OLcfgCt{Oc|At}:10 alias
OLcfgCt{Oc|At}:11 dsaschema
+OLcfgCt{Oc|At}:12 resultstats
--- /dev/null
+servers
+clients
--- /dev/null
+# $OpenLDAP$
+# This work is part of OpenLDAP Software <http://www.openldap.org/>.
+#
+# Copyright 1998-2024 The OpenLDAP Foundation.
+# Copyright 2024 Symas Corp. All Rights Reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted only as authorized by the OpenLDAP
+# Public License.
+#
+# A copy of this license is available in the file LICENSE in the
+# top-level directory of the distribution or, alternatively, at
+# <http://www.OpenLDAP.org/license.html>.
+
+LDAP_SRC = ../../..
+LDAP_BUILD = $(LDAP_SRC)
+LDAP_INC = -I$(LDAP_BUILD)/include -I$(LDAP_SRC)/include -I$(LDAP_SRC)/servers/slapd
+LDAP_LIB = $(LDAP_BUILD)/libraries/libldap/libldap.la \
+ $(LDAP_BUILD)/libraries/liblber/liblber.la
+
+SRCDIR = ./
+
+PLAT = UNIX
+NT_LIB = -L$(LDAP_BUILD)/servers/slapd -lslapd
+NT_LDFLAGS = -no-undefined -avoid-version
+UNIX_LDFLAGS = -version-info $(LTVER)
+
+LIBTOOL = $(LDAP_BUILD)/libtool
+INSTALL = /usr/bin/install
+CC = gcc
+OPT = -g -O0
+DEFS = -DSLAPD_OVER_RESULTSTATS=SLAPD_MOD_DYNAMIC
+INCS = $(LDAP_INC)
+LIBS = $($(PLAT)_LIB) $(LDAP_LIB)
+LD_FLAGS = $(LDFLAGS) $($(PLAT)_LDFLAGS) -rpath $(moduledir) -module
+
+PROGRAMS = resultstats.la
+MANPAGES = slapo_resultstats.5
+LTVER = 0:0:0
+CLEAN = *.o *.lo *.la .libs
+prefix=/usr/local
+exec_prefix=$(prefix)
+ldap_subdir=/openldap
+
+libdir=$(exec_prefix)/lib
+libexecdir=$(exec_prefix)/libexec
+moduledir = $(libexecdir)$(ldap_subdir)
+mandir = $(exec_prefix)/share/man
+man5dir = $(mandir)/man5
+
+.SUFFIXES: .c .o .lo
+
+.c.lo:
+ $(LIBTOOL) --mode=compile $(CC) $(CFLAGS) $(OPT) $(CPPFLAGS) $(DEFS) $(INCS) -c $<
+
+all: $(PROGRAMS)
+
+d :=
+sp :=
+dir := tests
+include $(dir)/Rules.mk
+
+
+resultstats.la: resultstats.lo
+ $(LIBTOOL) --mode=link $(CC) $(LD_FLAGS) -o $@ $? $(LIBS)
+
+clean:
+ rm -rf $(CLEAN)
+
+install: install-lib install-man
+
+install-lib: $(PROGRAMS)
+ mkdir -p $(DESTDIR)$(moduledir)
+ for p in $(PROGRAMS) ; do \
+ $(LIBTOOL) --mode=install cp $$p $(DESTDIR)$(moduledir) ; \
+ done
+
+install-man: $(MANPAGES)
+ mkdir -p $(DESTDIR)$(man5dir)
+ $(INSTALL) -m 644 $(MANPAGES) $(DESTDIR)$(man5dir)
--- /dev/null
+/* resultstats.c - gather result code statistics per operation */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2005-2025 The OpenLDAP Foundation.
+ * Copyright 2025 Symas Corp. All Rights Reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Nadezhda Ivanova for Symas Corp. for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_RESULTSTATS
+
+#include <stdio.h>
+
+#include <ac/unistd.h>
+#include <ac/string.h>
+#include <ac/ctype.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "slap-config.h"
+#include "lutil.h"
+#include "back-monitor/back-monitor.h"
+
+static slap_overinst resultstats;
+
+#define STATS_SIZE LDAP_OTHER+2
+
+static struct resultstats_ops_t {
+ struct berval rdn;
+ struct berval nrdn;
+} resultstats_op[] = {
+ { BER_BVC( "cn=Bind" ), BER_BVC( "cn=bind" ), },
+ { BER_BVC( "cn=Unbind" ), BER_BVC( "cn=unbind" ), },
+ { BER_BVC( "cn=Search" ), BER_BVC( "cn=search" ), },
+ { BER_BVC( "cn=Compare" ), BER_BVC( "cn=compare" ), },
+ { BER_BVC( "cn=Modify" ), BER_BVC( "cn=modify" ), },
+ { BER_BVC( "cn=Modrdn" ), BER_BVC( "cn=modrdn" ), },
+ { BER_BVC( "cn=Add" ), BER_BVC( "cn=add" ), },
+ { BER_BVC( "cn=Delete" ), BER_BVC( "cn=delete" ), },
+ { BER_BVC( "cn=Abandon" ), BER_BVC( "cn=abandon" ), },
+ { BER_BVC( "cn=Extended" ), BER_BVC( "cn=extended" ), },
+ { BER_BVNULL, BER_BVNULL }
+};
+
+typedef struct resultstats_t {
+ uintptr_t stats[ SLAP_OP_LAST ][ STATS_SIZE ];
+ struct berval monitor_ndn;
+ struct berval rslt_rdn;
+ struct berval mss_ndn;
+ monitor_subsys_t *mss;
+} resultstats_t;
+
+
+static int resultstats_monitor_db_init( void );
+static int resultstats_monitor_db_open( BackendDB *be );
+static int resultstats_monitor_db_close( BackendDB *be );
+//static int resultstats_monitor_db_destroy( BackendDB *be );
+
+static AttributeDescription *ad_olmResultCodeStat;
+static ObjectClass *oc_olmResultStatOperation;
+static ObjectClass *oc_monitorContainer;
+
+
+static struct {
+ char *desc;
+ ObjectClass **ocp;
+} s_oc[] = {
+ { "( OLcfgCtOc:12.1 "
+ "NAME ( 'olmResultStatOperation' ) "
+ "SUP monitoredObject "
+ "MAY ( "
+ "olmResultCodeStat"
+ " ) )",
+ &oc_olmResultStatOperation },
+
+ { NULL }
+};
+
+static struct {
+ char *desc;
+ AttributeDescription **adp;
+} s_ad[] = {
+ { "( OLcfgCtAt:12.1 "
+ "NAME 'olmResultCodeStat' "
+ "DESC 'Number of times an LDAP code result has been sent for this operation type' "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )",
+ &ad_olmResultCodeStat },
+
+ { NULL }
+};
+
+static AttributeDescription *ads[ STATS_SIZE ];
+
+static int
+resultstats_monitor_ops_update(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e,
+ void *priv )
+{
+ uintptr_t *stats = (uintptr_t *)priv;
+ int i, rc;
+ for ( i = 0; i < STATS_SIZE; i++ ) {
+ Attribute *a;
+ AttributeDescription *ad = NULL;
+ char name_buf[ BUFSIZ ];
+ char val_buf[ BUFSIZ ];
+ const char *text;
+ struct berval bv;
+ unsigned long int value = __atomic_load_n( &stats[i], __ATOMIC_RELAXED );
+ if ( value == 0 ) {
+ continue;
+ }
+
+ /* TODO This should be optimised by maintaining the attributes in the entry sorted by code,
+ and avoid searching through the full list every time */
+ ad = ads[i];
+ if ( ad == NULL ) {
+ if ( i <= LDAP_OTHER )
+ snprintf( name_buf, sizeof( name_buf ), "olmResultCodeStat;x-resultcode-%d", i );
+ else
+ snprintf( name_buf, sizeof( name_buf ), "olmResultCodeStat;x-resultcode-more");
+
+ rc = slap_str2ad( name_buf, &ad, &text );
+ if ( rc != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "resultstats_monitor_ops_update: "
+ "unable to find attribute description %s \n ", name_buf );
+ return 0;
+ }
+ }
+ ads[i] = ad;
+ bv.bv_val = val_buf;
+ bv.bv_len = snprintf( val_buf, sizeof( val_buf ), "%lu", value );
+ a = attr_find( e->e_attrs, ad );
+ if ( a != NULL ) {
+ ber_bvreplace( &a->a_vals[ 0 ], &bv );
+ } else {
+ attr_merge_one( e, ad, &bv, NULL );
+ }
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+resultstats_monitor_initialize( void )
+{
+ static int resultstats_monitor_initialized = 0;
+ int code, i;
+
+ if ( backend_info( "monitor" ) == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "resultstats_monitor_initialize: resultstats overlay requires cn=monitor");
+ return -1;
+ }
+
+ if ( resultstats_monitor_initialized++ ) {
+ return 0;
+ }
+
+ ad_define_option( "x-resultcode-", __FILE__, __LINE__ );
+ for ( i = 0; s_ad[i].desc != NULL; i++ ) {
+ code = register_at( s_ad[i].desc, s_ad[i].adp, 0 );
+ if ( code ) {
+ Debug( LDAP_DEBUG_ANY,
+ "resultstats_monitor_initialize: register_at #%d failed\n", i );
+ return code;
+ }
+ }
+
+ for ( i = 0; s_oc[i].desc != NULL; i++ ) {
+ code = register_oc( s_oc[i].desc, s_oc[i].ocp, 0 );
+ if ( code ) {
+ Debug( LDAP_DEBUG_ANY,
+ "resultstat_monitor_initialize: register_oc #%d failed\n", i );
+ return code;
+ }
+ }
+
+ oc_monitorContainer = oc_find( "monitorContainer" );
+ if ( !oc_monitorContainer ) {
+ Debug( LDAP_DEBUG_ANY,
+ "resultstats_monitor_initialize: failed to find objectClass (monitorContainer)\n" );
+ return 5;
+ }
+ return 0;
+}
+
+static int
+resultstats_monitor_register_entries( monitor_extra_t *mbe,
+ resultstats_t *rslt,
+ monitor_subsys_t *ms,
+ Entry *parent )
+{
+ int i, rc;
+ Entry *e;
+ for ( i = 0; i < SLAP_OP_LAST; i++ ) {
+ monitor_callback_t *cb;
+
+ e = mbe->entry_stub( &ms->mss_dn, &ms->mss_ndn,
+ &resultstats_op[i].rdn,
+ oc_olmResultStatOperation, NULL, NULL );
+ if ( e == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "resultstats_monitor_register_entries: "
+ "unable to create entry \"%s,%s\"\n",
+ resultstats_op[i].rdn.bv_val,
+ rslt->monitor_ndn.bv_val );
+ return( -1 );
+ }
+ cb = ch_calloc( sizeof( monitor_callback_t ), 1 );
+ cb->mc_update = resultstats_monitor_ops_update;
+ cb->mc_private = (void *)rslt->stats[i];
+
+ rc = mbe->register_entry( e, cb, ms, 0 );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "resultstats_monitor_register_entries: "
+ "unable to register entry \"%s\" for monitoring\n",
+ e->e_name.bv_val );
+ ch_free( cb );
+ entry_free( e );
+ return rc;
+ }
+ entry_free( e );
+ }
+ return 0;
+}
+
+static int
+resultstats_monitor_db_init( void )
+{
+ return resultstats_monitor_initialize();
+}
+
+static int
+resultstats_monitor_mss_init(
+ BackendDB *be,
+ monitor_subsys_t *ms )
+{
+ slap_overinst *on = (slap_overinst *)ms->mss_private;
+ resultstats_t *rslt = (resultstats_t *)on->on_bi.bi_private;
+ monitor_extra_t *mbe;
+
+ Entry *parent;
+ int rc;
+
+ assert( be != NULL );
+ mbe = (monitor_extra_t *) be->bd_info->bi_extra;
+
+ parent = mbe->entry_stub( &rslt->monitor_ndn, &rslt->monitor_ndn,
+ &rslt->rslt_rdn, oc_monitorContainer, NULL, NULL );
+ if ( parent == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "resultstats_monitor_mss_init: "
+ "unable to create entry \"%s,%s\"\n",
+ rslt->rslt_rdn.bv_val,
+ rslt->monitor_ndn.bv_val );
+ return( -1 );
+ }
+
+ ber_dupbv( &ms->mss_dn, &parent->e_name );
+ ber_dupbv( &ms->mss_ndn, &parent->e_nname );
+ ber_dupbv( &ms->mss_rdn, &rslt->rslt_rdn );
+
+ rc = mbe->register_entry( parent, NULL, ms, MONITOR_F_PERSISTENT_CH );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "resultstats_monitor_mss_init: "
+ "unable to register entry \"%s,%s\"\n",
+ ms->mss_rdn.bv_val,
+ ms->mss_ndn.bv_val );
+ entry_free( parent );
+ return ( -1 );
+ }
+
+ rc = resultstats_monitor_register_entries( mbe, rslt, ms, parent );
+
+ entry_free( parent );
+
+ return rc;
+}
+
+static int
+resultstats_monitor_mss_destroy (
+ BackendDB *be,
+ monitor_subsys_t *ms )
+{
+ if ( ms->mss_ndn.bv_len > 0 ) {
+ ch_free( ms->mss_ndn.bv_val );
+ }
+ if ( ms->mss_dn.bv_len > 0 ) {
+ ch_free( ms->mss_dn.bv_val );
+ }
+ return 0;
+}
+
+static int
+resultstats_monitor_db_open( BackendDB *be )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ resultstats_t *rslt = (resultstats_t *)on->on_bi.bi_private;
+ BackendInfo *bi;
+ int rc = 0;
+ monitor_extra_t *mbe;
+ Entry * parent;
+
+ /* check if monitor is configured and usable */
+ bi = backend_info( "monitor" );
+ if ( !bi || !bi->bi_extra ) {
+ return -1;
+ }
+ mbe = bi->bi_extra;
+
+ /* don't bother if monitor is not configured */
+ if ( !mbe->is_configured() ) {
+ Debug( LDAP_DEBUG_CONFIG, "resultstats_monitor_db_open: "
+ "monitoring disabled; "
+ "configure monitor database to enable\n" );
+ return -1;
+ }
+
+ BER_BVZERO( &rslt->monitor_ndn );
+ if ( ( rc = mbe->register_overlay( be, on, &rslt->monitor_ndn ) ) != 0 ) {
+ return rc;
+ }
+ parent = mbe->entry_stub( &rslt->monitor_ndn, &rslt->monitor_ndn,
+ &rslt->rslt_rdn, oc_monitorContainer, NULL, NULL );
+ if ( parent == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "resultstats_monitor_mss_init: "
+ "unable to create entry \"%s,%s\"\n",
+ rslt->rslt_rdn.bv_val,
+ rslt->monitor_ndn.bv_val );
+ return( -1 );
+ }
+
+ ber_dupbv( &rslt->mss_ndn, &parent->e_nname );
+ /* Check if the subsystem already exsists. This can happen if the overlay
+ has previously added and removed. For now it is safe to assume
+ that the dn will be unique, as databases cannot be removed.
+ This should be re-done when we enable database removal and fix monitor
+ so that subsystems can be unregistered */
+ rslt->mss = monitor_back_get_subsys_by_dn( &rslt->mss_ndn, 0 );
+ if ( rslt->mss == NULL ) {
+ /* this will leak at monitor_db_destroy, but it can't be helped */
+ rslt->mss = (monitor_subsys_t *)ch_calloc( 1, sizeof( monitor_subsys_t ) );
+ rslt->mss->mss_name = "Result code statistics";
+ rslt->mss->mss_flags = MONITOR_F_PERSISTENT_CH;
+ rslt->mss->mss_open = resultstats_monitor_mss_init;
+ rslt->mss->mss_destroy = resultstats_monitor_mss_destroy;
+ rslt->mss->mss_private = on;
+
+ if ( mbe->register_subsys_late( rslt->mss ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "resultsats_monitor_db_open: "
+ "failed to register result statistics subsystem" );
+ return -1;
+ }
+ } else {
+ rslt->mss->mss_private = on;
+ rc = mbe->register_entry( parent, NULL, rslt->mss, MONITOR_F_PERSISTENT_CH );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "resultstats_monitor_db_open: "
+ "unable to register entry \"%s,%s\"\n",
+ rslt->mss->mss_rdn.bv_val,
+ rslt->mss->mss_ndn.bv_val );
+ entry_free( parent );
+ return ( -1 );
+ }
+ rc = resultstats_monitor_register_entries( mbe, rslt, rslt->mss, parent );
+ }
+ entry_free( parent );
+
+ return rc;
+}
+
+static int
+resultstats_monitor_db_close( BackendDB *be )
+{
+
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ resultstats_t *rslt = (resultstats_t *)on->on_bi.bi_private;
+ monitor_extra_t *mbe;
+ BackendInfo *mi = backend_info( "monitor" );
+
+ if ( mi && mi->bi_extra ) {
+ int i;
+ mbe = mi->bi_extra;
+ for ( i = 0; i < SLAP_OP_LAST; i++ ) {
+ struct berval bv;
+ char buf[ BACKMONITOR_BUFSIZE ];
+ bv.bv_len = snprintf( buf, sizeof( buf ), "%s,%s",
+ resultstats_op[i].nrdn.bv_val,
+ rslt->mss_ndn.bv_val );
+ bv.bv_val = buf;
+ mbe->unregister_entry( &bv );
+
+ }
+ mbe->unregister_entry( &rslt->mss_ndn );
+ }
+ if ( !BER_BVISNULL(&rslt->mss_ndn) ) {
+ ch_free( rslt->mss_ndn.bv_val );
+ BER_BVZERO( &rslt->mss_ndn );
+ }
+ /* Make sure this does not point to a non-existent overlay instance */
+ rslt->mss->mss_private = NULL;
+ return 0;
+}
+
+static int
+resultstats_response( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ resultstats_t *rslt = (resultstats_t *)on->on_bi.bi_private;
+ int code, opidx;
+
+ /* skip internal ops */
+ if ( rs->sr_type != REP_RESULT || op->o_do_not_cache ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ code = slap_map_api2result( rs );
+ if ( code > LDAP_OTHER )
+ code = LDAP_OTHER+1;
+
+ opidx = slap_req2op( op->o_tag );
+ __atomic_fetch_add( &(rslt->stats[opidx][code]), 1, __ATOMIC_RELAXED );
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+resultstats_db_init( BackendDB *be, ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ resultstats_t *rslt;
+
+ rslt = (resultstats_t *)ch_calloc( 1, sizeof( resultstats_t ) );
+
+ on->on_bi.bi_private = (void *)rslt;
+ ber_str2bv( "cn=Result Stats", 0, 1,
+ &rslt->rslt_rdn );
+ return resultstats_monitor_db_init();
+}
+
+static int
+resultstats_db_open( BackendDB *be, ConfigReply *cr )
+{
+ return resultstats_monitor_db_open( be );
+}
+
+static int
+resultstats_db_destroy( BackendDB *be, ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ resultstats_t *rslt = (resultstats_t *)on->on_bi.bi_private;
+ if ( rslt->rslt_rdn.bv_len > 0 ) {
+ ch_free( rslt->rslt_rdn.bv_val );
+ }
+ ch_free( rslt );
+ return 0;
+}
+
+static int
+resultstats_db_close( BackendDB *be, ConfigReply *cr )
+{
+ return resultstats_monitor_db_close( be );
+}
+
+int
+resultstats_initialize( void )
+{
+ int code;
+
+ resultstats.on_bi.bi_type = "resultstats";
+ resultstats.on_bi.bi_db_init = resultstats_db_init;
+ resultstats.on_bi.bi_db_open = resultstats_db_open;
+ resultstats.on_bi.bi_db_destroy = resultstats_db_destroy;
+ resultstats.on_bi.bi_db_close = resultstats_db_close;
+ resultstats.on_bi.bi_flags = SLAPO_BFLAG_SINGLE;
+ resultstats.on_response = resultstats_response;
+
+ code = resultstats_monitor_initialize();
+ if ( code != 0)
+ return code;
+
+ return overlay_register( &resultstats );
+}
+
+#if SLAPD_OVER_RESULTSTATS == SLAPD_MOD_DYNAMIC
+int
+init_module( int argc, char *argv[] )
+{
+ return resultstats_initialize();
+}
+#endif /* SLAPD_OVER_RESULTSTATS == SLAPD_MOD_DYNAMIC */
+
+#endif /* SLAPD_OVER_RESULTSTATS */
--- /dev/null
+.TH SLAPO-RESULTSTATS 5 "RELEASEDATE" "OpenLDAP LDVERSION"
+.\" Copyright 2005-2025 The OpenLDAP Foundation All Rights Reserved.
+.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
+.\" $OpenLDAP$
+.SH NAME
+slapo\-resultstats \- Operation result statistics for slapd
+.SH SYNOPSIS
+ETCDIR/slapd.conf
+.SH DESCRIPTION
+The
+.B resultstats
+overlay adds per-operation counters for LDAP operation results and
+exposes them via
+.B cn=monitor.
+Result codes from
+.B LdapSuccess(0)
+to
+.B LdapOther(80)
+are counted separately as individual values. Codes higher than that are counted
+together.
+.P
+The overlay can be configured on a particular database, or on the front-end,
+or both. When configured on the front-end, it will count operations
+processed by all databases. When configured on a particular databases,
+only operations processed by that database will be counted. Monitoring
+must be enabled for the database (mdb databases have monitoring on by default),
+and
+.B cn=monitor
+bust be configured.
+.P
+.B Note:
+Bind request responses will be counted for the database where
+the identity used is located. For example, binding with the rootdn of
+the database will be counted for that database, but anonymous binds
+will be counted only at the front-end level. Failed schema checks
+(such as undefinedAttributeType) cannot be counted at all, since they
+happen before a database is chosen.
+.P
+When enabled, the resultstats overlay will add a
+.B cn=Result Stats
+container under the database entry in cn=monitor, and under it entries
+for all types of LDAP operations. The statistic is exposed via the
+.B olmResultCodeStat
+attribute with the
+.B x-resultcode-<number>
+tag. Errors higher than 80 will be exposed under the
+.B x-resultcode-more
+tag. Only result codes with counters higher than 0
+will be displayed.
+
+.SH EXAPLES
+This is an example entry for search requests, where we have 1 successful operation and one that returned
+.B NoSuchObject(32)
+.P
+.nf
+dn: cn=Search,cn=Result Stats,cn=database 2,cn=databases,cn=monitor
+objectClass: olmResultStatOperation
+olmResultCodeStat;x-resultcode-0: 1
+olmResultCodeStat;x-resultcode-32: 1
+entryDN: cn=Search,cn=Result Stats,cn=database 2,cn=databases,cn=monitor
+
+.SH CONFIGURATION
+This overlay does not have any configuration directives.
+.SH Configuration Example
+.nf
+moduleload resultstats.la
+
+database config
+rootdn cn=config
+rootpw password
+
+#resultstats configured on the front-end
+#entries will appear under cn=Result Stats,cn=database 0,cn=databases,cn=monitor
+overlay resultstats
+
+database monitor
+
+database mdb
+
+suffix dc=example,dc=com
+rootdn cn=admin,dc=example,dc=com
+rootpw password
+
+#resultstats configured on this database
+#entries will appear under cn=Result Stats,cn=database 2,cn=databases,cn=monitor
+overlay resultstats
+
+.SH FILES
+.TP
+ETCDIR/slapd.conf
+default slapd configuration file
+.SH SEE ALSO
+.BR slapd.conf (5),
+.BR slapd\-monitor (5),
+.SH AUTHOR
+Nadezhda Ivanova, on behalf of Symas Corp.
--- /dev/null
+progs
+schema
+testdata
+testrun
--- /dev/null
+sp := $(sp).x
+dirstack_$(sp) := $(d)
+d := $(dir)
+
+.PHONY: test
+
+CLEAN += clients servers tests/progs tests/schema tests/testdata tests/testrun
+
+test: all clients servers tests/progs
+
+test:
+ cd tests; \
+ SRCDIR=$(abspath $(LDAP_SRC)) \
+ LDAP_BUILD=$(abspath $(LDAP_BUILD)) \
+ TOPDIR=$(abspath $(SRCDIR)) \
+ LIBTOOL=$(abspath $(LIBTOOL)) \
+ $(abspath $(SRCDIR))/tests/run test001-resultstats
+
+servers clients tests/progs:
+ ln -s $(abspath $(LDAP_BUILD))/$@ $@
+
+d := $(dirstack_$(sp))
+sp := $(basename $(sp))
--- /dev/null
+# provider slapd config -- for testing
+# $OpenLDAP$
+
+include @SCHEMADIR@/core.schema
+include @SCHEMADIR@/cosine.schema
+include @SCHEMADIR@/inetorgperson.schema
+
+pidfile @TESTDIR@/slapd.m.pid
+
+argsfile @TESTDIR@/slapd.m.args
+
+#mod#modulepath ../servers/slapd/back-@BACKEND@/
+#mod#moduleload back_@BACKEND@.la
+
+moduleload ../resultstats.la
+
+pidfile @TESTDIR@/slapd.1.pid
+
+#######################################################################
+# database definitions
+#######################################################################
+database @BACKEND@
+suffix "dc=example,dc=com"
+
+directory @TESTDIR@/db.1.a
+# root or superuser
+rootdn "cn=Manager,dc=example,dc=com"
+rootpw secret
+
+database config
+include @TESTDIR@/configpw.conf
+
+database monitor
--- /dev/null
+dn: dc=example,dc=com
+objectClass: top
+objectClass: organization
+objectClass: domainRelatedObject
+objectClass: dcObject
+dc: example
+l: Anytown, Michigan
+st: Michigan
+o: Example, Inc.
+o: EX
+o: Ex.
+description: The Example, Inc. at Anytown
+postalAddress: Example, Inc. $ 535 W. William St. $ Anytown, MI 48109 $ US
+telephoneNumber: +1 313 555 1817
+associatedDomain: example.com
+
+dn: ou=people,dc=example,dc=com
+objectClass: organizationalUnit
+ou: people
+description: All domain members
+
+dn: cn=user01,ou=people,dc=example,dc=com
+cn: user01
+objectClass: inetOrgPerson
+userPassword:: UEBzc3cwcmQ=
+roomNumber: 101
+carLicense: 1234ha
+sn: user01
+mobile: 12345678
+mobile: 987654321
+ou: people
+preferredLanguage: English
+description: This is user user01
+
+dn: cn=user02,ou=people,dc=example,dc=com
+cn: user02
+objectClass: inetOrgPerson
+userPassword:: UEBzc3cwcmQ=
+roomNumber: 102
+carLicense: 1234hb
+sn: user02
+mobile: 12345678
+mobile: 987654321
+ou: people
+preferredLanguage: English
+description: This is user user02
+
+dn: cn=user03,ou=people,dc=example,dc=com
+cn: user03
+objectClass: inetOrgPerson
+userPassword:: UEBzc3cwcmQ=
+roomNumber: 103
+carLicense: 1234hc
+sn: user03
+mobile: 12345678
+mobile: 987654321
+ou: people
+preferredLanguage: English
+description: This is user user03
+
+dn: cn=user04,ou=people,dc=example,dc=com
+cn: user04
+objectClass: inetOrgPerson
+userPassword:: UEBzc3cwcmQ=
+roomNumber: 104
+carLicense: 1234ha
+sn: user04
+mobile: 12345678
+mobile: 987654321
+ou: people
+preferredLanguage: English
+description: This is user user04
+
+dn: cn=user05,ou=people,dc=example,dc=com
+cn: user05
+objectClass: inetOrgPerson
+userPassword:: UEBzc3cwcmQ=
+roomNumber: 105
+carLicense: 1234hb
+sn: user05
+mobile: 12345678
+mobile: 987654321
+ou: people
+preferredLanguage: English
+description: This is user user05
+
+dn: cn=user06,ou=people,dc=example,dc=com
+cn: user06
+objectClass: inetOrgPerson
+userPassword:: UEBzc3cwcmQ=
+roomNumber: 106
+carLicense: 1234hc
+sn: user06
+mobile: 12345678
+mobile: 987654321
+ou: people
+preferredLanguage: English
+description: This is user user06
+
--- /dev/null
+dn: cn=Bind,cn=Result Stats,cn=frontend,cn=databases,cn=monitor
+objectClass: olmResultStatOperation
+cn: Bind
+olmResultCodeStat;x-resultcode-0: 28
+olmResultCodeStat;x-resultcode-49: 5
+
--- /dev/null
+dn: cn=Modify,cn=Result Stats,cn=database 1,cn=databases,cn=monitor
+objectClass: olmResultStatOperation
+cn: Modify
+olmResultCodeStat;x-resultcode-0: 5
+olmResultCodeStat;x-resultcode-32: 5
+
--- /dev/null
+# base="cn=Result Stats,cn=database 1,cn=databases,cn=monitor"...
+dn: cn=Result Stats,cn=database 1,cn=databases,cn=monitor
+objectClass: monitorContainer
+cn: Result Stats
+
+dn: cn=Bind,cn=Result Stats,cn=database 1,cn=databases,cn=monitor
+objectClass: olmResultStatOperation
+cn: Bind
+
+dn: cn=Unbind,cn=Result Stats,cn=database 1,cn=databases,cn=monitor
+objectClass: olmResultStatOperation
+cn: Unbind
+
+dn: cn=Search,cn=Result Stats,cn=database 1,cn=databases,cn=monitor
+objectClass: olmResultStatOperation
+cn: Search
+
+dn: cn=Compare,cn=Result Stats,cn=database 1,cn=databases,cn=monitor
+objectClass: olmResultStatOperation
+cn: Compare
+
+dn: cn=Modify,cn=Result Stats,cn=database 1,cn=databases,cn=monitor
+objectClass: olmResultStatOperation
+cn: Modify
+
+dn: cn=Modrdn,cn=Result Stats,cn=database 1,cn=databases,cn=monitor
+objectClass: olmResultStatOperation
+cn: Modrdn
+
+dn: cn=Add,cn=Result Stats,cn=database 1,cn=databases,cn=monitor
+objectClass: olmResultStatOperation
+cn: Add
+
+dn: cn=Delete,cn=Result Stats,cn=database 1,cn=databases,cn=monitor
+objectClass: olmResultStatOperation
+cn: Delete
+
+dn: cn=Abandon,cn=Result Stats,cn=database 1,cn=databases,cn=monitor
+objectClass: olmResultStatOperation
+cn: Abandon
+
+dn: cn=Extended,cn=Result Stats,cn=database 1,cn=databases,cn=monitor
+objectClass: olmResultStatOperation
+cn: Extended
+
--- /dev/null
+dn: cn=Search,cn=Result Stats,cn=database 1,cn=databases,cn=monitor
+objectClass: olmResultStatOperation
+cn: Search
+olmResultCodeStat;x-resultcode-0: 5
+olmResultCodeStat;x-resultcode-32: 5
+
--- /dev/null
+#!/bin/sh
+## $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2024 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+
+TOPSRCDIR="$SRCDIR" OBJDIR="${LDAP_BUILD}" SRCDIR="${SRCDIR}/tests" DEFSDIR="${SRCDIR}/scripts" SCRIPTDIR="${TOPDIR}/tests/scripts" "${LDAP_BUILD}/tests/run" $*
+
--- /dev/null
+#! /bin/sh
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2025 The OpenLDAP Foundation.
+## Copyright 2025 Symas Corp.
+##
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+##
+## ACKNOWLEDGEMENTS:
+## This work was developed in 2025 by Nadezhda Ivanova for Symas Corp.
+echo "running defines.sh"
+. $SRCDIR/scripts/defines.sh
+
+echo ""
+
+rm -rf $TESTDIR
+
+mkdir -p $TESTDIR $DBDIR1
+
+$SLAPPASSWD -g -n >$CONFIGPWF
+echo "rootpw `$SLAPPASSWD -T $CONFIGPWF`" >$TESTDIR/configpw.conf
+
+echo "Starting slapd on TCP/IP port $PORT1..."
+. $CONFFILTER $BACKEND < data/slapd.conf > $CONF1
+$SLAPD -f $CONF1 -h $URI1 -d $LVL > $LOG1 2>&1 &
+PID=$!
+if test $WAIT != 0 ; then
+ echo PID $PID
+ read foo
+fi
+KILLPIDS="$PID"
+
+sleep 1
+
+echo "Using ldapsearch to check that slapd is running..."
+for i in 0 1 2 3 4 5; do
+ $LDAPSEARCH -s base -b "$MONITOR" -H $URI1 \
+ 'objectclass=*' > /dev/null 2>&1
+ RC=$?
+ if test $RC = 0 ; then
+ break
+ fi
+ echo "Waiting 5 seconds for slapd to start..."
+ sleep 5
+done
+if test $RC != 0 ; then
+ echo "ldapsearch failed ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+fi
+
+echo "Using ldapadd to populate the database..."
+$LDAPADD -D "$MANAGERDN" -H $URI1 -w $PASSWD < \
+ data/test001-add.ldif > $TESTOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+ echo "ldapadd failed ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+fi
+
+cat /dev/null > $SEARCHOUT
+
+echo "Searching base=\"$BASEDN\"..."
+echo "# searching base=\"$BASEDN\"..." >> $SEARCHOUT
+$LDAPSEARCH -S "" -H $URI1 -b "$BASEDN" >> $SEARCHOUT 2>&1
+RC=$?
+
+if test $RC != 0 ; then
+ echo "Search failed ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+fi
+#enable the overlay
+echo "Modifying cn=config, enabling resultstats overlay on $BACKEND"
+$LDAPADD -D cn=config -H $URI1 -y $CONFIGPWF \
+ >> $TESTOUT 2>&1 << EOMODS
+dn: olcOverlay=resultstats,olcDatabase={1}$BACKEND,cn=config
+objectClass: olcOverlayConfig
+EOMODS
+RC=$?
+if test $RC != 0 ; then
+ echo "ldapadd failed ($RC)! Unable to start resultstats overlay"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+fi
+echo "Modifying cn=config, enabling resultstats overlay on frontend"
+$LDAPADD -D cn=config -H $URI1 -y $CONFIGPWF \
+ >> $TESTOUT 2>&1 << EOMODS
+dn: olcOverlay=resultstats,olcDatabase={-1}frontend,cn=config
+objectClass: olcOverlayConfig
+EOMODS
+RC=$?
+if test $RC != 0 ; then
+ echo "ldapadd failed ($RC)! Unable to start resultstats overlay"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+fi
+#run successful searches
+echo "Performing successful search..."
+for i in 1 2 3 4 5; do
+ $LDAPSEARCH -S "" -H $URI1 -b "cn=user0$i,ou=people,$BASEDN" "(objectClass=*)" '*' \
+ > /dev/null 2>&1
+ RC=$?
+ if test $RC != 0 ; then
+ echo "Expected successful search ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+ fi
+done
+#verify
+
+RC=$?
+if test $RC != 0 ; then
+ echo "Expected successful search ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+fi
+
+#run noSuchObject searches
+echo "Performing incorrect search (noSuchObject).."
+for i in 1 2 3 4 5; do
+ $LDAPSEARCH -S "" -H $URI1 -b "cn=user0$i,$BASEDN" "(objectClass=*)" '*' \
+ > /dev/null 2>&1
+ RC=$?
+ if test $RC != 32 ; then
+ echo "Expected noSuchObject ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+ fi
+done
+#verify
+SEARCHDN="cn=Search,cn=Result Stats,cn=database 1,cn=databases,cn=monitor"
+echo "Verifying search statistics..."
+cat /dev/null > $SEARCHOUT
+
+echo " base=\"$SEARCHDN\"..."
+echo "# base=\"$SEARCHDN\"..." >> $SEARCHOUT
+$LDAPSEARCH -H $URI1 \
+ -b "$SEARCHDN" \
+ "(objectClass=*)" '*' >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+ echo "Unable to read monitor stats ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+fi
+echo "Filtering ldapsearch results..."
+$LDIFFILTER < $SEARCHOUT > $SEARCHFLT
+$LDIFFILTER < data/test001-search.ldif > $LDIFFLT
+echo "Comparing filter output..."
+$CMP $SEARCHFLT $LDIFFLT > $CMPOUT
+
+if test $? != 0 ; then
+ echo "comparison failed - search statistics incorrect"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit 1
+fi
+#run successful mods
+echo "Performing successful modify..."
+for i in 1 2 3 4 5; do
+ $LDAPMODIFY -D "$MANAGERDN" -H $URI1 -w $PASSWD \
+ >> $TESTOUT 2>&1 << EOMODS
+dn: cn=user0$i,ou=people,$BASEDN
+changetype: modify
+replace: roomNumber
+roomNumber: 200
+EOMODS
+ RC=$?
+ if test $RC != 0 ; then
+ echo "Expected successful modify ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+ fi
+done
+
+#run incorrect mods
+#this will not be counted, as it happens before database choice
+echo "Performing incorrect modify (undefinedAttributeType)..."
+for i in 1 2 3 4 5; do
+ $LDAPMODIFY -D "$MANAGERDN" -H $URI1 -w $PASSWD \
+ >> $TESTOUT 2>&1 << EOMODS
+dn: cn=user0$i,ou=people,$BASEDN
+changetype: modify
+replace: IsBusy
+IsBusy: TRUE
+EOMODS
+ RC=$?
+ if test $RC != 17 ; then
+ echo "Expected undefinedAttributeType ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+ fi
+done
+echo "Performing incorrect modify (noSuchObject)..."
+for i in 1 2 3 4 5; do
+ $LDAPMODIFY -D "$MANAGERDN" -H $URI1 -w $PASSWD \
+ >> $TESTOUT 2>&1 << EOMODS
+dn: cn=user0$i,$BASEDN
+changetype: modify
+replace: roomNumber
+roomNumber: 200
+EOMODS
+ RC=$?
+ if test $RC != 32 ; then
+ echo "Expected noSuchObject ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+ fi
+done
+echo "Performing incorrect modify (invalidCredentials)..."
+#this will only be counted on the frontend
+for i in 1 2 3 4 5; do
+ $LDAPMODIFY -D "$MANAGERDN" -H $URI1 -w "wrongpassword" \
+ >> $TESTOUT 2>&1 << EOMODS
+dn: cn=user0$i,ou=people,$BASEDN
+changetype: modify
+replace: roomNumber
+roomNumber: 200
+EOMODS
+ RC=$?
+ if test $RC != 49 ; then
+ echo "Expected invalidCredentials ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+ fi
+done
+#verify
+echo "Verifying modify statistics..."
+cat /dev/null > $SEARCHOUT
+MODIFYDN="cn=Modify,cn=Result Stats,cn=database 1,cn=databases,cn=monitor"
+echo " base=\"$MODIFYDN\"..."
+echo "# base=\"$MODIFYDN\"..." >> $SEARCHOUT
+$LDAPSEARCH -H $URI1 \
+ -b "$MODIFYDN" \
+ "(objectClass=*)" '*' >> $SEARCHOUT 2>&1
+
+RC=$?
+if test $RC != 0 ; then
+ echo "Unable to read monitor stats ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+fi
+echo "Filtering ldapsearch results..."
+$LDIFFILTER < $SEARCHOUT > $SEARCHFLT
+$LDIFFILTER < data/test001-modify.ldif > $LDIFFLT
+echo "Comparing filter output..."
+$CMP $SEARCHFLT $LDIFFLT > $CMPOUT
+if test $? != 0 ; then
+ echo "comparison failed - modify statistics incorrect"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit 1
+fi
+
+echo "Verifying bind statistics..."
+cat /dev/null > $SEARCHOUT
+BINDDN="cn=Bind,cn=Result Stats,cn=frontend,cn=databases,cn=monitor"
+echo " base=\"$BINDDN\"..."
+echo "# base=\"$BINDDN\"..." >> $SEARCHOUT
+$LDAPSEARCH -H $URI1 \
+ -b "$BINDDN" \
+ "(objectClass=*)" '*' >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+ echo "Unable to read monitor stats ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+fi
+echo "Filtering ldapsearch results..."
+$LDIFFILTER < $SEARCHOUT > $SEARCHFLT
+$LDIFFILTER < data/test001-bind.ldif > $LDIFFLT
+echo "Comparing filter output..."
+$CMP $SEARCHFLT $LDIFFLT > $CMPOUT
+
+if test $? != 0 ; then
+ echo "comparison failed - bind statistics incorrect"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit 1
+fi
+cat /dev/null > $SEARCHOUT
+
+OVERLAYDN="olcOverlay={0}resultstats,olcDatabase={1}$BACKEND,cn=config"
+echo "Modifying cn=config, deleting resultstats overlay on $BACKEND"
+$LDAPDELETE -D cn=config -H $URI1 -y $CONFIGPWF $OVERLAYDN\
+ >> $TESTOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+ echo "ldapdelete failed ($RC)! Unable to delete resultstats overlay"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+fi
+
+echo "Modifying cn=config, enabling resultstats overlay on $BACKEND"
+$LDAPADD -D cn=config -H $URI1 -y $CONFIGPWF \
+ >> $TESTOUT 2>&1 << EOMODS
+dn: olcOverlay=resultstats,olcDatabase={1}$BACKEND,cn=config
+objectClass: olcOverlayConfig
+EOMODS
+RC=$?
+if test $RC != 0 ; then
+ echo "ldapadd failed ($RC)! Unable to start resultstats overlay"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+fi
+
+echo "Verifying statistics are reset..."
+cat /dev/null > $SEARCHOUT
+RESDN="cn=Result Stats,cn=database 1,cn=databases,cn=monitor"
+echo " base=\"$RESDN\"..."
+echo "# base=\"$RESDN\"..." >> $SEARCHOUT
+$LDAPSEARCH -H $URI1 \
+ -b "$RESDN" \
+ "(objectClass=*)" '*' >> $SEARCHOUT 2>&1
+
+RC=$?
+if test $RC != 0 ; then
+ echo "Unable to read monitor stats ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+fi
+
+echo "Filtering ldapsearch results..."
+$LDIFFILTER < $SEARCHOUT > $SEARCHFLT
+$LDIFFILTER < data/test001-reset.ldif > $LDIFFLT
+echo "Comparing filter output..."
+$CMP $SEARCHFLT $LDIFFLT > $CMPOUT
+if test $? != 0 ; then
+ echo "comparison failed - reset statistics incorrect"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit 1
+fi
+
+OVERLAYDN="olcOverlay={0}resultstats,olcDatabase={1}$BACKEND,cn=config"
+echo "Modifying cn=config, deleting resultstats overlay on $BACKEND"
+$LDAPDELETE -D cn=config -H $URI1 -y $CONFIGPWF $OVERLAYDN\
+ >> $TESTOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+ echo "ldapdelete failed ($RC)! Unable to delete resultstats overlay"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+fi
+
+echo "Modifying cn=config, enabling resultstats overlay on $BACKEND"
+$LDAPADD -D cn=config -H $URI1 -y $CONFIGPWF \
+ >> $TESTOUT 2>&1 << EOMODS
+dn: olcOverlay=resultstats,olcDatabase={1}$BACKEND,cn=config
+objectClass: olcOverlayConfig
+EOMODS
+RC=$?
+if test $RC != 0 ; then
+ echo "ldapadd failed ($RC)! Unable to start resultstats overlay"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+fi
+
+test $KILLSERVERS != no && kill -HUP $KILLPIDS
+
+echo ">>>>> Test succeeded"
+
+test $KILLSERVERS != no && wait
+
+exit 0