/* sockbuf.c - i/o routines with support for adding i/o layers. */
/* $OpenLDAP: pkg/ldap/libraries/liblber/sockbuf.c,v 1.22.2.10 2001/05/06 19:53:58 kurt Exp $ */
/*
 * Copyright 1998-2000 The OpenLDAP Foundation, All Rights Reserved.
 * COPYING RESTRICTIONS APPLY, see COPYRIGHT file
 */

#include "portable.h"

#include <stdio.h>

#include <ac/stdlib.h>

#include <ac/ctype.h>
#include <ac/errno.h>
#include <ac/socket.h>
#include <ac/string.h>
#include <ac/unistd.h>

#ifdef HAVE_IO_H
#include <io.h>
#endif /* HAVE_IO_H */

#if defined( HAVE_FCNTL_H )
#include <fcntl.h>
#endif

#if defined( HAVE_SYS_FILIO_H )
#include <sys/filio.h>
#elif defined( HAVE_SYS_IOCTL_H )
#include <sys/ioctl.h>
#endif

#include "lber-int.h"

#define MIN_BUFF_SIZE		4096
#define MAX_BUFF_SIZE		65536
#define DEFAULT_READAHEAD	16384

Sockbuf *
ber_sockbuf_alloc( void )
{
	Sockbuf			*sb;

	ber_int_options.lbo_valid = LBER_INITIALIZED;

	sb = LBER_CALLOC( 1, sizeof( Sockbuf ) );

	if( sb == NULL ) return NULL;

	ber_int_sb_init( sb );
	return sb;
}

void
ber_sockbuf_free( Sockbuf *sb )
{
	assert( sb != NULL );
	assert( SOCKBUF_VALID( sb ) );

	ber_int_sb_close( sb );
	ber_int_sb_destroy( sb );
	LBER_FREE( sb );
}

/* Return values: -1: error, 0: no operation performed or the answer is false,
 * 1: successful operation or the answer is true
 */
int
ber_sockbuf_ctrl( Sockbuf *sb, int opt, void *arg )
{
	Sockbuf_IO_Desc		*p;
	int			ret = 0;

	assert( sb != NULL );
	assert( SOCKBUF_VALID( sb ) );

	switch ( opt ) {
		case LBER_SB_OPT_HAS_IO:
			p = sb->sb_iod;
			while ( p && p->sbiod_io != (Sockbuf_IO *)arg ) {
				p = p->sbiod_next;
			}
   
			if ( p ) {
				ret = 1;
			}
			break;

		case LBER_SB_OPT_GET_FD:
			if ( arg != NULL ) {
				*((int *)arg) = sb->sb_fd;
			}
			ret = ( sb->sb_fd == AC_SOCKET_INVALID ? -1 : 1);
			break;

		case LBER_SB_OPT_SET_FD:
			sb->sb_fd = *((int *)arg);
			ret = 1;
			break;

		case LBER_SB_OPT_SET_NONBLOCK:
			ret = ber_pvt_socket_set_nonblock( sb->sb_fd, arg != NULL)
				? -1 : 1;
			break;

		case LBER_SB_OPT_DRAIN: {
				/* Drain the data source to enable possible errors (e.g.
				 * TLS) to be propagated to the upper layers
				 */
				char buf[MIN_BUFF_SIZE];

				do {
					ret = ber_int_sb_read( sb, buf, sizeof( buf ) );
				} while ( ret == sizeof( buf ) );

				ret = 1;
			} break;

		case LBER_SB_OPT_NEEDS_READ:
			ret = ( sb->sb_trans_needs_read ? 1 : 0 );
			break;

		case LBER_SB_OPT_NEEDS_WRITE:
			ret = ( sb->sb_trans_needs_write ? 1 : 0 );
			break;

		case LBER_SB_OPT_GET_MAX_INCOMING:
			if ( arg != NULL ) {
				*((ber_len_t *)arg) = sb->sb_max_incoming;
			}
			ret = 1;
			break;

		case LBER_SB_OPT_SET_MAX_INCOMING:
			sb->sb_max_incoming = *((ber_len_t *)arg);
			ret = 1;
			break;

		default:
			ret = sb->sb_iod->sbiod_io->sbi_ctrl( sb->sb_iod,
				opt, arg );
			break;
   }

	return ret;
}

