avio.c 11 KB
Newer Older
Fabrice Bellard's avatar
Fabrice Bellard committed
1
/*
2
 * unbuffered I/O
3
 * Copyright (c) 2001 Fabrice Bellard
Fabrice Bellard's avatar
Fabrice Bellard committed
4
 *
5
 * This file is part of Libav.
6
 *
7
 * Libav is free software; you can redistribute it and/or
8 9
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
10
 * version 2.1 of the License, or (at your option) any later version.
Fabrice Bellard's avatar
Fabrice Bellard committed
11
 *
12
 * Libav is distributed in the hope that it will be useful,
Fabrice Bellard's avatar
Fabrice Bellard committed
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
Fabrice Bellard's avatar
Fabrice Bellard committed
16
 *
17
 * You should have received a copy of the GNU Lesser General Public
18
 * License along with Libav; if not, write to the Free Software
19
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Fabrice Bellard's avatar
Fabrice Bellard committed
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"
Fabrice Bellard's avatar
Fabrice Bellard committed
27
#include "avformat.h"
28 29 30
#if CONFIG_NETWORK
#include "network.h"
#endif
31
#include "url.h"
32 33 34 35 36 37

/** @name Logging context. */
/*@{*/
static const char *urlcontext_to_name(void *ptr)
{
    URLContext *h = (URLContext *)ptr;
38 39 40 41
    if (h->prot)
        return h->prot->name;
    else
        return "NULL";
42
}
43 44 45 46 47 48 49 50 51

static void *urlcontext_child_next(void *obj, void *prev)
{
    URLContext *h = obj;
    if (!prev && h->priv_data && h->prot->priv_data_class)
        return h->priv_data;
    return NULL;
}

52 53 54 55
static const AVOption options[] = {
    { "rw_timeout", "Timeout for IO operations (in microseconds)", offsetof(URLContext, rw_timeout), AV_OPT_TYPE_INT64, { .i64 = 0 }, 0, INT64_MAX, AV_OPT_FLAG_ENCODING_PARAM | AV_OPT_FLAG_DECODING_PARAM },
    { NULL }
};
56
const AVClass ffurl_context_class = {
57 58 59 60 61
    .class_name       = "URLContext",
    .item_name        = urlcontext_to_name,
    .option           = options,
    .version          = LIBAVUTIL_VERSION_INT,
    .child_next       = urlcontext_child_next,
62
    .child_class_next = ff_urlcontext_child_class_next,
63
};
64
/*@}*/
Fabrice Bellard's avatar
Fabrice Bellard committed
65

66
static int url_alloc_for_protocol(URLContext **puc, const URLProtocol *up,
67
                                  const char *filename, int flags,
68 69
                                  const AVIOInterruptCB *int_cb,
                                  const URLProtocol **protocols)
Fabrice Bellard's avatar
Fabrice Bellard committed
70 71 72 73
{
    URLContext *uc;
    int err;

74
#if CONFIG_NETWORK
75
    if (up->flags & URL_PROTOCOL_FLAG_NETWORK && !ff_network_init())
76 77
        return AVERROR(EIO);
#endif
78
    uc = av_mallocz(sizeof(URLContext) + strlen(filename) + 1);
79
    if (!uc) {
80
        err = AVERROR(ENOMEM);
81 82
        goto fail;
    }
83
    uc->av_class = &ffurl_context_class;
84
    uc->filename = (char *)&uc[1];
85
    strcpy(uc->filename, filename);
86 87 88
    uc->prot            = up;
    uc->flags           = flags;
    uc->is_streamed     = 0; /* default = not streamed */
89
    uc->max_packet_size = 0; /* default: stream file */
90
    uc->protocols       = protocols;
91 92
    if (up->priv_data_size) {
        uc->priv_data = av_mallocz(up->priv_data_size);
93 94 95 96
        if (!uc->priv_data) {
            err = AVERROR(ENOMEM);
            goto fail;
        }
97
        if (up->priv_data_class) {
98
            *(const AVClass **)uc->priv_data = up->priv_data_class;
99 100 101
            av_opt_set_defaults(uc->priv_data);
        }
    }
102 103
    if (int_cb)
        uc->interrupt_callback = *int_cb;
104

Fabrice Bellard's avatar
Fabrice Bellard committed
105 106
    *puc = uc;
    return 0;
107
fail:
108
    *puc = NULL;
109 110 111
    if (uc)
        av_freep(&uc->priv_data);
    av_freep(&uc);
112
#if CONFIG_NETWORK
113 114
    if (up->flags & URL_PROTOCOL_FLAG_NETWORK)
        ff_network_close();
115
#endif
116
    return err;
Fabrice Bellard's avatar
Fabrice Bellard committed
117 118
}

