Revision 238 (by dpavlin, 2004/03/08 17:46:16) tagging openisis 0.9.0
/*
	openisis - an open implementation of the CDS/ISIS database
	Version 0.8.x (patchlevel see file Version)
	Copyright (C) 2001-2003 by Erik Grziwotz, erik@openisis.org

	This library is free software; you can redistribute it and/or
	modify it under the terms of the GNU Lesser General Public
	License as published by the Free Software Foundation; either
	version 2.1 of the License, or (at your option) any later version.

	This library is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
	Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public
	License along with this library; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

	see README for more information
EOH */

/*
	$Id: lio.c,v 1.43 2003/06/11 14:53:08 kripke Exp $
	I/O support for the openisis library.
*/
#include <stdlib.h>
#include <stdio.h> /* vsnprintf */
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <limits.h> /* PATH_MAX */
#include <sys/types.h>

#ifdef WIN32
#define WIN32_LEAN_AND_MEAN
#define NONAMELESSUNION
#	include <windows.h>
#	include <winsock.h>
#	define SHUT_RD SD_RECEIVE
#	define SHUT_WR SD_SEND
#	define SHUT_RDWR SD_BOTH
# define snprintf _snprintf
# define vsnprintf _vsnprintf
#	include <sys/timeb.h>
#else	/* have operating system */
#	define __USE_UNIX98
/*
#	define _POSIX_MAPPED_FILES
# define _POSIX_SYNCHRONIZED_IO
*/
#	include <fcntl.h> /* or try unixio */
#	ifndef O_SYNC
#		if defined( __FreeBSD__ )
#			define O_SYNC O_FSYNC
#		else
#			define O_SYNC 0
#		endif
#	endif
#	include <sys/stat.h>
#	include <sys/file.h> /* flock */
#	include <sys/mman.h>
#	include <sys/socket.h>
#	include <unistd.h>
#endif /* WIN32 */
#include <sys/time.h> /* gettimeofday */
#include <time.h> /* localtime */

#include "luti.h" /* logging */
#include "lses.h"
#include "lstr.h" /* log_str */
#include "lio.h"



/* ************************************************************
	private data
*/
static int init;

#ifdef WIN32
/* it's not thread-safe, anyway */
static HANDLE hand[128];
#endif

/* ************************************************************
	private functions
*/
#ifndef WIN32
static int lerrno ( int errno_ )
{
	switch ( errno_ ) {
	case EINTR:
	case EAGAIN:
		return ERR_OK;
	case EFAULT: /* structurally bad address */
	case ENAMETOOLONG:
	case ELOOP:
		return ERR_FAULT;
	case EBADF: /* file "does not exist" */
	case ENOENT:
	case ENOTDIR:
		return ERR_BADF;
	case EIO: /* file "is not accessible" */
	case EEXIST:
	case EISDIR:
	case EACCES:
	case ENXIO:
	case ENODEV:
	case EROFS:
	case ETXTBSY:
	case ENOSPC:
	case EPIPE:
	case ESPIPE:
		return ERR_IO;
	case ENOMEM:
	case EMFILE:
	case ENFILE:
		return ERR_NOMEM;
	case EBUSY:
		return ERR_BUSY;
	}
	return ERR_INVAL;
}	/* lerrno */

/**
	check unixio errno.
	if harmless, return != 0 (is ok).
	else return 0, possibly closing the file
*/
int unixio_ok ( int *file, int io )
{
	switch ( errno ) {
	case EINTR:
	case EAGAIN:
		return !0;
	case EFAULT:
	case ESPIPE:
		break;
	default:
		log_msg( LOG_IOERR, "closing file %x for %x on %d", *file, io, errno );
		lio_close( file, io );
	}
	return 0;
}	/* unixio_err */
#endif


/**
	return wether printing should be skipped
*/
static int seterrlev ( int *err, int *level, int code )
{
	*level = LOG_MASK & code;
	*err = ERR_MASK & code;
	if ( ! code )
		return 0;
	if ( ! *err )
		switch ( *level ) {
		case LOG_FATAL: *err = ERR_IDIOT; break;
		case LOG_ERROR: *err = ERR_INVAL; break;
		case LOG_IOERR:
#ifndef WIN32
			if ( EAGAIN == errno || EINTR == errno ) /* no error */
				return !0;
		case LOG_SYSERR:
			*err = lerrno( errno );
#else
		case LOG_SYSERR:
			*err = ERR_INVAL;
#endif
			break;
		default:
			*err = 0;
		}
	else if ( ! *level ) {
		if ( ERR_TRASH <= *err )
			*level = LOG_FATAL;
		else if ( ERR_IO <= *err )
			*level = LOG_SYSERR;
		else if ( ERR_FAULT <= *err )
			*level = LOG_ERROR;
		else
			*level = LOG_VERBOSE;
	}
	return *level > (int)log_lev;
}	/* seterrlev */


