Page MenuHome GnuPG

w32: Synchronous spawning gpg-agent/dirmngr/keyboxd
Testing, WishlistPublic

Description

On POSIX machine, when gpg automatically invokes gpg-agent, it's done synchronously.

Currently, on Windows, it is done asynchronously and gpg frontend tries to connect to gpg-agent (with timeout).

Because it may takes loooong time like 20sec, it is good to do synchronous invocation on Windows too.

Event Timeline

gniibe triaged this task as Wishlist priority.Jul 11 2025, 10:43 AM
gniibe created this task.

I'm testing the following patch with experimental change of libgpg-error.

diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c
index ae1295977..728aed1ab 100644
--- a/agent/gpg-agent.c
+++ b/agent/gpg-agent.c
@@ -1855,6 +1855,25 @@ main (int argc, char **argv)
         }
 
       log_info ("%s %s started\n", gpgrt_strusage(11), gpgrt_strusage(13) );
+#ifdef HAVE_W32_SYSTEM
+      {
+        int null_fd = open ("NUL", O_RDWR);
+
+        if (null_fd < 0)
+          {
+            perror ("open failed");
+            write (1, "ERR 1\n", 6);
+          }
+        else
+          {
+            write (1, "OK\n", 3);
+            if (dup2 (null_fd, 1) < 0)
+              perror ("dup2 failed");
+            dup2 (null_fd, 2);
+            close (null_fd);
+          }
+      }
+#endif
       handle_connections (fd, fd_extra, fd_browser, fd_ssh,
                           reliable_homedir_inotify);
       assuan_sock_close (fd);
diff --git a/common/asshelp.c b/common/asshelp.c
index 0152d1243..3c275c011 100644
--- a/common/asshelp.c
+++ b/common/asshelp.c
@@ -536,8 +536,39 @@ start_new_service (assuan_context_t *r_ctx,
           && assuan_socket_connect (ctx, sockname, 0, connect_flags))
         {
 #ifdef HAVE_W32_SYSTEM
+#if 0
           err = gpgrt_process_spawn (program? program : program_name, argv,
                                      GPGRT_PROCESS_DETACHED, NULL, NULL);
+#else
+          {
+            gpgrt_process_t proc;
+
+            err = gpgrt_process_spawn (program? program : program_name, argv,
+                                       (GPGRT_PROCESS_DETACHED
+                                        |GPGRT_PROCESS_STDOUT_PIPE
+                                        |GPGRT_PROCESS_STDERR_KEEP),
+                                       NULL, &proc);
+            if (!err)
+              {
+                int pipe_in;
+                err = gpgrt_process_get_fds (proc, 0, NULL, &pipe_in, NULL);
+                if (!err)
+                  {
+                    char buf[256];
+                    int r;
+                    r = read (pipe_in, buf, sizeof buf);
+                    close (pipe_in);
+                    if (r < 0)
+                      {
+                        log_error ("read from child process failed: %s\n",
+                                   strerror (errno));
+                        err = gpg_error (GPG_ERR_SERVER_FAILED);
+                      }
+                  }
+                gpgrt_process_release (proc);
+              }
+          }
+#endif
 #else /*!W32*/
           err = gpgrt_process_spawn (program? program : program_name, argv,
                                      0, NULL, NULL);
diff --git a/common/stringhelp.c b/common/stringhelp.c
index 9a2265258..6596c65cd 100644
--- a/common/stringhelp.c
+++ b/common/stringhelp.c
@@ -70,6 +70,22 @@ change_slashes (char *name)
 {
 #ifdef HAVE_DOSISH_SYSTEM
   char *p;
+  /* 0: don't know yet, 1: it's under wine, -1: no */
+  static int semihosted_by_wine;
+
+  /* Under wine, no change.  */
+  if (!semihosted_by_wine)
+    {
+      HMODULE hntdll = GetModuleHandle ("ntdll.dll");
+      if (hntdll
+          && GetProcAddress (hntdll, "wine_get_version"))
+        semihosted_by_wine = 1;
+      else
+        semihosted_by_wine = -1;
+    }
+
+  if (semihosted_by_wine > 0)
+    return name;
 
   if (strchr (name, '\\'))
     {
diff --git a/dirmngr/dirmngr.c b/dirmngr/dirmngr.c
index d418d09e2..d8e5f8da5 100644
--- a/dirmngr/dirmngr.c
+++ b/dirmngr/dirmngr.c
@@ -1526,6 +1526,23 @@ main (int argc, char **argv)
       pid = getpid ();
       es_printf ("set %s=%s;%lu;1\n",
                  DIRMNGR_INFO_NAME, socket_name, (ulong) pid);
+      {
+        int null_fd = open ("NUL", O_RDWR);
+
+        if (null_fd < 0)
+          {
+            perror ("open failed");
+            write (1, "ERR 1\n", 6);
+          }
+        else
+          {
+            write (1, "OK\n", 3);
+            if (dup2 (null_fd, 1) < 0)
+              perror ("dup2 failed");
+            dup2 (null_fd, 2);
+            close (null_fd);
+          }
+      }
 #else
       pid = fork();
       if (pid == (pid_t)-1)
diff --git a/kbx/keyboxd.c b/kbx/keyboxd.c
index 197f01c74..842661327 100644
--- a/kbx/keyboxd.c
+++ b/kbx/keyboxd.c
@@ -735,6 +735,23 @@ main (int argc, char **argv )
       (void)nodetach;
       initialize_modules ();
 
+      {
+        int null_fd = open ("NUL", O_RDWR);
+
+        if (null_fd < 0)
+          {
+            perror ("open failed");
+            write (1, "ERR 1\n", 6);
+          }
+        else
+          {
+            write (1, "OK\n", 3);
+            if (dup2 (null_fd, 1) < 0)
+              perror ("dup2 failed");
+            dup2 (null_fd, 2);
+            close (null_fd);
+          }
+      }
 #else /*!HAVE_W32_SYSTEM*/
 
       pid = fork ();
gniibe mentioned this in Unknown Object (Maniphest Task).Jul 28 2025, 8:01 AM
gniibe mentioned this in Unknown Object (Maniphest Task).Aug 4 2025, 8:02 AM
gniibe mentioned this in Unknown Object (Maniphest Task).Sep 29 2025, 7:04 AM

I updated the branch.

Now, it's simply:

gniibe mentioned this in Unknown Object (Maniphest Task).Oct 6 2025, 7:33 AM
gniibe mentioned this in Unknown Object (Maniphest Task).Mon, Nov 10, 10:07 AM

Let me explain about the change rG57affc4e98ab.

With the change, before connecting to the assuan socket of the daemon, frontend process keeps the connection of standard output and error from the child process.

The standard output from the child process is used to make sure the daemon starts the service correctly.

By keeping the standard error from the child process, the initial error message can be seen by a user, this is an improvement both for normal users and developers (who debug the daemon).

For the standard output interaction at the start, I use something like assuan command and response syntax of "OK" and "ERR".
Possibly, this would be a source of confusion, as if it were doing layer violation of low-level communication directly by read/write routine.
It's not. It's the start up confirmation.

This start up confirmation can be changed. I'm open for this.

After the start up confirmation, the daemon closes its standard output and error.