Commit | Line | Data |
---|---|---|
7217e0ca ML |
1 | Last-Update: 2013-09-19 |
2 | ||
4db25562 JB |
3 | --- a/configure.ac |
4 | +++ b/configure.ac | |
5 | @@ -514,9 +514,9 @@ AC_MSG_RESULT([$FONTPATH]) | |
7217e0ca ML |
6 | AC_ARG_WITH(xkb-path, AS_HELP_STRING([--with-xkb-path=PATH], [Path to XKB base dir (default: ${datadir}/X11/xkb)]), |
7 | [ XKBPATH="$withval" ], | |
8 | [ XKBPATH="${datadir}/X11/xkb" ]) | |
9 | -AC_ARG_WITH(xkb-output, AS_HELP_STRING([--with-xkb-output=PATH], [Path to XKB output dir (default: ${datadir}/X11/xkb/compiled)]), | |
10 | +AC_ARG_WITH(xkb-output, AS_HELP_STRING([--with-xkb-output=PATH], [Path to XKB output dir (default: ${localstatedir}/cache/xkb)]), | |
11 | [ XKBOUTPUT="$withval" ], | |
12 | - [ XKBOUTPUT="compiled" ]) | |
13 | + [ XKBOUTPUT="${localstatedir}/cache/xkb" ]) | |
14 | AC_ARG_WITH(default-xkb-rules, AS_HELP_STRING([--with-default-xkb-rules=RULES], | |
15 | [Keyboard ruleset (default: base/evdev)]), | |
16 | [ XKB_DFLT_RULES="$withval" ], | |
4db25562 | 17 | @@ -1415,7 +1415,7 @@ AC_DEFINE_DIR(XKB_BIN_DIRECTORY, XKB_BIN |
7217e0ca ML |
18 | dnl Make sure XKM_OUTPUT_DIR is an absolute path |
19 | XKBOUTPUT_FIRSTCHAR=`echo $XKBOUTPUT | cut -b 1` | |
20 | if [[ x$XKBOUTPUT_FIRSTCHAR != x/ -a x$XKBOUTPUT_FIRSTCHAR != 'x$' ]] ; then | |
21 | - XKBOUTPUT="$XKB_BASE_DIRECTORY/$XKBOUTPUT" | |
22 | + AC_MSG_ERROR([xkb-output must be an absolute path.]) | |
23 | fi | |
24 | ||
25 | dnl XKM_OUTPUT_DIR (used in code) must end in / or file names get hosed | |
4db25562 JB |
26 | --- a/xkb/README.compiled |
27 | +++ b/xkb/README.compiled | |
28 | @@ -4,10 +4,10 @@ current keymap and/or any scratch keymap | |
7217e0ca ML |
29 | or some other tool might destroy or replace the files in this directory, |
30 | so it is not a safe place to store compiled keymaps for long periods of | |
31 | time. The default keymap for any server is usually stored in: | |
32 | - X<num>-default.xkm | |
33 | -where <num> is the display number of the server in question, which makes | |
34 | -it possible for several servers *on the same host* to share the same | |
35 | -directory. | |
36 | + server-<SHA1>.xkm | |
37 | + | |
38 | +where <SHA1> is the SHA1 hash of keymap source, so that compiled | |
39 | +keymap of different keymap sources are stored in different files. | |
40 | ||
41 | Unless the X server is modified, sharing this directory between servers on | |
42 | different hosts could cause problems. | |
4db25562 JB |
43 | --- a/xkb/ddxLoad.c |
44 | +++ b/xkb/ddxLoad.c | |
45 | @@ -30,6 +30,12 @@ THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
7217e0ca ML |
46 | |
47 | #include <xkb-config.h> | |
48 | ||
49 | +#ifdef HAVE_SHA1_IN_LIBGCRYPT /* Use libgcrypt for SHA1 */ | |
50 | +#include <gcrypt.h> | |
51 | +#else /* Use OpenSSL's libcrypto */ | |
52 | +#warning "xkbcomp caching support disabled" | |
53 | +#endif | |
54 | + | |
55 | #include <stdio.h> | |
56 | #include <ctype.h> | |
57 | #include <X11/X.h> | |
4db25562 | 58 | @@ -43,20 +49,9 @@ THE USE OR PERFORMANCE OF THIS SOFTWARE. |
7217e0ca ML |
59 | #define XKBSRV_NEED_FILE_FUNCS |
60 | #include <xkbsrv.h> | |
61 | #include <X11/extensions/XI.h> | |
62 | +#include <errno.h> | |
63 | #include "xkb.h" | |
64 | ||
65 | - /* | |
66 | - * If XKM_OUTPUT_DIR specifies a path without a leading slash, it is | |
67 | - * relative to the top-level XKB configuration directory. | |
68 | - * Making the server write to a subdirectory of that directory | |
69 | - * requires some work in the general case (install procedure | |
70 | - * has to create links to /var or somesuch on many machines), | |
71 | - * so we just compile into /usr/tmp for now. | |
72 | - */ | |
73 | -#ifndef XKM_OUTPUT_DIR | |
74 | -#define XKM_OUTPUT_DIR "compiled/" | |
75 | -#endif | |
76 | - | |
77 | #define PRE_ERROR_MSG "\"The XKEYBOARD keymap compiler (xkbcomp) reports:\"" | |
78 | #define ERROR_PREFIX "\"> \"" | |
79 | #define POST_ERROR_MSG1 "\"Errors from xkbcomp are not fatal to the X server\"" | |
4db25562 | 80 | @@ -69,35 +64,87 @@ THE USE OR PERFORMANCE OF THIS SOFTWARE. |
7217e0ca ML |
81 | #endif |
82 | ||
83 | static void | |
84 | -OutputDirectory(char *outdir, size_t size) | |
85 | +OutputDirectory(char *outdir, size_t size, Bool *is_private_directory) | |
86 | { | |
87 | #ifndef WIN32 | |
88 | /* Can we write an xkm and then open it too? */ | |
89 | if (access(XKM_OUTPUT_DIR, W_OK | X_OK) == 0 && | |
90 | (strlen(XKM_OUTPUT_DIR) < size)) { | |
91 | (void) strcpy(outdir, XKM_OUTPUT_DIR); | |
92 | + if (is_private_directory) | |
93 | + *is_private_directory = TRUE; | |
94 | } | |
95 | else | |
96 | #else | |
97 | if (strlen(Win32TempDir()) + 1 < size) { | |
98 | (void) strcpy(outdir, Win32TempDir()); | |
99 | (void) strcat(outdir, "\\"); | |
100 | + if (is_private_directory) | |
101 | + *is_private_directory = FALSE; | |
102 | } | |
103 | else | |
104 | #endif | |
105 | if (strlen("/tmp/") < size) { | |
106 | (void) strcpy(outdir, "/tmp/"); | |
107 | + if (is_private_directory) | |
108 | + *is_private_directory = FALSE; | |
109 | } | |
110 | } | |
111 | ||
112 | +#ifndef SHA_DIGEST_LENGTH | |
113 | +#define SHA_DIGEST_LENGTH 20 | |
114 | +#endif | |
115 | + | |
116 | +static Bool | |
117 | +Sha1Asc(char sha1Asc[SHA_DIGEST_LENGTH * 2 + 1], const char *input) | |
118 | +{ | |
119 | + int i; | |
120 | + unsigned char sha1[SHA_DIGEST_LENGTH]; | |
121 | + | |
122 | +#ifdef HAVE_SHA1_IN_LIBGCRYPT /* Use libgcrypt for SHA1 */ | |
123 | + static int init; | |
124 | + gcry_md_hd_t h; | |
125 | + gcry_error_t err; | |
126 | + | |
127 | + if (!init) { | |
128 | + if (!gcry_check_version(NULL)) | |
129 | + return BadAlloc; | |
130 | + gcry_control(GCRYCTL_DISABLE_SECMEM, 0); | |
131 | + gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); | |
132 | + init = 1; | |
133 | + } | |
134 | + | |
135 | + err = gcry_md_open(&h, GCRY_MD_SHA1, 0); | |
136 | + if (err) | |
137 | + return BadAlloc; | |
138 | + gcry_md_write(h, input, strlen(input)); | |
139 | + memcpy(sha1, gcry_md_read(h, GCRY_MD_SHA1), 20); | |
140 | + gcry_md_close(h); | |
141 | +#endif | |
142 | + | |
143 | + /* convert sha1 to sha1_asc */ | |
144 | + for (i = 0; i < SHA_DIGEST_LENGTH; ++i) { | |
145 | + sprintf(sha1Asc + i * 2, "%02X", sha1[i]); | |
146 | + } | |
147 | + | |
148 | + return Success; | |
149 | +} | |
150 | + | |
151 | +/* call xkbcomp and compile XKB keymap, return xkm file name in | |
152 | + nameRtrn */ | |
153 | static Bool | |
154 | XkbDDXCompileKeymapByNames(XkbDescPtr xkb, | |
155 | XkbComponentNamesPtr names, | |
156 | unsigned want, | |
157 | - unsigned need, char *nameRtrn, int nameRtrnLen) | |
158 | + unsigned need, char *nameRtrn, int nameRtrnLen, | |
159 | + Bool *is_private_directory) | |
160 | { | |
161 | FILE *out; | |
162 | - char *buf = NULL, keymap[PATH_MAX], xkm_output_dir[PATH_MAX]; | |
163 | + char *buf = NULL, xkmfile[PATH_MAX], xkm_output_dir[PATH_MAX]; | |
164 | + char *tmpXkmFile = NULL; | |
165 | + char *canonicalXkmFileName = NULL; | |
166 | + char sha1Asc[SHA_DIGEST_LENGTH * 2 + 1], xkbKeyMapBuf[100 * 1024]; | |
167 | + int ret, result; | |
168 | ||
169 | const char *emptystring = ""; | |
170 | char *xkbbasedirflag = NULL; | |
4db25562 | 171 | @@ -108,14 +155,68 @@ XkbDDXCompileKeymapByNames(XkbDescPtr xk |
7217e0ca ML |
172 | /* WIN32 has no popen. The input must be stored in a file which is |
173 | used as input for xkbcomp. xkbcomp does not read from stdin. */ | |
174 | char tmpname[PATH_MAX]; | |
175 | - const char *xkmfile = tmpname; | |
176 | + const char *xkbfile = tmpname; | |
177 | #else | |
178 | - const char *xkmfile = "-"; | |
179 | + const char *xkbfile = "-"; | |
180 | #endif | |
181 | ||
182 | - snprintf(keymap, sizeof(keymap), "server-%s", display); | |
183 | + /* Write keymap source (xkbfile) to memory buffer `xkbKeyMapBuf', | |
184 | + of which SHA1 is generated and used as result xkm file name */ | |
185 | + memset(xkbKeyMapBuf, 0, sizeof(xkbKeyMapBuf)); | |
186 | + out = fmemopen(xkbKeyMapBuf, sizeof(xkbKeyMapBuf), "w"); | |
187 | + if (NULL == out) { | |
188 | + ErrorF("[xkb] Open xkbKeyMapBuf for writing failed\n"); | |
189 | + return FALSE; | |
190 | + } | |
191 | + ret = XkbWriteXKBKeymapForNames(out, names, xkb, want, need); | |
192 | + if (fclose(out) != 0) { | |
193 | + ErrorF | |
194 | + ("[xkb] XkbWriteXKBKeymapForNames error, perhaps xkbKeyMapBuf is too small\n"); | |
195 | + return FALSE; | |
196 | + } | |
197 | +#ifdef DEBUG | |
198 | + if (xkbDebugFlags) { | |
199 | + ErrorF("[xkb] XkbDDXCompileKeymapByNames compiling keymap:\n"); | |
200 | + fputs(xkbKeyMapBuf, stderr); | |
201 | + } | |
202 | +#endif | |
203 | + if (!ret) { | |
204 | + ErrorF | |
205 | + ("[xkb] Generating XKB Keymap failed, giving up compiling keymap\n"); | |
206 | + return FALSE; | |
207 | + } | |
208 | + | |
209 | + DebugF("[xkb] computing SHA1 of keymap\n"); | |
210 | + if (Success == Sha1Asc(sha1Asc, xkbKeyMapBuf)) { | |
211 | + snprintf(xkmfile, sizeof(xkmfile), "server-%s", sha1Asc); | |
212 | + } | |
213 | + else { | |
214 | + ErrorF("[xkb] Computing SHA1 of keymap failed, " | |
215 | + "using display name instead as xkm file name\n"); | |
216 | + snprintf(xkmfile, sizeof(xkmfile), "server-%s", display); | |
217 | + } | |
218 | + | |
219 | + OutputDirectory(xkm_output_dir, sizeof(xkm_output_dir), is_private_directory); | |
220 | + /* set nameRtrn, fail if it's too small */ | |
221 | + if ((strlen(xkmfile) + 1 > nameRtrnLen) && nameRtrn) { | |
222 | + ErrorF("[xkb] nameRtrn too small to hold xkmfile name\n"); | |
223 | + return FALSE; | |
224 | + } | |
225 | + strncpy(nameRtrn, xkmfile, nameRtrnLen); | |
226 | ||
227 | - OutputDirectory(xkm_output_dir, sizeof(xkm_output_dir)); | |
228 | + /* if the xkm file already exists, reuse it */ | |
229 | + canonicalXkmFileName = Xprintf("%s%s.xkm", xkm_output_dir, xkmfile); | |
230 | + if ((*is_private_directory) && (access(canonicalXkmFileName, R_OK) == 0)) { | |
231 | + /* yes, we can reuse the old xkm file */ | |
232 | + LogMessage(X_INFO, "XKB: reuse xkmfile %s\n", canonicalXkmFileName); | |
233 | + result = TRUE; | |
234 | + goto _ret; | |
235 | + } | |
236 | + LogMessage(X_INFO, "XKB: generating xkmfile %s\n", canonicalXkmFileName); | |
237 | + | |
238 | + /* continue to call xkbcomp to compile the keymap. to avoid race | |
239 | + condition, we compile it to a tmpfile then rename it to | |
240 | + xkmfile */ | |
241 | ||
242 | #ifdef WIN32 | |
243 | strcpy(tmpname, Win32TempDir()); | |
4db25562 | 244 | @@ -139,15 +240,21 @@ XkbDDXCompileKeymapByNames(XkbDescPtr xk |
7217e0ca ML |
245 | } |
246 | } | |
247 | ||
248 | + if ((tmpXkmFile = tempnam(xkm_output_dir, NULL)) == NULL) { | |
249 | + ErrorF("[xkb] Can't generate temp xkm file name"); | |
250 | + result = FALSE; | |
251 | + goto _ret; | |
252 | + } | |
253 | + | |
254 | if (asprintf(&buf, | |
255 | "\"%s%sxkbcomp\" -w %d %s -xkm \"%s\" " | |
256 | - "-em1 %s -emp %s -eml %s \"%s%s.xkm\"", | |
257 | + "-em1 %s -emp %s -eml %s \"%s\"", | |
258 | xkbbindir, xkbbindirsep, | |
259 | ((xkbDebugFlags < 2) ? 1 : | |
260 | ((xkbDebugFlags > 10) ? 10 : (int) xkbDebugFlags)), | |
261 | - xkbbasedirflag ? xkbbasedirflag : "", xkmfile, | |
262 | + xkbbasedirflag ? xkbbasedirflag : "", xkbfile, | |
263 | PRE_ERROR_MSG, ERROR_PREFIX, POST_ERROR_MSG1, | |
264 | - xkm_output_dir, keymap) == -1) | |
265 | + tmpXkmFile) == -1) | |
266 | buf = NULL; | |
267 | ||
268 | free(xkbbasedirflag); | |
4db25562 | 269 | @@ -158,6 +265,11 @@ XkbDDXCompileKeymapByNames(XkbDescPtr xk |
7217e0ca ML |
270 | return FALSE; |
271 | } | |
272 | ||
273 | + /* there's a potential race condition between calling tempnam() | |
274 | + and invoking xkbcomp to write the result file (potential temp | |
275 | + file name conflicts), but since xkbcomp is a standalone | |
276 | + program, we have to live with this */ | |
277 | + | |
278 | #ifndef WIN32 | |
279 | out = Popen(buf, "w"); | |
280 | #else | |
4db25562 | 281 | @@ -165,32 +277,43 @@ XkbDDXCompileKeymapByNames(XkbDescPtr xk |
7217e0ca ML |
282 | #endif |
283 | ||
284 | if (out != NULL) { | |
285 | -#ifdef DEBUG | |
286 | - if (xkbDebugFlags) { | |
287 | - ErrorF("[xkb] XkbDDXCompileKeymapByNames compiling keymap:\n"); | |
288 | - XkbWriteXKBKeymapForNames(stderr, names, xkb, want, need); | |
289 | + /* write XKBKeyMapBuf to xkbcomp */ | |
290 | + if (EOF == fputs(xkbKeyMapBuf, out)) { | |
291 | + ErrorF("[xkb] Sending keymap to xkbcomp failed\n"); | |
292 | + result = FALSE; | |
293 | + goto _ret; | |
294 | } | |
295 | -#endif | |
296 | - XkbWriteXKBKeymapForNames(out, names, xkb, want, need); | |
297 | #ifndef WIN32 | |
298 | if (Pclose(out) == 0) | |
299 | #else | |
300 | if (fclose(out) == 0 && System(buf) >= 0) | |
301 | #endif | |
302 | { | |
303 | + /* xkbcomp success */ | |
304 | if (xkbDebugFlags) | |
305 | DebugF("[xkb] xkb executes: %s\n", buf); | |
306 | - if (nameRtrn) { | |
307 | - strlcpy(nameRtrn, keymap, nameRtrnLen); | |
308 | + | |
309 | + /* if canonicalXkmFileName already exists now, we simply | |
310 | + overwrite it, this is OK */ | |
311 | + ret = rename(tmpXkmFile, canonicalXkmFileName); | |
312 | + if (0 != ret) { | |
313 | + ErrorF("[xkb] Can't rename %s to %s, error: %s\n", | |
314 | + tmpXkmFile, canonicalXkmFileName, strerror(errno)); | |
315 | + | |
316 | + /* in case of error, don't unlink tmpXkmFile, leave i | |
317 | + for debugging */ | |
318 | + | |
319 | + result = FALSE; | |
320 | + goto _ret; | |
321 | } | |
322 | - free(buf); | |
323 | #ifdef WIN32 | |
324 | unlink(tmpname); | |
325 | #endif | |
326 | - return TRUE; | |
327 | + result = TRUE; | |
328 | + goto _ret; | |
329 | } | |
330 | else | |
331 | - LogMessage(X_ERROR, "Error compiling keymap (%s)\n", keymap); | |
332 | + LogMessage(X_ERROR, "Error compiling keymap (%s)\n", xkbfile); | |
333 | #ifdef WIN32 | |
334 | /* remove the temporary file */ | |
335 | unlink(tmpname); | |
4db25562 | 336 | @@ -205,8 +328,17 @@ XkbDDXCompileKeymapByNames(XkbDescPtr xk |
7217e0ca ML |
337 | } |
338 | if (nameRtrn) | |
339 | nameRtrn[0] = '\0'; | |
340 | - free(buf); | |
341 | - return FALSE; | |
342 | + result = FALSE; | |
343 | + | |
344 | + _ret: | |
345 | + if (tmpXkmFile) | |
346 | + free(tmpXkmFile); | |
347 | + if (canonicalXkmFileName) | |
348 | + free(canonicalXkmFileName); | |
349 | + if (buf) | |
350 | + free(buf); | |
351 | + | |
352 | + return result; | |
353 | } | |
354 | ||
355 | static FILE * | |
4db25562 | 356 | @@ -217,7 +349,7 @@ XkbDDXOpenConfigFile(char *mapName, char |
7217e0ca ML |
357 | |
358 | buf[0] = '\0'; | |
359 | if (mapName != NULL) { | |
360 | - OutputDirectory(xkm_output_dir, sizeof(xkm_output_dir)); | |
361 | + OutputDirectory(xkm_output_dir, sizeof(xkm_output_dir), NULL); | |
362 | if ((XkbBaseDirectory != NULL) && (xkm_output_dir[0] != '/') | |
363 | #ifdef WIN32 | |
364 | && (!isalpha(xkm_output_dir[0]) || xkm_output_dir[1] != ':') | |
4db25562 | 365 | @@ -256,6 +388,7 @@ XkbDDXLoadKeymapByNames(DeviceIntPtr key |
7217e0ca ML |
366 | FILE *file; |
367 | char fileName[PATH_MAX]; | |
368 | unsigned missing; | |
369 | + Bool is_private_directory; | |
370 | ||
371 | *xkbRtrn = NULL; | |
372 | if ((keybd == NULL) || (keybd->key == NULL) || | |
4db25562 | 373 | @@ -271,7 +404,8 @@ XkbDDXLoadKeymapByNames(DeviceIntPtr key |
7217e0ca ML |
374 | return 0; |
375 | } | |
376 | else if (!XkbDDXCompileKeymapByNames(xkb, names, want, need, | |
377 | - nameRtrn, nameRtrnLen)) { | |
378 | + nameRtrn, nameRtrnLen, | |
379 | + &is_private_directory)) { | |
380 | LogMessage(X_ERROR, "XKB: Couldn't compile keymap\n"); | |
381 | return 0; | |
382 | } | |
4db25562 | 383 | @@ -293,7 +427,8 @@ XkbDDXLoadKeymapByNames(DeviceIntPtr key |
7217e0ca ML |
384 | (*xkbRtrn)->defined); |
385 | } | |
386 | fclose(file); | |
387 | - (void) unlink(fileName); | |
388 | + if (!is_private_directory) | |
389 | + (void) unlink(fileName); | |
390 | return (need | want) & (~missing); | |
391 | } | |
392 |