fix assertion failure reported in #5578
[oweals/gnunet.git] / src / util / gnunet-helper-w32-console.c
1 /*
2      This file is part of GNUnet.
3      Copyright (C) 2014 GNUnet e.V.
4
5      GNUnet is free software: you can redistribute it and/or modify it
6      under the terms of the GNU Affero General Public License as published
7      by the Free Software Foundation, either version 3 of the License,
8      or (at your option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      Affero General Public License for more details.
14     
15      You should have received a copy of the GNU Affero General Public License
16      along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18      SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21 /**
22  * @file src/util/gnunet-helper-w32-console.c
23  * @brief Does blocking reads from the console, writes the results
24  *        into stdout, turning blocking console I/O into non-blocking
25  *        pipe I/O. For W32 only.
26  * @author LRN
27  */
28 #include "platform.h"
29 #include "gnunet_crypto_lib.h"
30 #include "gnunet_common.h"
31 #include "gnunet-helper-w32-console.h"
32
33 static unsigned long buffer_size;
34
35 static int chars;
36
37 static HANDLE parent_handle;
38
39 /**
40  * Write @a size bytes from @a buf into @a output.
41  *
42  * @param output the descriptor to write into
43  * @param buf buffer with data to write
44  * @param size number of bytes to write
45  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
46  */
47 static int
48 write_all (int output, 
49            const void *buf,
50            size_t size)
51 {
52   const char *cbuf = buf;
53   size_t total;
54   ssize_t wr;
55
56   total = 0;
57   do
58   {
59     wr = write (output,
60                 &cbuf[total],
61                 size - total);
62     if (wr > 0)
63       total += wr;
64   } while ( (wr > 0) && (total < size) );
65   if (wr <= 0)
66     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
67                 "Failed to write to stdout: %s\n",
68                 strerror (errno));
69   return (total == size) ? GNUNET_OK : GNUNET_SYSERR;
70 }
71
72
73 /**
74  * Write message to the master process.
75  *
76  * @param output the descriptor to write into
77  * @param message_type message type to use
78  * @param data data to append, NULL for none
79  * @param data_length number of bytes in @a data
80  * @return #GNUNET_SYSERR to stop scanning (the pipe was broken somehow)
81  */
82 static int
83 write_message (int output,
84                uint16_t message_type,
85                const char *data,
86                size_t data_length)
87 {
88   struct GNUNET_MessageHeader hdr;
89
90 #if 0
91   fprintf (stderr,
92            "Helper sends %u-byte message of type %u\n",
93            (unsigned int) (sizeof (struct GNUNET_MessageHeader) + data_length),
94            (unsigned int) message_type);
95 #endif
96   hdr.type = htons (message_type);
97   hdr.size = htons (sizeof (struct GNUNET_MessageHeader) + data_length);
98   if (GNUNET_OK != write_all (output, &hdr, sizeof (hdr)))
99     return GNUNET_SYSERR;
100   if (GNUNET_OK != write_all (output, data, data_length))
101     return GNUNET_SYSERR;
102   return GNUNET_OK;
103 }
104
105
106 /**
107  * Main function of the helper process. Reads input events from console,
108  * writes messages, into stdout.
109  *
110  * @param console a handle to a console to read from
111  * @param output_stream a stream to write messages to
112  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
113  */
114 static int
115 read_events (HANDLE console, int output_stream)
116 {
117   DWORD rr;
118   BOOL b;
119   INPUT_RECORD *buf;
120   DWORD i;
121   int result;
122
123   result = GNUNET_SYSERR;
124   buf = malloc (sizeof (INPUT_RECORD) * buffer_size);
125   if (NULL == buf)
126     return result;
127   b = TRUE;
128   rr = 1;
129   while (TRUE == b && 0 < rr)
130   {
131     rr = 0;
132     b = ReadConsoleInput (console, buf, buffer_size, &rr);
133     if (FALSE == b && ERROR_SUCCESS != GetLastError ())
134       break;
135     for (i = 0; i < rr; i++)
136     {
137       int r;
138       r = write_message (output_stream,
139                          GNUNET_MESSAGE_TYPE_W32_CONSOLE_HELPER_INPUT,
140                          (const char *) &buf[i],
141                          sizeof (INPUT_RECORD));
142       if (GNUNET_OK != r)
143         break;
144     }
145     if (rr + 1 != i)
146       break;
147   }
148   return result;
149 }
150
151
152 /**
153  * Main function of the helper process. Reads chars from console,
154  * writes messages, into stdout.
155  *
156  * @param console a handle to a console to read from
157  * @param output_stream a stream to write messages to
158  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
159  */
160 static int
161 read_chars (HANDLE console, int output_stream)
162 {
163   DWORD rr;
164   BOOL b;
165   wchar_t *buf;
166   char *small_ubuf;
167   char *large_ubuf;
168   char *ubuf;
169   int conv;
170   int r;
171   int result;
172
173   result = GNUNET_SYSERR;
174   buf = malloc (sizeof (wchar_t) * buffer_size);
175   if (NULL == buf)
176     return result;
177   small_ubuf = malloc (sizeof (char) * buffer_size * 2);
178   if (NULL == small_ubuf)
179   {
180     free (buf);
181     return result;
182   }
183   b = TRUE;
184   rr = 1;
185   while (TRUE == b)
186   {
187     large_ubuf = NULL;
188     rr = 0;
189     b = ReadConsoleW (console, buf, buffer_size, &rr, NULL);
190     if (FALSE == b && ERROR_SUCCESS != GetLastError ())
191       break;
192     if (0 == rr)
193       continue;
194     /* Caveat: if the UTF-16-encoded string is longer than BUFFER_SIZE,
195      * there's a possibility that we will read up to a word that constitutes
196      * a part of a multi-byte UTF-16 codepoint. Converting that to UTF-8
197      * will either drop invalid word (flags == 0) or bail out because of it
198      * (flags == WC_ERR_INVALID_CHARS).
199      */
200     conv = WideCharToMultiByte (CP_UTF8, 0, buf, rr, small_ubuf, 0, NULL, FALSE);
201     if (0 == conv || 0xFFFD == conv)
202       continue;
203     if (conv <= buffer_size * 2 - 1)
204     {
205       memset (small_ubuf, 0, buffer_size * 2);
206       conv = WideCharToMultiByte (CP_UTF8, 0, buf, rr, small_ubuf, buffer_size * 2 - 1, NULL, FALSE);
207       if (0 == conv || 0xFFFD == conv)
208         continue;
209       ubuf = small_ubuf;
210     }
211     else
212     {
213       large_ubuf = malloc (conv + 1);
214       if (NULL == large_ubuf)
215         continue;
216       memset (large_ubuf, 0, conv + 1);
217       conv = WideCharToMultiByte (CP_UTF8, 0, buf, rr, large_ubuf, conv, NULL, FALSE);
218       if (0 == conv || 0xFFFD == conv)
219       {
220         free (large_ubuf);
221         large_ubuf = NULL;
222         continue;
223       }
224       ubuf = large_ubuf;
225     }
226     r = write_message (output_stream,
227                        GNUNET_MESSAGE_TYPE_W32_CONSOLE_HELPER_CHARS,
228                        ubuf,
229                        conv + 1);
230     if (large_ubuf)
231       free (large_ubuf);
232     if (GNUNET_OK != r)
233       break;
234   }
235   free (small_ubuf);
236   free (buf);
237   return result;
238 }
239
240
241 DWORD WINAPI
242 watch_parent (LPVOID param)
243 {
244   WaitForSingleObject (parent_handle, INFINITE);
245   ExitProcess (1);
246   return 0;
247 }
248
249 /**
250  * Main function of the helper process to extract meta data.
251  *
252  * @param argc should be 3
253  * @param argv [0] our binary name
254  *             [1] name of the file or directory to process
255  *             [2] "-" to disable extraction, NULL for defaults,
256  *                 otherwise custom plugins to load from LE
257  * @return 0 on success
258  */
259 int
260 main (int argc,
261       char *const *argv)
262 {
263   HANDLE os_stdin;
264   DWORD parent_pid;
265   /* We're using stdout to communicate binary data back to the parent; use
266    * binary mode.
267    */
268   _setmode (1, _O_BINARY);
269
270   if (argc != 4)
271   {
272     fprintf (stderr,
273         "Usage: gnunet-helper-w32-console <chars|events> <buffer size> <parent pid>\n");
274     return 2;
275   }
276
277   if (0 == strcmp (argv[1], "chars"))
278     chars = GNUNET_YES;
279   else if (0 == strcmp (argv[1], "events"))
280     chars = GNUNET_NO;
281   else
282     return 3;
283
284   buffer_size = strtoul (argv[2], NULL, 10);
285   if (buffer_size <= 0)
286     return 4;
287
288   parent_pid = (DWORD) strtoul (argv[3], NULL, 10);
289   if (parent_pid == 0)
290     return 5;
291   parent_handle = OpenProcess (SYNCHRONIZE, FALSE, parent_pid);
292   if (NULL == parent_handle)
293     return 6;
294
295   CreateThread (NULL, 0, watch_parent, NULL, 0, NULL);
296
297   if (0 == AttachConsole (ATTACH_PARENT_PROCESS))
298   {
299     if (ERROR_ACCESS_DENIED != GetLastError ())
300       return 5;
301   }
302
303   /* Helper API overrides stdin, so we just attach to the console that we
304    * inherited. If we did.
305    */
306   os_stdin = CreateFile ("CONIN$", GENERIC_READ | GENERIC_WRITE,
307       FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0);
308   if (INVALID_HANDLE_VALUE == os_stdin)
309     return 1;
310
311   if (GNUNET_NO == chars)
312     return read_events (os_stdin, 1);
313   else
314     return read_chars (os_stdin, 1);
315
316 }
317
318 /* end of gnunet-helper-w32-console.c */