tests: Rename caution -> tests/qemu.
[libguestfs.git] / tests / c-api / test-user-cancel.c
1 /* libguestfs
2  * Copyright (C) 2011 Red Hat Inc.
3  *
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.
8  *
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.
13  *
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.
17  */
18
19 /* Test user cancellation.
20  *
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.
27  *
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
31  * cases.
32  */
33
34 #include <config.h>
35
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <unistd.h>
40 #include <fcntl.h>
41 #include <errno.h>
42 #include <sys/time.h>
43 #include <math.h>
44
45 #include <pthread.h>
46
47 #include "guestfs.h"
48
49 static const char *filename = "test.img";
50 static const off_t filesize = 1024*1024*1024;
51
52 static void remove_test_img (void);
53 static void *start_test_thread (void *);
54 static off_t random_cancel_posn (void);
55
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 */
64 };
65
66 int
67 main (int argc, char *argv[])
68 {
69   guestfs_h *g;
70   int fd;
71   char c = 0;
72   pthread_t test_thread;
73   struct test_thread_data data;
74   int fds[2], r, op_error, op_errno, errors = 0;
75   char dev_fd[64];
76
77   srand48 (time (NULL));
78
79   g = guestfs_create ();
80   if (g == NULL) {
81     fprintf (stderr, "failed to create handle\n");
82     exit (EXIT_FAILURE);
83   }
84
85   /* Create a test image and test data. */
86   fd = open (filename, O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY, 0666);
87   if (fd == -1) {
88     perror (filename);
89     exit (EXIT_FAILURE);
90   }
91
92   atexit (remove_test_img);
93
94   if (lseek (fd, filesize - 1, SEEK_SET) == (off_t) -1) {
95     perror ("lseek");
96     close (fd);
97     exit (EXIT_FAILURE);
98   }
99
100   if (write (fd, &c, 1) != 1) {
101     perror ("write");
102     close (fd);
103     exit (EXIT_FAILURE);
104   }
105
106   if (close (fd) == -1) {
107     perror ("test.img");
108     exit (EXIT_FAILURE);
109   }
110
111   if (guestfs_add_drive_opts (g, filename,
112                               GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw",
113                               -1) == -1)
114     exit (EXIT_FAILURE);
115
116   if (guestfs_launch (g) == -1)
117     exit (EXIT_FAILURE);
118
119   if (guestfs_part_disk (g, "/dev/sda", "mbr") == -1)
120     exit (EXIT_FAILURE);
121
122   if (guestfs_mkfs (g, "ext2", "/dev/sda1") == -1)
123     exit (EXIT_FAILURE);
124
125   if (guestfs_mount_options (g, "", "/dev/sda1", "/") == -1)
126     exit (EXIT_FAILURE);
127
128   /*----- Upload cancellation test -----*/
129
130   data.g = g;
131   data.direction = DIRECTION_UP;
132
133   if (pipe (fds) == -1) {
134     perror ("pipe");
135     exit (EXIT_FAILURE);
136   }
137
138   data.fd = fds[1];
139   snprintf (dev_fd, sizeof dev_fd, "/dev/fd/%d", fds[0]);
140
141   data.cancel_posn = random_cancel_posn ();
142
143   /* Create the test thread. */
144   r = pthread_create (&test_thread, NULL, start_test_thread, &data);
145   if (r != 0) {
146     fprintf (stderr, "pthread_create: %s", strerror (r));
147     exit (EXIT_FAILURE);
148   }
149
150   /* Do the upload. */
151   op_error = guestfs_upload (g, dev_fd, "/upload");
152   op_errno = guestfs_last_errno (g);
153
154   /* Kill the test thread and clean up. */
155   r = pthread_cancel (test_thread);
156   if (r != 0) {
157     fprintf (stderr, "pthread_cancel: %s", strerror (r));
158     exit (EXIT_FAILURE);
159   }
160   r = pthread_join (test_thread, NULL);
161   if (r != 0) {
162     fprintf (stderr, "pthread_join: %s", strerror (r));
163     exit (EXIT_FAILURE);
164   }
165
166   close (fds[0]);
167   close (fds[1]);
168
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);
173   else {
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));
177     errors++;
178   }
179
180   if (guestfs_rm (g, "/upload") == -1)
181     exit (EXIT_FAILURE);
182
183   /*----- Download cancellation test -----*/
184
185   if (guestfs_touch (g, "/download") == -1)
186     exit (EXIT_FAILURE);
187
188   if (guestfs_truncate_size (g, "/download", filesize/4) == -1)
189     exit (EXIT_FAILURE);
190
191   data.g = g;
192   data.direction = DIRECTION_DOWN;
193
194   if (pipe (fds) == -1) {
195     perror ("pipe");
196     exit (EXIT_FAILURE);
197   }
198
199   data.fd = fds[0];
200   snprintf (dev_fd, sizeof dev_fd, "/dev/fd/%d", fds[1]);
201
202   data.cancel_posn = random_cancel_posn ();
203
204   /* Create the test thread. */
205   r = pthread_create (&test_thread, NULL, start_test_thread, &data);
206   if (r != 0) {
207     fprintf (stderr, "pthread_create: %s", strerror (r));
208     exit (EXIT_FAILURE);
209   }
210
211   /* Do the download. */
212   op_error = guestfs_download (g, "/download", dev_fd);
213   op_errno = guestfs_last_errno (g);
214
215   /* Kill the test thread and clean up. */
216   r = pthread_cancel (test_thread);
217   if (r != 0) {
218     fprintf (stderr, "pthread_cancel: %s", strerror (r));
219     exit (EXIT_FAILURE);
220   }
221   r = pthread_join (test_thread, NULL);
222   if (r != 0) {
223     fprintf (stderr, "pthread_join: %s", strerror (r));
224     exit (EXIT_FAILURE);
225   }
226
227   close (fds[0]);
228   close (fds[1]);
229
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);
234   else {
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));
238     errors++;
239   }
240
241   exit (errors == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
242 }
243
244 static void
245 remove_test_img (void)
246 {
247   unlink (filename);
248 }
249
250 static char buffer[BUFSIZ];
251
252 #ifndef MIN
253 #define MIN(a,b) ((a)<(b)?(a):(b))
254 #endif
255
256 static void *
257 start_test_thread (void *datav)
258 {
259   struct test_thread_data *data = datav;
260   ssize_t r;
261   size_t n;
262
263   data->transfer_size = 0;
264
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);
271       if (r == -1) {
272         perror ("test thread: write to pipe before user cancel");
273         exit (EXIT_FAILURE);
274       }
275       data->transfer_size += r;
276     }
277
278     /* Keep feeding data after the cancellation point for as long as
279      * the main thread wants it.
280      */
281     while (1) {
282       /* Repeatedly assert the cancel flag.  We have to do this because
283        * the guestfs_upload command in the main thread may not have
284        * started yet.
285        */
286       guestfs_user_cancel (data->g);
287
288       r = write (data->fd, buffer, sizeof buffer);
289       if (r == -1) {
290         perror ("test thread: write to pipe after user cancel");
291         exit (EXIT_FAILURE);
292       }
293       data->transfer_size += r;
294     }
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);
301       if (r == -1) {
302         perror ("test thread: read from pipe before user cancel");
303         exit (EXIT_FAILURE);
304       }
305       if (r == 0) {
306         perror ("test thread: unexpected end of file before user cancel");
307         exit (EXIT_FAILURE);
308       }
309       data->transfer_size += r;
310     }
311
312     /* Do user cancellation. */
313     guestfs_user_cancel (data->g);
314
315     /* Keep sinking data as long as the main thread is writing. */
316     while (1) {
317       r = read (data->fd, buffer, sizeof buffer);
318       if (r == -1) {
319         perror ("test thread: read from pipe after user cancel");
320         exit (EXIT_FAILURE);
321       }
322       if (r == 0)
323         break;
324       data->transfer_size += r;
325     }
326
327     while (1)
328       pause ();
329   }
330
331   return NULL;
332 }
333
334 static double random_gauss (double mu, double sd);
335
336 /* Generate a random cancellation position, but skew it towards
337  * smaller numbers.
338  */
339 static off_t
340 random_cancel_posn (void)
341 {
342   const double mu = 65536;
343   const double sd = 65536 * 4;
344   double r;
345
346   do {
347     r = random_gauss (mu, sd);
348   } while (r <= 0);
349
350   return (off_t) r;
351 }
352
353 /* Generate a random Gaussian distributed number using the Box-Muller
354  * transformation.  (http://www.taygeta.com/random/gaussian.html)
355  */
356 static double
357 random_gauss (double mu, double sd)
358 {
359   double x1, x2, w, y1;
360
361   do {
362     x1 = 2. * drand48 () - 1.;
363     x2 = 2. * drand48 () - 1.;
364     w = x1 * x1 + x2 * x2;
365   } while (w >= 1.);
366
367   w = sqrt ((-2. * log (w)) / w);
368   y1 = x1 * w;
369   //y2 = x2 * w;
370   return mu + y1 * sd;
371 }