/* ************************************************************
	package data
*/

CLockFunc *lio_lock;
LogLevel log_lev = LOG_ERROR;
int log_flush = 1;

#ifndef WIN32
#	define LIO_STATICFD( nm, flg ) int nm = flg
#else
#	define LIO_STATICFD( nm, flg ) int nm
#endif
LIO_STATICFD( lio_in , LIO_IN | 0 );
LIO_STATICFD( lio_out, LIO_OUT | 1 );
LIO_STATICFD( lio_err, LIO_OUT | 2 );

/* ************************************************************
	package functions
*/
static void lio_init ()
{
#ifndef WIN32
	/* static */
#else
	lio_in  = lio_open( "CONIN$", LIO_RD );
	lio_out = lio_open( "CONOUT$", LIO_WR );
	lio_err = lio_open( "oisiserr.txt", LIO_WR|LIO_CREAT );
#endif
}	/* lio_init */


/*
	has "official" name, since visible to linker
	use ld -init openIsisInit for shared object
*/
void openIsisInit ()
{
	static const char inimsg[] = "openIsisInit\n";
	const char *ll = getenv("OPENISIS_LOGLEVEL");

	if ( init )
		return;
	init = 42;
	lio_init();
	if ( ll ) {
		cLog( ll[0], 0 );
#ifndef NDEBUG
		if ( LOG_DEBUG <= log_lev )
			lio_write( &lio_err, inimsg, sizeof(inimsg)-1 );
#endif
	}
	if ( getenv("OPENISIS_LOGBUFFERED") )
		log_flush = 0;
	lses_init();
}	/* openIsisInit */


/*
	use ld -fini openIsisFini for shared object
*/
void openIsisFini ()
{
	static const char finimsg[] = "openIsisFini\n";

	lses_fini();
#ifndef NDEBUG
	if ( LOG_DEBUG <= log_lev )
		lio_write( &lio_err, finimsg, sizeof(finimsg)-1 );
#endif
}	/* openIsisFini */


void linit () /* called by cOpen() */
{
	if ( init ) /* we're an .so -- implicitly initialized */
		return;
	/* we're statically linked */
	openIsisInit();
	atexit( openIsisFini );
}




