4522bf2c86dbcd0fa5bd668f43951264910b92ee
[people/dverkamp/gpxe.git] / src / core / download.c
1 /*
2  * Copyright (C) 2007 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 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 /**
20  * @file
21  *
22  * Download protocols
23  *
24  */
25
26 #include <stdint.h>
27 #include <stdlib.h>
28 #include <errno.h>
29 #include <gpxe/umalloc.h>
30 #include <gpxe/ebuffer.h>
31 #include <gpxe/download.h>
32
33 static struct async_operations download_async_operations;
34
35 /** Registered download protocols */
36 static struct download_protocol download_protocols[0]
37         __table_start ( struct download_protocol, download_protocols );
38 static struct download_protocol download_protocols_end[0]
39         __table_end ( struct download_protocol, download_protocols );
40
41 /**
42  * Identify download protocol
43  *
44  * @v name              Download protocol name
45  * @ret protocol        Download protocol, or NULL
46  */
47 static struct download_protocol * find_protocol ( const char *name ) {
48         struct download_protocol *protocol;
49
50         for ( protocol = download_protocols; protocol < download_protocols_end;
51               protocol++ ) {
52                 if ( strcmp ( name, protocol->name ) == 0 )
53                         return protocol;
54         }
55         return NULL;
56 }
57
58 /**
59  * Start download
60  *
61  * @v uri_string        URI as a string (e.g. "http://www.nowhere.com/vmlinuz")
62  * @v parent            Parent asynchronous operation
63  * @ret data            Loaded file
64  * @ret len             Length of loaded file
65  * @ret rc              Return status code
66  *
67  * Starts download of a file to a user buffer.  The user buffer is
68  * allocated with umalloc().  The parent asynchronous operation will
69  * be notified via SIGCHLD when the download completes.  If the
70  * download completes successfully, the @c data and @c len fields will
71  * have been filled in, and the parent takes ownership of the buffer,
72  * which must eventually be freed with ufree().
73  *
74  * The uri_string does not need to remain persistent for the duration
75  * of the download; the parent may discard it as soon as
76  * start_download returns.
77  */
78 int start_download ( const char *uri_string, struct async *parent,
79                      userptr_t *data, size_t *len ) {
80         struct download *download;
81         int rc;
82
83         /* Allocate and populate download structure */
84         download = malloc ( sizeof ( *download ) );
85         if ( ! download )
86                 return -ENOMEM;
87         memset ( download, 0, sizeof ( *download ) );
88         download->data = data;
89         download->len = len;
90         async_init ( &download->async, &download_async_operations, parent );
91
92         /* Parse the URI */
93         download->uri = parse_uri ( uri_string );
94         if ( ! download->uri ) {
95                 rc = -ENOMEM;
96                 goto err;
97         }
98
99         /* Allocate an expandable buffer to hold the file */
100         if ( ( rc = ebuffer_alloc ( &download->buffer, 0 ) ) != 0 )
101                 goto err;
102
103         /* Identify the download protocol */
104         download->protocol = find_protocol ( download->uri->scheme );
105         if ( ! download->protocol ) {
106                 DBG ( "No such protocol \"%s\"\n", download->uri->scheme );
107                 rc = -ENOTSUP;
108                 goto err;
109         }
110
111         /* Start the actual download */
112         if ( ( rc = download->protocol->start_download ( download->uri,
113                                &download->buffer, &download->async ) ) != 0 ) {
114                 DBG ( "Could not start \"%s\" download: %s\n",
115                       download->uri->scheme, strerror ( rc ) );
116                 goto err;
117         }
118
119         return 0;
120
121  err:
122         async_uninit ( &download->async );
123         ufree ( download->buffer.addr );
124         free_uri ( download->uri );
125         free ( download );
126         return rc;
127 }
128
129 /**
130  * Handle download termination
131  *
132  * @v async             Download asynchronous operation
133  * @v signal            SIGCHLD
134  */
135 static void download_sigchld ( struct async *async,
136                                enum signal signal __unused ) {
137         struct download *download =
138                 container_of ( async, struct download, async );
139         int rc;
140
141         /* Reap child */
142         async_wait ( async, &rc, 1 );
143
144         /* Clean up */
145         if ( rc == 0 ) {
146                 /* Transfer ownership of buffer to parent */
147                 *(download->data) = download->buffer.addr;
148                 *(download->len) = download->buffer.fill;
149         } else {
150                 /* Discard the buffer */
151                 ufree ( download->buffer.addr );
152         }
153         free_uri ( download->uri );
154         download->uri = NULL;
155
156         /* Terminate ourselves */
157         async_done ( async, rc );
158 }
159
160 /**
161  * Free download resources
162  *
163  * @v async             Download asynchronous operation
164  */
165 static void download_reap ( struct async *async ) {
166         free ( container_of ( async, struct download, async ) );
167 }
168
169 /** Download asynchronous operations */
170 static struct async_operations download_async_operations = {
171         .reap = download_reap,
172         .signal = {
173                 [SIGCHLD]       = download_sigchld,
174                 [SIGUPDATE]     = SIG_IGN,
175         },
176 };