You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

peermux.c 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. /*
  2. * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>.
  3. *
  4. * This program is free software; you can redistribute it and/or
  5. * modify it under the terms of the GNU General Public License as
  6. * published by the Free Software Foundation; either version 2 of the
  7. * License, or (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful, but
  10. * WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. * General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program; if not, write to the Free Software
  16. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  17. * 02110-1301, USA.
  18. *
  19. * You can also choose to distribute this program under the terms of
  20. * the Unmodified Binary Distribution Licence (as given in the file
  21. * COPYING.UBDL), provided that you have satisfied its requirements.
  22. */
  23. FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
  24. #include <stdlib.h>
  25. #include <stdio.h>
  26. #include <errno.h>
  27. #include <ipxe/uri.h>
  28. #include <ipxe/xferbuf.h>
  29. #include <ipxe/job.h>
  30. #include <ipxe/peerblk.h>
  31. #include <ipxe/peermux.h>
  32. /** @file
  33. *
  34. * Peer Content Caching and Retrieval (PeerDist) protocol multiplexer
  35. *
  36. */
  37. /**
  38. * Free PeerDist download multiplexer
  39. *
  40. * @v refcnt Reference count
  41. */
  42. static void peermux_free ( struct refcnt *refcnt ) {
  43. struct peerdist_multiplexer *peermux =
  44. container_of ( refcnt, struct peerdist_multiplexer, refcnt );
  45. uri_put ( peermux->uri );
  46. xferbuf_free ( &peermux->buffer );
  47. free ( peermux );
  48. }
  49. /**
  50. * Close PeerDist download multiplexer
  51. *
  52. * @v peermux PeerDist download multiplexer
  53. * @v rc Reason for close
  54. */
  55. static void peermux_close ( struct peerdist_multiplexer *peermux, int rc ) {
  56. unsigned int i;
  57. /* Stop block download initiation process */
  58. process_del ( &peermux->process );
  59. /* Shut down all block downloads */
  60. for ( i = 0 ; i < PEERMUX_MAX_BLOCKS ; i++ )
  61. intf_shutdown ( &peermux->block[i].xfer, rc );
  62. /* Shut down all other interfaces (which may be connected to
  63. * the same object).
  64. */
  65. intf_nullify ( &peermux->info ); /* avoid potential loops */
  66. intf_shutdown ( &peermux->xfer, rc );
  67. intf_shutdown ( &peermux->info, rc );
  68. }
  69. /**
  70. * Report progress of PeerDist download
  71. *
  72. * @v peermux PeerDist download multiplexer
  73. * @v progress Progress report to fill in
  74. * @ret ongoing_rc Ongoing job status code (if known)
  75. */
  76. static int peermux_progress ( struct peerdist_multiplexer *peermux,
  77. struct job_progress *progress ) {
  78. struct peerdist_statistics *stats = &peermux->stats;
  79. unsigned int percentage;
  80. /* Construct PeerDist status message */
  81. if ( stats->total ) {
  82. percentage = ( ( 100 * stats->local ) / stats->total );
  83. snprintf ( progress->message, sizeof ( progress->message ),
  84. "%3d%% from %d peers", percentage, stats->peers );
  85. }
  86. return 0;
  87. }
  88. /**
  89. * Receive content information
  90. *
  91. * @v peermux PeerDist download multiplexer
  92. * @v iobuf I/O buffer
  93. * @v meta Data transfer metadata
  94. * @ret rc Return status code
  95. */
  96. static int peermux_info_deliver ( struct peerdist_multiplexer *peermux,
  97. struct io_buffer *iobuf,
  98. struct xfer_metadata *meta ) {
  99. int rc;
  100. /* Add data to buffer */
  101. if ( ( rc = xferbuf_deliver ( &peermux->buffer, iobuf, meta ) ) != 0 )
  102. goto err;
  103. return 0;
  104. err:
  105. peermux_close ( peermux, rc );
  106. return rc;
  107. }
  108. /**
  109. * Close content information interface
  110. *
  111. * @v peermux PeerDist download multiplexer
  112. * @v rc Reason for close
  113. */
  114. static void peermux_info_close ( struct peerdist_multiplexer *peermux, int rc ){
  115. struct peerdist_info *info = &peermux->cache.info;
  116. size_t len;
  117. /* Terminate download on error */
  118. if ( rc != 0 )
  119. goto err;
  120. /* Successfully closing the content information interface
  121. * indicates that the content information has been fully
  122. * received, and initiates the actual PeerDist download.
  123. */
  124. /* Shut down content information interface */
  125. intf_shutdown ( &peermux->info, rc );
  126. /* Parse content information */
  127. if ( ( rc = peerdist_info ( info->raw.data, peermux->buffer.len,
  128. info ) ) != 0 ) {
  129. DBGC ( peermux, "PEERMUX %p could not parse content info: %s\n",
  130. peermux, strerror ( rc ) );
  131. goto err;
  132. }
  133. /* Notify recipient of total download size */
  134. len = ( info->trim.end - info->trim.start );
  135. if ( ( rc = xfer_seek ( &peermux->xfer, len ) ) != 0 ) {
  136. DBGC ( peermux, "PEERMUX %p could not presize buffer: %s\n",
  137. peermux, strerror ( rc ) );
  138. goto err;
  139. }
  140. xfer_seek ( &peermux->xfer, 0 );
  141. /* Start block download process */
  142. process_add ( &peermux->process );
  143. return;
  144. err:
  145. peermux_close ( peermux, rc );
  146. }
  147. /**
  148. * Initiate multiplexed block download
  149. *
  150. * @v peermux PeerDist download multiplexer
  151. */
  152. static void peermux_step ( struct peerdist_multiplexer *peermux ) {
  153. struct peerdist_info *info = &peermux->cache.info;
  154. struct peerdist_info_segment *segment = &peermux->cache.segment;
  155. struct peerdist_info_block *block = &peermux->cache.block;
  156. struct peerdist_multiplexed_block *peermblk;
  157. unsigned int next_segment;
  158. unsigned int next_block;
  159. int rc;
  160. /* Stop initiation process if all block downloads are busy */
  161. peermblk = list_first_entry ( &peermux->idle,
  162. struct peerdist_multiplexed_block, list );
  163. if ( ! peermblk ) {
  164. process_del ( &peermux->process );
  165. return;
  166. }
  167. /* Increment block index */
  168. next_block = ( block->index + 1 );
  169. /* Move to first/next segment, if applicable */
  170. if ( next_block >= segment->blocks ) {
  171. /* Reset block index */
  172. next_block = 0;
  173. /* Calculate segment index */
  174. next_segment = ( segment->info ? ( segment->index + 1 ) : 0 );
  175. /* If we have finished all segments and have no
  176. * remaining block downloads, then we are finished.
  177. */
  178. if ( next_segment >= info->segments ) {
  179. process_del ( &peermux->process );
  180. if ( list_empty ( &peermux->busy ) )
  181. peermux_close ( peermux, 0 );
  182. return;
  183. }
  184. /* Get content information segment */
  185. if ( ( rc = peerdist_info_segment ( info, segment,
  186. next_segment ) ) != 0 ) {
  187. DBGC ( peermux, "PEERMUX %p could not get segment %d "
  188. "information: %s\n", peermux, next_segment,
  189. strerror ( rc ) );
  190. goto err;
  191. }
  192. }
  193. /* Get content information block */
  194. if ( ( rc = peerdist_info_block ( segment, block, next_block ) ) != 0 ){
  195. DBGC ( peermux, "PEERMUX %p could not get segment %d block "
  196. "%d information: %s\n", peermux, segment->index,
  197. next_block, strerror ( rc ) );
  198. goto err;
  199. }
  200. /* Ignore block if it lies entirely outside the trimmed range */
  201. if ( block->trim.start == block->trim.end ) {
  202. DBGC ( peermux, "PEERMUX %p skipping segment %d block %d\n",
  203. peermux, segment->index, block->index );
  204. return;
  205. }
  206. /* Start downloading this block */
  207. if ( ( rc = peerblk_open ( &peermblk->xfer, peermux->uri,
  208. block ) ) != 0 ) {
  209. DBGC ( peermux, "PEERMUX %p could not start download for "
  210. "segment %d block %d: %s\n", peermux, segment->index,
  211. block->index, strerror ( rc ) );
  212. goto err;
  213. }
  214. /* Move to list of busy block downloads */
  215. list_del ( &peermblk->list );
  216. list_add_tail ( &peermblk->list, &peermux->busy );
  217. return;
  218. err:
  219. peermux_close ( peermux, rc );
  220. }
  221. /**
  222. * Receive data from multiplexed block download
  223. *
  224. * @v peermblk PeerDist multiplexed block download
  225. * @v iobuf I/O buffer
  226. * @v meta Data transfer metadata
  227. * @ret rc Return status code
  228. */
  229. static int peermux_block_deliver ( struct peerdist_multiplexed_block *peermblk,
  230. struct io_buffer *iobuf,
  231. struct xfer_metadata *meta ) {
  232. struct peerdist_multiplexer *peermux = peermblk->peermux;
  233. /* Sanity check: all block downloads must use absolute
  234. * positions for all deliveries, since they run concurrently.
  235. */
  236. assert ( meta->flags & XFER_FL_ABS_OFFSET );
  237. /* We can't use a simple passthrough interface descriptor,
  238. * since there are multiple block download interfaces.
  239. */
  240. return xfer_deliver ( &peermux->xfer, iob_disown ( iobuf ), meta );
  241. }
  242. /**
  243. * Get multiplexed block download underlying data transfer buffer
  244. *
  245. * @v peermblk PeerDist multiplexed download block
  246. * @ret xferbuf Data transfer buffer, or NULL on error
  247. */
  248. static struct xfer_buffer *
  249. peermux_block_buffer ( struct peerdist_multiplexed_block *peermblk ) {
  250. struct peerdist_multiplexer *peermux = peermblk->peermux;
  251. /* We can't use a simple passthrough interface descriptor,
  252. * since there are multiple block download interfaces.
  253. */
  254. return xfer_buffer ( &peermux->xfer );
  255. }
  256. /**
  257. * Record peer discovery statistics
  258. *
  259. * @v peermblk PeerDist multiplexed block download
  260. * @v peer Selected peer (or NULL)
  261. * @v peers List of available peers
  262. */
  263. static void peermux_block_stat ( struct peerdist_multiplexed_block *peermblk,
  264. struct peerdisc_peer *peer,
  265. struct list_head *peers ) {
  266. struct peerdist_multiplexer *peermux = peermblk->peermux;
  267. struct peerdist_statistics *stats = &peermux->stats;
  268. struct peerdisc_peer *tmp;
  269. unsigned int count = 0;
  270. /* Record maximum number of available peers */
  271. list_for_each_entry ( tmp, peers, list )
  272. count++;
  273. if ( count > stats->peers )
  274. stats->peers = count;
  275. /* Update block counts */
  276. if ( peer )
  277. stats->local++;
  278. stats->total++;
  279. DBGC2 ( peermux, "PEERMUX %p downloaded %d/%d from %d peers\n",
  280. peermux, stats->local, stats->total, stats->peers );
  281. }
  282. /**
  283. * Close multiplexed block download
  284. *
  285. * @v peermblk PeerDist multiplexed block download
  286. * @v rc Reason for close
  287. */
  288. static void peermux_block_close ( struct peerdist_multiplexed_block *peermblk,
  289. int rc ) {
  290. struct peerdist_multiplexer *peermux = peermblk->peermux;
  291. /* Move to list of idle downloads */
  292. list_del ( &peermblk->list );
  293. list_add_tail ( &peermblk->list, &peermux->idle );
  294. /* If any error occurred, terminate the whole multiplexer */
  295. if ( rc != 0 ) {
  296. peermux_close ( peermux, rc );
  297. return;
  298. }
  299. /* Restart data transfer interface */
  300. intf_restart ( &peermblk->xfer, rc );
  301. /* Restart block download initiation process */
  302. process_add ( &peermux->process );
  303. }
  304. /** Data transfer interface operations */
  305. static struct interface_operation peermux_xfer_operations[] = {
  306. INTF_OP ( job_progress, struct peerdist_multiplexer *,
  307. peermux_progress ),
  308. INTF_OP ( intf_close, struct peerdist_multiplexer *, peermux_close ),
  309. };
  310. /** Data transfer interface descriptor */
  311. static struct interface_descriptor peermux_xfer_desc =
  312. INTF_DESC_PASSTHRU ( struct peerdist_multiplexer, xfer,
  313. peermux_xfer_operations, info );
  314. /** Content information interface operations */
  315. static struct interface_operation peermux_info_operations[] = {
  316. INTF_OP ( xfer_deliver, struct peerdist_multiplexer *,
  317. peermux_info_deliver ),
  318. INTF_OP ( intf_close, struct peerdist_multiplexer *,
  319. peermux_info_close ),
  320. };
  321. /** Content information interface descriptor */
  322. static struct interface_descriptor peermux_info_desc =
  323. INTF_DESC_PASSTHRU ( struct peerdist_multiplexer, info,
  324. peermux_info_operations, xfer );
  325. /** Block download data transfer interface operations */
  326. static struct interface_operation peermux_block_operations[] = {
  327. INTF_OP ( xfer_deliver, struct peerdist_multiplexed_block *,
  328. peermux_block_deliver ),
  329. INTF_OP ( xfer_buffer, struct peerdist_multiplexed_block *,
  330. peermux_block_buffer ),
  331. INTF_OP ( peerdisc_stat, struct peerdist_multiplexed_block *,
  332. peermux_block_stat ),
  333. INTF_OP ( intf_close, struct peerdist_multiplexed_block *,
  334. peermux_block_close ),
  335. };
  336. /** Block download data transfer interface descriptor */
  337. static struct interface_descriptor peermux_block_desc =
  338. INTF_DESC ( struct peerdist_multiplexed_block, xfer,
  339. peermux_block_operations );
  340. /** Block download initiation process descriptor */
  341. static struct process_descriptor peermux_process_desc =
  342. PROC_DESC ( struct peerdist_multiplexer, process, peermux_step );
  343. /**
  344. * Add PeerDist content-encoding filter
  345. *
  346. * @v xfer Data transfer interface
  347. * @v info Content information interface
  348. * @v uri Original URI
  349. * @ret rc Return status code
  350. */
  351. int peermux_filter ( struct interface *xfer, struct interface *info,
  352. struct uri *uri ) {
  353. struct peerdist_multiplexer *peermux;
  354. struct peerdist_multiplexed_block *peermblk;
  355. unsigned int i;
  356. /* Allocate and initialise structure */
  357. peermux = zalloc ( sizeof ( *peermux ) );
  358. if ( ! peermux )
  359. return -ENOMEM;
  360. ref_init ( &peermux->refcnt, peermux_free );
  361. intf_init ( &peermux->xfer, &peermux_xfer_desc, &peermux->refcnt );
  362. intf_init ( &peermux->info, &peermux_info_desc, &peermux->refcnt );
  363. peermux->uri = uri_get ( uri );
  364. xferbuf_umalloc_init ( &peermux->buffer,
  365. &peermux->cache.info.raw.data );
  366. process_init_stopped ( &peermux->process, &peermux_process_desc,
  367. &peermux->refcnt );
  368. INIT_LIST_HEAD ( &peermux->busy );
  369. INIT_LIST_HEAD ( &peermux->idle );
  370. for ( i = 0 ; i < PEERMUX_MAX_BLOCKS ; i++ ) {
  371. peermblk = &peermux->block[i];
  372. peermblk->peermux = peermux;
  373. list_add_tail ( &peermblk->list, &peermux->idle );
  374. intf_init ( &peermblk->xfer, &peermux_block_desc,
  375. &peermux->refcnt );
  376. }
  377. /* Attach to parent interfaces, mortalise self, and return */
  378. intf_plug_plug ( &peermux->xfer, xfer );
  379. intf_plug_plug ( &peermux->info, info );
  380. ref_put ( &peermux->refcnt );
  381. return 0;
  382. }