int lio_open ( const char *name, int flags )
{
	int fd = -1;
	int faillvl = (LIO_TRY == ((LIO_TRY|LIO_WR)&flags))
		? (LOG_VERBOSE|ERR_BADF) : LOG_INFO|ERR_BADF /* LOG_IOERR too annoying ... */;

	if ( '&' == name[0] ) {
		int i = 1;
		fd = 0;
		while ( '0' <= name[i] && name[i] <= '9' )
			fd = 10*fd + name[i++] - '0';
	}

#ifndef WIN32
	if ( 0 > fd ) {
		int f = !(LIO_WR&flags) ? O_RDONLY
			: ((LIO_RD&flags) ? O_RDWR : O_WRONLY);
		if ( LIO_CREAT & flags && LIO_WR & flags ) f |= O_CREAT;
		if ( LIO_NBLK & flags ) f |= O_NONBLOCK;
		if ( LIO_SYNC & flags ) f |= O_SYNC;
		if ( LIO_TRUNC & flags ) f |= O_TRUNC;
		if ( ! (LIO_SEEK & flags) ) f |= O_APPEND;
		fd = open( name, f, 00664 ); /* let umask finetune */
		if ( LIO_FD < fd ) {
			log_msg( LOG_ERROR, "got big fd %d", fd );
			fd = -1;
		}
		if ( 0 > fd )
			return log_msg( faillvl, "could not open '%s' %x", name, flags );
		if ( LIO_FLOCK & flags ) {
			const char *lck = LIO_WR&flags ? "exclusive" : "shared";
	/*
		we want an advisory lock, so that tail -f can read changes.
		SYSV/POSIX fcntl might be mandatory, depending on files mode bits
		(and filesystem). Therefore prefer BSD style flock, if available.

		under linux, flock is never mandatory, while lockf/fcntl may be.
		/usr/src/linux/Documentation/mandatory.txt
		moreover fcntl attempts to lock over NFS, which is a very feeble idea.
	*/
#ifndef LOCK_SH
	/*
		on solaris, LOCK_SH is declared only with the /usr/ucb/cc includes.
		their flock on fcntl emulation "must not be used in MT environments".
	*/
			struct flock fl;
			memset( &fl, 0, sizeof(fl) );
			fl.l_type   = LIO_WR&flags ? F_WRLCK : F_RDLCK;
			fl.l_whence = SEEK_SET;
			fl.l_start  = 0;
			fl.l_len    = 1; /* 0 locks the whole file -- could be mandatory :( */
#endif
			log_msg( LOG_VERBOSE, "attempting %s lock on '%s'", lck, name );
			if (
#ifndef LOCK_SH
				/* hmm .. at least this is "POSIX" */
				fcntl( fd, LIO_WLOCK&flags ? F_SETLKW : F_SETLK, &fl )
#else
				flock( fd,
				(LIO_WR&flags ? LOCK_EX : LOCK_SH) | (LIO_WLOCK&flags ? 0 : LOCK_NB) )
#endif
			) {
				close( fd );
				return log_msg( ERR_BADF, "could not get %s lock on '%s'", lck, name );
			}
		}
	} else if ( -1 == fcntl( fd, F_GETFL ) ) /* check open */
		return -ERR_BADF;
#else
	if ( 0 > fd ) {
		HANDLE h;
		int acc = 0;
		int shr = FILE_SHARE_READ | (LIO_FLOCK&flags ? 0 : FILE_SHARE_WRITE);
		int cre = (LIO_CREAT & flags && LIO_WR & flags)
			? (LIO_TRUNC & flags) ? CREATE_ALWAYS : OPEN_ALWAYS
			: (LIO_TRUNC & flags) ? TRUNCATE_EXISTING : OPEN_EXISTING;
		int f = (LIO_WR&flags) ? FILE_ATTRIBUTE_ARCHIVE : FILE_ATTRIBUTE_NORMAL;

		if ( LIO_RD & flags ) acc |= GENERIC_READ; /* FILE_READ_DATA; */
		if ( LIO_WR & flags ) acc |= GENERIC_WRITE;
			/* some routines like GetFileSize are "documented" to require
				GENERIC_READ/GENERIC_WRITE, so it may not be sufficient to use:
			(LIO_SEEK & flags) ? FILE_WRITE_DATA : FILE_APPEND_DATA */;
		/* if ( LIO_NBLK & flags ) f |= FILE_FLAG_OVERLAPPED; */
		if ( LIO_SYNC & flags ) f |= FILE_FLAG_WRITE_THROUGH;
		f |= (LIO_SEEK & flags) ? FILE_FLAG_RANDOM_ACCESS
			: FILE_FLAG_SEQUENTIAL_SCAN;

		for ( fd=0; hand[fd]; )
			if ( sizeof(hand)/sizeof(hand[0]) == ++fd )
				return -EMFILE;
		h = CreateFile( name, acc, shr, 0, cre, f, 0 );
		if ( INVALID_HANDLE_VALUE == h )
			return log_msg( faillvl, "could not open '%s' %x", name, flags );
		hand[fd] = h;
		if ( (LIO_WR & flags) && ! (LIO_SEEK & flags) )
			SetFilePointer( hand[fd], 0, 0, FILE_END );
	} else if ( (int)(sizeof(hand)/sizeof(hand[0])) <= fd || ! hand[fd] )
		return -ERR_BADF;
#endif
	fd |= flags & LIO_WANT;
	/* add status flags */
	if ( LIO_RD & flags ) fd |= LIO_IN;
	if ( LIO_WR & flags ) fd |= LIO_OUT;
	return fd;
}	/* lio_open */