119
int ffurl_connect(URLContext *uc, AVDictionary **options)
120
{
121
    int err =
122 123 124 125
        uc->prot->url_open2 ? uc->prot->url_open2(uc,
                                                  uc->filename,
                                                  uc->flags,
                                                  options) :
126
        uc->prot->url_open(uc, uc->filename, uc->flags);
127 128 129
    if (err)
        return err;
    uc->is_connected = 1;
130 131 132 133 134
    /* We must be careful here as ffurl_seek() could be slow,
     * for example for http */
    if ((uc->flags & AVIO_FLAG_WRITE) || !strcmp(uc->prot->name, "file"))
        if (!uc->is_streamed && ffurl_seek(uc, 0, SEEK_SET) < 0)
            uc->is_streamed = 1;
135 136 137
    return 0;
}

138 139 140 141 142
#define URL_SCHEME_CHARS                        \
    "abcdefghijklmnopqrstuvwxyz"                \
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"                \
    "0123456789+-."

143
int ffurl_alloc(URLContext **puc, const char *filename, int flags,
144 145
                const AVIOInterruptCB *int_cb,
                const URLProtocol **protocols)
146
{
147
    char proto_str[128], proto_nested[128], *ptr;
148
    size_t proto_len = strspn(filename, URL_SCHEME_CHARS);
149
    int i;
150 151

    if (filename[proto_len] != ':' || is_dos_path(filename))
152
        strcpy(proto_str, "file");
153
    else
154 155
        av_strlcpy(proto_str, filename,
                   FFMIN(proto_len + 1, sizeof(proto_str)));
156

157 158 159 160
    av_strlcpy(proto_nested, proto_str, sizeof(proto_nested));
    if ((ptr = strchr(proto_nested, '+')))
        *ptr = '\0';

161 162
    for (i = 0; protocols[i]; i++) {
        const URLProtocol *up = protocols[i];
163 164 165
        if (!strcmp(proto_str, up->name))
            return url_alloc_for_protocol(puc, up, filename, flags, int_cb,
                                          protocols);
166
        if (up->flags & URL_PROTOCOL_FLAG_NESTED_SCHEME &&
167 168 169
            !strcmp(proto_nested, up->name))
            return url_alloc_for_protocol(puc, up, filename, flags, int_cb,
                                          protocols);
170 171
    }
    *puc = NULL;
172
    return AVERROR_PROTOCOL_NOT_FOUND;
173 174
}

175
int ffurl_open(URLContext **puc, const char *filename, int flags,
176
               const AVIOInterruptCB *int_cb, AVDictionary **options,
177 178
               const URLProtocol **protocols,
               URLContext *parent)
179
{
180
    int ret = ffurl_alloc(puc, filename, flags, int_cb, protocols);
181 182
    if (ret)
        return ret;
183 184
    if (parent)
        av_opt_copy(*puc, parent);
185 186 187
    if (options &&
        (ret = av_opt_set_dict(*puc, options)) < 0)
        goto fail;
188 189 190 191
    if (options && (*puc)->prot->priv_data_class &&
        (ret = av_opt_set_dict((*puc)->priv_data, options)) < 0)
        goto fail;
    ret = ffurl_connect(*puc, options);
192 193
    if (!ret)
        return 0;
194
fail:
195
    ffurl_close(*puc);
196 197 198 199
    *puc = NULL;
    return ret;
}

200 201 202 203 204
static inline int retry_transfer_wrapper(URLContext *h, uint8_t *buf,
                                         int size, int size_min,
                                         int (*transfer_func)(URLContext *h,
                                                              uint8_t *buf,
                                                              int size))
205 206
{
    int ret, len;
207
    int fast_retries = 5;
208
    int64_t wait_since = 0;
209 210

    len = 0;
211
    while (len < size_min) {
212
        ret = transfer_func(h, buf + len, size - len);
213 214
        if (ret == AVERROR(EINTR))
            continue;
215
        if (h->flags & AVIO_FLAG_NONBLOCK)
216
            return ret;
217 218
        if (ret == AVERROR(EAGAIN)) {
            ret = 0;
219
            if (fast_retries) {
220
                fast_retries--;
221 222 223 224 225 226 227
            } else {
                if (h->rw_timeout) {
                    if (!wait_since)
                        wait_since = av_gettime_relative();
                    else if (av_gettime_relative() > wait_since + h->rw_timeout)
                        return AVERROR(EIO);
                }
228
                av_usleep(1000);
229
            }
230
        } else if (ret < 1)
231
            return (ret < 0 && ret != AVERROR_EOF) ? ret : len;
232
        if (ret) {
233
            fast_retries = FFMAX(fast_retries, 2);
234 235
            wait_since = 0;
        }
236
        len += ret;
237
        if (ff_check_interrupt(&h->interrupt_callback))
238
            return AVERROR_EXIT;
239 240 241 242
    }
    return len;
}

243
int ffurl_read(URLContext *h, unsigned char *buf, int size)
244
{
245
    if (!(h->flags & AVIO_FLAG_READ))
246 247 248 249
        return AVERROR(EIO);
    return retry_transfer_wrapper(h, buf, size, 1, h->prot->url_read);
}

