/*
 * BIGFONT extension for sharing font metrics between clients (if possible)
 * and for transmitting font metrics to clients in a compressed form.
 *
 * Copyright (c) 1999-2000  Bruno Haible
 * Copyright (c) 1999-2000  The XFree86 Project, Inc.
 */

/* THIS IS NOT AN X CONSORTIUM STANDARD */

/*
 * Big fonts suffer from the following: All clients that have opened a
 * font can access the complete glyph metrics array (the XFontStruct member
 * `per_char') directly, without going through a macro. Moreover these
 * glyph metrics are ink metrics, i.e. are not redundant even for a
 * fixed-width font. For a Unicode font, the size of this array is 768 KB.
 *
 * Problems: 1. It eats a lot of memory in each client. 2. All this glyph
 * metrics data is piped through the socket when the font is opened.
 *
 * This extension addresses these two problems for local clients, by using
 * shared memory. It also addresses the second problem for non-local clients,
 * by compressing the data before transmit by a factor of nearly 6.
 *
 * If you use this extension, your OS ought to nicely support shared memory.
 * This means: Shared memory should be swappable to the swap, and the limits
 * should be high enough (SHMMNI at least 64, SHMMAX at least 768 KB,
 * SHMALL at least 48 MB). It is a plus if your OS allows shmat() calls
 * on segments that have already been marked "removed", because it permits
 * these segments to be cleaned up by the OS if the X server is killed with
 * signal SIGKILL.
 *
 * This extension is transparently exploited by Xlib (functions XQueryFont,
 * XLoadQueryFont).
 */

#ifdef HAVE_DIX_CONFIG_H
#include <dix-config.h>
#endif

#include <sys/types.h>
#ifdef HAS_SHM
#ifdef SVR4
#include <sys/sysmacros.h>
#endif
#if defined(__CYGWIN__)
#include <sys/param.h>
#include <sys/sysmacros.h>
#endif
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#endif

#include <X11/X.h>
#include <X11/Xproto.h>
#include <X11/fonts/fontstruct.h>
#include <X11/fonts/libxfont2.h>

#include "misc.h"
#include "os.h"
#include "dixstruct.h"
#include "gcstruct.h"
#include "dixfontstr.h"
#include "extnsionst.h"
#include "extinit.h"
#include "protocol-versions.h"

#include <X11/extensions/xf86bigfproto.h>
#include "xf86bigfontsrv.h"

static void XF86BigfontResetProc(ExtensionEntry *       /* extEntry */
    );

#ifdef HAS_SHM

/* A random signature, transmitted to the clients so they can verify that the
   shared memory segment they are attaching to was really established by the
   X server they are talking to. */
static CARD32 signature;

/* Index for additional information stored in a FontRec's devPrivates array. */
static int FontShmdescIndex;

static unsigned int pagesize;

static Bool badSysCall = FALSE;

#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__CYGWIN__) || defined(__DragonFly__)

static void
SigSysHandler(int signo)
{
    badSysCall = TRUE;
}

static Bool
CheckForShmSyscall(void)
{
    void (*oldHandler) (int);
    int shmid = -1;

    /* If no SHM support in the kernel, the bad syscall will generate SIGSYS */
    oldHandler = OsSignal(SIGSYS, SigSysHandler);

    badSysCall = FALSE;
    shmid = shmget(IPC_PRIVATE, 4096, IPC_CREAT);
    if (shmid != -1) {
        /* Successful allocation - clean up */
        shmctl(shmid, IPC_RMID, NULL);
    }
    else {
        /* Allocation failed */
        badSysCall = TRUE;
    }
    OsSignal(SIGSYS, oldHandler);
    return !badSysCall;
}

#define MUST_CHECK_FOR_SHM_SYSCALL

#endif

#endif

/* ========== Management of shared memory segments ========== */

#ifdef HAS_SHM

#ifdef __linux__
/* On Linux, shared memory marked as "removed" can still be attached.
   Nice feature, because the kernel will automatically free the associated
   storage when the server and all clients are gone. */
#define EARLY_REMOVE
#endif

typedef struct _ShmDesc {
    struct _ShmDesc *next;
    struct _ShmDesc **prev;
    int shmid;
    char *attach_addr;
} ShmDescRec, *ShmDescPtr;