int lio_close ( int *file, int flags )
{
	int fd = 0xffff & *file;
	int op = LIO_INOUT & *file;
	int cl = LIO_INOUT & flags;
	if ( ! op || ! cl )
		return 0;
	if ( 0 > *file ) { /* probably failed open */
		*file = 0;
		return 0;
	}
	*file &= ~cl;
	if ( op & ~cl ) { /* remains partly open */
		int how = (LIO_IN & cl) ? SHUT_RD : SHUT_WR;
		if ( !(LIO_SOCK & fd) )
			return 0;
#ifndef WIN32
		if ( ! shutdown( fd, how ) )
			return 0;
#else
		/* TODO */
#endif
		log_msg( LOG_SYSERR, "could not shutdown sock %d %d", fd, how );
		return errno ? -errno : -1;
	}
#ifndef WIN32
	for ( fsync( fd ); close( fd ); errno = 0 ) 
		if ( EINTR != errno ) {
			log_msg( LOG_SYSERR, "could not close file %d", fd );
			return errno ? -errno : -1;
		}
#else
	if ( (int)(sizeof(hand)/sizeof(hand[0])) <= fd )
		return -ERR_BADF;
	CloseHandle( hand[fd] );
	hand[fd] = 0;
#endif
	return 0;
}	/* lio_close */


int lio_size ( int file )
{
	if ( !((LIO_IN|LIO_OUT) & file) )
		return log_msg( ERR_BADF, "*file 0x%x not open for stat", file );
	{
	int fd = LIO_FD & file;
#ifndef WIN32
	struct stat s;
	return fstat( fd, &s ) ? 0 : s.st_size;
#else
	return GetFileSize( hand[fd], 0 );
#endif
	}
}	/* lio_size */


unsigned lio_time ( int file )
{
	if ( !((LIO_IN|LIO_OUT) & file) )
		return log_msg( ERR_BADF, "*file 0x%x not open for stat", file );
	{
	int fd = LIO_FD & file;
#ifndef WIN32
	struct stat s;
	return fstat( fd, &s ) ? 0 : (unsigned)s.st_mtime;
#else
	FILETIME foo; /* time since 160101011200 UTC in hundred nanoseconds !!! */
	ull bar;
	if ( ! GetFileTime( hand[fd], 0, 0, &foo ) )
		return 0;
	bar = ((ull)foo.dwHighDateTime<<32 | (ull)foo.dwLowDateTime) / ULL(10000000);
	return (unsigned)(bar - ULL(11644473600));
#endif
	}
}	/* lio_size */


int lio_read ( int *file, void *buf, unsigned count )
{
	if ( !(LIO_IN & *file) )
		return log_msg( ERR_BADF, "*file 0x%x not open for reading", *file );
	{
	int fd = LIO_FD & *file;
#ifndef WIN32
	int got = read( fd, buf, count );
	/* log_msg( LOG_ERROR, "read 0x%x got %d", *file, got ); */
	if ( got )
		return 0 < got ? got
			: unixio_ok( file, LIO_IN ) ? 0
			: log_msg( LOG_IOERR, "could not read %d bytes from %d", count, fd );
	else if ( LIO_SEEK & *file ) /* special EOF treatment */
		return 0;
	else {
		lio_close( file, LIO_IN );
		return -ERR_EOF;
	}
#else
	DWORD got = 0;
	int ok = ReadFile( hand[fd], buf, count, &got, 0 );
	return ok ? got
		: log_msg( LOG_IOERR, "could not read %d bytes from %d", count, fd );
#endif
	}
}	/* lio_read */


int lio_write ( int *file, const void *buf, unsigned count )
{
	if ( !(LIO_OUT & *file) )
		return log_msg( ERR_BADF, "*file 0x%x not open for writing", *file );
	{
	int fd = LIO_FD & *file;
#ifndef WIN32
	int got = write( fd, buf, count );
	return 0 <= got ? got
		: unixio_ok( file, LIO_OUT ) ? 0
		: log_msg( LOG_IOERR, "could not read %d bytes from %d", count, fd );
#else
	DWORD got = 0;
	int ok = WriteFile( hand[fd], buf, count, &got, 0 );
	return ok ? got
		: log_msg( LOG_IOERR, "could not read %d bytes from %d", count, fd );
#endif
	}
}	/* lio_write */



int lio_seek ( int *file, int offset )
{
	if ( !(LIO_INOUT & *file) )
		return log_msg( ERR_BADF, "*file 0x%x not open", *file );
	{
	int fd = LIO_FD & *file;
#ifndef WIN32
	int got = (int)lseek( fd, offset, SEEK_SET );
	return offset == got ? 0
		: log_msg( LOG_IOERR, "could not seek to %d", offset );
#else
	return 0xffffffff /* "INVALID_SET_FILE_POINTER" */
		== SetFilePointer( hand[fd], offset, 0, FILE_BEGIN )
		? -1 : 0;
#endif
	}
}	/* lio_seek */


