1/* Bug 22830: malloc_stats fails to re-enable cancellation on exit.
2 Copyright (C) 2018 Free Software Foundation.
3 Copying and distribution of this file, with or without modification,
4 are permitted in any medium without royalty provided the copyright
5 notice and this notice are preserved. This file is offered as-is,
6 without any warranty. */
7
8#include <errno.h>
9#include <stdio.h>
10#include <string.h>
11
12#include <pthread.h>
13#include <sys/stat.h>
14#include <sys/types.h>
15#include <fcntl.h>
16#include <unistd.h>
17
18#include <malloc.h>
19
20static void *
21test_threadproc (void *gatep)
22{
23 /* When we are released from the barrier, there is a cancellation
24 request pending for this thread. N.B. pthread_barrier_wait is
25 not itself a cancellation point (oddly enough). */
26 pthread_barrier_wait (barrier: (pthread_barrier_t *)gatep);
27 malloc_stats ();
28 fputs ("this call should trigger cancellation\n", stderr);
29 return 0;
30}
31
32/* We cannot replace stderr with a memstream because writes to memstreams
33 do not trigger cancellation. Instead, main swaps out fd 2 to point to
34 a pipe, and this thread reads from the pipe and writes to a memstream
35 until EOF, then returns the data accumulated in the memstream. main
36 can't do that itself because, when the test thread gets cancelled,
37 it doesn't close the pipe. */
38
39struct buffer_tp_args
40{
41 int ifd;
42 FILE *real_stderr;
43};
44
45static void *
46buffer_threadproc (void *argp)
47{
48 struct buffer_tp_args *args = argp;
49 int ifd = args->ifd;
50 char block[BUFSIZ], *p;
51 ssize_t nread;
52 size_t nwritten;
53
54 char *obuf = 0;
55 size_t obufsz = 0;
56 FILE *ofp = open_memstream (bufloc: &obuf, sizeloc: &obufsz);
57 if (!ofp)
58 {
59 fprintf (args->real_stderr,
60 "buffer_threadproc: open_memstream: %s\n", strerror (errno));
61 return 0;
62 }
63
64 while ((nread = read (ifd, block, BUFSIZ)) > 0)
65 {
66 p = block;
67 do
68 {
69 nwritten = fwrite_unlocked (p, 1, nread, ofp);
70 if (nwritten == 0)
71 {
72 fprintf (args->real_stderr,
73 "buffer_threadproc: fwrite_unlocked: %s\n",
74 strerror (errno));
75 return 0;
76 }
77 nread -= nwritten;
78 p += nwritten;
79 }
80 while (nread > 0);
81 }
82 if (nread == -1)
83 {
84 fprintf (args->real_stderr, "buffer_threadproc: read: %s\n",
85 strerror (errno));
86 return 0;
87 }
88 close (fd: ifd);
89 fclose (ofp);
90 return obuf;
91}
92
93
94static int
95do_test (void)
96{
97 int result = 0, err, real_stderr_fd, bufpipe[2];
98 pthread_t t_thr, b_thr;
99 pthread_barrier_t gate;
100 void *rv;
101 FILE *real_stderr;
102 char *obuf;
103 void *obuf_v;
104 struct buffer_tp_args b_args;
105
106 real_stderr_fd = dup (fd: 2);
107 if (real_stderr_fd == -1)
108 {
109 perror ("dup");
110 return 2;
111 }
112 real_stderr = fdopen(real_stderr_fd, "w");
113 if (!real_stderr)
114 {
115 perror ("fdopen");
116 return 2;
117 }
118 if (setvbuf (stream: real_stderr, buf: 0, _IOLBF, n: 0))
119 {
120 perror ("setvbuf(real_stderr)");
121 return 2;
122 }
123
124 if (pipe (pipedes: bufpipe))
125 {
126 perror ("pipe");
127 return 2;
128 }
129
130 /* Below this point, nobody other than the test_threadproc should use
131 the normal stderr. */
132 if (dup2 (fd: bufpipe[1], fd2: 2) == -1)
133 {
134 fprintf (real_stderr, "dup2: %s\n", strerror (errno));
135 return 2;
136 }
137 close (fd: bufpipe[1]);
138
139 b_args.ifd = bufpipe[0];
140 b_args.real_stderr = real_stderr;
141 err = pthread_create (newthread: &b_thr, attr: 0, start_routine: buffer_threadproc, arg: &b_args);
142 if (err)
143 {
144 fprintf (real_stderr, "pthread_create(buffer_thr): %s\n",
145 strerror (errnum: err));
146 return 2;
147 }
148
149 err = pthread_barrier_init (barrier: &gate, attr: 0, count: 2);
150 if (err)
151 {
152 fprintf (real_stderr, "pthread_barrier_init: %s\n", strerror (errnum: err));
153 return 2;
154 }
155
156 err = pthread_create (newthread: &t_thr, attr: 0, start_routine: test_threadproc, arg: &gate);
157 if (err)
158 {
159 fprintf (real_stderr, "pthread_create(test_thr): %s\n", strerror (errnum: err));
160 return 2;
161 }
162
163 err = pthread_cancel (th: t_thr);
164 if (err)
165 {
166 fprintf (real_stderr, "pthread_cancel: %s\n", strerror (errnum: err));
167 return 2;
168 }
169
170 pthread_barrier_wait (barrier: &gate); /* cannot fail */
171
172 err = pthread_join (th: t_thr, thread_return: &rv);
173 if (err)
174 {
175 fprintf (real_stderr, "pthread_join(test_thr): %s\n", strerror (errnum: err));
176 return 2;
177 }
178
179 /* Closing the normal stderr releases the buffer_threadproc from its
180 loop. */
181 fclose (stderr);
182 err = pthread_join (th: b_thr, thread_return: &obuf_v);
183 if (err)
184 {
185 fprintf (real_stderr, "pthread_join(buffer_thr): %s\n", strerror (errnum: err));
186 return 2;
187 }
188 obuf = obuf_v;
189 if (obuf == 0)
190 return 2; /* error within buffer_threadproc, already reported */
191
192 if (rv != PTHREAD_CANCELED)
193 {
194 fputs ("FAIL: thread was not cancelled\n", real_stderr);
195 result = 1;
196 }
197 /* obuf should have received all of the text printed by malloc_stats,
198 but not the text printed by the final call to fputs. */
199 if (!strstr (obuf, "max mmap bytes"))
200 {
201 fputs ("FAIL: malloc_stats output incomplete\n", real_stderr);
202 result = 1;
203 }
204 if (strstr (obuf, "this call should trigger cancellation"))
205 {
206 fputs ("FAIL: fputs produced output\n", real_stderr);
207 result = 1;
208 }
209
210 if (result == 1)
211 {
212 fputs ("--- output from thread below ---\n", real_stderr);
213 fputs (obuf, real_stderr);
214 }
215 return result;
216}
217
218#include <support/test-driver.c>
219

source code of glibc/malloc/tst-malloc-stats-cancellation.c