1/* 2 * Copyright (c) 2014 Lukasz Marek <lukasz.m.luki@gmail.com> 3 * 4 * This file is part of FFmpeg. 5 * 6 * FFmpeg is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU Lesser General Public 8 * License as published by the Free Software Foundation; either 9 * version 2.1 of the License, or (at your option) any later version. 10 * 11 * FFmpeg is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 * Lesser General Public License for more details. 15 * 16 * You should have received a copy of the GNU Lesser General Public 17 * License along with FFmpeg; if not, write to the Free Software 18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 */ 20 21#include <libsmbclient.h> 22#include "libavutil/avstring.h" 23#include "libavutil/opt.h" 24#include "avformat.h" 25#include "internal.h" 26#include "url.h" 27 28typedef struct { 29 const AVClass *class; 30 SMBCCTX *ctx; 31 int dh; 32 int fd; 33 int64_t filesize; 34 int trunc; 35 int timeout; 36 char *workgroup; 37} LIBSMBContext; 38 39static void libsmbc_get_auth_data(SMBCCTX *c, const char *server, const char *share, 40 char *workgroup, int workgroup_len, 41 char *username, int username_len, 42 char *password, int password_len) 43{ 44 /* Do nothing yet. Credentials are passed via url. 45 * Callback must exists, there might be a segmentation fault otherwise. */ 46} 47 48static av_cold int libsmbc_connect(URLContext *h) 49{ 50 LIBSMBContext *libsmbc = h->priv_data; 51 52 libsmbc->ctx = smbc_new_context(); 53 if (!libsmbc->ctx) { 54 int ret = AVERROR(errno); 55 av_log(h, AV_LOG_ERROR, "Cannot create context: %s.\n", strerror(errno)); 56 return ret; 57 } 58 if (!smbc_init_context(libsmbc->ctx)) { 59 int ret = AVERROR(errno); 60 av_log(h, AV_LOG_ERROR, "Cannot initialize context: %s.\n", strerror(errno)); 61 return ret; 62 } 63 smbc_set_context(libsmbc->ctx); 64 65 smbc_setOptionUserData(libsmbc->ctx, h); 66 smbc_setFunctionAuthDataWithContext(libsmbc->ctx, libsmbc_get_auth_data); 67 68 if (libsmbc->timeout != -1) 69 smbc_setTimeout(libsmbc->ctx, libsmbc->timeout); 70 if (libsmbc->workgroup) 71 smbc_setWorkgroup(libsmbc->ctx, libsmbc->workgroup); 72 73 if (smbc_init(NULL, 0) < 0) { 74 int ret = AVERROR(errno); 75 av_log(h, AV_LOG_ERROR, "Initialization failed: %s\n", strerror(errno)); 76 return ret; 77 } 78 return 0; 79} 80 81static av_cold int libsmbc_close(URLContext *h) 82{ 83 LIBSMBContext *libsmbc = h->priv_data; 84 if (libsmbc->fd >= 0) { 85 smbc_close(libsmbc->fd); 86 libsmbc->fd = -1; 87 } 88 if (libsmbc->ctx) { 89 smbc_free_context(libsmbc->ctx, 1); 90 libsmbc->ctx = NULL; 91 } 92 return 0; 93} 94 95static av_cold int libsmbc_open(URLContext *h, const char *url, int flags) 96{ 97 LIBSMBContext *libsmbc = h->priv_data; 98 int access, ret; 99 struct stat st; 100 101 libsmbc->fd = -1; 102 libsmbc->filesize = -1; 103 104 if ((ret = libsmbc_connect(h)) < 0) 105 goto fail; 106 107 if ((flags & AVIO_FLAG_WRITE) && (flags & AVIO_FLAG_READ)) { 108 access = O_CREAT | O_RDWR; 109 if (libsmbc->trunc) 110 access |= O_TRUNC; 111 } else if (flags & AVIO_FLAG_WRITE) { 112 access = O_CREAT | O_WRONLY; 113 if (libsmbc->trunc) 114 access |= O_TRUNC; 115 } else 116 access = O_RDONLY; 117 118 /* 0666 = -rw-rw-rw- = read+write for everyone, minus umask */ 119 if ((libsmbc->fd = smbc_open(url, access, 0666)) < 0) { 120 ret = AVERROR(errno); 121 av_log(h, AV_LOG_ERROR, "File open failed: %s\n", strerror(errno)); 122 goto fail; 123 } 124 125 if (smbc_fstat(libsmbc->fd, &st) < 0) 126 av_log(h, AV_LOG_WARNING, "Cannot stat file: %s\n", strerror(errno)); 127 else 128 libsmbc->filesize = st.st_size; 129 130 return 0; 131 fail: 132 libsmbc_close(h); 133 return ret; 134} 135 136static int64_t libsmbc_seek(URLContext *h, int64_t pos, int whence) 137{ 138 LIBSMBContext *libsmbc = h->priv_data; 139 int64_t newpos; 140 141 if (whence == AVSEEK_SIZE) { 142 if (libsmbc->filesize == -1) { 143 av_log(h, AV_LOG_ERROR, "Error during seeking: filesize is unknown.\n"); 144 return AVERROR(EIO); 145 } else 146 return libsmbc->filesize; 147 } 148 149 if ((newpos = smbc_lseek(libsmbc->fd, pos, whence)) < 0) { 150 int err = errno; 151 av_log(h, AV_LOG_ERROR, "Error during seeking: %s\n", strerror(err)); 152 return AVERROR(err); 153 } 154 155 return newpos; 156} 157 158static int libsmbc_read(URLContext *h, unsigned char *buf, int size) 159{ 160 LIBSMBContext *libsmbc = h->priv_data; 161 int bytes_read; 162 163 if ((bytes_read = smbc_read(libsmbc->fd, buf, size)) < 0) { 164 int ret = AVERROR(errno); 165 av_log(h, AV_LOG_ERROR, "Read error: %s\n", strerror(errno)); 166 return ret; 167 } 168 169 return bytes_read ? bytes_read : AVERROR_EOF; 170} 171 172static int libsmbc_write(URLContext *h, const unsigned char *buf, int size) 173{ 174 LIBSMBContext *libsmbc = h->priv_data; 175 int bytes_written; 176 177 if ((bytes_written = smbc_write(libsmbc->fd, buf, size)) < 0) { 178 int ret = AVERROR(errno); 179 av_log(h, AV_LOG_ERROR, "Write error: %s\n", strerror(errno)); 180 return ret; 181 } 182 183 return bytes_written; 184} 185 186static int libsmbc_open_dir(URLContext *h) 187{ 188 LIBSMBContext *libsmbc = h->priv_data; 189 int ret; 190 191 if ((ret = libsmbc_connect(h)) < 0) 192 goto fail; 193 194 if ((libsmbc->dh = smbc_opendir(h->filename)) < 0) { 195 ret = AVERROR(errno); 196 av_log(h, AV_LOG_ERROR, "Error opening dir: %s\n", strerror(errno)); 197 goto fail; 198 } 199 200 return 0; 201 202 fail: 203 libsmbc_close(h); 204 return ret; 205} 206 207static int libsmbc_read_dir(URLContext *h, AVIODirEntry **next) 208{ 209 LIBSMBContext *libsmbc = h->priv_data; 210 AVIODirEntry *entry; 211 struct smbc_dirent *dirent = NULL; 212 char *url = NULL; 213 int skip_entry; 214 215 *next = entry = ff_alloc_dir_entry(); 216 if (!entry) 217 return AVERROR(ENOMEM); 218 219 do { 220 skip_entry = 0; 221 dirent = smbc_readdir(libsmbc->dh); 222 if (!dirent) { 223 av_freep(next); 224 return 0; 225 } 226 switch (dirent->smbc_type) { 227 case SMBC_DIR: 228 entry->type = AVIO_ENTRY_DIRECTORY; 229 break; 230 case SMBC_FILE: 231 entry->type = AVIO_ENTRY_FILE; 232 break; 233 case SMBC_FILE_SHARE: 234 entry->type = AVIO_ENTRY_SHARE; 235 break; 236 case SMBC_SERVER: 237 entry->type = AVIO_ENTRY_SERVER; 238 break; 239 case SMBC_WORKGROUP: 240 entry->type = AVIO_ENTRY_WORKGROUP; 241 break; 242 case SMBC_COMMS_SHARE: 243 case SMBC_IPC_SHARE: 244 case SMBC_PRINTER_SHARE: 245 skip_entry = 1; 246 break; 247 case SMBC_LINK: 248 default: 249 entry->type = AVIO_ENTRY_UNKNOWN; 250 break; 251 } 252 } while (skip_entry || !strcmp(dirent->name, ".") || 253 !strcmp(dirent->name, "..")); 254 255 entry->name = av_strdup(dirent->name); 256 if (!entry->name) { 257 av_freep(next); 258 return AVERROR(ENOMEM); 259 } 260 261 url = av_append_path_component(h->filename, dirent->name); 262 if (url) { 263 struct stat st; 264 if (!smbc_stat(url, &st)) { 265 entry->group_id = st.st_gid; 266 entry->user_id = st.st_uid; 267 entry->size = st.st_size; 268 entry->filemode = st.st_mode & 0777; 269 entry->modification_timestamp = INT64_C(1000000) * st.st_mtime; 270 entry->access_timestamp = INT64_C(1000000) * st.st_atime; 271 entry->status_change_timestamp = INT64_C(1000000) * st.st_ctime; 272 } 273 av_free(url); 274 } 275 276 return 0; 277} 278 279static int libsmbc_close_dir(URLContext *h) 280{ 281 LIBSMBContext *libsmbc = h->priv_data; 282 if (libsmbc->dh >= 0) { 283 smbc_closedir(libsmbc->dh); 284 libsmbc->dh = -1; 285 } 286 libsmbc_close(h); 287 return 0; 288} 289 290static int libsmbc_delete(URLContext *h) 291{ 292 LIBSMBContext *libsmbc = h->priv_data; 293 int ret; 294 struct stat st; 295 296 if ((ret = libsmbc_connect(h)) < 0) 297 goto cleanup; 298 299 if ((libsmbc->fd = smbc_open(h->filename, O_WRONLY, 0666)) < 0) { 300 ret = AVERROR(errno); 301 goto cleanup; 302 } 303 304 if (smbc_fstat(libsmbc->fd, &st) < 0) { 305 ret = AVERROR(errno); 306 goto cleanup; 307 } 308 309 smbc_close(libsmbc->fd); 310 libsmbc->fd = -1; 311 312 if (S_ISDIR(st.st_mode)) { 313 if (smbc_rmdir(h->filename) < 0) { 314 ret = AVERROR(errno); 315 goto cleanup; 316 } 317 } else { 318 if (smbc_unlink(h->filename) < 0) { 319 ret = AVERROR(errno); 320 goto cleanup; 321 } 322 } 323 324 ret = 0; 325 326cleanup: 327 libsmbc_close(h); 328 return ret; 329} 330 331static int libsmbc_move(URLContext *h_src, URLContext *h_dst) 332{ 333 LIBSMBContext *libsmbc = h_src->priv_data; 334 int ret; 335 336 if ((ret = libsmbc_connect(h_src)) < 0) 337 goto cleanup; 338 339 if ((libsmbc->dh = smbc_rename(h_src->filename, h_dst->filename)) < 0) { 340 ret = AVERROR(errno); 341 goto cleanup; 342 } 343 344 ret = 0; 345 346cleanup: 347 libsmbc_close(h_src); 348 return ret; 349} 350 351#define OFFSET(x) offsetof(LIBSMBContext, x) 352#define D AV_OPT_FLAG_DECODING_PARAM 353#define E AV_OPT_FLAG_ENCODING_PARAM 354static const AVOption options[] = { 355 {"timeout", "set timeout in ms of socket I/O operations", OFFSET(timeout), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, D|E }, 356 {"truncate", "truncate existing files on write", OFFSET(trunc), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 1, E }, 357 {"workgroup", "set the workgroup used for making connections", OFFSET(workgroup), AV_OPT_TYPE_STRING, { 0 }, 0, 0, D|E }, 358 {NULL} 359}; 360 361static const AVClass libsmbclient_context_class = { 362 .class_name = "libsmbc", 363 .item_name = av_default_item_name, 364 .option = options, 365 .version = LIBAVUTIL_VERSION_INT, 366}; 367 368const URLProtocol ff_libsmbclient_protocol = { 369 .name = "smb", 370 .url_open = libsmbc_open, 371 .url_read = libsmbc_read, 372 .url_write = libsmbc_write, 373 .url_seek = libsmbc_seek, 374 .url_close = libsmbc_close, 375 .url_delete = libsmbc_delete, 376 .url_move = libsmbc_move, 377 .url_open_dir = libsmbc_open_dir, 378 .url_read_dir = libsmbc_read_dir, 379 .url_close_dir = libsmbc_close_dir, 380 .priv_data_size = sizeof(LIBSMBContext), 381 .priv_data_class = &libsmbclient_context_class, 382 .flags = URL_PROTOCOL_FLAG_NETWORK, 383}; 384