int
ber_sockbuf_add_io( Sockbuf *sb, Sockbuf_IO *sbio, int layer, void *arg )
{
	Sockbuf_IO_Desc		*d, *p, **q;
   
	assert( sb != NULL );
	assert( SOCKBUF_VALID( sb ) );
   
	if ( sbio == NULL ) {
		return -1;
	}
   
	q = &sb->sb_iod;
	p = *q;
	while ( p && p->sbiod_level > layer ) {
		q = &p->sbiod_next;
		p = *q;
	}
   
	d = LBER_MALLOC( sizeof( *d ) );
	if ( d == NULL ) {
		return -1;
	}
   
	d->sbiod_level = layer;
	d->sbiod_sb = sb;
	d->sbiod_io = sbio;
	memset( &d->sbiod_pvt, '\0', sizeof( d->sbiod_pvt ) );
	d->sbiod_next = p;
	*q = d;
      
	if ( sbio->sbi_setup != NULL && ( sbio->sbi_setup( d, arg ) < 0 ) ) {
		return -1;
	}
      
	return 0;
}
   
int
ber_sockbuf_remove_io( Sockbuf *sb, Sockbuf_IO *sbio, int layer )
{
	Sockbuf_IO_Desc		*p, **q;

	assert( sb != NULL );
	assert( SOCKBUF_VALID( sb ) );
   
	if ( sb->sb_iod == NULL ) {
		return -1;
	}
   
	q = &sb->sb_iod;
	while ( *q != NULL ) {
		p = *q;
		if ( layer == p->sbiod_level && p->sbiod_io == sbio ) {
			if ( p->sbiod_io->sbi_remove != NULL &&
				p->sbiod_io->sbi_remove( p ) < 0 )
			{
				return -1;
			}
			*q = p->sbiod_next;
			LBER_FREE( p );
		break;
		}
		q = &p->sbiod_next;
	}

	return 0;
}

void
ber_pvt_sb_buf_init( Sockbuf_Buf *buf )
{
	buf->buf_base = NULL;
	buf->buf_ptr = 0;
	buf->buf_end = 0;
	buf->buf_size = 0;
}

void
ber_pvt_sb_buf_destroy( Sockbuf_Buf *buf )
{
	assert( buf != NULL);

	if (buf->buf_base) {
		LBER_FREE( buf->buf_base );
	}
	ber_pvt_sb_buf_init( buf );
}

int
ber_pvt_sb_grow_buffer( Sockbuf_Buf *buf, ber_len_t minsize )
{
	ber_len_t		pw;
	char			*p;
   
	assert( buf != NULL );

	for ( pw = MIN_BUFF_SIZE; pw < minsize; pw <<= 1 ) {
		if (pw > MAX_BUFF_SIZE)
      return -1;
   }

	if ( buf->buf_size < pw ) {
		p = LBER_REALLOC( buf->buf_base, pw );
		if ( p == NULL )
			return -1;
		buf->buf_base = p;
		buf->buf_size = pw;
   }
	return 0;
}

ber_len_t
ber_pvt_sb_copy_out( Sockbuf_Buf *sbb, char *buf, ber_len_t len )
{
	ber_len_t		max;

	assert( buf != NULL );
	assert( sbb != NULL );
#if 0
	assert( sbb->buf_size > 0 );
#endif

	max = sbb->buf_end - sbb->buf_ptr;
	max = ( max < len) ? max : len;
	if ( max ) {
		AC_MEMCPY( buf, sbb->buf_base + sbb->buf_ptr, max );
		sbb->buf_ptr += max;
		if ( sbb->buf_ptr >= sbb->buf_end ) {
			sbb->buf_ptr = sbb->buf_end = 0;
		}
   }
	return max;
}

