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