FFmpeg
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 
24 #include "libavutil/attributes.h"
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 "url.h"
36 
37 #include <errno.h>
38 #include <fcntl.h>
39 #include <inttypes.h>
40 #include <stdatomic.h>
41 #include <string.h>
42 #include <sys/file.h>
43 #include <sys/mman.h>
44 #include <sys/stat.h>
45 #include <unistd.h>
46 
47 /**
48  * This hash should be resistant against collision attacks, so that an
49  * attacker could not generate e.g. two different URIs that map to the same
50  * cache file. This requires at least 64 bits of collision resistance in
51  * practice (i.e. 128 bits = 16 bytes of hash size). However, we can be
52  * conservative by computing e.g. a 256 bit hash and storing it inside the
53  * file header for verification.
54  *
55  * Note that due to the way we use atomics, we should avoid zero bytes in
56  * the resulting hash; hence we tweak the input slightly to avoid this.
57  * The resulting loss in hash strength is negligible, since 32 bytes is
58  * already much more than needed.
59  */
60 #define HASH_METHOD "SHA512/256"
61 #define HASH_SIZE 32
62 #define HEADER_MAGIC MKTAG(u'\xFF', 'S', 'h', '$')
63 #define HEADER_VERSION 3
64 
65 static int hash_uri(uint8_t hash[HASH_SIZE], const char *uri)
66 {
67  struct AVHashContext *ctx = NULL;
69  if (ret < 0)
70  return ret;
71 
72  const int16_t version = HEADER_VERSION;
75  av_hash_update(ctx, (const uint8_t *) &version, sizeof(version));
76  av_hash_update(ctx, (const uint8_t *) uri, strlen(uri));
79 
80  for (int i = 0; i < HASH_SIZE; i++)
81  hash[i] = hash[i] ? hash[i] : ~hash[i]; /* prevent zero bytes */
82  return 0;
83 }
84 
85 enum BlockState {
86  /* Reserved block state values */
87  BLOCK_NONE = 0, ///< block is not cached
88  BLOCK_PENDING, ///< a thread is currently trying to write this block
89  BLOCK_FAILED, ///< the underlying I/O source failed to read this block
90 
91  /**
92  * All other block states represent valid cached blocks, with the value
93  * being the CRC of the block data.
94  */
95 };
96 
97 static uint32_t get_block_crc(const uint8_t *block, size_t block_size)
98 {
99  uint32_t crc = av_crc(av_crc_get_table(AV_CRC_32_IEEE), 0, block, block_size);
100  switch (crc) {
101  case BLOCK_NONE:
102  case BLOCK_FAILED:
103  case BLOCK_PENDING:
104  return ~crc; /* avoid reserved block states */
105  default:
106  return crc;
107  }
108 }
109 
110 typedef struct Block {
111  atomic_uint state; /* enum BlockState */
112 } Block;
113 
114 typedef struct Spacemap {
118  atomic_ullong filesize; /* byte offset of true EOF, or 0 if unknown */
119  atomic_uchar hash[HASH_SIZE]; /* hash of resource URI / filename */
120  char reserved[80];
121 
123 } Spacemap;
124 
125 /* Set to value iff the current value is unset (zero) */
126 #define DEF_SET_ONCE(ctype, atype) \
127  static int set_once_##atype(atomic_##atype *const ptr, const ctype value) \
128  { \
129  ctype prev = 0; \
130  av_assert1(value != 0); \
131  if (atomic_compare_exchange_strong_explicit( \
132  ptr, &prev, value, memory_order_release, memory_order_relaxed)) \
133  return 1; \
134  else if (prev == value) \
135  return 0; \
136  else \
137  return AVERROR(EINVAL); \
138  }
139 
140 DEF_SET_ONCE(unsigned char, uchar)
141 DEF_SET_ONCE(unsigned int, uint)
142 DEF_SET_ONCE(unsigned short, ushort)
143 DEF_SET_ONCE(unsigned long long, ullong)
144 
145 typedef struct SharedContext {
146  AVClass *class;
149 
150  /* options */
151  char *cache_dir;
152  int block_shift; ///< requested shift; may disagree with actual
157  int verify;
158 
159  /* misc state */
160  int64_t pos; ///< current logical position
161  uint8_t *tmp_buf;
163  int write_err; ///< write error occurred
164 
165  /* cache file */
166  uint8_t *cache_data; ///< optional mmap of the cache file
167  char *cache_path;
168  off_t cache_size; ///< size of mapped memory region (for munmap)
169  int fd;
170 
171  /* space map */
173  char *map_path;
174  off_t map_size;
175  int mapfd;
176 
177  /* statistics */
180 } SharedContext;
181 
183 {
184  SharedContext *s = h->priv_data;
185 
186  ffurl_close(s->inner);
187  if (s->cache_data)
188  munmap(s->cache_data, s->cache_size);
189  if (s->spacemap)
190  munmap(s->spacemap, s->map_size);
191  if (s->fd != -1)
192  close(s->fd);
193  if (s->mapfd != -1)
194  close(s->mapfd);
195  av_freep(&s->cache_path);
196  av_freep(&s->map_path);
197  av_freep(&s->tmp_buf);
198 
199  av_log(h, AV_LOG_DEBUG, "Cache statistics: %"PRId64" hits, %"PRId64" misses\n",
200  s->nb_hit, s->nb_miss);
201  return 0;
202 }
203 
204 static int cache_map(URLContext *h, int64_t filesize);
205 static int spacemap_init(URLContext *h, const uint8_t hash[HASH_SIZE]);
206 static int spacemap_grow(URLContext *h, int64_t block);
207 
209 {
210  SharedContext *s = h->priv_data;
211  return atomic_load_explicit(&s->spacemap->filesize, memory_order_relaxed);
212 }
213 
214 static int set_filesize(URLContext *h, int64_t new_size)
215 {
216  SharedContext *s = h->priv_data;
217  int ret;
218 
219  if (!new_size)
220  return 0;
221 
222  ret = set_once_ullong(&s->spacemap->filesize, new_size);
223  if (ret < 0) {
224  av_log(h, AV_LOG_ERROR, "Cached file size mismatch, expected: "
225  "%"PRId64", got: %"PRIu64"!\n", new_size,
226  (uint64_t) atomic_load(&s->spacemap->filesize));
227  return ret;
228  } else if (ret) {
229  /* Opportunistically map the file; this also sets the correct filesize.
230  * Ignore errors as this is not critical to the cache logic. */
231  cache_map(h, new_size);
232  }
233 
234  return ret;
235 }
236 
237 static int shared_open(URLContext *h, const char *arg, int flags, AVDictionary **options)
238 {
239  SharedContext *s = h->priv_data;
240  int ret;
241 
242  if (!s->cache_dir || !s->cache_dir[0]) {
243  av_log(h, AV_LOG_ERROR, "Missing path for shared cache! Specify a "
244  "directory using the -cache_dir option.\n");
245  return AVERROR(EINVAL);
246  }
247 
248  s->fd = s->mapfd = -1; /* Set these early for shared_close() failure path */
249 
250  /* Open underlying protocol */
251  av_strstart(arg, "shared:", &arg);
252  ret = ffurl_open_whitelist(&s->inner, arg, flags, &h->interrupt_callback,
253  options, h->protocol_whitelist, h->protocol_blacklist, h);
254 
255  if (ret < 0)
256  goto fail;
257 
258  uint8_t hash[HASH_SIZE];
259  ret = hash_uri(hash, arg);
260  if (ret < 0)
261  goto fail;
262 
263  /* 128 bits is enough for collision resistance; we already store the full
264  * hash inside the header for verification */
265  char filename[2 * 16 + 1];
266  for (int i = 0; i < FF_ARRAY_ELEMS(filename) / 2; i++)
267  sprintf(&filename[i * 2], "%02X", hash[i]);
268  s->cache_path = av_asprintf("%s/%s.cache", s->cache_dir, filename);
269  s->map_path = av_asprintf("%s/%s.spacemap", s->cache_dir, filename);
270  if (!s->cache_path || !s->map_path) {
271  ret = AVERROR(ENOMEM);
272  goto fail;
273  }
274 
275  av_log(h, AV_LOG_VERBOSE, "Opening cache file '%s' for URI: '%s'\n",
276  s->cache_path, s->inner->filename);
277 
278  s->fd = avpriv_open(s->cache_path, O_RDWR | O_CREAT, 0660);
279  s->mapfd = avpriv_open(s->map_path, O_RDWR | O_CREAT, 0660);
280  if (s->fd < 0 || s->mapfd < 0) {
281  ret = AVERROR(errno);
282  av_log(h, AV_LOG_ERROR, "Failed to open '%s': %s\n",
283  s->fd < 0 ? s->cache_path : s->map_path, av_err2str(ret));
284  goto fail;
285  }
286 
287  ret = spacemap_init(h, hash);
288  if (ret < 0)
289  goto fail;
290 
291  s->block_size = 1 << atomic_load(&s->spacemap->block_shift);
292 
294  if (!filesize) {
295  /* Filesize is not yet known, try to get it from the underlying URL */
296  filesize = ffurl_size(s->inner);
297  if (filesize < 0 && filesize != AVERROR(ENOSYS)) {
298  ret = (int) filesize;
299  goto fail;
300  } else if (filesize > 0) {
302  if (ret < 0)
303  goto fail;
304  }
305  }
306 
307  if (filesize > 0) {
308  int64_t last_pos = filesize - 1;
309  int64_t last_block = last_pos >> atomic_load(&s->spacemap->block_shift);
310  ret = spacemap_grow(h, last_block);
311  if (ret < 0)
312  goto fail;
313 
314  /* If filesize is known, we can directly mmap() the cache file */
315  ret = cache_map(h, filesize);
316  if (ret < 0) {
317  av_log(h, AV_LOG_WARNING, "Failed to map cache file: %s. Falling "
318  "back to normal read/write\n", av_err2str(ret));
319  ret = 0;
320  }
321  }
322 
323  /* Temporary buffer needed for pread/pwrite() fallback */
324  s->tmp_buf = av_malloc(s->block_size);
325  if (!s->tmp_buf) {
326  ret = AVERROR(ENOMEM);
327  goto fail;
328  }
329 
330  h->max_packet_size = s->block_size;
331  h->min_packet_size = s->block_size;
332  ret = 0;
333 
334 fail:
335  if (ret < 0)
336  shared_close(h);
337  return ret;
338 }
339 
341 {
342  SharedContext *s = h->priv_data;
343  if (s->cache_size >= filesize || filesize > SIZE_MAX)
344  return 0;
345 
346  if (s->cache_data) {
347  munmap(s->cache_data, s->cache_size);
348  s->cache_data = NULL;
349  s->cache_size = 0;
350  }
351 
352  struct stat st;
353  int ret = fstat(s->fd, &st);
354  if (ret < 0)
355  return AVERROR(errno);
356 
357  if (st.st_size != filesize) {
358  /* Ensure the file size is correct before mapping; this can happen if
359  * another process wrote the correct filesize to the header but
360  * crashed right before actually successfully resizing the file. */
361  ret = ftruncate(s->fd, filesize);
362  if (ret < 0)
363  return AVERROR(errno);
364  }
365 
366  s->cache_data = mmap(NULL, filesize, PROT_READ | PROT_WRITE, MAP_SHARED, s->fd, 0);
367  if (s->cache_data == MAP_FAILED) {
368  s->cache_data = NULL;
369  return AVERROR(errno);
370  }
371 
372  s->cache_size = filesize;
373  return 0;
374 }
375 
376 static int spacemap_remap(URLContext *h, size_t map_size)
377 {
378  SharedContext *s = h->priv_data;
379  int ret, did_grow = 0, locked = 0;
380  if (map_size <= s->map_size)
381  return 0;
382 
383  /* Opportunistically get current filesize before attempting to lock */
384  struct stat st;
385  ret = fstat(s->mapfd, &st);
386  if (ret < 0) {
387  ret = AVERROR(errno);
388  goto fail;
389  }
390 
391  if (st.st_size >= map_size)
392  goto skip_resize;
393 
394  /* Lock the spacemap to ensure nobody else is currently resizing it */
395  ret = flock(s->mapfd, LOCK_EX);
396  if (ret < 0) {
397  ret = AVERROR(errno);
398  goto fail;
399  }
400  locked = 1;
401 
402  /* Refresh filesize after acquiring the lock */
403  ret = fstat(s->mapfd, &st);
404  if (ret < 0) {
405  ret = AVERROR(errno);
406  goto fail;
407  }
408 
409  if (st.st_size >= map_size)
410  goto skip_resize;
411 
412  ret = ftruncate(s->mapfd, map_size);
413  if (ret < 0) {
414  ret = AVERROR(errno);
415  goto fail;
416  }
417  st.st_size = map_size;
418  did_grow = 1;
419 
420 skip_resize:
421  if (s->spacemap)
422  munmap(s->spacemap, s->map_size);
423  s->map_size = st.st_size;
424  s->spacemap = mmap(NULL, s->map_size, PROT_READ | PROT_WRITE, MAP_SHARED, s->mapfd, 0);
425  if (s->spacemap == MAP_FAILED) {
426  s->spacemap = NULL; /* for munmap check */
427  s->map_size = 0;
428  ret = AVERROR(errno);
429  goto fail;
430  }
431 
432  if (locked) {
433  flock(s->mapfd, LOCK_UN);
434  locked = 0;
435  }
436 
437  return did_grow;
438 
439 fail:
440  if (locked)
441  flock(s->mapfd, LOCK_UN);
442  av_log(h, AV_LOG_ERROR, "Failed to resize space map: %s\n", av_err2str(ret));
443  return ret;
444 }
445 
447 {
448  SharedContext *s = h->priv_data;
449  int64_t num_blocks = block + 1;
450  size_t map_bytes = sizeof(Spacemap) + num_blocks * sizeof(Block);
451 
452  /* When streaming files without known size, round up the number of blocks
453  * to the nearest multiple of the block size to reduce the rate of resizes */
454  if (!get_filesize(h)) {
455  av_assert0(s->block_size > 0);
456  map_bytes = FFALIGN(map_bytes, (int64_t) s->block_size);
457  }
458 
459  if (map_bytes < num_blocks)
460  return AVERROR(EINVAL); /* overflow */
461 
462  const off_t old_size = s->map_size;
463  int ret = spacemap_remap(h, map_bytes);
464  if (ret < 0)
465  return ret;
466 
467  /* Report new size after successful grow */
468  if (s->map_size > old_size) {
469  num_blocks = (s->map_size - sizeof(Spacemap)) / sizeof(Block);
471  "%s %zu bytes, capacity: %"PRId64" blocks = %zu MB\n",
472  ret ? "Resized spacemap to" : "Mapped spacemap with",
473  (size_t) s->map_size, num_blocks,
474  (num_blocks * (int64_t) s->block_size) >> 20);
475  }
476  return 0;
477 }
478 
479 static int spacemap_init(URLContext *h, const uint8_t hash[HASH_SIZE])
480 {
481  SharedContext *s = h->priv_data;
482  int ret;
483 
484  ret = spacemap_remap(h, sizeof(Spacemap));
485  if (ret < 0)
486  return ret;
487 
488  if ((ret = set_once_uint(&s->spacemap->header_magic, HEADER_MAGIC)) < 0 ||
489  (ret = set_once_ushort(&s->spacemap->version, HEADER_VERSION)) < 0)
490  {
491  av_log(h, AV_LOG_ERROR, "Shared cache spacemap header mismatch!\n");
492  av_log(h, AV_LOG_ERROR, " Expected magic: 0x%X, version: %d\n",
494  av_log(h, AV_LOG_ERROR, " Got magic: 0x%X, version: %d\n",
495  atomic_load(&s->spacemap->header_magic),
496  atomic_load(&s->spacemap->version));
497  return ret;
498  }
499 
500  ret = set_once_ushort(&s->spacemap->block_shift, s->block_shift);
501  if (ret < 0) {
502  const int shift = atomic_load(&s->spacemap->block_shift);
503  av_log(h, AV_LOG_WARNING, "Shared cache uses block shift %d, "
504  "but requested block shift is %d.\n", shift, s->block_shift);
505  if (shift < 9 || shift > 30) {
506  av_log(h, AV_LOG_ERROR, "Invalid block shift %d in cache file!\n", shift);
507  return AVERROR(EINVAL);
508  }
509  }
510 
511  for (int i = 0; i < HASH_SIZE; i++) {
512  ret = set_once_uchar(&s->spacemap->hash[i], hash[i]);
513  if (ret < 0) {
514  av_log(h, AV_LOG_ERROR, "Shared cache spacemap hash mismatch!\n");
515  av_log(h, AV_LOG_ERROR, " Expected hash: ");
516  for (int j = 0; j < 32; j++)
517  av_log(h, AV_LOG_ERROR, "%02X", hash[j]);
518  av_log(h, AV_LOG_ERROR, "\n Got hash: ");
519  for (int j = 0; j < 32; j++)
520  av_log(h, AV_LOG_ERROR, "%02X", atomic_load(&s->spacemap->hash[j]));
521  av_log(h, AV_LOG_ERROR, "\n");
522  return ret;
523  }
524  }
525 
526  if (ret) /* set_once() return 1 if this is the first time setting the value */
527  av_log(h, AV_LOG_DEBUG, "Initialized new cache spacemap.\n");
528 
529  return ret;
530 }
531 
532 static int read_cache(SharedContext *s, uint8_t *buf, size_t size, off_t offset)
533 {
534  if (s->cache_data) {
535  av_assert1(offset + size <= s->cache_size);
536  memcpy(buf, s->cache_data + offset, size);
537  return 0;
538  }
539 
540  while (size) {
541  ssize_t ret = pread(s->fd, buf, size, offset);
542  if (ret <= 0)
543  return ret ? AVERROR(errno) : AVERROR(EIO);
544  buf += ret;
545  offset += ret;
546  size -= ret;
547  }
548 
549  return 0;
550 }
551 
552 static int write_cache(SharedContext *s, const uint8_t *buf, size_t size, off_t offset)
553 {
554  if (s->cache_data) {
555  av_assert1(offset + size <= s->cache_size);
556  memcpy(s->cache_data + offset, buf, size);
557  return 0;
558  }
559 
560  while (size) {
561  ssize_t ret = pwrite(s->fd, buf, size, offset);
562  if (ret <= 0)
563  return ret ? AVERROR(errno) : AVERROR(EIO);
564  buf += ret;
565  offset += ret;
566  size -= ret;
567  }
568 
569  return 0;
570 }
571 
572 static size_t clamp_size(URLContext *h, size_t size, int64_t pos)
573 {
574  const int64_t filesize = get_filesize(h);
575  if (!filesize)
576  return size;
577  else if (pos > filesize)
578  return 0;
579  else
580  return FFMIN(filesize - pos, size);
581 }
582 
583 static int shared_read(URLContext *h, unsigned char *buf, int size)
584 {
585  SharedContext *s = h->priv_data;
586  uint8_t *tmp;
587  int ret;
588 
589  if (size <= 0)
590  return 0;
591 
592  size = clamp_size(h, size, s->pos);
593  if (size <= 0)
594  return AVERROR_EOF;
595 
596  const int shift = atomic_load_explicit(&s->spacemap->block_shift, memory_order_relaxed);
597  const int64_t block_id = s->pos >> shift;
598  const int64_t offset = s->pos & (s->block_size - 1);
599  const int64_t block_pos = block_id * s->block_size;
600  int block_size = clamp_size(h, s->block_size, block_pos);
601  ret = spacemap_grow(h, block_id);
602  if (ret < 0)
603  return ret;
604 
605  Block *const block = &s->spacemap->blocks[block_id];
606  unsigned state = atomic_load_explicit(&block->state, memory_order_acquire);
607  int64_t pending_since = 0;
608  int verify_read = 0, is_race = 0;
609 
610 retry:
611  switch (state) {
612  default:
613  /* We always need to read the entire block to verify integrity */
614  block_size = clamp_size(h, block_size, block_pos); /* filesize may have changed */
615  if (s->cache_data) {
616  av_assert1(block_pos + block_size <= s->cache_size);
617  tmp = s->cache_data + block_pos;
618  } else {
619  tmp = s->tmp_buf;
620  ret = read_cache(s, tmp, block_size, block_pos);
621  if (ret < 0) {
622  av_log(h, AV_LOG_ERROR, "Failed to read from cache file: %s\n", av_err2str(ret));
623  return ret;
624  }
625  }
626 
627  uint32_t crc = get_block_crc(tmp, block_size);
628  if (crc != state) {
629  av_log(h, AV_LOG_ERROR, "Cache corruption detected for block 0x%"PRIx64" at "
630  "offset 0x%"PRIx64": expected CRC: 0x%08X, got: 0x%08X\n",
631  block_id, block_pos, state, crc);
632  if (s->retry_corrupt)
633  goto read_block;
634  return AVERROR(EIO);
635  }
636 
637  tmp += (ptrdiff_t) offset;
638  size = FFMIN(size, block_size - offset);
639  if (s->verify) {
640  verify_read = 1;
641  break; /* fall through to the cache miss logic */
642  }
643 
644  memcpy(buf, tmp, size);
645  s->nb_hit++;
646  s->pos += size;
647  return size;
648 
649  case BLOCK_FAILED:
650  if (s->retry_errors)
651  goto read_block;
652  return AVERROR(EIO);
653 
654 read_block:
655  case BLOCK_NONE:
656  if (s->read_only)
657  break; /* don't mark block as pending */
660  memory_order_acquire,
661  memory_order_acquire))
662  {
663  /* Acquired pending state, proceed to fetch the block */
665  break;
666  }
667  /* CAS failed, another thread changed the state; reload it */
668  goto retry;
669 
670  case BLOCK_PENDING:
671  /* Another thread is busy fetching this block, wait for it to finish */
672  if (!s->timeout) {
673  is_race = 1;
674  break; /* no timeout requested, immediately race to fetch block */
675  } else if (pending_since) {
677  if (new - pending_since >= s->timeout) {
678  is_race = 1;
679  break; /* timeout expired, try to fetch the block ourselves */
680  }
681  } else {
682  pending_since = av_gettime_relative();
683  }
684 
685  /* Make sure we try a few times before giving up */
686  av_usleep(s->timeout >> 4);
687  state = atomic_load_explicit(&block->state, memory_order_acquire);
688  goto retry;
689  }
690 
691  /* Cache miss, fetch this block from underlying protocol */
692  s->nb_miss++;
693 
694  const int read_only = s->read_only || s->write_err || verify_read;
695  int64_t inner_pos = read_only ? s->pos : block_pos;
696  if (s->inner_pos != inner_pos) {
697  inner_pos = ffurl_seek(s->inner, inner_pos, SEEK_SET);
698  if (inner_pos < 0) {
699  av_log(h, AV_LOG_ERROR, "Failed to seek underlying protocol: %s\n",
700  av_err2str(inner_pos));
701  if (!read_only) {
702  /* Release pending state to avoid stalling other threads. Don't
703  * mark this as failed, since the seek error may be unrelated to
704  * the block and should probably be tried again. */
706  BLOCK_NONE,
707  memory_order_relaxed,
708  memory_order_relaxed);
709  }
710  return inner_pos;
711  }
712 
713  av_log(h, AV_LOG_DEBUG, "Inner seek to 0x%"PRIx64"\n", inner_pos);
714  s->inner_pos = inner_pos;
715  }
716 
717  if (read_only) {
718  /* Directly defer to the underlying protocol */
719  ret = ffurl_read(s->inner, buf, size);
720  if (ret < 0)
721  return ret;
722 
723  /* Verify the read data against the cached data if requested */
724  if (verify_read && memcmp(buf, tmp, ret)) {
725  av_log(h, AV_LOG_ERROR, "Cache verification failed for %d bytes "
726  "in block 0x%"PRIx64" at offset 0x%"PRIx64" + %"PRId64"!\n",
727  ret, block_id, block_pos, offset);
728  }
729 
730  s->pos = s->inner_pos = inner_pos + ret;
731  return ret;
732  }
733 
734  int write_back = 1;
735  if (s->cache_data && !is_race) {
736  /* Read directly into memory mapped cache file */
737  tmp = s->cache_data + block_pos;
738  write_back = 0;
739  } else if (size >= block_size && !offset) {
740  /* Read directly into output buffer if aligned and large enough */
741  tmp = buf;
742  } else {
743  /* Read into temporary buffer and copy later */
744  tmp = s->tmp_buf;
745  }
746 
747  /* Try and fetch the entire block */
748  av_assert0(inner_pos == block_pos);
749  int bytes_read = 0;
750  while (bytes_read < block_size) {
751  ret = ffurl_read(s->inner, &tmp[bytes_read], block_size - bytes_read);
752  if (!ret || ret == AVERROR_EOF)
753  break;
754  else if (ret < 0) {
755  av_log(h, AV_LOG_ERROR, "Failed to read block 0x%"PRIx64": %s\n",
756  block_id, av_err2str(ret));
757  int new_state = BLOCK_FAILED;
758  if (ret == AVERROR(EAGAIN) || ret == AVERROR_EXIT)
759  new_state = BLOCK_NONE; /* transient error, allow retries */
760 
761  /* Try to mark block as failed; ignore errors - any mismatch
762  * here will mean that either another thread already marked it
763  * as failed, or successfully cached it in the meantime */
765  new_state,
766  memory_order_relaxed,
767  memory_order_relaxed);
768  return ret;
769  }
770 
771  bytes_read += ret;
772  s->inner_pos += ret;
773  }
774 
775  if (bytes_read < block_size) {
776  /* Learned location of true EOF, update filesize */
777  ret = set_filesize(h, inner_pos + bytes_read);
778  if (ret < 0)
779  return ret;
780  }
781 
782  if (bytes_read > 0) {
783  ret = write_back ? write_cache(s, tmp, bytes_read, block_pos) : 0;
784  if (ret < 0) {
785  av_log(h, AV_LOG_ERROR, "Failed to write to cache file: %s\n",
786  av_err2str(ret));
787  s->write_err = 1;
788  /* Mark as NONE, not FAILED, since the block itself is fine -
789  * just absent from the cache. */
791  BLOCK_NONE,
792  memory_order_relaxed,
793  memory_order_relaxed);
794  } else {
795  uint32_t crc = get_block_crc(tmp, bytes_read);
796  av_log(h, AV_LOG_TRACE, "Cached %d bytes to block 0x%"PRIx64" at "
797  "offset 0x%"PRIx64", CRC 0x%08X\n", bytes_read, block_id,
798  block_pos, crc);
799  atomic_store_explicit(&block->state, crc, memory_order_release);
800  }
801  } else {
802  return AVERROR_EOF;
803  }
804 
805  size = FFMIN(bytes_read - offset, size);
806  if (size <= 0)
807  return AVERROR_EOF;
808  if (tmp != buf)
809  memcpy(buf, &tmp[offset], size);
810  s->pos += size;
811  return size;
812 }
813 
814 static int64_t shared_seek(URLContext *h, int64_t pos, int whence)
815 {
816  SharedContext *s = h->priv_data;
817  const int64_t filesize = get_filesize(h);
818  int64_t res;
819 
820  switch (whence) {
821  case AVSEEK_SIZE:
822  if (filesize)
823  return filesize;
824  res = ffurl_seek(s->inner, pos, whence);
825  if (res > 0) {
826  if (set_filesize(h, res) < 0)
827  return AVERROR(EINVAL);
828  }
829  return res;
830  case SEEK_SET:
831  break;
832  case SEEK_CUR:
833  pos += s->pos;
834  break;
835  case SEEK_END:
836  if (filesize) {
837  pos += filesize;
838  break;
839  }
840  /* Defer to underlying protocol if filesize is unknown */
841  res = ffurl_seek(s->inner, pos, whence);
842  if (res < 0)
843  return res;
844  /* Opportunistically update known filesize */
845  if (set_filesize(h, res - pos) < 0)
846  return AVERROR(EINVAL);
847  av_log(h, AV_LOG_DEBUG, "Inner seek to 0x%"PRIx64"\n", res);
848  return s->pos = s->inner_pos = res;
849  default:
850  return AVERROR(EINVAL);
851  }
852 
853  if (pos < 0)
854  return AVERROR(EINVAL);
855 
856  av_log(h, AV_LOG_DEBUG, "Virtual seek to 0x%"PRIx64"\n", pos);
857  return s->pos = pos;
858 }
859 
861 {
862  SharedContext *s = h->priv_data;
863  return ffurl_get_file_handle(s->inner);
864 }
865 
867 {
868  SharedContext *s = h->priv_data;
869  int ret = ffurl_get_short_seek(s->inner);
870  return ret > 0 ? FFMAX(ret, s->block_size) : s->block_size;
871 }
872 
873 #define OFFSET(x) offsetof(SharedContext, x)
874 #define D AV_OPT_FLAG_DECODING_PARAM
875 
876 static const AVOption options[] = {
877  { "cache_dir", "Directory path for shared file cache", OFFSET(cache_dir), AV_OPT_TYPE_STRING, {.str = NULL}, .flags = D },
878  { "block_shift", "Set the base 2 logarithm of the block size", OFFSET(block_shift), AV_OPT_TYPE_INT, {.i64 = 15}, 9, 30, .flags = D },
879  { "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 },
880  { "cache_verify", "Verify correctness of the cache against the source", OFFSET(verify), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, .flags = D },
881  { "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 },
882  { "retry_errors", "Re-request blocks even if they previously failed", OFFSET(retry_errors), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, .flags = D },
883  { "retry_corrupt", "Re-request blocks that fail the CRC check", OFFSET(retry_corrupt), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, .flags = D },
884  {0},
885 };
886 
888  .class_name = "shared",
889  .item_name = av_default_item_name,
890  .option = options,
891  .version = LIBAVUTIL_VERSION_INT,
892 };
893 
895  .name = "shared",
896  .url_open2 = shared_open,
897  .url_read = shared_read,
898  .url_seek = shared_seek,
899  .url_close = shared_close,
900  .url_get_file_handle = shared_get_file_handle,
901  .url_get_short_seek = shared_get_short_seek,
902  .priv_data_size = sizeof(SharedContext),
903  .priv_data_class = &shared_context_class,
904 };
flags
const SwsFlags flags[]
Definition: swscale.c:85
ffurl_seek
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:222
av_gettime_relative
int64_t av_gettime_relative(void)
Get the current time in microseconds since some unspecified starting point.
Definition: time.c:57
AV_LOG_WARNING
#define AV_LOG_WARNING
Something somehow does not look correct.
Definition: log.h:216
AVHashContext::crc
uint32_t crc
Definition: hash.c:70
AVERROR
Filter the word “frame” indicates either a video frame or a group of audio as stored in an AVFrame structure Format for each input and each output the list of supported formats For video that means pixel format For audio that means channel sample they are references to shared objects When the negotiation mechanism computes the intersection of the formats supported at each end of a all references to both lists are replaced with a reference to the intersection And when a single format is eventually chosen for a link amongst the remaining all references to the list are updated That means that if a filter requires that its input and output have the same format amongst a supported all it has to do is use a reference to the same list of formats query_formats can leave some formats unset and return AVERROR(EAGAIN) to cause the negotiation mechanism toagain later. That can be used by filters with complex requirements to use the format negotiated on one link to set the formats supported on another. Frame references ownership and permissions
opt.h
Block::state
atomic_uint state
Definition: shared.c:111
shared_close
static int shared_close(URLContext *h)
Definition: shared.c:182
set_filesize
static int set_filesize(URLContext *h, int64_t new_size)
Definition: shared.c:214
AVERROR_EOF
#define AVERROR_EOF
End of file.
Definition: error.h:57
get_filesize
static int64_t get_filesize(URLContext *h)
Definition: shared.c:208
Spacemap::version
atomic_ushort version
Definition: shared.c:116
int64_t
long long int64_t
Definition: coverity.c:34
av_asprintf
char * av_asprintf(const char *fmt,...)
Definition: avstring.c:115
write_cache
static int write_cache(SharedContext *s, const uint8_t *buf, size_t size, off_t offset)
Definition: shared.c:552
atomic_ushort
intptr_t atomic_ushort
Definition: stdatomic.h:54
BLOCK_PENDING
@ BLOCK_PENDING
a thread is currently trying to write this block
Definition: shared.c:88
SharedContext::retry_corrupt
int retry_corrupt
Definition: shared.c:156
AVOption
AVOption.
Definition: opt.h:428
AVSEEK_SIZE
#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
atomic_compare_exchange_weak_explicit
#define atomic_compare_exchange_weak_explicit(object, expected, desired, success, failure)
Definition: stdatomic.h:129
HEADER_VERSION
#define HEADER_VERSION
Definition: shared.c:63
ff_shared_protocol
const URLProtocol ff_shared_protocol
Definition: shared.c:894
AV_LOG_VERBOSE
#define AV_LOG_VERBOSE
Detailed information.
Definition: log.h:226
ffurl_close
int ffurl_close(URLContext *h)
Definition: avio.c:617
AVDictionary
Definition: dict.c:32
FFMAX
#define FFMAX(a, b)
Definition: macros.h:47
SharedContext::cache_path
char * cache_path
Definition: shared.c:167
SharedContext::verify
int verify
Definition: shared.c:157
hash
static uint8_t hash[HASH_SIZE]
Definition: movenc.c:58
URLProtocol
Definition: url.h:51
hash_uri
static int hash_uri(uint8_t hash[HASH_SIZE], const char *uri)
Definition: shared.c:65
D
#define D
Definition: shared.c:874
BlockState
BlockState
Definition: shared.c:85
crc.h
close
static av_cold void close(AVCodecParserContext *s)
Definition: apv_parser.c:197
read_cache
static int read_cache(SharedContext *s, uint8_t *buf, size_t size, off_t offset)
Definition: shared.c:532
BLOCK_FAILED
@ BLOCK_FAILED
the underlying I/O source failed to read this block
Definition: shared.c:89
ffurl_get_short_seek
int ffurl_get_short_seek(void *urlcontext)
Return the current short seek threshold value for this URL.
Definition: avio.c:844
SharedContext::tmp_buf
uint8_t * tmp_buf
Definition: shared.c:161
SharedContext::cache_dir
char * cache_dir
Definition: shared.c:151
locked
FFmpeg hosted at Telepoint in bulgaria ns2 avcodec org Replica Name hosted at Prometeus Cdlan in italy instead several VMs run on it Main server peering exchange and hosting They have multiple DC buildings in Sofia and FFmpeg is hosted in XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX The building is locked down and accessible only with personal key cards that are registered People who are granted access to a rack have to go through the access center with their ID to get logged and receive a one time access card that can open the service elevator and only the hall where the destination rack is All racks are locked
Definition: infra.txt:35
SharedContext::nb_miss
int64_t nb_miss
Definition: shared.c:179
shared_get_file_handle
static int shared_get_file_handle(URLContext *h)
Definition: shared.c:860
avassert.h
AV_LOG_TRACE
#define AV_LOG_TRACE
Extremely verbose debugging, useful for libav* development.
Definition: log.h:236
AV_LOG_ERROR
#define AV_LOG_ERROR
Something went wrong and cannot losslessly be recovered.
Definition: log.h:210
FF_ARRAY_ELEMS
#define FF_ARRAY_ELEMS(a)
Definition: sinewin_tablegen.c:29
SharedContext::map_size
off_t map_size
Definition: shared.c:174
Spacemap::hash
atomic_uchar hash[HASH_SIZE]
Definition: shared.c:119
SharedContext::spacemap
Spacemap * spacemap
Definition: shared.c:172
ffurl_open_whitelist
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:368
spacemap_init
static int spacemap_init(URLContext *h, const uint8_t hash[HASH_SIZE])
Definition: shared.c:479
avpriv_open
int avpriv_open(const char *filename, int flags,...)
A wrapper for open() setting O_CLOEXEC.
Definition: file_open.c:67
DEF_SET_ONCE
#define DEF_SET_ONCE(ctype, atype)
Definition: shared.c:126
SharedContext::read_only
int read_only
Definition: shared.c:153
state
static struct @595 state
av_hash_alloc
int av_hash_alloc(AVHashContext **ctx, const char *name)
Allocate a hash context for the algorithm specified by name.
Definition: hash.c:114
AV_OPT_TYPE_INT64
@ AV_OPT_TYPE_INT64
Underlying C type is int64_t.
Definition: opt.h:262
av_assert0
#define av_assert0(cond)
assert() equivalent, that is always enabled.
Definition: avassert.h:42
shared_context_class
static const AVClass shared_context_class
Definition: shared.c:887
Spacemap::filesize
atomic_ullong filesize
Definition: shared.c:118
AV_LOG_DEBUG
#define AV_LOG_DEBUG
Stuff which is only useful for libav* developers.
Definition: log.h:231
ctx
static AVFormatContext * ctx
Definition: movenc.c:49
SharedContext::retry_errors
int retry_errors
Definition: shared.c:155
SharedContext::write_err
int write_err
write error occurred
Definition: shared.c:163
SharedContext::nb_hit
int64_t nb_hit
Definition: shared.c:178
av_usleep
int av_usleep(unsigned usec)
Sleep for a period of time.
Definition: time.c:93
atomic_load
#define atomic_load(object)
Definition: stdatomic.h:93
Spacemap::header_magic
atomic_uint header_magic
Definition: shared.c:115
file_open.h
tmp
static uint8_t tmp[40]
Definition: aes_ctr.c:52
arg
const char * arg
Definition: jacosubdec.c:65
atomic_compare_exchange_strong_explicit
#define atomic_compare_exchange_strong_explicit(object, expected, desired, success, failure)
Definition: stdatomic.h:123
SharedContext::inner_pos
int64_t inner_pos
Definition: shared.c:148
BLOCK_NONE
@ BLOCK_NONE
block is not cached
Definition: shared.c:87
fail
#define fail
Definition: test.h:478
LIBAVUTIL_VERSION_INT
#define LIBAVUTIL_VERSION_INT
Definition: version.h:85
SharedContext::map_path
char * map_path
Definition: shared.c:173
AVClass
Describe the class of an AVClass context structure.
Definition: log.h:76
NULL
#define NULL
Definition: coverity.c:32
av_hash_init
void av_hash_init(AVHashContext *ctx)
Initialize or reset a hash context.
Definition: hash.c:151
SharedContext
Definition: shared.c:145
av_default_item_name
const char * av_default_item_name(void *ptr)
Return the context name.
Definition: log.c:242
options
Definition: swscale.c:50
SharedContext::block_shift
int block_shift
requested shift; may disagree with actual
Definition: shared.c:152
spacemap_remap
static int spacemap_remap(URLContext *h, size_t map_size)
Definition: shared.c:376
time.h
av_hash_update
void av_hash_update(AVHashContext *ctx, const uint8_t *src, size_t len)
Update a hash context with additional data.
Definition: hash.c:172
attributes.h
atomic_load_explicit
#define atomic_load_explicit(object, order)
Definition: stdatomic.h:96
HEADER_MAGIC
#define HEADER_MAGIC
Definition: shared.c:62
av_hash_freep
void av_hash_freep(AVHashContext **ctx)
Free hash context and set hash context pointer to NULL.
Definition: hash.c:248
error.h
read_block
static int read_block(ALSDecContext *ctx, ALSBlockData *bd)
Read the block data.
Definition: alsdec.c:1031
Block
Definition: flashsv2enc.c:70
SharedContext::cache_size
off_t cache_size
size of mapped memory region (for munmap)
Definition: shared.c:168
shift
static int shift(int a, int b)
Definition: bonk.c:261
i
#define i(width, name, range_min, range_max)
Definition: cbs_h264.c:63
av_err2str
#define av_err2str(errnum)
Convenience macro, the return value should be used only directly in function arguments but never stan...
Definition: error.h:122
size
int size
Definition: twinvq_data.h:10344
URLProtocol::name
const char * name
Definition: url.h:52
av_hash_final
void av_hash_final(AVHashContext *ctx, uint8_t *dst)
Finalize a hash context and compute the actual hash value.
Definition: hash.c:193
shared_get_short_seek
static int shared_get_short_seek(URLContext *h)
Definition: shared.c:866
SharedContext::mapfd
int mapfd
Definition: shared.c:175
av_crc_get_table
const AVCRC * av_crc_get_table(AVCRCId crc_id)
Get an initialized standard CRC table.
Definition: crc.c:389
offset
it s the only field you need to keep assuming you have a context There is some magic you don t need to care about around this just let it vf offset
Definition: writing_filters.txt:86
AVHashContext
Definition: hash.c:66
av_strstart
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
version
version
Definition: libkvazaar.c:313
spacemap_grow
static int spacemap_grow(URLContext *h, int64_t block)
Definition: shared.c:446
atomic_uchar
intptr_t atomic_uchar
Definition: stdatomic.h:52
filesize
static int64_t filesize(AVIOContext *pb)
Definition: ffmpeg_mux.c:51
URLContext
Definition: url.h:35
av_malloc
#define av_malloc(s)
Definition: ops_asmgen.c:44
shared_seek
static int64_t shared_seek(URLContext *h, int64_t pos, int whence)
Definition: shared.c:814
av_assert1
#define av_assert1(cond)
assert() equivalent, that does not lie in speed critical code.
Definition: avassert.h:58
s
uint8_t s
Definition: llvidencdsp.c:39
atomic_store_explicit
#define atomic_store_explicit(object, desired, order)
Definition: stdatomic.h:90
FFMIN
#define FFMIN(a, b)
Definition: macros.h:49
url.h
atomic_ullong
intptr_t atomic_ullong
Definition: stdatomic.h:60
get_block_crc
static uint32_t get_block_crc(const uint8_t *block, size_t block_size)
Definition: shared.c:97
AV_CRC_32_IEEE
@ AV_CRC_32_IEEE
Definition: crc.h:52
SharedContext::cache_data
uint8_t * cache_data
optional mmap of the cache file
Definition: shared.c:166
ret
ret
Definition: filter_design.txt:187
AVClass::class_name
const char * class_name
The name of the class; usually it is the same name as the context structure type to which the AVClass...
Definition: log.h:81
pos
unsigned int pos
Definition: spdifenc.c:414
options
static const AVOption options[]
Definition: shared.c:876
hash.h
SharedContext::block_size
int block_size
Definition: shared.c:162
av_crc
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
SharedContext::timeout
int64_t timeout
Definition: shared.c:154
shared_open
static int shared_open(URLContext *h, const char *arg, int flags, AVDictionary **options)
Definition: shared.c:237
AV_OPT_TYPE_INT
@ AV_OPT_TYPE_INT
Underlying C type is int.
Definition: opt.h:258
av_hash_get_size
int av_hash_get_size(const AVHashContext *ctx)
Definition: hash.c:109
atomic_uint
intptr_t atomic_uint
Definition: stdatomic.h:56
HASH_METHOD
#define HASH_METHOD
This hash should be resistant against collision attacks, so that an attacker could not generate e....
Definition: shared.c:60
HASH_SIZE
#define HASH_SIZE
Definition: shared.c:61
mem.h
cache_map
static int cache_map(URLContext *h, int64_t filesize)
Definition: shared.c:340
Spacemap
Definition: shared.c:114
SharedContext::fd
int fd
Definition: shared.c:169
FFALIGN
#define FFALIGN(x, a)
Definition: macros.h:78
SharedContext::inner
URLContext * inner
Definition: shared.c:147
AV_OPT_TYPE_BOOL
@ AV_OPT_TYPE_BOOL
Underlying C type is int.
Definition: opt.h:326
av_freep
#define av_freep(p)
Definition: tableprint_vlc.h:35
Spacemap::blocks
Block blocks[]
Definition: shared.c:122
SharedContext::pos
int64_t pos
current logical position
Definition: shared.c:160
OFFSET
#define OFFSET(x)
Definition: shared.c:873
Spacemap::reserved
char reserved[80]
Definition: shared.c:120
block
The exact code depends on how similar the blocks are and how related they are to the block
Definition: filter_design.txt:207
av_log
#define av_log(a,...)
Definition: tableprint_vlc.h:27
ffurl_size
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:805
h
h
Definition: vp9dsp_template.c:2070
AVERROR_EXIT
#define AVERROR_EXIT
Immediate exit was requested; the called function should not be restarted.
Definition: error.h:58
avstring.h
shared_read
static int shared_read(URLContext *h, unsigned char *buf, int size)
Definition: shared.c:583
AV_OPT_TYPE_STRING
@ 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
clamp_size
static size_t clamp_size(URLContext *h, size_t size, int64_t pos)
Definition: shared.c:572
Spacemap::block_shift
atomic_ushort block_shift
Definition: shared.c:117
ffurl_get_file_handle
int ffurl_get_file_handle(URLContext *h)
Return the file descriptor associated with this URL.
Definition: avio.c:820
ffurl_read
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:181