Stable OCaml dependencies.
[libguestfs.git] / capitests / 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
44 #include <pthread.h>
45
46 #include "guestfs.h"
47
48 static const char *filename = "test.img";
49 static const off_t filesize = 1024*1024*1024;
50
51 static void remove_test_img (void);
52 static void *start_test_thread (void *);
53
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 */
62 };
63
64 int
65 main (int argc, char *argv[])
66 {
67   guestfs_h *g;
68   int fd;
69   char c = 0;
70   pthread_t test_thread;
71   struct test_thread_data data;
72   int fds[2], r, op_error, op_errno, errors = 0;
73   char dev_fd[64];
74
75   srandom (time (NULL));
76
77   g = guestfs_create ();
78   if (g == NULL) {
79     fprintf (stderr, "failed to create handle\n");
80     exit (EXIT_FAILURE);
81   }
82
83   /* Create a test image and test data. */
84   fd = open (filename, O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY, 0666);
85   if (fd == -1) {
86     perror (filename);
87     exit (EXIT_FAILURE);
88   }
89
90   atexit (remove_test_img);
91
92   if (lseek (fd, filesize - 1, SEEK_SET) == (off_t) -1) {
93     perror ("lseek");
94     close (fd);
95     exit (EXIT_FAILURE);
96   }
97
98   if (write (fd, &c, 1) != 1) {
99     perror ("write");
100     close (fd);
101     exit (EXIT_FAILURE);
102   }
103
104   if (close (fd) == -1) {
105     perror ("test.img");
106     exit (EXIT_FAILURE);
107   }
108
109   if (guestfs_add_drive_opts (g, filename,
110                               GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw",
111                               -1) == -1)
112     exit (EXIT_FAILURE);
113
114   if (guestfs_launch (g) == -1)
115     exit (EXIT_FAILURE);
116
117   if (guestfs_part_disk (g, "/dev/sda", "mbr") == -1)
118     exit (EXIT_FAILURE);
119
120   if (guestfs_mkfs (g, "ext2", "/dev/sda1") == -1)
121     exit (EXIT_FAILURE);
122
123   if (guestfs_mount_options (g, "", "/dev/sda1", "/") == -1)
124     exit (EXIT_FAILURE);
125
126   /*----- Upload cancellation test -----*/
127
128   data.g = g;
129   data.direction = DIRECTION_UP;
130
131   if (pipe (fds) == -1) {
132     perror ("pipe");
133     exit (EXIT_FAILURE);
134   }
135
136   data.fd = fds[1];
137   snprintf (dev_fd, sizeof dev_fd, "/dev/fd/%d", fds[0]);
138
139   data.cancel_posn = random () % (filesize/4);
140
141   /* Create the test thread. */
142   r = pthread_create (&test_thread, NULL, start_test_thread, &data);
143   if (r != 0) {
144     fprintf (stderr, "pthread_create: %s", strerror (r));
145     exit (EXIT_FAILURE);
146   }
147
148   /* Do the upload. */
149   op_error = guestfs_upload (g, dev_fd, "/upload");
150   op_errno = guestfs_last_errno (g);
151
152   /* Kill the test thread and clean up. */
153   r = pthread_cancel (test_thread);
154   if (r != 0) {
155     fprintf (stderr, "pthread_cancel: %s", strerror (r));
156     exit (EXIT_FAILURE);
157   }
158   r = pthread_join (test_thread, NULL);
159   if (r != 0) {
160     fprintf (stderr, "pthread_join: %s", strerror (r));
161     exit (EXIT_FAILURE);
162   }
163
164   close (fds[0]);
165   close (fds[1]);
166
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);
171   else {
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));
175     errors++;
176   }
177
178   if (guestfs_rm (g, "/upload") == -1)
179     exit (EXIT_FAILURE);
180
181   /*----- Download cancellation test -----*/
182
183   if (guestfs_touch (g, "/download") == -1)
184     exit (EXIT_FAILURE);
185
186   if (guestfs_truncate_size (g, "/download", filesize/4) == -1)
187     exit (EXIT_FAILURE);
188
189   data.g = g;
190   data.direction = DIRECTION_DOWN;
191
192   if (pipe (fds) == -1) {
193     perror ("pipe");
194     exit (EXIT_FAILURE);
195   }
196
197   data.fd = fds[0];
198   snprintf (dev_fd, sizeof dev_fd, "/dev/fd/%d", fds[1]);
199
200   data.cancel_posn = random () % (filesize/4);
201
202   /* Create the test thread. */
203   r = pthread_create (&test_thread, NULL, start_test_thread, &data);
204   if (r != 0) {
205     fprintf (stderr, "pthread_create: %s", strerror (r));
206     exit (EXIT_FAILURE);
207   }
208
209   /* Do the download. */
210   op_error = guestfs_download (g, "/download", dev_fd);
211   op_errno = guestfs_last_errno (g);
212
213   /* Kill the test thread and clean up. */
214   r = pthread_cancel (test_thread);
215   if (r != 0) {
216     fprintf (stderr, "pthread_cancel: %s", strerror (r));
217     exit (EXIT_FAILURE);
218   }
219   r = pthread_join (test_thread, NULL);
220   if (r != 0) {
221     fprintf (stderr, "pthread_join: %s", strerror (r));
222     exit (EXIT_FAILURE);
223   }
224
225   close (fds[0]);
226   close (fds[1]);
227
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);
232   else {
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));
236     errors++;
237   }
238
239   exit (errors == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
240 }
241
242 static void
243 remove_test_img (void)
244 {
245   unlink (filename);
246 }
247
248 static char buffer[BUFSIZ];
249
250 #ifndef MIN
251 #define MIN(a,b) ((a)<(b)?(a):(b))
252 #endif
253
254 static void *
255 start_test_thread (void *datav)
256 {
257   struct test_thread_data *data = datav;
258   ssize_t r;
259   size_t n;
260
261   data->transfer_size = 0;
262
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);
269       if (r == -1) {
270         perror ("test thread: write to pipe before user cancel");
271         exit (EXIT_FAILURE);
272       }
273       data->transfer_size += r;
274     }
275
276     /* Do user cancellation. */
277     guestfs_user_cancel (data->g);
278
279     /* Keep feeding data after the cancellation point for as long as
280      * the main thread wants it.
281      */
282     while (1) {
283       r = write (data->fd, buffer, sizeof buffer);
284       if (r == -1) {
285         perror ("test thread: write to pipe after user cancel");
286         exit (EXIT_FAILURE);
287       }
288       data->transfer_size += r;
289     }
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);
296       if (r == -1) {
297         perror ("test thread: read from pipe before user cancel");
298         exit (EXIT_FAILURE);
299       }
300       if (r == 0) {
301         perror ("test thread: unexpected end of file before user cancel");
302         exit (EXIT_FAILURE);
303       }
304       data->transfer_size += r;
305     }
306
307     /* Do user cancellation. */
308     guestfs_user_cancel (data->g);
309
310     /* Keep sinking data as long as the main thread is writing. */
311     while (1) {
312       r = read (data->fd, buffer, sizeof buffer);
313       if (r == -1) {
314         perror ("test thread: read from pipe after user cancel");
315         exit (EXIT_FAILURE);
316       }
317       if (r == 0)
318         break;
319       data->transfer_size += r;
320     }
321
322     while (1)
323       pause ();
324   }
325
326   return NULL;
327 }