static ShmDescPtr ShmList = (ShmDescPtr) NULL;

static ShmDescPtr
shmalloc(unsigned int size)
{
    ShmDescPtr pDesc;
    int shmid;
    char *addr;

#ifdef MUST_CHECK_FOR_SHM_SYSCALL
    if (pagesize == 0)
        return (ShmDescPtr) NULL;
#endif

    /* On some older Linux systems, the number of shared memory segments
       system-wide is 127. In Linux 2.4, it is 4095.
       Therefore there is a tradeoff to be made between allocating a
       shared memory segment on one hand, and allocating memory and piping
       the glyph metrics on the other hand. If the glyph metrics size is
       small, we prefer the traditional way. */
    if (size < 3500)
        return (ShmDescPtr) NULL;

    pDesc = malloc(sizeof(ShmDescRec));
    if (!pDesc)
        return (ShmDescPtr) NULL;

    size = (size + pagesize - 1) & -pagesize;
    shmid = shmget(IPC_PRIVATE, size, S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH);
    if (shmid == -1) {
        ErrorF(XF86BIGFONTNAME " extension: shmget() failed, size = %u, %s\n",
               size, strerror(errno));
        free(pDesc);
        return (ShmDescPtr) NULL;
    }

    if ((addr = shmat(shmid, 0, 0)) == (char *) -1) {
        ErrorF(XF86BIGFONTNAME " extension: shmat() failed, size = %u, %s\n",
               size, strerror(errno));
        shmctl(shmid, IPC_RMID, (void *) 0);
        free(pDesc);
        return (ShmDescPtr) NULL;
    }

#ifdef EARLY_REMOVE
    shmctl(shmid, IPC_RMID, (void *) 0);
#endif

    pDesc->shmid = shmid;
    pDesc->attach_addr = addr;
    if (ShmList)
        ShmList->prev = &pDesc->next;
    pDesc->next = ShmList;
    pDesc->prev = &ShmList;
    ShmList = pDesc;

    return pDesc;
}

static void
shmdealloc(ShmDescPtr pDesc)
{
#ifndef EARLY_REMOVE
    shmctl(pDesc->shmid, IPC_RMID, (void *) 0);
#endif
    shmdt(pDesc->attach_addr);

    if (pDesc->next)
        pDesc->next->prev = pDesc->prev;
    *pDesc->prev = pDesc->next;
    free(pDesc);
}

#endif

/* Called when a font is closed. */
void
XF86BigfontFreeFontShm(FontPtr pFont)
{
#ifdef HAS_SHM
    ShmDescPtr pDesc;

    /* If during shutdown of the server, XF86BigfontCleanup() has already
     * called shmdealloc() for all segments, we don't need to do it here.
     */
    if (!ShmList)
        return;

    pDesc = (ShmDescPtr) FontGetPrivate(pFont, FontShmdescIndex);
    if (pDesc)
        shmdealloc(pDesc);
#endif
}

/* Called upon fatal signal. */
void
XF86BigfontCleanup(void)
{
#ifdef HAS_SHM
    while (ShmList)
        shmdealloc(ShmList);
#endif
}

/* Called when a server generation dies. */
static void
XF86BigfontResetProc(ExtensionEntry * extEntry)
{
    /* This function is normally called from CloseDownExtensions(), called
     * from main(). It will be followed by a call to FreeAllResources(),
     * which will call XF86BigfontFreeFontShm() for each font. Thus it
     * appears that we do not need to do anything in this function. --
     * But I prefer to write robust code, and not keep shared memory lying
     * around when it's not needed any more. (Someone might close down the
     * extension without calling FreeAllResources()...)
     */
    XF86BigfontCleanup();
}

/* ========== Handling of extension specific requests ========== */

