Beispiel #1
0
/**
 * \brief Used to check the thread for certain conditions of failure.  If the
 *        thread has been specified to restart on failure, the thread is
 *        restarted.  If the thread has been specified to gracefully shutdown
 *        the engine on failure, it does so.  The global aof flag, tv_aof
 *        overrides the thread aof flag, if it holds a THV_ENGINE_EXIT;
 */
void TmThreadCheckThreadState(void)
{
    ThreadVars *tv = NULL;
    int i = 0;

    for (i = 0; i < TVT_MAX; i++) {
        tv = tv_root[i];

        while (tv) {
            if (TmThreadsCheckFlag(tv, THV_FAILED)) {
                pthread_join(tv->t, NULL);
                if ( !(tv_aof & THV_ENGINE_EXIT) &&
                     (tv->aof & THV_RESTART_THREAD) ) {
                    TmThreadRestartThread(tv);
                } else {
                    TmThreadsSetFlag(tv, THV_CLOSED);
                    EngineKill();
                }
            }
            tv = tv->next;
        }
    }

    return;
}
Beispiel #2
0
/** \brief separate run function so we can call it recursively
 *
 *  \todo deal with post_pq for slots beyond the first
 */
static inline TmEcode TmThreadsSlotVarRun (ThreadVars *tv, Packet *p, TmSlot *slot) {
    TmEcode r = TM_ECODE_OK;
    TmSlot *s = NULL;

    for (s = slot; s != NULL; s = s->slot_next) {
        if (s->id == 0) {
            r = s->SlotFunc(tv, p, s->slot_data, &s->slot_pre_pq, &s->slot_post_pq);
        } else {
            r = s->SlotFunc(tv, p, s->slot_data, &s->slot_pre_pq, NULL);
        }
        /* handle error */
        if (r == TM_ECODE_FAILED) {
            /* Encountered error.  Return packets to packetpool and return */
            TmqhReleasePacketsToPacketPool(&s->slot_pre_pq);
            TmqhReleasePacketsToPacketPool(&s->slot_post_pq);
            TmThreadsSetFlag(tv, THV_FAILED);
            return TM_ECODE_FAILED;
        }

        /* handle new packets */
        while (s->slot_pre_pq.top != NULL) {
            Packet *extra_p = PacketDequeue(&s->slot_pre_pq);
            if (extra_p == NULL)
                continue;

            /* see if we need to process the packet */
            if (s->slot_next != NULL) {
                r = TmThreadsSlotVarRun(tv, extra_p, s->slot_next);
                /* XXX handle error */
                if (r == TM_ECODE_FAILED) {
                    //printf("TmThreadsSlotVarRun: recursive TmThreadsSlotVarRun returned 1\n");
                    TmqhReleasePacketsToPacketPool(&s->slot_pre_pq);
                    TmqhReleasePacketsToPacketPool(&s->slot_post_pq);
                    TmqhOutputPacketpool(tv, extra_p);
                    TmThreadsSetFlag(tv, THV_FAILED);
                    return TM_ECODE_FAILED;
                }
            }
            tv->tmqh_out(tv, extra_p);
        }

        /** \todo post pq */
    }

    return TM_ECODE_OK;
}
Beispiel #3
0
/**
 * \brief Force reassembly for all the flows that have unprocessed segments.
 */
