/[axel]/branches/3.x-broken/src/axel.c
ViewVC logotype

Contents of /branches/3.x-broken/src/axel.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 70 - (show annotations) (download)
Mon Dec 22 19:18:31 2008 UTC (4 years, 5 months ago) by phihag-guest
File MIME type: text/plain
File size: 18689 byte(s)
Use own strdup variants exclusively
work on conn.*

1 /********************************************************************\
2 * Axel -- A lighter download accelerator for Linux and other Unices. *
3 * *
4 * Copyright 2001 Wilmer van der Gaast *
5 \********************************************************************/
6
7 /* Main control */
8
9 /*
10 This program is free software; you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation; either version 2 of the License, or
13 (at your option) any later version.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License with
21 the Debian GNU/Linux distribution in file /usr/doc/copyright/GPL;
22 if not, write to the Free Software Foundation, Inc., 59 Temple Place,
23 Suite 330, Boston, MA 02111-1307 USA
24 */
25
26 #include "axel.h"
27
28 static void axel_prepare(axel_t* axel);
29 static void axel_teardown(axel_t* axel);
30 static void axel_save_state(axel_t* axel);
31 static void axel_set_state(axel_t* axel, int state);
32 static void axel_update_display(const axel_t* axel);
33
34 /**
35 * Add a URL. urlstr is a pointer to a string specified by the user
36 * @param priority The priority of this URL. If not URL_PRIO_NONE, this overwrites any priority in the URL.
37 * @return 1 iff the URL was added
38 */
39 _Bool axel_addurlstr(axel_t* axel, const char* urlstr, int priority) {
40 url_t* url = url_parse_heuristic(urlstr);
41
42 if (url == NULL) {
43 return 0;
44 }
45
46 if (priority != URL_PRIO_NONE) {
47 url->priority = priority;
48 }
49
50 urllist_add(axel->urls, url);
51
52 return 1;
53 }
54
55 void axel_init(axel_t* ax, const conf_t* conf) {
56 ax->message_handler = NULL;
57 ax->display_handler = NULL;
58
59 ax->conf = conf;
60 urllist_init(ax->urls);
61
62 ax->conncount = -1;
63 // conn is set once we know conncount
64
65 ax->filename = NULL;
66 ax->outfd = -1;
67
68 ax->statefilename = NULL;
69
70 ax->size = -1;
71 // start_utime is set once we're ready to start downloading
72
73 ax->state = AXEL_STATE_INIT;
74
75 ax->delay_time = 0;
76 }
77
78 void axel_destroy(axel_t* axel) {
79 urllist_destroy(axel->urls);
80
81 free(axel->filename);
82 free(axel->statefilename);
83 }
84
85 /**
86 * Download a file. If you want to know anything more about the download, register handlers.
87 * While the download is running, the current thread will dispatch the callback functions
88 * Afterwards, a program has still to call axel_destroy() and then exit.
89 * @return 0 on success, an error code otherwise
90 */
91 int axel_download(axel_t* axel) {
92 axel_prepare(axel);
93
94 if (axel->state != AXEL_STATE_DOWNLOADING) {
95 axel_set_state(axel, AXEL_STATE_ERROR);
96
97 return axel->state;
98 }
99
100
101 for (int cid = 0;;cid = (cid < axel->conncount) ? cid + 1 : 0) {
102 // Main loop, visit all connections
103
104
105
106
107 }
108
109
110 // TODO set state to finished if not erred
111
112
113 return axel->state;
114 }
115
116 /**
117 * Prepare a download, start all threads
118 */
119 static void axel_prepare(axel_t* axel) {
120 // TODO Determine file name
121 // TODO determine file size
122
123
124 if (axel->filename == NULL) {
125 axel->filename = safe_strdup(axel->conf->default_filename);
126 }
127
128 // TODO Open outfile
129
130
131 // Determine state file name
132 const size_t SUFFIXLEN = strlen(STATEFILE_SUFFIX);
133 size_t fnlen = strlen(axel->filename);
134 axel->statefilename = malloc(fnlen + SUFFIXLEN + 1);
135 memcpy(axel->statefilename + fnlen, STATEFILE_SUFFIX, SUFFIXLEN);
136 axel->statefilename[fnlen + SUFFIXLEN + SUFFIXLEN] = '\0';
137
138 // Start counting time
139 axel->start_utime = getutime();
140 axel_set_state(axel, AXEL_STATE_DOWNLOADING);
141
142 // TODO start threads
143 // TODO set conncount according to conf
144 }
145
146 /**
147 * Prepare axel to be freed (cancel all connections, close outfile etc.)
148 */
149 static void axel_teardown(axel_t* axel) {
150 // TODO Cancel all remaining connections
151
152
153 // TODO Close outfile
154 }
155
156 /**
157 * @param message The message to send. Note that this must be freed by the caller
158 */
159 void axel_message(const axel_t* axel, int verbosity, const char* message) {
160 if (axel->message_handler != NULL) {
161 axel->message_handler(axel, verbosity, message);
162 }
163 axel_update_display();
164 }
165
166 void axel_message_fmt(const axel_t *axel, int verbosity, const char *format, ...) {
167 const MAX_MSG_SIZE = 1024;
168 char* buf = alloca(MAX_MSG_SIZE);
169
170 va_list params;
171 va_start(params, format);
172 vsnprintf(buf, MAX_MSG_SIZE, format, params );
173 va_end(params);
174
175 axel_message(axel, verbosity, buf);
176
177 free(buf);
178 }
179
180 static void axel_update_display(const axel_t* axel) {
181 if (axel->display_handler != NULL) {
182 axel->display_handler(axel);
183 }
184 }
185
186 static void axel_set_state(axel_t* axel, int state) {
187 axel->state = state;
188 axel_update_display();
189 }
190
191 void axel_save_state(axel_t* axel) {
192 // TODO copy stuff from save_state, sanitize
193 }
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209 /* Create a new axel_t structure */
210 axel_t *axel_new( conf_t *conf, int count, void *url )
211 {
212 search_t *res;
213 axel_t *axel;
214 url_t *u;
215 char *s;
216 int i;
217
218 axel = malloc( sizeof( axel_t ) );
219 memset( axel, 0, sizeof( axel_t ) );
220 *axel->conf = *conf;
221 axel->conn = malloc( sizeof( conn_t ) * axel->conf->num_connections );
222 memset( axel->conn, 0, sizeof( conn_t ) * axel->conf->num_connections );
223 if( axel->conf->max_speed > 0 )
224 {
225 if( (float) axel->conf->max_speed / axel->conf->buffer_size < 0.5 )
226 {
227 axel_message( axel, VERBOSITY_NORMAL, _("Buffer resized for this speed."));
228 axel->conf->buffer_size = axel->conf->max_speed;
229 }
230 axel->delay_time = (int) ( (float) 1000000 / axel->conf->max_speed * axel->conf->buffer_size * axel->conf->num_connections );
231 }
232 if( buffer == NULL )
233 buffer = malloc( max( MAX_STRING, axel->conf->buffer_size ) );
234
235 if( count == 0 )
236 {
237 axel->url = malloc( sizeof( url_t ) );
238 axel->url->next = axel->url;
239 strncpy( axel->url->text, (char *) url, MAX_STRING );
240 }
241 else
242 {
243 res = (search_t *) url;
244 u = axel->url = malloc( sizeof( url_t ) );
245 for( i = 0; i < count; i ++ )
246 {
247 strncpy( u->text, res[i].url, MAX_STRING );
248 if( i < count - 1 )
249 {
250 u->next = malloc( sizeof( url_t ) );
251 u = u->next;
252 }
253 else
254 {
255 u->next = axel->url;
256 }
257 }
258 }
259
260 axel->conn[0].conf = axel->conf;
261 if( !conn_set( &axel->conn[0], axel->url->text ) )
262 {
263 axel_message( axel, _("Could not parse URL.\n") );
264 axel->ready = -1;
265 return( axel );
266 }
267
268 axel->conn[0].local_if = axel->conf->interfaces->text;
269 axel->conf->interfaces = axel->conf->interfaces->next;
270
271 strncpy( axel->filename, axel->conn[0].file, MAX_STRING );
272 http_decode( axel->filename );
273 if( *axel->filename == 0 ) /* Index page == no fn */
274 strncpy( axel->filename, axel->conf->default_filename, MAX_STRING );
275 if( ( s = strchr( axel->filename, '?' ) ) != NULL && axel->conf->strip_cgi_parameters )
276 *s = 0; /* Get rid of CGI parameters */
277
278 if( !conn_init( &axel->conn[0] ) )
279 {
280 axel_message( axel, axel->conn[0].message );
281 axel->ready = -1;
282 return( axel );
283 }
284
285 /* This does more than just checking the file size, it all depends
286 on the protocol used. */
287 if( !conn_info( &axel->conn[0] ) )
288 {
289 axel_message( axel, axel->conn[0].message );
290 axel->ready = -1;
291 return( axel );
292 }
293 s = conn_url( axel->conn );
294 strncpy( axel->url->text, s, MAX_STRING );
295 if( ( axel->size = axel->conn[0].size ) != INT_MAX )
296 {
297 if( axel->conf->verbose > 0 )
298 axel_message( axel, _("File size: %lld bytes"), axel->size );
299 }
300
301 /* Wildcards in URL --> Get complete filename */
302 if( strchr( axel->filename, '*' ) || strchr( axel->filename, '?' ) )
303 strncpy( axel->filename, axel->conn[0].file, MAX_STRING );
304
305 return( axel );
306 }
307
308 /* Open a local file to store the downloaded data */
309 int axel_open( axel_t *axel )
310 {
311 int i, fd;
312 long long int j;
313
314 if( axel->conf->verbose > 0 )
315 axel_message( axel, _("Opening output file %s"), axel->filename );
316 snprintf( buffer, MAX_STRING, "%s.st", axel->filename );
317
318 axel->outfd = -1;
319
320 /* Check whether server knows about RESTart and switch back to
321 single connection download if necessary */
322 if( !axel->conn[0].supported )
323 {
324 axel_message( axel, _("Server unsupported, "
325 "starting from scratch with one connection.") );
326 axel->conf->num_connections = 1;
327 axel->conn = realloc( axel->conn, sizeof( conn_t ) );
328 axel_divide( axel );
329 }
330 else if( ( fd = open( buffer, O_RDONLY ) ) != -1 )
331 {
332 read( fd, &axel->conf->num_connections, sizeof( axel->conf->num_connections ) );
333
334 axel->conn = realloc( axel->conn, sizeof( conn_t ) * axel->conf->num_connections );
335 memset( axel->conn + 1, 0, sizeof( conn_t ) * ( axel->conf->num_connections - 1 ) );
336
337 axel_divide( axel );
338
339 read( fd, &axel->bytes_done, sizeof( axel->bytes_done ) );
340 for( i = 0; i < axel->conf->num_connections; i ++ )
341 read( fd, &axel->conn[i].currentbyte, sizeof( axel->conn[i].currentbyte ) );
342
343 axel_message( axel, _("State file found: %lld bytes downloaded, %lld to go."),
344 axel->bytes_done, axel->size - axel->bytes_done );
345
346 close( fd );
347
348 if( ( axel->outfd = open( axel->filename, O_WRONLY, 0666 ) ) == -1 )
349 {
350 axel_message( axel, _("Error opening local file") );
351 return( 0 );
352 }
353 }
354
355 /* If outfd == -1 we have to start from scrath now */
356 if( axel->outfd == -1 )
357 {
358 axel_divide( axel );
359
360 if( ( axel->outfd = open( axel->filename, O_CREAT | O_WRONLY, 0666 ) ) == -1 )
361 {
362 axel_message( axel, _("Error opening local file") );
363 return( 0 );
364 }
365
366 /* And check whether the filesystem can handle seeks to
367 past-EOF areas.. Speeds things up. :) AFAIK this
368 should just not happen: */
369 if( lseek( axel->outfd, axel->size, SEEK_SET ) == -1 && axel->conf->num_connections > 1 )
370 {
371 /* But if the OS/fs does not allow to seek behind
372 EOF, we have to fill the file with zeroes before
373 starting. Slow.. */
374 axel_message( axel, _("Crappy filesystem/OS.. Working around. :-(") );
375 lseek( axel->outfd, 0, SEEK_SET );
376 memset( buffer, 0, axel->conf->buffer_size );
377 j = axel->size;
378 while( j > 0 )
379 {
380 write( axel->outfd, buffer, min( j, axel->conf->buffer_size ) );
381 j -= axel->conf->buffer_size;
382 }
383 }
384 }
385
386 return( 1 );
387 }
388
389 /* Start downloading */
390 void axel_start( axel_t *axel ) {
391 int i;
392
393 /* HTTP might've redirected and FTP handles wildcards, so
394 re-scan the URL for every conn */
395 for( i = 0; i < axel->conf->num_connections; i ++ )
396 {
397 conn_set( &axel->conn[i], axel->url->text );
398 axel->url = axel->url->next;
399 axel->conn[i].local_if = axel->conf->interfaces->text;
400 axel->conf->interfaces = axel->conf->interfaces->next;
401 axel->conn[i].conf = axel->conf;
402 if( i ) axel->conn[i].supported = 1;
403 }
404
405 if( axel->conf->verbose > 0 )
406 axel_message( axel, _("Starting download") );
407
408 for( i = 0; i < axel->conf->num_connections; i ++ )
409 if( axel->conn[i].currentbyte <= axel->conn[i].lastbyte )
410 {
411 if( axel->conf->verbose >= 2 )
412 axel_message( axel, _("Connection %i downloading from %s:%i using interface %s"),
413 i, axel->conn[i].host, axel->conn[i].port, axel->conn[i].local_if );
414 if( pthread_create( axel->conn[i].setup_thread, NULL, setup_thread, &axel->conn[i] ) != 0 )
415 {
416 axel_message( axel, _("pthread error!!!") );
417 axel->ready = -1;
418 }
419 else
420 {
421 axel->conn[i].last_transfer = gettime();
422 axel->conn[i].state = 1;
423 }
424 }
425 }
426
427 /* Main 'loop' */
428 void axel_do( axel_t *axel )
429 {
430 fd_set fds[1];
431 int hifd, i, j;
432 long long int size;
433 struct timeval timeval[1];
434
435 /* Create statefile if necessary */
436 if( gettime() > axel->next_state )
437 {
438 save_state( axel );
439 axel->next_state = gettime() + axel->conf->save_state_interval;
440 }
441
442 /* Wait for data on (one of) the connections */
443 FD_ZERO( fds );
444 hifd = 0;
445 for( i = 0; i < axel->conncount; i ++ )
446 {
447 if( axel->conn[i].enabled )
448 FD_SET( axel->conn[i].fd, fds );
449 hifd = max( hifd, axel->conn[i].fd );
450 }
451 if( hifd == 0 )
452 {
453 /* No connections yet. Wait... */
454 usleep( 100000 );
455 goto conn_check;
456 }
457 else
458 {
459 timeval->tv_sec = 0;
460 timeval->tv_usec = 100000;
461 /* A select() error probably means it was interrupted
462 by a signal, or that something else's very wrong... */
463 if( select( hifd + 1, fds, NULL, NULL, timeval ) == -1 )
464 {
465 axel->ready = -1;
466 return;
467 }
468 }
469
470 /* Handle connections which need attention */
471 for( i = 0; i < axel->conf->num_connections; i ++ )
472 if( axel->conn[i].enabled ) {
473 if( FD_ISSET( axel->conn[i].fd, fds ) )
474 {
475 axel->conn[i].last_transfer = gettime();
476 size = read( axel->conn[i].fd, buffer, axel->conf->buffer_size );
477 if( size == -1 )
478 {
479 if( axel->conf->verbose )
480 {
481 axel_message( axel, _("Error on connection %i! "
482 "Connection closed"), i );
483 }
484 axel->conn[i].enabled = 0;
485 conn_disconnect( &axel->conn[i] );
486 continue;
487 }
488 else if( size == 0 )
489 {
490 if( axel->conf->verbose )
491 {
492 /* Only abnormal behaviour if: */
493 if( axel->conn[i].currentbyte < axel->conn[i].lastbyte && axel->size != INT_MAX )
494 {
495 axel_message( axel, _("Connection %i unexpectedly closed"), i );
496 }
497 else
498 {
499 axel_message( axel, _("Connection %i finished"), i );
500 }
501 }
502 if( !axel->conn[0].supported )
503 {
504 axel->ready = 1;
505 }
506 axel->conn[i].enabled = 0;
507 conn_disconnect( &axel->conn[i] );
508 continue;
509 }
510 /* j == Bytes to go */
511 j = axel->conn[i].lastbyte - axel->conn[i].currentbyte + 1;
512 if( j < size )
513 {
514 if( axel->conf->verbose )
515 {
516 axel_message( axel, _("Connection %i finished"), i );
517 }
518 axel->conn[i].enabled = 0;
519 conn_disconnect( &axel->conn[i] );
520 size = j;
521 /* Don't terminate, still stuff to write! */
522 }
523 /* This should always succeed.. */
524 lseek( axel->outfd, axel->conn[i].currentbyte, SEEK_SET );
525 if( write( axel->outfd, buffer, size ) != size )
526 {
527
528 axel_message( axel, _("Write error!") );
529 axel->ready = -1;
530 return;
531 }
532 axel->conn[i].currentbyte += size;
533 axel->bytes_done += size;
534 }
535 else
536 {
537 if( gettime() > axel->conn[i].last_transfer + axel->conf->connection_timeout )
538 {
539 if( axel->conf->verbose )
540 axel_message( axel, _("Connection %i timed out"), i );
541 conn_disconnect( &axel->conn[i] );
542 axel->conn[i].enabled = 0;
543 }
544 } }
545
546 if( axel->ready )
547 return;
548
549 conn_check:
550 /* Look for aborted connections and attempt to restart them. */
551 for( i = 0; i < axel->conf->num_connections; i ++ )
552 {
553 if( !axel->conn[i].enabled && axel->conn[i].currentbyte < axel->conn[i].lastbyte )
554 {
555 if( axel->conn[i].state == 0 )
556 {
557 conn_set( &axel->conn[i], axel->url->text );
558 axel->url = axel->url->next;
559 /* axel->conn[i].local_if = axel->conf->interfaces->text;
560 axel->conf->interfaces = axel->conf->interfaces->next; */
561 if( axel->conf->verbose >= 2 )
562 axel_message( axel, _("Connection %i downloading from %s:%i using interface %s"),
563 i, axel->conn[i].host, axel->conn[i].port, axel->conn[i].local_if );
564 if( pthread_create( axel->conn[i].setup_thread, NULL, setup_thread, &axel->conn[i] ) == 0 )
565 {
566 axel->conn[i].state = 1;
567 axel->conn[i].last_transfer = gettime();
568 }
569 else
570 {
571 axel_message( axel, _("pthread error!!!") );
572 axel->ready = -1;
573 }
574 }
575 else
576 {
577 if( gettime() > axel->conn[i].last_transfer + axel->conf->reconnect_delay )
578 {
579 pthread_cancel( *axel->conn[i].setup_thread );
580 axel->conn[i].state = 0;
581 }
582 }
583 }
584 }
585
586 /* Calculate current average speed and finish_time */
587 axel->bytes_per_second = (int) ( (double) ( axel->bytes_done - axel->start_byte ) / ( gettime() - axel->start_time ) );
588 axel->finish_time = (int) ( axel->start_time + (double) ( axel->size - axel->start_byte ) / axel->bytes_per_second );
589
590 /* Check speed. If too high, delay for some time to slow things
591 down a bit. I think a 5% deviation should be acceptable. */
592 if( axel->conf->max_speed > 0 )
593 {
594 if( (float) axel->bytes_per_second / axel->conf->max_speed > 1.05 )
595 axel->delay_time += 10000;
596 else if( ( (float) axel->bytes_per_second / axel->conf->max_speed < 0.95 ) && ( axel->delay_time >= 10000 ) )
597 axel->delay_time -= 10000;
598 else if( ( (float) axel->bytes_per_second / axel->conf->max_speed < 0.95 ) )
599 axel->delay_time = 0;
600 usleep( axel->delay_time );
601 }
602
603 /* Ready? */
604 if( axel->bytes_done == axel->size )
605 axel->ready = 1;
606 }
607
608 /* Close an axel connection */
609 void axel_close( axel_t *axel )
610 {
611 int i;
612 message_t *m;
613
614 /* Terminate any thread still running */
615 for( i = 0; i < axel->conf->num_connections; i ++ )
616 /* don't try to kill non existing thread */
617 if ( *axel->conn[i].setup_thread != 0 )
618 pthread_cancel( *axel->conn[i].setup_thread );
619
620 /* Delete state file if necessary */
621 if( axel->ready == 1 )
622 {
623 snprintf( buffer, MAX_STRING, "%s.st", axel->filename );
624 unlink( buffer );
625 }
626 /* Else: Create it.. */
627 else if( axel->bytes_done > 0 )
628 {
629 save_state( axel );
630 }
631
632 /* Delete any message not processed yet */
633 while( axel->message )
634 {
635 m = axel->message;
636 axel->message = axel->message->next;
637 free( m );
638 }
639
640 /* Close all connections and local file */
641 close( axel->outfd );
642 for( i = 0; i < axel->conf->num_connections; i ++ )
643 conn_disconnect( &axel->conn[i] );
644
645 free( axel->conn );
646 free( axel );
647 }
648
649 /* Save the state of the current download */
650 void save_state( axel_t *axel )
651 {
652 int fd, i;
653 char fn[MAX_STRING+4];
654
655 snprintf( fn, MAX_STRING, "%s" STATEFILE_SUFFIX, axel->filename );
656 if( ( fd = open( fn, O_CREAT | O_TRUNC | O_WRONLY, 0600 ) ) == -1 )
657 {
658 return; /* Not 100% fatal.. */
659 }
660 write( fd, &axel->conf->num_connections, sizeof( axel->conf->num_connections ) );
661 write( fd, &axel->bytes_done, sizeof( axel->bytes_done ) );
662 for( i = 0; i < axel->conf->num_connections; i ++ )
663 {
664 write( fd, &axel->conn[i].currentbyte, sizeof( axel->conn[i].currentbyte ) );
665 }
666 close( fd );
667 }
668
669 /* Divide the file and set the locations for each connection */
670 static void axel_divide( axel_t *axel )
671 {
672 int i;
673
674 axel->conn[0].currentbyte = 0;
675 axel->conn[0].lastbyte = axel->size / axel->conf->num_connections - 1;
676 for( i = 1; i < axel->conf->num_connections; i ++ )
677 {
678 #ifdef DEBUG
679 printf( "Downloading %lld-%lld using conn. %i\n", axel->conn[i-1].currentbyte, axel->conn[i-1].lastbyte, i - 1 );
680 #endif
681 axel->conn[i].currentbyte = axel->conn[i-1].lastbyte + 1;
682 axel->conn[i].lastbyte = axel->conn[i].currentbyte + axel->size / axel->conf->num_connections;
683 }
684 axel->conn[axel->conf->num_connections-1].lastbyte = axel->size - 1;
685 #ifdef DEBUG
686 printf( "Downloading %lld-%lld using conn. %i\n", axel->conn[i-1].currentbyte, axel->conn[i-1].lastbyte, i - 1 );
687 #endif
688 }

  ViewVC Help
Powered by ViewVC 1.1.5