static int
ProcXF86BigfontQueryVersion(ClientPtr client)
{
    xXF86BigfontQueryVersionReply reply;

    REQUEST_SIZE_MATCH(xXF86BigfontQueryVersionReq);
    reply = (xXF86BigfontQueryVersionReply) {
        .type = X_Reply,
        .sequenceNumber = client->sequence,
        .length = 0,
        .majorVersion = SERVER_XF86BIGFONT_MAJOR_VERSION,
        .minorVersion = SERVER_XF86BIGFONT_MINOR_VERSION,
        .uid = geteuid(),
        .gid = getegid(),
#ifdef HAS_SHM
        .signature = signature,
        .capabilities = (client->local && !client->swapped)
                         ? XF86Bigfont_CAP_LocalShm : 0
#else
        .signature = 0,
        .capabilities = 0
#endif
    };
    if (client->swapped) {
        swaps(&reply.sequenceNumber);
        swapl(&reply.length);
        swaps(&reply.majorVersion);
        swaps(&reply.minorVersion);
        swapl(&reply.uid);
        swapl(&reply.gid);
        swapl(&reply.signature);
    }
    WriteToClient(client, sizeof(xXF86BigfontQueryVersionReply), &reply);
    return Success;
}

static void
swapCharInfo(xCharInfo * pCI)
{
    swaps(&pCI->leftSideBearing);
    swaps(&pCI->rightSideBearing);
    swaps(&pCI->characterWidth);
    swaps(&pCI->ascent);
    swaps(&pCI->descent);
    swaps(&pCI->attributes);
}

/* static CARD32 hashCI (xCharInfo *p); */
#define hashCI(p) \
	(CARD32)(((p->leftSideBearing << 27) + (p->leftSideBearing >> 5) + \
	          (p->rightSideBearing << 23) + (p->rightSideBearing >> 9) + \
	          (p->characterWidth << 16) + \
	          (p->ascent << 11) + (p->descent << 6)) ^ p->attributes)