ber_slen_t
ber_pvt_sb_do_write( Sockbuf_IO_Desc *sbiod, Sockbuf_Buf *buf_out )
{
	ber_len_t		to_go;
	ber_slen_t ret;

	assert( sbiod != NULL );
	assert( SOCKBUF_VALID( sbiod->sbiod_sb ) );

	to_go = buf_out->buf_end - buf_out->buf_ptr;
	assert( to_go > 0 );
   
	for(;;) {
		ret = LBER_SBIOD_WRITE_NEXT( sbiod, buf_out->buf_base +
			buf_out->buf_ptr, to_go );
#ifdef EINTR
		if ((ret<0) && (errno==EINTR)) continue;
#endif
		break;
	}

	if ( ret <= 0 ) return ret;
   
	buf_out->buf_ptr += ret;
	if (buf_out->buf_ptr == buf_out->buf_end) {
		buf_out->buf_end = buf_out->buf_ptr = 0;
	}

	if ( (ber_len_t)ret < to_go ) {
		/* not enough data, so pretend no data was sent. */
		return -1;
	}

	return ret;
}

int
ber_pvt_socket_set_nonblock( ber_socket_t sd, int nb )
{
#if HAVE_FCNTL
	int flags = fcntl( sd, F_GETFL);
	if( nb ) {
		flags |= O_NONBLOCK;
	} else {
		flags &= ~O_NONBLOCK;
	}
	return fcntl( sd, F_SETFL, flags );
		
#elif defined( FIONBIO )
	ioctl_t status = nb ? 1 : 0;
	return ioctl( sd, FIONBIO, &status );
#endif
}

int
ber_int_sb_init( Sockbuf *sb )
{
	assert( sb != NULL);

	sb->sb_valid=LBER_VALID_SOCKBUF;
	sb->sb_options = 0;
	sb->sb_debug = ber_int_debug;
	sb->sb_fd = AC_SOCKET_INVALID;
	sb->sb_iod = NULL;
	sb->sb_trans_needs_read = 0;
	sb->sb_trans_needs_write = 0;
   
	assert( SOCKBUF_VALID( sb ) );
	return 0;
}
   
int
ber_int_sb_close( Sockbuf *sb )
{
	Sockbuf_IO_Desc		*p;

	assert( sb != NULL);
   
	p = sb->sb_iod;
	while ( p ) {
		if ( p->sbiod_io->sbi_close &&
			p->sbiod_io->sbi_close( p ) < 0 )
		{
			return -1;
		}
		p = p->sbiod_next;
	}
   
	sb->sb_fd = AC_SOCKET_INVALID;
   
	return 0;
}

int
ber_int_sb_destroy( Sockbuf *sb )
{
	Sockbuf_IO_Desc		*p;

	assert( sb != NULL);
	assert( SOCKBUF_VALID( sb ) );
   
	while ( sb->sb_iod ) {
		p = sb->sb_iod->sbiod_next;
		ber_sockbuf_remove_io( sb, sb->sb_iod->sbiod_io,
			sb->sb_iod->sbiod_level );
		sb->sb_iod = p;
	}

	return ber_int_sb_init( sb );
}

ber_slen_t
ber_int_sb_read( Sockbuf *sb, void *buf, ber_len_t len )
{
	ber_slen_t		ret;

	assert( buf != NULL );
	assert( sb != NULL);
	assert( sb->sb_iod != NULL );
	assert( SOCKBUF_VALID( sb ) );

	for (;;) {
		ret = sb->sb_iod->sbiod_io->sbi_read( sb->sb_iod, buf, len );

#ifdef EINTR	
		if ( ( ret < 0 ) && ( errno == EINTR ) ) continue;
#endif
		break;
	}

	return ret;
}

ber_slen_t
ber_int_sb_write( Sockbuf *sb, void *buf, ber_len_t len )
{
	ber_slen_t		ret;

	assert( buf != NULL );
	assert( sb != NULL);
	assert( sb->sb_iod != NULL );
	assert( SOCKBUF_VALID( sb ) );

	for (;;) {
		ret = sb->sb_iod->sbiod_io->sbi_write( sb->sb_iod, buf, len );

#ifdef EINTR	
		if ( ( ret < 0 ) && ( errno == EINTR ) ) continue;
#endif
		break;
	}

	return ret;
}

/*
 * Support for TCP
 */