int lio_pread ( int *file, void *buf, unsigned count, int offset )
{
	if ( !(LIO_IN & *file) )
		return log_msg( ERR_BADF, "*file 0x%x not open for reading", *file );
	{
	int fd = LIO_FD & *file;
#ifndef WIN32
	int got = pread( fd, buf, count, offset );
	return 0 <= got ? got
		: unixio_ok( file, LIO_IN ) ? 0
		: log_msg( LOG_IOERR, "could not read %d bytes from %d at %d",
				count, fd, offset );
#else
	return 0xffffffff /* "INVALID_SET_FILE_POINTER" */
		== SetFilePointer( hand[fd], offset, 0, FILE_BEGIN )
		? -1 : lio_read( file, buf, count );
#endif
	}
}	/* lio_pread */


int lio_pwrite ( int *file, const void *buf, unsigned count, int offset )
{
	if ( !(LIO_OUT & *file) )
		return log_msg( ERR_BADF, "*file 0x%x not open for writing", *file );
	{
	int fd = LIO_FD & *file;
#ifndef WIN32
	int got = pwrite( fd, buf, count, offset );
	return 0 <= got ? got
		: unixio_ok( file, LIO_OUT ) ? 0
		: log_msg( LOG_IOERR, "could not read %d bytes from %d at %d",
				count, fd, offset );
#else
	return 0xffffffff /* "INVALID_SET_FILE_POINTER" */
		== SetFilePointer( hand[fd], offset, 0, FILE_BEGIN )
		? -1 : lio_write( file, buf, count );
#endif
	}
}	/* lio_pwrite */


int lio_trunc ( int *file, int offset )
{
	int fd = LIO_FD & *file;
#ifndef WIN32
	ftruncate( fd, offset );
#else
	/* SetFileValidData( hand[fd], offset ); XP only */
	return 0xffffffff /* "INVALID_SET_FILE_POINTER" */
		== SetFilePointer( hand[fd], offset, 0, FILE_BEGIN )
		? -1 : SetEndOfFile( hand[fd] )
		? 0 : log_msg( LOG_IOERR, "could not truncate %d to %d", fd, offset );
#endif
	return 0;
}	/* lio_trunc */


int lio_mmap ( int *file, void **map, int length )
{
	if ( ! map )
		return log_msg( ERR_INVAL, "no map" );
	if ( 0 >= length && 0 >= (length = lio_size( *file )) ) {
		*map = 0;
		return length;
	}
#ifndef WIN32
#if 0 /* solaris states it will round up itself, and linux does */
	{
		static int ps = 0;
		if ( !ps )
			ps = sysconf(_SC_PAGESIZE); /* getpagesize(); */
	}
#endif
	if ( file ) {
		if ( *map )
			return msync( *map, length, (LIO_SYNC & *file)
				? (MS_SYNC|MS_INVALIDATE) : MS_ASYNC )
				? log_msg( LOG_IOERR, "msync" ) : 0;
		*map = mmap( 0, length, PROT_READ | ((LIO_OUT&*file) ? PROT_WRITE : 0),
			MAP_SHARED, LIO_FD&*file, 0 );
		if ( MAP_FAILED != *map )
			return length;
		*map = 0;
		return log_msg( ERR_NOMEM, "mmap failed on fd %08x", *file );
	}
	if ( ! *map )
		return log_msg( ERR_INVAL, "no *map" );
	munmap( *map, length );
	*map = 0;
	return 0;
#else
	/* awfully complicated here
		-- need  CreateFileMapping, MapViewOfFile
		stupid piece of shrott
		true mapping supported in NT family only, 9x copies to swap
 */
	*map = 0;
	return 0;
#endif
}	/* lio_mmap */


int lio_slurp ( char **buf, int sz, const char *name, int opt )
{
	int file = lio_open( name, LIO_RD|LIO_SEEK|(opt ? LIO_TRY : 0) );
	int size;
	int ret = -ERR_NOMEM;
	char *p = *buf;
	/* log_msg( LOG_IOERR, "open '%s' = %d", name, file ); */
	if ( 0 > file )
		return file;
	size = lio_size( file );
	if ( 0 >= size )
		return size;
	if ( size > sz )
		LOG_OTO( done, ( ERR_INVAL, "file '%.30s' too big: %d > %d",
			name, size, sz ) );
	if ( ! p && !(p = mAlloc(size)) )
		goto done;
	ret = lio_read( &file, p, size );
	if ( size == ret )
		*buf = p;
	else {
		if ( ret >= 0 )
			ret = log_msg( ERR_IO, "OOPS! got %d of %d bytes from '%.30s'",
				ret, size, name );
		if ( ! *buf )
			free( p );
	}
done:
	lio_close( &file, LIO_INOUT );
	return ret;
}	/* lio_slurp */


