| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * buffered file I/O | ||
| 3 | * Copyright (c) 2001 Fabrice Bellard | ||
| 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 | |||
| 22 | #include "config_components.h" | ||
| 23 | |||
| 24 | #include "libavutil/avstring.h" | ||
| 25 | #include "libavutil/file_open.h" | ||
| 26 | #include "libavutil/internal.h" | ||
| 27 | #include "libavutil/mem.h" | ||
| 28 | #include "libavutil/opt.h" | ||
| 29 | #include "avio.h" | ||
| 30 | #if HAVE_DIRENT_H | ||
| 31 | #include <dirent.h> | ||
| 32 | #endif | ||
| 33 | #include <fcntl.h> | ||
| 34 | #if HAVE_IO_H | ||
| 35 | #include <io.h> | ||
| 36 | #endif | ||
| 37 | #if HAVE_UNISTD_H | ||
| 38 | #include <unistd.h> | ||
| 39 | #endif | ||
| 40 | #include <sys/stat.h> | ||
| 41 | #include <stdlib.h> | ||
| 42 | #include "os_support.h" | ||
| 43 | #include "url.h" | ||
| 44 | |||
| 45 | /* Some systems may not have S_ISFIFO */ | ||
| 46 | #ifndef S_ISFIFO | ||
| 47 | # ifdef S_IFIFO | ||
| 48 | # define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO) | ||
| 49 | # else | ||
| 50 | # define S_ISFIFO(m) 0 | ||
| 51 | # endif | ||
| 52 | #endif | ||
| 53 | |||
| 54 | /* Not available in POSIX.1-1996 */ | ||
| 55 | #ifndef S_ISLNK | ||
| 56 | # ifdef S_IFLNK | ||
| 57 | # define S_ISLNK(m) (((m) & S_IFLNK) == S_IFLNK) | ||
| 58 | # else | ||
| 59 | # define S_ISLNK(m) 0 | ||
| 60 | # endif | ||
| 61 | #endif | ||
| 62 | |||
| 63 | /* Not available in POSIX.1-1996 */ | ||
| 64 | #ifndef S_ISSOCK | ||
| 65 | # ifdef S_IFSOCK | ||
| 66 | # define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK) | ||
| 67 | # else | ||
| 68 | # define S_ISSOCK(m) 0 | ||
| 69 | # endif | ||
| 70 | #endif | ||
| 71 | |||
| 72 | /* S_ISREG not available on Windows */ | ||
| 73 | #ifndef S_ISREG | ||
| 74 | # ifdef S_IFREG | ||
| 75 | # define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) | ||
| 76 | # else | ||
| 77 | # define S_ISREG(m) 0 | ||
| 78 | # endif | ||
| 79 | #endif | ||
| 80 | |||
| 81 | /* S_ISBLK not available on Windows */ | ||
| 82 | #ifndef S_ISBLK | ||
| 83 | # ifdef S_IFBLK | ||
| 84 | # define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK) | ||
| 85 | # else | ||
| 86 | # define S_ISBLK(m) 0 | ||
| 87 | # endif | ||
| 88 | #endif | ||
| 89 | |||
| 90 | /* standard file protocol */ | ||
| 91 | |||
| 92 | typedef struct FileContext { | ||
| 93 | const AVClass *class; | ||
| 94 | int fd; | ||
| 95 | int trunc; | ||
| 96 | int blocksize; | ||
| 97 | int pkt_size; | ||
| 98 | int follow; | ||
| 99 | int seekable; | ||
| 100 | #if HAVE_DIRENT_H | ||
| 101 | DIR *dir; | ||
| 102 | #endif | ||
| 103 | } FileContext; | ||
| 104 | |||
| 105 | static const AVOption file_options[] = { | ||
| 106 | { "truncate", "truncate existing files on write", offsetof(FileContext, trunc), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, AV_OPT_FLAG_ENCODING_PARAM }, | ||
| 107 | { "blocksize", "set I/O operation maximum block size", offsetof(FileContext, blocksize), AV_OPT_TYPE_INT, { .i64 = INT_MAX }, 1, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM }, | ||
| 108 | { "follow", "Follow a file as it is being written", offsetof(FileContext, follow), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, AV_OPT_FLAG_DECODING_PARAM }, | ||
| 109 | { "seekable", "Sets if the file is seekable", offsetof(FileContext, seekable), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, 0, AV_OPT_FLAG_DECODING_PARAM | AV_OPT_FLAG_ENCODING_PARAM }, | ||
| 110 | { "pkt_size", "Maximum packet size", offsetof(FileContext, pkt_size), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, AV_OPT_FLAG_DECODING_PARAM | AV_OPT_FLAG_ENCODING_PARAM }, | ||
| 111 | { NULL } | ||
| 112 | }; | ||
| 113 | |||
| 114 | static const AVOption pipe_options[] = { | ||
| 115 | { "blocksize", "set I/O operation maximum block size", offsetof(FileContext, blocksize), AV_OPT_TYPE_INT, { .i64 = INT_MAX }, 1, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM }, | ||
| 116 | { "fd", "set file descriptor", offsetof(FileContext, fd), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM }, | ||
| 117 | { NULL } | ||
| 118 | }; | ||
| 119 | |||
| 120 | static const AVClass file_class = { | ||
| 121 | .class_name = "file", | ||
| 122 | .item_name = av_default_item_name, | ||
| 123 | .option = file_options, | ||
| 124 | .version = LIBAVUTIL_VERSION_INT, | ||
| 125 | }; | ||
| 126 | |||
| 127 | static const AVClass pipe_class = { | ||
| 128 | .class_name = "pipe", | ||
| 129 | .item_name = av_default_item_name, | ||
| 130 | .option = pipe_options, | ||
| 131 | .version = LIBAVUTIL_VERSION_INT, | ||
| 132 | }; | ||
| 133 | |||
| 134 | static const AVClass fd_class = { | ||
| 135 | .class_name = "fd", | ||
| 136 | .item_name = av_default_item_name, | ||
| 137 | .option = pipe_options, | ||
| 138 | .version = LIBAVUTIL_VERSION_INT, | ||
| 139 | }; | ||
| 140 | |||
| 141 | 223577 | static int file_read(URLContext *h, unsigned char *buf, int size) | |
| 142 | { | ||
| 143 | 223577 | FileContext *c = h->priv_data; | |
| 144 | int ret; | ||
| 145 | 223577 | size = FFMIN(size, c->blocksize); | |
| 146 | 223577 | ret = read(c->fd, buf, size); | |
| 147 |
3/4✓ Branch 0 taken 12632 times.
✓ Branch 1 taken 210945 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 12632 times.
|
223577 | if (ret == 0 && c->follow) |
| 148 | ✗ | return AVERROR(EAGAIN); | |
| 149 |
2/2✓ Branch 0 taken 12632 times.
✓ Branch 1 taken 210945 times.
|
223577 | if (ret == 0) |
| 150 | 12632 | return AVERROR_EOF; | |
| 151 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 210945 times.
|
210945 | return (ret == -1) ? AVERROR(errno) : ret; |
| 152 | } | ||
| 153 | |||
| 154 | 297081 | static int file_write(URLContext *h, const unsigned char *buf, int size) | |
| 155 | { | ||
| 156 | 297081 | FileContext *c = h->priv_data; | |
| 157 | int ret; | ||
| 158 | 297081 | size = FFMIN(size, c->blocksize); | |
| 159 | 297081 | ret = write(c->fd, buf, size); | |
| 160 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 297081 times.
|
297081 | return (ret == -1) ? AVERROR(errno) : ret; |
| 161 | } | ||
| 162 | |||
| 163 | ✗ | static int file_get_handle(URLContext *h) | |
| 164 | { | ||
| 165 | ✗ | FileContext *c = h->priv_data; | |
| 166 | ✗ | return c->fd; | |
| 167 | } | ||
| 168 | |||
| 169 | 52492 | static int file_check(URLContext *h, int mask) | |
| 170 | { | ||
| 171 | 52492 | int ret = 0; | |
| 172 | 52492 | const char *filename = h->filename; | |
| 173 | 52492 | av_strstart(filename, "file:", &filename); | |
| 174 | |||
| 175 | { | ||
| 176 | #if HAVE_ACCESS && defined(R_OK) | ||
| 177 |
2/2✓ Branch 1 taken 12428 times.
✓ Branch 2 taken 40064 times.
|
52492 | if (access(filename, F_OK) < 0) |
| 178 | 12428 | return AVERROR(errno); | |
| 179 |
1/2✓ Branch 0 taken 40064 times.
✗ Branch 1 not taken.
|
40064 | if (mask&AVIO_FLAG_READ) |
| 180 |
1/2✓ Branch 1 taken 40064 times.
✗ Branch 2 not taken.
|
40064 | if (access(filename, R_OK) >= 0) |
| 181 | 40064 | ret |= AVIO_FLAG_READ; | |
| 182 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 40064 times.
|
40064 | if (mask&AVIO_FLAG_WRITE) |
| 183 | ✗ | if (access(filename, W_OK) >= 0) | |
| 184 | ✗ | ret |= AVIO_FLAG_WRITE; | |
| 185 | #else | ||
| 186 | struct stat st; | ||
| 187 | ret = stat(filename, &st); | ||
| 188 | if (ret < 0) | ||
| 189 | return AVERROR(errno); | ||
| 190 | |||
| 191 | ret |= st.st_mode&S_IRUSR ? mask&AVIO_FLAG_READ : 0; | ||
| 192 | ret |= st.st_mode&S_IWUSR ? mask&AVIO_FLAG_WRITE : 0; | ||
| 193 | #endif | ||
| 194 | } | ||
| 195 | 40064 | return ret; | |
| 196 | } | ||
| 197 | |||
| 198 | #if CONFIG_FD_PROTOCOL || CONFIG_PIPE_PROTOCOL | ||
| 199 | 2756 | static int fd_dup(URLContext *h, int oldfd) | |
| 200 | { | ||
| 201 | int newfd; | ||
| 202 | |||
| 203 | #ifdef F_DUPFD_CLOEXEC | ||
| 204 | newfd = fcntl(oldfd, F_DUPFD_CLOEXEC, 0); | ||
| 205 | #else | ||
| 206 | 2756 | newfd = dup(oldfd); | |
| 207 | #endif | ||
| 208 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2756 times.
|
2756 | if (newfd == -1) |
| 209 | ✗ | return newfd; | |
| 210 | |||
| 211 | #if HAVE_FCNTL | ||
| 212 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 2756 times.
|
2756 | if (fcntl(newfd, F_SETFD, FD_CLOEXEC) == -1) |
| 213 | ✗ | av_log(h, AV_LOG_DEBUG, "Failed to set close on exec\n"); | |
| 214 | #endif | ||
| 215 | |||
| 216 | #if HAVE_SETMODE | ||
| 217 | setmode(newfd, O_BINARY); | ||
| 218 | #endif | ||
| 219 | 2756 | return newfd; | |
| 220 | } | ||
| 221 | #endif | ||
| 222 | |||
| 223 | 114057 | static int file_close(URLContext *h) | |
| 224 | { | ||
| 225 | 114057 | FileContext *c = h->priv_data; | |
| 226 | 114057 | int ret = close(c->fd); | |
| 227 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 114057 times.
|
114057 | return (ret == -1) ? AVERROR(errno) : 0; |
| 228 | } | ||
| 229 | |||
| 230 | /* XXX: use llseek */ | ||
| 231 | 379561 | static int64_t file_seek(URLContext *h, int64_t pos, int whence) | |
| 232 | { | ||
| 233 | 379561 | FileContext *c = h->priv_data; | |
| 234 | int64_t ret; | ||
| 235 | |||
| 236 |
2/2✓ Branch 0 taken 256540 times.
✓ Branch 1 taken 123021 times.
|
379561 | if (whence == AVSEEK_SIZE) { |
| 237 | struct stat st; | ||
| 238 | 256540 | ret = fstat(c->fd, &st); | |
| 239 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 256540 times.
✓ Branch 2 taken 256540 times.
✗ Branch 3 not taken.
|
256540 | return ret < 0 ? AVERROR(errno) : (S_ISFIFO(st.st_mode) ? 0 : st.st_size); |
| 240 | } | ||
| 241 | |||
| 242 | 123021 | ret = lseek(c->fd, pos, whence); | |
| 243 | |||
| 244 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 123021 times.
|
123021 | return ret < 0 ? AVERROR(errno) : ret; |
| 245 | } | ||
| 246 | |||
| 247 | #if CONFIG_FILE_PROTOCOL | ||
| 248 | |||
| 249 | ✗ | static int file_delete(URLContext *h) | |
| 250 | { | ||
| 251 | #if HAVE_UNISTD_H | ||
| 252 | int ret; | ||
| 253 | ✗ | const char *filename = h->filename; | |
| 254 | ✗ | av_strstart(filename, "file:", &filename); | |
| 255 | |||
| 256 | ✗ | ret = rmdir(filename); | |
| 257 | ✗ | if (ret < 0 && (errno == ENOTDIR | |
| 258 | # ifdef _WIN32 | ||
| 259 | || errno == EINVAL | ||
| 260 | # endif | ||
| 261 | )) | ||
| 262 | ✗ | ret = unlink(filename); | |
| 263 | ✗ | if (ret < 0) | |
| 264 | ✗ | return AVERROR(errno); | |
| 265 | |||
| 266 | ✗ | return ret; | |
| 267 | #else | ||
| 268 | return AVERROR(ENOSYS); | ||
| 269 | #endif /* HAVE_UNISTD_H */ | ||
| 270 | } | ||
| 271 | |||
| 272 | 101 | static int file_move(URLContext *h_src, URLContext *h_dst) | |
| 273 | { | ||
| 274 | 101 | const char *filename_src = h_src->filename; | |
| 275 | 101 | const char *filename_dst = h_dst->filename; | |
| 276 | 101 | av_strstart(filename_src, "file:", &filename_src); | |
| 277 | 101 | av_strstart(filename_dst, "file:", &filename_dst); | |
| 278 | |||
| 279 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 101 times.
|
101 | if (rename(filename_src, filename_dst) < 0) |
| 280 | ✗ | return AVERROR(errno); | |
| 281 | |||
| 282 | 101 | return 0; | |
| 283 | } | ||
| 284 | |||
| 285 | 111302 | static int file_open(URLContext *h, const char *filename, int flags) | |
| 286 | { | ||
| 287 | 111302 | FileContext *c = h->priv_data; | |
| 288 | int access; | ||
| 289 | int fd; | ||
| 290 | struct stat st; | ||
| 291 | |||
| 292 | 111302 | av_strstart(filename, "file:", &filename); | |
| 293 | |||
| 294 |
3/4✓ Branch 0 taken 4182 times.
✓ Branch 1 taken 107120 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 4182 times.
|
111302 | if (flags & AVIO_FLAG_WRITE && flags & AVIO_FLAG_READ) { |
| 295 | ✗ | access = O_CREAT | O_RDWR; | |
| 296 | ✗ | if (c->trunc) | |
| 297 | ✗ | access |= O_TRUNC; | |
| 298 |
2/2✓ Branch 0 taken 4182 times.
✓ Branch 1 taken 107120 times.
|
111302 | } else if (flags & AVIO_FLAG_WRITE) { |
| 299 | 4182 | access = O_CREAT | O_WRONLY; | |
| 300 |
1/2✓ Branch 0 taken 4182 times.
✗ Branch 1 not taken.
|
4182 | if (c->trunc) |
| 301 | 4182 | access |= O_TRUNC; | |
| 302 | } else { | ||
| 303 | 107120 | access = O_RDONLY; | |
| 304 | } | ||
| 305 | #ifdef O_BINARY | ||
| 306 | access |= O_BINARY; | ||
| 307 | #endif | ||
| 308 | 111302 | fd = avpriv_open(filename, access, 0666); | |
| 309 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 111301 times.
|
111302 | if (fd == -1) |
| 310 | 1 | return AVERROR(errno); | |
| 311 | 111301 | c->fd = fd; | |
| 312 | |||
| 313 |
2/4✓ Branch 1 taken 111301 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 111301 times.
|
111301 | h->is_streamed = !fstat(fd, &st) && S_ISFIFO(st.st_mode); |
| 314 | |||
| 315 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 111301 times.
|
111301 | if (c->pkt_size) { |
| 316 | ✗ | h->max_packet_size = c->pkt_size; | |
| 317 | } else { | ||
| 318 | /* Buffer writes more than the default 32k to improve throughput especially | ||
| 319 | * with networked file systems */ | ||
| 320 |
3/4✓ Branch 0 taken 111301 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4182 times.
✓ Branch 3 taken 107119 times.
|
111301 | if (!h->is_streamed && flags & AVIO_FLAG_WRITE) |
| 321 | 4182 | h->max_packet_size = 262144; | |
| 322 | } | ||
| 323 | /* Disable per-packet flushing by default to improve throughput especially | ||
| 324 | * with networked file systems */ | ||
| 325 |
3/4✓ Branch 0 taken 111301 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4182 times.
✓ Branch 3 taken 107119 times.
|
111301 | if (!h->is_streamed && flags & AVIO_FLAG_WRITE) |
| 326 | 4182 | h->min_packet_size = h->max_packet_size; | |
| 327 | |||
| 328 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 111301 times.
|
111301 | if (c->seekable >= 0) |
| 329 | ✗ | h->is_streamed = !c->seekable; | |
| 330 | |||
| 331 | 111301 | return 0; | |
| 332 | } | ||
| 333 | |||
| 334 | ✗ | static int file_open_dir(URLContext *h) | |
| 335 | { | ||
| 336 | #if HAVE_LSTAT | ||
| 337 | ✗ | FileContext *c = h->priv_data; | |
| 338 | |||
| 339 | ✗ | c->dir = opendir(h->filename); | |
| 340 | ✗ | if (!c->dir) | |
| 341 | ✗ | return AVERROR(errno); | |
| 342 | |||
| 343 | ✗ | return 0; | |
| 344 | #else | ||
| 345 | return AVERROR(ENOSYS); | ||
| 346 | #endif /* HAVE_LSTAT */ | ||
| 347 | } | ||
| 348 | |||
| 349 | ✗ | static int file_read_dir(URLContext *h, AVIODirEntry **next) | |
| 350 | { | ||
| 351 | #if HAVE_LSTAT | ||
| 352 | ✗ | FileContext *c = h->priv_data; | |
| 353 | struct dirent *dir; | ||
| 354 | ✗ | char *fullpath = NULL; | |
| 355 | |||
| 356 | ✗ | *next = ff_alloc_dir_entry(); | |
| 357 | ✗ | if (!*next) | |
| 358 | ✗ | return AVERROR(ENOMEM); | |
| 359 | do { | ||
| 360 | ✗ | errno = 0; | |
| 361 | ✗ | dir = readdir(c->dir); | |
| 362 | ✗ | if (!dir) { | |
| 363 | ✗ | av_freep(next); | |
| 364 | ✗ | return AVERROR(errno); | |
| 365 | } | ||
| 366 | ✗ | } while (!strcmp(dir->d_name, ".") || !strcmp(dir->d_name, "..")); | |
| 367 | |||
| 368 | ✗ | fullpath = av_append_path_component(h->filename, dir->d_name); | |
| 369 | ✗ | if (fullpath) { | |
| 370 | struct stat st; | ||
| 371 | ✗ | if (!lstat(fullpath, &st)) { | |
| 372 | ✗ | if (S_ISDIR(st.st_mode)) | |
| 373 | ✗ | (*next)->type = AVIO_ENTRY_DIRECTORY; | |
| 374 | ✗ | else if (S_ISFIFO(st.st_mode)) | |
| 375 | ✗ | (*next)->type = AVIO_ENTRY_NAMED_PIPE; | |
| 376 | ✗ | else if (S_ISCHR(st.st_mode)) | |
| 377 | ✗ | (*next)->type = AVIO_ENTRY_CHARACTER_DEVICE; | |
| 378 | ✗ | else if (S_ISBLK(st.st_mode)) | |
| 379 | ✗ | (*next)->type = AVIO_ENTRY_BLOCK_DEVICE; | |
| 380 | ✗ | else if (S_ISLNK(st.st_mode)) | |
| 381 | ✗ | (*next)->type = AVIO_ENTRY_SYMBOLIC_LINK; | |
| 382 | ✗ | else if (S_ISSOCK(st.st_mode)) | |
| 383 | ✗ | (*next)->type = AVIO_ENTRY_SOCKET; | |
| 384 | ✗ | else if (S_ISREG(st.st_mode)) | |
| 385 | ✗ | (*next)->type = AVIO_ENTRY_FILE; | |
| 386 | else | ||
| 387 | ✗ | (*next)->type = AVIO_ENTRY_UNKNOWN; | |
| 388 | |||
| 389 | ✗ | (*next)->group_id = st.st_gid; | |
| 390 | ✗ | (*next)->user_id = st.st_uid; | |
| 391 | ✗ | (*next)->size = st.st_size; | |
| 392 | ✗ | (*next)->filemode = st.st_mode & 0777; | |
| 393 | ✗ | (*next)->modification_timestamp = INT64_C(1000000) * st.st_mtime; | |
| 394 | ✗ | (*next)->access_timestamp = INT64_C(1000000) * st.st_atime; | |
| 395 | ✗ | (*next)->status_change_timestamp = INT64_C(1000000) * st.st_ctime; | |
| 396 | } | ||
| 397 | ✗ | av_free(fullpath); | |
| 398 | } | ||
| 399 | |||
| 400 | ✗ | (*next)->name = av_strdup(dir->d_name); | |
| 401 | ✗ | return 0; | |
| 402 | #else | ||
| 403 | return AVERROR(ENOSYS); | ||
| 404 | #endif /* HAVE_LSTAT */ | ||
| 405 | } | ||
| 406 | |||
| 407 | ✗ | static int file_close_dir(URLContext *h) | |
| 408 | { | ||
| 409 | #if HAVE_LSTAT | ||
| 410 | ✗ | FileContext *c = h->priv_data; | |
| 411 | ✗ | closedir(c->dir); | |
| 412 | ✗ | return 0; | |
| 413 | #else | ||
| 414 | return AVERROR(ENOSYS); | ||
| 415 | #endif /* HAVE_LSTAT */ | ||
| 416 | } | ||
| 417 | |||
| 418 | const URLProtocol ff_file_protocol = { | ||
| 419 | .name = "file", | ||
| 420 | .url_open = file_open, | ||
| 421 | .url_read = file_read, | ||
| 422 | .url_write = file_write, | ||
| 423 | .url_seek = file_seek, | ||
| 424 | .url_close = file_close, | ||
| 425 | .url_get_file_handle = file_get_handle, | ||
| 426 | .url_check = file_check, | ||
| 427 | .url_delete = file_delete, | ||
| 428 | .url_move = file_move, | ||
| 429 | .priv_data_size = sizeof(FileContext), | ||
| 430 | .priv_data_class = &file_class, | ||
| 431 | .url_open_dir = file_open_dir, | ||
| 432 | .url_read_dir = file_read_dir, | ||
| 433 | .url_close_dir = file_close_dir, | ||
| 434 | .default_whitelist = "file,crypto,data" | ||
| 435 | }; | ||
| 436 | |||
| 437 | #endif /* CONFIG_FILE_PROTOCOL */ | ||
| 438 | |||
| 439 | #if CONFIG_PIPE_PROTOCOL | ||
| 440 | |||
| 441 | 2756 | static int pipe_open(URLContext *h, const char *filename, int flags) | |
| 442 | { | ||
| 443 | 2756 | FileContext *c = h->priv_data; | |
| 444 | int fd; | ||
| 445 | char *final; | ||
| 446 | |||
| 447 |
1/2✓ Branch 0 taken 2756 times.
✗ Branch 1 not taken.
|
2756 | if (c->fd < 0) { |
| 448 | 2756 | av_strstart(filename, "pipe:", &filename); | |
| 449 | |||
| 450 |
2/2✓ Branch 0 taken 2729 times.
✓ Branch 1 taken 27 times.
|
2756 | if (!*filename) { |
| 451 |
1/2✓ Branch 0 taken 2729 times.
✗ Branch 1 not taken.
|
2729 | if (flags & AVIO_FLAG_WRITE) { |
| 452 | 2729 | fd = 1; | |
| 453 | } else { | ||
| 454 | ✗ | fd = 0; | |
| 455 | } | ||
| 456 | } else { | ||
| 457 | 27 | fd = strtol(filename, &final, 10); | |
| 458 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
|
27 | if (*final) /* No digits found, or something like 10ab */ |
| 459 | ✗ | return AVERROR(EINVAL); | |
| 460 | } | ||
| 461 | 2756 | c->fd = fd; | |
| 462 | } | ||
| 463 | |||
| 464 | 2756 | c->fd = fd_dup(h, c->fd); | |
| 465 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2756 times.
|
2756 | if (c->fd == -1) |
| 466 | ✗ | return AVERROR(errno); | |
| 467 | 2756 | h->is_streamed = 1; | |
| 468 | 2756 | return 0; | |
| 469 | } | ||
| 470 | |||
| 471 | const URLProtocol ff_pipe_protocol = { | ||
| 472 | .name = "pipe", | ||
| 473 | .url_open = pipe_open, | ||
| 474 | .url_read = file_read, | ||
| 475 | .url_write = file_write, | ||
| 476 | .url_close = file_close, | ||
| 477 | .url_get_file_handle = file_get_handle, | ||
| 478 | .url_check = file_check, | ||
| 479 | .priv_data_size = sizeof(FileContext), | ||
| 480 | .priv_data_class = &pipe_class, | ||
| 481 | .default_whitelist = "crypto,data" | ||
| 482 | }; | ||
| 483 | |||
| 484 | #endif /* CONFIG_PIPE_PROTOCOL */ | ||
| 485 | |||
| 486 | #if CONFIG_FD_PROTOCOL | ||
| 487 | |||
| 488 | ✗ | static int fd_open(URLContext *h, const char *filename, int flags) | |
| 489 | { | ||
| 490 | ✗ | FileContext *c = h->priv_data; | |
| 491 | struct stat st; | ||
| 492 | |||
| 493 | ✗ | if (strcmp(filename, "fd:") != 0) { | |
| 494 | ✗ | av_log(h, AV_LOG_ERROR, "Doesn't support pass file descriptor via URL," | |
| 495 | " please set it via -fd {num}\n"); | ||
| 496 | ✗ | return AVERROR(EINVAL); | |
| 497 | } | ||
| 498 | |||
| 499 | ✗ | if (c->fd < 0) { | |
| 500 | ✗ | if (flags & AVIO_FLAG_WRITE) { | |
| 501 | ✗ | c->fd = 1; | |
| 502 | } else { | ||
| 503 | ✗ | c->fd = 0; | |
| 504 | } | ||
| 505 | } | ||
| 506 | ✗ | if (fstat(c->fd, &st) < 0) | |
| 507 | ✗ | return AVERROR(errno); | |
| 508 | ✗ | h->is_streamed = !(S_ISREG(st.st_mode) || S_ISBLK(st.st_mode)); | |
| 509 | ✗ | c->fd = fd_dup(h, c->fd); | |
| 510 | ✗ | if (c->fd == -1) | |
| 511 | ✗ | return AVERROR(errno); | |
| 512 | |||
| 513 | ✗ | return 0; | |
| 514 | } | ||
| 515 | |||
| 516 | const URLProtocol ff_fd_protocol = { | ||
| 517 | .name = "fd", | ||
| 518 | .url_open = fd_open, | ||
| 519 | .url_read = file_read, | ||
| 520 | .url_write = file_write, | ||
| 521 | .url_seek = file_seek, | ||
| 522 | .url_close = file_close, | ||
| 523 | .url_get_file_handle = file_get_handle, | ||
| 524 | .url_check = file_check, | ||
| 525 | .priv_data_size = sizeof(FileContext), | ||
| 526 | .priv_data_class = &fd_class, | ||
| 527 | .default_whitelist = "crypto,data" | ||
| 528 | }; | ||
| 529 | |||
| 530 | #endif /* CONFIG_FD_PROTOCOL */ | ||
| 531 | |||
| 532 | #if CONFIG_ANDROID_CONTENT_PROTOCOL | ||
| 533 | #include <jni.h> | ||
| 534 | #include "libavcodec/ffjni.h" | ||
| 535 | #include "libavcodec/jni.h" | ||
| 536 | |||
| 537 | typedef struct JFields { | ||
| 538 | jclass uri_class; | ||
| 539 | jmethodID parse_id; | ||
| 540 | |||
| 541 | jclass context_class; | ||
| 542 | jmethodID get_content_resolver_id; | ||
| 543 | |||
| 544 | jclass content_resolver_class; | ||
| 545 | jmethodID open_file_descriptor_id; | ||
| 546 | |||
| 547 | jclass parcel_file_descriptor_class; | ||
| 548 | jmethodID detach_fd_id; | ||
| 549 | } JFields; | ||
| 550 | |||
| 551 | #define OFFSET(x) offsetof(JFields, x) | ||
| 552 | static const struct FFJniField jfields_mapping[] = { | ||
| 553 | { "android/net/Uri", NULL, NULL, FF_JNI_CLASS, OFFSET(uri_class), 1 }, | ||
| 554 | { "android/net/Uri", "parse", "(Ljava/lang/String;)Landroid/net/Uri;", FF_JNI_STATIC_METHOD, OFFSET(parse_id), 1 }, | ||
| 555 | |||
| 556 | { "android/content/Context", NULL, NULL, FF_JNI_CLASS, OFFSET(context_class), 1 }, | ||
| 557 | { "android/content/Context", "getContentResolver", "()Landroid/content/ContentResolver;", FF_JNI_METHOD, OFFSET(get_content_resolver_id), 1 }, | ||
| 558 | |||
| 559 | { "android/content/ContentResolver", NULL, NULL, FF_JNI_CLASS, OFFSET(content_resolver_class), 1 }, | ||
| 560 | { "android/content/ContentResolver", "openFileDescriptor", "(Landroid/net/Uri;Ljava/lang/String;)Landroid/os/ParcelFileDescriptor;", FF_JNI_METHOD, OFFSET(open_file_descriptor_id), 1 }, | ||
| 561 | |||
| 562 | { "android/os/ParcelFileDescriptor", NULL, NULL, FF_JNI_CLASS, OFFSET(parcel_file_descriptor_class), 1 }, | ||
| 563 | { "android/os/ParcelFileDescriptor", "detachFd", "()I", FF_JNI_METHOD, OFFSET(detach_fd_id), 1 }, | ||
| 564 | |||
| 565 | { NULL } | ||
| 566 | }; | ||
| 567 | #undef OFFSET | ||
| 568 | |||
| 569 | static int android_content_open(URLContext *h, const char *filename, int flags) | ||
| 570 | { | ||
| 571 | FileContext *c = h->priv_data; | ||
| 572 | int fd, ret; | ||
| 573 | struct stat st; | ||
| 574 | const char *mode_str = "r"; | ||
| 575 | |||
| 576 | JNIEnv *env; | ||
| 577 | JFields jfields = { 0 }; | ||
| 578 | jobject application_context = NULL; | ||
| 579 | jobject url = NULL; | ||
| 580 | jobject mode = NULL; | ||
| 581 | jobject uri = NULL; | ||
| 582 | jobject content_resolver = NULL; | ||
| 583 | jobject parcel_file_descriptor = NULL; | ||
| 584 | |||
| 585 | env = ff_jni_get_env(c); | ||
| 586 | if (!env) { | ||
| 587 | return AVERROR(EINVAL); | ||
| 588 | } | ||
| 589 | |||
| 590 | ret = ff_jni_init_jfields(env, &jfields, jfields_mapping, 0, c); | ||
| 591 | if (ret < 0) { | ||
| 592 | av_log(c, AV_LOG_ERROR, "failed to initialize jni fields\n"); | ||
| 593 | return ret; | ||
| 594 | } | ||
| 595 | |||
| 596 | application_context = av_jni_get_android_app_ctx(); | ||
| 597 | if (!application_context) { | ||
| 598 | av_log(c, AV_LOG_ERROR, "application context is not set\n"); | ||
| 599 | ret = AVERROR_EXTERNAL; | ||
| 600 | goto done; | ||
| 601 | } | ||
| 602 | |||
| 603 | url = ff_jni_utf_chars_to_jstring(env, filename, c); | ||
| 604 | if (!url) { | ||
| 605 | ret = AVERROR_EXTERNAL; | ||
| 606 | goto done; | ||
| 607 | } | ||
| 608 | |||
| 609 | if (flags & AVIO_FLAG_WRITE && flags & AVIO_FLAG_READ) | ||
| 610 | mode_str = "rw"; | ||
| 611 | else if (flags & AVIO_FLAG_WRITE) | ||
| 612 | mode_str = "w"; | ||
| 613 | |||
| 614 | mode = ff_jni_utf_chars_to_jstring(env, mode_str, c); | ||
| 615 | if (!mode) { | ||
| 616 | ret = AVERROR_EXTERNAL; | ||
| 617 | goto done; | ||
| 618 | } | ||
| 619 | |||
| 620 | uri = (*env)->CallStaticObjectMethod(env, jfields.uri_class, jfields.parse_id, url); | ||
| 621 | ret = ff_jni_exception_check(env, 1, c); | ||
| 622 | if (ret < 0) | ||
| 623 | goto done; | ||
| 624 | |||
| 625 | content_resolver = (*env)->CallObjectMethod(env, application_context, jfields.get_content_resolver_id); | ||
| 626 | ret = ff_jni_exception_check(env, 1, c); | ||
| 627 | if (ret < 0) | ||
| 628 | goto done; | ||
| 629 | |||
| 630 | parcel_file_descriptor = (*env)->CallObjectMethod(env, content_resolver, jfields.open_file_descriptor_id, uri, mode); | ||
| 631 | ret = ff_jni_exception_check(env, 1, c); | ||
| 632 | if (ret < 0) | ||
| 633 | goto done; | ||
| 634 | if (!parcel_file_descriptor) { | ||
| 635 | av_log(c, AV_LOG_ERROR, "file descriptor is null\n"); | ||
| 636 | ret = AVERROR_EXTERNAL; | ||
| 637 | goto done; | ||
| 638 | } | ||
| 639 | |||
| 640 | fd = (*env)->CallIntMethod(env, parcel_file_descriptor, jfields.detach_fd_id); | ||
| 641 | ret = ff_jni_exception_check(env, 1, c); | ||
| 642 | if (ret < 0) | ||
| 643 | goto done; | ||
| 644 | |||
| 645 | if (fstat(fd, &st) < 0) { | ||
| 646 | close(fd); | ||
| 647 | return AVERROR(errno); | ||
| 648 | } | ||
| 649 | |||
| 650 | c->fd = fd; | ||
| 651 | h->is_streamed = !(S_ISREG(st.st_mode) || S_ISBLK(st.st_mode)); | ||
| 652 | |||
| 653 | done: | ||
| 654 | (*env)->DeleteLocalRef(env, url); | ||
| 655 | (*env)->DeleteLocalRef(env, mode); | ||
| 656 | (*env)->DeleteLocalRef(env, uri); | ||
| 657 | (*env)->DeleteLocalRef(env, content_resolver); | ||
| 658 | (*env)->DeleteLocalRef(env, parcel_file_descriptor); | ||
| 659 | ff_jni_reset_jfields(env, &jfields, jfields_mapping, 0, c); | ||
| 660 | |||
| 661 | return ret; | ||
| 662 | } | ||
| 663 | |||
| 664 | static const AVOption android_content_options[] = { | ||
| 665 | { "blocksize", "set I/O operation maximum block size", offsetof(FileContext, blocksize), AV_OPT_TYPE_INT, { .i64 = INT_MAX }, 1, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM }, | ||
| 666 | { NULL } | ||
| 667 | }; | ||
| 668 | |||
| 669 | static const AVClass android_content_class = { | ||
| 670 | .class_name = "android_content", | ||
| 671 | .item_name = av_default_item_name, | ||
| 672 | .option = android_content_options, | ||
| 673 | .version = LIBAVUTIL_VERSION_INT, | ||
| 674 | }; | ||
| 675 | |||
| 676 | const URLProtocol ff_android_content_protocol = { | ||
| 677 | .name = "content", | ||
| 678 | .url_open = android_content_open, | ||
| 679 | .url_read = file_read, | ||
| 680 | .url_write = file_write, | ||
| 681 | .url_seek = file_seek, | ||
| 682 | .url_close = file_close, | ||
| 683 | .url_get_file_handle = file_get_handle, | ||
| 684 | .url_check = NULL, | ||
| 685 | .priv_data_size = sizeof(FileContext), | ||
| 686 | .priv_data_class = &android_content_class, | ||
| 687 | }; | ||
| 688 | |||
| 689 | #endif /* CONFIG_ANDROID_CONTENT_PROTOCOL */ | ||
| 690 |