static ber_slen_t
sb_stream_read( Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len )
{
	assert( sbiod != NULL);
	assert( SOCKBUF_VALID( sbiod->sbiod_sb ) );

#if defined(MACOS)
/*
 * MacTCP/OpenTransport
 */
	return tcpread( sbiod->sbiod_sb->sb_fd, 0, (unsigned char *)buf,
		   len, NULL );

#elif defined( HAVE_PCNFS ) || \
   defined( HAVE_WINSOCK ) || defined ( __BEOS__ )
/*
 * PCNFS (under DOS)
 */
/*
 * Windows Socket API (under DOS/Windows 3.x)
 */
/*
 * 32-bit Windows Socket API (under Windows NT or Windows 95)
 */
	{
		int rc;

		rc = recv( sbiod->sbiod_sb->sb_fd, buf, len, 0 );

#ifdef HAVE_WINSOCK
		if ( rc < 0 ) {
			int err;

			err = WSAGetLastError();
			errno = err;
		}
#endif

		return rc;
	}

#elif defined( HAVE_NCSA )
/*
 * NCSA Telnet TCP/IP stack (under DOS)
 */
	return nread( sbiod->sbiod_sb->sb_fd, buf, len );

#else
	return read( sbiod->sbiod_sb->sb_fd, buf, len );
#endif
}

static ber_slen_t
sb_stream_write( Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len )
{
	assert( sbiod != NULL);
	assert( SOCKBUF_VALID( sbiod->sbiod_sb ) );

#if defined(MACOS) 
/*
 * MacTCP/OpenTransport
 */
#define MAX_WRITE	65535
	return tcpwrite( sbiod->sbiod_sb->sb_fd, (unsigned char *)buf,
		(len<MAX_WRITE) ? len : MAX_WRITE );

#elif defined( HAVE_PCNFS) \
	|| defined( HAVE_WINSOCK) || defined ( __BEOS__ )
/*
 * PCNFS (under DOS)
 */
/*
 * Windows Socket API (under DOS/Windows 3.x)
 */
/*
 * 32-bit Windows Socket API (under Windows NT or Windows 95)
 */
	{
		int rc = send( sbiod->sbiod_sb->sb_fd, buf, len, 0 );

#ifdef HAVE_WINSOCK
		if ( rc < 0 ) {
			int err;
			err = WSAGetLastError();
			errno = err;
		}
#endif
		return rc;
	}

#elif defined(HAVE_NCSA)
	return netwrite( sbiod->sbiod_sb->sb_fd, buf, len );

#elif defined(VMS)
/*
 * VMS -- each write must be 64K or smaller
 */
#define MAX_WRITE 65535
	return write( sbiod->sbiod_sb->sb_fd, buf,
		(len<MAX_WRITE) ? len : MAX_WRITE);
#else
	return write( sbiod->sbiod_sb->sb_fd, buf, len );
#endif   
}   
   
static int 
sb_stream_close( Sockbuf_IO_Desc *sbiod )
{
	assert( sbiod != NULL );
	assert( SOCKBUF_VALID( sbiod->sbiod_sb ) );
	tcp_close( sbiod->sbiod_sb->sb_fd );
   return 0;
}

/* The argument is a pointer to the socket descriptor */
static int
sb_stream_setup( Sockbuf_IO_Desc *sbiod, void *arg ) {
	assert( sbiod != NULL );

	if ( arg != NULL ) {
		sbiod->sbiod_sb->sb_fd = *((int *)arg);
	}
	return 0;
}

static int
sb_stream_ctrl( Sockbuf_IO_Desc *sbiod, int opt, void *arg ) {
	/* This is an end IO descriptor */
	return 0;
}

Sockbuf_IO ber_sockbuf_io_tcp = {
	sb_stream_setup,	/* sbi_setup */
	NULL,				/* sbi_remove */
	sb_stream_ctrl,		/* sbi_ctrl */
	sb_stream_read,		/* sbi_read */
	sb_stream_write,	/* sbi_write */
	sb_stream_close		/* sbi_close */
};


/*
 * Support for readahead (UDP needs it)
 */

static int
sb_rdahead_setup( Sockbuf_IO_Desc *sbiod, void *arg )
{
	Sockbuf_Buf		*p;

	assert( sbiod != NULL );

	p = LBER_MALLOC( sizeof( *p ) );
	if ( p == NULL ) return -1;

	ber_pvt_sb_buf_init( p );

	if ( arg == NULL ) {
		ber_pvt_sb_grow_buffer( p, DEFAULT_READAHEAD );
	} else {
		ber_pvt_sb_grow_buffer( p, *((int *)arg) );
	}

	sbiod->sbiod_pvt = p;
	return 0;
}

