root/usr/src/cmd/lp/lib/msgs/mlisten.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*      Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/*        All Rights Reserved   */

# include       <unistd.h>
# include       <stdlib.h>
# include       <string.h>
# include       <poll.h>
# include       <stropts.h>
# include       <fcntl.h>
# include       <errno.h>
#include        <syslog.h>
#include <user_attr.h>
#include <secdb.h>
#include <pwd.h>

# include       "lp.h"
# include       "msgs.h"

#define TURN_ON(X,F)    (void)Fcntl(X, F_SETFL, (Fcntl(X, F_GETFL, 0)|(F)))

static int              NumEvents = 0;
static int              NumCons = 0;
static int              ConsSize= 0;
static int              NumNewCons = 0;
static MESG **          Connections = NULL;
static struct pollfd *  PollFdList = NULL;

int
mlisteninit(MESG * md)
{
    if (md == NULL)
    {
        errno = EINVAL;
        return(-1);
    }

    if (ConsSize > 0)
    {
        errno = EBUSY;
        return(-1);
    }

    ConsSize = 20;
    Connections = (MESG **) Malloc(ConsSize * MDSIZE);
    PollFdList = (struct pollfd*) Malloc(ConsSize * sizeof(struct pollfd));
    if (Connections == NULL || PollFdList == NULL)
    {
        errno = ENOMEM;
        return(-1);
    }
    Connections[0] = md;
    PollFdList[0].fd = md->readfd;
    PollFdList[0].events = POLLIN;
    PollFdList[0].revents = 0;
    NumCons = 1;
    return(0);
}

int
mlistenadd(MESG * md, short events)
{
    int                 slack;
    struct pollfd *     fdp;

    /*
    **  See if we have room in the connection table.
    **  Realloc(3) the table if the number of connections
    **  changes by more than 20.
    */

    slack = ConsSize - (NumCons + NumNewCons + 1);

    if (slack < 0)
    {
        ConsSize += 20;
        Connections = (MESG **) Realloc(Connections, ConsSize * MDSIZE);
        PollFdList = (struct pollfd*) Realloc(PollFdList, ConsSize * sizeof(struct pollfd));
        if (Connections == NULL || PollFdList == NULL)
        {
            errno = ENOMEM;
            return(-1);
        }
    }

    if (slack > 20)
    {
        ConsSize -= 20;
        Connections = (MESG **) Realloc(Connections, ConsSize * MDSIZE);
        PollFdList = (struct pollfd*) Realloc(PollFdList, ConsSize * sizeof(struct pollfd));
        if (Connections == NULL || PollFdList == NULL)
        {
            errno = ENOMEM;
            return(-1);
        }
    }

    fdp = PollFdList + (NumCons + NumNewCons);
    fdp->fd = md->readfd;
    fdp->events = events;
    fdp->revents = 0;

    /*
    **  Now add the entry to the connection table
    **  NumCons will be updated above.
    */
    Connections[NumCons + NumNewCons++] = md;
    return(0);
}

MESG *
mlistenreset ( void )   /* funcdef */
{
    int         x;
    MESG *      md;

    if (ConsSize == 0)
        return(NULL);

    ConsSize = 0;

    for (x = 1; x < NumCons; x++)
        (void) mdisconnect(Connections[x]);

    md = Connections[0];

    Free(Connections);
    Free(PollFdList);

    Connections = NULL;
    PollFdList = NULL;
    NumCons = 0;
    NumNewCons = 0;
    NumEvents = 0;
    return(md);
}

