/* * 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 #endif #include #ifdef HAS_SHM #if defined(linux) && (!defined(__GNU_LIBRARY__) || __GNU_LIBRARY__ < 2) /* libc4 does not define __GNU_LIBRARY__, libc5 defines __GNU_LIBRARY__ as 1 */ /* Linux libc4 and libc5 only (because glibc doesn't include kernel headers): Linux 2.0.x and 2.2.x define SHMLBA as PAGE_SIZE, but forget to define PAGE_SIZE. It is defined in . */ #include #endif #ifdef SVR4 #include #endif #if defined(__CYGWIN__) #include #include #endif #include #include #include #include #include #include #include #endif #include #include #include "misc.h" #include "os.h" #include "dixstruct.h" #include "gcstruct.h" #include "dixfontstr.h" #include "extnsionst.h" #include "protocol-versions.h" #include #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__) #include 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 = signal(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; } signal(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.type = X_Reply; reply.length = 0; reply.sequenceNumber = client->sequence; reply.majorVersion = SERVER_XF86BIGFONT_MAJOR_VERSION; reply.minorVersion = SERVER_XF86BIGFONT_MINOR_VERSION; reply.uid = geteuid(); reply.gid = getegid(); #ifdef HAS_SHM reply.signature = signature; #else reply.signature = 0; /* This is redundant. Avoids uninitialized memory. */ #endif reply.capabilities = #ifdef HAS_SHM (LocalClient(client) && !client->swapped ? XF86Bigfont_CAP_LocalShm : 0) #else 0 #endif ; /* may add more bits here in future versions */ if (client->swapped) { char tmp; 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) { char tmp; 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 = (LocalClient(client) && !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 = malloc(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 (!FontSetPrivate(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 = malloc((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) { char tmp; 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) { char tmp; 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) { char tmp; 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 SProcXF86BigfontQueryVersion(ClientPtr client) { REQUEST(xXF86BigfontQueryVersionReq); char tmp; swaps(&stuff->length); return ProcXF86BigfontQueryVersion(client); } static int SProcXF86BigfontQueryFont(ClientPtr client) { REQUEST(xXF86BigfontQueryFontReq); char tmp; swaps(&stuff->length); REQUEST_SIZE_MATCH(xXF86BigfontQueryFontReq); swapl(&stuff->id); return ProcXF86BigfontQueryFont(client); } static int 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 = AllocateFontPrivateIndex(); #if !defined(CSRG_BASED) && !defined(__CYGWIN__) pagesize = SHMLBA; #else #ifdef _SC_PAGESIZE pagesize = sysconf(_SC_PAGESIZE); #else pagesize = getpagesize(); #endif #endif #endif } }