static int
sb_rdahead_remove( Sockbuf_IO_Desc *sbiod )
{
	Sockbuf_Buf		*p;

	assert( sbiod != NULL );

	p = (Sockbuf_Buf *)sbiod->sbiod_pvt;

	if ( p->buf_ptr != p->buf_end ) return -1;

	ber_pvt_sb_buf_destroy( (Sockbuf_Buf *)(sbiod->sbiod_pvt) );
	LBER_FREE( sbiod->sbiod_pvt );
	sbiod->sbiod_pvt = NULL;

	return 0;
}

static ber_slen_t
sb_rdahead_read( Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len )
{
	Sockbuf_Buf		*p;
	ber_slen_t		bufptr = 0, ret, max;

	assert( sbiod != NULL );
	assert( SOCKBUF_VALID( sbiod->sbiod_sb ) );
	assert( sbiod->sbiod_next != NULL );

	p = (Sockbuf_Buf *)sbiod->sbiod_pvt;

	assert( p->buf_size > 0 );

	/* Are there anything left in the buffer? */
	ret = ber_pvt_sb_copy_out( p, buf, len );
	bufptr += ret;
	len -= ret;

	if ( len == 0 ) return bufptr;

	max = p->buf_size - p->buf_end;
	ret = 0;
	while ( max > 0 ) {
		ret = LBER_SBIOD_READ_NEXT( sbiod, p->buf_base + p->buf_end,
			max );
#ifdef EINTR	
		if ( ( ret < 0 ) && ( errno == EINTR ) ) continue;
#endif
		break;
	}

	if ( ret < 0 ) {
		return ( bufptr ? bufptr : ret );
	}

	p->buf_end += ret;
	bufptr += ber_pvt_sb_copy_out( p, (char *) buf + bufptr, len );
	return bufptr;
}

static ber_slen_t
sb_rdahead_write( Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len )
{
	assert( sbiod != NULL );
	assert( sbiod->sbiod_next != NULL );

	return LBER_SBIOD_WRITE_NEXT( sbiod, buf, len );
}

static int
sb_rdahead_close( Sockbuf_IO_Desc *sbiod )
{
	assert( sbiod != NULL );

	/* Just erase the buffer */
	ber_pvt_sb_buf_destroy((Sockbuf_Buf *)sbiod->sbiod_pvt);
	return 0;
}

static int
sb_rdahead_ctrl( Sockbuf_IO_Desc *sbiod, int opt, void *arg )
{
	Sockbuf_Buf		*p;

	p = (Sockbuf_Buf *)sbiod->sbiod_pvt;

	if ( opt == LBER_SB_OPT_DATA_READY ) {
		if ( p->buf_ptr != p->buf_end ) {
			return 1;
		}

	} else if ( opt == LBER_SB_OPT_SET_READAHEAD ) {
		if ( p->buf_size >= *((ber_len_t *)arg) ) {
			return 0;
		}
		return ( ber_pvt_sb_grow_buffer( p, *((int *)arg) ) ?
			-1 : 1 );
	}

	return LBER_SBIOD_CTRL_NEXT( sbiod, opt, arg );
}

Sockbuf_IO ber_sockbuf_io_readahead = {
	sb_rdahead_setup,	/* sbi_setup */
	sb_rdahead_remove,	/* sbi_remove */
	sb_rdahead_ctrl,	/* sbi_ctrl */
	sb_rdahead_read,	/* sbi_read */
	sb_rdahead_write,	/* sbi_write */
	sb_rdahead_close	/* sbi_close */
};

/*
 * Support for simple file IO
 */

static ber_slen_t
sb_fd_read( Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len )
{
	assert( sbiod != NULL);
	assert( SOCKBUF_VALID( sbiod->sbiod_sb ) );

	return read( sbiod->sbiod_sb->sb_fd, buf, len );
}

static ber_slen_t
sb_fd_write( Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len )
{
	assert( sbiod != NULL);
	assert( SOCKBUF_VALID( sbiod->sbiod_sb ) );

	return write( sbiod->sbiod_sb->sb_fd, buf, len );
}

