/* sockbuf.c - i/o routines with support for adding i/o layers. */ /* $OpenLDAP$ */ /* * Copyright 1998-2000 The OpenLDAP Foundation, All Rights Reserved. * COPYING RESTRICTIONS APPLY, see COPYRIGHT file */ #include "portable.h" #include #include #include #include #include #include #include #ifdef HAVE_IO_H #include #endif /* HAVE_IO_H */ #if defined( HAVE_SYS_FILIO_H ) #include #elif defined( HAVE_SYS_IOCTL_H ) #include #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; char buf[4096]; assert( sb != NULL ); 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 */ 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; 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, (lensbiod_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, (lensbiod_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 */ };