1 /* GLIB - Library of useful routines for C programming
2 * Copyright (C) 2000 Tor Lillqvist
3 *
4 * SPDX-License-Identifier: LGPL-2.1-or-later
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
18 */
19
20 /* A test program for the main loop and IO channel code.
21 * Just run it. Optional parameter is number of sub-processes.
22 */
23
24 /* We are using g_io_channel_read() which is deprecated */
25 #ifndef GLIB_DISABLE_DEPRECATION_WARNINGS
26 #define GLIB_DISABLE_DEPRECATION_WARNINGS
27 #endif
28
29 #include "config.h"
30
31 #include <glib.h>
32
33 #include <stdio.h>
34
35 #ifdef G_OS_WIN32
36 #include <io.h>
37 #include <fcntl.h>
38 #include <process.h>
39 #define STRICT
40 #include <windows.h>
41 #define pipe(fds) _pipe(fds, 4096, _O_BINARY)
42 #endif
43
44 #ifdef G_OS_UNIX
45 #include <unistd.h>
46 #endif
47
48 static int nrunning;
49 static GMainLoop *main_loop;
50
51 /* Larger than the circular buffer in giowin32.c on purpose */
52 #define BUFSIZE 5000
53
54 static int nkiddies;
55 static char *exec_name;
56
57 static struct {
58 int fd;
59 int seq;
60 } *seqtab;
61
62 static GIOError
63 read_all (int fd,
64 GIOChannel *channel,
65 char *buffer,
66 guint nbytes,
67 guint *bytes_read)
68 {
69 guint left = nbytes;
70 gsize nb;
71 GIOError error = G_IO_ERROR_NONE;
72 char *bufp = buffer;
73
74 /* g_io_channel_read() doesn't necessarily return all the
75 * data we want at once.
76 */
77 *bytes_read = 0;
78 while (left)
79 {
80 error = g_io_channel_read (channel, bufp, left, &nb);
81
82 if (error != G_IO_ERROR_NONE)
83 {
84 g_test_message ("io-channel-basic: ...from %d: %d", fd, error);
85 if (error == G_IO_ERROR_AGAIN)
86 continue;
87 break;
88 }
89 if (nb == 0)
90 return error;
91 left -= nb;
92 bufp += nb;
93 *bytes_read += nb;
94 }
95 return error;
96 }
97
98 static void
99 shutdown_source (gpointer data)
100 {
101 guint *fd_ptr = data;
102
103 if (*fd_ptr != 0)
104 {
105 g_source_remove (*fd_ptr);
106 *fd_ptr = 0;
107
108 nrunning--;
109 if (nrunning == 0)
110 g_main_loop_quit (main_loop);
111 }
112 }
113
114 static gboolean
115 recv_message (GIOChannel *channel,
116 GIOCondition cond,
117 gpointer data)
118 {
119 gint fd = g_io_channel_unix_get_fd (channel);
120 gboolean retval = TRUE;
121
122 g_debug ("io-channel-basic: ...from %d:%s%s%s%s", fd,
123 (cond & G_IO_ERR) ? " ERR" : "",
124 (cond & G_IO_HUP) ? " HUP" : "",
125 (cond & G_IO_IN) ? " IN" : "",
126 (cond & G_IO_PRI) ? " PRI" : "");
127
128 if (cond & (G_IO_ERR | G_IO_HUP))
129 {
130 shutdown_source (data);
131 retval = FALSE;
132 }
133
134 if (cond & G_IO_IN)
135 {
136 char buf[BUFSIZE];
137 guint nbytes = 0;
138 guint nb;
139 guint j;
140 int i, seq;
141 GIOError error;
142
143 error = read_all (fd, channel, (gchar *) &seq, sizeof (seq), &nb);
144 if (error == G_IO_ERROR_NONE)
145 {
146 if (nb == 0)
147 {
148 g_debug ("io-channel-basic: ...from %d: EOF", fd);
149 shutdown_source (data);
150 return FALSE;
151 }
152 g_assert_cmpuint (nb, ==, sizeof (nbytes));
153
154 for (i = 0; i < nkiddies; i++)
155 if (seqtab[i].fd == fd)
156 {
157 g_assert_cmpint (seq, ==, seqtab[i].seq);
158 seqtab[i].seq++;
159 break;
160 }
161
162 error =
163 read_all (fd, channel, (gchar *) &nbytes, sizeof (nbytes), &nb);
164 }
165
166 if (error != G_IO_ERROR_NONE)
167 return FALSE;
168
169 if (nb == 0)
170 {
171 g_debug ("io-channel-basic: ...from %d: EOF", fd);
172 shutdown_source (data);
173 return FALSE;
174 }
175 g_assert_cmpuint (nb, ==, sizeof (nbytes));
176
177 g_assert_cmpuint (nbytes, <, BUFSIZE);
178 g_debug ("io-channel-basic: ...from %d: %d bytes", fd, nbytes);
179 if (nbytes > 0)
180 {
181 error = read_all (fd, channel, buf, nbytes, &nb);
182
183 if (error != G_IO_ERROR_NONE)
184 return FALSE;
185
186 if (nb == 0)
187 {
188 g_debug ("io-channel-basic: ...from %d: EOF", fd);
189 shutdown_source (data);
190 return FALSE;
191 }
192
193 for (j = 0; j < nbytes; j++)
194 g_assert_cmpint (buf[j], ==, ' ' + (char) ((nbytes + j) % 95));
195 g_debug ("io-channel-basic: ...from %d: OK", fd);
196 }
197 }
198 return retval;
199 }
200
201 #ifdef G_OS_WIN32
202 static gboolean
203 recv_windows_message (GIOChannel *channel,
204 GIOCondition cond,
205 gpointer data)
206 {
207 GIOError error;
208 MSG msg;
209 gsize nb;
210
211 while (1)
212 {
213 error = g_io_channel_read (channel, (gchar *) &msg, sizeof (MSG), &nb);
214
215 if (error != G_IO_ERROR_NONE)
216 {
217 g_test_message ("io-channel-basic: ...reading Windows message: G_IO_ERROR_%s",
218 (error == G_IO_ERROR_AGAIN ? "AGAIN" : (error == G_IO_ERROR_INVAL ? "INVAL" : (error == G_IO_ERROR_UNKNOWN ? "UNKNOWN" : "???"))));
219 if (error == G_IO_ERROR_AGAIN)
220 continue;
221 }
222 break;
223 }
224
225 g_test_message ("io-channel-basic: ...Windows message for 0x%p: %d,%" G_GUINTPTR_FORMAT ",%" G_GINTPTR_FORMAT,
226 msg.hwnd, msg.message, msg.wParam, (gintptr) msg.lParam);
227
228 return TRUE;
229 }
230
231 LRESULT CALLBACK window_procedure (HWND hwnd,
232 UINT message,
233 WPARAM wparam,
234 LPARAM lparam);
235
236 LRESULT CALLBACK
237 window_procedure (HWND hwnd,
238 UINT message,
239 WPARAM wparam,
240 LPARAM lparam)
241 {
242 g_test_message ("io-channel-basic: window_procedure for 0x%p: %d,%" G_GUINTPTR_FORMAT ",%" G_GINTPTR_FORMAT,
243 hwnd, message, wparam, (gintptr) lparam);
244 return DefWindowProc (hwnd, message, wparam, lparam);
245 }
246 #endif
247
248 static void
249 spawn_process (int children_nb)
250 {
251 GIOChannel *my_read_channel;
252 gchar *cmdline;
253 int i;
254
255 #ifdef G_OS_WIN32
256 gint64 start, end;
257 GPollFD pollfd;
258 int pollresult;
259 ATOM klass;
260 static WNDCLASS wcl;
261 HWND hwnd;
262 GIOChannel *windows_messages_channel;
263
264 wcl.style = 0;
265 wcl.lpfnWndProc = window_procedure;
266 wcl.cbClsExtra = 0;
267 wcl.cbWndExtra = 0;
268 wcl.hInstance = GetModuleHandle (NULL);
269 wcl.hIcon = NULL;
270 wcl.hCursor = NULL;
271 wcl.hbrBackground = NULL;
272 wcl.lpszMenuName = NULL;
273 wcl.lpszClassName = L"io-channel-basic";
274
275 klass = RegisterClass (&wcl);
276 g_assert_cmpint (klass, !=, 0);
277
278 hwnd = CreateWindow (MAKEINTATOM (klass), L"io-channel-basic", 0, 0, 0, 10, 10,
279 NULL, NULL, wcl.hInstance, NULL);
280 g_assert_nonnull (hwnd);
281
282 windows_messages_channel =
283 g_io_channel_win32_new_messages ((guint) (guintptr) hwnd);
284 g_io_add_watch (windows_messages_channel, G_IO_IN, recv_windows_message, 0);
285 #endif
286
287 nkiddies = (children_nb > 0 ? children_nb : 1);
288 seqtab = g_malloc (nkiddies * 2 * sizeof (int));
289
290 for (i = 0; i < nkiddies; i++)
291 {
292 guint *id;
293 int pipe_to_sub[2], pipe_from_sub[2];
294
295 if (pipe (pipe_to_sub) == -1 || pipe (pipe_from_sub) == -1)
296 {
297 perror ("pipe");
298 exit (1);
299 }
300
301 seqtab[i].fd = pipe_from_sub[0];
302 seqtab[i].seq = 0;
303
304 my_read_channel = g_io_channel_unix_new (pipe_from_sub[0]);
305
306 id = g_new (guint, 1);
307 *id = g_io_add_watch_full (my_read_channel,
308 G_PRIORITY_DEFAULT,
309 G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP,
310 recv_message,
311 id, g_free);
312 nrunning++;
313
314 #ifdef G_OS_WIN32
315 /* Spawn new Win32 process */
316 cmdline =
317 g_strdup_printf ("%d:%d:0x%p", pipe_to_sub[0], pipe_from_sub[1], hwnd);
318 _spawnl (_P_NOWAIT, exec_name, exec_name, "--child", cmdline, NULL);
319 #else
320 /* Spawn new Unix process */
321 cmdline = g_strdup_printf ("%s --child %d:%d &",
322 exec_name, pipe_to_sub[0], pipe_from_sub[1]);
323 g_assert_no_errno (system (cmdline));
324 #endif
325 g_free (cmdline);
326
327 /* Closing pipes */
328 close (pipe_to_sub[0]);
329 close (pipe_from_sub[1]);
330
331 #ifdef G_OS_WIN32
332 start = g_get_monotonic_time();
333 g_io_channel_win32_make_pollfd (my_read_channel, G_IO_IN, &pollfd);
334 pollresult = g_io_channel_win32_poll (&pollfd, 1, 100);
335 end = g_get_monotonic_time();
336
337 g_test_message ("io-channel-basic: had to wait %" G_GINT64_FORMAT "s, result:%d",
338 (end - start) / 1000000, pollresult);
339 #endif
340 g_io_channel_unref (my_read_channel);
341 }
342
343 main_loop = g_main_loop_new (NULL, FALSE);
344 g_main_loop_run (main_loop);
345
346 g_main_loop_unref (main_loop);
347 g_free (seqtab);
348 }
349
350 static void
351 run_process (int argc, char *argv[])
352 {
353 int readfd, writefd;
354 gint64 dt;
355 char buf[BUFSIZE];
356 int buflen, i, j, n;
357 #ifdef G_OS_WIN32
358 HWND hwnd;
359 #endif
360
361 /* Extract parameters */
362 sscanf (argv[2], "%d:%d%n", &readfd, &writefd, &n);
363 #ifdef G_OS_WIN32
364 sscanf (argv[2] + n, ":0x%p", &hwnd);
365 #endif
366
367 dt = g_get_monotonic_time();
368 srand (dt ^ (dt / 1000) ^ readfd ^ (writefd << 4));
369
370 for (i = 0; i < 20 + rand () % 10; i++)
371 {
372 g_usleep ((100 + rand () % 10) * 2500);
373 buflen = rand () % BUFSIZE;
374 for (j = 0; j < buflen; j++)
375 buf[j] = ' ' + ((buflen + j) % 95);
376 g_debug ("io-channel-basic: child writing %d+%d bytes to %d",
377 (int) (sizeof (i) + sizeof (buflen)), buflen, writefd);
378 g_assert_cmpint (write (writefd, &i, sizeof (i)), ==, sizeof (i));
379 g_assert_cmpint (write (writefd, &buflen, sizeof (buflen)), ==, sizeof (buflen));
380 g_assert_cmpint (write (writefd, buf, buflen), ==, buflen);
381
382 #ifdef G_OS_WIN32
383 if (i % 10 == 0)
384 {
385 int msg = WM_USER + (rand () % 100);
386 WPARAM wparam = rand ();
387 LPARAM lparam = rand ();
388 g_test_message ("io-channel-basic: child posting message %d,%" G_GUINTPTR_FORMAT ",%" G_GINTPTR_FORMAT " to 0x%p",
389 msg, wparam, (gintptr) lparam, hwnd);
390 PostMessage (hwnd, msg, wparam, lparam);
391 }
392 #endif
393 }
394 g_debug ("io-channel-basic: child exiting, closing %d", writefd);
395 close (writefd);
396 }
397
398 static void
399 test_io_basics (void)
400 {
401 spawn_process (1);
402 #ifndef G_OS_WIN32
403 spawn_process (5);
404 #endif
405 }
406
407 int
408 main (int argc, char *argv[])
409 {
410 /* Get executable name */
411 exec_name = argv[0];
412
413 /* Run the tests */
414 g_test_init (&argc, &argv, NULL);
415
416 /* Run subprocess, if it is the case */
417 if (argc > 2)
418 {
419 run_process (argc, argv);
420 return 0;
421 }
422
423 g_test_add_func ("/gio/io-basics", test_io_basics);
424
425 return g_test_run ();
426 }