1 /* Test that iconv works in a multi-threaded program.
2    Copyright (C) 2017-2022 Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
4 
5    The GNU C Library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 2.1 of the License, or (at your option) any later version.
9 
10    The GNU C Library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14 
15    You should have received a copy of the GNU Lesser General Public
16    License along with the GNU C Library; if not, see
17    <https://www.gnu.org/licenses/>.  */
18 
19 /* This test runs several worker threads that perform the following three
20    steps in staggered synchronization with each other:
21    1. initialization (iconv_open)
22    2. conversion (iconv)
23    3. cleanup (iconv_close)
24    Having several threads synchronously (using pthread_barrier_*) perform
25    these routines exercises iconv code that handles concurrent attempts to
26    initialize and/or read global iconv state (namely configuration).  */
27 
28 #include <iconv.h>
29 #include <stdio.h>
30 #include <string.h>
31 
32 #include <support/support.h>
33 #include <support/xthread.h>
34 #include <support/check.h>
35 
36 #define TCOUNT 16
37 _Static_assert (TCOUNT %2 == 0,
38                 "thread count must be even, since we need two groups.");
39 
40 
41 #define CONV_INPUT "Hello, iconv!"
42 
43 
44 pthread_barrier_t sync;
45 pthread_barrier_t sync_half_pool;
46 
47 
48 /* Execute iconv_open, iconv and iconv_close in a synchronized way in
49    conjunction with other sibling worker threads.  If any step fails, print
50    an error to stdout and return NULL to the main thread to indicate the
51    error.  */
52 static void *
worker(void * arg)53 worker (void * arg)
54 {
55   long int tidx = (long int) arg;
56 
57   iconv_t cd;
58 
59   char ascii[] = CONV_INPUT;
60   char *inbufpos = ascii;
61   size_t inbytesleft = sizeof (CONV_INPUT);
62 
63   char *utf8 = xcalloc (sizeof (CONV_INPUT), 1);
64   char *outbufpos = utf8;
65   size_t outbytesleft = sizeof (CONV_INPUT);
66 
67   if (tidx < TCOUNT/2)
68     /* The first half of the worker thread pool synchronize together here,
69        then call iconv_open immediately after.  */
70     xpthread_barrier_wait (&sync_half_pool);
71   else
72     /* The second half wait for the first half to finish iconv_open and
73        continue to the next barrier (before the call to iconv below).  */
74     xpthread_barrier_wait (&sync);
75 
76   /* The above block of code staggers all subsequent pthread_barrier_wait
77      calls in a way that ensures a high chance of encountering these
78      combinations of concurrent iconv usage:
79      1) concurrent calls to iconv_open,
80      2) concurrent calls to iconv_open *and* iconv,
81      3) concurrent calls to iconv,
82      4) concurrent calls to iconv *and* iconv_close,
83      5) concurrent calls to iconv_close.  */
84 
85   cd = iconv_open ("UTF8", "ASCII");
86   TEST_VERIFY_EXIT (cd != (iconv_t) -1);
87 
88   xpthread_barrier_wait (&sync);
89 
90   TEST_VERIFY_EXIT (iconv (cd, &inbufpos, &inbytesleft, &outbufpos,
91                            &outbytesleft)
92                     != (size_t) -1);
93 
94   *outbufpos = '\0';
95 
96   xpthread_barrier_wait (&sync);
97 
98   TEST_VERIFY_EXIT (iconv_close (cd) == 0);
99 
100   /* The next conditional barrier wait is needed because we staggered the
101      threads into two groups in the beginning and at this point, the second
102      half of worker threads are waiting for the first half to finish
103      iconv_close before they can executing the same:  */
104   if (tidx < TCOUNT/2)
105     xpthread_barrier_wait (&sync);
106 
107   if (strncmp (utf8, CONV_INPUT, sizeof CONV_INPUT))
108     {
109       printf ("FAIL: thread %lx: invalid conversion output from iconv\n", tidx);
110       pthread_exit ((void *) (long int) 1);
111     }
112 
113   pthread_exit (NULL);
114 }
115 
116 
117 static int
do_test(void)118 do_test (void)
119 {
120   pthread_t thread[TCOUNT];
121   void * worker_output;
122   int i;
123 
124   xpthread_barrier_init (&sync, NULL, TCOUNT);
125   xpthread_barrier_init (&sync_half_pool, NULL, TCOUNT/2);
126 
127   for (i = 0; i < TCOUNT; i++)
128     thread[i] = xpthread_create (NULL, worker, (void *) (long int) i);
129 
130   for (i = 0; i < TCOUNT; i++)
131     {
132       worker_output = xpthread_join (thread[i]);
133       TEST_VERIFY_EXIT (worker_output == NULL);
134     }
135 
136   xpthread_barrier_destroy (&sync);
137   xpthread_barrier_destroy (&sync_half_pool);
138 
139   return 0;
140 }
141 
142 #include <support/test-driver.c>
143