/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/




#include "system.h"

#include <osl/pipe.h>
#include <osl/diagnose.h>
/*#include <osl/signal.h>*/
#include <osl/thread.h>
#include <osl/interlck.h>

#include "sockimpl.h"

#define PIPEDEFAULTPATH		"/tmp"
#define PIPEALTERNATEPATH	"/var/tmp"

#define PIPENAMEMASK	"OSL_PIPE_%s"
#define SECPIPENAMEMASK	"OSL_PIPE_%s_%s"

sal_Bool SAL_CALL osl_psz_getUserIdent(oslSecurity Security, sal_Char *pszIdent, sal_uInt32 nMax);
oslPipe SAL_CALL osl_psz_createPipe(const sal_Char *pszPipeName, oslPipeOptions Options, oslSecurity Security);

/*#define DEBUG_OSL_PIPE*/
/*#define TRACE_OSL_PIPE*/


/*****************************************************************************/
/* enum oslPipeError */
/*****************************************************************************/

static struct
{
	int            errcode;
	oslPipeError   error;
} PipeError[]= {
	{ 0,			   osl_Pipe_E_None		    	},	/* no error */
	{ EPROTOTYPE,	   osl_Pipe_E_NoProtocol	    },	/* Protocol wrong type for socket */
	{ ENOPROTOOPT,	   osl_Pipe_E_NoProtocol	    },	/* Protocol not available */
	{ EPROTONOSUPPORT, osl_Pipe_E_NoProtocol		},	/* Protocol not supported */
	{ ESOCKTNOSUPPORT, osl_Pipe_E_NoProtocol 		},	/* Socket type not supported */
	{ EPFNOSUPPORT,	   osl_Pipe_E_NoProtocol     	},	/* Protocol family not supported */
	{ EAFNOSUPPORT,	   osl_Pipe_E_NoProtocol     	},	/* Address family not supported by */
														/* protocol family */
	{ ENETRESET,	   osl_Pipe_E_NetworkReset 		},	/* Network dropped connection because */
											 			/* of reset */
	{ ECONNABORTED,	   osl_Pipe_E_ConnectionAbort 	},	/* Software caused connection abort */
	{ ECONNRESET,	   osl_Pipe_E_ConnectionReset 	},	/* Connection reset by peer */
	{ ENOBUFS,		   osl_Pipe_E_NoBufferSpace 	},	/* No buffer space available */
	{ ETIMEDOUT,	   osl_Pipe_E_TimedOut 			},	/* Connection timed out */
	{ ECONNREFUSED,	   osl_Pipe_E_ConnectionRefused	},	/* Connection refused */
	{ -1,		   	   osl_Pipe_E_invalidError 		}
};


/* map */
/* mfe: NOT USED
   static int osl_NativeFromPipeError(oslPipeError errorCode)
   {
   int i = 0;

   while ((PipeError[i].error != osl_Pipe_E_invalidError) &&
   (PipeError[i].error != errorCode)) i++;

   return PipeError[i].errcode;

   }
*/

/* reverse map */
static oslPipeError osl_PipeErrorFromNative(int nativeType)
{
	int i = 0;

	while ((PipeError[i].error != osl_Pipe_E_invalidError) &&
		   (PipeError[i].errcode != nativeType)) i++;

	return PipeError[i].error;
}


/* macros */
#define ERROR_TO_NATIVE(x)		osl_NativeFromPipeError(x)
#define ERROR_FROM_NATIVE(y)	osl_PipeErrorFromNative(y)


/*****************************************************************************/
/* osl_create/destroy-PipeImpl */
/*****************************************************************************/

oslPipe __osl_createPipeImpl()
{
	oslPipe pPipeImpl;

	pPipeImpl = (oslPipe)calloc(1, sizeof(struct oslPipeImpl));
	pPipeImpl->m_nRefCount =1;
	pPipeImpl->m_bClosed = sal_False;
#if defined(LINUX)
	pPipeImpl->m_bIsInShutdown = sal_False;
	pPipeImpl->m_bIsAccepting = sal_False;
#endif
	return pPipeImpl;
}

void __osl_destroyPipeImpl(oslPipe pImpl)
{
	if (pImpl != NULL)
		free(pImpl);
}


