6ba49c42008582b6df3058292e1f157a27be526f
[gpgme.git] / src / engine-g13.c
1 /* engine-g13.c - G13 engine.
2    Copyright (C) 2000 Werner Koch (dd9jn)
3    Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 2009 g10 Code GmbH
4  
5    This file is part of GPGME.
6
7    GPGME is free software; you can redistribute it and/or modify it
8    under the terms of the GNU Lesser General Public License as
9    published by the Free Software Foundation; either version 2.1 of
10    the License, or (at your option) any later version.
11    
12    GPGME is distributed in the hope that it will be useful, but
13    WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15    Lesser General Public License for more details.
16    
17    You should have received a copy of the GNU Lesser General Public
18    License along with this program; if not, write to the Free Software
19    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
20    02111-1307, USA.  */
21
22 #if HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <stdlib.h>
27 #include <string.h>
28 #ifdef HAVE_SYS_TYPES_H
29 # include <sys/types.h>
30 #endif
31 #include <assert.h>
32 #ifdef HAVE_UNISTD_H
33 # include <unistd.h>
34 #endif
35 #ifdef HAVE_LOCALE_H
36 #include <locale.h>
37 #endif
38 #include <fcntl.h> /* FIXME */
39 #include <errno.h>
40
41 #include "gpgme.h"
42 #include "util.h"
43 #include "ops.h"
44 #include "wait.h"
45 #include "priv-io.h"
46 #include "sema.h"
47
48 #include "assuan.h"
49 #include "debug.h"
50
51 #include "engine-backend.h"
52
53 \f
54 typedef struct
55 {
56   int fd;       /* FD we talk about.  */
57   int server_fd;/* Server FD for this connection.  */
58   int dir;      /* Inbound/Outbound, maybe given implicit?  */
59   void *data;   /* Handler-specific data.  */
60   void *tag;    /* ID from the user for gpgme_remove_io_callback.  */
61   char server_fd_str[15]; /* Same as SERVER_FD but as a string.  We
62                              need this because _gpgme_io_fd2str can't
63                              be used on a closed descriptor.  */
64 } iocb_data_t;
65
66
67 struct engine_g13
68 {
69   assuan_context_t assuan_ctx;
70
71   int lc_ctype_set;
72   int lc_messages_set;
73
74   iocb_data_t status_cb;
75
76   struct gpgme_io_cbs io_cbs;
77
78   /* User provided callbacks.  */
79   struct {
80     gpgme_assuan_data_cb_t data_cb;
81     void *data_cb_value;
82
83     gpgme_assuan_inquire_cb_t inq_cb;
84     void *inq_cb_value;
85
86     gpgme_assuan_status_cb_t status_cb;
87     void *status_cb_value;
88   } user;
89 };
90
91 typedef struct engine_g13 *engine_g13_t;
92
93
94 static void g13_io_event (void *engine, 
95                             gpgme_event_io_t type, void *type_data);
96
97
98 \f
99 static char *
100 g13_get_version (const char *file_name)
101 {
102   return _gpgme_get_program_version (file_name ? file_name
103                                      : _gpgme_get_g13_path ());
104 }
105
106
107 static const char *
108 g13_get_req_version (void)
109 {
110   return NEED_G13_VERSION;
111 }
112
113 \f
114 static void
115 close_notify_handler (int fd, void *opaque)
116 {
117   engine_g13_t g13 = opaque;
118
119   assert (fd != -1);
120   if (g13->status_cb.fd == fd)
121     {
122       if (g13->status_cb.tag)
123         (*g13->io_cbs.remove) (g13->status_cb.tag);
124       g13->status_cb.fd = -1;
125       g13->status_cb.tag = NULL;
126     }
127 }
128
129
130 /* This is the default inquiry callback.  We use it to handle the
131    Pinentry notifications.  */
132 static gpgme_error_t
133 default_inq_cb (engine_g13_t g13, const char *keyword, const char *args)
134 {
135   gpg_error_t err;
136
137   if (!strcmp (keyword, "PINENTRY_LAUNCHED"))
138     {
139       _gpgme_allow_set_foreground_window ((pid_t)strtoul (args, NULL, 10));
140     }
141
142   if (g13->user.inq_cb)
143     {
144       gpgme_data_t data = NULL;
145
146       err = g13->user.inq_cb (g13->user.inq_cb_value,
147                                 keyword, args, &data);
148       if (!err && data)
149         {
150           /* FIXME: Returning data is not yet implemented.  However we
151              need to allow the caller to cleanup his data object.
152              Thus we run the callback in finish mode immediately.  */
153           err = g13->user.inq_cb (g13->user.inq_cb_value,
154                                   NULL, NULL, &data);
155         }
156     }
157   else
158     err = 0;
159
160   return err;
161 }
162
163
164 static gpgme_error_t
165 g13_cancel (void *engine)
166 {
167   engine_g13_t g13 = engine;
168
169   if (!g13)
170     return gpg_error (GPG_ERR_INV_VALUE);
171
172   if (g13->status_cb.fd != -1)
173     _gpgme_io_close (g13->status_cb.fd);
174
175   if (g13->assuan_ctx)
176     {
177       assuan_release (g13->assuan_ctx);
178       g13->assuan_ctx = NULL;
179     }
180
181   return 0;
182 }
183
184
185 static gpgme_error_t
186 g13_cancel_op (void *engine)
187 {
188   engine_g13_t g13 = engine;
189
190   if (!g13)
191     return gpg_error (GPG_ERR_INV_VALUE);
192
193   if (g13->status_cb.fd != -1)
194     _gpgme_io_close (g13->status_cb.fd);
195
196   return 0;
197 }
198
199
200 static void
201 g13_release (void *engine)
202 {
203   engine_g13_t g13 = engine;
204
205   if (!g13)
206     return;
207
208   g13_cancel (engine);
209
210   free (g13);
211 }
212
213
214 static gpgme_error_t
215 g13_new (void **engine, const char *file_name, const char *home_dir)
216 {
217   gpgme_error_t err = 0;
218   engine_g13_t g13;
219   int argc;
220   const char *argv[5];
221   char *dft_display = NULL;
222   char dft_ttyname[64];
223   char *dft_ttytype = NULL;
224   char *optstr;
225
226   g13 = calloc (1, sizeof *g13);
227   if (!g13)
228     return gpg_error_from_errno (errno);
229
230   g13->status_cb.fd = -1;
231   g13->status_cb.dir = 1;
232   g13->status_cb.tag = 0;
233   g13->status_cb.data = g13;
234
235   argc = 0;
236   argv[argc++] = "g13";
237   if (home_dir)
238     {
239       argv[argc++] = "--homedir";
240       argv[argc++] = home_dir;
241     }
242   argv[argc++] = "--server";
243   argv[argc++] = NULL;
244
245   err = assuan_new_ext (&g13->assuan_ctx, GPG_ERR_SOURCE_GPGME,
246                         &_gpgme_assuan_malloc_hooks, _gpgme_assuan_log_cb,
247                         NULL);
248   if (err)
249     goto leave;
250   assuan_ctx_set_system_hooks (g13->assuan_ctx, &_gpgme_assuan_system_hooks);
251
252 #if USE_DESCRIPTOR_PASSING
253   err = assuan_pipe_connect
254     (g13->assuan_ctx, file_name ? file_name : _gpgme_get_g13_path (),
255      argv, NULL, NULL, NULL, ASSUAN_PIPE_CONNECT_FDPASSING);
256 #else
257   err = assuan_pipe_connect
258     (g13->assuan_ctx, file_name ? file_name : _gpgme_get_g13_path (),
259      argv, NULL, NULL, NULL, 0);
260 #endif
261   if (err)
262     goto leave;
263
264   err = _gpgme_getenv ("DISPLAY", &dft_display);
265   if (err)
266     goto leave;
267   if (dft_display)
268     {
269       if (asprintf (&optstr, "OPTION display=%s", dft_display) < 0)
270         {
271           free (dft_display);
272           err = gpg_error_from_errno (errno);
273           goto leave;
274         }
275       free (dft_display);
276
277       err = assuan_transact (g13->assuan_ctx, optstr, NULL, NULL, NULL,
278                              NULL, NULL, NULL);
279       free (optstr);
280       if (err)
281         goto leave;
282     }
283
284   if (isatty (1))
285     {
286       int rc;
287
288       rc = ttyname_r (1, dft_ttyname, sizeof (dft_ttyname));
289       if (rc)
290         {
291           err = gpg_error_from_errno (rc);
292           goto leave;
293         }
294       else
295         {
296           if (asprintf (&optstr, "OPTION ttyname=%s", dft_ttyname) < 0)
297             {
298               err = gpg_error_from_errno (errno);
299               goto leave;
300             }
301           err = assuan_transact (g13->assuan_ctx, optstr, NULL, NULL, NULL,
302                                  NULL, NULL, NULL);
303           free (optstr);
304           if (err)
305             goto leave;
306
307           err = _gpgme_getenv ("TERM", &dft_ttytype);
308           if (err)
309             goto leave;
310           if (dft_ttytype)
311             {
312               if (asprintf (&optstr, "OPTION ttytype=%s", dft_ttytype) < 0)
313                 {
314                   free (dft_ttytype);
315                   err = gpg_error_from_errno (errno);
316                   goto leave;
317                 }
318               free (dft_ttytype);
319
320               err = assuan_transact (g13->assuan_ctx, optstr, NULL, NULL,
321                                      NULL, NULL, NULL, NULL);
322               free (optstr);
323               if (err)
324                 goto leave;
325             }
326         }
327     }
328
329 #ifdef HAVE_W32_SYSTEM
330   /* Under Windows we need to use AllowSetForegroundWindow.  Tell
331      g13 to tell us when it needs it.  */
332   if (!err)
333     {
334       err = assuan_transact (g13->assuan_ctx, "OPTION allow-pinentry-notify",
335                              NULL, NULL, NULL, NULL, NULL, NULL);
336       if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION)
337         err = 0; /* This is a new feature of g13.  */
338     }
339 #endif /*HAVE_W32_SYSTEM*/
340
341  leave:
342
343   if (err)
344     g13_release (g13);
345   else
346     *engine = g13;
347
348   return err;
349 }
350
351
352 static gpgme_error_t
353 g13_set_locale (void *engine, int category, const char *value)
354 {
355   engine_g13_t g13 = engine;
356   gpgme_error_t err;
357   char *optstr;
358   char *catstr;
359
360   /* FIXME: If value is NULL, we need to reset the option to default.
361      But we can't do this.  So we error out here.  G13 needs support
362      for this.  */
363   if (0)
364     ;
365 #ifdef LC_CTYPE
366   else if (category == LC_CTYPE)
367     {
368       catstr = "lc-ctype";
369       if (!value && g13->lc_ctype_set)
370         return gpg_error (GPG_ERR_INV_VALUE);
371       if (value)
372         g13->lc_ctype_set = 1;
373     }
374 #endif
375 #ifdef LC_MESSAGES
376   else if (category == LC_MESSAGES)
377     {
378       catstr = "lc-messages";
379       if (!value && g13->lc_messages_set)
380         return gpg_error (GPG_ERR_INV_VALUE);
381       if (value)
382         g13->lc_messages_set = 1;
383     }
384 #endif /* LC_MESSAGES */
385   else
386     return gpg_error (GPG_ERR_INV_VALUE);
387
388   /* FIXME: Reset value to default.  */
389   if (!value) 
390     return 0;
391
392   if (asprintf (&optstr, "OPTION %s=%s", catstr, value) < 0)
393     err = gpg_error_from_errno (errno);
394   else
395     {
396       err = assuan_transact (g13->assuan_ctx, optstr, NULL, NULL,
397                              NULL, NULL, NULL, NULL);
398       free (optstr);
399     }
400
401   return err;
402 }
403
404
405 #if USE_DESCRIPTOR_PASSING
406 static gpgme_error_t
407 g13_assuan_simple_command (assuan_context_t ctx, char *cmd,
408                            engine_status_handler_t status_fnc,
409                            void *status_fnc_value)
410 {
411   gpg_error_t err;
412   char *line;
413   size_t linelen;
414
415   err = assuan_write_line (ctx, cmd);
416   if (err)
417     return err;
418
419   do
420     {
421       err = assuan_read_line (ctx, &line, &linelen);
422       if (err)
423         return err;
424
425       if (*line == '#' || !linelen)
426         continue;
427
428       if (linelen >= 2
429           && line[0] == 'O' && line[1] == 'K'
430           && (line[2] == '\0' || line[2] == ' '))
431         return 0;
432       else if (linelen >= 4
433           && line[0] == 'E' && line[1] == 'R' && line[2] == 'R'
434           && line[3] == ' ')
435         err = atoi (&line[4]);
436       else if (linelen >= 2
437                && line[0] == 'S' && line[1] == ' ')
438         {
439           char *rest;
440
441           rest = strchr (line + 2, ' ');
442           if (!rest)
443             rest = line + linelen; /* set to an empty string */
444           else
445             *(rest++) = 0;
446
447           /* Nothing to do with status lines.  */
448         }
449       else
450         err = gpg_error (GPG_ERR_GENERAL);
451     }
452   while (!err);
453
454   return err;
455 }
456 #endif
457
458
459 static gpgme_error_t
460 status_handler (void *opaque, int fd)
461 {
462   struct io_cb_data *data = (struct io_cb_data *) opaque;
463   engine_g13_t g13 = (engine_g13_t) data->handler_value;
464   gpgme_error_t err = 0;
465   char *line;
466   size_t linelen;
467
468   do
469     {
470       err = assuan_read_line (g13->assuan_ctx, &line, &linelen);
471       if (err)
472         {
473           /* Try our best to terminate the connection friendly.  */
474           /*      assuan_write_line (g13->assuan_ctx, "BYE"); */
475           TRACE2 (DEBUG_CTX, "gpgme:status_handler", g13,
476                   "fd 0x%x: error reading assuan line: %s",
477                   fd, gpg_strerror (err));
478         }
479       else if (linelen >= 3
480                && line[0] == 'E' && line[1] == 'R' && line[2] == 'R'
481                && (line[3] == '\0' || line[3] == ' '))
482         {
483           if (line[3] == ' ')
484             err = atoi (&line[4]);
485           if (! err)
486             err = gpg_error (GPG_ERR_GENERAL);
487           TRACE2 (DEBUG_CTX, "gpgme:status_handler", g13,
488                   "fd 0x%x: ERR line: %s",
489                   fd, err ? gpg_strerror (err) : "ok");
490           
491           /* Command execution errors are not fatal, as we use
492              a session based protocol.  */
493           data->op_err = err;
494
495           /* The caller will do the rest (namely, call cancel_op,
496              which closes status_fd).  */
497           return 0;
498         }
499       else if (linelen >= 2
500                && line[0] == 'O' && line[1] == 'K'
501                && (line[2] == '\0' || line[2] == ' '))
502         {
503           TRACE1 (DEBUG_CTX, "gpgme:status_handler", g13,
504                   "fd 0x%x: OK line", fd);
505
506           _gpgme_io_close (g13->status_cb.fd);
507           return 0;
508         }
509       else if (linelen > 2
510                && line[0] == 'D' && line[1] == ' ')
511         {
512           /* We are using the colon handler even for plain inline data
513              - strange name for that function but for historic reasons
514              we keep it.  */
515           /* FIXME We can't use this for binary data because we
516              assume this is a string.  For the current usage of colon
517              output it is correct.  */
518           char *src = line + 2;
519           char *end = line + linelen;
520           char *dst = src;
521
522           linelen = 0;
523           while (src < end)
524             {
525               if (*src == '%' && src + 2 < end)
526                 {
527                   /* Handle escaped characters.  */
528                   ++src;
529                   *dst++ = _gpgme_hextobyte (src);
530                   src += 2;
531                 }
532               else
533                 *dst++ = *src++;
534
535               linelen++;
536             }
537
538           src = line + 2;
539           if (linelen && g13->user.data_cb)
540             err = g13->user.data_cb (g13->user.data_cb_value,
541                                        src, linelen);
542           else
543             err = 0;
544
545           TRACE2 (DEBUG_CTX, "gpgme:g13_status_handler", g13,
546                   "fd 0x%x: D inlinedata; status from cb: %s",
547                   fd, (g13->user.data_cb ?
548                        (err? gpg_strerror (err):"ok"):"no callback"));
549
550         }
551       else if (linelen > 2
552                && line[0] == 'S' && line[1] == ' ')
553         {
554           char *src;
555           char *args;
556
557           src = line + 2;
558           while (*src == ' ')
559             src++;
560           
561           args = strchr (line + 2, ' ');
562           if (!args)
563             args = line + linelen; /* set to an empty string */
564           else
565             *(args++) = 0;
566
567           while (*args == ' ')
568             args++;
569
570           if (g13->user.status_cb)
571             err = g13->user.status_cb (g13->user.status_cb_value,
572                                        src, args);
573           else
574             err = 0;
575
576           TRACE3 (DEBUG_CTX, "gpgme:g13_status_handler", g13,
577                   "fd 0x%x: S line (%s) - status from cb: %s",
578                   fd, line+2, (g13->user.status_cb ?
579                                (err? gpg_strerror (err):"ok"):"no callback"));
580         }
581       else if (linelen >= 7
582                && line[0] == 'I' && line[1] == 'N' && line[2] == 'Q'
583                && line[3] == 'U' && line[4] == 'I' && line[5] == 'R'
584                && line[6] == 'E' 
585                && (line[7] == '\0' || line[7] == ' '))
586         {
587           char *src;
588           char *args;
589           
590           for (src=line+7; *src == ' '; src++)
591             ;
592
593           args = strchr (src, ' ');
594           if (!args)
595             args = line + linelen; /* Let it point to an empty string.  */
596           else
597             *(args++) = 0;
598
599           while (*args == ' ')
600             args++;
601
602           err = default_inq_cb (g13, src, args);
603           if (!err) 
604             {
605               /* Flush and send END.  */
606               err = assuan_send_data (g13->assuan_ctx, NULL, 0);
607             }
608           else if (gpg_err_code (err) == GPG_ERR_ASS_CANCELED)
609             {
610               /* Flush and send CANcel.  */
611               err = assuan_send_data (g13->assuan_ctx, NULL, 1);
612             }
613           assuan_write_line (g13->assuan_ctx, "END");
614         }
615     }
616   while (!err && assuan_pending_line (g13->assuan_ctx));
617   
618   return err;
619 }
620   
621
622 static gpgme_error_t
623 add_io_cb (engine_g13_t g13, iocb_data_t *iocbd, gpgme_io_cb_t handler)
624 {
625   gpgme_error_t err;
626
627   TRACE_BEG2 (DEBUG_ENGINE, "engine-g13:add_io_cb", g13,
628               "fd %d, dir %d", iocbd->fd, iocbd->dir);
629   err = (*g13->io_cbs.add) (g13->io_cbs.add_priv,
630                               iocbd->fd, iocbd->dir,
631                               handler, iocbd->data, &iocbd->tag);
632   if (err)
633     return TRACE_ERR (err);
634   if (!iocbd->dir)
635     /* FIXME Kludge around poll() problem.  */
636     err = _gpgme_io_set_nonblocking (iocbd->fd);
637   return TRACE_ERR (err);
638 }
639
640
641 static gpgme_error_t
642 start (engine_g13_t g13, const char *command)
643 {
644   gpgme_error_t err;
645   assuan_fd_t afdlist[5];
646   int fdlist[5];
647   int nfds;
648   int i;
649
650   /* We need to know the fd used by assuan for reads.  We do this by
651      using the assumption that the first returned fd from
652      assuan_get_active_fds() is always this one.  */
653   nfds = assuan_get_active_fds (g13->assuan_ctx, 0 /* read fds */,
654                                 afdlist, DIM (afdlist));
655   if (nfds < 1)
656     return gpg_error (GPG_ERR_GENERAL); /* FIXME */
657   /* For now... */
658   for (i = 0; i < nfds; i++)
659     fdlist[i] = (int) afdlist[i];
660
661   /* We "duplicate" the file descriptor, so we can close it here (we
662      can't close fdlist[0], as that is closed by libassuan, and
663      closing it here might cause libassuan to close some unrelated FD
664      later).  Alternatively, we could special case status_fd and
665      register/unregister it manually as needed, but this increases
666      code duplication and is more complicated as we can not use the
667      close notifications etc.  A third alternative would be to let
668      Assuan know that we closed the FD, but that complicates the
669      Assuan interface.  */
670
671   g13->status_cb.fd = _gpgme_io_dup (fdlist[0]);
672   if (g13->status_cb.fd < 0)
673     return gpg_error_from_syserror ();
674
675   if (_gpgme_io_set_close_notify (g13->status_cb.fd,
676                                   close_notify_handler, g13))
677     {
678       _gpgme_io_close (g13->status_cb.fd);
679       g13->status_cb.fd = -1;
680       return gpg_error (GPG_ERR_GENERAL);
681     }
682
683   err = add_io_cb (g13, &g13->status_cb, status_handler);
684   if (!err)
685     err = assuan_write_line (g13->assuan_ctx, command);
686
687   if (!err)
688     g13_io_event (g13, GPGME_EVENT_START, NULL);
689
690   return err;
691 }
692
693
694 #if USE_DESCRIPTOR_PASSING
695 static gpgme_error_t
696 g13_reset (void *engine)
697 {
698   engine_g13_t g13 = engine;
699
700   /* We must send a reset because we need to reset the list of
701      signers.  Note that RESET does not reset OPTION commands. */
702   return g13_assuan_simple_command (g13->assuan_ctx, "RESET", NULL, NULL);
703 }
704 #endif
705
706
707 static gpgme_error_t
708 g13_transact (void *engine,
709                 const char *command,
710                 gpgme_assuan_data_cb_t data_cb,
711                 void *data_cb_value,
712                 gpgme_assuan_inquire_cb_t inq_cb,
713                 void *inq_cb_value,
714                 gpgme_assuan_status_cb_t status_cb,
715                 void *status_cb_value)
716 {
717   engine_g13_t g13 = engine;
718   gpgme_error_t err;
719
720   if (!g13 || !command || !*command)
721     return gpg_error (GPG_ERR_INV_VALUE);
722
723   g13->user.data_cb = data_cb;
724   g13->user.data_cb_value = data_cb_value;
725   g13->user.inq_cb = inq_cb;
726   g13->user.inq_cb_value = inq_cb_value;
727   g13->user.status_cb = status_cb;
728   g13->user.status_cb_value = status_cb_value;
729
730   err = start (g13, command);
731   return err;
732 }
733
734
735
736 static void
737 g13_set_io_cbs (void *engine, gpgme_io_cbs_t io_cbs)
738 {
739   engine_g13_t g13 = engine;
740   g13->io_cbs = *io_cbs;
741 }
742
743
744 static void
745 g13_io_event (void *engine, gpgme_event_io_t type, void *type_data)
746 {
747   engine_g13_t g13 = engine;
748
749   TRACE3 (DEBUG_ENGINE, "gpgme:g13_io_event", g13,
750           "event %p, type %d, type_data %p",
751           g13->io_cbs.event, type, type_data);
752   if (g13->io_cbs.event)
753     (*g13->io_cbs.event) (g13->io_cbs.event_priv, type, type_data);
754 }
755
756
757 struct engine_ops _gpgme_engine_ops_g13 =
758   {
759     /* Static functions.  */
760     _gpgme_get_g13_path,
761     NULL,
762     g13_get_version,
763     g13_get_req_version,
764     g13_new,
765
766     /* Member functions.  */
767     g13_release,
768 #if USE_DESCRIPTOR_PASSING
769     g13_reset,
770 #else
771     NULL,                       /* reset */
772 #endif
773     NULL,               /* set_status_handler */
774     NULL,               /* set_command_handler */
775     NULL,               /* set_colon_line_handler */
776     g13_set_locale,
777     NULL,               /* set_protocol */
778     NULL,               /* decrypt */
779     NULL,               /* decrypt_verify */
780     NULL,               /* delete */
781     NULL,               /* edit */
782     NULL,               /* encrypt */
783     NULL,               /* encrypt_sign */
784     NULL,               /* export */
785     NULL,               /* export_ext */
786     NULL,               /* genkey */
787     NULL,               /* import */
788     NULL,               /* keylist */
789     NULL,               /* keylist_ext */
790     NULL,               /* sign */
791     NULL,               /* trustlist */
792     NULL,               /* verify */
793     NULL,               /* getauditlog */
794     g13_transact,
795     NULL,               /* conf_load */
796     NULL,               /* conf_save */
797     g13_set_io_cbs,
798     g13_io_event,
799     g13_cancel,
800     g13_cancel_op,
801   };