Go to most recent revision | Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
4349 | Serge | 1 | /* |
2 | * unbuffered I/O |
||
3 | * Copyright (c) 2001 Fabrice Bellard |
||
4 | * |
||
5 | * This file is part of FFmpeg. |
||
6 | * |
||
7 | * FFmpeg is free software; you can redistribute it and/or |
||
8 | * modify it under the terms of the GNU Lesser General Public |
||
9 | * License as published by the Free Software Foundation; either |
||
10 | * version 2.1 of the License, or (at your option) any later version. |
||
11 | * |
||
12 | * FFmpeg is distributed in the hope that it will be useful, |
||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
||
15 | * Lesser General Public License for more details. |
||
16 | * |
||
17 | * You should have received a copy of the GNU Lesser General Public |
||
18 | * License along with FFmpeg; if not, write to the Free Software |
||
19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
||
20 | */ |
||
21 | |||
22 | #include "libavutil/avstring.h" |
||
23 | #include "libavutil/dict.h" |
||
24 | #include "libavutil/opt.h" |
||
25 | #include "libavutil/time.h" |
||
26 | #include "os_support.h" |
||
27 | #include "avformat.h" |
||
28 | #if CONFIG_NETWORK |
||
29 | #include "network.h" |
||
30 | #endif |
||
31 | #include "url.h" |
||
32 | |||
33 | static URLProtocol *first_protocol = NULL; |
||
34 | |||
35 | URLProtocol *ffurl_protocol_next(URLProtocol *prev) |
||
36 | { |
||
37 | return prev ? prev->next : first_protocol; |
||
38 | } |
||
39 | |||
40 | /** @name Logging context. */ |
||
41 | /*@{*/ |
||
42 | static const char *urlcontext_to_name(void *ptr) |
||
43 | { |
||
44 | URLContext *h = (URLContext *)ptr; |
||
45 | if(h->prot) return h->prot->name; |
||
46 | else return "NULL"; |
||
47 | } |
||
48 | |||
49 | static void *urlcontext_child_next(void *obj, void *prev) |
||
50 | { |
||
51 | URLContext *h = obj; |
||
52 | if (!prev && h->priv_data && h->prot->priv_data_class) |
||
53 | return h->priv_data; |
||
54 | return NULL; |
||
55 | } |
||
56 | |||
57 | static const AVClass *urlcontext_child_class_next(const AVClass *prev) |
||
58 | { |
||
59 | URLProtocol *p = NULL; |
||
60 | |||
61 | /* find the protocol that corresponds to prev */ |
||
62 | while (prev && (p = ffurl_protocol_next(p))) |
||
63 | if (p->priv_data_class == prev) |
||
64 | break; |
||
65 | |||
66 | /* find next protocol with priv options */ |
||
67 | while (p = ffurl_protocol_next(p)) |
||
68 | if (p->priv_data_class) |
||
69 | return p->priv_data_class; |
||
70 | return NULL; |
||
71 | |||
72 | } |
||
73 | |||
74 | static const AVOption options[] = {{NULL}}; |
||
75 | const AVClass ffurl_context_class = { |
||
76 | .class_name = "URLContext", |
||
77 | .item_name = urlcontext_to_name, |
||
78 | .option = options, |
||
79 | .version = LIBAVUTIL_VERSION_INT, |
||
80 | .child_next = urlcontext_child_next, |
||
81 | .child_class_next = urlcontext_child_class_next, |
||
82 | }; |
||
83 | /*@}*/ |
||
84 | |||
85 | |||
86 | const char *avio_enum_protocols(void **opaque, int output) |
||
87 | { |
||
88 | URLProtocol *p; |
||
89 | *opaque = ffurl_protocol_next(*opaque); |
||
90 | if (!(p = *opaque)) return NULL; |
||
91 | if ((output && p->url_write) || (!output && p->url_read)) |
||
92 | return p->name; |
||
93 | return avio_enum_protocols(opaque, output); |
||
94 | } |
||
95 | |||
96 | int ffurl_register_protocol(URLProtocol *protocol, int size) |
||
97 | { |
||
98 | URLProtocol **p; |
||
99 | if (size < sizeof(URLProtocol)) { |
||
100 | URLProtocol* temp = av_mallocz(sizeof(URLProtocol)); |
||
101 | memcpy(temp, protocol, size); |
||
102 | protocol = temp; |
||
103 | } |
||
104 | p = &first_protocol; |
||
105 | while (*p != NULL) p = &(*p)->next; |
||
106 | *p = protocol; |
||
107 | protocol->next = NULL; |
||
108 | return 0; |
||
109 | } |
||
110 | |||
111 | static int url_alloc_for_protocol (URLContext **puc, struct URLProtocol *up, |
||
112 | const char *filename, int flags, |
||
113 | const AVIOInterruptCB *int_cb) |
||
114 | { |
||
115 | URLContext *uc; |
||
116 | int err; |
||
117 | |||
118 | #if CONFIG_NETWORK |
||
119 | if (up->flags & URL_PROTOCOL_FLAG_NETWORK && !ff_network_init()) |
||
120 | return AVERROR(EIO); |
||
121 | #endif |
||
122 | if ((flags & AVIO_FLAG_READ) && !up->url_read) { |
||
123 | av_log(NULL, AV_LOG_ERROR, |
||
124 | "Impossible to open the '%s' protocol for reading\n", up->name); |
||
125 | return AVERROR(EIO); |
||
126 | } |
||
127 | if ((flags & AVIO_FLAG_WRITE) && !up->url_write) { |
||
128 | av_log(NULL, AV_LOG_ERROR, |
||
129 | "Impossible to open the '%s' protocol for writing\n", up->name); |
||
130 | return AVERROR(EIO); |
||
131 | } |
||
132 | uc = av_mallocz(sizeof(URLContext) + strlen(filename) + 1); |
||
133 | if (!uc) { |
||
134 | err = AVERROR(ENOMEM); |
||
135 | goto fail; |
||
136 | } |
||
137 | uc->av_class = &ffurl_context_class; |
||
138 | uc->filename = (char *) &uc[1]; |
||
139 | strcpy(uc->filename, filename); |
||
140 | uc->prot = up; |
||
141 | uc->flags = flags; |
||
142 | uc->is_streamed = 0; /* default = not streamed */ |
||
143 | uc->max_packet_size = 0; /* default: stream file */ |
||
144 | if (up->priv_data_size) { |
||
145 | uc->priv_data = av_mallocz(up->priv_data_size); |
||
146 | if (!uc->priv_data) { |
||
147 | err = AVERROR(ENOMEM); |
||
148 | goto fail; |
||
149 | } |
||
150 | if (up->priv_data_class) { |
||
151 | int proto_len= strlen(up->name); |
||
152 | char *start = strchr(uc->filename, ','); |
||
153 | *(const AVClass**)uc->priv_data = up->priv_data_class; |
||
154 | av_opt_set_defaults(uc->priv_data); |
||
155 | if(!strncmp(up->name, uc->filename, proto_len) && uc->filename + proto_len == start){ |
||
156 | int ret= 0; |
||
157 | char *p= start; |
||
158 | char sep= *++p; |
||
159 | char *key, *val; |
||
160 | p++; |
||
161 | while(ret >= 0 && (key= strchr(p, sep)) && p |
||
162 | *val= *key= 0; |
||
163 | ret= av_opt_set(uc->priv_data, p, key+1, 0); |
||
164 | if (ret == AVERROR_OPTION_NOT_FOUND) |
||
165 | av_log(uc, AV_LOG_ERROR, "Key '%s' not found.\n", p); |
||
166 | *val= *key= sep; |
||
167 | p= val+1; |
||
168 | } |
||
169 | if(ret<0 || p!=key){ |
||
170 | av_log(uc, AV_LOG_ERROR, "Error parsing options string %s\n", start); |
||
171 | av_freep(&uc->priv_data); |
||
172 | av_freep(&uc); |
||
173 | err = AVERROR(EINVAL); |
||
174 | goto fail; |
||
175 | } |
||
176 | memmove(start, key+1, strlen(key)); |
||
177 | } |
||
178 | } |
||
179 | } |
||
180 | if (int_cb) |
||
181 | uc->interrupt_callback = *int_cb; |
||
182 | |||
183 | *puc = uc; |
||
184 | return 0; |
||
185 | fail: |
||
186 | *puc = NULL; |
||
187 | if (uc) |
||
188 | av_freep(&uc->priv_data); |
||
189 | av_freep(&uc); |
||
190 | #if CONFIG_NETWORK |
||
191 | if (up->flags & URL_PROTOCOL_FLAG_NETWORK) |
||
192 | ff_network_close(); |
||
193 | #endif |
||
194 | return err; |
||
195 | } |
||
196 | |||
197 | int ffurl_connect(URLContext* uc, AVDictionary **options) |
||
198 | { |
||
199 | int err = |
||
200 | uc->prot->url_open2 ? uc->prot->url_open2(uc, uc->filename, uc->flags, options) : |
||
201 | uc->prot->url_open(uc, uc->filename, uc->flags); |
||
202 | if (err) |
||
203 | return err; |
||
204 | uc->is_connected = 1; |
||
205 | //We must be careful here as ffurl_seek() could be slow, for example for http |
||
206 | if( (uc->flags & AVIO_FLAG_WRITE) |
||
207 | || !strcmp(uc->prot->name, "file")) |
||
208 | if(!uc->is_streamed && ffurl_seek(uc, 0, SEEK_SET) < 0) |
||
209 | uc->is_streamed= 1; |
||
210 | return 0; |
||
211 | } |
||
212 | |||
213 | #define URL_SCHEME_CHARS \ |
||
214 | "abcdefghijklmnopqrstuvwxyz" \ |
||
215 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ |
||
216 | "0123456789+-." |
||
217 | |||
218 | int ffurl_alloc(URLContext **puc, const char *filename, int flags, |
||
219 | const AVIOInterruptCB *int_cb) |
||
220 | { |
||
221 | URLProtocol *up = NULL; |
||
222 | char proto_str[128], proto_nested[128], *ptr; |
||
223 | size_t proto_len = strspn(filename, URL_SCHEME_CHARS); |
||
224 | |||
225 | if (!first_protocol) { |
||
226 | av_log(NULL, AV_LOG_WARNING, "No URL Protocols are registered. " |
||
227 | "Missing call to av_register_all()?\n"); |
||
228 | } |
||
229 | |||
230 | if (filename[proto_len] != ':' && |
||
231 | (filename[proto_len] != ',' || !strchr(filename + proto_len + 1, ':')) || |
||
232 | is_dos_path(filename)) |
||
233 | strcpy(proto_str, "file"); |
||
234 | else |
||
235 | av_strlcpy(proto_str, filename, FFMIN(proto_len+1, sizeof(proto_str))); |
||
236 | |||
237 | if ((ptr = strchr(proto_str, ','))) |
||
238 | *ptr = '\0'; |
||
239 | av_strlcpy(proto_nested, proto_str, sizeof(proto_nested)); |
||
240 | if ((ptr = strchr(proto_nested, '+'))) |
||
241 | *ptr = '\0'; |
||
242 | |||
243 | while (up = ffurl_protocol_next(up)) { |
||
244 | if (!strcmp(proto_str, up->name)) |
||
245 | return url_alloc_for_protocol (puc, up, filename, flags, int_cb); |
||
246 | if (up->flags & URL_PROTOCOL_FLAG_NESTED_SCHEME && |
||
247 | !strcmp(proto_nested, up->name)) |
||
248 | return url_alloc_for_protocol (puc, up, filename, flags, int_cb); |
||
249 | } |
||
250 | *puc = NULL; |
||
251 | if (!strcmp("https", proto_str)) |
||
252 | av_log(NULL, AV_LOG_WARNING, "https protocol not found, recompile with openssl or gnutls enabled.\n"); |
||
253 | return AVERROR_PROTOCOL_NOT_FOUND; |
||
254 | } |
||
255 | |||
256 | int ffurl_open(URLContext **puc, const char *filename, int flags, |
||
257 | const AVIOInterruptCB *int_cb, AVDictionary **options) |
||
258 | { |
||
259 | int ret = ffurl_alloc(puc, filename, flags, int_cb); |
||
260 | if (ret) |
||
261 | return ret; |
||
262 | if (options && (*puc)->prot->priv_data_class && |
||
263 | (ret = av_opt_set_dict((*puc)->priv_data, options)) < 0) |
||
264 | goto fail; |
||
265 | ret = ffurl_connect(*puc, options); |
||
266 | if (!ret) |
||
267 | return 0; |
||
268 | fail: |
||
269 | ffurl_close(*puc); |
||
270 | *puc = NULL; |
||
271 | return ret; |
||
272 | } |
||
273 | |||
274 | static inline int retry_transfer_wrapper(URLContext *h, unsigned char *buf, int size, int size_min, |
||
275 | int (*transfer_func)(URLContext *h, unsigned char *buf, int size)) |
||
276 | { |
||
277 | int ret, len; |
||
278 | int fast_retries = 5; |
||
279 | int64_t wait_since = 0; |
||
280 | |||
281 | len = 0; |
||
282 | while (len < size_min) { |
||
283 | if (ff_check_interrupt(&h->interrupt_callback)) |
||
284 | return AVERROR_EXIT; |
||
285 | ret = transfer_func(h, buf+len, size-len); |
||
286 | if (ret == AVERROR(EINTR)) |
||
287 | continue; |
||
288 | if (h->flags & AVIO_FLAG_NONBLOCK) |
||
289 | return ret; |
||
290 | if (ret == AVERROR(EAGAIN)) { |
||
291 | ret = 0; |
||
292 | if (fast_retries) { |
||
293 | fast_retries--; |
||
294 | } else { |
||
295 | if (h->rw_timeout) { |
||
296 | if (!wait_since) |
||
297 | wait_since = av_gettime(); |
||
298 | else if (av_gettime() > wait_since + h->rw_timeout) |
||
299 | return AVERROR(EIO); |
||
300 | } |
||
301 | av_usleep(1000); |
||
302 | } |
||
303 | } else if (ret < 1) |
||
304 | return (ret < 0 && ret != AVERROR_EOF) ? ret : len; |
||
305 | if (ret) |
||
306 | fast_retries = FFMAX(fast_retries, 2); |
||
307 | len += ret; |
||
308 | } |
||
309 | return len; |
||
310 | } |
||
311 | |||
312 | int ffurl_read(URLContext *h, unsigned char *buf, int size) |
||
313 | { |
||
314 | if (!(h->flags & AVIO_FLAG_READ)) |
||
315 | return AVERROR(EIO); |
||
316 | return retry_transfer_wrapper(h, buf, size, 1, h->prot->url_read); |
||
317 | } |
||
318 | |||
319 | int ffurl_read_complete(URLContext *h, unsigned char *buf, int size) |
||
320 | { |
||
321 | if (!(h->flags & AVIO_FLAG_READ)) |
||
322 | return AVERROR(EIO); |
||
323 | return retry_transfer_wrapper(h, buf, size, size, h->prot->url_read); |
||
324 | } |
||
325 | |||
326 | int ffurl_write(URLContext *h, const unsigned char *buf, int size) |
||
327 | { |
||
328 | if (!(h->flags & AVIO_FLAG_WRITE)) |
||
329 | return AVERROR(EIO); |
||
330 | /* avoid sending too big packets */ |
||
331 | if (h->max_packet_size && size > h->max_packet_size) |
||
332 | return AVERROR(EIO); |
||
333 | |||
334 | return retry_transfer_wrapper(h, (unsigned char *)buf, size, size, (void*)h->prot->url_write); |
||
335 | } |
||
336 | |||
337 | int64_t ffurl_seek(URLContext *h, int64_t pos, int whence) |
||
338 | { |
||
339 | int64_t ret; |
||
340 | |||
341 | if (!h->prot->url_seek) |
||
342 | return AVERROR(ENOSYS); |
||
343 | ret = h->prot->url_seek(h, pos, whence & ~AVSEEK_FORCE); |
||
344 | return ret; |
||
345 | } |
||
346 | |||
347 | int ffurl_closep(URLContext **hh) |
||
348 | { |
||
349 | URLContext *h= *hh; |
||
350 | int ret = 0; |
||
351 | if (!h) return 0; /* can happen when ffurl_open fails */ |
||
352 | |||
353 | if (h->is_connected && h->prot->url_close) |
||
354 | ret = h->prot->url_close(h); |
||
355 | #if CONFIG_NETWORK |
||
356 | if (h->prot->flags & URL_PROTOCOL_FLAG_NETWORK) |
||
357 | ff_network_close(); |
||
358 | #endif |
||
359 | if (h->prot->priv_data_size) { |
||
360 | if (h->prot->priv_data_class) |
||
361 | av_opt_free(h->priv_data); |
||
362 | av_freep(&h->priv_data); |
||
363 | } |
||
364 | av_freep(hh); |
||
365 | return ret; |
||
366 | } |
||
367 | |||
368 | int ffurl_close(URLContext *h) |
||
369 | { |
||
370 | return ffurl_closep(&h); |
||
371 | } |
||
372 | |||
373 | |||
374 | int avio_check(const char *url, int flags) |
||
375 | { |
||
376 | URLContext *h; |
||
377 | int ret = ffurl_alloc(&h, url, flags, NULL); |
||
378 | if (ret) |
||
379 | return ret; |
||
380 | |||
381 | if (h->prot->url_check) { |
||
382 | ret = h->prot->url_check(h, flags); |
||
383 | } else { |
||
384 | ret = ffurl_connect(h, NULL); |
||
385 | if (ret >= 0) |
||
386 | ret = flags; |
||
387 | } |
||
388 | |||
389 | ffurl_close(h); |
||
390 | return ret; |
||
391 | } |
||
392 | |||
393 | int64_t ffurl_size(URLContext *h) |
||
394 | { |
||
395 | int64_t pos, size; |
||
396 | |||
397 | size= ffurl_seek(h, 0, AVSEEK_SIZE); |
||
398 | if(size<0){ |
||
399 | pos = ffurl_seek(h, 0, SEEK_CUR); |
||
400 | if ((size = ffurl_seek(h, -1, SEEK_END)) < 0) |
||
401 | return size; |
||
402 | size++; |
||
403 | ffurl_seek(h, pos, SEEK_SET); |
||
404 | } |
||
405 | return size; |
||
406 | } |
||
407 | |||
408 | int ffurl_get_file_handle(URLContext *h) |
||
409 | { |
||
410 | if (!h->prot->url_get_file_handle) |
||
411 | return -1; |
||
412 | return h->prot->url_get_file_handle(h); |
||
413 | } |
||
414 | |||
415 | int ffurl_get_multi_file_handle(URLContext *h, int **handles, int *numhandles) |
||
416 | { |
||
417 | if (!h->prot->url_get_multi_file_handle) { |
||
418 | if (!h->prot->url_get_file_handle) |
||
419 | return AVERROR(ENOSYS); |
||
420 | *handles = av_malloc(sizeof(**handles)); |
||
421 | if (!*handles) |
||
422 | return AVERROR(ENOMEM); |
||
423 | *numhandles = 1; |
||
424 | *handles[0] = h->prot->url_get_file_handle(h); |
||
425 | return 0; |
||
426 | } |
||
427 | return h->prot->url_get_multi_file_handle(h, handles, numhandles); |
||
428 | } |
||
429 | |||
430 | int ffurl_shutdown(URLContext *h, int flags) |
||
431 | { |
||
432 | if (!h->prot->url_shutdown) |
||
433 | return AVERROR(EINVAL); |
||
434 | return h->prot->url_shutdown(h, flags); |
||
435 | } |
||
436 | |||
437 | int ff_check_interrupt(AVIOInterruptCB *cb) |
||
438 | { |
||
439 | int ret; |
||
440 | if (cb && cb->callback && (ret = cb->callback(cb->opaque))) |
||
441 | return ret; |
||
442 | return 0; |
||
443 | }>0){ |