/*****************************************************************************/
/* osl_createPipe  */
/*****************************************************************************/
oslPipe SAL_CALL osl_createPipe(rtl_uString *ustrPipeName, oslPipeOptions Options, oslSecurity Security)
{
    oslPipe pPipe=0;
    rtl_String* strPipeName=0;
    sal_Char* pszPipeName=0;

    if ( ustrPipeName != 0 )
    {
        rtl_uString2String( &strPipeName,
                            rtl_uString_getStr(ustrPipeName),
                            rtl_uString_getLength(ustrPipeName),
                            osl_getThreadTextEncoding(),
                            OUSTRING_TO_OSTRING_CVTFLAGS );
        pszPipeName = rtl_string_getStr(strPipeName);
        pPipe = osl_psz_createPipe(pszPipeName, Options, Security);

        if ( strPipeName != 0 )
        {
            rtl_string_release(strPipeName);
        }
    }

    return pPipe;

}

oslPipe SAL_CALL osl_psz_createPipe(const sal_Char *pszPipeName, oslPipeOptions Options,
                       oslSecurity Security)
{
	int    Flags;
	size_t	   len;
	struct sockaddr_un addr;

    sal_Char  	 name[PATH_MAX + 1];
	oslPipe  pPipe;

	if (access(PIPEDEFAULTPATH, R_OK|W_OK) == 0)
    {
		strncpy(name, PIPEDEFAULTPATH, sizeof(name));
    }
	else
    {
		strncpy(name, PIPEALTERNATEPATH, sizeof(name));
    }


	strncat(name, "/", sizeof(name));

	if (Security)
	{
		sal_Char Ident[256];

        Ident[0] = '\0';

		OSL_VERIFY(osl_psz_getUserIdent(Security, Ident, sizeof(Ident)));

		snprintf(&name[strlen(name)], sizeof(name), SECPIPENAMEMASK, Ident, pszPipeName);
	}
	else
	{
		snprintf(&name[strlen(name)], sizeof(name), PIPENAMEMASK, pszPipeName);
	}


	/* alloc memory */
	pPipe= __osl_createPipeImpl();

	/* create socket */
	pPipe->m_Socket = socket(AF_UNIX, SOCK_STREAM, 0);
	if ( pPipe->m_Socket < 0 )
	{
		OSL_TRACE("osl_createPipe socket failed. Errno: %d; %s\n",errno, strerror(errno));
		__osl_destroyPipeImpl(pPipe);
		return NULL;
	}

/*    OSL_TRACE("osl_createPipe : new Pipe on fd %i\n",pPipe->m_Socket);*/

	/* set close-on-exec flag */
	if ((Flags = fcntl(pPipe->m_Socket, F_GETFD, 0)) != -1)
	{
		Flags |= FD_CLOEXEC;
		if (fcntl(pPipe->m_Socket, F_SETFD, Flags) == -1)
		{
			OSL_TRACE("osl_createPipe failed changing socket flags. Errno: %d; %s\n",errno,strerror(errno));
		}
	}

	memset(&addr, 0, sizeof(addr));

    OSL_TRACE("osl_createPipe : Pipe Name '%s'",name);

	addr.sun_family = AF_UNIX;
	strncpy(addr.sun_path, name, sizeof(addr.sun_path));
#if defined(FREEBSD)
	len = SUN_LEN(&addr);
#else
	len = sizeof(addr);
#endif

	if ( Options & osl_Pipe_CREATE )
	{
		struct stat status;

		/* check if there exists an orphan filesystem entry */
		if ( ( stat(name, &status) == 0) &&
			 ( S_ISSOCK(status.st_mode) || S_ISFIFO(status.st_mode) ) )
		{
			if ( connect(pPipe->m_Socket,(struct sockaddr *)&addr,len) >= 0 )
			{
				OSL_TRACE("osl_createPipe : Pipe already in use. Errno: %d; %s\n",errno,strerror(errno));
				close (pPipe->m_Socket);
				__osl_destroyPipeImpl(pPipe);
				return NULL;
			}

			unlink(name);
		}

		/* ok, fs clean */
		if ( bind(pPipe->m_Socket, (struct sockaddr *)&addr, len) < 0 )
		{
			OSL_TRACE("osl_createPipe : failed to bind socket. Errno: %d; %s\n",errno,strerror(errno));
			close (pPipe->m_Socket);
			__osl_destroyPipeImpl(pPipe);
			return NULL;
		}

		/*	Only give access to all if no security handle was specified, otherwise security
			depends on umask */

		if ( !Security )
			chmod(name,S_IRWXU | S_IRWXG |S_IRWXO);


		strncpy(pPipe->m_Name, name, sizeof(pPipe->m_Name));

		if ( listen(pPipe->m_Socket, 5) < 0 )
		{
			OSL_TRACE("osl_createPipe failed to listen. Errno: %d; %s\n",errno,strerror(errno));
			unlink(name);	/* remove filesystem entry */
			close (pPipe->m_Socket);
			__osl_destroyPipeImpl(pPipe);
			return NULL;
		}

		return (pPipe);
	}
	else
	{   /* osl_pipe_OPEN */
		if ( access(name, F_OK) != -1 )
		{
			if ( connect( pPipe->m_Socket, (struct sockaddr *)&addr, len) >= 0 )
			{
				return (pPipe);
			}

			OSL_TRACE("osl_createPipe failed to connect. Errno: %d; %s\n",errno,strerror(errno));
		}

		close (pPipe->m_Socket);
		__osl_destroyPipeImpl(pPipe);
		return NULL;
	}
}

