root/usr/src/cmd/lp/lib/msgs/read_fifo.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (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 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

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

/* LINTLIBRARY */

# include       <errno.h>
# include       <string.h>
#include <syslog.h>

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

extern char     Resync[];
extern char     Endsync[];
static int      Had_Full_Buffer = 1;
int             Garbage_Bytes   = 0;
int             Garbage_Messages= 0;

static int _buffer(int);

/*
** A real message is written in one piece, and the write
** is atomic. Thus, even if the O_NDELAY flag is set,
** if we read part of the real message, we can continue
** to read the rest of it in as many steps as we want
** (up to the size of the message, of course!) without
** UNIX returning 0 because no data is available.
** So, a real message doesn't have to be read in one piece,
** which is good since we don't know how much to read!
**
** Fake messages, or improperly written messages, don't
** have this nice property.
**
** INTERRUPTED READS:
**
** If a signal occurs during an attempted read, we can exit.
** The caller can retry the read and we will correctly restart
** it. The correctness of this assertion can be seen by noticing
** that at the beginning of each READ below, we can go back
** to the first statement executed (the first READ below)
** and correctly reexecute the code.
**
** If the last writer closed the fifo, we'll read 0 bytes
** (at least on the subsequent read). If we were in the
** middle of reading a message, we were reading a bogus
** message (but see below).
**
** If we read less than we expect, it's because we were
** reading a fake message (but see below).
**
** HOWEVER: In the last two cases, we may have ONE OR MORE
** REAL MESSAGES snuggled in amongst the trash!
**
** All this verbal rambling is preface to let you understand why we
** buffer the data (which is a shame, but necessary).
*/

/*
** As long as we get real messages, we can avoid needless function calls.
** The SYNC argument in this macro should be set if the resynch. bytes
** have been read--i.e. if the rest of the message is trying to be read.
** In this case, if we had not read a full buffer last time, then we
** must be in the middle of a bogus message.
*/

#define UNSYNCHED_READ(N) \
    if (fbp->psave_end - fbp->psave < N || fbp->psave >= fbp->psave_end) \
    { \
        switch (_buffer(fifo)) \
        { \
            case -1: \
                return (-1); \
            case 0: \
                if (fbp->psave_end > fbp->psave) \
                    goto SyncUp; \
                return (0); \
        } \
    }

#define SYNCHED_READ(N) \
    if (fbp->psave_end - fbp->psave < N || fbp->psave >= fbp->psave_end) \
    { \
        switch (_buffer(fifo)) \
        { \
            case -1: \
                return (-1); \
            case 0: \
                if (fbp->psave_end > fbp->psave) \
                    goto SyncUp; \
                return (0); \
        } \
        if (!Had_Full_Buffer) \
            goto SyncUp; \
    }

/*
** read_fifo() - READ A BUFFER WITH HEADER AND CHECKSUM
*/
int
read_fifo (fifo, buf, size)
int             fifo;
char            *buf;
unsigned int    size;
{
    register fifobuffer_t *fbp;
    register unsigned int real_chksum,
                          chksum,
                          real_size;

    /*
    ** Make sure we start on a message boundary. The first
    ** line of defense is to look for the resync. bytes.
    **
    ** The "SyncUp" label is global to this routine (below this point)
    ** and is called whenever we determine that we're out
    ** of sync. with the incoming bytes.
    */

    if (!(fbp=GetFifoBuffer (fifo)))
        return  -1;

    UNSYNCHED_READ (HEAD_RESYNC_LEN);
    while (*fbp->psave != Resync[0] || *(fbp->psave + 1) != Resync[1])
    {
SyncUp:
#if     defined(TRACE_MESSAGES)
        if (trace_messages)
                syslog(LOG_DEBUG, "DISCARD %c\n", *fbp->psave);
#endif
        fbp->psave++;
        Garbage_Bytes++;
        UNSYNCHED_READ (HEAD_RESYNC_LEN);
    }


    /*
    ** We're sync'd, so read the full header.
    */

    SYNCHED_READ (HEAD_LEN);


    /*
    ** If the header size is smaller than the minimum size for a header,
    ** or larger than allowed, we must assume that we really aren't
    ** synchronized.
    */

    real_size = stoh(fbp->psave + HEAD_SIZE);
    if (real_size < CONTROL_LEN || MSGMAX < real_size)
    {
#if     defined(TRACE_MESSAGES)
        if (trace_messages)
                syslog(LOG_DEBUG, "BAD SIZE\n");
#endif
        goto SyncUp;
    }

    /*
    ** We have the header. Now we can finally read the rest of the
    ** message...
    */

    SYNCHED_READ (real_size);


    /*
    ** ...but did we read a real message?...
    */

    if
    (
           *(fbp->psave + TAIL_ENDSYNC(real_size)) != Endsync[0]
        || *(fbp->psave + TAIL_ENDSYNC(real_size) + 1) != Endsync[1]
    )
    {
#if     defined(TRACE_MESSAGES)
        if (trace_messages)
                syslog(LOG_DEBUG, "BAD ENDSYNC\n");
#endif
        Garbage_Messages++;
        goto SyncUp;
    }

    chksum = stoh(fbp->psave + TAIL_CHKSUM(real_size));
    CALC_CHKSUM (fbp->psave, real_size, real_chksum);
    if (real_chksum != chksum)
    {
#if     defined(TRACE_MESSAGES)
        if (trace_messages)
                syslog(LOG_DEBUG, "BAD CHKSUM\n");
#endif
        Garbage_Messages++;
        goto SyncUp;
    }

    /*
    ** ...yes!...but can the caller handle the message?
    */

    if (size < real_size)
    {
        errno = E2BIG;
        return (-1);
    }


    /*
    ** Yes!! We can finally copy the message into the caller's buffer
    ** and remove it from our buffer. That wasn't so bad, was it?
    */

#if     defined(TRACE_MESSAGES)
    if (trace_messages)
        syslog(LOG_DEBUG, "MESSAGE: %-.*s", real_size, fbp->psave);
#endif
    (void)memcpy (buf, fbp->psave, real_size);
    fbp->psave += real_size;
    return (real_size);
}

int
peek3_2 (fifo)
int             fifo;
{
    register fifobuffer_t       *fbp;
    register unsigned int       real_size;

    /*
    ** Make sure we start on a message boundary. The first
    ** line of defense is to look for the resync. bytes.
    **
    ** The "SyncUp" label is global to this routine (below this point)
    ** and is called whenever we determine that we're out
    ** of sync. with the incoming bytes.
    */

    if (!(fbp=GetFifoBuffer (fifo)))
        return  -1;
    UNSYNCHED_READ (HEAD_RESYNC_LEN);
    while (*fbp->psave != Resync[0] || *(fbp->psave + 1) != Resync[1])
    {
SyncUp:
        fbp->psave++;
        Garbage_Bytes++;
        UNSYNCHED_READ (HEAD_RESYNC_LEN);
    }


    /*
    ** We're sync'd, so read the full header.
    */

    SYNCHED_READ (HEAD_LEN);


    /*
    ** If the header size is smaller than the minimum size for a header,
    ** or larger than allowed, we must assume that we really aren't
    ** synchronized.
    */

    real_size = stoh(fbp->psave + HEAD_SIZE);
    if (real_size < CONTROL_LEN || MSGMAX < real_size)
    {
        goto SyncUp;
    }

    return(real_size);
}

static int
_buffer(int fifo)
{
             int           n, nbytes, count = 0;
    register fifobuffer_t  *fbp;

    /*
    ** As long as we get real messages, and if we chose
    ** SAVE_SIZE well, we shouldn't have to move the data
    ** in the "else" branch below: Each time we call "read"
    ** we aren't likely to get as many bytes as we ask for,
    ** just as many as are in the fifo, AND THIS SHOULD
    ** REPRESENT AN INTEGRAL NUMBER OF MESSAGES. Since
    ** the "read_fifo" routine reads complete messages,
    ** it will end its read at the end of the message,
    ** which (eventually) will make "psave_end" == "psave".
    */

    /*
    ** If the buffer is empty, there's nothing to move.
    */
    if (!(fbp = GetFifoBuffer (fifo)))
        return  -1;
    if (fbp->psave_end == fbp->psave)
        fbp->psave = fbp->psave_end = fbp->save;        /* sane pointers! */

    /*
    ** If the buffer has data at the high end, move it down.
    */
    else
    if (fbp->psave != fbp->save)                /* sane pointers! */
    {
        /*
        ** Move the data still left in the buffer to the
        ** front, so we can read as much as possible into
        ** buffer after it.
        */

        memmove(fbp->save, fbp->psave, fbp->psave_end - fbp->psave);

        fbp->psave_end = fbp->save + (fbp->psave_end - fbp->psave);
        fbp->psave = fbp->save; /* sane pointers! */
    }

    /*
    ** The "fbp->psave" and "fbp->psave_end" pointers must be in a sane
    ** state when we get here, in case the "read()" gets interrupted.
    ** When that happens, we return to the caller who may try
    ** to restart us! Sane: fbp->psave == fbp->save (HERE!)
    */

    nbytes = MSGMAX - (fbp->psave_end - fbp->save);

    while ((n = read(fifo, fbp->psave_end, nbytes)) == 0 && count < 60)
    {
        (void)  sleep ((unsigned) 1);
        count++;
    }

    if (n > 0)
        fbp->psave_end += n;

    Had_Full_Buffer = fbp->full;
    fbp->full = (nbytes == n);

    return (n);
}