int log_msg ( int code, const char *fmt, ... )
{
	static const char toolong[] = ": message too long !!!\n";
	int err, level;
	va_list ap;
	va_start( ap, fmt );
#if 0
	ret = sMsg( MSG_VA|code, fmt, ap );
	make log_msg unbuffered, use sMsg for buffered logging
#else
	if ( ! seterrlev( &err, &level, code ) && (LIO_OUT & lio_err) ) {
		char buf[4096];
		int len = vsnprintf( (char*)buf, sizeof(buf), fmt, ap );
		if ( 0 < len && len < (int)sizeof(buf) ) {
			buf[len++] = '\n';
			lio_write( &lio_err, buf, len );
		} else {
			lio_write( &lio_err, fmt, strlen(fmt) );
			lio_write( &lio_err, toolong, sizeof(toolong)-1 );
		}
	}
#endif
	va_end( ap );
	return -err;
}	/* log_msg */


void log_str ( LogLevel level, int *rec, const char **desc )
{
	int occ = -1;
	int nmbrs  = LSTRFIX(*rec);
	int *mbr  = rec+1;
	char *base  = (char*)rec;

	if ( level > log_lev || ! desc )
		return;

	sMsg( 2, "record %.20s\n", *desc++ );
	/* dump the fixed part (occ==-1) and each occurrence of repeated part. */
	for ( ;/* occ < LSTROCC(*dst) */; ) { /* dump one part */
		int i;
		for ( i=0; i<nmbrs; i++, mbr++ ) { /* dump one mbr */
			if ( '\'' == *desc[i] )
				sMsg( 2, "%3d.%2d %4.4s %.67s<\n",
					occ, i, desc[i], base+*mbr );
			else
				sMsg( 2, "%3d.%2d %4.4s 0x%08x = %d\n",
					occ, i, desc[i], *mbr, *mbr );
		}	/* for mbrs */

		if ( ++occ >= LSTROCC(*rec) )
			break;
		if ( ! occ ) { /* was the fixed part, setup for repeated */
			nmbrs = LSTRREP(*rec);
			desc  += i;
		}
	}
}	/* log_str */


void log_hex ( LogLevel level, const void *mem, int len )
{
	const char *p = (const char *)mem;
	char buf[82];
	int i = 0;

	if ( level > log_lev )
		return;
	for ( ; i<len; i+=16, p+=16 ) {
		int j = 0;
		int left = len -i;
		int pos = 10;
		sprintf( buf, "%08x  ", i );
		if ( left > 16 )
			left = 16;
		for ( ; j < left; j++ ) {
			sprintf( buf+pos, "%02x", p[j] );
			pos += 2;
			if ( 3 == j%4 ) { buf[pos++] = ' '; buf[pos++] = ' '; }
		}
		for ( ; j < 16; j++ ) {
			buf[pos++] = ' '; buf[pos++] = ' ';
			if ( 3 == j%4 ) { buf[pos++] = ' '; buf[pos++] = ' '; }
		}
		/* got 50 = 10 + 16*2 + 4*2 */
		for ( j=0; j < left; j++ ) /* add up to 16 */
			buf[pos++] = (0x60 & p[j]) ? p[j] : '.';
		buf[pos++] = '\n';
		buf[pos] = 0;
		sMsg( 2, "%.*s", pos, buf );
	}
}	/* log_hex */


/* ************************************************************
	public functions
*/
int timeUpd ( Tm *tm )
{
	Tm o;
#ifdef WIN32
	struct _timeb tb;
	_ftime( &tb );
	if ( !tm )
		return tb.time;
	o = *tm;
	tm->millis = tb.time*LLL(1000) + tb.millitm;
#else
	struct timeval tv;
	gettimeofday( &tv, 0 );
	if ( !tm )
		return tv.tv_sec;
	o = *tm;
	tm->millis = tv.tv_sec*LLL(1000) + tv.tv_usec/1000;
#endif
	return (int)(tm->millis - o.millis);
}	/* timeUpd */