void FlowForceReassembly(void)
{
    /* Do remember.  We need to have packet acquire disabled by now */

    /** ----- Part 1 ------*/
    /* Flush out unattended packets */
    FlowForceReassemblyFlushPendingPseudoPackets();

    /** ----- Part 2 ----- **/
    /* Check if all threads are idle.  We need this so that we have all
     * packets freeds.  As a consequence, no flows are in use */

    SCMutexLock(&tv_root_lock);

    /* all receive threads are part of packet processing threads */
    ThreadVars *tv = tv_root[TVT_PPT];

    /* we are doing this in order receive -> decode -> ... -> log */
    while (tv != NULL) {
        if (tv->inq != NULL) {
            /* we wait till we dry out all the inq packets, before we
             * kill this thread.  Do note that you should have disabled
             * packet acquire by now using TmThreadDisableReceiveThreads()*/
            if (!(strlen(tv->inq->name) == strlen("packetpool") &&
                  strcasecmp(tv->inq->name, "packetpool") == 0)) {
                PacketQueue *q = &trans_q[tv->inq->id];
                while (q->len != 0) {
                    usleep(100);
                }
                TmThreadsSetFlag(tv, THV_PAUSE);
                if (tv->inq->q_type == 0)
                    SCCondSignal(&trans_q[tv->inq->id].cond_q);
                else
                    SCCondSignal(&data_queues[tv->inq->id].cond_q);
                while (!TmThreadsCheckFlag(tv, THV_PAUSED)) {
                    if (tv->inq->q_type == 0)
                        SCCondSignal(&trans_q[tv->inq->id].cond_q);
                    else
                        SCCondSignal(&data_queues[tv->inq->id].cond_q);
                    usleep(100);
                }
                TmThreadsUnsetFlag(tv, THV_PAUSE);
            }
        }
        tv = tv->next;
    }

    SCMutexUnlock(&tv_root_lock);

    /** ----- Part 3 ----- **/
    /* Carry out flow reassembly for unattended flows */
    FlowForceReassemblyForHash();

    return;
}
Beispiel #4
0
/**
 * \brief Kill a thread.
 *
 * \param tv A ThreadVars instance corresponding to the thread that has to be
 *           killed.
 */
void TmThreadKillThread(ThreadVars *tv)
{
    int i = 0;

    if (tv == NULL)
        return;

    /* set the thread flag informing the thread that it needs to be
     * terminated */
    TmThreadsSetFlag(tv, THV_KILL);

    if (tv->inq != NULL) {
        /* signal the queue for the number of users */
                if (tv->InShutdownHandler != NULL) {
                    tv->InShutdownHandler(tv);
                }
        for (i = 0; i < (tv->inq->reader_cnt + tv->inq->writer_cnt); i++)
            SCCondSignal(&trans_q[tv->inq->id].cond_q);

        /* to be sure, signal more */
        while (1) {
            if (TmThreadsCheckFlag(tv, THV_CLOSED)) {
                break;
            }

                if (tv->InShutdownHandler != NULL) {
                    tv->InShutdownHandler(tv);
                }
            for (i = 0; i < (tv->inq->reader_cnt + tv->inq->writer_cnt); i++)
                SCCondSignal(&trans_q[tv->inq->id].cond_q);

            usleep(100);
        }
    }

    if (tv->cond != NULL ) {
        while (1) {
            if (TmThreadsCheckFlag(tv, THV_CLOSED)) {
                break;
            }

            pthread_cond_broadcast(tv->cond);

            usleep(100);
        }
    }

    return;
}
Beispiel #5
0
/**
 * \brief Creates and returns the TV instance for a new thread.
 *
 * \param name       Name of this TV instance
 * \param inq_name   Incoming queue name
 * \param inqh_name  Incoming queue handler name as set by TmqhSetup()
 * \param outq_name  Outgoing queue name
 * \param outqh_name Outgoing queue handler as set by TmqhSetup()
 * \param slots      String representation for the slot function to be used
 * \param fn_p       Pointer to function when \"slots\" is of type \"custom\"
 * \param mucond     Flag to indicate whether to initialize the condition
 *                   and the mutex variables for this newly created TV.
 *
 * \retval the newly created TV instance, or NULL on error
 */
