FFmpeg
Loading...
Searching...
No Matches
shared.c
Go to the documentation of this file.
1/*
2 * Shared file cache protocol.
3 * Copyright (c) 2026 Niklas Haas
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 * Based on cache.c by Michael Niedermayer
22 */
23
25#include "libavutil/avassert.h"
26#include "libavutil/avstring.h"
27#include "libavutil/crc.h"
28#include "libavutil/error.h"
29#include "libavutil/file.h"
30#include "libavutil/hash.h"
31#include "libavutil/file_open.h"
32#include "libavutil/mem.h"
33#include "libavutil/opt.h"
34#include "libavutil/time.h"
35
36#include "internal.h"
37#include "os_support.h"
38#include "url.h"
39
40#include <assert.h>
41#include <errno.h>
42#include <fcntl.h>
43#include <inttypes.h>
44#include <stdatomic.h>
45#include <string.h>
46#include <sys/stat.h>
47#if HAVE_UNISTD_H
48#include <unistd.h>
49#endif
50#ifndef _WIN32
51#include <sys/file.h>
52#endif
53
54/**
55 * This hash should be resistant against collision attacks, so that an
56 * attacker could not generate e.g. two different URIs that map to the same
57 * cache file. This requires at least 64 bits of collision resistance in
58 * practice (i.e. 128 bits = 16 bytes of hash size). However, we can be
59 * conservative by computing e.g. a 256 bit hash and storing it inside the
60 * file header for verification.
61 *
62 * Note that due to the way we use atomics, we should avoid zero bytes in
63 * the resulting hash; hence we tweak the input slightly to avoid this.
64 * The resulting loss in hash strength is negligible, since 32 bytes is
65 * already much more than needed.
66 */
67#define HASH_METHOD "SHA512/256"
68#define HASH_SIZE 32
69#define HEADER_MAGIC MKTAG(u'\xFF', 'S', 'h', '$')
70#define HEADER_VERSION 3
71
72/**
73 * Hard watershed of consecutive failed blocks before we give up on the cache
74 * file altogether and assume it's entirely lost to us.
75 **/
76#define MAX_CORRUPT_BLOCKS 10
77
78static int hash_uri(uint8_t hash[HASH_SIZE], const char *uri)
79{
80 struct AVHashContext *ctx = NULL;
81 int ret = av_hash_alloc(&ctx, HASH_METHOD);
82 if (ret < 0)
83 return ret;
84
85 const int16_t version = HEADER_VERSION;
88 av_hash_update(ctx, (const uint8_t *) &version, sizeof(version));
89 av_hash_update(ctx, (const uint8_t *) uri, strlen(uri));
92
93 for (int i = 0; i < HASH_SIZE; i++)
94 hash[i] = hash[i] ? hash[i] : ~hash[i]; /* prevent zero bytes */
95 return 0;
96}
97
99 /* Reserved block state values */
100 BLOCK_NONE = 0, ///< block is not cached
101 BLOCK_PENDING, ///< a thread is currently trying to write this block
102 BLOCK_FAILED, ///< the underlying I/O source failed to read this block
103
104 /**
105 * All other block states represent valid cached blocks, with the value
106 * being the CRC of the block data.
107 */
108};
109
110static uint32_t get_block_crc(const uint8_t *block, size_t block_size)
111{
112 uint32_t crc = av_crc(av_crc_get_table(AV_CRC_32_IEEE), 0, block, block_size);
113 switch (crc) {
114 case BLOCK_NONE:
115 case BLOCK_FAILED:
116 case BLOCK_PENDING:
117 return ~crc; /* avoid reserved block states */
118 default:
119 return crc;
120 }
121}
122
123typedef struct Block {
124 atomic_uint state; /* enum BlockState */
125} Block;
126
127typedef struct Spacemap {
131 atomic_ullong filesize; /* byte offset of true EOF, or 0 if unknown */
132 atomic_uchar hash[HASH_SIZE]; /* hash of resource URI / filename */
133 atomic_ullong blocks_cached; /* (lower bound on) the number of blocks cached */
134 char reserved[72];
135
137} Spacemap;
138
139static_assert(offsetof(Spacemap, blocks) == 128, "Spacemap header layout mismatch");
140
141/* Set to value iff the current value is unset (zero) */
142#define DEF_SET_ONCE(ctype, atype) \
143 static int set_once_##atype(atomic_##atype *const ptr, const ctype value) \
144 { \
145 ctype prev = 0; \
146 av_assert1(value != 0); \
147 if (atomic_compare_exchange_strong_explicit( \
148 ptr, &prev, value, memory_order_release, memory_order_relaxed)) \
149 return 1; \
150 else if (prev == value) \
151 return 0; \
152 else \
153 return AVERROR(EINVAL); \
154 }
155
156DEF_SET_ONCE(unsigned char, uchar)
157DEF_SET_ONCE(unsigned int, uint)
158DEF_SET_ONCE(unsigned short, ushort)
159DEF_SET_ONCE(unsigned long long, ullong)
160
161typedef struct SharedContext {
162 AVClass *class;
165
166 /* options */
169 int block_shift; ///< requested shift; updated on init if it disagrees
177
178 /* misc state */
179 int64_t pos; ///< current logical position
180 uint8_t *tmp_buf;
182 int write_err; ///< write error occurred
184 int64_t filesize; ///< once known
185 int64_t blocks_max; ///< maximum number of blocks to cache
186
187 /* cache file */
188 uint8_t *cache_data; ///< optional mapping of the cache file
190 off_t cache_size; ///< size of mapped memory region (for unmapping)
191 int fd;
192
193 /* space map */
195 char *map_path;
196 off_t map_size;
197 int mapfd;
198
199 /* statistics */
203
205{
206 SharedContext *s = h->priv_data;
207
208 ffurl_close(s->inner);
209 av_file_unmap_shared(s->cache_data, s->cache_size);
210 av_file_unmap_shared(s->spacemap, s->map_size);
211 if (s->fd != -1)
212 close(s->fd);
213 if (s->mapfd != -1)
214 close(s->mapfd);
215 av_freep(&s->cache_path);
216 av_freep(&s->map_path);
217 av_freep(&s->tmp_buf);
218
219 av_log(h, AV_LOG_DEBUG, "Cache statistics: %"PRId64" hits, %"PRId64" misses\n",
220 s->nb_hit, s->nb_miss);
221 return 0;
222}
223
225static int spacemap_init(URLContext *h, const uint8_t hash[HASH_SIZE]);
227
229{
230 SharedContext *s = h->priv_data;
231 if (!s->filesize) {
232 uint64_t size = atomic_load_explicit(&s->spacemap->filesize, memory_order_relaxed);
233 if (size > INT64_MAX)
234 return AVERROR(EINVAL);
235 else if (size)
236 s->filesize = size;
237 }
238
239 return s->filesize;
240}
241
242static int set_filesize(URLContext *h, int64_t new_size)
243{
244 SharedContext *s = h->priv_data;
245 int ret;
246
247 if (!new_size)
248 return 0;
249
250 ret = set_once_ullong(&s->spacemap->filesize, new_size);
251 if (ret < 0) {
252 av_log(h, AV_LOG_ERROR, "Cached file size mismatch, expected: "
253 "%"PRId64", got: %"PRIu64"!\n", new_size,
254 (uint64_t) atomic_load(&s->spacemap->filesize));
255 return ret;
256 } else if (ret) {
257 /* Opportunistically map the file; this also sets the correct filesize.
258 * Ignore errors as this is not critical to the cache logic. */
259 cache_map(h, new_size);
260 }
261
262 return ret;
263}
264
266{
267 switch (err) {
268 case AVERROR_EXIT:
269 case AVERROR_EOF:
270 case AVERROR_BUG:
271 case AVERROR(EAGAIN):
272 case AVERROR(ENOSYS):
273 case AVERROR(EINVAL):
274 return 0;
275 default:
276 return err < 0;
277 }
278}
279
280static int shared_open(URLContext *h, const char *arg, int flags, AVDictionary **options)
281{
282 SharedContext *s = h->priv_data;
283 int ret;
284
285 if (!s->cache_dir || !s->cache_dir[0]) {
286 av_log(h, AV_LOG_ERROR, "Missing path for shared cache! Specify a "
287 "directory using the -cache_dir option.\n");
288 return AVERROR(EINVAL);
289 }
290
291 const char *spacemap_dir = s->cache_dir;
292 if (s->metadata_dir && s->metadata_dir[0])
293 spacemap_dir = s->metadata_dir;
294
295 s->fd = s->mapfd = -1; /* Set these early for shared_close() failure path */
296
297 /* Open underlying protocol */
298 av_strstart(arg, "shared:", &arg);
299 ret = ffurl_open_whitelist(&s->inner, arg, flags, &h->interrupt_callback,
300 options, h->protocol_whitelist, h->protocol_blacklist, h);
301 if (is_ignorable_error(ret) && s->ignore_errors) {
302 av_log(h, AV_LOG_WARNING, "Underlying URL failed to open: %s. "
303 "Continuing with cache file only.\n", av_err2str(ret));
304 } else if (ret < 0)
305 goto fail;
306
307 uint8_t hash[HASH_SIZE];
308 ret = hash_uri(hash, arg);
309 if (ret < 0)
310 goto fail;
311
312 /* 128 bits is enough for collision resistance; we already store the full
313 * hash inside the header for verification */
314 char filename[2 * 16 + 1];
315 ff_data_to_hex(filename, hash, sizeof(filename)/2, 0);
316 s->cache_path = av_asprintf("%s/%s.cache", s->cache_dir, filename);
317 s->map_path = av_asprintf("%s/%s.spacemap", spacemap_dir, filename);
318 if (!s->cache_path || !s->map_path) {
319 ret = AVERROR(ENOMEM);
320 goto fail;
321 }
322
323 av_log(h, AV_LOG_VERBOSE, "Opening cache file '%s' for URI: '%s'\n",
324 s->cache_path, s->inner ? s->inner->filename : arg);
325
326 const int mode = O_RDWR | O_BINARY | (s->inner ? O_CREAT : 0);
327 s->fd = avpriv_open(s->cache_path, mode, 0660);
328 s->mapfd = s->fd >= 0 ? avpriv_open(s->map_path, mode, 0660) : -1;
329 if (s->fd < 0 || s->mapfd < 0) {
330 ret = AVERROR(errno);
331 av_log(h, AV_LOG_ERROR, "Failed to open '%s': %s\n",
332 s->fd < 0 ? s->cache_path : s->map_path, av_err2str(ret));
333 goto fail;
334 }
335
336 ret = spacemap_init(h, hash);
337 if (ret < 0)
338 goto fail;
339
340 /* s->block_shift is fully settled after spacemap_init() */
341 s->block_size = 1 << s->block_shift;
342 s->blocks_max = s->cache_size_max >> s->block_shift;
343
345 if (filesize < 0) {
346 ret = (int) filesize;
347 goto fail;
348 } else if (!filesize) {
349 /* Filesize is not yet known, try to get it from the underlying URL;
350 * go through our own seek function to handle errors and updates */
352 if (filesize < 0 && filesize != AVERROR(ENOSYS)) {
353 ret = (int) filesize;
354 goto fail;
355 }
356 }
357
358 if (filesize > 0) {
359 int64_t last_pos = filesize - 1;
360 int64_t last_block = last_pos >> s->block_shift;
361 ret = spacemap_grow(h, last_block);
362 if (ret < 0)
363 goto fail;
364
365 /* If filesize is known, we can directly map the cache file */
366 ret = cache_map(h, filesize);
367 if (ret < 0) {
368 av_log(h, AV_LOG_WARNING, "Failed to map cache file: %s. Falling "
369 "back to normal read/write\n", av_err2str(ret));
370 ret = 0;
371 }
372 }
373
374 /* Temporary buffer needed for pread/pwrite() fallback */
375 s->tmp_buf = av_malloc(s->block_size);
376 if (!s->tmp_buf) {
377 ret = AVERROR(ENOMEM);
378 goto fail;
379 }
380
381 h->max_packet_size = s->block_size;
382 h->min_packet_size = s->block_size;
383 ret = 0;
384
385fail:
386 if (ret < 0)
388 return ret;
389}
390
392{
393 SharedContext *s = h->priv_data;
394 if (s->cache_size >= filesize || filesize > SIZE_MAX)
395 return 0;
396
397 if (s->cache_data) {
398 av_file_unmap_shared(s->cache_data, s->cache_size);
399 s->cache_data = NULL;
400 s->cache_size = 0;
401 }
402
403 /* The mapping extends the file to the file size; it can be shorter if
404 * another process wrote the correct filesize to the header but crashed
405 * right before actually successfully resizing the file. */
406 void *map;
407 int ret = av_file_map_shared(s->fd, filesize, &map);
408 if (ret < 0)
409 return ret;
410
411 s->cache_data = map;
412 s->cache_size = filesize;
413 return 0;
414}
415
416static int spacemap_remap(URLContext *h, size_t map_size)
417{
418 SharedContext *s = h->priv_data;
419 int ret, did_grow = 0, locked = 0;
420 if (map_size <= s->map_size)
421 return 0;
422
423 /* Opportunistically get current filesize before attempting to lock */
424 struct stat st;
425 ret = fstat(s->mapfd, &st);
426 if (ret < 0) {
427 ret = AVERROR(errno);
428 goto fail;
429 }
430
431 if (st.st_size >= map_size)
432 goto skip_resize;
433
434 /* Lock the spacemap to ensure nobody else is currently resizing it */
435 ret = flock(s->mapfd, LOCK_EX);
436 if (ret < 0) {
437 ret = AVERROR(errno);
438 goto fail;
439 }
440 locked = 1;
441
442 /* Refresh filesize after acquiring the lock */
443 ret = fstat(s->mapfd, &st);
444 if (ret < 0) {
445 ret = AVERROR(errno);
446 goto fail;
447 }
448
449 if (st.st_size >= map_size)
450 goto skip_resize;
451
452 /* The new mapping extends the file */
453 st.st_size = map_size;
454 did_grow = 1;
455
456skip_resize:
457 av_file_unmap_shared(s->spacemap, s->map_size);
458 s->spacemap = NULL;
459 s->map_size = st.st_size;
460
461 void *map;
462 ret = av_file_map_shared(s->mapfd, s->map_size, &map);
463 if (ret < 0) {
464 s->map_size = 0;
465 goto fail;
466 }
467 s->spacemap = map;
468
469 if (locked) {
470 flock(s->mapfd, LOCK_UN);
471 locked = 0;
472 }
473
474 return did_grow;
475
476fail:
477 if (locked)
478 flock(s->mapfd, LOCK_UN);
479 av_log(h, AV_LOG_ERROR, "Failed to resize space map: %s\n", av_err2str(ret));
480 return ret;
481}
482
484{
485 SharedContext *s = h->priv_data;
486 int64_t num_blocks = block + 1;
487 size_t map_bytes = sizeof(Spacemap) + num_blocks * sizeof(Block);
488
489 /* When streaming files without known size, round up the number of blocks
490 * to the nearest multiple of the block size to reduce the rate of resizes */
492 if (filesize < 0)
493 return (int) filesize;
494 else if (!filesize) {
495 av_assert0(s->block_size > 0);
496 map_bytes = FFALIGN(map_bytes, (int64_t) s->block_size);
497 }
498
499 if (map_bytes < num_blocks)
500 return AVERROR(EINVAL); /* overflow */
501
502 const off_t old_size = s->map_size;
503 int ret = spacemap_remap(h, map_bytes);
504 if (ret < 0)
505 return ret;
506
507 /* Report new size after successful grow */
508 if (s->map_size > old_size) {
509 num_blocks = (s->map_size - sizeof(Spacemap)) / sizeof(Block);
511 "%s %zu bytes, capacity: %"PRId64" blocks = %"PRId64" MB\n",
512 ret ? "Resized spacemap to" : "Mapped spacemap with",
513 (size_t) s->map_size, num_blocks,
514 (num_blocks * (int64_t) s->block_size) >> 20);
515 }
516 return 0;
517}
518
519static int spacemap_init(URLContext *h, const uint8_t hash[HASH_SIZE])
520{
521 SharedContext *s = h->priv_data;
522 int ret;
523
524 ret = spacemap_remap(h, sizeof(Spacemap));
525 if (ret < 0)
526 return ret;
527
528 if ((ret = set_once_uint(&s->spacemap->header_magic, HEADER_MAGIC)) < 0 ||
529 (ret = set_once_ushort(&s->spacemap->version, HEADER_VERSION)) < 0)
530 {
531 av_log(h, AV_LOG_ERROR, "Shared cache spacemap header mismatch!\n");
532 av_log(h, AV_LOG_ERROR, " Expected magic: 0x%X, version: %d\n",
534 av_log(h, AV_LOG_ERROR, " Got magic: 0x%X, version: %d\n",
535 atomic_load(&s->spacemap->header_magic),
536 atomic_load(&s->spacemap->version));
537 return ret;
538 }
539
540 ret = set_once_ushort(&s->spacemap->block_shift, s->block_shift);
541 if (ret < 0) {
542 const int shift = atomic_load(&s->spacemap->block_shift);
543 av_log(h, AV_LOG_WARNING, "Shared cache uses block shift %d, "
544 "but requested block shift is %d.\n", shift, s->block_shift);
545 if (shift < 9 || shift > 30) {
546 av_log(h, AV_LOG_ERROR, "Invalid block shift %d in cache file!\n", shift);
547 return AVERROR(EINVAL);
548 }
549 s->block_shift = shift;
550 }
551
552 for (int i = 0; i < HASH_SIZE; i++) {
553 ret = set_once_uchar(&s->spacemap->hash[i], hash[i]);
554 if (ret < 0) {
555 av_log(h, AV_LOG_ERROR, "Shared cache spacemap hash mismatch!\n");
556 char hash_hex[2 * HASH_SIZE + 1];
557 ff_data_to_hex(hash_hex, hash, HASH_SIZE, 0);
558 av_log(h, AV_LOG_ERROR, " Expected hash: %s\n", hash_hex);
559 uint8_t hash2[HASH_SIZE];
560 for (int j = 0; j < HASH_SIZE; ++j)
561 hash2[j] = atomic_load_explicit(&s->spacemap->hash[j], memory_order_relaxed);
562 ff_data_to_hex(hash_hex, hash2, HASH_SIZE, 0);
563 av_log(h, AV_LOG_ERROR, " Got hash: %s\n", hash_hex);
564 return ret;
565 }
566 }
567
568 if (ret) /* set_once() return 1 if this is the first time setting the value */
569 av_log(h, AV_LOG_DEBUG, "Initialized new cache spacemap.\n");
570
571 return ret;
572}
573
574static int read_cache(SharedContext *s, uint8_t *buf, size_t size, off_t offset)
575{
576 if (s->cache_data) {
577 av_assert1(offset + size <= s->cache_size);
578 memcpy(buf, s->cache_data + offset, size);
579 return 0;
580 }
581
582 while (size) {
583 ssize_t ret = pread(s->fd, buf, size, offset);
584 if (ret <= 0)
585 return ret ? AVERROR(errno) : AVERROR_EOF;
586 buf += ret;
587 offset += ret;
588 size -= ret;
589 }
590
591 return 0;
592}
593
594static int write_cache(SharedContext *s, const uint8_t *buf, size_t size, off_t offset)
595{
596 if (s->cache_data) {
597 av_assert1(offset + size <= s->cache_size);
598 memcpy(s->cache_data + offset, buf, size);
599 return 0;
600 }
601
602 while (size) {
603 ssize_t ret = pwrite(s->fd, buf, size, offset);
604 if (ret <= 0)
605 return ret ? AVERROR(errno) : AVERROR(EIO);
606 buf += ret;
607 offset += ret;
608 size -= ret;
609 }
610
611 return 0;
612}
613
615{
616 if (!filesize)
617 return size;
618 else if (pos > filesize)
619 return 0;
620 else
621 return FFMIN(filesize - pos, size);
622}
623
624static int shared_read(URLContext *h, unsigned char *buf, int size)
625{
626 SharedContext *s = h->priv_data;
627 uint8_t *tmp;
628 int ret;
629 if (!s->spacemap)
630 return AVERROR(EIO);
631
632 if (size <= 0)
633 return 0;
634
636 if (filesize < 0)
637 return (int) filesize;
638
639 size = clamp_size(h, size, s->pos, filesize);
640 if (size <= 0)
641 return AVERROR_EOF;
642
643 const int64_t block_id = s->pos >> s->block_shift;
644 const int64_t offset = s->pos & (s->block_size - 1);
645 const int64_t block_pos = block_id * s->block_size;
646 int block_size = clamp_size(h, s->block_size, block_pos, filesize);
647 ret = spacemap_grow(h, block_id);
648 if (ret < 0)
649 return ret;
650
651 Block *const block = &s->spacemap->blocks[block_id];
653 int64_t pending_since = 0;
654 int verify_read = 0, acquired = 0, allocated = 0;
655
656retry:
657 switch (state) {
658 default:
659 if (s->num_corrupt >= MAX_CORRUPT_BLOCKS)
660 goto read_block; /* assume broken cache file */
661
662 /* filesize may have become known in the meantime */
664 if (filesize < 0)
665 return (int) filesize;
666
667 /* We always need to read the entire block to verify integrity */
668 block_size = clamp_size(h, block_size, block_pos, filesize);
669 if (s->cache_data) {
670 av_assert1(block_pos + block_size <= s->cache_size);
671 tmp = s->cache_data + block_pos;
672 } else {
673 tmp = s->tmp_buf;
674 ret = read_cache(s, tmp, block_size, block_pos);
675 if (ret < 0) {
676 av_log(h, AV_LOG_ERROR, "Failed to read from cache file: %s\n", av_err2str(ret));
677 if (ret == AVERROR_EOF) { /* e.g. cache appears truncated? */
678 if (s->retry_corrupt) {
679 s->num_corrupt++;
680 goto read_block;
681 }
682 ret = AVERROR(EIO); /* don't propagate EOF to caller */
683 }
684 return ret;
685 }
686 }
687
688 uint32_t crc = get_block_crc(tmp, block_size);
689 if (crc != state) {
690 av_log(h, AV_LOG_ERROR, "Cache corruption detected for block 0x%"PRIx64" at "
691 "offset 0x%"PRIx64": expected CRC: 0x%08X, got: 0x%08X\n",
692 block_id, block_pos, state, crc);
693 if (s->retry_corrupt) {
694 s->num_corrupt++;
695 goto read_block;
696 }
697 return AVERROR(EIO);
698 } else
699 s->num_corrupt = 0; /* reset corrupt block count on success */
700
701 tmp += (ptrdiff_t) offset;
702 size = FFMIN(size, block_size - offset);
703 if (size <= 0)
704 return AVERROR_EOF;
705 if (s->verify) {
706 verify_read = 1;
707 break; /* fall through to the cache miss logic */
708 }
709
710 memcpy(buf, tmp, size);
711 s->nb_hit++;
712 s->pos += size;
713 return size;
714
715 case BLOCK_FAILED:
716 if (s->retry_errors)
717 goto read_block;
718 return AVERROR(EIO);
719
721 if (s->num_corrupt == MAX_CORRUPT_BLOCKS) {
722 av_log(h, AV_LOG_ERROR, "Too many consecutive corrupt blocks; "
723 "assuming cache file is completely broken.\n");
724 s->num_corrupt++; /* silence this log on subsequent reads */
725 }
727
728 case BLOCK_NONE:
729 if (s->read_only || s->write_err || !s->inner)
730 break; /* don't mark block as pending */
731 else if (s->cache_size_max) {
732 int64_t cached = atomic_load_explicit(&s->spacemap->blocks_cached,
734 if (cached >= s->blocks_max) {
735 av_log(h, AV_LOG_WARNING, "Cache size limit reached (%"PRId64" "
736 "blocks = %"PRId64" bytes), switching to read-only mode.\n",
737 s->blocks_max, s->blocks_max << s->block_shift);
738 s->read_only = 1;
739 break;
740 }
741 }
742
747 {
748 /* Acquired pending state, proceed to fetch the block */
749 acquired = 1;
750 allocated = (state == BLOCK_NONE || state == BLOCK_FAILED);
752 break;
753 }
754 /* CAS failed, another thread changed the state; reload it */
755 goto retry;
756
757 case BLOCK_PENDING:
758 /* Another thread is busy fetching this block, wait for it to finish */
759 if (!s->timeout) {
760 break; /* no timeout requested, immediately race to fetch block */
761 } else if (pending_since) {
763 if (new - pending_since >= s->timeout)
764 break; /* timeout expired, try to fetch the block ourselves */
765 } else {
766 pending_since = av_gettime_relative();
767 }
768
769 if (h->flags & AVIO_FLAG_NONBLOCK)
770 return AVERROR(EAGAIN);
771
772 /* Make sure we try a few times before giving up */
773 av_usleep(FFMIN(s->timeout >> 4, 10000));
774 if (ff_check_interrupt(&h->interrupt_callback))
775 return AVERROR_EXIT;
776
778 goto retry;
779 }
780
781 /* Release pending state on failure to avoid stalling other threads */
782#define RELEASE_PENDING(block, state) \
783 do { \
784 if (acquired) { \
785 av_assert1(state == BLOCK_PENDING); \
786 atomic_compare_exchange_strong_explicit( \
787 &block->state, &state, BLOCK_NONE, memory_order_relaxed, \
788 memory_order_relaxed); \
789 } \
790 } while (0)
791
792 /* Cache miss, fetch this block from underlying protocol */
793 s->nb_miss++;
794
795 if (!s->inner) {
796 av_log(h, AV_LOG_ERROR, "Cache miss for block 0x%"PRIx64" at offset "
797 "0x%"PRIx64", but underlying protocol is not available!\n",
798 block_id, block_pos);
799 av_assert0(!acquired);
800 return AVERROR(EIO);
801 }
802
803 const int read_only = s->read_only || s->write_err || verify_read;
804 int64_t inner_pos = read_only ? s->pos : block_pos;
805 if (s->inner_pos != inner_pos) {
806 inner_pos = ffurl_seek(s->inner, inner_pos, SEEK_SET);
807 if (inner_pos < 0) {
808 av_log(h, AV_LOG_ERROR, "Failed to seek underlying protocol: %s\n",
809 av_err2str(inner_pos));
811 return inner_pos;
812 }
813
814 av_log(h, AV_LOG_DEBUG, "Inner seek to 0x%"PRIx64"\n", inner_pos);
815 s->inner_pos = inner_pos;
816 }
817
818 if (read_only) {
819 /* Directly defer to the underlying protocol */
820 ret = ffurl_read(s->inner, buf, size);
821 if (ret < 0) {
822 av_assert1(!acquired);
823 return ret;
824 } else {
825 s->inner_pos = inner_pos + ret;
826 }
827
828 /* Verify the read data against the cached data if requested */
829 if (verify_read && memcmp(buf, tmp, ret)) {
830 av_log(h, AV_LOG_ERROR, "Cache verification failed for %d bytes "
831 "in block 0x%"PRIx64" at offset 0x%"PRIx64" + %"PRId64"!\n",
832 ret, block_id, block_pos, offset);
833 return AVERROR(EIO);
834 }
835
836 s->pos = s->inner_pos;
837 return ret;
838 }
839
840 int write_back = 1;
841 if (s->cache_data && acquired) {
842 /* Read directly into memory mapped cache file */
843 tmp = s->cache_data + block_pos;
844 write_back = 0;
845 } else if (size >= block_size && !offset) {
846 /* Read directly into output buffer if aligned and large enough */
847 tmp = buf;
848 } else {
849 /* Read into temporary buffer and copy later */
850 tmp = s->tmp_buf;
851 }
852
853 /* Try and fetch the entire block */
854 av_assert0(inner_pos == block_pos);
855 int bytes_read = 0;
856 while (bytes_read < block_size) {
857 ret = ffurl_read(s->inner, &tmp[bytes_read], block_size - bytes_read);
858 if (!ret || ret == AVERROR_EOF)
859 break;
860 else if (ret < 0) {
861 av_log(h, AV_LOG_ERROR, "Failed to read block 0x%"PRIx64": %s\n",
862 block_id, av_err2str(ret));
863 if (ret == AVERROR(EAGAIN) || ret == AVERROR_EXIT) {
865 return ret; /* transient error, allow retries */
866 }
867
868 /* Try to mark block as failed; ignore errors - any mismatch
869 * here will mean that either another thread already marked it
870 * as failed, or successfully cached it in the meantime */
875 return ret;
876 }
877
878 bytes_read += ret;
879 s->inner_pos += ret;
880 }
881
882 if (bytes_read < block_size) {
883 /* Learned location of true EOF, update filesize */
884 ret = set_filesize(h, inner_pos + bytes_read);
885 if (ret < 0) {
887 return ret;
888 }
889 }
890
891 if (bytes_read > 0) {
892 ret = write_back ? write_cache(s, tmp, bytes_read, block_pos) : 0;
893 if (ret < 0) {
894 if (ret != AVERROR(EINTR)) {
895 av_log(h, AV_LOG_ERROR, "Failed to write to cache file: %s\n",
896 av_err2str(ret));
897 s->write_err = 1;
898 }
900 } else {
901 uint32_t crc = get_block_crc(tmp, bytes_read);
902 av_log(h, AV_LOG_TRACE, "Cached %d bytes to block 0x%"PRIx64" at "
903 "offset 0x%"PRIx64", CRC 0x%08X\n", bytes_read, block_id,
904 block_pos, crc);
906 if (allocated)
907 atomic_fetch_add_explicit(&s->spacemap->blocks_cached, 1, memory_order_release);
908 }
909 } else {
911 return AVERROR_EOF;
912 }
913
914 size = FFMIN(bytes_read - offset, size);
915 if (size <= 0)
916 return AVERROR_EOF;
917 if (tmp != buf)
918 memcpy(buf, &tmp[offset], size);
919 s->pos += size;
920 return size;
921}
922
924{
925 SharedContext *s = h->priv_data;
926 int64_t res;
927 if (!s->spacemap)
928 return AVERROR(EIO);
929
931 if (filesize < 0)
932 return filesize;
933
934 switch (whence) {
935 case AVSEEK_SIZE:
936 if (filesize)
937 return filesize;
938 res = s->inner ? ffurl_seek(s->inner, pos, whence) : AVERROR(ENOSYS);
939 if (res > 0) {
940 if (set_filesize(h, res) < 0)
941 return AVERROR(EINVAL);
942 } else if (is_ignorable_error(res) && s->ignore_errors) {
943 av_log(h, AV_LOG_WARNING, "Underlying URL failed to get size: %s. "
944 "Continuing with cache file only.\n", av_err2str(res));
945 ffurl_closep(&s->inner);
946 res = AVERROR(ENOSYS);
947 }
948 return res;
949 case SEEK_SET:
950 break;
951 case SEEK_CUR:
952 pos += s->pos;
953 break;
954 case SEEK_END:
955 if (filesize) {
956 pos += filesize;
957 break;
958 }
959
960 /* Defer to underlying protocol if filesize is unknown */
961 res = s->inner ? ffurl_seek(s->inner, pos, whence) : AVERROR(ENOSYS);
962 if (is_ignorable_error(res) && s->ignore_errors) {
963 av_log(h, AV_LOG_WARNING, "Underlying URL failed to seek: %s. "
964 "Continuing with cache file only.\n", av_err2str(res));
965 ffurl_closep(&s->inner);
966 return AVERROR(ENOSYS);
967 } else if (res < 0)
968 return res;
969
970 /* Opportunistically update known filesize */
971 if (set_filesize(h, res - pos) < 0)
972 return AVERROR(EINVAL);
973 av_log(h, AV_LOG_DEBUG, "Inner seek to 0x%"PRIx64"\n", res);
974 return s->pos = s->inner_pos = res;
975 default:
976 return AVERROR(EINVAL);
977 }
978
979 if (pos < 0)
980 return AVERROR(EINVAL);
981
982 av_log(h, AV_LOG_DEBUG, "Virtual seek to 0x%"PRIx64"\n", pos);
983 return s->pos = pos;
984}
985
987{
988 SharedContext *s = h->priv_data;
989 return s->inner ? ffurl_get_file_handle(s->inner) : -1;
990}
991
993{
994 SharedContext *s = h->priv_data;
995 int ret = s->inner ? ffurl_get_short_seek(s->inner) : 0;
996 return ret > 0 ? FFMAX(ret, s->block_size) : s->block_size;
997}
998
999#define OFFSET(x) offsetof(SharedContext, x)
1000#define D AV_OPT_FLAG_DECODING_PARAM
1001
1002static const AVOption options[] = {
1003 { "cache_dir", "Directory path for shared file cache", OFFSET(cache_dir), AV_OPT_TYPE_STRING, {.str = NULL}, .flags = D },
1004 { "metadata_dir", "Directory path for shared file cache metadata", OFFSET(metadata_dir), AV_OPT_TYPE_STRING, {.str = NULL}, .flags = D },
1005 { "block_shift", "Set the base 2 logarithm of the block size", OFFSET(block_shift), AV_OPT_TYPE_INT, {.i64 = 15}, 9, 30, .flags = D },
1006 { "read_only", "Don't write data to the cache, only read from it", OFFSET(read_only), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, .flags = D },
1007 { "cache_verify", "Verify correctness of the cache against the source", OFFSET(verify), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, .flags = D },
1008 { "cache_timeout", "Time in us to wait before re-fetching pending blocks", OFFSET(timeout), AV_OPT_TYPE_INT64, {.i64 = 10000}, 0, INT64_MAX, .flags = D },
1009 { "ignore_errors", "Continue even if the inner URL failed", OFFSET(ignore_errors), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, .flags = D },
1010 { "retry_errors", "Re-request blocks even if they previously failed", OFFSET(retry_errors), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, .flags = D },
1011 { "retry_corrupt", "Re-request blocks that fail the CRC check", OFFSET(retry_corrupt), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, .flags = D },
1012 { "cache_size_max", "Limit the maximum amount of data cached", OFFSET(cache_size_max), AV_OPT_TYPE_INT64, {.i64 = 0}, 0, INT64_MAX, .flags = D },
1013 {0},
1014};
1015
1017 .class_name = "shared",
1018 .item_name = av_default_item_name,
1019 .option = options,
1020 .version = LIBAVUTIL_VERSION_INT,
1021};
1022
1024 .name = "shared",
1025 .url_open2 = shared_open,
1026 .url_read = shared_read,
1027 .url_seek = shared_seek,
1028 .url_close = shared_close,
1029 .url_get_file_handle = shared_get_file_handle,
1030 .url_get_short_seek = shared_get_short_seek,
1031 .priv_data_size = sizeof(SharedContext),
1032 .priv_data_class = &shared_context_class,
1033};
static int read_block(ALSDecContext *ctx, ALSBlockData *bd)
Read the block data.
Definition alsdec.c:1031
static uint8_t hash[HASH_SIZE]
static AVFormatContext * ctx
static av_cold void close(AVCodecParserContext *s)
Definition apv_parser.c:197
simple assert() macros that are a bit more flexible than ISO C assert().
#define av_assert1(cond)
assert() equivalent, that does not lie in speed critical code.
Definition avassert.h:58
#define av_assert0(cond)
assert() equivalent, that is always enabled.
Definition avassert.h:42
#define D
Definition avdct.c:35
int ff_check_interrupt(AVIOInterruptCB *cb)
Check if the user has requested to interrupt a blocking function associated with cb.
Definition avio.c:929
int ffurl_open_whitelist(URLContext **puc, const char *filename, int flags, const AVIOInterruptCB *int_cb, AVDictionary **options, const char *whitelist, const char *blacklist, URLContext *parent)
Create an URLContext for accessing to the resource indicated by url, and open it.
Definition avio.c:466
int64_t ffurl_size(URLContext *h)
Return the filesize of the resource accessed by h, AVERROR(ENOSYS) if the operation is not supported ...
Definition avio.c:874
int ffurl_closep(URLContext **hh)
Close the resource accessed by the URLContext h, and free the memory used by it.
Definition avio.c:663
int ffurl_close(URLContext *h)
Definition avio.c:686
int ffurl_get_short_seek(void *urlcontext)
Return the current short seek threshold value for this URL.
Definition avio.c:913
int ffurl_get_file_handle(URLContext *h)
Return the file descriptor associated with this URL.
Definition avio.c:889
#define AVSEEK_SIZE
Passing this as the "whence" parameter to a seek function causes it to return the filesize without se...
Definition avio.h:468
#define AVIO_FLAG_NONBLOCK
Use non-blocking mode.
Definition avio.h:636
char * av_asprintf(const char *fmt,...)
Definition avstring.c:115
#define flags(name, subs,...)
Definition cbs_h264.c:74
#define i(width, name, range_min, range_max)
Definition cbs_h264.c:63
#define s(width, name)
Definition cbs_vp9.c:198
#define NULL
Definition coverity.c:32
long long int64_t
Definition coverity.c:34
Public header for CRC hash function implementation.
static int16_t block[64]
Definition dct.c:125
error code definitions
static struct @346255127015250356166251341105367306144006377143 state
static int64_t filesize(AVIOContext *pb)
Definition ffmpeg_mux.c:52
Misc file utilities.
#define fail
Definition test.h:479
@ AV_OPT_TYPE_INT64
Underlying C type is int64_t.
Definition opt.h:262
@ AV_OPT_TYPE_INT
Underlying C type is int.
Definition opt.h:258
@ AV_OPT_TYPE_BOOL
Underlying C type is int.
Definition opt.h:326
@ AV_OPT_TYPE_STRING
Underlying C type is a uint8_t* that is either NULL or points to a C string allocated with the av_mal...
Definition opt.h:275
const AVCRC * av_crc_get_table(AVCRCId crc_id)
Get an initialized standard CRC table.
Definition crc.c:389
uint32_t av_crc(const AVCRC *ctx, uint32_t crc, const uint8_t *buffer, size_t length)
Calculate the CRC of a block.
Definition crc.c:421
@ AV_CRC_32_IEEE
Definition crc.h:52
#define AVERROR_EXIT
Immediate exit was requested; the called function should not be restarted.
Definition error.h:58
#define AVERROR_BUG
Internal bug, also see AVERROR_BUG2.
Definition error.h:52
#define AVERROR_EOF
End of file.
Definition error.h:57
#define av_err2str(errnum)
Convenience macro, the return value should be used only directly in function arguments but never stan...
Definition error.h:122
#define AVERROR(e)
Definition error.h:45
void av_hash_freep(AVHashContext **ctx)
Free hash context and set hash context pointer to NULL.
Definition hash.c:248
void av_hash_init(AVHashContext *ctx)
Initialize or reset a hash context.
Definition hash.c:151
void av_hash_update(AVHashContext *ctx, const uint8_t *src, size_t len)
Update a hash context with additional data.
Definition hash.c:172
int av_hash_alloc(AVHashContext **ctx, const char *name)
Allocate a hash context for the algorithm specified by name.
Definition hash.c:114
void av_hash_final(AVHashContext *ctx, uint8_t *dst)
Finalize a hash context and compute the actual hash value.
Definition hash.c:193
#define AV_LOG_TRACE
Extremely verbose debugging, useful for libav* development.
Definition log.h:236
#define AV_LOG_DEBUG
Stuff which is only useful for libav* developers.
Definition log.h:231
#define AV_LOG_WARNING
Something somehow does not look correct.
Definition log.h:216
#define AV_LOG_VERBOSE
Detailed information.
Definition log.h:226
#define AV_LOG_ERROR
Something went wrong and cannot losslessly be recovered.
Definition log.h:210
const char * av_default_item_name(void *ptr)
Return the context name.
Definition log.c:241
int av_strstart(const char *str, const char *pfx, const char **ptr)
Return non-zero if pfx is a prefix of str.
Definition avstring.c:36
#define LIBAVUTIL_VERSION_INT
Definition version.h:85
int av_hash_get_size(const AVHashContext *ctx)
Definition hash.c:109
Generic hashing API.
const VDPAUPixFmtMap * map
unsigned offset
Definition libaomenc.c:763
static int shift(int a, int b)
Definition bonk.c:261
const char * arg
Definition jacosubdec.c:65
char * ff_data_to_hex(char *buf, const uint8_t *src, int size, int lowercase)
Write hexadecimal string corresponding to given binary data.
Definition utils.c:473
Macro definitions for various function/variable attributes.
#define av_fallthrough
Definition attributes.h:67
int av_file_map_shared(int fd, size_t size, void **bufptr)
Map the beginning of an open file into memory for shared read and write access.
Definition file.c:186
void av_file_unmap_shared(void *bufptr, size_t size)
Unmap the memory mapped by av_file_map_shared().
Definition file.c:198
int avpriv_open(const char *filename, int flags,...)
A wrapper for open() setting O_CLOEXEC.
Definition file_open.c:67
version
Definition libkvazaar.c:313
#define FFMIN(a, b)
Definition macros.h:49
#define FFMAX(a, b)
Definition macros.h:47
#define FFALIGN(x, a)
Definition macros.h:78
Memory handling functions.
#define av_malloc(s)
Definition ops_static.c:52
AVOptions.
miscellaneous OS support macros and functions.
#define O_BINARY
Definition os_support.h:36
const URLProtocol ff_shared_protocol
Definition shared.c:1023
static int shared_read(URLContext *h, unsigned char *buf, int size)
Definition shared.c:624
static int read_cache(SharedContext *s, uint8_t *buf, size_t size, off_t offset)
Definition shared.c:574
static int spacemap_grow(URLContext *h, int64_t block)
Definition shared.c:483
static int is_ignorable_error(int64_t err)
Definition shared.c:265
#define HEADER_VERSION
Definition shared.c:70
#define DEF_SET_ONCE(ctype, atype)
Definition shared.c:142
static int set_filesize(URLContext *h, int64_t new_size)
Definition shared.c:242
static int shared_get_short_seek(URLContext *h)
Definition shared.c:992
#define HASH_METHOD
This hash should be resistant against collision attacks, so that an attacker could not generate e....
Definition shared.c:67
static int shared_open(URLContext *h, const char *arg, int flags, AVDictionary **options)
Definition shared.c:280
static int64_t shared_seek(URLContext *h, int64_t pos, int whence)
Definition shared.c:923
#define RELEASE_PENDING(block, state)
static int shared_close(URLContext *h)
Definition shared.c:204
static int shared_get_file_handle(URLContext *h)
Definition shared.c:986
static int64_t get_filesize(URLContext *h)
Definition shared.c:228
static int hash_uri(uint8_t hash[HASH_SIZE], const char *uri)
Definition shared.c:78
static int cache_map(URLContext *h, int64_t filesize)
Definition shared.c:391
static int write_cache(SharedContext *s, const uint8_t *buf, size_t size, off_t offset)
Definition shared.c:594
static const AVClass shared_context_class
Definition shared.c:1016
static int spacemap_remap(URLContext *h, size_t map_size)
Definition shared.c:416
#define MAX_CORRUPT_BLOCKS
Hard watershed of consecutive failed blocks before we give up on the cache file altogether and assume...
Definition shared.c:76
static uint32_t get_block_crc(const uint8_t *block, size_t block_size)
Definition shared.c:110
#define OFFSET(x)
Definition shared.c:999
static int spacemap_init(URLContext *h, const uint8_t hash[HASH_SIZE])
Definition shared.c:519
#define HASH_SIZE
Definition shared.c:68
BlockState
Definition shared.c:98
@ BLOCK_NONE
block is not cached
Definition shared.c:100
@ BLOCK_PENDING
a thread is currently trying to write this block
Definition shared.c:101
@ BLOCK_FAILED
the underlying I/O source failed to read this block
Definition shared.c:102
#define HEADER_MAGIC
Definition shared.c:69
static int clamp_size(URLContext *h, int size, int64_t pos, int64_t filesize)
Definition shared.c:614
unsigned int pos
Definition spdifenc.c:431
@ memory_order_release
Definition stdatomic.h:32
@ memory_order_relaxed
Definition stdatomic.h:29
@ memory_order_acquire
Definition stdatomic.h:31
#define atomic_fetch_add_explicit(object, operand, order)
Definition stdatomic.h:297
unsigned char atomic_uchar
Definition stdatomic.h:60
FF_ATOMIC_ALIGN64 unsigned long long atomic_ullong
Definition stdatomic.h:68
#define atomic_compare_exchange_strong_explicit(object, expected, desired, success, failure)
Definition stdatomic.h:274
unsigned int atomic_uint
Definition stdatomic.h:64
#define atomic_load_explicit(object, order)
Definition stdatomic.h:247
unsigned short atomic_ushort
Definition stdatomic.h:62
#define atomic_load(object)
Definition stdatomic.h:250
#define atomic_store_explicit(object, desired, order)
Definition stdatomic.h:253
Describe the class of an AVClass context structure.
Definition log.h:76
uint32_t crc
Definition hash.c:70
AVOption.
Definition opt.h:428
atomic_uint state
Definition shared.c:124
char * cache_dir
Definition shared.c:167
char * metadata_dir
Definition shared.c:168
int write_err
write error occurred
Definition shared.c:182
int block_size
Definition shared.c:181
int64_t nb_hit
Definition shared.c:200
int block_shift
requested shift; updated on init if it disagrees
Definition shared.c:169
int64_t filesize
once known
Definition shared.c:184
int64_t nb_miss
Definition shared.c:201
Spacemap * spacemap
Definition shared.c:194
uint8_t * tmp_buf
Definition shared.c:180
int64_t timeout
Definition shared.c:171
URLContext * inner
Definition shared.c:163
int retry_errors
Definition shared.c:173
uint8_t * cache_data
optional mapping of the cache file
Definition shared.c:188
int64_t inner_pos
Definition shared.c:164
int num_corrupt
Definition shared.c:183
int ignore_errors
Definition shared.c:172
int64_t blocks_max
maximum number of blocks to cache
Definition shared.c:185
int64_t pos
current logical position
Definition shared.c:179
off_t cache_size
size of mapped memory region (for unmapping)
Definition shared.c:190
int64_t cache_size_max
Definition shared.c:176
int read_only
Definition shared.c:170
off_t map_size
Definition shared.c:196
char * cache_path
Definition shared.c:189
char * map_path
Definition shared.c:195
int retry_corrupt
Definition shared.c:174
atomic_uint header_magic
Definition shared.c:128
atomic_ushort version
Definition shared.c:129
Block blocks[]
Definition shared.c:136
atomic_uchar hash[HASH_SIZE]
Definition shared.c:132
atomic_ushort block_shift
Definition shared.c:130
char reserved[72]
Definition shared.c:134
atomic_ullong blocks_cached
Definition shared.c:133
atomic_ullong filesize
Definition shared.c:131
Definition swscale.c:71
#define av_freep(p)
#define av_log(a,...)
static uint8_t tmp[40]
Definition aes_ctr.c:52
int av_usleep(unsigned usec)
Sleep for a period of time.
Definition time.c:93
int64_t av_gettime_relative(void)
Get the current time in microseconds since some unspecified starting point.
Definition time.c:57
int size
unbuffered private I/O API
static int64_t ffurl_seek(URLContext *h, int64_t pos, int whence)
Change the position that will be used by the next read/write operation on the resource accessed by h.
Definition url.h:225
static int ffurl_read(URLContext *h, uint8_t *buf, int size)
Read up to size bytes from the resource accessed by h, and store the read bytes in buf.
Definition url.h:184