]> git.sesse.net Git - ffmpeg/blob - libavformat/libsmbclient.c
avformat/avio: Add Metacube support
[ffmpeg] / libavformat / libsmbclient.c
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
28 typedef 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
39 static 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
48 static 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
81 static 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
95 static 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
136 static 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
158 static 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
172 static 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
186 static 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
207 static 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
279 static 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
290 static 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
326 cleanup:
327     libsmbc_close(h);
328     return ret;
329 }
330
331 static 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
346 cleanup:
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
354 static 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
361 static 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
368 const 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 };