ThreadVars *TmThreadCreate(char *name, char *inq_name, char *inqh_name,
                           char *outq_name, char *outqh_name, char *slots,
                           void * (*fn_p)(void *), int mucond)
{
    ThreadVars *tv = NULL;
    Tmq *tmq = NULL;
    Tmqh *tmqh = NULL;

    SCLogDebug("creating thread \"%s\"...", name);

    /* XXX create separate function for this: allocate a thread container */
    tv = SCMalloc(sizeof(ThreadVars));
    if (tv == NULL)
        goto error;
    memset(tv, 0, sizeof(ThreadVars));

    SC_ATOMIC_INIT(tv->flags);
    SCMutexInit(&tv->sc_perf_pctx.m, NULL);

    tv->name = name;
    /* default state for every newly created thread */
    TmThreadsSetFlag(tv, THV_PAUSE);
    TmThreadsSetFlag(tv, THV_USE);
    /* default aof for every newly created thread */
    tv->aof = THV_RESTART_THREAD;

    /* set the incoming queue */
    if (inq_name != NULL && strcmp(inq_name,"packetpool") != 0) {
        SCLogDebug("inq_name \"%s\"", inq_name);

        tmq = TmqGetQueueByName(inq_name);
        if (tmq == NULL) {
            tmq = TmqCreateQueue(inq_name);
            if (tmq == NULL) goto error;
        }
        SCLogDebug("tmq %p", tmq);

        tv->inq = tmq;
        tv->inq->reader_cnt++;
        SCLogDebug("tv->inq %p", tv->inq);
    }
    if (inqh_name != NULL) {
        SCLogDebug("inqh_name \"%s\"", inqh_name);

        tmqh = TmqhGetQueueHandlerByName(inqh_name);
        if (tmqh == NULL) goto error;

        tv->tmqh_in = tmqh->InHandler;
        tv->InShutdownHandler = tmqh->InShutdownHandler;
        SCLogDebug("tv->tmqh_in %p", tv->tmqh_in);
    }

    /* set the outgoing queue */
    if (outqh_name != NULL) {
        SCLogDebug("outqh_name \"%s\"", outqh_name);

        tmqh = TmqhGetQueueHandlerByName(outqh_name);
        if (tmqh == NULL) goto error;

        tv->tmqh_out = tmqh->OutHandler;

        if (outq_name != NULL && strcmp(outq_name,"packetpool") != 0) {
            SCLogDebug("outq_name \"%s\"", outq_name);

            if (tmqh->OutHandlerCtxSetup != NULL) {
                tv->outctx = tmqh->OutHandlerCtxSetup(outq_name);
                tv->outq = NULL;
            } else {
                tmq = TmqGetQueueByName(outq_name);
                if (tmq == NULL) {
                    tmq = TmqCreateQueue(outq_name);
                    if (tmq == NULL) goto error;
                }
                SCLogDebug("tmq %p", tmq);

                tv->outq = tmq;
                tv->outctx = NULL;
                tv->outq->writer_cnt++;
            }
        }
    }

    if (TmThreadSetSlots(tv, slots, fn_p) != TM_ECODE_OK) {
        goto error;
    }

    if (mucond != 0)
        TmThreadInitMC(tv);

    return tv;
error:
    printf("ERROR: failed to setup a thread.\n");
    return NULL;
}
Beispiel #6
0
/**
 *  \todo only the first "slot" currently makes the "post_pq" available
 *        to the thread module.
 */