static int
ProcXF86BigfontQueryFont(ClientPtr client)
{
    FontPtr pFont;

    REQUEST(xXF86BigfontQueryFontReq);
    CARD32 stuff_flags;
    xCharInfo *pmax;
    xCharInfo *pmin;
    int nCharInfos;
    int shmid;

#ifdef HAS_SHM
    ShmDescPtr pDesc = NULL;
#else
#define pDesc 0
#endif
    xCharInfo *pCI;
    CARD16 *pIndex2UniqIndex;
    CARD16 *pUniqIndex2Index;
    CARD32 nUniqCharInfos;

#if 0
    REQUEST_SIZE_MATCH(xXF86BigfontQueryFontReq);
#else
    switch (client->req_len) {
    case 2:                    /* client with version 1.0 libX11 */
        stuff_flags = (client->local &&
                       !client->swapped ? XF86Bigfont_FLAGS_Shm : 0);
        break;
    case 3:                    /* client with version 1.1 libX11 */
        stuff_flags = stuff->flags;
        break;
    default:
        return BadLength;
    }
#endif
    if (dixLookupFontable(&pFont, stuff->id, client, DixGetAttrAccess) !=
        Success)
        return BadFont;         /* procotol spec says only error is BadFont */

    pmax = FONTINKMAX(pFont);
    pmin = FONTINKMIN(pFont);
    nCharInfos =
        (pmax->rightSideBearing == pmin->rightSideBearing
         && pmax->leftSideBearing == pmin->leftSideBearing
         && pmax->descent == pmin->descent
         && pmax->ascent == pmin->ascent
         && pmax->characterWidth == pmin->characterWidth)
        ? 0 : N2dChars(pFont);
    shmid = -1;
    pCI = NULL;
    pIndex2UniqIndex = NULL;
    pUniqIndex2Index = NULL;
    nUniqCharInfos = 0;

    if (nCharInfos > 0) {
#ifdef HAS_SHM
        if (!badSysCall)
            pDesc = (ShmDescPtr) FontGetPrivate(pFont, FontShmdescIndex);
        if (pDesc) {
            pCI = (xCharInfo *) pDesc->attach_addr;
            if (stuff_flags & XF86Bigfont_FLAGS_Shm)
                shmid = pDesc->shmid;
        }
        else {
            if (stuff_flags & XF86Bigfont_FLAGS_Shm && !badSysCall)
                pDesc = shmalloc(nCharInfos * sizeof(xCharInfo)
                                 + sizeof(CARD32));
            if (pDesc) {
                pCI = (xCharInfo *) pDesc->attach_addr;
                shmid = pDesc->shmid;
            }
            else {
#endif
                pCI = xallocarray(nCharInfos, sizeof(xCharInfo));
                if (!pCI)
                    return BadAlloc;
#ifdef HAS_SHM
            }
#endif
            /* Fill nCharInfos starting at pCI. */
            {
                xCharInfo *prCI = pCI;
                int ninfos = 0;
                int ncols = pFont->info.lastCol - pFont->info.firstCol + 1;
                int row;

                for (row = pFont->info.firstRow;
                     row <= pFont->info.lastRow && ninfos < nCharInfos; row++) {
                    unsigned char chars[512];
                    xCharInfo *tmpCharInfos[256];
                    unsigned long count;
                    int col;
                    unsigned long i;

                    i = 0;
                    for (col = pFont->info.firstCol;
                         col <= pFont->info.lastCol; col++) {
                        chars[i++] = row;
                        chars[i++] = col;
                    }
                    (*pFont->get_metrics) (pFont, ncols, chars, TwoD16Bit,
                                           &count, tmpCharInfos);
                    for (i = 0; i < count && ninfos < nCharInfos; i++) {
                        *prCI++ = *tmpCharInfos[i];
                        ninfos++;
                    }
                }
            }
#ifdef HAS_SHM
            if (pDesc && !badSysCall) {
                *(CARD32 *) (pCI + nCharInfos) = signature;
                if (!xfont2_font_set_private(pFont, FontShmdescIndex, pDesc)) {
                    shmdealloc(pDesc);
                    return BadAlloc;
                }
            }
        }
#endif
        if (shmid == -1) {
            /* Cannot use shared memory, so remove-duplicates the xCharInfos
               using a temporary hash table. */
            /* Note that CARD16 is suitable as index type, because
               nCharInfos <= 0x10000. */
            CARD32 hashModulus;
            CARD16 *pHash2UniqIndex;
            CARD16 *pUniqIndex2NextUniqIndex;
            CARD32 NextIndex;
            CARD32 NextUniqIndex;
            CARD16 *tmp;
            CARD32 i, j;

            hashModulus = 67;
            if (hashModulus > nCharInfos + 1)
                hashModulus = nCharInfos + 1;

            tmp = xallocarray(4 * nCharInfos + 1, sizeof(CARD16));
            if (!tmp) {
                if (!pDesc)
                    free(pCI);
                return BadAlloc;
            }
            pIndex2UniqIndex = tmp;
            /* nCharInfos elements */
            pUniqIndex2Index = tmp + nCharInfos;
            /* max. nCharInfos elements */
            pUniqIndex2NextUniqIndex = tmp + 2 * nCharInfos;
            /* max. nCharInfos elements */
            pHash2UniqIndex = tmp + 3 * nCharInfos;
            /* hashModulus (<= nCharInfos+1) elements */

            /* Note that we can use 0xffff as end-of-list indicator, because
               even if nCharInfos = 0x10000, 0xffff can not occur as valid
               entry before the last element has been inserted. And once the
               last element has been inserted, we don't need the hash table
               any more. */
            for (j = 0; j < hashModulus; j++)
                pHash2UniqIndex[j] = (CARD16) (-1);

            NextUniqIndex = 0;
            for (NextIndex = 0; NextIndex < nCharInfos; NextIndex++) {
                xCharInfo *p = &pCI[NextIndex];
                CARD32 hashCode = hashCI(p) % hashModulus;

                for (i = pHash2UniqIndex[hashCode];
                     i != (CARD16) (-1); i = pUniqIndex2NextUniqIndex[i]) {
                    j = pUniqIndex2Index[i];
                    if (pCI[j].leftSideBearing == p->leftSideBearing
                        && pCI[j].rightSideBearing == p->rightSideBearing
                        && pCI[j].characterWidth == p->characterWidth
                        && pCI[j].ascent == p->ascent
                        && pCI[j].descent == p->descent
                        && pCI[j].attributes == p->attributes)
                        break;
                }
                if (i != (CARD16) (-1)) {
                    /* Found *p at Index j, UniqIndex i */
                    pIndex2UniqIndex[NextIndex] = i;
                }
                else {
                    /* Allocate a new entry in the Uniq table */
                    if (hashModulus <= 2 * NextUniqIndex
                        && hashModulus < nCharInfos + 1) {
                        /* Time to increate hash table size */
                        hashModulus = 2 * hashModulus + 1;
                        if (hashModulus > nCharInfos + 1)
                            hashModulus = nCharInfos + 1;
                        for (j = 0; j < hashModulus; j++)
                            pHash2UniqIndex[j] = (CARD16) (-1);
                        for (i = 0; i < NextUniqIndex; i++)
                            pUniqIndex2NextUniqIndex[i] = (CARD16) (-1);
                        for (i = 0; i < NextUniqIndex; i++) {
                            j = pUniqIndex2Index[i];
                            p = &pCI[j];
                            hashCode = hashCI(p) % hashModulus;
                            pUniqIndex2NextUniqIndex[i] =
                                pHash2UniqIndex[hashCode];
                            pHash2UniqIndex[hashCode] = i;
                        }
                        p = &pCI[NextIndex];
                        hashCode = hashCI(p) % hashModulus;
                    }
                    i = NextUniqIndex++;
                    pUniqIndex2NextUniqIndex[i] = pHash2UniqIndex[hashCode];
                    pHash2UniqIndex[hashCode] = i;
                    pUniqIndex2Index[i] = NextIndex;
                    pIndex2UniqIndex[NextIndex] = i;
                }
            }
            nUniqCharInfos = NextUniqIndex;
            /* fprintf(stderr, "font metrics: nCharInfos = %d, nUniqCharInfos = %d, hashModulus = %d\n", nCharInfos, nUniqCharInfos, hashModulus); */
        }
    }

    {
        int nfontprops = pFont->info.nprops;
        int rlength = sizeof(xXF86BigfontQueryFontReply)
            + nfontprops * sizeof(xFontProp)
            + (nCharInfos > 0 && shmid == -1
               ? nUniqCharInfos * sizeof(xCharInfo)
               + (nCharInfos + 1) / 2 * 2 * sizeof(CARD16)
               : 0);
        xXF86BigfontQueryFontReply *reply = calloc(1, rlength);
        char *p;

        if (!reply) {
            if (nCharInfos > 0) {
                if (shmid == -1)
                    free(pIndex2UniqIndex);
                if (!pDesc)
                    free(pCI);
            }
            return BadAlloc;
        }
        reply->type = X_Reply;
        reply->length = bytes_to_int32(rlength - sizeof(xGenericReply));
        reply->sequenceNumber = client->sequence;
        reply->minBounds = pFont->info.ink_minbounds;
        reply->maxBounds = pFont->info.ink_maxbounds;
        reply->minCharOrByte2 = pFont->info.firstCol;
        reply->maxCharOrByte2 = pFont->info.lastCol;
        reply->defaultChar = pFont->info.defaultCh;
        reply->nFontProps = pFont->info.nprops;
        reply->drawDirection = pFont->info.drawDirection;
        reply->minByte1 = pFont->info.firstRow;
        reply->maxByte1 = pFont->info.lastRow;
        reply->allCharsExist = pFont->info.allExist;
        reply->fontAscent = pFont->info.fontAscent;
        reply->fontDescent = pFont->info.fontDescent;
        reply->nCharInfos = nCharInfos;
        reply->nUniqCharInfos = nUniqCharInfos;
        reply->shmid = shmid;
        reply->shmsegoffset = 0;
        if (client->swapped) {
            swaps(&reply->sequenceNumber);
            swapl(&reply->length);
            swapCharInfo(&reply->minBounds);
            swapCharInfo(&reply->maxBounds);
            swaps(&reply->minCharOrByte2);
            swaps(&reply->maxCharOrByte2);
            swaps(&reply->defaultChar);
            swaps(&reply->nFontProps);
            swaps(&reply->fontAscent);
            swaps(&reply->fontDescent);
            swapl(&reply->nCharInfos);
            swapl(&reply->nUniqCharInfos);
            swapl(&reply->shmid);
            swapl(&reply->shmsegoffset);
        }
        p = (char *) &reply[1];
        {
            FontPropPtr pFP;
            xFontProp *prFP;
            int i;

            for (i = 0, pFP = pFont->info.props, prFP = (xFontProp *) p;
                 i < nfontprops; i++, pFP++, prFP++) {
                prFP->name = pFP->name;
                prFP->value = pFP->value;
                if (client->swapped) {
                    swapl(&prFP->name);
                    swapl(&prFP->value);
                }
            }
            p = (char *) prFP;
        }
        if (nCharInfos > 0 && shmid == -1) {
            xCharInfo *pci;
            CARD16 *ps;
            int i, j;

            pci = (xCharInfo *) p;
            for (i = 0; i < nUniqCharInfos; i++, pci++) {
                *pci = pCI[pUniqIndex2Index[i]];
                if (client->swapped)
                    swapCharInfo(pci);
            }
            ps = (CARD16 *) pci;
            for (j = 0; j < nCharInfos; j++, ps++) {
                *ps = pIndex2UniqIndex[j];
                if (client->swapped) {
                    swaps(ps);
                }
            }
        }
        WriteToClient(client, rlength, reply);
        free(reply);
        if (nCharInfos > 0) {
            if (shmid == -1)
                free(pIndex2UniqIndex);
            if (!pDesc)
                free(pCI);
        }
        return Success;
    }
}

