/* libguestfs * Copyright (C) 2011 Red Hat Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ /* Test user cancellation. * * We perform the test using two threads. The main thread issues * guestfs commands to download and upload large files. Uploads and * downloads are done to/from a pipe which is connected back to the * current process. The second test thread sits on the other end of * the pipe, feeding or consuming data slowly, and injecting the user * cancel events at a particular place in the transfer. * * It is important to test both download and upload separately, since * these exercise different code paths in the library. However this * adds complexity here because these tests are symmetric-but-opposite * cases. */ #include #include #include #include #include #include #include #include #include #include #include "guestfs.h" static const char *filename = "test.img"; static const off_t filesize = 1024*1024*1024; static void remove_test_img (void); static void *start_test_thread (void *); static off_t random_cancel_posn (void); struct test_thread_data { guestfs_h *g; /* handle */ int direction; /* direction of transfer */ #define DIRECTION_UP 1 /* upload (test thread is writing) */ #define DIRECTION_DOWN 2 /* download (test thread is reading) */ int fd; /* pipe to read/write */ off_t cancel_posn; /* position at which to cancel */ off_t transfer_size; /* how much data thread wrote/read */ }; int main (int argc, char *argv[]) { guestfs_h *g; int fd; char c = 0; pthread_t test_thread; struct test_thread_data data; int fds[2], r, op_error, op_errno, errors = 0; char dev_fd[64]; srand48 (time (NULL)); g = guestfs_create (); if (g == NULL) { fprintf (stderr, "failed to create handle\n"); exit (EXIT_FAILURE); } /* Create a test image and test data. */ fd = open (filename, O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY, 0666); if (fd == -1) { perror (filename); exit (EXIT_FAILURE); } atexit (remove_test_img); if (lseek (fd, filesize - 1, SEEK_SET) == (off_t) -1) { perror ("lseek"); close (fd); exit (EXIT_FAILURE); } if (write (fd, &c, 1) != 1) { perror ("write"); close (fd); exit (EXIT_FAILURE); } if (close (fd) == -1) { perror ("test.img"); exit (EXIT_FAILURE); } if (guestfs_add_drive_opts (g, filename, GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw", -1) == -1) exit (EXIT_FAILURE); if (guestfs_launch (g) == -1) exit (EXIT_FAILURE); if (guestfs_part_disk (g, "/dev/sda", "mbr") == -1) exit (EXIT_FAILURE); if (guestfs_mkfs (g, "ext2", "/dev/sda1") == -1) exit (EXIT_FAILURE); if (guestfs_mount_options (g, "", "/dev/sda1", "/") == -1) exit (EXIT_FAILURE); /*----- Upload cancellation test -----*/ data.g = g; data.direction = DIRECTION_UP; if (pipe (fds) == -1) { perror ("pipe"); exit (EXIT_FAILURE); } data.fd = fds[1]; snprintf (dev_fd, sizeof dev_fd, "/dev/fd/%d", fds[0]); data.cancel_posn = random_cancel_posn (); /* Create the test thread. */ r = pthread_create (&test_thread, NULL, start_test_thread, &data); if (r != 0) { fprintf (stderr, "pthread_create: %s", strerror (r)); exit (EXIT_FAILURE); } /* Do the upload. */ op_error = guestfs_upload (g, dev_fd, "/upload"); op_errno = guestfs_last_errno (g); /* Kill the test thread and clean up. */ r = pthread_cancel (test_thread); if (r != 0) { fprintf (stderr, "pthread_cancel: %s", strerror (r)); exit (EXIT_FAILURE); } r = pthread_join (test_thread, NULL); if (r != 0) { fprintf (stderr, "pthread_join: %s", strerror (r)); exit (EXIT_FAILURE); } close (fds[0]); close (fds[1]); /* We expect to get an error, with errno == EINTR. */ if (op_error == -1 && op_errno == EINTR) printf ("test-user-cancel: upload cancellation test passed (%ld/%ld)\n", (long) data.cancel_posn, (long) data.transfer_size); else { fprintf (stderr, "test-user-cancel: upload cancellation test FAILED\n"); fprintf (stderr, "cancel_posn %ld, upload returned %d, errno = %d (%s)\n", (long) data.cancel_posn, op_error, op_errno, strerror (op_errno)); errors++; } if (guestfs_rm (g, "/upload") == -1) exit (EXIT_FAILURE); /*----- Download cancellation test -----*/ if (guestfs_touch (g, "/download") == -1) exit (EXIT_FAILURE); if (guestfs_truncate_size (g, "/download", filesize/4) == -1) exit (EXIT_FAILURE); data.g = g; data.direction = DIRECTION_DOWN; if (pipe (fds) == -1) { perror ("pipe"); exit (EXIT_FAILURE); } data.fd = fds[0]; snprintf (dev_fd, sizeof dev_fd, "/dev/fd/%d", fds[1]); data.cancel_posn = random_cancel_posn (); /* Create the test thread. */ r = pthread_create (&test_thread, NULL, start_test_thread, &data); if (r != 0) { fprintf (stderr, "pthread_create: %s", strerror (r)); exit (EXIT_FAILURE); } /* Do the download. */ op_error = guestfs_download (g, "/download", dev_fd); op_errno = guestfs_last_errno (g); /* Kill the test thread and clean up. */ r = pthread_cancel (test_thread); if (r != 0) { fprintf (stderr, "pthread_cancel: %s", strerror (r)); exit (EXIT_FAILURE); } r = pthread_join (test_thread, NULL); if (r != 0) { fprintf (stderr, "pthread_join: %s", strerror (r)); exit (EXIT_FAILURE); } close (fds[0]); close (fds[1]); /* We expect to get an error, with errno == EINTR. */ if (op_error == -1 && op_errno == EINTR) printf ("test-user-cancel: download cancellation test passed (%ld/%ld)\n", (long) data.cancel_posn, (long) data.transfer_size); else { fprintf (stderr, "test-user-cancel: download cancellation test FAILED\n"); fprintf (stderr, "cancel_posn %ld, upload returned %d, errno = %d (%s)\n", (long) data.cancel_posn, op_error, op_errno, strerror (op_errno)); errors++; } exit (errors == 0 ? EXIT_SUCCESS : EXIT_FAILURE); } static void remove_test_img (void) { unlink (filename); } static char buffer[BUFSIZ]; #ifndef MIN #define MIN(a,b) ((a)<(b)?(a):(b)) #endif static void * start_test_thread (void *datav) { struct test_thread_data *data = datav; ssize_t r; size_t n; data->transfer_size = 0; if (data->direction == DIRECTION_UP) { /* thread is writing */ /* Feed data in, up to the cancellation point. */ while (data->transfer_size < data->cancel_posn) { n = MIN (sizeof buffer, (size_t) (data->cancel_posn - data->transfer_size)); r = write (data->fd, buffer, n); if (r == -1) { perror ("test thread: write to pipe before user cancel"); exit (EXIT_FAILURE); } data->transfer_size += r; } /* Keep feeding data after the cancellation point for as long as * the main thread wants it. */ while (1) { /* Repeatedly assert the cancel flag. We have to do this because * the guestfs_upload command in the main thread may not have * started yet. */ guestfs_user_cancel (data->g); r = write (data->fd, buffer, sizeof buffer); if (r == -1) { perror ("test thread: write to pipe after user cancel"); exit (EXIT_FAILURE); } data->transfer_size += r; } } else { /* thread is reading */ /* Sink data, up to the cancellation point. */ while (data->transfer_size < data->cancel_posn) { n = MIN (sizeof buffer, (size_t) (data->cancel_posn - data->transfer_size)); r = read (data->fd, buffer, n); if (r == -1) { perror ("test thread: read from pipe before user cancel"); exit (EXIT_FAILURE); } if (r == 0) { perror ("test thread: unexpected end of file before user cancel"); exit (EXIT_FAILURE); } data->transfer_size += r; } /* Do user cancellation. */ guestfs_user_cancel (data->g); /* Keep sinking data as long as the main thread is writing. */ while (1) { r = read (data->fd, buffer, sizeof buffer); if (r == -1) { perror ("test thread: read from pipe after user cancel"); exit (EXIT_FAILURE); } if (r == 0) break; data->transfer_size += r; } while (1) pause (); } return NULL; } static double random_gauss (double mu, double sd); /* Generate a random cancellation position, but skew it towards * smaller numbers. */ static off_t random_cancel_posn (void) { const double mu = 65536; const double sd = 65536 * 4; double r; do { r = random_gauss (mu, sd); } while (r <= 0); return (off_t) r; } /* Generate a random Gaussian distributed number using the Box-Muller * transformation. (http://www.taygeta.com/random/gaussian.html) */ static double random_gauss (double mu, double sd) { double x1, x2, w, y1; do { x1 = 2. * drand48 () - 1.; x2 = 2. * drand48 () - 1.; w = x1 * x1 + x2 * x2; } while (w >= 1.); w = sqrt ((-2. * log (w)) / w); y1 = x1 * w; //y2 = x2 * w; return mu + y1 * sd; }