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
48 static const char *filename = "test.img";
49 static const off_t filesize = 1024*1024*1024;
51 static void remove_test_img (void);
52 static void *start_test_thread (void *);
54 struct test_thread_data {
55 guestfs_h *g; /* handle */
56 int direction; /* direction of transfer */
57 #define DIRECTION_UP 1 /* upload (test thread is writing) */
58 #define DIRECTION_DOWN 2 /* download (test thread is reading) */
59 int fd; /* pipe to read/write */
60 off_t cancel_posn; /* position at which to cancel */
61 off_t transfer_size; /* how much data thread wrote/read */
65 main (int argc, char *argv[])
70 pthread_t test_thread;
71 struct test_thread_data data;
72 int fds[2], r, op_error, op_errno, errors = 0;
75 srandom (time (NULL));
77 g = guestfs_create ();
79 fprintf (stderr, "failed to create handle\n");
83 /* Create a test image and test data. */
84 fd = open (filename, O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY, 0666);
90 atexit (remove_test_img);
92 if (lseek (fd, filesize - 1, SEEK_SET) == (off_t) -1) {
98 if (write (fd, &c, 1) != 1) {
104 if (close (fd) == -1) {
109 if (guestfs_add_drive_opts (g, filename,
110 GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw",
114 if (guestfs_launch (g) == -1)
117 if (guestfs_part_disk (g, "/dev/sda", "mbr") == -1)
120 if (guestfs_mkfs (g, "ext2", "/dev/sda1") == -1)
123 if (guestfs_mount_options (g, "", "/dev/sda1", "/") == -1)
126 /*----- Upload cancellation test -----*/
129 data.direction = DIRECTION_UP;
131 if (pipe (fds) == -1) {
137 snprintf (dev_fd, sizeof dev_fd, "/dev/fd/%d", fds[0]);
139 data.cancel_posn = random () % (filesize/4);
141 /* Create the test thread. */
142 r = pthread_create (&test_thread, NULL, start_test_thread, &data);
144 fprintf (stderr, "pthread_create: %s", strerror (r));
149 op_error = guestfs_upload (g, dev_fd, "/upload");
150 op_errno = guestfs_last_errno (g);
152 /* Kill the test thread and clean up. */
153 r = pthread_cancel (test_thread);
155 fprintf (stderr, "pthread_cancel: %s", strerror (r));
158 r = pthread_join (test_thread, NULL);
160 fprintf (stderr, "pthread_join: %s", strerror (r));
167 /* We expect to get an error, with errno == EINTR. */
168 if (op_error == -1 && op_errno == EINTR)
169 printf ("test-user-cancel: upload cancellation test passed (%ld/%ld)\n",
170 (long) data.cancel_posn, (long) data.transfer_size);
172 fprintf (stderr, "test-user-cancel: upload cancellation test FAILED\n");
173 fprintf (stderr, "cancel_posn %ld, upload returned %d, errno = %d (%s)\n",
174 (long) data.cancel_posn, op_error, op_errno, strerror (op_errno));
178 if (guestfs_rm (g, "/upload") == -1)
181 /*----- Download cancellation test -----*/
183 if (guestfs_touch (g, "/download") == -1)
186 if (guestfs_truncate_size (g, "/download", filesize/4) == -1)
190 data.direction = DIRECTION_DOWN;
192 if (pipe (fds) == -1) {
198 snprintf (dev_fd, sizeof dev_fd, "/dev/fd/%d", fds[1]);
200 data.cancel_posn = random () % (filesize/4);
202 /* Create the test thread. */
203 r = pthread_create (&test_thread, NULL, start_test_thread, &data);
205 fprintf (stderr, "pthread_create: %s", strerror (r));
209 /* Do the download. */
210 op_error = guestfs_download (g, "/download", dev_fd);
211 op_errno = guestfs_last_errno (g);
213 /* Kill the test thread and clean up. */
214 r = pthread_cancel (test_thread);
216 fprintf (stderr, "pthread_cancel: %s", strerror (r));
219 r = pthread_join (test_thread, NULL);
221 fprintf (stderr, "pthread_join: %s", strerror (r));
228 /* We expect to get an error, with errno == EINTR. */
229 if (op_error == -1 && op_errno == EINTR)
230 printf ("test-user-cancel: download cancellation test passed (%ld/%ld)\n",
231 (long) data.cancel_posn, (long) data.transfer_size);
233 fprintf (stderr, "test-user-cancel: download cancellation test FAILED\n");
234 fprintf (stderr, "cancel_posn %ld, upload returned %d, errno = %d (%s)\n",
235 (long) data.cancel_posn, op_error, op_errno, strerror (op_errno));
239 exit (errors == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
243 remove_test_img (void)
248 static char buffer[BUFSIZ];
251 #define MIN(a,b) ((a)<(b)?(a):(b))
255 start_test_thread (void *datav)
257 struct test_thread_data *data = datav;
261 data->transfer_size = 0;
263 if (data->direction == DIRECTION_UP) { /* thread is writing */
264 /* Feed data in, up to the cancellation point. */
265 while (data->transfer_size < data->cancel_posn) {
266 n = MIN (sizeof buffer,
267 (size_t) (data->cancel_posn - data->transfer_size));
268 r = write (data->fd, buffer, n);
270 perror ("test thread: write to pipe before user cancel");
273 data->transfer_size += r;
276 /* Do user cancellation. */
277 guestfs_user_cancel (data->g);
279 /* Keep feeding data after the cancellation point for as long as
280 * the main thread wants it.
283 r = write (data->fd, buffer, sizeof buffer);
285 perror ("test thread: write to pipe after user cancel");
288 data->transfer_size += r;
290 } else { /* thread is reading */
291 /* Sink data, up to the cancellation point. */
292 while (data->transfer_size < data->cancel_posn) {
293 n = MIN (sizeof buffer,
294 (size_t) (data->cancel_posn - data->transfer_size));
295 r = read (data->fd, buffer, n);
297 perror ("test thread: read from pipe before user cancel");
301 perror ("test thread: unexpected end of file before user cancel");
304 data->transfer_size += r;
307 /* Do user cancellation. */
308 guestfs_user_cancel (data->g);
310 /* Keep sinking data as long as the main thread is writing. */
312 r = read (data->fd, buffer, sizeof buffer);
314 perror ("test thread: read from pipe after user cancel");
319 data->transfer_size += r;