/[axel]/branches/2.x/axel.c
ViewVC logotype

Contents of /branches/2.x/axel.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 14 - (show annotations) (download)
Wed Jan 16 07:56:47 2008 UTC (5 years, 4 months ago) by appaji-guest
Original Path: trunk/axel.c
File MIME type: text/plain
File size: 16532 byte(s)
Use strncpy instead of strcpy for length sensitive copies
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 /* Axel */
29 static void save_state( axel_t *axel );
30 static void *setup_thread( void * );
31 static void axel_message( axel_t *axel, char *format, ... );
32 static void axel_divide( axel_t *axel );
33
34 static char *buffer = NULL;
35
36 /* Create a new axel_t structure */
37 axel_t *axel_new( conf_t *conf, int count, void *url )
38 {
39 search_t *res;
40 axel_t *axel;
41 url_t *u;
42 char *s;
43 int i;
44
45 axel = malloc( sizeof( axel_t ) );
46 memset( axel, 0, sizeof( axel_t ) );
47 *axel->conf = *conf;
48 axel->conn = malloc( sizeof( conn_t ) * axel->conf->num_connections );
49 memset( axel->conn, 0, sizeof( conn_t ) * axel->conf->num_connections );
50 if( axel->conf->max_speed > 0 )
51 {
52 if( (float) axel->conf->max_speed / axel->conf->buffer_size < 0.5 )
53 {
54 if( axel->conf->verbose >= 2 )
55 axel_message( axel, _("Buffer resized for this speed.") );
56 axel->conf->buffer_size = axel->conf->max_speed;
57 }
58 axel->delay_time = (int) ( (float) 1000000 / axel->conf->max_speed * axel->conf->buffer_size * axel->conf->num_connections );
59 }
60 if( buffer == NULL )
61 buffer = malloc( max( MAX_STRING, axel->conf->buffer_size ) );
62
63 if( count == 0 )
64 {
65 axel->url = malloc( sizeof( url_t ) );
66 axel->url->next = axel->url;
67 strncpy( axel->url->text, (char *) url, MAX_STRING );
68 }
69 else
70 {
71 res = (search_t *) url;
72 u = axel->url = malloc( sizeof( url_t ) );
73 for( i = 0; i < count; i ++ )
74 {
75 strncpy( u->text, res[i].url, MAX_STRING );
76 if( i < count - 1 )
77 {
78 u->next = malloc( sizeof( url_t ) );
79 u = u->next;
80 }
81 else
82 {
83 u->next = axel->url;
84 }
85 }
86 }
87
88 axel->conn[0].conf = axel->conf;
89 if( !conn_set( &axel->conn[0], axel->url->text ) )
90 {
91 axel_message( axel, _("Could not parse URL.\n") );
92 axel->ready = -1;
93 return( axel );
94 }
95
96 axel->conn[0].local_if = axel->conf->interfaces->text;
97 axel->conf->interfaces = axel->conf->interfaces->next;
98
99 strncpy( axel->filename, axel->conn[0].file, MAX_STRING );
100 http_decode( axel->filename );
101 if( *axel->filename == 0 ) /* Index page == no fn */
102 strncpy( axel->filename, axel->conf->default_filename, MAX_STRING );
103 if( ( s = strchr( axel->filename, '?' ) ) != NULL && axel->conf->strip_cgi_parameters )
104 *s = 0; /* Get rid of CGI parameters */
105
106 if( !conn_init( &axel->conn[0] ) )
107 {
108 axel_message( axel, axel->conn[0].message );
109 axel->ready = -1;
110 return( axel );
111 }
112
113 /* This does more than just checking the file size, it all depends
114 on the protocol used. */
115 if( !conn_info( &axel->conn[0] ) )
116 {
117 axel_message( axel, axel->conn[0].message );
118 axel->ready = -1;
119 return( axel );
120 }
121 s = conn_url( axel->conn );
122 strncpy( axel->url->text, s, MAX_STRING );
123 if( ( axel->size = axel->conn[0].size ) != INT_MAX )
124 {
125 if( axel->conf->verbose > 0 )
126 axel_message( axel, _("File size: %i bytes"), axel->size );
127 }
128
129 /* Wildcards in URL --> Get complete filename */
130 if( strchr( axel->filename, '*' ) || strchr( axel->filename, '?' ) )
131 strncpy( axel->filename, axel->conn[0].file, MAX_STRING );
132
133 return( axel );
134 }
135
136 /* Open a local file to store the downloaded data */
137 int axel_open( axel_t *axel )
138 {
139 int i, fd;
140
141 if( axel->conf->verbose > 0 )
142 axel_message( axel, _("Opening output file %s"), axel->filename );
143 snprintf( buffer, MAX_STRING, "%s.st", axel->filename );
144
145 axel->outfd = -1;
146
147 /* Check whether server knows about RESTart and switch back to
148 single connection download if necessary */
149 if( !axel->conn[0].supported )
150 {
151 axel_message( axel, _("Server unsupported, "
152 "starting from scratch with one connection.") );
153 axel->conf->num_connections = 1;
154 axel->conn = realloc( axel->conn, sizeof( conn_t ) );
155 axel_divide( axel );
156 }
157 else if( ( fd = open( buffer, O_RDONLY ) ) != -1 )
158 {
159 read( fd, &axel->conf->num_connections, sizeof( axel->conf->num_connections ) );
160
161 axel->conn = realloc( axel->conn, sizeof( conn_t ) * axel->conf->num_connections );
162 memset( axel->conn + 1, 0, sizeof( conn_t ) * ( axel->conf->num_connections - 1 ) );
163
164 axel_divide( axel );
165
166 read( fd, &axel->bytes_done, sizeof( axel->bytes_done ) );
167 for( i = 0; i < axel->conf->num_connections; i ++ )
168 read( fd, &axel->conn[i].currentbyte, sizeof( axel->conn[i].currentbyte ) );
169
170 axel_message( axel, _("State file found: %i bytes downloaded, %i to go."),
171 axel->bytes_done, axel->size - axel->bytes_done );
172
173 close( fd );
174
175 if( ( axel->outfd = open( axel->filename, O_WRONLY, 0666 ) ) == -1 )
176 {
177 axel_message( axel, _("Error opening local file") );
178 return( 0 );
179 }
180 }
181
182 /* If outfd == -1 we have to start from scrath now */
183 if( axel->outfd == -1 )
184 {
185 axel_divide( axel );
186
187 if( ( axel->outfd = open( axel->filename, O_CREAT | O_WRONLY, 0666 ) ) == -1 )
188 {
189 axel_message( axel, _("Error opening local file") );
190 return( 0 );
191 }
192
193 /* And check whether the filesystem can handle seeks to
194 past-EOF areas.. Speeds things up. :) AFAIK this
195 should just not happen: */
196 if( lseek( axel->outfd, axel->size, SEEK_SET ) == -1 && axel->conf->num_connections > 1 )
197 {
198 /* But if the OS/fs does not allow to seek behind
199 EOF, we have to fill the file with zeroes before
200 starting. Slow.. */
201 axel_message( axel, _("Crappy filesystem/OS.. Working around. :-(") );
202 lseek( axel->outfd, 0, SEEK_SET );
203 memset( buffer, 0, axel->conf->buffer_size );
204 i = axel->size;
205 while( i > 0 )
206 {
207 write( axel->outfd, buffer, min( i, axel->conf->buffer_size ) );
208 i -= axel->conf->buffer_size;
209 }
210 }
211 }
212
213 return( 1 );
214 }
215
216 /* Start downloading */
217 void axel_start( axel_t *axel )
218 {
219 int i;
220
221 /* HTTP might've redirected and FTP handles wildcards, so
222 re-scan the URL for every conn */
223 for( i = 0; i < axel->conf->num_connections; i ++ )
224 {
225 conn_set( &axel->conn[i], axel->url->text );
226 axel->url = axel->url->next;
227 axel->conn[i].local_if = axel->conf->interfaces->text;
228 axel->conf->interfaces = axel->conf->interfaces->next;
229 axel->conn[i].conf = axel->conf;
230 if( i ) axel->conn[i].supported = 1;
231 }
232
233 if( axel->conf->verbose > 0 )
234 axel_message( axel, _("Starting download") );
235
236 for( i = 0; i < axel->conf->num_connections; i ++ )
237 if( axel->conn[i].currentbyte <= axel->conn[i].lastbyte )
238 {
239 if( axel->conf->verbose >= 2 )
240 axel_message( axel, _("Connection %i downloading from %s:%i using interface %s"),
241 i, axel->conn[i].host, axel->conn[i].port, axel->conn[i].local_if );
242 if( pthread_create( axel->conn[i].setup_thread, NULL, setup_thread, &axel->conn[i] ) != 0 )
243 {
244 axel_message( axel, _("pthread error!!!") );
245 axel->ready = -1;
246 }
247 else
248 {
249 axel->conn[i].last_transfer = gettime();
250 axel->conn[i].state = 1;
251 }
252 }
253
254 /* The real downloading will start now, so let's start counting */
255 axel->start_time = gettime();
256 axel->ready = 0;
257 }
258
259 /* Main 'loop' */
260 void axel_do( axel_t *axel )
261 {
262 fd_set fds[1];
263 int hifd, i, j, size;
264 struct timeval timeval[1];
265
266 /* Create statefile if necessary */
267 if( gettime() > axel->next_state )
268 {
269 save_state( axel );
270 axel->next_state = gettime() + axel->conf->save_state_interval;
271 }
272
273 /* Wait for data on (one of) the connections */
274 FD_ZERO( fds );
275 hifd = 0;
276 for( i = 0; i < axel->conf->num_connections; i ++ )
277 {
278 if( axel->conn[i].enabled )
279 FD_SET( axel->conn[i].fd, fds );
280 hifd = max( hifd, axel->conn[i].fd );
281 }
282 if( hifd == 0 )
283 {
284 /* No connections yet. Wait... */
285 usleep( 100000 );
286 goto conn_check;
287 }
288 else
289 {
290 timeval->tv_sec = 0;
291 timeval->tv_usec = 100000;
292 /* A select() error probably means it was interrupted
293 by a signal, or that something else's very wrong... */
294 if( select( hifd + 1, fds, NULL, NULL, timeval ) == -1 )
295 {
296 axel->ready = -1;
297 return;
298 }
299 }
300
301 /* Handle connections which need attention */
302 for( i = 0; i < axel->conf->num_connections; i ++ )
303 if( axel->conn[i].enabled ) {
304 if( FD_ISSET( axel->conn[i].fd, fds ) )
305 {
306 axel->conn[i].last_transfer = gettime();
307 size = read( axel->conn[i].fd, buffer, axel->conf->buffer_size );
308 if( size == -1 )
309 {
310 if( axel->conf->verbose )
311 {
312 axel_message( axel, _("Error on connection %i! "
313 "Connection closed"), i );
314 }
315 axel->conn[i].enabled = 0;
316 conn_disconnect( &axel->conn[i] );
317 continue;
318 }
319 else if( size == 0 )
320 {
321 if( axel->conf->verbose )
322 {
323 /* Only abnormal behaviour if: */
324 if( axel->conn[i].currentbyte < axel->conn[i].lastbyte && axel->size != INT_MAX )
325 {
326 axel_message( axel, _("Connection %i unexpectedly closed"), i );
327 }
328 else
329 {
330 axel_message( axel, _("Connection %i finished"), i );
331 }
332 }
333 if( !axel->conn[0].supported )
334 {
335 axel->ready = 1;
336 }
337 axel->conn[i].enabled = 0;
338 conn_disconnect( &axel->conn[i] );
339 continue;
340 }
341 /* j == Bytes to go */
342 j = axel->conn[i].lastbyte - axel->conn[i].currentbyte + 1;
343 if( j < size )
344 {
345 if( axel->conf->verbose )
346 {
347 axel_message( axel, _("Connection %i finished"), i );
348 }
349 axel->conn[i].enabled = 0;
350 conn_disconnect( &axel->conn[i] );
351 size = j;
352 /* Don't terminate, still stuff to write! */
353 }
354 /* This should always succeed.. */
355 lseek( axel->outfd, axel->conn[i].currentbyte, SEEK_SET );
356 if( write( axel->outfd, buffer, size ) != size )
357 {
358
359 axel_message( axel, _("Write error!") );
360 axel->ready = -1;
361 return;
362 }
363 axel->conn[i].currentbyte += size;
364 axel->bytes_done += size;
365 }
366 else
367 {
368 if( gettime() > axel->conn[i].last_transfer + axel->conf->connection_timeout )
369 {
370 if( axel->conf->verbose )
371 axel_message( axel, _("Connection %i timed out"), i );
372 conn_disconnect( &axel->conn[i] );
373 axel->conn[i].enabled = 0;
374 }
375 } }
376
377 if( axel->ready )
378 return;
379
380 conn_check:
381 /* Look for aborted connections and attempt to restart them. */
382 for( i = 0; i < axel->conf->num_connections; i ++ )
383 {
384 if( !axel->conn[i].enabled && axel->conn[i].currentbyte < axel->conn[i].lastbyte )
385 {
386 if( axel->conn[i].state == 0 )
387 {
388 conn_set( &axel->conn[i], axel->url->text );
389 axel->url = axel->url->next;
390 /* axel->conn[i].local_if = axel->conf->interfaces->text;
391 axel->conf->interfaces = axel->conf->interfaces->next; */
392 if( axel->conf->verbose >= 2 )
393 axel_message( axel, _("Connection %i downloading from %s:%i using interface %s"),
394 i, axel->conn[i].host, axel->conn[i].port, axel->conn[i].local_if );
395 if( pthread_create( axel->conn[i].setup_thread, NULL, setup_thread, &axel->conn[i] ) == 0 )
396 {
397 axel->conn[i].state = 1;
398 axel->conn[i].last_transfer = gettime();
399 }
400 else
401 {
402 axel_message( axel, _("pthread error!!!") );
403 axel->ready = -1;
404 }
405 }
406 else
407 {
408 if( gettime() > axel->conn[i].last_transfer + axel->conf->reconnect_delay )
409 {
410 pthread_cancel( *axel->conn[i].setup_thread );
411 axel->conn[i].state = 0;
412 }
413 }
414 }
415 }
416
417 /* Calculate current average speed and finish_time */
418 axel->bytes_per_second = (int) ( (double) ( axel->bytes_done - axel->start_byte ) / ( gettime() - axel->start_time ) );
419 axel->finish_time = (int) ( axel->start_time + (double) ( axel->size - axel->start_byte ) / axel->bytes_per_second );
420
421 /* Check speed. If too high, delay for some time to slow things
422 down a bit. I think a 5% deviation should be acceptable. */
423 if( axel->conf->max_speed > 0 )
424 {
425 if( (float) axel->bytes_per_second / axel->conf->max_speed > 1.05 )
426 axel->delay_time += 10000;
427 else if( ( (float) axel->bytes_per_second / axel->conf->max_speed < 0.95 ) && ( axel->delay_time >= 10000 ) )
428 axel->delay_time -= 10000;
429 else if( ( (float) axel->bytes_per_second / axel->conf->max_speed < 0.95 ) )
430 axel->delay_time = 0;
431 usleep( axel->delay_time );
432 }
433
434 /* Ready? */
435 if( axel->bytes_done == axel->size )
436 axel->ready = 1;
437 }
438
439 /* Close an axel connection */
440 void axel_close( axel_t *axel )
441 {
442 int i;
443 message_t *m;
444
445 /* Terminate any thread still running */
446 for( i = 0; i < axel->conf->num_connections; i ++ )
447 /* don't try to kill non existing thread */
448 if ( *axel->conn[i].setup_thread != 0 )
449 pthread_cancel( *axel->conn[i].setup_thread );
450
451 /* Delete state file if necessary */
452 if( axel->ready == 1 )
453 {
454 snprintf( buffer, MAX_STRING, "%s.st", axel->filename );
455 unlink( buffer );
456 }
457 /* Else: Create it.. */
458 else if( axel->bytes_done > 0 )
459 {
460 save_state( axel );
461 }
462
463 /* Delete any message not processed yet */
464 while( axel->message )
465 {
466 m = axel->message;
467 axel->message = axel->message->next;
468 free( m );
469 }
470
471 /* Close all connections and local file */
472 close( axel->outfd );
473 for( i = 0; i < axel->conf->num_connections; i ++ )
474 conn_disconnect( &axel->conn[i] );
475
476 free( axel->conn );
477 free( axel );
478 }
479
480 /* time() with more precision */
481 double gettime()
482 {
483 struct timeval time[1];
484
485 gettimeofday( time, 0 );
486 return( (double) time->tv_sec + (double) time->tv_usec / 1000000 );
487 }
488
489 /* Save the state of the current download */
490 void save_state( axel_t *axel )
491 {
492 int fd, i;
493 char fn[MAX_STRING+4];
494
495 /* No use for such a file if the server doesn't support
496 resuming anyway.. */
497 if( !axel->conn[0].supported )
498 return;
499
500 snprintf( fn, MAX_STRING, "%s.st", axel->filename );
501 if( ( fd = open( fn, O_CREAT | O_TRUNC | O_WRONLY, 0666 ) ) == -1 )
502 {
503 return; /* Not 100% fatal.. */
504 }
505 write( fd, &axel->conf->num_connections, sizeof( axel->conf->num_connections ) );
506 write( fd, &axel->bytes_done, sizeof( axel->bytes_done ) );
507 for( i = 0; i < axel->conf->num_connections; i ++ )
508 {
509 write( fd, &axel->conn[i].currentbyte, sizeof( axel->conn[i].currentbyte ) );
510 }
511 close( fd );
512 }
513
514 /* Thread used to set up a connection */
515 void *setup_thread( void *c )
516 {
517 conn_t *conn = c;
518 int oldstate;
519
520 /* Allow this thread to be killed at any time. */
521 pthread_setcancelstate( PTHREAD_CANCEL_ENABLE, &oldstate );
522 pthread_setcanceltype( PTHREAD_CANCEL_ASYNCHRONOUS, &oldstate );
523
524 if( conn_setup( conn ) )
525 {
526 conn->last_transfer = gettime();
527 if( conn_exec( conn ) )
528 {
529 conn->last_transfer = gettime();
530 conn->enabled = 1;
531 conn->state = 0;
532 return( NULL );
533 }
534 }
535
536 conn_disconnect( conn );
537 conn->state = 0;
538 return( NULL );
539 }
540
541 /* Add a message to the axel->message structure */
542 static void axel_message( axel_t *axel, char *format, ... )
543 {
544 message_t *m = malloc( sizeof( message_t ) ), *n = axel->message;
545 va_list params;
546
547 memset( m, 0, sizeof( message_t ) );
548 va_start( params, format );
549 vsnprintf( m->text, MAX_STRING, format, params );
550 va_end( params );
551
552 if( axel->message == NULL )
553 {
554 axel->message = m;
555 }
556 else
557 {
558 while( n->next != NULL )
559 n = n->next;
560 n->next = m;
561 }
562 }
563
564 /* Divide the file and set the locations for each connection */
565 static void axel_divide( axel_t *axel )
566 {
567 int i;
568
569 axel->conn[0].currentbyte = 0;
570 axel->conn[0].lastbyte = axel->size / axel->conf->num_connections - 1;
571 for( i = 1; i < axel->conf->num_connections; i ++ )
572 {
573 #ifdef DEBUG
574 printf( "Downloading %i-%i using conn. %i\n", axel->conn[i-1].currentbyte, axel->conn[i-1].lastbyte, i - 1 );
575 #endif
576 axel->conn[i].currentbyte = axel->conn[i-1].lastbyte + 1;
577 axel->conn[i].lastbyte = axel->conn[i].currentbyte + axel->size / axel->conf->num_connections;
578 }
579 axel->conn[axel->conf->num_connections-1].lastbyte = axel->size - 1;
580 #ifdef DEBUG
581 printf( "Downloading %i-%i using conn. %i\n", axel->conn[i-1].currentbyte, axel->conn[i-1].lastbyte, i - 1 );
582 #endif
583 }

  ViewVC Help
Powered by ViewVC 1.1.5