e83a2782425f07ccb69c1559a8f8210d145b83c1
[gpgme.git] / gpgme / w32-io.c
1 /* w32-io.c - W32 API I/O functions.
2    Copyright (C) 2000 Werner Koch (dd9jn)
3    Copyright (C) 2001, 2002, 2003, 2004, 2007 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 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <assert.h>
29 #include <errno.h>
30 #include <signal.h>
31 #include <fcntl.h>
32 #include <sys/time.h>
33 #include <sys/types.h>
34 #include <windows.h>
35 #include <io.h>
36
37 #include "util.h"
38 #include "sema.h"
39 #include "priv-io.h"
40 #include "debug.h"
41
42 /* We assume that a HANDLE can be represented by an int which should
43    be true for all i386 systems (HANDLE is defined as void *) and
44    these are the only systems for which Windows is available.  Further
45    we assume that -1 denotes an invalid handle.  */
46
47 #define fd_to_handle(a)  ((HANDLE)(a))
48 #define handle_to_fd(a)  ((int)(a))
49 #define pid_to_handle(a) ((HANDLE)(a))
50 #define handle_to_pid(a) ((int)(a))
51
52 #define READBUF_SIZE 4096
53 #define WRITEBUF_SIZE 4096
54 #define PIPEBUF_SIZE  4096
55 #define MAX_READERS 20
56 #define MAX_WRITERS 20
57
58 static struct
59 {
60   int inuse;
61   int fd;
62   _gpgme_close_notify_handler_t handler;
63   void *value;
64 } notify_table[256];
65 DEFINE_STATIC_LOCK (notify_table_lock);
66
67
68 struct reader_context_s
69 {
70   HANDLE file_hd;
71   HANDLE thread_hd;     
72   int refcount;
73
74   DECLARE_LOCK (mutex);
75
76   int stop_me;
77   int eof;
78   int eof_shortcut;
79   int error;
80   int error_code;
81   
82   /* This is manually reset.  */
83   HANDLE have_data_ev;
84   /* This is automatically reset.  */
85   HANDLE have_space_ev;
86   HANDLE stopped;
87   size_t readpos, writepos;
88   char buffer[READBUF_SIZE];
89 };
90
91
92 static struct
93 {
94   volatile int used;
95   int fd;
96   struct reader_context_s *context;
97 } reader_table[MAX_READERS];
98 static int reader_table_size= MAX_READERS;
99 DEFINE_STATIC_LOCK (reader_table_lock);
100
101
102 struct writer_context_s
103 {
104   HANDLE file_hd;
105   HANDLE thread_hd;     
106   int refcount;
107
108   DECLARE_LOCK (mutex);
109   
110   int stop_me;
111   int error;
112   int error_code;
113
114   /* This is manually reset.  */
115   HANDLE have_data;
116   HANDLE is_empty;
117   HANDLE stopped;
118   size_t nbytes; 
119   char buffer[WRITEBUF_SIZE];
120 };
121
122
123 static struct
124 {
125   volatile int used;
126   int fd;
127   struct writer_context_s *context;
128 } writer_table[MAX_WRITERS];
129 static int writer_table_size= MAX_WRITERS;
130 DEFINE_STATIC_LOCK (writer_table_lock);
131
132
133 static int
134 get_desired_thread_priority (void)
135 {
136   int value;
137
138   if (!_gpgme_get_conf_int ("IOThreadPriority", &value))
139     {
140       value = THREAD_PRIORITY_HIGHEST;
141       TRACE1 (DEBUG_SYSIO, "gpgme:get_desired_thread_priority", 0,
142               "%d (default)", value);
143     }
144   else
145     {
146       TRACE1 (DEBUG_SYSIO, "gpgme:get_desired_thread_priority", 0,
147               "%d (configured)", value);
148     }
149   return value;
150 }
151
152
153 static HANDLE
154 set_synchronize (HANDLE hd)
155 {
156   HANDLE new_hd;
157
158   /* For NT we have to set the sync flag.  It seems that the only way
159      to do it is by duplicating the handle.  Tsss...  */
160   if (!DuplicateHandle (GetCurrentProcess (), hd,
161                         GetCurrentProcess (), &new_hd,
162                         EVENT_MODIFY_STATE | SYNCHRONIZE, FALSE, 0))
163     {
164       TRACE1 (DEBUG_SYSIO, "gpgme:set_synchronize", hd,
165               "DuplicateHandle failed: ec=%d", (int) GetLastError ());
166       /* FIXME: Should translate the error code.  */
167       errno = EIO;
168       return INVALID_HANDLE_VALUE;
169     }
170
171   CloseHandle (hd);
172   return new_hd;
173 }
174
175
176 static DWORD CALLBACK 
177 reader (void *arg)
178 {
179   struct reader_context_s *ctx = arg;
180   int nbytes;
181   DWORD nread;
182   TRACE_BEG1 (DEBUG_SYSIO, "gpgme:reader", ctx->file_hd,
183               "thread=%p", ctx->thread_hd);
184
185   for (;;)
186     {
187       LOCK (ctx->mutex);
188       /* Leave a 1 byte gap so that we can see whether it is empty or
189          full.  */
190       if ((ctx->writepos + 1) % READBUF_SIZE == ctx->readpos)
191         { 
192           /* Wait for space.  */
193           if (!ResetEvent (ctx->have_space_ev))
194             TRACE_LOG1 ("ResetEvent failed: ec=%d", (int) GetLastError ());
195           UNLOCK (ctx->mutex);
196           TRACE_LOG ("waiting for space");
197           WaitForSingleObject (ctx->have_space_ev, INFINITE);
198           TRACE_LOG ("got space");
199           LOCK (ctx->mutex);
200         }
201       if (ctx->stop_me)
202         {
203           UNLOCK (ctx->mutex);
204           break;
205         }
206       nbytes = (ctx->readpos + READBUF_SIZE
207                 - ctx->writepos - 1) % READBUF_SIZE;
208       if (nbytes > READBUF_SIZE - ctx->writepos)
209         nbytes = READBUF_SIZE - ctx->writepos;
210       UNLOCK (ctx->mutex);
211       
212       TRACE_LOG1 ("reading %d bytes", nbytes);
213       if (!ReadFile (ctx->file_hd,
214                      ctx->buffer + ctx->writepos, nbytes, &nread, NULL))
215         {
216           ctx->error_code = (int) GetLastError ();
217           if (ctx->error_code == ERROR_BROKEN_PIPE)
218             {
219               ctx->eof = 1;
220               TRACE_LOG ("got EOF (broken pipe)");
221             }
222           else
223             {
224               ctx->error = 1;
225               TRACE_LOG1 ("read error: ec=%d", ctx->error_code);
226             }
227           break;
228         }
229       if (!nread)
230         {
231           ctx->eof = 1;
232           TRACE_LOG ("got eof");
233           break;
234         }
235       TRACE_LOG1 ("got %u bytes", nread);
236       
237       LOCK (ctx->mutex);
238       if (ctx->stop_me)
239         {
240           UNLOCK (ctx->mutex);
241           break;
242         }
243       ctx->writepos = (ctx->writepos + nread) % READBUF_SIZE;
244       if (!SetEvent (ctx->have_data_ev))
245         TRACE_LOG1 ("SetEvent failed: ec=%d", (int) GetLastError ());
246       UNLOCK (ctx->mutex);
247     }
248   /* Indicate that we have an error or EOF.  */
249   if (!SetEvent (ctx->have_data_ev))
250     TRACE_LOG1 ("SetEvent failed: ec=%d", (int) GetLastError ());
251   SetEvent (ctx->stopped);
252   
253   return TRACE_SUC ();
254 }
255
256
257 static struct reader_context_s *
258 create_reader (HANDLE fd)
259 {
260   struct reader_context_s *ctx;
261   SECURITY_ATTRIBUTES sec_attr;
262   DWORD tid;
263
264   TRACE_BEG (DEBUG_SYSIO, "gpgme:create_reader", fd);
265
266   memset (&sec_attr, 0, sizeof sec_attr);
267   sec_attr.nLength = sizeof sec_attr;
268   sec_attr.bInheritHandle = FALSE;
269   
270   ctx = calloc (1, sizeof *ctx);
271   if (!ctx)
272     {
273       TRACE_SYSERR (errno);
274       return NULL;
275     }
276
277   ctx->file_hd = fd;
278   ctx->refcount = 1;
279   ctx->have_data_ev = CreateEvent (&sec_attr, TRUE, FALSE, NULL);
280   if (ctx->have_data_ev)
281     ctx->have_space_ev = CreateEvent (&sec_attr, FALSE, TRUE, NULL);
282   if (ctx->have_space_ev)
283     ctx->stopped = CreateEvent (&sec_attr, TRUE, FALSE, NULL);
284   if (!ctx->have_data_ev || !ctx->have_space_ev || !ctx->stopped)
285     {
286       TRACE_LOG1 ("CreateEvent failed: ec=%d", (int) GetLastError ());
287       if (ctx->have_data_ev)
288         CloseHandle (ctx->have_data_ev);
289       if (ctx->have_space_ev)
290         CloseHandle (ctx->have_space_ev);
291       if (ctx->stopped)
292         CloseHandle (ctx->stopped);
293       free (ctx);
294       /* FIXME: Translate the error code.  */
295       TRACE_SYSERR (EIO);
296       return NULL;
297     }
298
299   ctx->have_data_ev = set_synchronize (ctx->have_data_ev);
300   INIT_LOCK (ctx->mutex);
301
302   ctx->thread_hd = CreateThread (&sec_attr, 0, reader, ctx, 0, &tid);
303   if (!ctx->thread_hd)
304     {
305       TRACE_LOG1 ("CreateThread failed: ec=%d", (int) GetLastError ());
306       DESTROY_LOCK (ctx->mutex);
307       if (ctx->have_data_ev)
308         CloseHandle (ctx->have_data_ev);
309       if (ctx->have_space_ev)
310         CloseHandle (ctx->have_space_ev);
311       if (ctx->stopped)
312         CloseHandle (ctx->stopped);
313       free (ctx);
314       TRACE_SYSERR (EIO);
315       return NULL;
316     }    
317   else
318     {
319       /* We set the priority of the thread higher because we know that
320          it only runs for a short time.  This greatly helps to
321          increase the performance of the I/O.  */
322       SetThreadPriority (ctx->thread_hd, get_desired_thread_priority ());
323     }
324
325   TRACE_SUC ();
326   return ctx;
327 }
328
329
330 static void
331 destroy_reader (struct reader_context_s *ctx)
332 {
333   LOCK (ctx->mutex);
334   ctx->refcount--;
335   if (ctx->refcount != 0)
336     {
337       UNLOCK (ctx->mutex);
338       return;
339     }
340   ctx->stop_me = 1;
341   if (ctx->have_space_ev) 
342     SetEvent (ctx->have_space_ev);
343   UNLOCK (ctx->mutex);
344
345   TRACE1 (DEBUG_SYSIO, "gpgme:destroy_reader", ctx->file_hd,
346           "waiting for termination of thread %p", ctx->thread_hd);
347   WaitForSingleObject (ctx->stopped, INFINITE);
348   TRACE1 (DEBUG_SYSIO, "gpgme:destroy_reader", ctx->file_hd,
349           "thread %p has terminated", ctx->thread_hd);
350     
351   if (ctx->stopped)
352     CloseHandle (ctx->stopped);
353   if (ctx->have_data_ev)
354     CloseHandle (ctx->have_data_ev);
355   if (ctx->have_space_ev)
356     CloseHandle (ctx->have_space_ev);
357   CloseHandle (ctx->thread_hd);
358   DESTROY_LOCK (ctx->mutex);
359   free (ctx);
360 }
361
362
363 /* Find a reader context or create a new one.  Note that the reader
364    context will last until a _gpgme_io_close.  */
365 static struct reader_context_s *
366 find_reader (int fd, int start_it)
367 {
368   struct reader_context_s *rd = NULL;
369   int i;
370
371   LOCK (reader_table_lock);
372   for (i = 0; i < reader_table_size; i++)
373     if (reader_table[i].used && reader_table[i].fd == fd)
374       rd = reader_table[i].context;
375
376   if (rd || !start_it)
377     {
378       UNLOCK (reader_table_lock);
379       return rd;
380     }
381
382   for (i = 0; i < reader_table_size; i++)
383     if (!reader_table[i].used)
384       break;
385
386   if (i != reader_table_size)
387     {
388       rd = create_reader (fd_to_handle (fd));
389       reader_table[i].fd = fd;
390       reader_table[i].context = rd;
391       reader_table[i].used = 1;
392     }
393
394   UNLOCK (reader_table_lock);
395   return rd;
396 }
397
398
399 static void
400 kill_reader (int fd)
401 {
402   int i;
403
404   LOCK (reader_table_lock);
405   for (i = 0; i < reader_table_size; i++)
406     {
407       if (reader_table[i].used && reader_table[i].fd == fd)
408         {
409           destroy_reader (reader_table[i].context);
410           reader_table[i].context = NULL;
411           reader_table[i].used = 0;
412           break;
413         }
414     }
415   UNLOCK (reader_table_lock);
416 }
417
418
419 int
420 _gpgme_io_read (int fd, void *buffer, size_t count)
421 {
422   int nread;
423   struct reader_context_s *ctx;
424   TRACE_BEG2 (DEBUG_SYSIO, "_gpgme_io_read", fd,
425               "buffer=%p, count=%u", buffer, count);
426   
427   ctx = find_reader (fd, 1);
428   if (!ctx)
429     {
430       errno = EBADF;
431       return TRACE_SYSRES (-1);
432     }
433   if (ctx->eof_shortcut)
434     return TRACE_SYSRES (0);
435
436   LOCK (ctx->mutex);
437   if (ctx->readpos == ctx->writepos && !ctx->error)
438     {
439       /* No data available.  */
440       UNLOCK (ctx->mutex);
441       TRACE_LOG1 ("waiting for data from thread %p", ctx->thread_hd);
442       WaitForSingleObject (ctx->have_data_ev, INFINITE);
443       TRACE_LOG1 ("data from thread %p available", ctx->thread_hd);
444       LOCK (ctx->mutex);
445     }
446   
447   if (ctx->readpos == ctx->writepos || ctx->error)
448     {
449       UNLOCK (ctx->mutex);
450       ctx->eof_shortcut = 1;
451       if (ctx->eof)
452         return TRACE_SYSRES (0);
453       if (!ctx->error)
454         {
455           TRACE_LOG ("EOF but ctx->eof flag not set");
456           return 0;
457         }
458       errno = ctx->error_code;
459       return TRACE_SYSRES (-1);
460     }
461   
462   nread = ctx->readpos < ctx->writepos
463     ? ctx->writepos - ctx->readpos
464     : READBUF_SIZE - ctx->readpos;
465   if (nread > count)
466     nread = count;
467   memcpy (buffer, ctx->buffer + ctx->readpos, nread);
468   ctx->readpos = (ctx->readpos + nread) % READBUF_SIZE;
469   if (ctx->readpos == ctx->writepos && !ctx->eof)
470     {
471       if (!ResetEvent (ctx->have_data_ev))
472         {
473           TRACE_LOG1 ("ResetEvent failed: ec=%d", (int) GetLastError ());
474           UNLOCK (ctx->mutex);
475           /* FIXME: Should translate the error code.  */
476           errno = EIO;
477           return TRACE_SYSRES (-1);
478         }
479     }
480   if (!SetEvent (ctx->have_space_ev))
481     {
482       TRACE_LOG1 ("SetEvent failed: ec=%d", (int) GetLastError ());
483       UNLOCK (ctx->mutex);
484       /* FIXME: Should translate the error code.  */
485       errno = EIO;
486       return TRACE_SYSRES (-1);
487     }
488   UNLOCK (ctx->mutex);
489   
490   TRACE_LOGBUF (buffer, nread);
491   return TRACE_SYSRES (nread);
492 }
493
494
495 /* The writer does use a simple buffering strategy so that we are
496    informed about write errors as soon as possible (i. e. with the the
497    next call to the write function.  */
498 static DWORD CALLBACK 
499 writer (void *arg)
500 {
501   struct writer_context_s *ctx = arg;
502   DWORD nwritten;
503   TRACE_BEG1 (DEBUG_SYSIO, "gpgme:writer", ctx->file_hd,
504               "thread=%p", ctx->thread_hd);
505
506   for (;;)
507     {
508       LOCK (ctx->mutex);
509       if (ctx->stop_me)
510         {
511           UNLOCK (ctx->mutex);
512           break;
513         }
514       if (!ctx->nbytes)
515         { 
516           if (!SetEvent (ctx->is_empty))
517             TRACE_LOG1 ("SetEvent failed: ec=%d", (int) GetLastError ());
518           if (!ResetEvent (ctx->have_data))
519             TRACE_LOG1 ("ResetEvent failed: ec=%d", (int) GetLastError ());
520           UNLOCK (ctx->mutex);
521           TRACE_LOG ("idle");
522           WaitForSingleObject (ctx->have_data, INFINITE);
523           TRACE_LOG ("got data to send");
524           LOCK (ctx->mutex);
525         }
526       if (ctx->stop_me)
527         {
528           UNLOCK (ctx->mutex);
529           break;
530         }
531       UNLOCK (ctx->mutex);
532       
533       TRACE_LOG1 ("writing %d bytes", ctx->nbytes);
534       /* Note that CTX->nbytes is not zero at this point, because
535          _gpgme_io_write always writes at least 1 byte before waking
536          us up, unless CTX->stop_me is true, which we catch above.  */
537       if (!WriteFile (ctx->file_hd, ctx->buffer,
538                       ctx->nbytes, &nwritten, NULL))
539         {
540           ctx->error_code = (int) GetLastError ();
541           ctx->error = 1;
542           TRACE_LOG1 ("write error: ec=%d", ctx->error_code);
543           break;
544         }
545       TRACE_LOG1 ("wrote %d bytes", (int) nwritten);
546       
547       LOCK (ctx->mutex);
548       ctx->nbytes -= nwritten;
549       UNLOCK (ctx->mutex);
550     }
551   /* Indicate that we have an error.  */
552   if (!SetEvent (ctx->is_empty))
553     TRACE_LOG1 ("SetEvent failed: ec=%d", (int) GetLastError ());
554   SetEvent (ctx->stopped);
555
556   return TRACE_SUC ();
557 }
558
559
560 static struct writer_context_s *
561 create_writer (HANDLE fd)
562 {
563   struct writer_context_s *ctx;
564   SECURITY_ATTRIBUTES sec_attr;
565   DWORD tid;
566
567   TRACE_BEG (DEBUG_SYSIO, "gpgme:create_writer", fd);
568
569   memset (&sec_attr, 0, sizeof sec_attr);
570   sec_attr.nLength = sizeof sec_attr;
571   sec_attr.bInheritHandle = FALSE;
572
573   ctx = calloc (1, sizeof *ctx);
574   if (!ctx)
575     {
576       TRACE_SYSERR (errno);
577       return NULL;
578     }
579   
580   ctx->file_hd = fd;
581   ctx->refcount = 1;
582   ctx->have_data = CreateEvent (&sec_attr, TRUE, FALSE, NULL);
583   if (ctx->have_data)
584     ctx->is_empty  = CreateEvent (&sec_attr, TRUE, TRUE, NULL);
585   if (ctx->is_empty)
586     ctx->stopped = CreateEvent (&sec_attr, TRUE, FALSE, NULL);
587   if (!ctx->have_data || !ctx->is_empty || !ctx->stopped)
588     {
589       TRACE_LOG1 ("CreateEvent failed: ec=%d", (int) GetLastError ());
590       if (ctx->have_data)
591         CloseHandle (ctx->have_data);
592       if (ctx->is_empty)
593         CloseHandle (ctx->is_empty);
594       if (ctx->stopped)
595         CloseHandle (ctx->stopped);
596       free (ctx);
597       /* FIXME: Translate the error code.  */
598       TRACE_SYSERR (EIO);
599       return NULL;
600     }
601
602   ctx->is_empty = set_synchronize (ctx->is_empty);
603   INIT_LOCK (ctx->mutex);
604
605   ctx->thread_hd = CreateThread (&sec_attr, 0, writer, ctx, 0, &tid );
606   if (!ctx->thread_hd)
607     {
608       TRACE_LOG1 ("CreateThread failed: ec=%d", (int) GetLastError ());
609       DESTROY_LOCK (ctx->mutex);
610       if (ctx->have_data)
611         CloseHandle (ctx->have_data);
612       if (ctx->is_empty)
613         CloseHandle (ctx->is_empty);
614       if (ctx->stopped)
615         CloseHandle (ctx->stopped);
616       free (ctx);
617       TRACE_SYSERR (EIO);
618       return NULL;
619     }    
620   else
621     {
622       /* We set the priority of the thread higher because we know
623          that it only runs for a short time.  This greatly helps to
624          increase the performance of the I/O.  */
625       SetThreadPriority (ctx->thread_hd, get_desired_thread_priority ());
626     }
627
628   TRACE_SUC ();
629   return ctx;
630 }
631
632 static void
633 destroy_writer (struct writer_context_s *ctx)
634 {
635   LOCK (ctx->mutex);
636   ctx->refcount--;
637   if (ctx->refcount != 0)
638     {
639       UNLOCK (ctx->mutex);
640       return;
641     }
642   ctx->stop_me = 1;
643   if (ctx->have_data) 
644     SetEvent (ctx->have_data);
645   UNLOCK (ctx->mutex);
646   
647   TRACE1 (DEBUG_SYSIO, "gpgme:destroy_writer", ctx->file_hd,
648           "waiting for termination of thread %p", ctx->thread_hd);
649   WaitForSingleObject (ctx->stopped, INFINITE);
650   TRACE1 (DEBUG_SYSIO, "gpgme:destroy_writer", ctx->file_hd,
651           "thread %p has terminated", ctx->thread_hd);
652   
653   if (ctx->stopped)
654     CloseHandle (ctx->stopped);
655   if (ctx->have_data)
656     CloseHandle (ctx->have_data);
657   if (ctx->is_empty)
658     CloseHandle (ctx->is_empty);
659   CloseHandle (ctx->thread_hd);
660   DESTROY_LOCK (ctx->mutex);
661   free (ctx);
662 }
663
664
665 /* Find a writer context or create a new one.  Note that the writer
666    context will last until a _gpgme_io_close.  */
667 static struct writer_context_s *
668 find_writer (int fd, int start_it)
669 {
670   struct writer_context_s *wt = NULL;
671   int i;
672
673   LOCK (writer_table_lock);
674   for (i = 0; i < writer_table_size; i++)
675     if (writer_table[i].used && writer_table[i].fd == fd)
676       wt = writer_table[i].context;
677
678   if (wt || !start_it)
679     {
680       UNLOCK (writer_table_lock);
681       return wt;
682     }
683
684   for (i = 0; i < writer_table_size; i++)
685     if (!writer_table[i].used)
686       break;
687
688   if (i != writer_table_size)
689     {
690       wt = create_writer (fd_to_handle (fd));
691       writer_table[i].fd = fd;
692       writer_table[i].context = wt; 
693       writer_table[i].used = 1;
694     }
695
696   UNLOCK (writer_table_lock);
697   return wt;
698 }
699
700
701 static void
702 kill_writer (int fd)
703 {
704   int i;
705
706   LOCK (writer_table_lock);
707   for (i = 0; i < writer_table_size; i++)
708     {
709       if (writer_table[i].used && writer_table[i].fd == fd)
710         {
711           destroy_writer (writer_table[i].context);
712           writer_table[i].context = NULL;
713           writer_table[i].used = 0;
714           break;
715         }
716     }
717   UNLOCK (writer_table_lock);
718 }
719
720
721 int
722 _gpgme_io_write (int fd, const void *buffer, size_t count)
723 {
724   struct writer_context_s *ctx;
725   TRACE_BEG2 (DEBUG_SYSIO, "_gpgme_io_write", fd,
726               "buffer=%p, count=%u", buffer, count);
727   TRACE_LOGBUF (buffer, count);
728
729   if (count == 0)
730     return TRACE_SYSRES (0);
731
732   ctx = find_writer (fd, 1);
733   if (!ctx)
734     return TRACE_SYSRES (-1);
735
736   LOCK (ctx->mutex);
737   if (!ctx->error && ctx->nbytes)
738     {
739       /* Bytes are pending for send.  */
740
741       /* Reset the is_empty event.  Better safe than sorry.  */
742       if (!ResetEvent (ctx->is_empty))
743         {
744           TRACE_LOG1 ("ResetEvent failed: ec=%d", (int) GetLastError ());
745           UNLOCK (ctx->mutex);
746           /* FIXME: Should translate the error code.  */
747           errno = EIO;
748           return TRACE_SYSRES (-1);
749         }
750       UNLOCK (ctx->mutex);
751       TRACE_LOG1 ("waiting for empty buffer in thread %p", ctx->thread_hd);
752       WaitForSingleObject (ctx->is_empty, INFINITE);
753       TRACE_LOG1 ("thread %p buffer is empty", ctx->thread_hd);
754       LOCK (ctx->mutex);
755     }
756
757   if (ctx->error)
758     {
759       UNLOCK (ctx->mutex);
760       if (ctx->error_code == ERROR_NO_DATA)
761         errno = EPIPE;
762       else
763         errno = EIO;
764       return TRACE_SYSRES (-1);
765     }
766
767   /* If no error occured, the number of bytes in the buffer must be
768      zero.  */
769   assert (!ctx->nbytes);
770
771   if (count > WRITEBUF_SIZE)
772     count = WRITEBUF_SIZE;
773   memcpy (ctx->buffer, buffer, count);
774   ctx->nbytes = count;
775
776   /* We have to reset the is_empty event early, because it is also
777      used by the select() implementation to probe the channel.  */
778   if (!ResetEvent (ctx->is_empty))
779     {
780       TRACE_LOG1 ("ResetEvent failed: ec=%d", (int) GetLastError ());
781       UNLOCK (ctx->mutex);
782       /* FIXME: Should translate the error code.  */
783       errno = EIO;
784       return TRACE_SYSRES (-1);
785     }
786   if (!SetEvent (ctx->have_data))
787     {
788       TRACE_LOG1 ("SetEvent failed: ec=%d", (int) GetLastError ());
789       UNLOCK (ctx->mutex);
790       /* FIXME: Should translate the error code.  */
791       errno = EIO;
792       return TRACE_SYSRES (-1);
793     }
794   UNLOCK (ctx->mutex);
795
796   return TRACE_SYSRES ((int) count);
797 }
798
799
800 int
801 _gpgme_io_pipe (int filedes[2], int inherit_idx)
802 {
803   HANDLE rh;
804   HANDLE wh;
805   SECURITY_ATTRIBUTES sec_attr;
806   TRACE_BEG2 (DEBUG_SYSIO, "_gpgme_io_pipe", filedes,
807               "inherit_idx=%i (GPGME uses it for %s)",
808               inherit_idx, inherit_idx ? "reading" : "writing");
809
810   memset (&sec_attr, 0, sizeof (sec_attr));
811   sec_attr.nLength = sizeof (sec_attr);
812   sec_attr.bInheritHandle = FALSE;
813   
814   if (!CreatePipe (&rh, &wh, &sec_attr, PIPEBUF_SIZE))
815     {
816       TRACE_LOG1 ("CreatePipe failed: ec=%d", (int) GetLastError ());
817       /* FIXME: Should translate the error code.  */
818       errno = EIO;
819       return TRACE_SYSRES (-1);
820     }
821
822   /* Make one end inheritable.  */
823   if (inherit_idx == 0)
824     {
825       HANDLE hd;
826       if (!DuplicateHandle (GetCurrentProcess(), rh,
827                             GetCurrentProcess(), &hd, 0,
828                             TRUE, DUPLICATE_SAME_ACCESS))
829         {
830           TRACE_LOG1 ("DuplicateHandle failed: ec=%d",
831                       (int) GetLastError ());
832           CloseHandle (rh);
833           CloseHandle (wh);
834           /* FIXME: Should translate the error code.  */
835           errno = EIO;
836           return TRACE_SYSRES (-1);
837         }
838       CloseHandle (rh);
839       rh = hd;
840     }
841   else if (inherit_idx == 1)
842     {
843       HANDLE hd;
844       if (!DuplicateHandle( GetCurrentProcess(), wh,
845                             GetCurrentProcess(), &hd, 0,
846                             TRUE, DUPLICATE_SAME_ACCESS))
847         {
848           TRACE_LOG1 ("DuplicateHandle failed: ec=%d",
849                       (int) GetLastError ());
850           CloseHandle (rh);
851           CloseHandle (wh);
852           /* FIXME: Should translate the error code.  */
853           errno = EIO;
854           return TRACE_SYSRES (-1);
855         }
856       CloseHandle (wh);
857       wh = hd;
858     }
859   
860   filedes[0] = handle_to_fd (rh);
861   filedes[1] = handle_to_fd (wh);
862   return TRACE_SUC2 ("read=%p, write=%p", rh, wh);
863 }
864
865
866 int
867 _gpgme_io_close (int fd)
868 {
869   int i;
870   _gpgme_close_notify_handler_t handler = NULL;
871   void *value = NULL;
872   TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_close", fd);
873
874   if (fd == -1)
875     {
876       errno = EBADF;
877       return TRACE_SYSRES (-1);
878     }
879
880   kill_reader (fd);
881   kill_writer (fd);
882   LOCK (notify_table_lock);
883   for (i = 0; i < DIM (notify_table); i++)
884     {
885       if (notify_table[i].inuse && notify_table[i].fd == fd)
886         {
887           handler = notify_table[i].handler;
888           value   = notify_table[i].value;
889           notify_table[i].handler = NULL;
890           notify_table[i].value = NULL;
891           notify_table[i].inuse = 0;
892           break;
893         }
894     }
895   UNLOCK (notify_table_lock);
896   if (handler)
897     handler (fd, value);
898
899   if (!CloseHandle (fd_to_handle (fd)))
900     { 
901       TRACE_LOG1 ("CloseHandle failed: ec=%d", (int) GetLastError ());
902       /* FIXME: Should translate the error code.  */
903       errno = EIO;
904       return TRACE_SYSRES (-1);
905     }
906
907   return TRACE_SYSRES (0);
908 }
909
910
911 int
912 _gpgme_io_set_close_notify (int fd, _gpgme_close_notify_handler_t handler,
913                             void *value)
914 {
915   int i;
916   TRACE_BEG2 (DEBUG_SYSIO, "_gpgme_io_set_close_notify", fd,
917               "close_handler=%p/%p", handler, value);
918
919   assert (fd != -1);
920
921   LOCK (notify_table_lock);
922   for (i=0; i < DIM (notify_table); i++)
923     if (notify_table[i].inuse && notify_table[i].fd == fd)
924       break;
925   if (i == DIM (notify_table))
926     for (i = 0; i < DIM (notify_table); i++)
927       if (!notify_table[i].inuse)
928         break;
929   if (i == DIM (notify_table))
930     {
931       UNLOCK (notify_table_lock);
932       errno = EINVAL;
933       return TRACE_SYSRES (-1);
934     }
935   notify_table[i].fd = fd;
936   notify_table[i].handler = handler;
937   notify_table[i].value = value;
938   notify_table[i].inuse = 1;
939   UNLOCK (notify_table_lock);
940   return TRACE_SYSRES (0);
941 }
942
943
944 int
945 _gpgme_io_set_nonblocking (int fd)
946 {
947   TRACE (DEBUG_SYSIO, "_gpgme_io_set_nonblocking", fd);
948   return 0;
949 }
950
951
952 static char *
953 build_commandline (char **argv)
954 {
955   int i;
956   int n = 0;
957   char *buf;
958   char *p;
959   
960   /* We have to quote some things because under Windows the program
961      parses the commandline and does some unquoting.  We enclose the
962      whole argument in double-quotes, and escape literal double-quotes
963      as well as backslashes with a backslash.  We end up with a
964      trailing space at the end of the line, but that is harmless.  */
965   for (i = 0; argv[i]; i++)
966     {
967       p = argv[i];
968       /* The leading double-quote.  */
969       n++;
970       while (*p)
971         {
972           /* An extra one for each literal that must be escaped.  */
973           if (*p == '\\' || *p == '"')
974             n++;
975           n++;
976           p++;
977         }
978       /* The trailing double-quote and the delimiter.  */
979       n += 2;
980     }
981   /* And a trailing zero.  */
982   n++;
983
984   buf = p = malloc (n);
985   if (!buf)
986     return NULL;
987   for (i = 0; argv[i]; i++)
988     {
989       char *argvp = argv[i];
990
991       *(p++) = '"';
992       while (*argvp)
993         {
994           if (*argvp == '\\' || *argvp == '"')
995             *(p++) = '\\';
996           *(p++) = *(argvp++);
997         }
998       *(p++) = '"';
999       *(p++) = ' ';
1000     }
1001   *(p++) = 0;
1002
1003   return buf;
1004 }
1005
1006
1007 int
1008 _gpgme_io_spawn (const char *path, char **argv,
1009                  struct spawn_fd_item_s *fd_child_list,
1010                  struct spawn_fd_item_s *fd_parent_list)
1011 {
1012   SECURITY_ATTRIBUTES sec_attr;
1013   PROCESS_INFORMATION pi =
1014     {
1015       NULL,      /* returns process handle */
1016       0,         /* returns primary thread handle */
1017       0,         /* returns pid */
1018       0         /* returns tid */
1019     };
1020   STARTUPINFO si;
1021   char *envblock = NULL;
1022   int cr_flags = CREATE_DEFAULT_ERROR_MODE
1023     | GetPriorityClass (GetCurrentProcess ());
1024   int i;
1025   char *arg_string;
1026   int duped_stdin = 0;
1027   int duped_stderr = 0;
1028   HANDLE hnul = INVALID_HANDLE_VALUE;
1029   /* FIXME.  */
1030   int debug_me = 0;
1031   TRACE_BEG1 (DEBUG_SYSIO, "_gpgme_io_spawn", path,
1032               "path=%s", path);
1033   i = 0;
1034   while (argv[i])
1035     {
1036       TRACE_LOG2 ("argv[%2i] = %s", i, argv[i]);
1037       i++;
1038     }
1039
1040   memset (&sec_attr, 0, sizeof sec_attr);
1041   sec_attr.nLength = sizeof sec_attr;
1042   sec_attr.bInheritHandle = FALSE;
1043   
1044   arg_string = build_commandline (argv);
1045   if (!arg_string)
1046     return TRACE_SYSRES (-1);
1047   
1048   memset (&si, 0, sizeof si);
1049   si.cb = sizeof (si);
1050   si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
1051   si.wShowWindow = debug_me ? SW_SHOW : SW_HIDE;
1052   si.hStdInput = GetStdHandle (STD_INPUT_HANDLE);
1053   si.hStdOutput = GetStdHandle (STD_OUTPUT_HANDLE);
1054   si.hStdError = GetStdHandle (STD_ERROR_HANDLE);
1055
1056   for (i = 0; fd_child_list[i].fd != -1; i++)
1057     {
1058       if (fd_child_list[i].dup_to == 0)
1059         {
1060           si.hStdInput = fd_to_handle (fd_child_list[i].fd);
1061           TRACE_LOG1 ("using 0x%x for stdin", fd_child_list[i].fd);
1062           duped_stdin = 1;
1063         }
1064       else if (fd_child_list[i].dup_to == 1)
1065         {
1066           si.hStdOutput = fd_to_handle (fd_child_list[i].fd);
1067           TRACE_LOG1 ("using 0x%x for stdout", fd_child_list[i].fd);
1068         }
1069       else if (fd_child_list[i].dup_to == 2)
1070         {
1071           si.hStdError = fd_to_handle (fd_child_list[i].fd);
1072           TRACE_LOG1 ("using 0x%x for stderr", fd_child_list[i].fd);
1073           duped_stderr = 1;
1074         }
1075     }
1076   
1077   if (!duped_stdin || !duped_stderr)
1078     {
1079       SECURITY_ATTRIBUTES sa;
1080       
1081       memset (&sa, 0, sizeof sa);
1082       sa.nLength = sizeof sa;
1083       sa.bInheritHandle = TRUE;
1084       hnul = CreateFile ("nul",
1085                          GENERIC_READ|GENERIC_WRITE,
1086                          FILE_SHARE_READ|FILE_SHARE_WRITE,
1087                          &sa,
1088                          OPEN_EXISTING,
1089                          FILE_ATTRIBUTE_NORMAL,
1090                          NULL);
1091       if (hnul == INVALID_HANDLE_VALUE)
1092         {
1093           TRACE_LOG1 ("CreateFile (\"nul\") failed: ec=%d",
1094                       (int) GetLastError ());
1095           free (arg_string);
1096           /* FIXME: Should translate the error code.  */
1097           errno = EIO;
1098           return TRACE_SYSRES (-1);
1099         }
1100       /* Make sure that the process has a connected stdin.  */
1101       if (!duped_stdin)
1102         {
1103           si.hStdInput = hnul;
1104           TRACE_LOG1 ("using 0x%x for dummy stdin", (int) hnul);
1105         }
1106       /* We normally don't want all the normal output.  */
1107       if (!duped_stderr)
1108         {
1109           si.hStdError = hnul;
1110           TRACE_LOG1 ("using 0x%x for dummy stderr", (int) hnul);
1111         }
1112     }
1113   
1114   cr_flags |= CREATE_SUSPENDED; 
1115   cr_flags |= DETACHED_PROCESS;
1116   if (!CreateProcessA (path,
1117                        arg_string,
1118                        &sec_attr,     /* process security attributes */
1119                        &sec_attr,     /* thread security attributes */
1120                        TRUE,          /* inherit handles */
1121                        cr_flags,      /* creation flags */
1122                        envblock,      /* environment */
1123                        NULL,          /* use current drive/directory */
1124                        &si,           /* startup information */
1125                        &pi))          /* returns process information */
1126     {
1127       TRACE_LOG1 ("CreateProcess failed: ec=%d", (int) GetLastError ());
1128       free (arg_string);
1129       /* FIXME: Should translate the error code.  */
1130       errno = EIO;
1131       return TRACE_SYSRES (-1);
1132     }
1133
1134   /* Close the /dev/nul handle if used.  */
1135   if (hnul != INVALID_HANDLE_VALUE)
1136     {
1137       if (!CloseHandle (hnul))
1138         TRACE_LOG1 ("CloseHandle (hnul) failed: ec=%d (ignored)",
1139                     (int) GetLastError ());
1140     }
1141   
1142   /* Close the other ends of the pipes.  */
1143   for (i = 0; fd_parent_list[i].fd != -1; i++)
1144     _gpgme_io_close (fd_parent_list[i].fd);
1145   
1146   TRACE_LOG4 ("CreateProcess ready: hProcess=%p, hThread=%p, "
1147               "dwProcessID=%d, dwThreadId=%d",
1148               pi.hProcess, pi.hThread, 
1149               (int) pi.dwProcessId, (int) pi.dwThreadId);
1150   
1151   if (ResumeThread (pi.hThread) < 0)
1152     TRACE_LOG1 ("ResumeThread failed: ec=%d", (int) GetLastError ());
1153   
1154   if (!CloseHandle (pi.hThread))
1155     TRACE_LOG1 ("CloseHandle of thread failed: ec=%d",
1156                 (int) GetLastError ());
1157
1158   TRACE_SUC1 ("process=%p", pi.hProcess);
1159   return handle_to_pid (pi.hProcess);
1160 }
1161
1162
1163 /* Select on the list of fds.  Returns: -1 = error, 0 = timeout or
1164    nothing to select, > 0 = number of signaled fds.  */
1165 int
1166 _gpgme_io_select (struct io_select_fd_s *fds, size_t nfds, int nonblock)
1167 {
1168   HANDLE waitbuf[MAXIMUM_WAIT_OBJECTS];
1169   int waitidx[MAXIMUM_WAIT_OBJECTS];
1170   int code;
1171   int nwait;
1172   int i;
1173   int any;
1174   int count;
1175   void *dbg_help;
1176   TRACE_BEG2 (DEBUG_SYSIO, "_gpgme_io_select", fds,
1177               "nfds=%u, nonblock=%u", nfds, nonblock);
1178
1179  restart:
1180   TRACE_SEQ (dbg_help, "select on [ ");
1181   any = 0;
1182   nwait = 0;
1183   count = 0;
1184   for (i=0; i < nfds; i++)
1185     {
1186       if (fds[i].fd == -1)
1187         continue;
1188       fds[i].signaled = 0;
1189       if (fds[i].for_read || fds[i].for_write)
1190         {
1191           if (fds[i].frozen)
1192             TRACE_ADD1 (dbg_help, "f0x%x ", fds[i].fd);
1193           else if (fds[i].for_read)
1194             {
1195               struct reader_context_s *ctx = find_reader (fds[i].fd,1);
1196               
1197               if (!ctx)
1198                 TRACE_LOG1 ("error: no reader for FD 0x%x (ignored)",
1199                             fds[i].fd);
1200               else
1201                 {
1202                   if (nwait >= DIM (waitbuf))
1203                     {
1204                       TRACE_END (dbg_help, "oops ]");
1205                       TRACE_LOG ("Too many objects for WFMO!");
1206                       /* FIXME: Should translate the error code.  */
1207                       errno = EIO;
1208                       return TRACE_SYSRES (-1);
1209                     }
1210                   waitidx[nwait] = i;
1211                   waitbuf[nwait++] = ctx->have_data_ev;
1212                 }
1213               TRACE_ADD1 (dbg_help, "r0x%x ", fds[i].fd);
1214               any = 1;
1215             }
1216           else if (fds[i].for_write)
1217             {
1218               struct writer_context_s *ctx = find_writer (fds[i].fd,1);
1219               
1220               if (!ctx)
1221                 TRACE_LOG1 ("error: no writer for FD 0x%x (ignored)",
1222                             fds[i].fd);
1223               else
1224                 {
1225                   if (nwait >= DIM (waitbuf))
1226                     {
1227                       TRACE_END (dbg_help, "oops ]");
1228                       TRACE_LOG ("Too many objects for WFMO!");
1229                       /* FIXME: Should translate the error code.  */
1230                       errno = EIO;
1231                       return TRACE_SYSRES (-1);
1232                     }
1233                   waitidx[nwait] = i;
1234                   waitbuf[nwait++] = ctx->is_empty;
1235                 }
1236               TRACE_ADD1 (dbg_help, "w0x%x ", fds[i].fd);
1237               any = 1;
1238             }
1239         }
1240     }
1241   TRACE_END (dbg_help, "]");
1242   if (!any) 
1243     return TRACE_SYSRES (0);
1244
1245   code = WaitForMultipleObjects (nwait, waitbuf, 0, nonblock ? 0 : 1000);
1246   if (code >= WAIT_OBJECT_0 && code < WAIT_OBJECT_0 + nwait)
1247     {
1248       /* This WFMO is a really silly function: It does return either
1249          the index of the signaled object or if 2 objects have been
1250          signalled at the same time, the index of the object with the
1251          lowest object is returned - so and how do we find out how
1252          many objects have been signaled???.  The only solution I can
1253          imagine is to test each object starting with the returned
1254          index individually - how dull.  */
1255       any = 0;
1256       for (i = code - WAIT_OBJECT_0; i < nwait; i++)
1257         {
1258           if (WaitForSingleObject (waitbuf[i], 0) == WAIT_OBJECT_0)
1259             {
1260               assert (waitidx[i] >=0 && waitidx[i] < nfds);
1261               fds[waitidx[i]].signaled = 1;
1262               any = 1;
1263               count++;
1264             }
1265         }
1266       if (!any)
1267         {
1268           TRACE_LOG ("no signaled objects found after WFMO");
1269           count = -1;
1270         }
1271     }
1272   else if (code == WAIT_TIMEOUT)
1273     TRACE_LOG ("WFMO timed out");
1274   else if (code == WAIT_FAILED)
1275     {
1276       int le = (int) GetLastError ();
1277       if (le == ERROR_INVALID_HANDLE)
1278         {
1279           int k;
1280           int j = handle_to_fd (waitbuf[i]);
1281           
1282           TRACE_LOG1 ("WFMO invalid handle %d removed", j);
1283           for (k = 0 ; k < nfds; k++)
1284             {
1285               if (fds[k].fd == j)
1286                 {
1287                   fds[k].for_read = fds[k].for_write = 0;
1288                   goto restart;
1289                 }
1290             }
1291           TRACE_LOG (" oops, or not???");
1292         }
1293       TRACE_LOG1 ("WFMO failed: %d", le);
1294       count = -1;
1295     }
1296   else
1297     {
1298       TRACE_LOG1 ("WFMO returned %d", code);
1299       count = -1;
1300     }
1301   
1302   if (count > 0)
1303     {
1304       TRACE_SEQ (dbg_help, "select OK [ ");
1305       for (i = 0; i < nfds; i++)
1306         {
1307           if (fds[i].fd == -1)
1308             continue;
1309           if ((fds[i].for_read || fds[i].for_write) && fds[i].signaled)
1310             TRACE_ADD2 (dbg_help, "%c0x%x ",
1311                         fds[i].for_read ? 'r' : 'w', fds[i].fd);
1312         }
1313       TRACE_END (dbg_help, "]");
1314     }
1315
1316   if (count < 0)
1317     {
1318       /* FIXME: Should determine a proper error code.  */
1319       errno = EIO;
1320     }
1321   
1322   return TRACE_SYSRES (count);
1323 }
1324
1325
1326 void
1327 _gpgme_io_subsystem_init (void)
1328 {
1329   /* Nothing to do.  */
1330 }
1331
1332
1333 /* Write the printable version of FD to the buffer BUF of length
1334    BUFLEN.  The printable version is the representation on the command
1335    line that the child process expects.  */
1336 int
1337 _gpgme_io_fd2str (char *buf, int buflen, int fd)
1338 {
1339   return snprintf (buf, buflen, "%d", fd);
1340 }
1341
1342
1343 int
1344 _gpgme_io_dup (int fd)
1345 {
1346   HANDLE handle = fd_to_handle (fd);
1347   HANDLE new_handle = fd_to_handle (fd);
1348   int i;
1349   struct reader_context_s *rd_ctx;
1350   struct writer_context_s *wt_ctx;
1351
1352   TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_dup", fd);
1353
1354   if (!DuplicateHandle (GetCurrentProcess(), handle,
1355                         GetCurrentProcess(), &new_handle,
1356                         0, FALSE, DUPLICATE_SAME_ACCESS))
1357     {
1358       TRACE_LOG1 ("DuplicateHandle failed: ec=%d\n", (int) GetLastError ());
1359       /* FIXME: Translate error code.  */
1360       errno = EIO;
1361       return TRACE_SYSRES (-1);
1362     }
1363
1364   rd_ctx = find_reader (fd, 1);
1365   if (rd_ctx)
1366     {
1367       /* No need for locking, as the only races are against the reader
1368          thread itself, which doesn't touch refcount.  */
1369       rd_ctx->refcount++;
1370
1371       LOCK (reader_table_lock);
1372       for (i = 0; i < reader_table_size; i++)
1373         if (!reader_table[i].used)
1374           break;
1375       /* FIXME.  */
1376       assert (i != reader_table_size);
1377       reader_table[i].fd = handle_to_fd (new_handle);
1378       reader_table[i].context = rd_ctx;
1379       reader_table[i].used = 1;
1380       UNLOCK (reader_table_lock);
1381     }
1382
1383   wt_ctx = find_writer (fd, 1);
1384   if (wt_ctx)
1385     {
1386       /* No need for locking, as the only races are against the writer
1387          thread itself, which doesn't touch refcount.  */
1388       wt_ctx->refcount++;
1389
1390       LOCK (writer_table_lock);
1391       for (i = 0; i < writer_table_size; i++)
1392         if (!writer_table[i].used)
1393           break;
1394       /* FIXME.  */
1395       assert (i != writer_table_size);
1396       writer_table[i].fd = handle_to_fd (new_handle);
1397       writer_table[i].context = wt_ctx;
1398       writer_table[i].used = 1;
1399       UNLOCK (writer_table_lock);
1400     }
1401
1402   return TRACE_SYSRES (handle_to_fd (new_handle));
1403 }
1404
1405 \f
1406 /* The following interface is only useful for GPGME Glib and Qt.  */
1407
1408 /* Compatibility interface, obsolete.  */
1409 void *
1410 gpgme_get_giochannel (int fd)
1411 {
1412   return NULL;
1413 }
1414
1415
1416 /* Look up the giochannel or qiodevice for file descriptor FD.  */
1417 void *
1418 gpgme_get_fdptr (int fd)
1419 {
1420   return NULL;
1421 }