~ubuntu-branches/ubuntu/trusty/libguestfs/trusty

« back to all changes in this revision

Viewing changes to capitests/test-user-cancel.c

  • Committer: Package Import Robot
  • Author(s): Hilko Bengen
  • Date: 2012-04-13 20:14:25 UTC
  • mfrom: (15.1.29 sid)
  • Revision ID: package-import@ubuntu.com-20120413201425-28jcqz73eewoq1my
Tags: 1:1.16.18-1
New upstream version

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
 
}