void SAL_CALL osl_acquirePipe( oslPipe pPipe )
{
	osl_incrementInterlockedCount( &(pPipe->m_nRefCount) );
}

void SAL_CALL osl_releasePipe( oslPipe pPipe )
{

	if( 0 == pPipe )
		return;

	if( 0 == osl_decrementInterlockedCount( &(pPipe->m_nRefCount) ) )
	{
		if( ! pPipe->m_bClosed )
			osl_closePipe( pPipe );

		__osl_destroyPipeImpl( pPipe );
	}
}

void SAL_CALL osl_closePipe( oslPipe pPipe )
{
    int nRet;
#if defined(LINUX)
    size_t	   len;
	struct sockaddr_un addr;
    int fd;
#endif
    int ConnFD;

	if( ! pPipe )
	{
		return;
	}

	if( pPipe->m_bClosed )
	{
		return;
	}

    ConnFD = pPipe->m_Socket;

	/*
	  Thread does not return from accept on linux, so
	  connect to the accepting pipe
	 */
#if defined(LINUX)
    if ( pPipe->m_bIsAccepting )
    {
        pPipe->m_bIsInShutdown = sal_True;
        pPipe->m_Socket = -1;
        fd = socket(AF_UNIX, SOCK_STREAM, 0);
        memset(&addr, 0, sizeof(addr));

        OSL_TRACE("osl_destroyPipe : Pipe Name '%s'",pPipe->m_Name);

        addr.sun_family = AF_UNIX;
        strncpy(addr.sun_path, pPipe->m_Name, sizeof(addr.sun_path));
		len = sizeof(addr);

        nRet = connect( fd, (struct sockaddr *)&addr, len);
#if OSL_DEBUG_LEVEL > 1
        if ( nRet < 0 )
        {
            perror("connect in osl_destroyPipe");
        }
#endif /* OSL_DEBUG_LEVEL */
        close(fd);
    }
#endif /* LINUX */


	nRet = shutdown(ConnFD, 2);
    if ( nRet < 0 )
    {
        OSL_TRACE("shutdown in destroyPipe failed : '%s'\n",strerror(errno));
    }

	nRet = close(ConnFD);
    if ( nRet < 0 )
    {
        OSL_TRACE("close in destroyPipe failed : '%s'\n",strerror(errno));
    }
	/* remove filesystem entry */
	if ( strlen(pPipe->m_Name) > 0 )
	{
		unlink(pPipe->m_Name);
	}
	pPipe->m_bClosed = sal_True;

/*      OSL_TRACE("Out osl_destroyPipe");     */
}