250
int ffurl_read_complete(URLContext *h, unsigned char *buf, int size)
251
{
252
    if (!(h->flags & AVIO_FLAG_READ))
253 254
        return AVERROR(EIO);
    return retry_transfer_wrapper(h, buf, size, size, h->prot->url_read);
255 256
}

257
int ffurl_write(URLContext *h, const unsigned char *buf, int size)
Fabrice Bellard's avatar
Fabrice Bellard committed
258
{
259
    if (!(h->flags & AVIO_FLAG_WRITE))
260
        return AVERROR(EIO);
261 262
    /* avoid sending too big packets */
    if (h->max_packet_size && size > h->max_packet_size)
263
        return AVERROR(EIO);
264

265 266 267
    return retry_transfer_wrapper(h, buf, size, size,
                                  (int (*)(struct URLContext *, uint8_t *, int))
                                  h->prot->url_write);
Fabrice Bellard's avatar
Fabrice Bellard committed
268 269
}

270
int64_t ffurl_seek(URLContext *h, int64_t pos, int whence)
Fabrice Bellard's avatar
Fabrice Bellard committed
271
{
272
    int64_t ret;
Fabrice Bellard's avatar
Fabrice Bellard committed
273 274

    if (!h->prot->url_seek)
275
        return AVERROR(ENOSYS);
276
    ret = h->prot->url_seek(h, pos, whence & ~AVSEEK_FORCE);
Fabrice Bellard's avatar
Fabrice Bellard committed
277 278 279
    return ret;
}

280
int ffurl_close(URLContext *h)
Fabrice Bellard's avatar
Fabrice Bellard committed
281
{
282
    int ret = 0;
283 284
    if (!h)
        return 0;     /* can happen when ffurl_open fails */
Fabrice Bellard's avatar
Fabrice Bellard committed
285

286
    if (h->is_connected && h->prot->url_close)
287
        ret = h->prot->url_close(h);
288
#if CONFIG_NETWORK
289 290
    if (h->prot->flags & URL_PROTOCOL_FLAG_NETWORK)
        ff_network_close();
291
#endif
292 293 294
    if (h->prot->priv_data_size) {
        if (h->prot->priv_data_class)
            av_opt_free(h->priv_data);
295
        av_free(h->priv_data);
296
    }
297
    av_free(h);
Fabrice Bellard's avatar
Fabrice Bellard committed
298 299 300
    return ret;
}

301 302
int avio_check(const char *url, int flags)
{
303
    const URLProtocol **protocols;
304
    URLContext *h;
305 306 307 308 309 310 311 312 313
    int ret;

    protocols = ffurl_get_protocols(NULL, NULL);
    if (!protocols)
        return AVERROR(ENOMEM);

    ret = ffurl_alloc(&h, url, flags, NULL, protocols);
    if (ret) {
        av_freep(&protocols);
314
        return ret;
315
    }
316 317 318 319

    if (h->prot->url_check) {
        ret = h->prot->url_check(h, flags);
    } else {
320
        ret = ffurl_connect(h, NULL);
321 322 323 324 325
        if (ret >= 0)
            ret = flags;
    }

    ffurl_close(h);
326
    av_freep(&protocols);
327 328 329
    return ret;
}

330
int64_t ffurl_size(URLContext *h)
Fabrice Bellard's avatar
Fabrice Bellard committed
331
{
332
    int64_t pos, size;
333

334 335
    size = ffurl_seek(h, 0, AVSEEK_SIZE);
    if (size < 0) {
336 337
        pos = ffurl_seek(h, 0, SEEK_CUR);
        if ((size = ffurl_seek(h, -1, SEEK_END)) < 0)
338 339
            return size;
        size++;
340
        ffurl_seek(h, pos, SEEK_SET);
341
    }
Fabrice Bellard's avatar
Fabrice Bellard committed
342 343
    return size;
}
344

345
int ffurl_get_file_handle(URLContext *h)
346 347 348 349 350 351
{
    if (!h->prot->url_get_file_handle)
        return -1;
    return h->prot->url_get_file_handle(h);
}

352 353 354 355 356
int ffurl_get_multi_file_handle(URLContext *h, int **handles, int *numhandles)
{
    if (!h->prot->url_get_multi_file_handle) {
        if (!h->prot->url_get_file_handle)
            return AVERROR(ENOSYS);
357
        *handles = av_malloc(sizeof(**handles));
358 359 360 361 362 363 364 365 366
        if (!*handles)
            return AVERROR(ENOMEM);
        *numhandles = 1;
        *handles[0] = h->prot->url_get_file_handle(h);
        return 0;
    }
    return h->prot->url_get_multi_file_handle(h, handles, numhandles);
}

367 368 369 370 371 372 373
int ffurl_shutdown(URLContext *h, int flags)
{
    if (!h->prot->url_shutdown)
        return AVERROR(EINVAL);
    return h->prot->url_shutdown(h, flags);
}

374 375 376 377 378
int ff_check_interrupt(AVIOInterruptCB *cb)
{
    int ret;
    if (cb && cb->callback && (ret = cb->callback(cb->opaque)))
        return ret;
379
    return 0;
380
}