/* If NAME is NULL, don't exec, just fork. FD_CHILD_LIST is modified to reflect the value of the FD in the peer process (on Windows). */ static int my_spawn (assuan_context_t ctx, pid_t *r_pid, const char *name, const char **argv, assuan_fd_t fd_in, assuan_fd_t fd_out, assuan_fd_t *fd_child_list, void (*atfork) (void *opaque, int reserved), void *atforkvalue, unsigned int flags) { int err; struct spawn_fd_item_s *fd_items; int i; assert (name); if (! name) { gpg_err_set_errno (ENOSYS); return -1; } i = 0; if (fd_child_list) { while (fd_child_list[i] != ASSUAN_INVALID_FD) i++; } /* fd_in, fd_out, terminator */ i += 3; fd_items = calloc (i, sizeof (struct spawn_fd_item_s)); if (! fd_items) return -1; i = 0; if (fd_child_list) { while (fd_child_list[i] != ASSUAN_INVALID_FD) { fd_items[i].fd = (int) fd_child_list[i]; fd_items[i].dup_to = -1; i++; } } if (fd_in != ASSUAN_INVALID_FD) { fd_items[i].fd = (int) fd_in; fd_items[i].dup_to = 0; i++; } if (fd_out != ASSUAN_INVALID_FD) { fd_items[i].fd = (int) fd_out; fd_items[i].dup_to = 1; i++; } fd_items[i].fd = -1; fd_items[i].dup_to = -1; err = _gpgme_io_spawn (name, (char*const*)argv, (IOSPAWN_FLAG_NOCLOSE | IOSPAWN_FLAG_DETACHED), fd_items, atfork, atforkvalue, r_pid); if (! err) { i = 0; if (fd_child_list) { while (fd_child_list[i] != ASSUAN_INVALID_FD) { fd_child_list[i] = (assuan_fd_t) fd_items[i].peer_name; i++; } } } free (fd_items); return err; }
static gpgme_error_t engspawn_start (engine_spawn_t esp, const char *file, const char *argv[], unsigned int flags) { gpgme_error_t err; int i, n; int status; struct spawn_fd_item_s *fd_list; pid_t pid; unsigned int spflags; const char *save_argv0 = NULL; if (!esp || !file || !argv || !argv[0]) return gpg_error (GPG_ERR_INV_VALUE); spflags = 0; if ((flags & GPGME_SPAWN_DETACHED)) spflags |= IOSPAWN_FLAG_DETACHED; if ((flags & GPGME_SPAWN_ALLOW_SET_FG)) spflags |= IOSPAWN_FLAG_ALLOW_SET_FG; err = build_fd_data_map (esp); if (err) return err; n = 0; for (i = 0; esp->fd_data_map[i].data; i++) n++; fd_list = calloc (n+1, sizeof *fd_list); if (!fd_list) return gpg_error_from_syserror (); /* Build the fd list for the child. */ n = 0; for (i = 0; esp->fd_data_map[i].data; i++) { fd_list[n].fd = esp->fd_data_map[i].peer_fd; fd_list[n].dup_to = esp->fd_data_map[i].dup_to; n++; } fd_list[n].fd = -1; fd_list[n].dup_to = -1; if (argv[0] && !*argv[0]) { save_argv0 = argv[0]; argv[0] = _gpgme_get_basename (file); } status = _gpgme_io_spawn (file, (char * const *)argv, spflags, fd_list, NULL, NULL, &pid); if (save_argv0) argv[0] = save_argv0; free (fd_list); if (status == -1) return gpg_error_from_syserror (); for (i = 0; esp->fd_data_map[i].data; i++) { err = add_io_cb (esp, esp->fd_data_map[i].fd, esp->fd_data_map[i].inbound, esp->fd_data_map[i].inbound ? _gpgme_data_inbound_handler : _gpgme_data_outbound_handler, esp->fd_data_map[i].data, &esp->fd_data_map[i].tag); if (err) return err; /* FIXME: kill the child */ } engspawn_io_event (esp, GPGME_EVENT_START, NULL); return 0; }
/* Read from gpgconf and pass line after line to the hook function. We put a limit of 64 k on the maximum size for a line. This should allow for quite a long "group" line, which is usually the longest line (mine is currently ~3k). */ static gpgme_error_t gpgconf_read (void *engine, char *arg1, char *arg2, gpgme_error_t (*cb) (void *hook, char *line), void *hook) { struct engine_gpgconf *gpgconf = engine; gpgme_error_t err = 0; char *linebuf; size_t linebufsize; int linelen; char *argv[4] = { NULL /* file_name */, NULL, NULL, NULL }; int rp[2]; struct spawn_fd_item_s cfd[] = { {-1, 1 /* STDOUT_FILENO */, -1, 0}, {-1, -1} }; int status; int nread; char *mark = NULL; argv[1] = arg1; argv[2] = arg2; /* FIXME: Deal with engine->home_dir. */ /* _gpgme_engine_new guarantees that this is not NULL. */ argv[0] = gpgconf->file_name; if (_gpgme_io_pipe (rp, 1) < 0) return gpg_error_from_syserror (); cfd[0].fd = rp[1]; status = _gpgme_io_spawn (gpgconf->file_name, argv, 0, cfd, NULL, NULL, NULL); if (status < 0) { _gpgme_io_close (rp[0]); _gpgme_io_close (rp[1]); return gpg_error_from_syserror (); } linebufsize = 1024; /* Usually enough for conf lines. */ linebuf = malloc (linebufsize); if (!linebuf) { err = gpg_error_from_syserror (); goto leave; } linelen = 0; while ((nread = _gpgme_io_read (rp[0], linebuf + linelen, linebufsize - linelen - 1))) { char *line; const char *lastmark = NULL; size_t nused; if (nread < 0) { err = gpg_error_from_syserror (); goto leave; } linelen += nread; linebuf[linelen] = '\0'; for (line=linebuf; (mark = strchr (line, '\n')); line = mark+1 ) { lastmark = mark; if (mark > line && mark[-1] == '\r') mark[-1] = '\0'; else mark[0] = '\0'; /* Got a full line. Due to the CR removal code (which occurs only on Windows) we might be one-off and thus would see empty lines. Don't pass them to the callback. */ err = *line? (*cb) (hook, line) : 0; if (err) goto leave; } nused = lastmark? (lastmark + 1 - linebuf) : 0; memmove (linebuf, linebuf + nused, linelen - nused); linelen -= nused; if (!(linelen < linebufsize - 1)) { char *newlinebuf; if (linelen < 8 * 1024 - 1) linebufsize = 8 * 1024; else if (linelen < 64 * 1024 - 1) linebufsize = 64 * 1024; else { /* We reached our limit - give up. */ err = gpg_error (GPG_ERR_LINE_TOO_LONG); goto leave; } newlinebuf = realloc (linebuf, linebufsize); if (!newlinebuf) { err = gpg_error_from_syserror (); goto leave; } linebuf = newlinebuf; } } leave: free (linebuf); _gpgme_io_close (rp[0]); return err; }
static gpgme_error_t gpgconf_write (void *engine, char *arg1, char *arg2, gpgme_data_t conf) { struct engine_gpgconf *gpgconf = engine; gpgme_error_t err = 0; #define BUFLEN 1024 char buf[BUFLEN]; int buflen = 0; char *argv[] = { NULL /* file_name */, arg1, arg2, 0 }; int rp[2]; struct spawn_fd_item_s cfd[] = { {-1, 0 /* STDIN_FILENO */}, {-1, -1} }; int status; int nwrite; /* FIXME: Deal with engine->home_dir. */ /* _gpgme_engine_new guarantees that this is not NULL. */ argv[0] = gpgconf->file_name; if (_gpgme_io_pipe (rp, 0) < 0) return gpg_error_from_syserror (); cfd[0].fd = rp[0]; status = _gpgme_io_spawn (gpgconf->file_name, argv, 0, cfd, NULL, NULL, NULL); if (status < 0) { _gpgme_io_close (rp[0]); _gpgme_io_close (rp[1]); return gpg_error_from_syserror (); } for (;;) { if (buflen == 0) { do { buflen = gpgme_data_read (conf, buf, BUFLEN); } while (buflen < 0 && errno == EAGAIN); if (buflen < 0) { err = gpg_error_from_syserror (); _gpgme_io_close (rp[1]); return err; } else if (buflen == 0) { /* All is written. */ _gpgme_io_close (rp[1]); return 0; } } do { nwrite = _gpgme_io_write (rp[1], buf, buflen); } while (nwrite < 0 && errno == EAGAIN); if (nwrite > 0) { buflen -= nwrite; if (buflen > 0) memmove (&buf[0], &buf[nwrite], buflen); } else if (nwrite < 0) { _gpgme_io_close (rp[1]); return gpg_error_from_syserror (); } } return 0; }
static gpgme_error_t gpgconf_read (void *engine, char *arg1, char *arg2, gpgme_error_t (*cb) (void *hook, char *line), void *hook) { struct engine_gpgconf *gpgconf = engine; gpgme_error_t err = 0; #define LINELENGTH 1024 char linebuf[LINELENGTH] = ""; int linelen = 0; char *argv[4] = { NULL /* file_name */, NULL, NULL, NULL }; int rp[2]; struct spawn_fd_item_s cfd[] = { {-1, 1 /* STDOUT_FILENO */, -1, 0}, {-1, -1} }; int status; int nread; char *mark = NULL; argv[1] = arg1; argv[2] = arg2; /* FIXME: Deal with engine->home_dir. */ /* _gpgme_engine_new guarantees that this is not NULL. */ argv[0] = gpgconf->file_name; if (_gpgme_io_pipe (rp, 1) < 0) return gpg_error_from_syserror (); cfd[0].fd = rp[1]; status = _gpgme_io_spawn (gpgconf->file_name, argv, 0, cfd, NULL, NULL, NULL); if (status < 0) { _gpgme_io_close (rp[0]); _gpgme_io_close (rp[1]); return gpg_error_from_syserror (); } do { nread = _gpgme_io_read (rp[0], linebuf + linelen, LINELENGTH - linelen - 1); if (nread > 0) { char *line; const char *lastmark = NULL; size_t nused; linelen += nread; linebuf[linelen] = '\0'; for (line=linebuf; (mark = strchr (line, '\n')); line = mark+1 ) { lastmark = mark; if (mark > line && mark[-1] == '\r') mark[-1] = '\0'; else mark[0] = '\0'; /* Got a full line. Due to the CR removal code (which occurs only on Windows) we might be one-off and thus would see empty lines. Don't pass them to the callback. */ err = *line? (*cb) (hook, line) : 0; if (err) goto leave; } nused = lastmark? (lastmark + 1 - linebuf) : 0; memmove (linebuf, linebuf + nused, linelen - nused); linelen -= nused; } } while (nread > 0 && linelen < LINELENGTH - 1); if (!err && nread < 0) err = gpg_error_from_syserror (); if (!err && nread > 0) err = gpg_error (GPG_ERR_LINE_TOO_LONG); leave: _gpgme_io_close (rp[0]); return err; }