/* iminternal.c
 * This file set implements the internal messages input module for rsyslog.
 * Note: we currently do not have an input module spec, but
 * we will have one in the future. This module needs then to be
 * adapted.
 *
 * File begun on 2007-08-03 by RGerhards
 *
 * Copyright 2007-2022 Rainer Gerhards and Adiscon GmbH.
 *
 * This file is part of rsyslog.
 *
 * Licensed 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
 *       -or-
 *       see COPYING.ASL20 in the source distribution
 *
 * 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 "config.h"
#include "rsyslog.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <signal.h>

#include "syslogd.h"
#include "linkedlist.h"
#include "iminternal.h"
#include "unicode-helper.h"

static linkedList_t llMsgs;
static pthread_mutex_t mutList = PTHREAD_MUTEX_INITIALIZER;


/* destructs an iminternal object
 */
static rsRetVal iminternalDestruct(iminternal_t *pThis) {
    DEFiRet;

    if (pThis->pMsg != NULL) msgDestruct(&pThis->pMsg);

    free(pThis);

    RETiRet;
}


/* Construct an iminternal object
 */
static rsRetVal iminternalConstruct(iminternal_t **ppThis) {
    DEFiRet;
    if ((*ppThis = (iminternal_t *)calloc(1, sizeof(iminternal_t))) == NULL) {
        iRet = RS_RET_OUT_OF_MEMORY;
    }
    RETiRet;
}


/* add a message to the linked list
 * Note: the pMsg reference counter is not incremented. Consequently,
 * the caller must NOT decrement it. The caller actually hands over
 * full ownership of the pMsg object.
 */
rsRetVal iminternalAddMsg(smsg_t *pMsg) {
    DEFiRet;
    iminternal_t *pThis = NULL;
    struct timespec to;
    int r;
    int is_locked = 0;

    /* we guard against deadlock, so we can guarantee rsyslog will never
     * block due to internal messages. The 1 second timeout should be
     * sufficient under all circumstances.
     */
    to.tv_sec = time(NULL) + 1;
    to.tv_nsec = 0;
#if !defined(__APPLE__)
    r = pthread_mutex_timedlock(&mutList, &to);
#else
    r = pthread_mutex_trylock(&mutList);  // must check
#endif
    if (r != 0) {
        dbgprintf("iminternalAddMsg: timedlock for mutex failed with %d, msg %s\n", r, getMSG(pMsg));
        /* the message is lost, nothing we can do against this! */
        msgDestruct(&pMsg);
        ABORT_FINALIZE(RS_RET_ERR);
    }
    is_locked = 1;
    CHKiRet(iminternalConstruct(&pThis));
    pThis->pMsg = pMsg;
    CHKiRet(llAppend(&llMsgs, NULL, (void *)pThis));

    if (PREFER_FETCH_32BIT(bHaveMainQueue)) {
        DBGPRINTF("signaling new internal message via SIGTTOU: '%s'\n", pThis->pMsg->pszRawMsg);
        kill(glblGetOurPid(), SIGTTOU);
    }

finalize_it:
    if (is_locked) {
        pthread_mutex_unlock(&mutList);
    }
    if (iRet != RS_RET_OK) {
        dbgprintf("iminternalAddMsg() error %d - can not otherwise report this error, message lost\n", iRet);
        if (pThis != NULL) iminternalDestruct(pThis);
    }

    RETiRet;
}


/* pull the first error message from the linked list, remove it
 * from the list and return it to the caller. The caller is
 * responsible for freeing the message!
 */
rsRetVal iminternalRemoveMsg(smsg_t **ppMsg) {
    DEFiRet;
    iminternal_t *pThis;
    linkedListCookie_t llCookie = NULL;

    pthread_mutex_lock(&mutList);
    CHKiRet(llGetNextElt(&llMsgs, &llCookie, (void *)&pThis));
    if (!strcmp((char *)pThis->pMsg->pszHOSTNAME, "[localhost]")) {
        /* early (pre-conf) startup message detected, need to set real hostname now */
        MsgSetHOSTNAME(pThis->pMsg, glblGetLocalHostName(), ustrlen(glblGetLocalHostName()));
    }
    *ppMsg = pThis->pMsg;
    pThis->pMsg = NULL; /* we do no longer own it - important for destructor */

    if (llDestroyRootElt(&llMsgs) != RS_RET_OK) {
        dbgprintf(
            "Root element of iminternal linked list could not be destroyed - there is "
            "nothing we can do against it, we ignore it for now. Things may go wild "
            "from here on. This is most probably a program logic error.\n");
    }

finalize_it:
    pthread_mutex_unlock(&mutList);
    RETiRet;
}


/* initialize the iminternal subsystem
 * must be called once at the start of the program
 */
rsRetVal modInitIminternal(void) {
    DEFiRet;
    iRet = llInit(&llMsgs, (rsRetVal(*)(void *))iminternalDestruct, NULL, NULL);
    RETiRet;
}


/* de-initialize the iminternal subsystem
 * must be called once at the end of the program
 * Note: the error list must have been pulled first. We do
 * NOT care if there are any errors left - we simply destroy
 * them.
 */
rsRetVal modExitIminternal(void) {
    DEFiRet;
    iRet = llDestroy(&llMsgs);
    RETiRet;
}