static int timeLoc ( struct tm *t, Tm *tm )
{
	Tm x;
	time_t tt;
	if ( !tm || !tm->millis )
		timeUpd( tm ? tm : (tm = &x) );
	tt = (time_t)(tm->millis / 1000);
#ifdef WIN32 /* did I mention it's not threadsafe ? */
	*t = *localtime( &tt );
#else
	localtime_r( &tt, t );
#endif
	return (int)(tm->millis % 1000);
}	/* timeLoc */

char *timeGtf ( char *buf, Tm *tm )
{
	struct tm t;
	timeLoc( &t, tm );
	snprintf( buf, 15, "%04u%02u%02u%02u%02u%02u",
		1900+t.tm_year, 1+t.tm_mon, t.tm_mday,
		t.tm_hour, t.tm_min, t.tm_sec );
	buf[14] = 0;
	return buf;
}	/* timeGtf */


char *timeGtfm ( char *buf, Tm *tm )
{
	struct tm t;
	int millis = timeLoc( &t, tm );
	snprintf( buf, 19, "%04u%02u%02u%02u%02u%02u%03u",
		1900+t.tm_year, 1+t.tm_mon, t.tm_mday,
		t.tm_hour, t.tm_min, t.tm_sec, millis );
	buf[18] = 0;
	return buf;
}	/* timeGtfm */


void timeSleep ( Tm *tm )
{
#ifdef WIN32
	Sleep( tm->millis );
#else
	struct timespec ts;
	ts.tv_sec = tm->millis / 1000;
	ts.tv_nsec = 1000000 * (int)(tm->millis % 1000);
	nanosleep( &ts, 0 );
#endif
}	/* timeSleep */


int ioStream ( Ios *s, int op )
{
	Buf *b;
	switch ( op ) {
	case LIO_SSIZE:
		return sizeof(Ios);
	case LIO_SOPEN:
		s->pos = s->b.fill = s->b.done = 0;
	case LIO_SCLOSE:
		s->file &= ~LIO_INOUT;
		return 0;
	case LIO_SPURGE:
		if ( !(LIO_IN & s->file) )
			return -ERR_BADF;
		/* try to get 4k chunk */
		b = /* s->alt ? s->alt : */ &s->b;
		if ( LIO_BUFSIZ/2 < b->fill && b->done ) { /* move contents downwards */
			if ( b->done < b->fill )
				memmove( b->c, b->c + b->done, b->fill - b->done );
			s->pos += b->done;
			b->fill -= b->done;
			b->done = 0;
		}
		return LIO_BUFSIZ <= b->fill ? 0
			: LIO_BUFSIZ/2 < b->fill ? LIO_BUFSIZ - b->fill
			: LIO_BUFSIZ/2;
	case LIO_SFILL:
	case LIO_SFLUSH:
		s->file &= ~LIO_INOUT;
	}
	return -ERR_EOF;
}	/* ioStream */


int ioStdio ( Ios *s, int op )
{
	Buf *b;
	int ret;
	switch ( op ) {
	case LIO_SOPEN:
		ioStream( s, op );
		if ( 0 < (ret = lio_open( s->name, s->file )) )
			s->file = ret;
		return ret;
	case LIO_SCLOSE:
		if ( LIO_OUT & s->file )
			s->func( s, LIO_SFLUSH );
		/* don't close borrowed fd */
		return '&' == *s->name ? 0 : lio_close( &s->file, LIO_INOUT );
	case LIO_SFILL:
		if ( !(LIO_IN & s->file) )
			return -ERR_BADF;
		if ( 0 < (ret = ioStream( s, LIO_SPURGE ))
			&& 0 <= (ret = lio_read( &s->file, s->b.c + s->b.fill, ret ))
		)
			s->b.fill += ret;
		log_msg( LOG_ALL, "LIO_FILL: got %d now %d", ret, s->b.fill );
		return ret;
	case LIO_SFLUSH:
		if ( !(LIO_OUT & s->file) )
			return -ERR_BADF;
		b = /* s->alt ? s->alt : */ &s->b;
		if ( b->fill <= b->done )
			return 0;
		if ( 0 < (ret = lio_write( &s->file, b->c + b->done, b->fill - b->done )) )
			b->done += ret;
		if ( b->fill == b->done ) {
			s->pos += b->done;
			b->fill = b->done = 0;
		} else if ( b->done && LIO_BUFSIZ/2 < b->fill )
			ioStream( s, LIO_SPURGE );
		return ret;
	}
	return ioStream( s, op );
}	/* ioStdio */


