Set dll directory in kleopatra
[gpg4win.git] / src / kleowrap.c
1 /* kleowrap.c - Wrapper to call gpg under Windows.
2  * Copyright (C) 2007, 2008 g10 Code GmbH
3  *
4  * This file is part of Gpg4win.
5  *
6  * Gpg4win is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * Gpg4win is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19  * MA 02110-1301, USA.
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <process.h>
29 #include <windows.h>
30 #include <errno.h>
31
32
33 #define DEBUG_W32_SPAWN 0
34
35
36 static HANDLE
37 w32_open_null (int for_write)
38 {
39   HANDLE hfile;
40
41   hfile = CreateFile ("nul",
42                       for_write ? GENERIC_WRITE : GENERIC_READ,
43                       FILE_SHARE_READ | FILE_SHARE_WRITE,
44                       NULL, OPEN_EXISTING, 0, NULL);
45   return hfile;
46 }
47
48
49 char *
50 stpcpy(char *a,const char *b)
51 {
52     while( *b )
53         *a++ = *b++;
54     *a = 0;
55
56     return (char*)a;
57 }
58
59
60 /* Helper function to build_w32_commandline. */
61 static char *
62 build_w32_commandline_copy (char *buffer, const char *string)
63 {
64   char *p = buffer;
65   const char *s;
66
67   if (!*string) /* Empty string. */
68     p = stpcpy (p, "\"\"");
69   else if (strpbrk (string, " \t\n\v\f\""))
70     {
71       /* Need top do some kind of quoting.  */
72       p = stpcpy (p, "\"");
73       for (s=string; *s; s++)
74         {
75           *p++ = *s;
76           if (*s == '\"')
77             *p++ = *s;
78         }
79       *p++ = '\"';
80       *p = 0;
81     }
82   else
83     p = stpcpy (p, string);
84
85   return p;
86 }
87
88
89 /* Build a command line for use with W32's CreateProcess.  On success
90    CMDLINE gets the address of a newly allocated string.  */
91 static char *
92 build_w32_commandline (const char *pgmname, const char * const *argv)
93 {
94   int i, n;
95   const char *s;
96   char *buf, *p;
97
98   n = 0;
99   s = pgmname;
100   n += strlen (s) + 1 + 2;  /* (1 space, 2 quoting */
101   for (; *s; s++)
102     if (*s == '\"')
103       n++;  /* Need to double inner quotes.  */
104   for (i=0; (s=argv[i]); i++)
105     {
106       n += strlen (s) + 1 + 2;  /* (1 space, 2 quoting */
107       for (; *s; s++)
108         if (*s == '\"')
109           n++;  /* Need to double inner quotes.  */
110     }
111   n++;
112
113   buf = p = malloc (n);
114   if (! buf)
115     return NULL;
116
117   p = build_w32_commandline_copy (p, pgmname);
118   for (i = 0; argv[i]; i++) 
119     {
120       *p++ = ' ';
121       p = build_w32_commandline_copy (p, argv[i]);
122     }
123
124   return buf;
125 }
126
127
128 int
129 spawn_process_and_wait (const char *pgmname, const char * const *argv)
130 {
131   int err;
132   SECURITY_ATTRIBUTES sec_attr;
133   PROCESS_INFORMATION pi = 
134     {
135       NULL,      /* Returns process handle.  */
136       0,         /* Returns primary thread handle.  */
137       0,         /* Returns pid.  */
138       0          /* Returns tid.  */
139     };
140   STARTUPINFO si;
141   int cr_flags;
142   char *cmdline;
143   HANDLE proc;
144   int code;
145   DWORD exc;
146
147   /* Prepare security attributes.  */
148   memset (&sec_attr, 0, sizeof sec_attr);
149   sec_attr.nLength = sizeof sec_attr;
150   sec_attr.bInheritHandle = FALSE;
151   
152   /* Build the command line.  */
153   cmdline = build_w32_commandline (pgmname, argv);
154   if (! cmdline)
155     return -1; 
156
157   /* Start the process.  Note that we can't run the PREEXEC function
158      because this would change our own environment. */
159   memset (&si, 0, sizeof si);
160   si.cb = sizeof (si);
161   si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
162   si.wShowWindow = DEBUG_W32_SPAWN ? SW_SHOW : SW_MINIMIZE;
163   si.hStdInput = w32_open_null (0);
164   si.hStdOutput = w32_open_null (1);
165   si.hStdError = w32_open_null (1);
166
167   cr_flags = (CREATE_DEFAULT_ERROR_MODE
168               | DETACHED_PROCESS
169               | GetPriorityClass (GetCurrentProcess ())
170               | CREATE_SUSPENDED); 
171   if (!CreateProcess (pgmname,       /* Program to start.  */
172                       cmdline,       /* Command line arguments.  */
173                       &sec_attr,     /* Process security attributes.  */
174                       &sec_attr,     /* Thread security attributes.  */
175                       TRUE,          /* Inherit handles.  */
176                       cr_flags,      /* Creation flags.  */
177                       NULL,          /* Environment.  */
178                       NULL,          /* Use current drive/directory.  */
179                       &si,           /* Startup information. */
180                       &pi            /* Returns process information.  */
181                       ))
182     {
183       free (cmdline);
184       CloseHandle (si.hStdInput);
185       CloseHandle (si.hStdOutput);
186       CloseHandle (si.hStdError);
187       return -1;
188     }
189   free (cmdline);
190   cmdline = NULL;
191
192   /* Process has been created suspended; resume it now. */
193   ResumeThread (pi.hThread);
194   CloseHandle (pi.hThread); 
195
196   /* Wait for it to finish.  */
197
198   proc = pi.hProcess;
199   err = 0;
200
201   code = WaitForSingleObject (proc, INFINITE);
202   switch (code) 
203     {
204       case WAIT_OBJECT_0:
205         if (! GetExitCodeProcess (proc, &exc))
206           err = -1;
207         else
208           err = exc;
209         break;
210
211       default:
212         err = -1;
213         break;
214     }
215
216   CloseHandle (proc);
217
218   return err;
219 }
220
221
222 /* Assumes that the current working directory is the Gpg4win INSTDIR
223    installation directory.  */
224 int
225 run_kbuildsycoca (void)
226 {
227   const char *argv[1];
228   int rc;
229
230   if (! SetEnvironmentVariable ("XDG_DATA_DIRS", "share")
231       || ! SetEnvironmentVariable ("XDG_CONFIG_DIRS", "etc\\xdg"))
232     {
233       fprintf (stderr, "Executing kbuildsycoca4.exe failed: "
234                "Could not set XDG environment variables\n");
235       return -1;
236     }
237
238   argv[0] = NULL;
239   rc = spawn_process_and_wait ("bin\\kbuildsycoca4.exe", argv);
240   if (rc)
241     fprintf (stderr, "Executing bin\\kbuildsycoca4.exe failed: %i\n", rc);
242
243   return rc;
244 }
245
246
247 /* Return a copy of ARGV, but with proper quoting.  To release the
248    copy, you have to free argv_quoted[0] and argv_quoted.  */
249 static char **
250 build_commandline (const char * const *argv)
251 {
252   int i;
253   int j;
254   int n = 0;
255   char *buf;
256   char *p;
257   char **argv_quoted;
258
259   /* We have to quote some things because under Windows the program
260      parses the commandline and does some unquoting.  We enclose the
261      whole argument in double-quotes, and escape literal double-quotes
262      as well as backslashes with a backslash.  We end up with a
263      trailing space at the end of the line, but that is harmless.  */
264   for (i = 0; argv[i]; i++)
265     {
266       p = (char *) argv[i];
267       /* The leading double-quote.  */
268       n++;
269       while (*p)
270         {
271           /* An extra one for each literal that must be escaped.  */
272           if (*p == '\\' || *p == '"')
273             n++;
274           n++;
275           p++;
276         }
277       /* The trailing double-quote and the delimiter.  */
278       n += 2;
279     }
280   /* And a trailing zero.  */
281   n++;
282
283   /* Allocate a new vector.  */
284   argv_quoted = malloc (sizeof (char *) * (i + 1));
285   if (!argv_quoted)
286     return NULL;
287
288   buf = p = malloc (n);
289   if (!buf)
290     {
291       free (argv_quoted);
292       return NULL;
293     }
294
295   for (i = 0; argv[i]; i++)
296     {
297       const char *argvp = argv[i];
298
299       argv_quoted[i] = p;
300
301       *(p++) = '"';
302       while (*argvp)
303         {
304           if (*argvp == '\\' || *argvp == '"')
305             *(p++) = '\\';
306           *(p++) = *(argvp++);
307         }
308       *(p++) = '"';
309       *(p++) = 0;
310     }
311   *(p++) = 0;
312   argv_quoted[i] = NULL;
313
314   return argv_quoted;
315 }
316
317 static void
318 kleowrap_set_dll_directory (const char *path)
319 {
320   /* Set DLL directory is only necessary on Windows XP after SP2
321      but it is also only available on those systems */
322   typedef BOOL (CALLBACK* LPFNSETDLLDIRECTORY)(LPCTSTR);
323   LPFNSETDLLDIRECTORY my_set_dll_directory;
324
325   HMODULE hmod;
326
327   if (!(hmod = GetModuleHandle ("kernel32.dll")))
328     {
329       fprintf (stderr, "kleowrap: failed to get kernel32.dll handle: rc=%d\n",
330                GetLastError());
331       return;
332     }
333
334   my_set_dll_directory =
335     (LPFNSETDLLDIRECTORY) GetProcAddress (hmod, "SetDllDirectoryA");
336
337   if (!my_set_dll_directory)
338     {
339       /* Not supported and so not necessary */
340       return;
341     }
342
343   if (!my_set_dll_directory (path))
344     {
345       fprintf (stderr, "kleowrap: failed to set module handle",
346                GetLastError());
347       return;
348     }
349   OutputDebugString ("Andre entferne mich: SetDllDirectory success.");
350 }
351
352 int
353 main (int argc, const char * const *argv)
354 {
355   int rc;
356   char pgm[MAX_PATH+100];
357   char *p, *p0;
358   char **argv_quoted;
359
360
361   if (!GetModuleFileNameA (NULL, pgm, sizeof (pgm) - 1))
362     {
363       fprintf (stderr, "kleowrap: error getting my own name: rc=%d\n",
364                GetLastError());
365       return 2;
366     }
367
368   /* Switch directory and insert bin directory.  */
369   p = strrchr (pgm, '\\');
370   if (!p)
371     goto leave;
372   *p = '\0';
373   chdir (pgm);
374   kleowrap_set_dll_directory (pgm);
375   *(p++) = '\\';
376   memmove (p + 4, p, strlen (p) + 1);
377   strncpy (p, "bin\\", 4);
378
379   /* Hack to output our own version along with the real file name
380      before the actual, we require that the --version option is given
381      twice. */
382   if (argc > 2
383       && !strcmp(argv[1], "--version")
384       && !strcmp(argv[2], "--version"))
385     {
386       fputs ("kleowrap (Gpg4win) " PACKAGE_VERSION " ;", stdout);
387       fputs (pgm, stdout);
388       fputc ('\n', stdout);
389       fflush (stdout);
390     }
391
392   argv_quoted = build_commandline (argv);
393   if (! argv_quoted)
394     goto leave;
395
396   /* Now that the current working is INSTDIR, try to run kbuildsycoca
397      (create/update plugin cache).  We don't check the return value,
398      as kbuildsycoca is allowed to fail (and will if kleopatra is
399      already running).  */
400   run_kbuildsycoca();
401
402   /* Using execv does not replace the existing program image, but
403      spawns a new one and daemonizes it, confusing the command line
404      interpreter.  So we have to use spawnv.  */
405   rc = _spawnv (_P_NOWAIT, pgm, (const char **) argv_quoted);
406   if (rc < 0)
407     {
408       fprintf (stderr, "kleowrap: executing `%s' failed: %s\n",
409                pgm, strerror (errno));
410       return 2;
411     }
412
413   return rc;
414
415  leave:
416   fprintf (stderr, "kleowrap: internal error parsing my own name `%s'\n",
417            pgm);
418   return 2;
419 }