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);