void *TmThreadsSlotVar(void *td) {
    ThreadVars *tv = (ThreadVars *)td;
    TmVarSlot *s = (TmVarSlot *)tv->tm_slots;
    Packet *p = NULL;
    char run = 1;
    TmEcode r = TM_ECODE_OK;
    TmSlot *slot = NULL;

    /* Set the thread name */
    SCSetThreadName(tv->name);

    /* Drop the capabilities for this thread */
    SCDropCaps(tv);

    if (tv->thread_setup_flags != 0)
        TmThreadSetupOptions(tv);

    /* check if we are setup properly */
    if (s == NULL || s->s == NULL || tv->tmqh_in == NULL || tv->tmqh_out == NULL) {
        EngineKill();

        TmThreadsSetFlag(tv, THV_CLOSED);
        pthread_exit((void *) -1);
    }

    for (slot = s->s; slot != NULL; slot = slot->slot_next) {
        if (slot->SlotThreadInit != NULL) {
            r = slot->SlotThreadInit(tv, slot->slot_initdata, &slot->slot_data);
            if (r != TM_ECODE_OK) {
                EngineKill();

                TmThreadsSetFlag(tv, THV_CLOSED);
                pthread_exit((void *) -1);
            }
        }
        memset(&slot->slot_pre_pq, 0, sizeof(PacketQueue));
        memset(&slot->slot_post_pq, 0, sizeof(PacketQueue));
    }

    TmThreadsSetFlag(tv, THV_INIT_DONE);

    while(run) {
        TmThreadTestThreadUnPaused(tv);

        /* input a packet */
        p = tv->tmqh_in(tv);

        if (p != NULL) {
            /* run the thread module(s) */
            r = TmThreadsSlotVarRun(tv, p, s->s);
            if (r == TM_ECODE_FAILED) {
                TmqhOutputPacketpool(tv, p);
                TmThreadsSetFlag(tv, THV_FAILED);
                break;
            }

            /* output the packet */
            tv->tmqh_out(tv, p);

            /* now handle the post_pq packets */
            while (s->s->slot_post_pq.top != NULL) {
                Packet *extra_p = PacketDequeue(&s->s->slot_post_pq);
                if (extra_p == NULL)
                    continue;

                if (s->s->slot_next != NULL) {
                    r = TmThreadsSlotVarRun(tv, extra_p, s->s->slot_next);
                    if (r == TM_ECODE_FAILED) {
                        TmqhOutputPacketpool(tv, extra_p);
                        TmThreadsSetFlag(tv, THV_FAILED);
                        break;
                    }
                }

                /* output the packet */
                tv->tmqh_out(tv, extra_p);
            }
        }

        if (TmThreadsCheckFlag(tv, THV_KILL)) {
            run = 0;
        }
    }
    SCPerfUpdateCounterArray(tv->sc_perf_pca, &tv->sc_perf_pctx, 0);

    for (slot = s->s; slot != NULL; slot = slot->slot_next) {
        if (slot->SlotThreadExitPrintStats != NULL) {
            slot->SlotThreadExitPrintStats(tv, slot->slot_data);
        }

        if (slot->SlotThreadDeinit != NULL) {
            r = slot->SlotThreadDeinit(tv, slot->slot_data);
            if (r != TM_ECODE_OK) {
                TmThreadsSetFlag(tv, THV_CLOSED);
                pthread_exit((void *) -1);
            }
        }
    }

    SCLogDebug("%s ending", tv->name);
    TmThreadsSetFlag(tv, THV_CLOSED);
    pthread_exit((void *) 0);
}
Beispiel #7
0
void *TmThreadsSlot1(void *td) {
    ThreadVars *tv = (ThreadVars *)td;
    Tm1Slot *s = (Tm1Slot *)tv->tm_slots;
    Packet *p = NULL;
    char run = 1;
    TmEcode r = TM_ECODE_OK;

    /* Set the thread name */
    SCSetThreadName(tv->name);

    /* Drop the capabilities for this thread */
    SCDropCaps(tv);

    if (tv->thread_setup_flags != 0)
        TmThreadSetupOptions(tv);

    SCLogDebug("%s starting", tv->name);

    if (s->s.SlotThreadInit != NULL) {
        r = s->s.SlotThreadInit(tv, s->s.slot_initdata, &s->s.slot_data);
        if (r != TM_ECODE_OK) {
            EngineKill();

            TmThreadsSetFlag(tv, THV_CLOSED);
            pthread_exit((void *) -1);
        }
    }
    memset(&s->s.slot_pre_pq, 0, sizeof(PacketQueue));
    memset(&s->s.slot_post_pq, 0, sizeof(PacketQueue));

    TmThreadsSetFlag(tv, THV_INIT_DONE);
    while(run) {
        TmThreadTestThreadUnPaused(tv);

        /* input a packet */
        p = tv->tmqh_in(tv);

        if (p == NULL) {
            //printf("%s: TmThreadsSlot1: p == NULL\n", tv->name);
        } else {
            r = s->s.SlotFunc(tv, p, s->s.slot_data, &s->s.slot_pre_pq, &s->s.slot_post_pq);
            /* handle error */
            if (r == TM_ECODE_FAILED) {
                TmqhReleasePacketsToPacketPool(&s->s.slot_pre_pq);
                TmqhReleasePacketsToPacketPool(&s->s.slot_post_pq);
                TmqhOutputPacketpool(tv, p);
                TmThreadsSetFlag(tv, THV_FAILED);
                break;
            }

            while (s->s.slot_pre_pq.top != NULL) {
                /* handle new packets from this func */
                Packet *extra_p = PacketDequeue(&s->s.slot_pre_pq);
                if (extra_p != NULL) {
                    tv->tmqh_out(tv, extra_p);
                }
            }

            /* output the packet */
            tv->tmqh_out(tv, p);

            while (s->s.slot_post_pq.top != NULL) {
                /* handle new packets from this func */
                Packet *extra_p = PacketDequeue(&s->s.slot_post_pq);
                if (extra_p != NULL) {
                    tv->tmqh_out(tv, extra_p);
                }
            }
        }

        if (TmThreadsCheckFlag(tv, THV_KILL)) {
            //printf("%s: TmThreadsSlot1: KILL is set\n", tv->name);
            SCPerfUpdateCounterArray(tv->sc_perf_pca, &tv->sc_perf_pctx, 0);
            run = 0;
        }
    }

    if (s->s.SlotThreadExitPrintStats != NULL) {
        s->s.SlotThreadExitPrintStats(tv, s->s.slot_data);
    }

    if (s->s.SlotThreadDeinit != NULL) {
        r = s->s.SlotThreadDeinit(tv, s->s.slot_data);
        if (r != TM_ECODE_OK) {
            TmThreadsSetFlag(tv, THV_CLOSED);
            pthread_exit((void *) -1);
        }
    }

    SCLogDebug("%s ending", tv->name);
    TmThreadsSetFlag(tv, THV_CLOSED);
    pthread_exit((void *) 0);
}
Beispiel #8
0
void *TmThreadsSlot1NoInOut(void *td) {
    ThreadVars *tv = (ThreadVars *)td;
    Tm1Slot *s = (Tm1Slot *)tv->tm_slots;
    char run = 1;
    TmEcode r = TM_ECODE_OK;

    /* Set the thread name */
    SCSetThreadName(tv->name);

    /* Drop the capabilities for this thread */
    SCDropCaps(tv);

    if (tv->thread_setup_flags != 0)
        TmThreadSetupOptions(tv);

    SCLogDebug("%s starting", tv->name);

    if (s->s.SlotThreadInit != NULL) {
        r = s->s.SlotThreadInit(tv, s->s.slot_initdata, &s->s.slot_data);
        if (r != TM_ECODE_OK) {
            EngineKill();

            TmThreadsSetFlag(tv, THV_CLOSED);
            pthread_exit((void *) -1);
        }
    }
    memset(&s->s.slot_pre_pq, 0, sizeof(PacketQueue));
    memset(&s->s.slot_post_pq, 0, sizeof(PacketQueue));

    TmThreadsSetFlag(tv, THV_INIT_DONE);

    while(run) {
        TmThreadTestThreadUnPaused(tv);

        r = s->s.SlotFunc(tv, NULL, s->s.slot_data, /* no outqh, no pq */NULL, NULL);
        //printf("%s: TmThreadsSlot1NoInNoOut: r %" PRId32 "\n", tv->name, r);

        /* handle error */
        if (r == TM_ECODE_FAILED) {
            TmThreadsSetFlag(tv, THV_FAILED);
            break;
        }

        if (TmThreadsCheckFlag(tv, THV_KILL)) {
            //printf("%s: TmThreadsSlot1NoInOut: KILL is set\n", tv->name);
            SCPerfUpdateCounterArray(tv->sc_perf_pca, &tv->sc_perf_pctx, 0);
            run = 0;
        }
    }

    if (s->s.SlotThreadExitPrintStats != NULL) {
        s->s.SlotThreadExitPrintStats(tv, s->s.slot_data);
    }

    if (s->s.SlotThreadDeinit != NULL) {
        r = s->s.SlotThreadDeinit(tv, s->s.slot_data);
        if (r != TM_ECODE_OK) {
            TmThreadsSetFlag(tv, THV_CLOSED);
            pthread_exit((void *) -1);
        }
    }

    //printf("TmThreadsSlot1NoInOut: %s ending\n", tv->name);
    TmThreadsSetFlag(tv, THV_CLOSED);
    pthread_exit((void *) 0);
}
Beispiel #9
0
void *TmThreadsSlot1NoOut(void *td) {
    ThreadVars *tv = (ThreadVars *)td;
    Tm1Slot *s = (Tm1Slot *)tv->tm_slots;
    Packet *p = NULL;
    char run = 1;
    TmEcode r = TM_ECODE_OK;

    /* Set the thread name */
    SCSetThreadName(tv->name);

    /* Drop the capabilities for this thread */
    SCDropCaps(tv);

    if (tv->thread_setup_flags != 0)
        TmThreadSetupOptions(tv);

    if (s->s.SlotThreadInit != NULL) {
        r = s->s.SlotThreadInit(tv, s->s.slot_initdata, &s->s.slot_data);
        if (r != TM_ECODE_OK) {
            EngineKill();

            TmThreadsSetFlag(tv, THV_CLOSED);
            pthread_exit((void *) -1);
        }
    }
    memset(&s->s.slot_pre_pq, 0, sizeof(PacketQueue));
    memset(&s->s.slot_post_pq, 0, sizeof(PacketQueue));

    TmThreadsSetFlag(tv, THV_INIT_DONE);

    while(run) {
        TmThreadTestThreadUnPaused(tv);

        p = tv->tmqh_in(tv);

        r = s->s.SlotFunc(tv, p, s->s.slot_data, /* no outqh no pq */NULL, /* no outqh no pq */NULL);
        /* handle error */
        if (r == TM_ECODE_FAILED) {
            TmqhOutputPacketpool(tv, p);
            TmThreadsSetFlag(tv, THV_FAILED);
            break;
        }

        if (TmThreadsCheckFlag(tv, THV_KILL)) {
            SCPerfUpdateCounterArray(tv->sc_perf_pca, &tv->sc_perf_pctx, 0);
            run = 0;
        }
    }

    if (s->s.SlotThreadExitPrintStats != NULL) {
        s->s.SlotThreadExitPrintStats(tv, s->s.slot_data);
    }

    if (s->s.SlotThreadDeinit != NULL) {
        r = s->s.SlotThreadDeinit(tv, s->s.slot_data);
        if (r != TM_ECODE_OK) {
            TmThreadsSetFlag(tv, THV_CLOSED);
            pthread_exit((void *) -1);
        }
    }

    TmThreadsSetFlag(tv, THV_CLOSED);
    pthread_exit((void *) 0);
}
Beispiel #10
0
/**
 * \brief Pauses a thread
 *
 * \param tv Pointer to a TV instance that has to be paused
 */
