1 /* 2 * Copyright (C) 2007 Michael Brown <mbrown (at) 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 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., 675 Mass Ave, Cambridge, MA 02139, USA. 17 */ 18 19 FILE_LICENCE ( GPL2_OR_LATER ); 20 21 #include <stdlib.h> 22 #include <stdarg.h> 23 #include <errno.h> 24 #include <gpxe/xfer.h> 25 #include <gpxe/open.h> 26 #include <gpxe/job.h> 27 #include <gpxe/uaccess.h> 28 #include <gpxe/umalloc.h> 29 #include <gpxe/image.h> 30 #include <gpxe/downloader.h> 31 32 /** @file 33 * 34 * Image downloader 35 * 36 */ 37 38 /** A downloader */ 39 struct downloader { 40 /** Reference count for this object */ 41 struct refcnt refcnt; 42 43 /** Job control interface */ 44 struct job_interface job; 45 /** Data transfer interface */ 46 struct xfer_interface xfer; 47 48 /** Image to contain downloaded file */ 49 struct image *image; 50 /** Current position within image buffer */ 51 size_t pos; 52 /** Image registration routine */ 53 int ( * register_image ) ( struct image *image ); 54 }; 55 56 /** 57 * Free downloader object 58 * 59 * @v refcnt Downloader reference counter 60 */ 61 static void downloader_free ( struct refcnt *refcnt ) { 62 struct downloader *downloader = 63 container_of ( refcnt, struct downloader, refcnt ); 64 65 image_put ( downloader->image ); 66 free ( downloader ); 67 } 68 69 /** 70 * Terminate download 71 * 72 * @v downloader Downloader 73 * @v rc Reason for termination 74 */ 75 static void downloader_finished ( struct downloader *downloader, int rc ) { 76 77 /* Block further incoming messages */ 78 job_nullify ( &downloader->job ); 79 xfer_nullify ( &downloader->xfer ); 80 81 /* Free resources and close interfaces */ 82 xfer_close ( &downloader->xfer, rc ); 83 job_done ( &downloader->job, rc ); 84 } 85 86 /** 87 * Ensure that download buffer is large enough for the specified size 88 * 89 * @v downloader Downloader 90 * @v len Required minimum size 91 * @ret rc Return status code 92 */ 93 static int downloader_ensure_size ( struct downloader *downloader, 94 size_t len ) { 95 userptr_t new_buffer; 96 97 /* If buffer is already large enough, do nothing */ 98 if ( len <= downloader->image->len ) 99 return 0; 100 101 DBGC ( downloader, "Downloader %p extending to %zd bytes\n", 102 downloader, len ); 103 104 /* Extend buffer */ 105 new_buffer = urealloc ( downloader->image->data, len ); 106 if ( ! new_buffer ) { 107 DBGC ( downloader, "Downloader %p could not extend buffer to " 108 "%zd bytes\n", downloader, len ); 109 return -ENOBUFS; 110 } 111 downloader->image->data = new_buffer; 112 downloader->image->len = len; 113 114 return 0; 115 } 116 117 /**************************************************************************** 118 * 119 * Job control interface 120 * 121 */ 122 123 /** 124 * Handle kill() event received via job control interface 125 * 126 * @v job Downloader job control interface 127 */ 128 static void downloader_job_kill ( struct job_interface *job ) { 129 struct downloader *downloader = 130 container_of ( job, struct downloader, job ); 131 132 /* Terminate download */ 133 downloader_finished ( downloader, -ECANCELED ); 134 } 135 136 /** 137 * Report progress of download job 138 * 139 * @v job Downloader job control interface 140 * @v progress Progress report to fill in 141 */ 142 static void downloader_job_progress ( struct job_interface *job, 143 struct job_progress *progress ) { 144 struct downloader *downloader = 145 container_of ( job, struct downloader, job ); 146 147 /* This is not entirely accurate, since downloaded data may 148 * arrive out of order (e.g. with multicast protocols), but 149 * it's a reasonable first approximation. 150 */ 151 progress->completed = downloader->pos; 152 progress->total = downloader->image->len; 153 } 154 155 /** Downloader job control interface operations */ 156 static struct job_interface_operations downloader_job_operations = { 157 .done = ignore_job_done, 158 .kill = downloader_job_kill, 159 .progress = downloader_job_progress, 160 }; 161 162 /**************************************************************************** 163 * 164 * Data transfer interface 165 * 166 */ 167 168 /** 169 * Handle deliver_raw() event received via data transfer interface 170 * 171 * @v xfer Downloader data transfer interface 172 * @v iobuf Datagram I/O buffer 173 * @v meta Data transfer metadata 174 * @ret rc Return status code 175 */ 176 static int downloader_xfer_deliver_iob ( struct xfer_interface *xfer, 177 struct io_buffer *iobuf, 178 struct xfer_metadata *meta ) { 179 struct downloader *downloader = 180 container_of ( xfer, struct downloader, xfer ); 181 size_t len; 182 size_t max; 183 int rc; 184 185 /* Calculate new buffer position */ 186 if ( meta->whence != SEEK_CUR ) 187 downloader->pos = 0; 188 downloader->pos += meta->offset; 189 190 /* Ensure that we have enough buffer space for this data */ 191 len = iob_len ( iobuf ); 192 max = ( downloader->pos + len ); 193 if ( ( rc = downloader_ensure_size ( downloader, max ) ) != 0 ) 194 goto done; 195 196 /* Copy data to buffer */ 197 copy_to_user ( downloader->image->data, downloader->pos, 198 iobuf->data, len ); 199 200 /* Update current buffer position */ 201 downloader->pos += len; 202 203 done: 204 free_iob ( iobuf ); 205 return rc; 206 } 207 208 /** 209 * Handle close() event received via data transfer interface 210 * 211 * @v xfer Downloader data transfer interface 212 * @v rc Reason for close 213 */ 214 static void downloader_xfer_close ( struct xfer_interface *xfer, int rc ) { 215 struct downloader *downloader = 216 container_of ( xfer, struct downloader, xfer ); 217 218 /* Register image if download was successful */ 219 if ( rc == 0 ) 220 rc = downloader->register_image ( downloader->image ); 221 222 /* Terminate download */ 223 downloader_finished ( downloader, rc ); 224 } 225 226 /** Downloader data transfer interface operations */ 227 static struct xfer_interface_operations downloader_xfer_operations = { 228 .close = downloader_xfer_close, 229 .vredirect = xfer_vreopen, 230 .window = unlimited_xfer_window, 231 .alloc_iob = default_xfer_alloc_iob, 232 .deliver_iob = downloader_xfer_deliver_iob, 233 .deliver_raw = xfer_deliver_as_iob, 234 }; 235 236 /**************************************************************************** 237 * 238 * Instantiator 239 * 240 */ 241 242 /** 243 * Instantiate a downloader 244 * 245 * @v job Job control interface 246 * @v image Image to fill with downloaded file 247 * @v register_image Image registration routine 248 * @v type Location type to pass to xfer_open() 249 * @v ... Remaining arguments to pass to xfer_open() 250 * @ret rc Return status code 251 * 252 * Instantiates a downloader object to download the specified URI into 253 * the specified image object. If the download is successful, the 254 * image registration routine @c register_image() will be called. 255 */ 256 int create_downloader ( struct job_interface *job, struct image *image, 257 int ( * register_image ) ( struct image *image ), 258 int type, ... ) { 259 struct downloader *downloader; 260 va_list args; 261 int rc; 262 263 /* Allocate and initialise structure */ 264 downloader = zalloc ( sizeof ( *downloader ) ); 265 if ( ! downloader ) 266 return -ENOMEM; 267 downloader->refcnt.free = downloader_free; 268 job_init ( &downloader->job, &downloader_job_operations, 269 &downloader->refcnt ); 270 xfer_init ( &downloader->xfer, &downloader_xfer_operations, 271 &downloader->refcnt ); 272 downloader->image = image_get ( image ); 273 downloader->register_image = register_image; 274 va_start ( args, type ); 275 276 /* Instantiate child objects and attach to our interfaces */ 277 if ( ( rc = xfer_vopen ( &downloader->xfer, type, args ) ) != 0 ) 278 goto err; 279 280 /* Attach parent interface, mortalise self, and return */ 281 job_plug_plug ( &downloader->job, job ); 282 ref_put ( &downloader->refcnt ); 283 va_end ( args ); 284 return 0; 285 286 err: 287 downloader_finished ( downloader, rc ); 288 ref_put ( &downloader->refcnt ); 289 va_end ( args ); 290 return rc; 291 } 292