int sMsg ( int to, const char *fmt, ... )
{
	SESDECL
	int code = (LOG_MASK|ERR_MASK) & to, err, level;
	int fd = LIO_FD & to; /* stream id uses same mask as system fd */
	Ios *o = ses->io[ fd ? fd : code ? 2 : 1 ];
	va_list ap;
	int space;
	int ret;
	
	if ( LSES_FILE_MAX <= fd || ! o )
		return -1;

	if ( seterrlev( &err, &level, code ) ) /* logging */
		return -err;

	/* core fprintf */
	if ( LIO_BUFSIZ/2 < o->b.fill )
		LIO_FLUSH( o );
	space = LIO_BUFSIZ - o->b.fill;
	va_start( ap, fmt );
	ret = vsnprintf( (char*)o->b.c + o->b.fill, space, fmt,
		(MSG_VA & to) ? va_arg( ap, va_list ) : ap );
	va_end( ap );
	if ( ret < 0 || space < ret ) /* outta space */
		ret = space;
	o->b.fill += ret;
	if ( ! code )
		return ret;

	/* logging afterburner */
	if ( LOG_SYSERR == level || LOG_IOERR == level ) {
		int len;
		char *syserr =
#ifndef WIN32
			strerror(errno);
#else
			0;
		char buf[256] = "";
		if ( 
			FormatMessage(
				FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS,
				0, GetLastError(), 0, buf, sizeof(buf)-1, 0 )
		)
			syserr = buf;
		buf[ sizeof(buf)-1 ] = 0;
#endif
		if ( syserr ) {
			len = strlen( syserr );
			if ( o->b.fill+4+len < LIO_BUFSIZ ) {
				memcpy( o->b.c + o->b.fill, "\n\t: ", 4 );
				memcpy( o->b.c + o->b.fill+4, syserr, len );
				o->b.fill += 4+len;
			}
		}
	}
	if ( o->b.fill < LIO_BUFSIZ )
		o->b.c[ o->b.fill++ ] = '\n';
	if ( log_flush )
		LIO_FLUSH( o );
	return -err;
}	/* sMsg */


int sGets ( int fd, char **ptr, char delim )
{
	SESDECL
	Ios *s = ses->io[ LIO_FD & fd ];

	if ( s->b.fill > s->b.done || 0 < LIO_FILL( s ) ) for (;;) {
		char *f = memchr( s->b.c + s->b.done, delim, s->b.fill - s->b.done );
		int len;
		log_msg( LOG_ALL, "sGets: done %d fill %d", s->b.done, s->b.fill );
		f = memchr( s->b.c + s->b.done, delim, s->b.fill - s->b.done );
		if ( ! f ) {
			if ( (s->b.fill < LIO_BUFSIZ || s->b.done) /* buffer avail */
				&& (LIO_IN & s->file) /* file open */
				&& 0 < LIO_FILL( s )
			)
				continue;
			else if ( s->b.done >= s->b.fill )
				break;
			f = (char*)s->b.c + s->b.fill;
		}
		len = f - (*ptr = (char*)s->b.c + s->b.done);
		s->b.done += len + 1;
		if ( len && '\n' == delim && '\r' == (*ptr)[len-1] )
			len--;
		return len;
	}
	*ptr = 0;
	return -1;
}	/* sGets */


void cLog ( int level, const char *filename )
{
	if ( 0 > level ) /* no change */
		;
	else if ( LOG_LEVELS > level ) /* by basic number */
		level <<= LOG_SHIFT;
	else if ( 'z' >= level ) { /* by ascii value */
		switch ( level ) {
		case '-': level = LOG_NOCHANGE; break;
		case 'o': level = LOG_OFF; break;
		case 'f': level = LOG_FATAL; break;
		case 's': level = LOG_IOERR /*LOG_SYSERR*/; break;
		default:
			if ( '0' <= level && level <= '9' ) {
				level = (level - '0')<<LOG_SHIFT;
				break;
			}
		case 'e': level = LOG_ERROR; break;
		case 'w': level = LOG_WARN; break;
		case 'i': level = LOG_INFO; break;
		case 'v': level = LOG_VERBOSE; break;
		case 'd': level = LOG_DEBUG; break;
		case 't': level = LOG_TRACE; break;
		case 'a': level = LOG_ALL; break;
		}
	}
	if ( level >= 0 )
		log_lev = (LogLevel)level;
	(void)filename;
	/* TODO if ( filename )
		sOpen( cOpen(0), "2>"filename, 0, ioStdio ) */
}	/* cLog */