2 * Copyright (C) 2011 Red Hat Inc.
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 /* Test user cancellation.
21 * We perform the test using two threads. The main thread issues
22 * guestfs commands to download and upload large files. Uploads and
23 * downloads are done to/from a pipe which is connected back to the
24 * current process. The second test thread sits on the other end of
25 * the pipe, feeding or consuming data slowly, and injecting the user
26 * cancel events at a particular place in the transfer.
28 * It is important to test both download and upload separately, since
29 * these exercise different code paths in the library. However this
30 * adds complexity here because these tests are symmetric-but-opposite
49 static const char *filename = "test.img";
50 static const off_t filesize = 1024*1024*1024;
52 static void remove_test_img (void);
53 static void *start_test_thread (void *);
54 static off_t random_cancel_posn (void);
56 struct test_thread_data {
57 guestfs_h *g; /* handle */
58 int direction; /* direction of transfer */
59 #define DIRECTION_UP 1 /* upload (test thread is writing) */
60 #define DIRECTION_DOWN 2 /* download (test thread is reading) */
61 int fd; /* pipe to read/write */
62 off_t cancel_posn; /* position at which to cancel */
63 off_t transfer_size; /* how much data thread wrote/read */
67 main (int argc, char *argv[])
72 pthread_t test_thread;
73 struct test_thread_data data;
74 int fds[2], r, op_error, op_errno, errors = 0;
77 srand48 (time (NULL));
79 g = guestfs_create ();
81 fprintf (stderr, "failed to create handle\n");
85 /* Create a test image and test data. */
86 fd = open (filename, O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY, 0666);
92 atexit (remove_test_img);
94 if (lseek (fd, filesize - 1, SEEK_SET) == (off_t) -1) {
100 if (write (fd, &c, 1) != 1) {
106 if (close (fd) == -1) {
111 if (guestfs_add_drive_opts (g, filename,
112 GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw",
116 if (guestfs_launch (g) == -1)
119 if (guestfs_part_disk (g, "/dev/sda", "mbr") == -1)
122 if (guestfs_mkfs (g, "ext2", "/dev/sda1") == -1)
125 if (guestfs_mount_options (g, "", "/dev/sda1", "/") == -1)
128 /*----- Upload cancellation test -----*/
131 data.direction = DIRECTION_UP;
133 if (pipe (fds) == -1) {
139 snprintf (dev_fd, sizeof dev_fd, "/dev/fd/%d", fds[0]);
141 data.cancel_posn = random_cancel_posn ();
143 /* Create the test thread. */
144 r = pthread_create (&test_thread, NULL, start_test_thread, &data);
146 fprintf (stderr, "pthread_create: %s", strerror (r));
151 op_error = guestfs_upload (g, dev_fd, "/upload");
152 op_errno = guestfs_last_errno (g);
154 /* Kill the test thread and clean up. */
155 r = pthread_cancel (test_thread);
157 fprintf (stderr, "pthread_cancel: %s", strerror (r));
160 r = pthread_join (test_thread, NULL);
162 fprintf (stderr, "pthread_join: %s", strerror (r));
169 /* We expect to get an error, with errno == EINTR. */
170 if (op_error == -1 && op_errno == EINTR)
171 printf ("test-user-cancel: upload cancellation test passed (%ld/%ld)\n",
172 (long) data.cancel_posn, (long) data.transfer_size);
174 fprintf (stderr, "test-user-cancel: upload cancellation test FAILED\n");
175 fprintf (stderr, "cancel_posn %ld, upload returned %d, errno = %d (%s)\n",
176 (long) data.cancel_posn, op_error, op_errno, strerror (op_errno));
180 if (guestfs_rm (g, "/upload") == -1)
183 /*----- Download cancellation test -----*/
185 if (guestfs_touch (g, "/download") == -1)
188 if (guestfs_truncate_size (g, "/download", filesize/4) == -1)
192 data.direction = DIRECTION_DOWN;
194 if (pipe (fds) == -1) {
200 snprintf (dev_fd, sizeof dev_fd, "/dev/fd/%d", fds[1]);
202 data.cancel_posn = random_cancel_posn ();
204 /* Create the test thread. */
205 r = pthread_create (&test_thread, NULL, start_test_thread, &data);
207 fprintf (stderr, "pthread_create: %s", strerror (r));
211 /* Do the download. */
212 op_error = guestfs_download (g, "/download", dev_fd);
213 op_errno = guestfs_last_errno (g);
215 /* Kill the test thread and clean up. */
216 r = pthread_cancel (test_thread);
218 fprintf (stderr, "pthread_cancel: %s", strerror (r));
221 r = pthread_join (test_thread, NULL);
223 fprintf (stderr, "pthread_join: %s", strerror (r));
230 /* We expect to get an error, with errno == EINTR. */
231 if (op_error == -1 && op_errno == EINTR)
232 printf ("test-user-cancel: download cancellation test passed (%ld/%ld)\n",
233 (long) data.cancel_posn, (long) data.transfer_size);
235 fprintf (stderr, "test-user-cancel: download cancellation test FAILED\n");
236 fprintf (stderr, "cancel_posn %ld, upload returned %d, errno = %d (%s)\n",
237 (long) data.cancel_posn, op_error, op_errno, strerror (op_errno));
241 exit (errors == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
245 remove_test_img (void)
250 static char buffer[BUFSIZ];
253 #define MIN(a,b) ((a)<(b)?(a):(b))
257 start_test_thread (void *datav)
259 struct test_thread_data *data = datav;
263 data->transfer_size = 0;
265 if (data->direction == DIRECTION_UP) { /* thread is writing */
266 /* Feed data in, up to the cancellation point. */
267 while (data->transfer_size < data->cancel_posn) {
268 n = MIN (sizeof buffer,
269 (size_t) (data->cancel_posn - data->transfer_size));
270 r = write (data->fd, buffer, n);
272 perror ("test thread: write to pipe before user cancel");
275 data->transfer_size += r;
278 /* Keep feeding data after the cancellation point for as long as
279 * the main thread wants it.
282 /* Repeatedly assert the cancel flag. We have to do this because
283 * the guestfs_upload command in the main thread may not have
286 guestfs_user_cancel (data->g);
288 r = write (data->fd, buffer, sizeof buffer);
290 perror ("test thread: write to pipe after user cancel");
293 data->transfer_size += r;
295 } else { /* thread is reading */
296 /* Sink data, up to the cancellation point. */
297 while (data->transfer_size < data->cancel_posn) {
298 n = MIN (sizeof buffer,
299 (size_t) (data->cancel_posn - data->transfer_size));
300 r = read (data->fd, buffer, n);
302 perror ("test thread: read from pipe before user cancel");
306 perror ("test thread: unexpected end of file before user cancel");
309 data->transfer_size += r;
312 /* Do user cancellation. */
313 guestfs_user_cancel (data->g);
315 /* Keep sinking data as long as the main thread is writing. */
317 r = read (data->fd, buffer, sizeof buffer);
319 perror ("test thread: read from pipe after user cancel");
324 data->transfer_size += r;
334 static double random_gauss (double mu, double sd);
336 /* Generate a random cancellation position, but skew it towards
340 random_cancel_posn (void)
342 const double mu = 65536;
343 const double sd = 65536 * 4;
347 r = random_gauss (mu, sd);
353 /* Generate a random Gaussian distributed number using the Box-Muller
354 * transformation. (http://www.taygeta.com/random/gaussian.html)
357 random_gauss (double mu, double sd)
359 double x1, x2, w, y1;
362 x1 = 2. * drand48 () - 1.;
363 x2 = 2. * drand48 () - 1.;
364 w = x1 * x1 + x2 * x2;
367 w = sqrt ((-2. * log (w)) / w);