static int 
sb_fd_close( Sockbuf_IO_Desc *sbiod )
{   
	assert( sbiod != NULL );
	assert( SOCKBUF_VALID( sbiod->sbiod_sb ) );

	close( sbiod->sbiod_sb->sb_fd );
	return 0;
}

/* The argument is a pointer to the file descriptor */
static int
sb_fd_setup( Sockbuf_IO_Desc *sbiod, void *arg ) {
	assert( sbiod != NULL );

	if ( arg != NULL )
		sbiod->sbiod_sb->sb_fd = *((int *)arg);
	return 0;
}

static int
sb_fd_ctrl( Sockbuf_IO_Desc *sbiod, int opt, void *arg ) {
	/* This is an end IO descriptor */
	return 0;
}

Sockbuf_IO ber_sockbuf_io_fd = {
	sb_fd_setup,	/* sbi_setup */
	NULL,			/* sbi_remove */
	sb_fd_ctrl,		/* sbi_ctrl */
	sb_fd_read,		/* sbi_read */
	sb_fd_write,		/* sbi_write */
	sb_fd_close		/* sbi_close */
};

/*
 * Debugging layer
 */

static int
sb_debug_setup( Sockbuf_IO_Desc *sbiod, void *arg )
{
	assert( sbiod != NULL );
	
	if ( arg == NULL ) arg = "sockbuf_";

	sbiod->sbiod_pvt = LBER_MALLOC( strlen( arg ) + 1 );
	if ( sbiod->sbiod_pvt == NULL ) return -1;

	strcpy( (char *)sbiod->sbiod_pvt, (char *)arg );
	return 0;
}

static int
sb_debug_remove( Sockbuf_IO_Desc *sbiod )
{
	assert( sbiod != NULL );
	assert( sbiod->sbiod_pvt != NULL );

	LBER_FREE( sbiod->sbiod_pvt );
	sbiod->sbiod_pvt = NULL;
	return 0;
}

static int
sb_debug_ctrl( Sockbuf_IO_Desc *sbiod, int opt, void *arg )
{
	return LBER_SBIOD_CTRL_NEXT( sbiod, opt, arg );
}

static ber_slen_t
sb_debug_read( Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len )
{
	ber_slen_t		ret;

	ret = LBER_SBIOD_READ_NEXT( sbiod, buf, len );
	if ( ret < 0 ) {
		ber_log_printf( LDAP_DEBUG_PACKETS, sbiod->sbiod_sb->sb_debug,
			"%sread: want=%ld error=%s\n", (char *)sbiod->sbiod_pvt,
			(long)len, STRERROR( errno ) );

	} else {
		ber_log_printf( LDAP_DEBUG_PACKETS, sbiod->sbiod_sb->sb_debug,
			"%sread: want=%ld, got=%ld\n", (char *)sbiod->sbiod_pvt,
			(long)len, (long)ret );
		ber_log_bprint( LDAP_DEBUG_PACKETS, sbiod->sbiod_sb->sb_debug,
			(const char *)buf, ret );
	}
	return ret;
}

static ber_slen_t
sb_debug_write( Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len )
{
	ber_slen_t		ret;

	ret = LBER_SBIOD_WRITE_NEXT( sbiod, buf, len );
	if ( ret < 0 ) {
		ber_log_printf( LDAP_DEBUG_PACKETS, sbiod->sbiod_sb->sb_debug,
			"%swrite: want=%ld error=%s\n",
			(char *)sbiod->sbiod_pvt, (long)len,
			STRERROR( errno ) );

	} else {
		ber_log_printf( LDAP_DEBUG_PACKETS, sbiod->sbiod_sb->sb_debug,
			"%swrite: want=%ld, written=%ld\n",
			(char *)sbiod->sbiod_pvt, (long)len, (long)ret );
		ber_log_bprint( LDAP_DEBUG_PACKETS, sbiod->sbiod_sb->sb_debug,
			(const char *)buf, ret );
	}

	return ret;
}

Sockbuf_IO ber_sockbuf_io_debug = {
	sb_debug_setup,		/* sbi_setup */
	sb_debug_remove,	/* sbi_remove */
	sb_debug_ctrl,		/* sbi_ctrl */
	sb_debug_read,		/* sbi_read */
	sb_debug_write,		/* sbi_write */
	NULL				/* sbi_close */
};

