From 40b49283442698a26dd25e8d6739a0534918585f Mon Sep 17 00:00:00 2001 From: Paulo Zanoni <pzanoni@mandriva.com> Date: Tue, 1 Mar 2011 10:58:16 -0300 Subject: [PATCH 908/908] XKB: cache xkbcomp output for fast start-up v.1 for 1.10.0 The patch stores the cached files inside /usr/share/X11/xkb/compiled. This should save ~0.6 seconds at each boot. This patch is a new version of the patch written by Yan Li and used in Moblin 1.6. Moblin's patch is based on pcpa's patch. pcpa's patch had ~1100 line changes, while this one has ~200 changes, so it should be easier to port to newer versions. I really hope we deprecate this patch soon! (libxkbcommon??) --- xkb/README.compiled | 8 +- xkb/ddxLoad.c | 192 ++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 162 insertions(+), 38 deletions(-) diff --git a/xkb/README.compiled b/xkb/README.compiled index 71caa2f..a4a2ae0 100644 --- a/xkb/README.compiled +++ b/xkb/README.compiled @@ -4,10 +4,10 @@ current keymap and/or any scratch keymaps used by clients. The X server or some other tool might destroy or replace the files in this directory, so it is not a safe place to store compiled keymaps for long periods of time. The default keymap for any server is usually stored in: - X<num>-default.xkm -where <num> is the display number of the server in question, which makes -it possible for several servers *on the same host* to share the same -directory. + server-<SHA1>.xkm + +where <SHA1> is the SHA1 hash of keymap source, so that compiled +keymap of different keymap sources are stored in different files. Unless the X server is modified, sharing this directory between servers on different hosts could cause problems. diff --git a/xkb/ddxLoad.c b/xkb/ddxLoad.c index e102035..4075d31 100644 --- a/xkb/ddxLoad.c +++ b/xkb/ddxLoad.c @@ -30,6 +30,13 @@ THE USE OR PERFORMANCE OF THIS SOFTWARE. #include <xkb-config.h> +#ifdef HAVE_SHA1_IN_LIBMD /* Use libmd for SHA1 */ +# include <sha1.h> +#else /* Use OpenSSL's libcrypto */ +# include <stddef.h> /* buggy openssl/sha.h wants size_t */ +# include <openssl/sha.h> +#endif + #include <stdio.h> #include <ctype.h> #include <X11/X.h> @@ -43,20 +50,9 @@ THE USE OR PERFORMANCE OF THIS SOFTWARE. #define XKBSRV_NEED_FILE_FUNCS #include <xkbsrv.h> #include <X11/extensions/XI.h> +#include <errno.h> #include "xkb.h" - /* - * If XKM_OUTPUT_DIR specifies a path without a leading slash, it is - * relative to the top-level XKB configuration directory. - * Making the server write to a subdirectory of that directory - * requires some work in the general case (install procedure - * has to create links to /var or somesuch on many machines), - * so we just compile into /usr/tmp for now. - */ -#ifndef XKM_OUTPUT_DIR -#define XKM_OUTPUT_DIR "compiled/" -#endif - #define PRE_ERROR_MSG "\"The XKEYBOARD keymap compiler (xkbcomp) reports:\"" #define ERROR_PREFIX "\"> \"" #define POST_ERROR_MSG1 "\"Errors from xkbcomp are not fatal to the X server\"" @@ -166,13 +162,56 @@ OutputDirectory(char *outdir, size_t siz } static Bool +Sha1Asc(char sha1Asc[SHA_DIGEST_LENGTH*2+1], const char * input) +{ + int i; + unsigned char sha1[SHA_DIGEST_LENGTH]; + +#ifdef HAVE_SHA1_IN_LIBMD /* Use libmd for SHA1 */ + SHA1_CTX ctx; + + SHA1Init (&ctx); + SHA1Update (&ctx, input, strlen(input)); + SHA1Final (sha1, &ctx); +#else /* Use OpenSSL's libcrypto */ + SHA_CTX ctx; + int success; + + success = SHA1_Init (&ctx); + if (! success) + return BadAlloc; + + success = SHA1_Update (&ctx, input, strlen(input)); + if (! success) + return BadAlloc; + + success = SHA1_Final (sha1, &ctx); + if (! success) + return BadAlloc; +#endif + + /* convert sha1 to sha1_asc */ + for(i=0; i<SHA_DIGEST_LENGTH; ++i) { + sprintf(sha1Asc+i*2, "%02X", sha1[i]); + } + + return Success; +} + +/* call xkbcomp and compile XKB keymap, return xkm file name in + nameRtrn */ +static Bool XkbDDXCompileKeymapByNames(XkbDescPtr xkb, XkbComponentNamesPtr names, unsigned want, unsigned need, char *nameRtrn, int nameRtrnLen) { FILE *out; - char *buf = NULL, keymap[PATH_MAX], xkm_output_dir[PATH_MAX]; + char *buf = NULL, xkmfile[PATH_MAX], xkm_output_dir[PATH_MAX]; + char *tmpXkmFile = NULL; + char *canonicalXkmFileName = NULL; + char sha1Asc[SHA_DIGEST_LENGTH*2+1], xkbKeyMapBuf[100*1024]; + int ret, result; const char *emptystring = ""; char *xkbbasedirflag = NULL; @@ -183,15 +222,70 @@ XkbDDXCompileKeymapByNames(XkbDescPtr xk /* WIN32 has no popen. The input must be stored in a file which is used as input for xkbcomp. xkbcomp does not read from stdin. */ char tmpname[PATH_MAX]; - const char *xkmfile = tmpname; + const char *xkbfile = tmpname; #else - const char *xkmfile = "-"; + const char *xkbfile = "-"; #endif - snprintf(keymap, sizeof(keymap), "server-%s", display); + /* Write keymap source (xkbfile) to memory buffer `xkbKeyMapBuf', + of which SHA1 is generated and used as result xkm file name */ + memset(xkbKeyMapBuf, 0, sizeof(xkbKeyMapBuf)); + out = fmemopen(xkbKeyMapBuf, sizeof(xkbKeyMapBuf), "w"); + if (NULL == out) { + ErrorF("[xkb] Open xkbKeyMapBuf for writing failed\n"); + return FALSE; + } + ret = XkbWriteXKBKeymapForNames(out, names, xkb, want, need); + if (fclose(out) !=0) + { + ErrorF("[xkb] XkbWriteXKBKeymapForNames error, perhaps xkbKeyMapBuf is too small\n"); + return FALSE; + } +#ifdef DEBUG + if (xkbDebugFlags) { + ErrorF("[xkb] XkbDDXCompileKeymapByNames compiling keymap:\n"); + fputs(xkbKeyMapBuf, stderr); + } +#endif + if (!ret) { + ErrorF("[xkb] Generating XKB Keymap failed, giving up compiling keymap\n"); + return FALSE; + } + + DebugF("[xkb] computing SHA1 of keymap\n"); + if (Success == Sha1Asc(sha1Asc, xkbKeyMapBuf)) { + snprintf(xkmfile, sizeof(xkmfile), "server-%s", sha1Asc); + } + else { + ErrorF("[xkb] Computing SHA1 of keymap failed, " + "using display name instead as xkm file name\n"); + snprintf(xkmfile, sizeof(xkmfile), "server-%s", display); + } + /* XkbEnsureSafeMapName(xkmfile); */ OutputDirectory(xkm_output_dir, sizeof(xkm_output_dir)); + /* set nameRtrn, fail if it's too small */ + if ((strlen(xkmfile)+1 > nameRtrnLen) && nameRtrn) { + ErrorF("[xkb] nameRtrn too small to hold xkmfile name\n"); + return FALSE; + } + strncpy(nameRtrn, xkmfile, nameRtrnLen); + + /* if the xkm file already exists, reuse it */ + canonicalXkmFileName = Xprintf("%s%s.xkm", xkm_output_dir, xkmfile); + if (access(canonicalXkmFileName, R_OK) == 0) { + /* yes, we can reuse the old xkm file */ + LogMessage(X_INFO, "XKB: reuse xkmfile %s\n", canonicalXkmFileName); + result = TRUE; + goto _ret; + } + LogMessage(X_INFO, "XKB: generating xkmfile %s\n", canonicalXkmFileName); + + /* continue to call xkbcomp to compile the keymap. to avoid race + condition, we compile it to a tmpfile then rename it to + xkmfile */ + #ifdef WIN32 strcpy(tmpname, Win32TempDir()); strcat(tmpname, "\\xkb_XXXXXX"); @@ -214,15 +308,27 @@ XkbDDXCompileKeymapByNames(XkbDescPtr xk } } + /* tmpXkmFile will be moved to xkm_output_dir using rename(2), which doesn't + * work between multiple file systems, so we better generate the temporary + * file inside xkm_output_dir + * This temporary file is used to prevent race conditions (multiple + * Xservers), so we use the display string on its name to guarantee that + * concurrent Xservers won't use the same tmpfile name */ + if (asprintf(&tmpXkmFile, "%s/tmp-%s.xkm", xkm_output_dir, display) == -1) { + ErrorF("[xkb] Can't generate temp xkm file name"); + result = FALSE; + goto _ret; + } + if (asprintf(&buf, "\"%s%sxkbcomp\" -w %d %s -xkm \"%s\" " - "-em1 %s -emp %s -eml %s \"%s%s.xkm\"", + "-em1 %s -emp %s -eml %s \"%s\"", xkbbindir, xkbbindirsep, ((xkbDebugFlags < 2) ? 1 : ((xkbDebugFlags > 10) ? 10 : (int) xkbDebugFlags)), - xkbbasedirflag ? xkbbasedirflag : "", xkmfile, + xkbbasedirflag ? xkbbasedirflag : "", xkbfile, PRE_ERROR_MSG, ERROR_PREFIX, POST_ERROR_MSG1, - xkm_output_dir, keymap) == -1) + tmpXkmFile) == -1) buf = NULL; free(xkbbasedirflag); @@ -240,32 +346,46 @@ XkbDDXCompileKeymapByNames(XkbDescPtr xk #endif if (out != NULL) { -#ifdef DEBUG - if (xkbDebugFlags) { - ErrorF("[xkb] XkbDDXCompileKeymapByNames compiling keymap:\n"); - XkbWriteXKBKeymapForNames(stderr, names, xkb, want, need); - } -#endif XkbWriteXKBKeymapForNames(out, names, xkb, want, need); + /* write XKBKeyMapBuf to xkbcomp */ + if (EOF==fputs(xkbKeyMapBuf, out)) + { + ErrorF("[xkb] Sending keymap to xkbcomp failed\n"); + result = FALSE; + goto _ret; + } #ifndef WIN32 if (Pclose(out) == 0) #else if (fclose(out) == 0 && System(buf) >= 0) #endif + /* xkbcomp success */ { if (xkbDebugFlags) DebugF("[xkb] xkb executes: %s\n", buf); - if (nameRtrn) { - strlcpy(nameRtrn, keymap, nameRtrnLen); + /* if canonicalXkmFileName already exists now, we simply + overwrite it, this is OK */ + ret = rename(tmpXkmFile, canonicalXkmFileName); + if (0 != ret) { + ErrorF("[xkb] Can't rename %s to %s, error: %s\n", + tmpXkmFile, canonicalXkmFileName, + strerror(errno)); + + /* in case of error, don't unlink tmpXkmFile, leave it + for debugging */ + + result = FALSE; + goto _ret; } - free(buf); + result = TRUE; + goto _ret; #ifdef WIN32 unlink(tmpname); #endif return TRUE; } else - LogMessage(X_ERROR, "Error compiling keymap (%s)\n", keymap); + LogMessage(X_ERROR, "Error compiling keymap (%s)\n", xkbfile); #ifdef WIN32 /* remove the temporary file */ unlink(tmpname); @@ -280,8 +400,15 @@ XkbDDXCompileKeymapByNames(XkbDescPtr xk } if (nameRtrn) nameRtrn[0] = '\0'; + + result = FALSE; +_ret: + if (tmpXkmFile) + free(tmpXkmFile); + if (canonicalXkmFileName) + xfree(canonicalXkmFileName); free(buf); - return FALSE; + return result; } static FILE * @@ -360,7 +487,6 @@ XkbDDXLoadKeymapByNames(DeviceIntPtr key if (*xkbRtrn == NULL) { LogMessage(X_ERROR, "Error loading keymap %s\n", fileName); fclose(file); - (void) unlink(fileName); return 0; } else { -- 1.7.6