// Iterate entries. The first call establishes the first entry. Returns false // if no entry found, otherwise returns true and sets mpa->entry/entry_filename. bool mp_archive_next_entry(struct mp_archive *mpa) { mpa->entry = NULL; talloc_free(mpa->entry_filename); mpa->entry_filename = NULL; while (!mp_cancel_test(mpa->primary_src->cancel)) { struct archive_entry *entry; int r = archive_read_next_header(mpa->arch, &entry); if (r == ARCHIVE_EOF) break; if (r < ARCHIVE_OK) MP_ERR(mpa, "%s\n", archive_error_string(mpa->arch)); if (r < ARCHIVE_WARN) { MP_FATAL(mpa, "could not read archive entry\n"); break; } if (archive_entry_filetype(entry) != AE_IFREG) continue; // Some archives may have no filenames, or libarchive won't return some. const char *fn = archive_entry_pathname(entry); char buf[64]; if (!fn || bstr_validate_utf8(bstr0(fn)) < 0) { snprintf(buf, sizeof(buf), "mpv_unknown#%d", mpa->entry_num); fn = buf; } mpa->entry = entry; mpa->entry_filename = talloc_strdup(mpa, fn); mpa->entry_num += 1; return true; } return false; }
static int archive_entry_seek(stream_t *s, int64_t newpos) { struct priv *p = s->priv; if (!p->mpa) return -1; if (archive_seek_data(p->mpa->arch, newpos, SEEK_SET) >= 0) return 1; // libarchive can't seek in most formats. if (newpos < s->pos) { // Hack seeking backwards into working by reopening the archive and // starting over. MP_VERBOSE(s, "trying to reopen archive for performing seek\n"); if (reopen_archive(s) < STREAM_OK) return -1; s->pos = 0; } if (newpos > s->pos) { // For seeking forwards, just keep reading data (there's no libarchive // skip function either). char buffer[4096]; while (newpos > s->pos) { if (mp_cancel_test(s->cancel)) return -1; int size = MPMIN(newpos - s->pos, sizeof(buffer)); int r = archive_read_data(p->mpa->arch, buffer, size); if (r < 0) { MP_ERR(s, "%s\n", archive_error_string(p->mpa->arch)); return -1; } s->pos += r; } } return 1; }
static int cache_fill_buffer(struct stream *cache, char *buffer, int max_len) { struct priv *s = cache->priv; assert(s->cache_thread_running); pthread_mutex_lock(&s->mutex); if (cache->pos != s->read_filepos) MP_ERR(s, "!!! read_filepos differs !!! report this bug...\n"); int readb = 0; if (max_len > 0) { double retry_time = 0; int64_t retry = s->reads - 1; // try at least 1 read on EOF while (1) { readb = read_buffer(s, buffer, max_len, s->read_filepos); s->read_filepos += readb; if (readb > 0) break; if (s->eof && s->read_filepos >= s->max_filepos && s->reads >= retry) break; s->idle = false; if (mp_cancel_test(s->cache->cancel)) break; cache_wakeup_and_wait(s, &retry_time); } } // wakeup the cache thread, possibly make it read more data ahead pthread_cond_signal(&s->wakeup); pthread_mutex_unlock(&s->mutex); return readb; }
static int archive_entry_seek(stream_t *s, int64_t newpos) { struct priv *p = s->priv; if (p->mpa && !p->broken_seek) { locale_t oldlocale = uselocale(p->mpa->locale); int r = archive_seek_data(p->mpa->arch, newpos, SEEK_SET); uselocale(oldlocale); if (r >= 0) return 1; MP_WARN(s, "possibly unsupported seeking - switching to reopening\n"); p->broken_seek = true; if (reopen_archive(s) < STREAM_OK) return -1; } // libarchive can't seek in most formats. if (newpos < s->pos) { // Hack seeking backwards into working by reopening the archive and // starting over. MP_VERBOSE(s, "trying to reopen archive for performing seek\n"); if (reopen_archive(s) < STREAM_OK) return -1; s->pos = 0; } if (newpos > s->pos) { // For seeking forwards, just keep reading data (there's no libarchive // skip function either). char buffer[4096]; while (newpos > s->pos) { if (mp_cancel_test(s->cancel)) return -1; int size = MPMIN(newpos - s->pos, sizeof(buffer)); locale_t oldlocale = uselocale(p->mpa->locale); int r = archive_read_data(p->mpa->arch, buffer, size); if (r <= 0) { if (r == 0 && newpos > p->entry_size) { MP_ERR(s, "demuxer trying to seek beyond end of archive " "entry\n"); } else if (r == 0) { MP_ERR(s, "end of archive entry reached while seeking\n"); } else { MP_ERR(s, "%s\n", archive_error_string(p->mpa->arch)); } uselocale(oldlocale); if (mp_archive_check_fatal(p->mpa, r)) { mp_archive_free(p->mpa); p->mpa = NULL; } return -1; } uselocale(oldlocale); s->pos += r; } } return 1; }
// Return true if this was a readable directory. static bool scan_dir(struct pl_parser *p, char *path, struct stat *dir_stack, int num_dir_stack, char ***files, int *num_files) { if (strlen(path) >= 8192 || num_dir_stack == MAX_DIR_STACK) return false; // things like mount bind loops DIR *dp = opendir(path); if (!dp) { MP_ERR(p, "Could not read directory.\n"); return false; } struct dirent *ep; while ((ep = readdir(dp))) { if (ep->d_name[0] == '.') continue; if (mp_cancel_test(p->s->cancel)) break; char *file = mp_path_join(p, path, ep->d_name); struct stat st; if (stat(file, &st) == 0 && S_ISDIR(st.st_mode)) { for (int n = 0; n < num_dir_stack; n++) { if (same_st(&dir_stack[n], &st)) { MP_VERBOSE(p, "Skip recursive entry: %s\n", file); goto skip; } } dir_stack[num_dir_stack] = st; scan_dir(p, file, dir_stack, num_dir_stack + 1, files, num_files); } else { MP_TARRAY_APPEND(p, *files, *num_files, file); } skip: ; } closedir(dp); return true; }
static int cache_control(stream_t *cache, int cmd, void *arg) { struct priv *s = cache->priv; int r = STREAM_ERROR; assert(cmd > 0); pthread_mutex_lock(&s->mutex); r = cache_get_cached_control(cache, cmd, arg); if (r != STREAM_ERROR) goto done; MP_VERBOSE(s, "blocking for STREAM_CTRL %d\n", cmd); s->control = cmd; s->control_arg = arg; double retry = 0; while (s->control != CACHE_CTRL_NONE) { if (mp_cancel_test(s->cache->cancel)) { s->eof = 1; r = STREAM_UNSUPPORTED; goto done; } cache_wakeup_and_wait(s, &retry); } r = s->control_res; if (s->control_flush) { stream_drop_buffers(cache); cache->pos = s->read_filepos; } done: pthread_mutex_unlock(&s->mutex); return r; }
// return 1 on success, 0 if the cache is disabled/not needed, and -1 on error // or if the cache is disabled int stream_cache_init(stream_t *cache, stream_t *stream, struct mp_cache_opts *opts) { if (opts->size < 1) return 0; struct priv *s = talloc_zero(NULL, struct priv); s->log = cache->log; s->eof_pos = -1; cache_drop_contents(s); s->seek_limit = opts->seek_min * 1024ULL; s->back_size = opts->back_buffer * 1024ULL; int64_t cache_size = opts->size * 1024ULL; int64_t file_size = stream_get_size(stream); if (file_size >= 0) cache_size = MPMIN(cache_size, file_size); if (resize_cache(s, cache_size) != STREAM_OK) { MP_ERR(s, "Failed to allocate cache buffer.\n"); talloc_free(s); return -1; } MP_VERBOSE(cache, "Cache size set to %lld KiB (%lld KiB backbuffer)\n", (long long)(s->buffer_size / 1024), (long long)(s->back_size / 1024)); pthread_mutex_init(&s->mutex, NULL); pthread_cond_init(&s->wakeup, NULL); cache->priv = s; s->cache = cache; s->stream = stream; cache->seek = cache_seek; cache->fill_buffer = cache_fill_buffer; cache->control = cache_control; cache->close = cache_uninit; int64_t min = opts->initial * 1024ULL; if (min > s->buffer_size - FILL_LIMIT) min = s->buffer_size - FILL_LIMIT; s->seekable = stream->seekable; if (pthread_create(&s->cache_thread, NULL, cache_thread, s) != 0) { MP_ERR(s, "Starting cache thread failed.\n"); return -1; } s->cache_thread_running = true; // wait until cache is filled with at least min bytes if (min < 1) return 1; for (;;) { if (mp_cancel_test(cache->cancel)) return -1; int64_t fill; int idle; if (stream_control(s->cache, STREAM_CTRL_GET_CACHE_FILL, &fill) < 0) break; if (stream_control(s->cache, STREAM_CTRL_GET_CACHE_IDLE, &idle) < 0) break; MP_INFO(s, "\rCache fill: %5.2f%% " "(%" PRId64 " bytes) ", 100.0 * fill / s->buffer_size, fill); if (fill >= min) break; if (idle) break; // file is smaller than prefill size // Wake up if the cache is done reading some data (or on timeout/abort) pthread_mutex_lock(&s->mutex); s->control = CACHE_CTRL_PING; pthread_cond_signal(&s->wakeup); cache_wakeup_and_wait(s, &(double){0}); pthread_mutex_unlock(&s->mutex); }
// Runs in the cache thread. // Returns true if reading was attempted, and the mutex was shortly unlocked. static bool cache_fill(struct priv *s) { int64_t read = s->read_filepos; int len = 0; // drop cache contents only if seeking backward or too much fwd. // This is also done for on-disk files, since it loses the backseek cache. // That in turn can cause major bandwidth increase and performance // issues with e.g. mov or badly interleaved files if (read < s->min_filepos || read > s->max_filepos + s->seek_limit) { MP_VERBOSE(s, "Dropping cache at pos %"PRId64", " "cached range: %"PRId64"-%"PRId64".\n", read, s->min_filepos, s->max_filepos); cache_drop_contents(s); } if (stream_tell(s->stream) != s->max_filepos && s->seekable) { MP_VERBOSE(s, "Seeking underlying stream: %"PRId64" -> %"PRId64"\n", stream_tell(s->stream), s->max_filepos); stream_seek(s->stream, s->max_filepos); if (stream_tell(s->stream) != s->max_filepos) goto done; } if (mp_cancel_test(s->cache->cancel)) goto done; // number of buffer bytes which should be preserved in backwards direction int64_t back = MPCLAMP(read - s->min_filepos, 0, s->back_size); // limit maximum readahead so that the backbuffer space is reserved, even // if the backbuffer is not used. limit it to ensure that we don't stall the // network when starting a file, or we wouldn't download new data until we // get new free space again. (unless everything fits in the cache.) if (s->stream_size > s->buffer_size) back = MPMAX(back, s->back_size); // number of buffer bytes that are valid and can be read int64_t newb = FFMAX(s->max_filepos - read, 0); // max. number of bytes that can be written (starting from max_filepos) int64_t space = s->buffer_size - (newb + back); // offset into the buffer that maps to max_filepos int64_t pos = s->max_filepos - s->offset; if (pos >= s->buffer_size) pos -= s->buffer_size; // wrap-around if (space < FILL_LIMIT) { s->idle = true; s->reads++; // don't stuck main thread return false; } // limit to end of buffer (without wrapping) if (pos + space >= s->buffer_size) space = s->buffer_size - pos; // limit read size (or else would block and read the entire buffer in 1 call) space = FFMIN(space, s->stream->read_chunk); // back+newb+space <= buffer_size int64_t back2 = s->buffer_size - (space + newb); // max back size if (s->min_filepos < (read - back2)) s->min_filepos = read - back2; // The read call might take a long time and block, so drop the lock. pthread_mutex_unlock(&s->mutex); len = stream_read_partial(s->stream, &s->buffer[pos], space); pthread_mutex_lock(&s->mutex); // Do this after reading a block, because at least libdvdnav updates the // stream position only after actually reading something after a seek. if (s->start_pts == MP_NOPTS_VALUE) { double pts; if (stream_control(s->stream, STREAM_CTRL_GET_CURRENT_TIME, &pts) > 0) s->start_pts = pts; } s->max_filepos += len; if (pos + len == s->buffer_size) s->offset += s->buffer_size; // wrap... done: s->eof = len <= 0; s->idle = s->eof; s->reads++; if (s->eof) { s->eof_pos = stream_tell(s->stream); MP_TRACE(s, "EOF reached.\n"); } pthread_cond_signal(&s->wakeup); return true; }