void TmThreadPause(ThreadVars *tv)
{
    TmThreadsSetFlag(tv, THV_PAUSE);
    return;
}
Beispiel #11
0
void TmThreadKillThreads(void) {
    ThreadVars *tv = NULL;
    int i = 0;

    for (i = 0; i < TVT_MAX; i++) {
        tv = tv_root[i];


        while (tv) {
            TmThreadsSetFlag(tv, THV_KILL);
            SCLogDebug("told thread %s to stop", tv->name);

            if (tv->inq != NULL) {
                int i;

                //printf("TmThreadKillThreads: (t->inq->reader_cnt + t->inq->writer_cnt) %" PRIu32 "\n", (t->inq->reader_cnt + t->inq->writer_cnt));

                /* make sure our packet pending counter doesn't block */
                //SCCondSignal(&cond_pending);

                /* signal the queue for the number of users */

                if (tv->InShutdownHandler != NULL) {
                    tv->InShutdownHandler(tv);
                }
                for (i = 0; i < (tv->inq->reader_cnt + tv->inq->writer_cnt); i++) {
                    if (tv->inq->q_type == 0)
                        SCCondSignal(&trans_q[tv->inq->id].cond_q);
                    else
                        SCCondSignal(&data_queues[tv->inq->id].cond_q);
                }

                /* to be sure, signal more */
                int cnt = 0;
                while (1) {
                    if (TmThreadsCheckFlag(tv, THV_CLOSED)) {
                        SCLogDebug("signalled the thread %" PRId32 " times", cnt);
                        break;
                    }

                    cnt++;

                    if (tv->InShutdownHandler != NULL) {
                        tv->InShutdownHandler(tv);
                    }

                    for (i = 0; i < (tv->inq->reader_cnt + tv->inq->writer_cnt); i++) {
                        if (tv->inq->q_type == 0)
                            SCCondSignal(&trans_q[tv->inq->id].cond_q);
                        else
                            SCCondSignal(&data_queues[tv->inq->id].cond_q);
                    }
                    usleep(100);
                }

                SCLogDebug("signalled tv->inq->id %" PRIu32 "", tv->inq->id);
            }

            if (tv->cond != NULL ) {
                int cnt = 0;
                while (1) {
                    if (TmThreadsCheckFlag(tv, THV_CLOSED)) {
                        SCLogDebug("signalled the thread %" PRId32 " times", cnt);
                        break;
                    }

                    cnt++;

                    pthread_cond_broadcast(tv->cond);

                    usleep(100);
                }
            }

            /* join it */
            pthread_join(tv->t, NULL);
            SCLogDebug("thread %s stopped", tv->name);

            tv = tv->next;
        }
    }
}