/*****************************************************************************/
/* osl_acceptPipe  */
/*****************************************************************************/
oslPipe SAL_CALL osl_acceptPipe(oslPipe pPipe)
{
	int     s, flags;
	oslPipe pAcceptedPipe;

	OSL_ASSERT(pPipe);
	if ( pPipe == 0 )
	{
		return NULL;
	}

	OSL_ASSERT(strlen(pPipe->m_Name) > 0);

#if defined(LINUX)
    pPipe->m_bIsAccepting = sal_True;
#endif

    s = accept(pPipe->m_Socket, NULL, NULL);

#if defined(LINUX)
    pPipe->m_bIsAccepting = sal_False;
#endif

    if (s < 0)
	{
        OSL_TRACE("osl_acceptPipe : accept error '%s'", strerror(errno));
		return NULL;
	}

#if defined(LINUX)
    if ( pPipe->m_bIsInShutdown  )
    {
        close(s);
        return NULL;
    }
#endif /* LINUX */
    else
	{
		/* alloc memory */
		pAcceptedPipe= __osl_createPipeImpl();

		OSL_ASSERT(pAcceptedPipe);
		if(pAcceptedPipe==NULL)
		{
			close(s);
			return NULL;
		}

		/* set close-on-exec flag */
		if (!((flags = fcntl(s, F_GETFD, 0)) < 0))
		{
			flags |= FD_CLOEXEC;
			if (fcntl(s, F_SETFD, flags) < 0)
			{
				OSL_TRACE("osl_acceptPipe: error changing socket flags. "
						  "Errno: %d; %s",errno,strerror(errno));
			}
		}

		pAcceptedPipe->m_Socket = s;
	}

	return pAcceptedPipe;
}

/*****************************************************************************/
/* osl_receivePipe  */
/*****************************************************************************/
sal_Int32 SAL_CALL osl_receivePipe(oslPipe pPipe,
					    void* pBuffer,
					    sal_Int32 BytesToRead)
{
    int nRet = 0;

	OSL_ASSERT(pPipe);

	if ( pPipe == 0 )
	{
        OSL_TRACE("osl_receivePipe : Invalid socket");
        errno=EINVAL;
		return -1;
	}

    nRet = recv(pPipe->m_Socket,
  				(sal_Char*)pBuffer,
  				BytesToRead, 0);

    if ( nRet < 0 )
    {
        OSL_TRACE("osl_receivePipe failed : %i '%s'",nRet,strerror(errno));
    }

  	return nRet;
}


/*****************************************************************************/
/* osl_sendPipe  */
/*****************************************************************************/
sal_Int32 SAL_CALL osl_sendPipe(oslPipe pPipe,
				       const void* pBuffer,
				       sal_Int32 BytesToSend)
{
    int nRet=0;

	OSL_ASSERT(pPipe);

	if ( pPipe == 0 )
	{
        OSL_TRACE("osl_sendPipe : Invalid socket");
        errno=EINVAL;
		return -1;
	}

    nRet = send(pPipe->m_Socket,
  				(sal_Char*)pBuffer,
  				BytesToSend, 0);


    if ( nRet <= 0 )
    {
        OSL_TRACE("osl_sendPipe failed : %i '%s'",nRet,strerror(errno));
    }

 	return nRet;
}


/*****************************************************************************/
/* osl_getLastPipeError  */
/*****************************************************************************/
oslPipeError SAL_CALL osl_getLastPipeError(oslPipe pPipe)
{
    (void) pPipe; /* unused */
	return ERROR_FROM_NATIVE(errno);
}


sal_Int32 SAL_CALL osl_writePipe( oslPipe pPipe, const void *pBuffer , sal_Int32 n )
{
	/* loop until all desired bytes were send or an error occured */
	sal_Int32 BytesSend= 0;
	sal_Int32 BytesToSend= n;

	OSL_ASSERT(pPipe);
	while (BytesToSend > 0)
	{
		sal_Int32 RetVal;

		RetVal= osl_sendPipe(pPipe, pBuffer, BytesToSend);

		/* error occured? */
		if(RetVal <= 0)
		{
			break;
		}

		BytesToSend -= RetVal;
		BytesSend += RetVal;
		pBuffer= (sal_Char*)pBuffer + RetVal;
	}

	return BytesSend;
}

sal_Int32 SAL_CALL osl_readPipe( oslPipe pPipe, void *pBuffer , sal_Int32 n )
{
	/* loop until all desired bytes were read or an error occured */
	sal_Int32 BytesRead= 0;
	sal_Int32 BytesToRead= n;

	OSL_ASSERT( pPipe );
	while (BytesToRead > 0)
	{
		sal_Int32 RetVal;
		RetVal= osl_receivePipe(pPipe, pBuffer, BytesToRead);

		/* error occured? */
		if(RetVal <= 0)
		{
			break;
		}

		BytesToRead -= RetVal;
		BytesRead += RetVal;
		pBuffer= (sal_Char*)pBuffer + RetVal;
	}
	return BytesRead;
}


