1/***************************************************************************
2 *                                  _   _ ____  _
3 *  Project                     ___| | | |  _ \| |
4 *                             / __| | | | |_) | |
5 *                            | (__| |_| |  _ <| |___
6 *                             \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 1998 - 2012, Daniel Stenberg, <daniel@haxx.se>, et al.
9 *
10 * This software is licensed as described in the file COPYING, which
11 * you should have received as part of this distribution. The terms
12 * are also available at http://curl.haxx.se/docs/copyright.html.
13 *
14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15 * copies of the Software, and permit persons to whom the Software is
16 * furnished to do so, under the terms of the COPYING file.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 ***************************************************************************/
22#include "test.h"
23
24#include <fcntl.h>
25
26#include "testutil.h"
27#include "warnless.h"
28#include "memdebug.h"
29
30#define TEST_HANG_TIMEOUT 60 * 1000
31
32struct Sockets
33{
34  curl_socket_t *sockets;
35  int count;      /* number of sockets actually stored in array */
36  int max_count;  /* max number of sockets that fit in allocated array */
37};
38
39struct ReadWriteSockets
40{
41  struct Sockets read, write;
42};
43
44/**
45 * Remove a file descriptor from a sockets array.
46 */
47static void removeFd(struct Sockets* sockets, curl_socket_t fd, int mention)
48{
49  int i;
50
51  if(mention)
52    fprintf(stderr, "Remove socket fd %d\n", (int) fd);
53
54  for (i = 0; i < sockets->count; ++i) {
55    if (sockets->sockets[i] == fd) {
56      if (i < sockets->count - 1)
57        memmove(&sockets->sockets[i], &sockets->sockets[i + 1],
58              sizeof(curl_socket_t) * (sockets->count - (i + 1)));
59      --sockets->count;
60    }
61  }
62}
63
64/**
65 * Add a file descriptor to a sockets array.
66 */
67static void addFd(struct Sockets* sockets, curl_socket_t fd, const char *what)
68{
69  /**
70   * To ensure we only have each file descriptor once, we remove it then add
71   * it again.
72   */
73  fprintf(stderr, "Add socket fd %d for %s\n", (int) fd, what);
74  removeFd(sockets, fd, 0);
75  /*
76   * Allocate array storage when required.
77   */
78  if(!sockets->sockets) {
79    sockets->sockets = malloc(sizeof(curl_socket_t) * 20U);
80    if(!sockets->sockets)
81      return;
82    sockets->max_count = 20;
83  }
84  else if(sockets->count + 1 > sockets->max_count) {
85    curl_socket_t *oldptr = sockets->sockets;
86    sockets->sockets = realloc(oldptr, sizeof(curl_socket_t) *
87                               (sockets->max_count + 20));
88    if(!sockets->sockets) {
89      /* cleanup in test_cleanup */
90      sockets->sockets = oldptr;
91      return;
92    }
93    sockets->max_count += 20;
94  }
95  /*
96   * Add file descriptor to array.
97   */
98  sockets->sockets[sockets->count] = fd;
99  ++sockets->count;
100}
101
102/**
103 * Callback invoked by curl to poll reading / writing of a socket.
104 */
105static int curlSocketCallback(CURL *easy, curl_socket_t s, int action,
106                              void *userp, void *socketp)
107{
108  struct ReadWriteSockets* sockets = userp;
109
110  (void)easy; /* unused */
111  (void)socketp; /* unused */
112
113  if (action == CURL_POLL_IN || action == CURL_POLL_INOUT)
114    addFd(&sockets->read, s, "read");
115
116  if (action == CURL_POLL_OUT || action == CURL_POLL_INOUT)
117    addFd(&sockets->write, s, "write");
118
119  if(action == CURL_POLL_REMOVE) {
120    removeFd(&sockets->read, s, 1);
121    removeFd(&sockets->write, s, 0);
122  }
123
124  return 0;
125}
126
127/**
128 * Callback invoked by curl to set a timeout.
129 */
130static int curlTimerCallback(CURLM *multi, long timeout_ms, void *userp)
131{
132  struct timeval* timeout = userp;
133
134  (void)multi; /* unused */
135  if (timeout_ms != -1) {
136    *timeout = tutil_tvnow();
137    timeout->tv_usec += timeout_ms * 1000;
138  }
139  else {
140    timeout->tv_sec = -1;
141  }
142  return 0;
143}
144
145/**
146 * Check for curl completion.
147 */
148static int checkForCompletion(CURLM* curl, int* success)
149{
150  int numMessages;
151  CURLMsg* message;
152  int result = 0;
153  *success = 0;
154  while ((message = curl_multi_info_read(curl, &numMessages)) != NULL) {
155    if (message->msg == CURLMSG_DONE) {
156      result = 1;
157      if (message->data.result == CURLE_OK)
158        *success = 1;
159      else
160        *success = 0;
161    }
162    else {
163      fprintf(stderr, "Got an unexpected message from curl: %i\n",
164              (int)message->msg);
165      result = 1;
166      *success = 0;
167    }
168  }
169  return result;
170}
171
172static int getMicroSecondTimeout(struct timeval* timeout)
173{
174  struct timeval now;
175  ssize_t result;
176  now = tutil_tvnow();
177  result = (timeout->tv_sec - now.tv_sec) * 1000000 +
178    timeout->tv_usec - now.tv_usec;
179  if (result < 0)
180    result = 0;
181
182  return curlx_sztosi(result);
183}
184
185/**
186 * Update a fd_set with all of the sockets in use.
187 */
188static void updateFdSet(struct Sockets* sockets, fd_set* fdset,
189                        curl_socket_t *maxFd)
190{
191  int i;
192  for (i = 0; i < sockets->count; ++i) {
193    FD_SET(sockets->sockets[i], fdset);
194    if (*maxFd < sockets->sockets[i] + 1) {
195      *maxFd = sockets->sockets[i] + 1;
196    }
197  }
198}
199
200static void notifyCurl(CURLM *curl, curl_socket_t s, int evBitmask,
201                       const char *info)
202{
203  int numhandles = 0;
204  CURLMcode result = curl_multi_socket_action(curl, s, evBitmask, &numhandles);
205  if (result != CURLM_OK) {
206    fprintf(stderr, "Curl error on %s: %i (%s)\n",
207            info, result, curl_multi_strerror(result));
208  }
209}
210
211/**
212 * Invoke curl when a file descriptor is set.
213 */
214static void checkFdSet(CURLM *curl, struct Sockets *sockets, fd_set *fdset,
215                       int evBitmask, const char *name)
216{
217  int i;
218  for (i = 0; i < sockets->count; ++i) {
219    if (FD_ISSET(sockets->sockets[i], fdset)) {
220      notifyCurl(curl, sockets->sockets[i], evBitmask, name);
221    }
222  }
223}
224
225int test(char *URL)
226{
227  int res = 0;
228  CURL *curl = NULL;
229  FILE *hd_src = NULL;
230  int hd ;
231  int error;
232  struct_stat file_info;
233  CURLM *m = NULL;
234  struct ReadWriteSockets sockets = {{NULL, 0, 0}, {NULL, 0, 0}};
235  struct timeval timeout = {-1, 0};
236  int success = 0;
237
238  start_test_timing();
239
240  if (!libtest_arg3) {
241    fprintf(stderr, "Usage: lib582 [url] [filename] [username]\n");
242    return TEST_ERR_USAGE;
243  }
244
245  hd_src = fopen(libtest_arg2, "rb");
246  if(NULL == hd_src) {
247    error = ERRNO;
248    fprintf(stderr, "fopen() failed with error: %d (%s)\n",
249            error, strerror(error));
250    fprintf(stderr, "Error opening file: (%s)\n", libtest_arg2);
251    return TEST_ERR_FOPEN;
252  }
253
254  /* get the file size of the local file */
255  hd = fstat(fileno(hd_src), &file_info);
256  if(hd == -1) {
257    /* can't open file, bail out */
258    error = ERRNO;
259    fprintf(stderr, "fstat() failed with error: %d (%s)\n",
260            error, strerror(error));
261    fprintf(stderr, "ERROR: cannot open file (%s)\n", libtest_arg2);
262    fclose(hd_src);
263    return TEST_ERR_FSTAT;
264  }
265  fprintf(stderr, "Set to upload %d bytes\n", (int)file_info.st_size);
266
267  res_global_init(CURL_GLOBAL_ALL);
268  if(res) {
269    fclose(hd_src);
270    return res;
271  }
272
273  easy_init(curl);
274
275  /* enable uploading */
276  easy_setopt(curl, CURLOPT_UPLOAD, 1L);
277
278  /* specify target */
279  easy_setopt(curl,CURLOPT_URL, URL);
280
281  /* go verbose */
282  easy_setopt(curl, CURLOPT_VERBOSE, 1L);
283
284  /* now specify which file to upload */
285  easy_setopt(curl, CURLOPT_READDATA, hd_src);
286
287  easy_setopt(curl, CURLOPT_USERPWD, libtest_arg3);
288  easy_setopt(curl, CURLOPT_SSH_PUBLIC_KEYFILE, "curl_client_key.pub");
289  easy_setopt(curl, CURLOPT_SSH_PRIVATE_KEYFILE, "curl_client_key");
290
291  easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)file_info.st_size);
292
293  multi_init(m);
294
295  multi_setopt(m, CURLMOPT_SOCKETFUNCTION, curlSocketCallback);
296  multi_setopt(m, CURLMOPT_SOCKETDATA, &sockets);
297
298  multi_setopt(m, CURLMOPT_TIMERFUNCTION, curlTimerCallback);
299  multi_setopt(m, CURLMOPT_TIMERDATA, &timeout);
300
301  multi_add_handle(m, curl);
302
303  while (!checkForCompletion(m, &success))
304  {
305    fd_set readSet, writeSet;
306    curl_socket_t maxFd = 0;
307    struct timeval tv = {10, 0};
308
309    FD_ZERO(&readSet);
310    FD_ZERO(&writeSet);
311    updateFdSet(&sockets.read, &readSet, &maxFd);
312    updateFdSet(&sockets.write, &writeSet, &maxFd);
313
314    if (timeout.tv_sec != -1)
315    {
316      int usTimeout = getMicroSecondTimeout(&timeout);
317      tv.tv_sec = usTimeout / 1000000;
318      tv.tv_usec = usTimeout % 1000000;
319    }
320    else if (maxFd <= 0)
321    {
322      tv.tv_sec = 0;
323      tv.tv_usec = 100000;
324    }
325
326    select_test(maxFd, &readSet, &writeSet, NULL, &tv);
327
328    /* Check the sockets for reading / writing */
329    checkFdSet(m, &sockets.read, &readSet, CURL_CSELECT_IN, "read");
330    checkFdSet(m, &sockets.write, &writeSet, CURL_CSELECT_OUT, "write");
331
332    if (timeout.tv_sec != -1 && getMicroSecondTimeout(&timeout) == 0)
333    {
334      /* Curl's timer has elapsed. */
335      notifyCurl(m, CURL_SOCKET_TIMEOUT, 0, "timeout");
336    }
337
338    abort_on_test_timeout();
339  }
340
341  if (!success)
342  {
343    fprintf(stderr, "Error uploading file.\n");
344    res = TEST_ERR_MAJOR_BAD;
345  }
346
347test_cleanup:
348
349  /* proper cleanup sequence - type PB */
350
351  curl_multi_remove_handle(m, curl);
352  curl_easy_cleanup(curl);
353  curl_multi_cleanup(m);
354  curl_global_cleanup();
355
356  /* close the local file */
357  fclose(hd_src);
358
359  /* free local memory */
360  if(sockets.read.sockets)
361    free(sockets.read.sockets);
362  if(sockets.write.sockets)
363    free(sockets.write.sockets);
364
365  return res;
366}
367