/* nsdpoll_ptcp.c
*
* An implementation of the nsd epoll() interface for plain tcp sockets.
*
* Copyright 2009 Rainer Gerhards and Adiscon GmbH.
*
* This file is part of the rsyslog runtime library.
*
* The rsyslog runtime 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 3 of the License, or
* (at your option) any later version.
*
* The rsyslog runtime 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 the rsyslog runtime library. If not, see .
*
* A copy of the GPL can be found in the file "COPYING" in this distribution.
* A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution.
*/
#include "config.h"
#ifdef HAVE_EPOLL_CREATE /* this module requires epoll! */
#include
#include
#include
#include
#if HAVE_SYS_EPOLL_H
# include
#endif
#include "rsyslog.h"
#include "module-template.h"
#include "obj.h"
#include "errmsg.h"
#include "srUtils.h"
#include "nspoll.h"
#include "nsd_ptcp.h"
#include "nsdpoll_ptcp.h"
/* static data */
DEFobjStaticHelpers
DEFobjCurrIf(errmsg)
DEFobjCurrIf(glbl)
/* -START------------------------- helpers for event list ------------------------------------ */
/* add new entry to list. We assume that the fd is not already present and DO NOT check this!
* Returns newly created entry in pEvtLst.
* Note that we currently need to use level-triggered mode, because the upper layers do not work
* in parallel. As such, in edge-triggered mode we may not get notified, because new data comes
* in after we have read everything that was present. To use ET mode, we need to change the upper
* peers so that they immediately start a new wait before processing the data read. That obviously
* requires more elaborate redesign and we postpone this until the current more simplictic mode has
* been proven OK in practice.
* rgerhards, 2009-11-18
*/
static inline rsRetVal
addEvent(nsdpoll_ptcp_t *pThis, int id, void *pUsr, int mode, nsd_ptcp_t *pSock, nsdpoll_epollevt_lst_t **pEvtLst) {
nsdpoll_epollevt_lst_t *pNew;
DEFiRet;
CHKmalloc(pNew = (nsdpoll_epollevt_lst_t*) calloc(1, sizeof(nsdpoll_epollevt_lst_t)));
pNew->id = id;
pNew->pUsr = pUsr;
pNew->pSock = pSock;
pNew->event.events = 0; /* TODO: at some time we should be able to use EPOLLET */
//pNew->event.events = EPOLLET;
if(mode & NSDPOLL_IN)
pNew->event.events |= EPOLLIN;
if(mode & NSDPOLL_OUT)
pNew->event.events |= EPOLLOUT;
pNew->event.data.ptr = pNew;
pthread_mutex_lock(&pThis->mutEvtLst);
pNew->pNext = pThis->pRoot;
pThis->pRoot = pNew;
pthread_mutex_unlock(&pThis->mutEvtLst);
*pEvtLst = pNew;
finalize_it:
RETiRet;
}
/* find and unlink the entry identified by id/pUsr from the list.
* rgerhards, 2009-11-23
*/
static inline rsRetVal
unlinkEvent(nsdpoll_ptcp_t *pThis, int id, void *pUsr, nsdpoll_epollevt_lst_t **ppEvtLst) {
nsdpoll_epollevt_lst_t *pEvtLst;
nsdpoll_epollevt_lst_t *pPrev = NULL;
DEFiRet;
pthread_mutex_lock(&pThis->mutEvtLst);
pEvtLst = pThis->pRoot;
while(pEvtLst != NULL && !(pEvtLst->id == id && pEvtLst->pUsr == pUsr)) {
pPrev = pEvtLst;
pEvtLst = pEvtLst->pNext;
}
if(pEvtLst == NULL)
ABORT_FINALIZE(RS_RET_NOT_FOUND);
*ppEvtLst = pEvtLst;
/* unlink */
if(pPrev == NULL)
pThis->pRoot = pEvtLst->pNext;
else
pPrev->pNext = pEvtLst->pNext;
finalize_it:
pthread_mutex_unlock(&pThis->mutEvtLst);
RETiRet;
}
/* destruct the provided element. It must already be unlinked from the list.
* rgerhards, 2009-11-23
*/
static inline rsRetVal
delEvent(nsdpoll_epollevt_lst_t **ppEvtLst) {
DEFiRet;
free(*ppEvtLst);
*ppEvtLst = NULL;
RETiRet;
}
/* -END--------------------------- helpers for event list ------------------------------------ */
/* Standard-Constructor
*/
BEGINobjConstruct(nsdpoll_ptcp) /* be sure to specify the object type also in END macro! */
#if defined(EPOLL_CLOEXEC) && defined(HAVE_EPOLL_CREATE1)
DBGPRINTF("nsdpoll_ptcp uses epoll_create1()\n");
pThis->efd = epoll_create1(EPOLL_CLOEXEC);
if(pThis->efd < 0 && errno == ENOSYS)
#endif
{
DBGPRINTF("nsdpoll_ptcp uses epoll_create()\n");
pThis->efd = epoll_create(100); /* size is ignored in newer kernels, but 100 is not bad... */
}
if(pThis->efd < 0) {
DBGPRINTF("epoll_create1() could not create fd\n");
ABORT_FINALIZE(RS_RET_IO_ERROR);
}
pthread_mutex_init(&pThis->mutEvtLst, NULL);
finalize_it:
ENDobjConstruct(nsdpoll_ptcp)
/* destructor for the nsdpoll_ptcp object */
BEGINobjDestruct(nsdpoll_ptcp) /* be sure to specify the object type also in END and CODESTART macros! */
nsdpoll_epollevt_lst_t *node;
nsdpoll_epollevt_lst_t *nextnode;
CODESTARTobjDestruct(nsdpoll_ptcp)
/* we check if the epoll list still holds entries. This may happen, but
* is a bit unusual.
*/
if(pThis->pRoot != NULL) {
for(node = pThis->pRoot ; node != NULL ; node = nextnode) {
nextnode = node->pNext;
dbgprintf("nsdpoll_ptcp destruct, need to destruct node %p\n", node);
delEvent(&node);
}
}
pthread_mutex_destroy(&pThis->mutEvtLst);
ENDobjDestruct(nsdpoll_ptcp)
/* Modify socket set */
static rsRetVal
Ctl(nsdpoll_t *pNsdpoll, nsd_t *pNsd, int id, void *pUsr, int mode, int op) {
nsdpoll_ptcp_t *pThis = (nsdpoll_ptcp_t*) pNsdpoll;
nsd_ptcp_t *pSock = (nsd_ptcp_t*) pNsd;
nsdpoll_epollevt_lst_t *pEventLst;
int errSave;
char errStr[512];
DEFiRet;
if(op == NSDPOLL_ADD) {
dbgprintf("adding nsdpoll entry %d/%p, sock %d\n", id, pUsr, pSock->sock);
CHKiRet(addEvent(pThis, id, pUsr, mode, pSock, &pEventLst));
if(epoll_ctl(pThis->efd, EPOLL_CTL_ADD, pSock->sock, &pEventLst->event) < 0) {
errSave = errno;
rs_strerror_r(errSave, errStr, sizeof(errStr));
errmsg.LogError(errSave, RS_RET_ERR_EPOLL_CTL,
"epoll_ctl failed on fd %d, id %d/%p, op %d with %s\n",
pSock->sock, id, pUsr, mode, errStr);
}
} else if(op == NSDPOLL_DEL) {
dbgprintf("removing nsdpoll entry %d/%p, sock %d\n", id, pUsr, pSock->sock);
CHKiRet(unlinkEvent(pThis, id, pUsr, &pEventLst));
if(epoll_ctl(pThis->efd, EPOLL_CTL_DEL, pSock->sock, &pEventLst->event) < 0) {
errSave = errno;
rs_strerror_r(errSave, errStr, sizeof(errStr));
errmsg.LogError(errSave, RS_RET_ERR_EPOLL_CTL,
"epoll_ctl failed on fd %d, id %d/%p, op %d with %s\n",
pSock->sock, id, pUsr, mode, errStr);
ABORT_FINALIZE(RS_RET_ERR_EPOLL_CTL);
}
CHKiRet(delEvent(&pEventLst));
} else {
dbgprintf("program error: invalid NSDPOLL_mode %d - ignoring request\n", op);
ABORT_FINALIZE(RS_RET_ERR);
}
finalize_it:
RETiRet;
}
/* Wait for io to become ready. After the successful call, idRdy contains the
* id set by the caller for that i/o event, ppUsr is a pointer to a location
* where the user pointer shall be stored.
* numEntries contains the maximum number of entries on entry and the actual
* number of entries actually read on exit.
* rgerhards, 2009-11-18
*/
static rsRetVal
Wait(nsdpoll_t *pNsdpoll, int timeout, int *numEntries, nsd_epworkset_t workset[]) {
nsdpoll_ptcp_t *pThis = (nsdpoll_ptcp_t*) pNsdpoll;
nsdpoll_epollevt_lst_t *pOurEvt;
struct epoll_event event[128];
int nfds;
int i;
DEFiRet;
assert(workset != NULL);
if(*numEntries > 128)
*numEntries = 128;
DBGPRINTF("doing epoll_wait for max %d events\n", *numEntries);
nfds = epoll_wait(pThis->efd, event, *numEntries, timeout);
if(nfds == -1) {
if(errno == EINTR) {
ABORT_FINALIZE(RS_RET_EINTR);
} else {
DBGPRINTF("epoll() returned with error code %d\n", errno);
ABORT_FINALIZE(RS_RET_ERR_EPOLL);
}
} else if(nfds == 0) {
ABORT_FINALIZE(RS_RET_TIMEOUT);
}
/* we got valid events, so tell the caller... */
dbgprintf("epoll returned %d entries\n", nfds);
for(i = 0 ; i < nfds ; ++i) {
pOurEvt = (nsdpoll_epollevt_lst_t*) event[i].data.ptr;
workset[i].id = pOurEvt->id;
workset[i].pUsr = pOurEvt->pUsr;
dbgprintf("epoll push ppusr[%d]: %p\n", i, pOurEvt->pUsr);
}
*numEntries = nfds;
finalize_it:
RETiRet;
}
/* ------------------------------ end support for the epoll() interface ------------------------------ */
/* queryInterface function */
BEGINobjQueryInterface(nsdpoll_ptcp)
CODESTARTobjQueryInterface(nsdpoll_ptcp)
if(pIf->ifVersion != nsdCURR_IF_VERSION) {/* check for current version, increment on each change */
ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED);
}
/* ok, we have the right interface, so let's fill it
* Please note that we may also do some backwards-compatibility
* work here (if we can support an older interface version - that,
* of course, also affects the "if" above).
*/
pIf->Construct = (rsRetVal(*)(nsdpoll_t**)) nsdpoll_ptcpConstruct;
pIf->Destruct = (rsRetVal(*)(nsdpoll_t**)) nsdpoll_ptcpDestruct;
pIf->Ctl = Ctl;
pIf->Wait = Wait;
finalize_it:
ENDobjQueryInterface(nsdpoll_ptcp)
/* exit our class
*/
BEGINObjClassExit(nsdpoll_ptcp, OBJ_IS_CORE_MODULE) /* CHANGE class also in END MACRO! */
CODESTARTObjClassExit(nsdpoll_ptcp)
/* release objects we no longer need */
objRelease(glbl, CORE_COMPONENT);
objRelease(errmsg, CORE_COMPONENT);
ENDObjClassExit(nsdpoll_ptcp)
/* Initialize the nsdpoll_ptcp class. Must be called as the very first method
* before anything else is called inside this class.
* rgerhards, 2008-02-19
*/
BEGINObjClassInit(nsdpoll_ptcp, 1, OBJ_IS_CORE_MODULE) /* class, version */
/* request objects we use */
CHKiRet(objUse(errmsg, CORE_COMPONENT));
CHKiRet(objUse(glbl, CORE_COMPONENT));
/* set our own handlers */
ENDObjClassInit(nsdpoll_ptcp)
#endif /* #ifdef HAVE_EPOLL_CREATE this module requires epoll! */
/* vi:set ai:
*/