Ticket #3406: internal.c

File internal.c, 16.0 KB (added by sand, 7 years ago)
Line 
1/* Virtual File System: SFTP file system.
2   The internal functions
3
4   Copyright (C) 2011-2016
5   Free Software Foundation, Inc.
6
7   Written by:
8   Ilia Maslakov <il.smind@gmail.com>, 2011
9   Slava Zanko <slavazanko@gmail.com>, 2011, 2012
10
11   This file is part of the Midnight Commander.
12
13   The Midnight Commander is free software: you can redistribute it
14   and/or modify it under the terms of the GNU General Public License as
15   published by the Free Software Foundation, either version 3 of the License,
16   or (at your option) any later version.
17
18   The Midnight Commander is distributed in the hope that it will be useful,
19   but WITHOUT ANY WARRANTY; without even the implied warranty of
20   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21   GNU General Public License for more details.
22
23   You should have received a copy of the GNU General Public License
24   along with this program.  If not, see <http://www.gnu.org/licenses/>.
25 */
26
27#include <config.h>
28#include <errno.h>
29
30#include "lib/global.h"
31#include "lib/util.h"
32
33#include "src/filemanager/ioblksize.h"          /* IO_BUFSIZE */
34
35#include "internal.h"
36
37
38
39/*** global variables ****************************************************************************/
40
41GString *sftpfs_filename_buffer = NULL;
42
43/*** file scope macro definitions ****************************************************************/
44#define mc_return_val_if_error_and_free(mcerror, mcvalue, to_free) \
45    do {                                                           \
46        if (mcerror != NULL && *mcerror != NULL)                   \
47        {                                                          \
48            if (to_free != NULL)                                   \
49                g_free(to_free);                                   \
50            return mcvalue;                                        \
51        }                                                          \
52    } while (0)
53
54#define _is_sftp_proto_error(super_data, res, libssh_errno)             \
55    ((res == LIBSSH2_ERROR_SFTP_PROTOCOL) &&                            \
56     (libssh2_sftp_last_error((super_data)->sftp_session) == libssh_errno))
57
58/*** file scope type declarations ****************************************************************/
59
60/*** file scope variables ************************************************************************/
61
62/*** file scope functions ************************************************************************/
63
64static int
65_waitsocket_or_error(sftpfs_super_data_t *super_data, int res,
66                     GError ** mcerror, void *to_free)
67{
68    if (res != LIBSSH2_ERROR_EAGAIN)
69    {
70        sftpfs_ssherror_to_gliberror (super_data, res, mcerror);
71        if (to_free != NULL)
72            g_free(to_free);
73        return -1;
74    }
75
76    sftpfs_waitsocket (super_data, mcerror);
77    mc_return_val_if_error_and_free (mcerror, -1, to_free);
78
79    return 0;
80}
81
82/* --------------------------------------------------------------------------------------------- */
83static int
84_sftpfs_op_init(sftpfs_super_data_t ** super_data,
85                const vfs_path_element_t ** path_element,
86                const vfs_path_t * vpath, GError ** mcerror)
87{
88    struct vfs_s_super *super;
89
90    mc_return_val_if_error (mcerror, -1);
91
92    *path_element = vfs_path_get_by_index (vpath, -1);
93
94    if (vfs_s_get_path (vpath, &super, 0) == NULL)
95        return -1;
96
97    if (super == NULL)
98        return -1;
99
100    *super_data = (sftpfs_super_data_t *) super->data;
101    if ((*super_data)->sftp_session == NULL)
102        return -1;
103
104    return 0;
105}
106
107/* --------------------------------------------------------------------------------------------- */
108
109static int
110_sftpfs_stat (sftpfs_super_data_t ** super_data,
111              const vfs_path_element_t ** path_element,
112              const vfs_path_t * vpath, GError ** mcerror, const int stat_type,
113              LIBSSH2_SFTP_ATTRIBUTES *attrs)
114{
115    int res;
116
117    if (_sftpfs_op_init(super_data, path_element, vpath, mcerror) != 0)
118        return -1;
119
120    do
121    {
122        const char *fixfname;
123
124        fixfname = sftpfs_fix_filename ((*path_element)->path);
125
126        res = libssh2_sftp_stat_ex ((*super_data)->sftp_session, fixfname,
127                                    sftpfs_filename_buffer->len,
128                                    stat_type, attrs);
129
130        if (res >= 0)
131            break;
132
133        if (_is_sftp_proto_error(*super_data, res,
134                                LIBSSH2_FX_PERMISSION_DENIED))
135            return EACCES;
136
137        if (_is_sftp_proto_error(*super_data, res, LIBSSH2_FX_NO_SUCH_FILE))
138            return ENOENT;
139
140        if (_waitsocket_or_error(*super_data, res, mcerror, NULL) != 0)
141            return -1;
142    }
143    while (res == LIBSSH2_ERROR_EAGAIN);
144
145    return res;
146}
147
148/* --------------------------------------------------------------------------------------------- */
149
150static void
151_sftpfs_attr_to_buf(struct stat *buf, LIBSSH2_SFTP_ATTRIBUTES *attrs)
152{
153    if ((attrs->flags & LIBSSH2_SFTP_ATTR_UIDGID) != 0)
154    {
155        buf->st_uid = attrs->uid;
156        buf->st_gid = attrs->gid;
157    }
158
159    if ((attrs->flags & LIBSSH2_SFTP_ATTR_ACMODTIME) != 0)
160    {
161        buf->st_atime = attrs->atime;
162        buf->st_mtime = attrs->mtime;
163        buf->st_ctime = attrs->mtime;
164    }
165
166    if ((attrs->flags & LIBSSH2_SFTP_ATTR_SIZE) != 0)
167        buf->st_size = attrs->filesize;
168
169    if ((attrs->flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) != 0)
170        buf->st_mode = attrs->permissions;
171
172    /* patch or segfault here */
173    buf->st_blksize = IO_BUFSIZE;
174    buf->st_blocks = 1 + ((buf->st_size - 1) / buf->st_blksize);
175}
176
177/* --------------------------------------------------------------------------------------------- */
178/*** public functions ****************************************************************************/
179/* --------------------------------------------------------------------------------------------- */
180/**
181 * Convert libssh error to GError object.
182 *
183 * @param super_data   extra data for SFTP connection
184 * @param libssh_errno errno from libssh
185 * @param mcerror      pointer to the error object
186 */
187
188void
189sftpfs_ssherror_to_gliberror (sftpfs_super_data_t * super_data, int libssh_errno, GError ** mcerror)
190{
191    char *err = NULL;
192    int err_len;
193
194    mc_return_if_error (mcerror);
195
196    libssh2_session_last_error (super_data->session, &err, &err_len, 1);
197    if (libssh_errno == LIBSSH2_ERROR_SFTP_PROTOCOL &&
198        super_data->sftp_session != NULL)
199    {
200        mc_propagate_error (mcerror, libssh_errno, "%s %lu", err,
201                            libssh2_sftp_last_error(super_data->sftp_session));
202    } else {
203        mc_propagate_error (mcerror, libssh_errno, "%s", err);
204    }
205    g_free (err);
206}
207
208/* --------------------------------------------------------------------------------------------- */
209/**
210 * Fix filename for SFTP operations: add leading slash to file name.
211 *
212 * @param file_name file name
213 * @return newly allocated string contains the file name with leading slash
214 */
215
216const char *
217sftpfs_fix_filename (const char *file_name)
218{
219    g_string_printf (sftpfs_filename_buffer, "%c%s", PATH_SEP, file_name);
220    return sftpfs_filename_buffer->str;
221}
222
223/* --------------------------------------------------------------------------------------------- */
224/**
225 * Awaiting for any activity on socket.
226 *
227 * @param super_data extra data for SFTP connection
228 * @param mcerror    pointer to the error object
229 * @return 0 if success, negative value otherwise
230 */
231
232int
233sftpfs_waitsocket (sftpfs_super_data_t * super_data, GError ** mcerror)
234{
235    struct timeval timeout = { 10, 0 };
236    fd_set fd;
237    fd_set *writefd = NULL;
238    fd_set *readfd = NULL;
239    int dir;
240
241    mc_return_val_if_error (mcerror, -1);
242
243    FD_ZERO (&fd);
244    FD_SET (super_data->socket_handle, &fd);
245
246    /* now make sure we wait in the correct direction */
247    dir = libssh2_session_block_directions (super_data->session);
248
249    if ((dir & LIBSSH2_SESSION_BLOCK_INBOUND) != 0)
250        readfd = &fd;
251
252    if ((dir & LIBSSH2_SESSION_BLOCK_OUTBOUND) != 0)
253        writefd = &fd;
254
255    return select (super_data->socket_handle + 1, readfd, writefd, NULL, &timeout);
256}
257
258/* --------------------------------------------------------------------------------------------- */
259/**
260 * Getting information about a symbolic link.
261 *
262 * @param vpath   path to file, directory or symbolic link
263 * @param buf     buffer for store stat-info
264 * @param mcerror pointer to error object
265 * @return 0 if success, negative value otherwise
266 */
267
268int
269sftpfs_lstat (const vfs_path_t * vpath, struct stat *buf, GError ** mcerror)
270{
271    const vfs_path_element_t *path_element;
272    sftpfs_super_data_t *super_data;
273    LIBSSH2_SFTP_ATTRIBUTES attrs;
274    int res;
275
276    res = _sftpfs_stat (&super_data, &path_element, vpath, mcerror,
277                        LIBSSH2_SFTP_LSTAT, &attrs);
278
279    if (res == -1 || res == EACCES || res == ENOENT)
280        return res;
281
282    _sftpfs_attr_to_buf(buf, &attrs);
283
284    return 0;
285}
286
287/* --------------------------------------------------------------------------------------------- */
288/**
289 * Getting information about a file or directory.
290 *
291 * @param vpath   path to file or directory
292 * @param buf     buffer for store stat-info
293 * @param mcerror pointer to error object
294 * @return 0 if success, negative value otherwise
295 */
296
297int
298sftpfs_stat (const vfs_path_t * vpath, struct stat *buf, GError ** mcerror)
299{
300    const vfs_path_element_t *path_element;
301    sftpfs_super_data_t *super_data;
302    LIBSSH2_SFTP_ATTRIBUTES attrs;
303    int res;
304
305    res = _sftpfs_stat (&super_data, &path_element, vpath, mcerror,
306                        LIBSSH2_SFTP_STAT, &attrs);
307
308    if (res == -1 || res == EACCES || res == ENOENT)
309        return res;
310
311    buf->st_nlink = 1;
312
313    _sftpfs_attr_to_buf(buf, &attrs);
314
315    return 0;
316}
317
318/* --------------------------------------------------------------------------------------------- */
319/**
320 * Read value of a symbolic link.
321 *
322 * @param vpath   path to file or directory
323 * @param buf     buffer for store stat-info
324 * @param size    buffer size
325 * @param mcerror pointer to error object
326 * @return 0 if success, negative value otherwise
327 */
328
329int
330sftpfs_readlink (const vfs_path_t * vpath, char *buf, size_t size, GError ** mcerror)
331{
332    const vfs_path_element_t *path_element;
333    sftpfs_super_data_t *super_data;
334    int res;
335
336    if (_sftpfs_op_init(&super_data, &path_element, vpath, mcerror) != 0)
337        return -1;
338
339    do
340    {
341        const char *fixfname;
342
343        fixfname = sftpfs_fix_filename (path_element->path);
344
345        res = libssh2_sftp_readlink (super_data->sftp_session, fixfname,
346                                     buf, size);
347        if (res >= 0)
348            break;
349
350        if (_waitsocket_or_error(super_data, res,mcerror, NULL) != 0)
351            return -1;
352    }
353    while (res == LIBSSH2_ERROR_EAGAIN);
354
355    return res;
356}
357
358/* --------------------------------------------------------------------------------------------- */
359/**
360 * Create symlink to file or directory
361 *
362 * @param vpath1  path to file or directory
363 * @param vpath2  path to symlink
364 * @param mcerror pointer to error object
365 * @return 0 if success, negative value otherwise
366 */
367
368int
369sftpfs_symlink (const vfs_path_t * vpath1, const vfs_path_t * vpath2, GError ** mcerror)
370{
371    const vfs_path_element_t *path_element1;
372    const vfs_path_element_t *path_element2;
373    sftpfs_super_data_t *super_data;
374    char *tmp_path;
375    int res;
376
377    if (_sftpfs_op_init(&super_data, &path_element2, vpath2, mcerror) != 0)
378        return -1;
379
380    tmp_path = g_strdup_printf ("%c%s", PATH_SEP, path_element2->path);
381    path_element1 = vfs_path_get_by_index (vpath1, -1);
382
383    do
384    {
385        const char *fixfname;
386
387        fixfname = sftpfs_fix_filename (path_element1->path);
388
389        res = libssh2_sftp_symlink_ex (super_data->sftp_session, fixfname,
390                                       sftpfs_filename_buffer->len, tmp_path,
391                                       strlen (tmp_path), LIBSSH2_SFTP_SYMLINK);
392        if (res >= 0)
393            break;
394
395        if (_waitsocket_or_error(super_data, res,mcerror, tmp_path) != 0)
396            return -1;
397    }
398    while (res == LIBSSH2_ERROR_EAGAIN);
399    g_free (tmp_path);
400
401    return 0;
402}
403
404/* --------------------------------------------------------------------------------------------- */
405/**
406 * Changes the permissions of the file.
407 *
408 * @param vpath   path to file or directory
409 * @param mode    mode (see man 2 open)
410 * @param mcerror pointer to error object
411 * @return 0 if success, negative value otherwise
412 */
413
414int
415sftpfs_chmod (const vfs_path_t * vpath, mode_t mode, GError ** mcerror)
416{
417    const vfs_path_element_t *path_element;
418    sftpfs_super_data_t *super_data;
419    LIBSSH2_SFTP_ATTRIBUTES attrs;
420    int res;
421
422    res = _sftpfs_stat(&super_data, &path_element, vpath, mcerror,
423                       LIBSSH2_SFTP_LSTAT, &attrs);
424
425    if (res == -1 || res == EACCES || res == ENOENT)
426        return res;
427
428    attrs.permissions = mode;
429
430    do
431    {
432        const char *fixfname;
433
434        fixfname = sftpfs_fix_filename (path_element->path);
435
436        res = libssh2_sftp_stat_ex (super_data->sftp_session, fixfname,
437                                    sftpfs_filename_buffer->len,
438                                    LIBSSH2_SFTP_SETSTAT, &attrs);
439        if (res >= 0)
440            break;
441
442        if (_is_sftp_proto_error(super_data, res, LIBSSH2_FX_FAILURE))
443        {
444            res = 0;  /* need something like ftpfs_ignore_chattr_errors */
445            break;
446        }
447
448        if (_is_sftp_proto_error(super_data, res, LIBSSH2_FX_NO_SUCH_FILE))
449            return ENOENT;
450
451        if (_waitsocket_or_error(super_data, res,mcerror, NULL) != 0)
452            return -1;
453    }
454    while (res == LIBSSH2_ERROR_EAGAIN);
455
456    return res;
457}
458
459/* --------------------------------------------------------------------------------------------- */
460/**
461 * Delete a name from the file system.
462 *
463 * @param vpath   path to file or directory
464 * @param mcerror pointer to error object
465 * @return 0 if success, negative value otherwise
466 */
467
468int
469sftpfs_unlink (const vfs_path_t * vpath, GError ** mcerror)
470{
471    const vfs_path_element_t *path_element;
472    sftpfs_super_data_t *super_data;
473    int res;
474
475    if (_sftpfs_op_init(&super_data, &path_element, vpath, mcerror) != 0)
476        return -1;
477
478    do
479    {
480        const char *fixfname;
481
482        fixfname = sftpfs_fix_filename (path_element->path);
483
484        res = libssh2_sftp_unlink_ex (super_data->sftp_session, fixfname,
485                                      sftpfs_filename_buffer->len);
486        if (res >= 0)
487            break;
488
489        if (_waitsocket_or_error(super_data, res,mcerror, NULL) != 0)
490            return -1;
491    }
492    while (res == LIBSSH2_ERROR_EAGAIN);
493
494    return res;
495}
496
497/* --------------------------------------------------------------------------------------------- */
498/**
499 * Rename a file, moving it between directories if required.
500 *
501 * @param vpath1   path to source file or directory
502 * @param vpath2   path to destination file or directory
503 * @param mcerror  pointer to error object
504 * @return 0 if success, negative value otherwise
505 */
506
507int
508sftpfs_rename (const vfs_path_t * vpath1, const vfs_path_t * vpath2, GError ** mcerror)
509{
510    const vfs_path_element_t *path_element1;
511    const vfs_path_element_t *path_element2;
512    sftpfs_super_data_t *super_data;
513    char *tmp_path;
514    int res;
515
516    if (_sftpfs_op_init(&super_data, &path_element2, vpath2, mcerror) != 0)
517        return -1;
518
519    tmp_path = g_strdup_printf ("%c%s", PATH_SEP, path_element2->path);
520    path_element1 = vfs_path_get_by_index (vpath1, -1);
521
522    do
523    {
524        const char *fixfname;
525
526        fixfname = sftpfs_fix_filename (path_element1->path);
527
528        res = libssh2_sftp_rename_ex (super_data->sftp_session, fixfname,
529                                      sftpfs_filename_buffer->len, tmp_path,
530                                      strlen (tmp_path), LIBSSH2_SFTP_SYMLINK);
531
532        if (res >= 0)
533            break;
534
535        if (_waitsocket_or_error(super_data, res,mcerror, tmp_path) != 0)
536            return -1;
537    }
538    while (res == LIBSSH2_ERROR_EAGAIN);
539    g_free (tmp_path);
540
541    return 0;
542}
543
544/* --------------------------------------------------------------------------------------------- */