FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/fftools/ffplay_dir.c
Date: 2026-10-19 01:23:10
Exec Total Coverage
Lines: 0 93 0.0%
Functions: 0 8 0.0%
Branches: 0 114 0.0%

Line Branch Exec Source
1 /*
2 * This file is part of FFmpeg.
3 *
4 * FFmpeg is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * FFmpeg is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with FFmpeg; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 */
18
19 #include "config.h"
20
21 #include <errno.h>
22 #include <limits.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #if HAVE_LSTAT
26 #include <sys/stat.h>
27 #endif
28
29 #include "libavformat/avio.h"
30 #include "libavutil/avstring.h"
31 #include "libavutil/dict.h"
32 #include "libavutil/dynarray.h"
33 #include "libavutil/error.h"
34 #include "libavutil/mem.h"
35
36 #include "ffplay_dir.h"
37
38 enum EntryKind {
39 ENTRY_FILE,
40 ENTRY_MAYBE_DIR,
41 ENTRY_DIR,
42 };
43
44 /* the directories first, then the entries that may be directories, then the files, each by name; the kind of an entry
45 * is kept in its length until the entries are sorted */
46 ✗ static int compare_entries(const void *a, const void *b)
47 {
48 ✗ const ListEntry *ea = a, *eb = b;
49 ✗ int kind = (eb->length > ea->length) - (eb->length < ea->length);
50 ✗ int cmp = av_strcasecmp(ea->title, eb->title);
51
52 ✗ return kind ? kind : cmp ? cmp : strcmp(ea->title, eb->title);
53 }
54
55 /* adds the entry, titled with a trailing slash when it is a directory and with its size when that is not negative */
56 ✗ static int add_entry(DirList *dl, const char *name, enum EntryKind kind, int64_t size)
57 {
58 ListEntry *entry;
59
60 ✗ FF_DYNARRAY_ADD(INT_MAX, sizeof(*entry), dl->entries, dl->nb_entries, {
61 entry = &dl->entries[dl->nb_entries];
62 }, {
63 return AVERROR(ENOMEM);
64 });
65 ✗ *entry = (ListEntry){ av_asprintf("%s%s", name, kind == ENTRY_DIR ? "/" : ""), NULL, kind, NULL };
66 ✗ if (!entry->title || (size >= 0 && av_dict_set_int(&entry->metadata, "size", size, 0) < 0))
67 ✗ return AVERROR(ENOMEM);
68 ✗ return 0;
69 }
70
71 /* whether the entry is listed, which hidden entries, special files and links to them are not; a link in a local
72 * directory is listed as what it points to, with its size */
73 ✗ static int listed(const char *local_dir, AVIODirEntry *dirent)
74 {
75 ✗ if (!dirent->name || !*dirent->name || dirent->name[0] == '.')
76 ✗ return 0;
77 #if HAVE_LSTAT
78 ✗ if (dirent->type == AVIO_ENTRY_SYMBOLIC_LINK && local_dir) {
79 ✗ char *path = av_append_path_component(local_dir, dirent->name);
80 struct stat st;
81 int found;
82
83 ✗ if (!path)
84 ✗ return AVERROR(ENOMEM);
85 ✗ found = !stat(path, &st);
86 ✗ av_free(path);
87 ✗ if (!found || !(S_ISDIR(st.st_mode) || S_ISREG(st.st_mode)))
88 ✗ return 0;
89 ✗ dirent->type = S_ISDIR(st.st_mode) ? AVIO_ENTRY_DIRECTORY : AVIO_ENTRY_FILE;
90 ✗ dirent->size = st.st_size;
91 }
92 #endif
93 ✗ return dirent->type != AVIO_ENTRY_NAMED_PIPE && dirent->type != AVIO_ENTRY_CHARACTER_DEVICE &&
94 ✗ dirent->type != AVIO_ENTRY_BLOCK_DEVICE && dirent->type != AVIO_ENTRY_SOCKET;
95 }
96
97 /* the local directory as an absolute path without links, NULL with errno set when it cannot be resolved */
98 ✗ static char *resolve_local(const char *path)
99 {
100 #if HAVE_REALPATH
101 ✗ char *resolved = realpath(path, NULL), *copy = resolved ? av_strdup(resolved) : NULL;
102
103 ✗ if (resolved && !copy)
104 ✗ errno = ENOMEM;
105 ✗ free(resolved);
106 ✗ return copy;
107 #else
108 errno = ENOSYS;
109 return NULL;
110 #endif
111 }
112
113 ✗ int dir_list_read(DirList **pdl, const char *path, const AVIOInterruptCB *int_cb, AVDictionary **options)
114 {
115 ✗ const char *protocol = avio_find_protocol_name(path);
116 ✗ int local = !protocol || !strcmp(protocol, "file");
117 ✗ AVIODirContext *ctx = NULL;
118 ✗ AVIODirEntry *dirent = NULL;
119 ✗ DirList *dl = av_mallocz(sizeof(*dl));
120 int ret;
121
122 ✗ if (!dl)
123 ✗ return AVERROR(ENOMEM);
124 ✗ if (local)
125 ✗ av_strstart(path, "file:", &path);
126 ✗ dl->path = local ? resolve_local(path) : av_strdup(path);
127 ✗ if (!dl->path) {
128 ✗ ret = AVERROR(local ? errno : ENOMEM);
129 ✗ goto fail;
130 }
131 ✗ if ((ret = add_entry(dl, "..", ENTRY_DIR, -1)) < 0 || (ret = avio_open_dir2(&ctx, dl->path, int_cb, options)) < 0)
132 ✗ goto fail;
133 ✗ while ((ret = avio_read_dir(ctx, &dirent)) >= 0 && dirent) {
134 ✗ if (int_cb && int_cb->callback && int_cb->callback(int_cb->opaque))
135 ✗ ret = AVERROR_EXIT;
136 ✗ else if ((ret = listed(local ? dl->path : NULL, dirent)) > 0)
137 ✗ ret = add_entry(dl, dirent->name, dirent->type == AVIO_ENTRY_DIRECTORY ? ENTRY_DIR :
138 ✗ dirent->type == AVIO_ENTRY_FILE ? ENTRY_FILE : ENTRY_MAYBE_DIR, dirent->size);
139 ✗ avio_free_directory_entry(&dirent);
140 ✗ if (ret < 0)
141 ✗ break;
142 }
143 ✗ avio_close_dir(&ctx);
144 ✗ if (ret < 0)
145 ✗ goto fail;
146 ✗ qsort(dl->entries + 1, dl->nb_entries - 1, sizeof(*dl->entries), compare_entries);
147 ✗ for (int i = 0; i < dl->nb_entries; i++) {
148 ✗ dl->nb_dirs += dl->entries[i].length == ENTRY_DIR;
149 ✗ dl->nb_maybe_dirs += dl->entries[i].length == ENTRY_MAYBE_DIR;
150 ✗ dl->entries[i].length = -1;
151 }
152 ✗ *pdl = dl;
153 ✗ return 0;
154 ✗ fail:
155 ✗ dir_list_free(&dl);
156 ✗ return ret;
157 }
158
159 ✗ void dir_list_free(DirList **pdl)
160 {
161 ✗ DirList *dl = *pdl;
162
163 ✗ if (!dl)
164 ✗ return;
165 ✗ for (int i = 0; i < dl->nb_entries; i++) {
166 ✗ av_freep(&dl->entries[i].title);
167 ✗ av_dict_free(&dl->entries[i].metadata);
168 }
169 ✗ av_freep(&dl->entries);
170 ✗ av_freep(&dl->path);
171 ✗ av_freep(pdl);
172 }
173
174 /* the directory above, which is the root of a path or the host of a URL at most */
175 ✗ static char *parent_path(const char *path)
176 {
177 ✗ const char *scheme = *path == '/' ? NULL : strstr(path, "://");
178 ✗ const char *top = scheme ? strchr(scheme + 3, '/') : path;
179 ✗ const char *slash = top ? strrchr(top, '/') : NULL;
180
181 ✗ return av_strndup(path, slash ? FFMAX(slash, top + !scheme) - path : strlen(path));
182 }
183
184 ✗ char *dir_list_path(const DirList *dl, int entry)
185 {
186 ✗ const char *title = dl->entries[entry].title;
187 char *path;
188
189 ✗ if (!entry)
190 ✗ return parent_path(dl->path);
191 ✗ path = av_append_path_component(dl->path, title);
192 ✗ if (path && entry < dl->nb_dirs)
193 ✗ path[strlen(path) - 1] = 0;
194 ✗ return path;
195 }
196