static int
ProcXF86BigfontDispatch(ClientPtr client)
{
    REQUEST(xReq);

    switch (stuff->data) {
    case X_XF86BigfontQueryVersion:
        return ProcXF86BigfontQueryVersion(client);
    case X_XF86BigfontQueryFont:
        return ProcXF86BigfontQueryFont(client);
    default:
        return BadRequest;
    }
}

static int _X_COLD
SProcXF86BigfontQueryVersion(ClientPtr client)
{
    REQUEST(xXF86BigfontQueryVersionReq);

    swaps(&stuff->length);
    return ProcXF86BigfontQueryVersion(client);
}

static int _X_COLD
SProcXF86BigfontQueryFont(ClientPtr client)
{
    REQUEST(xXF86BigfontQueryFontReq);

    swaps(&stuff->length);
    REQUEST_SIZE_MATCH(xXF86BigfontQueryFontReq);
    swapl(&stuff->id);
    return ProcXF86BigfontQueryFont(client);
}

static int _X_COLD
SProcXF86BigfontDispatch(ClientPtr client)
{
    REQUEST(xReq);

    switch (stuff->data) {
    case X_XF86BigfontQueryVersion:
        return SProcXF86BigfontQueryVersion(client);
    case X_XF86BigfontQueryFont:
        return SProcXF86BigfontQueryFont(client);
    default:
        return BadRequest;
    }
}

