1 /***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 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 https://curl.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 * SPDX-License-Identifier: curl
22 *
23 ***************************************************************************/
24 #include "test.h"
25
26 #include <fcntl.h>
27
28 #include "testutil.h"
29 #include "warnless.h"
30 #include "memdebug.h"
31
32 #define TEST_HANG_TIMEOUT 60 * 1000
33
34 struct Sockets {
35 curl_socket_t *sockets;
36 int count; /* number of sockets actually stored in array */
37 int max_count; /* max number of sockets that fit in allocated array */
38 };
39
40 struct ReadWriteSockets {
41 struct Sockets read, write;
42 };
43
44 /**
45 * Remove a file descriptor from a sockets array.
46 */
removeFd(struct Sockets * sockets,curl_socket_t fd,int mention)47 static 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 */
addFd(struct Sockets * sockets,curl_socket_t fd,const char * what)67 static 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 >= sockets->max_count) {
85 /* this can't happen in normal cases */
86 fprintf(stderr, "too many file handles error\n");
87 exit(2);
88 }
89 /*
90 * Add file descriptor to array.
91 */
92 sockets->sockets[sockets->count] = fd;
93 ++sockets->count;
94 }
95
96 /**
97 * Callback invoked by curl to poll reading / writing of a socket.
98 */
curlSocketCallback(CURL * easy,curl_socket_t s,int action,void * userp,void * socketp)99 static int curlSocketCallback(CURL *easy, curl_socket_t s, int action,
100 void *userp, void *socketp)
101 {
102 struct ReadWriteSockets *sockets = userp;
103
104 (void)easy; /* unused */
105 (void)socketp; /* unused */
106
107 if(action == CURL_POLL_IN || action == CURL_POLL_INOUT)
108 addFd(&sockets->read, s, "read");
109
110 if(action == CURL_POLL_OUT || action == CURL_POLL_INOUT)
111 addFd(&sockets->write, s, "write");
112
113 if(action == CURL_POLL_REMOVE) {
114 removeFd(&sockets->read, s, 1);
115 removeFd(&sockets->write, s, 0);
116 }
117
118 return 0;
119 }
120
121 /**
122 * Callback invoked by curl to set a timeout.
123 */
curlTimerCallback(CURLM * multi,long timeout_ms,void * userp)124 static int curlTimerCallback(CURLM *multi, long timeout_ms, void *userp)
125 {
126 struct timeval *timeout = userp;
127
128 (void)multi; /* unused */
129 if(timeout_ms != -1) {
130 *timeout = tutil_tvnow();
131 timeout->tv_usec += (int)timeout_ms * 1000;
132 }
133 else {
134 timeout->tv_sec = -1;
135 }
136 return 0;
137 }
138
139 /**
140 * Check for curl completion.
141 */
checkForCompletion(CURLM * curl,int * success)142 static int checkForCompletion(CURLM *curl, int *success)
143 {
144 int result = 0;
145 *success = 0;
146 while(1) {
147 int numMessages;
148 CURLMsg *message = curl_multi_info_read(curl, &numMessages);
149 if(!message)
150 break;
151 if(message->msg == CURLMSG_DONE) {
152 result = 1;
153 if(message->data.result == CURLE_OK)
154 *success = 1;
155 else
156 *success = 0;
157 }
158 else {
159 fprintf(stderr, "Got an unexpected message from curl: %i\n",
160 (int)message->msg);
161 result = 1;
162 *success = 0;
163 }
164 }
165 return result;
166 }
167
getMicroSecondTimeout(struct timeval * timeout)168 static int getMicroSecondTimeout(struct timeval *timeout)
169 {
170 struct timeval now;
171 ssize_t result;
172 now = tutil_tvnow();
173 result = (ssize_t)((timeout->tv_sec - now.tv_sec) * 1000000 +
174 timeout->tv_usec - now.tv_usec);
175 if(result < 0)
176 result = 0;
177
178 return curlx_sztosi(result);
179 }
180
181 /**
182 * Update a fd_set with all of the sockets in use.
183 */
updateFdSet(struct Sockets * sockets,fd_set * fdset,curl_socket_t * maxFd)184 static void updateFdSet(struct Sockets *sockets, fd_set* fdset,
185 curl_socket_t *maxFd)
186 {
187 int i;
188 for(i = 0; i < sockets->count; ++i) {
189 FD_SET(sockets->sockets[i], fdset);
190 if(*maxFd < sockets->sockets[i] + 1) {
191 *maxFd = sockets->sockets[i] + 1;
192 }
193 }
194 }
195
notifyCurl(CURLM * curl,curl_socket_t s,int evBitmask,const char * info)196 static void notifyCurl(CURLM *curl, curl_socket_t s, int evBitmask,
197 const char *info)
198 {
199 int numhandles = 0;
200 CURLMcode result = curl_multi_socket_action(curl, s, evBitmask, &numhandles);
201 if(result != CURLM_OK) {
202 fprintf(stderr, "Curl error on %s: %i (%s)\n",
203 info, result, curl_multi_strerror(result));
204 }
205 }
206
207 /**
208 * Invoke curl when a file descriptor is set.
209 */
checkFdSet(CURLM * curl,struct Sockets * sockets,fd_set * fdset,int evBitmask,const char * name)210 static void checkFdSet(CURLM *curl, struct Sockets *sockets, fd_set *fdset,
211 int evBitmask, const char *name)
212 {
213 int i;
214 for(i = 0; i < sockets->count; ++i) {
215 if(FD_ISSET(sockets->sockets[i], fdset)) {
216 notifyCurl(curl, sockets->sockets[i], evBitmask, name);
217 }
218 }
219 }
220
test(char * URL)221 CURLcode test(char *URL)
222 {
223 CURLcode res = CURLE_OK;
224 CURL *curl = NULL;
225 FILE *hd_src = NULL;
226 int hd;
227 struct_stat file_info;
228 CURLM *m = NULL;
229 struct ReadWriteSockets sockets = {{NULL, 0, 0}, {NULL, 0, 0}};
230 struct timeval timeout = {-1, 0};
231 int success = 0;
232
233 assert(test_argc >= 5);
234
235 start_test_timing();
236
237 if(!libtest_arg3) {
238 fprintf(stderr, "Usage: lib582 [url] [filename] [username]\n");
239 return TEST_ERR_USAGE;
240 }
241
242 hd_src = fopen(libtest_arg2, "rb");
243 if(!hd_src) {
244 fprintf(stderr, "fopen() failed with error: %d (%s)\n",
245 errno, strerror(errno));
246 fprintf(stderr, "Error opening file: (%s)\n", libtest_arg2);
247 return TEST_ERR_FOPEN;
248 }
249
250 /* get the file size of the local file */
251 hd = fstat(fileno(hd_src), &file_info);
252 if(hd == -1) {
253 /* can't open file, bail out */
254 fprintf(stderr, "fstat() failed with error: %d (%s)\n",
255 errno, strerror(errno));
256 fprintf(stderr, "ERROR: cannot open file (%s)\n", libtest_arg2);
257 fclose(hd_src);
258 return TEST_ERR_FSTAT;
259 }
260 fprintf(stderr, "Set to upload %d bytes\n", (int)file_info.st_size);
261
262 res_global_init(CURL_GLOBAL_ALL);
263 if(res) {
264 fclose(hd_src);
265 return res;
266 }
267
268 easy_init(curl);
269
270 /* enable uploading */
271 easy_setopt(curl, CURLOPT_UPLOAD, 1L);
272
273 /* specify target */
274 easy_setopt(curl, CURLOPT_URL, URL);
275
276 /* go verbose */
277 easy_setopt(curl, CURLOPT_VERBOSE, 1L);
278
279 /* now specify which file to upload */
280 easy_setopt(curl, CURLOPT_READDATA, hd_src);
281
282 easy_setopt(curl, CURLOPT_USERPWD, libtest_arg3);
283 easy_setopt(curl, CURLOPT_SSH_PUBLIC_KEYFILE, test_argv[4]);
284 easy_setopt(curl, CURLOPT_SSH_PRIVATE_KEYFILE, test_argv[5]);
285 easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
286
287 easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)file_info.st_size);
288
289 multi_init(m);
290
291 multi_setopt(m, CURLMOPT_SOCKETFUNCTION, curlSocketCallback);
292 multi_setopt(m, CURLMOPT_SOCKETDATA, &sockets);
293
294 multi_setopt(m, CURLMOPT_TIMERFUNCTION, curlTimerCallback);
295 multi_setopt(m, CURLMOPT_TIMERDATA, &timeout);
296
297 multi_add_handle(m, curl);
298
299 while(!checkForCompletion(m, &success)) {
300 fd_set readSet, writeSet;
301 curl_socket_t maxFd = 0;
302 struct timeval tv = {10, 0};
303
304 FD_ZERO(&readSet);
305 FD_ZERO(&writeSet);
306 updateFdSet(&sockets.read, &readSet, &maxFd);
307 updateFdSet(&sockets.write, &writeSet, &maxFd);
308
309 if(timeout.tv_sec != -1) {
310 int usTimeout = getMicroSecondTimeout(&timeout);
311 tv.tv_sec = usTimeout / 1000000;
312 tv.tv_usec = usTimeout % 1000000;
313 }
314 else if(maxFd <= 0) {
315 tv.tv_sec = 0;
316 tv.tv_usec = 100000;
317 }
318
319 select_test((int)maxFd, &readSet, &writeSet, NULL, &tv);
320
321 /* Check the sockets for reading / writing */
322 checkFdSet(m, &sockets.read, &readSet, CURL_CSELECT_IN, "read");
323 checkFdSet(m, &sockets.write, &writeSet, CURL_CSELECT_OUT, "write");
324
325 if(timeout.tv_sec != -1 && getMicroSecondTimeout(&timeout) == 0) {
326 /* Curl's timer has elapsed. */
327 notifyCurl(m, CURL_SOCKET_TIMEOUT, 0, "timeout");
328 }
329
330 abort_on_test_timeout();
331 }
332
333 if(!success) {
334 fprintf(stderr, "Error uploading file.\n");
335 res = TEST_ERR_MAJOR_BAD;
336 }
337
338 test_cleanup:
339
340 /* proper cleanup sequence - type PB */
341
342 curl_multi_remove_handle(m, curl);
343 curl_easy_cleanup(curl);
344 curl_multi_cleanup(m);
345 curl_global_cleanup();
346
347 /* close the local file */
348 fclose(hd_src);
349
350 /* free local memory */
351 free(sockets.read.sockets);
352 free(sockets.write.sockets);
353
354 return res;
355 }
356