blob: b68cd8bd79fb446124eafc49a6f9f2034ba594b5
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; |
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 | }; |
384 |