void
XFree86BigfontExtensionInit(void)
{
    if (AddExtension(XF86BIGFONTNAME,
                     XF86BigfontNumberEvents,
                     XF86BigfontNumberErrors,
                     ProcXF86BigfontDispatch,
                     SProcXF86BigfontDispatch,
                     XF86BigfontResetProc, StandardMinorOpcode)) {
#ifdef HAS_SHM
#ifdef MUST_CHECK_FOR_SHM_SYSCALL
        /*
         * Note: Local-clients will not be optimized without shared memory
         * support. Remote-client optimization does not depend on shared
         * memory support.  Thus, the extension is still registered even
         * when shared memory support is not functional.
         */
        if (!CheckForShmSyscall()) {
            ErrorF(XF86BIGFONTNAME
                   " extension local-client optimization disabled due to lack of shared memory support in the kernel\n");
            return;
        }
#endif

        srand((unsigned int) time(NULL));
        signature = ((unsigned int) (65536.0 / (RAND_MAX + 1.0) * rand()) << 16)
            + (unsigned int) (65536.0 / (RAND_MAX + 1.0) * rand());
        /* fprintf(stderr, "signature = 0x%08X\n", signature); */

        FontShmdescIndex = xfont2_allocate_font_private_index();

#if !defined(CSRG_BASED) && !defined(__CYGWIN__)
        pagesize = SHMLBA;
#else
#ifdef _SC_PAGESIZE
        pagesize = sysconf(_SC_PAGESIZE);
#else
        pagesize = getpagesize();
#endif
#endif
#endif
    }
}
