iPXE
peermux.c
Go to the documentation of this file.
00001 /*
00002  * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>.
00003  *
00004  * This program is free software; you can redistribute it and/or
00005  * modify it under the terms of the GNU General Public License as
00006  * published by the Free Software Foundation; either version 2 of the
00007  * License, or (at your option) any later version.
00008  *
00009  * This program is distributed in the hope that it will be useful, but
00010  * WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012  * General Public License for more details.
00013  *
00014  * You should have received a copy of the GNU General Public License
00015  * along with this program; if not, write to the Free Software
00016  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
00017  * 02110-1301, USA.
00018  *
00019  * You can also choose to distribute this program under the terms of
00020  * the Unmodified Binary Distribution Licence (as given in the file
00021  * COPYING.UBDL), provided that you have satisfied its requirements.
00022  */
00023 
00024 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
00025 
00026 #include <stdlib.h>
00027 #include <stdio.h>
00028 #include <errno.h>
00029 #include <ipxe/uri.h>
00030 #include <ipxe/xferbuf.h>
00031 #include <ipxe/job.h>
00032 #include <ipxe/peerblk.h>
00033 #include <ipxe/peermux.h>
00034 
00035 /** @file
00036  *
00037  * Peer Content Caching and Retrieval (PeerDist) protocol multiplexer
00038  *
00039  */
00040 
00041 /**
00042  * Free PeerDist download multiplexer
00043  *
00044  * @v refcnt            Reference count
00045  */
00046 static void peermux_free ( struct refcnt *refcnt ) {
00047         struct peerdist_multiplexer *peermux =
00048                 container_of ( refcnt, struct peerdist_multiplexer, refcnt );
00049 
00050         uri_put ( peermux->uri );
00051         xferbuf_free ( &peermux->buffer );
00052         free ( peermux );
00053 }
00054 
00055 /**
00056  * Close PeerDist download multiplexer
00057  *
00058  * @v peermux           PeerDist download multiplexer
00059  * @v rc                Reason for close
00060  */
00061 static void peermux_close ( struct peerdist_multiplexer *peermux, int rc ) {
00062         unsigned int i;
00063 
00064         /* Stop block download initiation process */
00065         process_del ( &peermux->process );
00066 
00067         /* Shut down all block downloads */
00068         for ( i = 0 ; i < PEERMUX_MAX_BLOCKS ; i++ )
00069                 intf_shutdown ( &peermux->block[i].xfer, rc );
00070 
00071         /* Shut down all other interfaces (which may be connected to
00072          * the same object).
00073          */
00074         intf_nullify ( &peermux->info ); /* avoid potential loops */
00075         intf_shutdown ( &peermux->xfer, rc );
00076         intf_shutdown ( &peermux->info, rc );
00077 }
00078 
00079 /**
00080  * Report progress of PeerDist download
00081  *
00082  * @v peermux           PeerDist download multiplexer
00083  * @v progress          Progress report to fill in
00084  * @ret ongoing_rc      Ongoing job status code (if known)
00085  */
00086 static int peermux_progress ( struct peerdist_multiplexer *peermux,
00087                               struct job_progress *progress ) {
00088         struct peerdist_statistics *stats = &peermux->stats;
00089         unsigned int percentage;
00090 
00091         /* Construct PeerDist status message */
00092         if ( stats->total ) {
00093                 percentage = ( ( 100 * stats->local ) / stats->total );
00094                 snprintf ( progress->message, sizeof ( progress->message ),
00095                            "%3d%% from %d peers", percentage, stats->peers );
00096         }
00097 
00098         return 0;
00099 }
00100 
00101 /**
00102  * Receive content information
00103  *
00104  * @v peermux           PeerDist download multiplexer
00105  * @v iobuf             I/O buffer
00106  * @v meta              Data transfer metadata
00107  * @ret rc              Return status code
00108  */
00109 static int peermux_info_deliver ( struct peerdist_multiplexer *peermux,
00110                                   struct io_buffer *iobuf,
00111                                   struct xfer_metadata *meta ) {
00112         int rc;
00113 
00114         /* Add data to buffer */
00115         if ( ( rc = xferbuf_deliver ( &peermux->buffer, iobuf, meta ) ) != 0 )
00116                 goto err;
00117 
00118         return 0;
00119 
00120  err:
00121         peermux_close ( peermux, rc );
00122         return rc;
00123 }
00124 
00125 /**
00126  * Close content information interface
00127  *
00128  * @v peermux           PeerDist download multiplexer
00129  * @v rc                Reason for close
00130  */
00131 static void peermux_info_close ( struct peerdist_multiplexer *peermux, int rc ){
00132         struct peerdist_info *info = &peermux->cache.info;
00133         size_t len;
00134 
00135         /* Terminate download on error */
00136         if ( rc != 0 )
00137                 goto err;
00138 
00139         /* Successfully closing the content information interface
00140          * indicates that the content information has been fully
00141          * received, and initiates the actual PeerDist download.
00142          */
00143 
00144         /* Shut down content information interface */
00145         intf_shutdown ( &peermux->info, rc );
00146 
00147         /* Parse content information */
00148         if ( ( rc = peerdist_info ( info->raw.data, peermux->buffer.len,
00149                                     info ) ) != 0 ) {
00150                 DBGC ( peermux, "PEERMUX %p could not parse content info: %s\n",
00151                        peermux, strerror ( rc ) );
00152                 goto err;
00153         }
00154 
00155         /* Notify recipient of total download size */
00156         len = ( info->trim.end - info->trim.start );
00157         if ( ( rc = xfer_seek ( &peermux->xfer, len ) ) != 0 ) {
00158                 DBGC ( peermux, "PEERMUX %p could not presize buffer: %s\n",
00159                        peermux, strerror ( rc ) );
00160                 goto err;
00161         }
00162         xfer_seek ( &peermux->xfer, 0 );
00163 
00164         /* Start block download process */
00165         process_add ( &peermux->process );
00166 
00167         return;
00168 
00169  err:
00170         peermux_close ( peermux, rc );
00171 }
00172 
00173 /**
00174  * Initiate multiplexed block download
00175  *
00176  * @v peermux           PeerDist download multiplexer
00177  */
00178 static void peermux_step ( struct peerdist_multiplexer *peermux ) {
00179         struct peerdist_info *info = &peermux->cache.info;
00180         struct peerdist_info_segment *segment = &peermux->cache.segment;
00181         struct peerdist_info_block *block = &peermux->cache.block;
00182         struct peerdist_multiplexed_block *peermblk;
00183         unsigned int next_segment;
00184         unsigned int next_block;
00185         int rc;
00186 
00187         /* Stop initiation process if all block downloads are busy */
00188         peermblk = list_first_entry ( &peermux->idle,
00189                                       struct peerdist_multiplexed_block, list );
00190         if ( ! peermblk ) {
00191                 process_del ( &peermux->process );
00192                 return;
00193         }
00194 
00195         /* Increment block index */
00196         next_block = ( block->index + 1 );
00197 
00198         /* Move to first/next segment, if applicable */
00199         if ( next_block >= segment->blocks ) {
00200 
00201                 /* Reset block index */
00202                 next_block = 0;
00203 
00204                 /* Calculate segment index */
00205                 next_segment = ( segment->info ? ( segment->index + 1 ) : 0 );
00206 
00207                 /* If we have finished all segments and have no
00208                  * remaining block downloads, then we are finished.
00209                  */
00210                 if ( next_segment >= info->segments ) {
00211                         process_del ( &peermux->process );
00212                         if ( list_empty ( &peermux->busy ) )
00213                                 peermux_close ( peermux, 0 );
00214                         return;
00215                 }
00216 
00217                 /* Get content information segment */
00218                 if ( ( rc = peerdist_info_segment ( info, segment,
00219                                                     next_segment ) ) != 0 ) {
00220                         DBGC ( peermux, "PEERMUX %p could not get segment %d "
00221                                "information: %s\n", peermux, next_segment,
00222                                strerror ( rc ) );
00223                         goto err;
00224                 }
00225         }
00226 
00227         /* Get content information block */
00228         if ( ( rc = peerdist_info_block ( segment, block, next_block ) ) != 0 ){
00229                 DBGC ( peermux, "PEERMUX %p could not get segment %d block "
00230                        "%d information: %s\n", peermux, segment->index,
00231                        next_block, strerror ( rc ) );
00232                 goto err;
00233         }
00234 
00235         /* Ignore block if it lies entirely outside the trimmed range */
00236         if ( block->trim.start == block->trim.end ) {
00237                 DBGC ( peermux, "PEERMUX %p skipping segment %d block %d\n",
00238                        peermux, segment->index, block->index );
00239                 return;
00240         }
00241 
00242         /* Start downloading this block */
00243         if ( ( rc = peerblk_open ( &peermblk->xfer, peermux->uri,
00244                                    block ) ) != 0 ) {
00245                 DBGC ( peermux, "PEERMUX %p could not start download for "
00246                        "segment %d block %d: %s\n", peermux, segment->index,
00247                        block->index, strerror ( rc ) );
00248                 goto err;
00249         }
00250 
00251         /* Move to list of busy block downloads */
00252         list_del ( &peermblk->list );
00253         list_add_tail ( &peermblk->list, &peermux->busy );
00254 
00255         return;
00256 
00257  err:
00258         peermux_close ( peermux, rc );
00259 }
00260 
00261 /**
00262  * Receive data from multiplexed block download
00263  *
00264  * @v peermblk          PeerDist multiplexed block download
00265  * @v iobuf             I/O buffer
00266  * @v meta              Data transfer metadata
00267  * @ret rc              Return status code
00268  */
00269 static int peermux_block_deliver ( struct peerdist_multiplexed_block *peermblk,
00270                                    struct io_buffer *iobuf,
00271                                    struct xfer_metadata *meta ) {
00272         struct peerdist_multiplexer *peermux = peermblk->peermux;
00273 
00274         /* Sanity check: all block downloads must use absolute
00275          * positions for all deliveries, since they run concurrently.
00276          */
00277         assert ( meta->flags & XFER_FL_ABS_OFFSET );
00278 
00279         /* We can't use a simple passthrough interface descriptor,
00280          * since there are multiple block download interfaces.
00281          */
00282         return xfer_deliver ( &peermux->xfer, iob_disown ( iobuf ), meta );
00283 }
00284 
00285 /**
00286  * Get multiplexed block download underlying data transfer buffer
00287  *
00288  * @v peermblk          PeerDist multiplexed download block
00289  * @ret xferbuf         Data transfer buffer, or NULL on error
00290  */
00291 static struct xfer_buffer *
00292 peermux_block_buffer ( struct peerdist_multiplexed_block *peermblk ) {
00293         struct peerdist_multiplexer *peermux = peermblk->peermux;
00294 
00295         /* We can't use a simple passthrough interface descriptor,
00296          * since there are multiple block download interfaces.
00297          */
00298         return xfer_buffer ( &peermux->xfer );
00299 }
00300 
00301 /**
00302  * Record peer discovery statistics
00303  *
00304  * @v peermblk          PeerDist multiplexed block download
00305  * @v peer              Selected peer (or NULL)
00306  * @v peers             List of available peers
00307  */
00308 static void peermux_block_stat ( struct peerdist_multiplexed_block *peermblk,
00309                                  struct peerdisc_peer *peer,
00310                                  struct list_head *peers ) {
00311         struct peerdist_multiplexer *peermux = peermblk->peermux;
00312         struct peerdist_statistics *stats = &peermux->stats;
00313         struct peerdisc_peer *tmp;
00314         unsigned int count = 0;
00315 
00316         /* Record maximum number of available peers */
00317         list_for_each_entry ( tmp, peers, list )
00318                 count++;
00319         if ( count > stats->peers )
00320                 stats->peers = count;
00321 
00322         /* Update block counts */
00323         if ( peer )
00324                 stats->local++;
00325         stats->total++;
00326         DBGC2 ( peermux, "PEERMUX %p downloaded %d/%d from %d peers\n",
00327                 peermux, stats->local, stats->total, stats->peers );
00328 }
00329 
00330 /**
00331  * Close multiplexed block download
00332  *
00333  * @v peermblk          PeerDist multiplexed block download
00334  * @v rc                Reason for close
00335  */
00336 static void peermux_block_close ( struct peerdist_multiplexed_block *peermblk,
00337                                   int rc ) {
00338         struct peerdist_multiplexer *peermux = peermblk->peermux;
00339 
00340         /* Move to list of idle downloads */
00341         list_del ( &peermblk->list );
00342         list_add_tail ( &peermblk->list, &peermux->idle );
00343 
00344         /* If any error occurred, terminate the whole multiplexer */
00345         if ( rc != 0 ) {
00346                 peermux_close ( peermux, rc );
00347                 return;
00348         }
00349 
00350         /* Restart data transfer interface */
00351         intf_restart ( &peermblk->xfer, rc );
00352 
00353         /* Restart block download initiation process */
00354         process_add ( &peermux->process );
00355 }
00356 
00357 /** Data transfer interface operations */
00358 static struct interface_operation peermux_xfer_operations[] = {
00359         INTF_OP ( job_progress, struct peerdist_multiplexer *,
00360                   peermux_progress ),
00361         INTF_OP ( intf_close, struct peerdist_multiplexer *, peermux_close ),
00362 };
00363 
00364 /** Data transfer interface descriptor */
00365 static struct interface_descriptor peermux_xfer_desc =
00366         INTF_DESC_PASSTHRU ( struct peerdist_multiplexer, xfer,
00367                              peermux_xfer_operations, info );
00368 
00369 /** Content information interface operations */
00370 static struct interface_operation peermux_info_operations[] = {
00371         INTF_OP ( xfer_deliver, struct peerdist_multiplexer *,
00372                   peermux_info_deliver ),
00373         INTF_OP ( intf_close, struct peerdist_multiplexer *,
00374                   peermux_info_close ),
00375 };
00376 
00377 /** Content information interface descriptor */
00378 static struct interface_descriptor peermux_info_desc =
00379         INTF_DESC_PASSTHRU ( struct peerdist_multiplexer, info,
00380                              peermux_info_operations, xfer );
00381 
00382 /** Block download data transfer interface operations */
00383 static struct interface_operation peermux_block_operations[] = {
00384         INTF_OP ( xfer_deliver, struct peerdist_multiplexed_block *,
00385                   peermux_block_deliver ),
00386         INTF_OP ( xfer_buffer, struct peerdist_multiplexed_block *,
00387                   peermux_block_buffer ),
00388         INTF_OP ( peerdisc_stat, struct peerdist_multiplexed_block *,
00389                   peermux_block_stat ),
00390         INTF_OP ( intf_close, struct peerdist_multiplexed_block *,
00391                   peermux_block_close ),
00392 };
00393 
00394 /** Block download data transfer interface descriptor */
00395 static struct interface_descriptor peermux_block_desc =
00396         INTF_DESC ( struct peerdist_multiplexed_block, xfer,
00397                     peermux_block_operations );
00398 
00399 /** Block download initiation process descriptor */
00400 static struct process_descriptor peermux_process_desc =
00401         PROC_DESC ( struct peerdist_multiplexer, process, peermux_step );
00402 
00403 /**
00404  * Add PeerDist content-encoding filter
00405  *
00406  * @v xfer              Data transfer interface
00407  * @v info              Content information interface
00408  * @v uri               Original URI
00409  * @ret rc              Return status code
00410  */
00411 int peermux_filter ( struct interface *xfer, struct interface *info,
00412                      struct uri *uri ) {
00413         struct peerdist_multiplexer *peermux;
00414         struct peerdist_multiplexed_block *peermblk;
00415         unsigned int i;
00416 
00417         /* Allocate and initialise structure */
00418         peermux = zalloc ( sizeof ( *peermux ) );
00419         if ( ! peermux )
00420                 return -ENOMEM;
00421         ref_init ( &peermux->refcnt, peermux_free );
00422         intf_init ( &peermux->xfer, &peermux_xfer_desc, &peermux->refcnt );
00423         intf_init ( &peermux->info, &peermux_info_desc, &peermux->refcnt );
00424         peermux->uri = uri_get ( uri );
00425         xferbuf_umalloc_init ( &peermux->buffer,
00426                                &peermux->cache.info.raw.data );
00427         process_init_stopped ( &peermux->process, &peermux_process_desc,
00428                                &peermux->refcnt );
00429         INIT_LIST_HEAD ( &peermux->busy );
00430         INIT_LIST_HEAD ( &peermux->idle );
00431         for ( i = 0 ; i < PEERMUX_MAX_BLOCKS ; i++ ) {
00432                 peermblk = &peermux->block[i];
00433                 peermblk->peermux = peermux;
00434                 list_add_tail ( &peermblk->list, &peermux->idle );
00435                 intf_init ( &peermblk->xfer, &peermux_block_desc,
00436                             &peermux->refcnt );
00437         }
00438 
00439         /* Attach to parent interfaces, mortalise self, and return */
00440         intf_plug_plug ( &peermux->xfer, xfer );
00441         intf_plug_plug ( &peermux->info, info );
00442         ref_put ( &peermux->refcnt );
00443         return 0;
00444 }