MESG *
mlisten()
{
    extern uid_t        Lp_Uid;

    MESG *              mdp;
    MESG *              md;
    MQUE *              p;
    int                 flag = 0;
    int                 disconacts;
    int                 x;
    int                 y;
    struct pollfd *     fdp;
    struct strrecvfd    recbuf;
#if defined(NOCONNLD)
    struct strbuf       ctl;
    char                cbuff[MSGMAX];
#endif

#if defined(NOCONNLD)
    /*
    **  Set up buffer for receiving messages.
    */
    ctl.buf = cbuff;
    ctl.maxlen = sizeof (cbuff);
#endif

    /*
    **  This loop exists to return control to poll after the
    **  result of poll yeilds no new connections or serviceable
    **  messages.
    */
    for (;;)
    {
        /*
        **      If there are no unserviced events pending, call poll(2)
        **      and wait for a message or connection.
        **      NumEvents may be -1 in the event of an interrupt, hence
        **      <= 0
        */
        if (NumEvents <= 0)
        {
            /*
            **  Add new connections, if any, reset connection counter
            */
            NumCons += NumNewCons;
            NumNewCons = 0;

            if (NumCons <= 0)
            {
                errno = EINTR;
                return(NULL);
            }

            /*
            **  Scan the connection table and remove any holes
            */
            for (x = 0; x < NumCons; x++)
            {
                mdp = Connections[x];

                /*
                **      Disconnected, clear the node and compress the
                **      tables.  If the disconnect called any
                **      on_discon functions (disconacts > 0), return
                **      because there may be something to clean up.
                **      Finally, decrement <x> so that the next node
                **      doesn't get missed.
                */
                if (mdp->readfd == -1)
                {
                    disconacts = mdisconnect(mdp);
                    NumCons--;
                    for (y = x; y < (NumCons + NumNewCons); y++)
                    {
                        Connections[y] = Connections[y + 1];
                        PollFdList[y] = PollFdList[y + 1];
                    }
                    if (disconacts > 0)
                    {
                        errno = EINTR;
                        return(NULL);
                    }
                    else
                        x--;
                } else {
                    /*
                     * We are in "mlisten", POLLIN is always set.  We'll look
                     * at POLLOUT possibility when mque is non-NULL.
                     */
                    PollFdList[x].events = POLLIN;
                    if (mdp->mque)
                        PollFdList[x].events |= POLLOUT;
                }
            }

            /*
            **  Wait for a message or a connection.
            **  This call may be interrupted by alarms used
            **  elsewhere, so if poll fails, return NULL and
            **  set errno to EAGAIN.
            */
            if ((NumEvents = poll(PollFdList, NumCons, -1)) < 0)
            {
                errno = EAGAIN;
                return(NULL);
            }
        }

        for (x = 0; x < NumCons; x++)
        {
            mdp = Connections[x];
            fdp = PollFdList + x;

            if (fdp->revents == 0)
                continue;

            switch (mdp->type) {
            case MD_MASTER:
                /*
                **      Only valid revent is: POLLIN
                */
                if (fdp->revents != POLLIN)
                {
                    errno = EINVAL;
                    return(NULL);
                }

                /*
                **      Retrieve the file descriptor
                */
                if (ioctl(mdp->readfd, I_RECVFD, &recbuf) != 0)
                {
                    if (errno == EINTR)
                    {
                        errno = EAGAIN;
                        return(NULL);
                    }
                    if (errno == ENXIO)
                    {
                        fdp->revents = 0;
                        NumEvents--;
                        continue;
                    }
#if defined(NOCONNLD)
                    if (errno == EBADMSG)
                        while (Getmsg(mdp, &ctl, &ctl, &flag) >= 0);
#endif
                    return(NULL);
                }

                TURN_ON(recbuf.fd, O_NDELAY);
                /*
                **      Now, create the message descriptor
                **      and populate it with what we know.
                */
                if ((md = (MESG *)Malloc(MDSIZE)) == NULL)
                {
                    errno = ENOMEM;
                    return(NULL);
                }

                memset(md, 0, sizeof (MESG));
                md->gid = recbuf.gid;
                md->readfd = md->writefd = recbuf.fd;
                md->state = MDS_IDLE;
                md->type = MD_UNKNOWN;
                md->uid = recbuf.uid;

                /*
                 * Determine if a print administrator is contacting lpsched.
                 * currently, root, lp and users with the "solaris.print.admin"
                 * privilege are print administrators
                 */
                md->admin = (md->uid == 0 || md->uid == Lp_Uid);
                if (md->admin == 0) {
                        struct passwd *pw = NULL;

                        if ((pw = getpwuid(md->uid)) != NULL)
                                md->admin = chkauthattr("solaris.print.admin",
                                                        pw->pw_name);
                }

                get_peer_label(md->readfd, &md->slabel);

                if (mlistenadd(md, POLLIN) != 0)
                    return(NULL);

                ResetFifoBuffer (md->readfd);
                /*
                **      Reset fdp because mlistenadd may have
                **      realloc()ed PollFdList and changed its
                **      physical location.
                */
                fdp = PollFdList + x;

                /*
                **      Clear the event that brought us here,
                **      decrement the event counter, and get the
                **      next event.
                */
                fdp->revents = 0;
                NumEvents--;
                break;

            case MD_CHILD:
                /*
                **      If this connection is a child process, just
                **      save the event and return the message descriptor
                */

                if (fdp->revents & POLLOUT) {
                        if (mdp->mque) {
                                if (mflush(mdp) < 0) {
                                        syslog(LOG_DEBUG,
                                                "MD_CHILD mflush failed");
                                }
                        }
                }

                if (fdp->revents & POLLIN) {
                        mdp->event = fdp->revents;
                        NumEvents--;
                        fdp->revents = 0;
                        return (mdp);           /* we are in listening mode */
                }

                NumEvents--;
                fdp->revents = 0;
                break;

            default:
                    /*
                    **  POLLNVAL means this client disconnected and
                    **  all messages have been processed.
                    */
                    if (fdp->revents & POLLNVAL) /* disconnected & no msg */
                    {
                        if (mdp->readfd >= 0) {
                                Close (mdp->readfd);
                                if (mdp->writefd == mdp->readfd)
                                        mdp->writefd = -1;
                                mdp->readfd = -1;
                        }
                        fdp->revents = 0;
                        NumEvents--;
                        continue;
                    }

                    /*
                    **  POLLERR means an error message is on the
                    **  stream.  Since this is totally unexpected,
                    **  the assumption is made that this stream will
                    **  be flagged POLLNVAL next time through poll
                    **  and will be removed at that time.
                    */
                    if (fdp->revents & POLLERR) /* uh, oh! */
                    {
                        if (mdp->readfd >= 0) {
                                Close (mdp->readfd);
                                if (mdp->writefd == mdp->readfd)
                                        mdp->writefd = -1;
                                mdp->readfd = -1;
                        }
                        NumEvents--;
                        fdp->revents = 0;
                        continue;
                    }


                    /*
                    **  POLLHUP means the client aborted the call.
                    **  The client is not removed, because there may
                    **  still be messages on the stream.
                    */
                    if (fdp->revents & POLLHUP) /* disconnected */
                    {
                        NumEvents--;
                        fdp->revents = 0;
                        /*
                         * MORE: This is odd. Why are we closing the
                         * stream if there ``may still be messages''???
                         */
                        if (mdp->readfd >= 0) {
                                Close (mdp->readfd);
                                if (mdp->writefd == mdp->readfd)
                                        mdp->writefd = -1;
                                mdp->readfd = -1;
                        }
                        continue;

                        /*
                         * MORE: Why is this here??
                         *
                        if (mdp->type == MD_SYS_FIFO)
                            (void) Close(mdp->writefd);

                        mdp->writefd = -1;

                        if (fdp->revents == POLLHUP)
                        {
                            NumEvents--;
                            fdp->revents = 0;
                            (void) Close(mdp->readfd);
                            mdp->readfd = -1;
                            continue;
                        }
                         *
                         */
                    }
                    /*
                    **  POLLOUT means that the client had a full
                    **  stream and messages became backlogged and
                    **  now the stream is empty.  So the queued msgs
                    **  are sent with putmsg(2)
                    */
                    if (fdp->revents & POLLOUT)
                    {
                        if (mdp->mque == NULL)
                        {
                            NumEvents--;
                            fdp->revents = 0;
                            continue;
                        }
                        while (mdp->mque) {
                            if (Putmsg(mdp, NULL, mdp->mque->dat, 0))
                                break;  /* failed for some reason */
                            p = mdp->mque;
                            mdp->mque = p->next;
                            Free(p->dat->buf);
                            Free(p->dat);
                            Free(p);
                        }
                        NumEvents--;
                        fdp->revents = 0;
                        continue;
                    }

                    /*
                    **  POLLIN means that there is a message on the
                    **  stream.
                    **  Return the message descriptor to the caller
                    **  so that the message may be received and
                    **  processed.
                    */
                    if (fdp->revents & POLLIN)  /* got a message */
                    {
                        NumEvents--;
                        mdp->event = fdp->revents;
                        fdp->revents = 0;
                        if (mdp->type == MD_UNKNOWN)
                            mdp->type = MD_STREAM;
                        return(mdp);
                    }
                    break;
            }
        }
    }
}

# define        VOID_FUNC_PTR           void (*)()
# define        PTR_TO_VOID_FUNC_PTR    void (**)()

int
mon_discon(MESG * md, void (*fn)())
{
    int         size = 2;
    void        (**fnp) ();

    if (md->on_discon)
    {
        for (fnp = md->on_discon; *fnp; fnp++)
            size++;
        if ((md->on_discon = (PTR_TO_VOID_FUNC_PTR) Realloc (md->on_discon, size * sizeof(VOID_FUNC_PTR))) == NULL)
        {
            errno = ENOMEM;
            return(-1);
        }
    }
    else
        if ((md->on_discon = (PTR_TO_VOID_FUNC_PTR) Malloc (size * sizeof(VOID_FUNC_PTR))) == NULL)
        {
            errno = ENOMEM;
            return(-1);
        }

    size--;
    md->on_discon[size] = NULL;
    size--;
    md->on_discon[size] = fn;
    return(0);
}