diff options
Diffstat (limited to 'src/vfs/sftpfs/internal.c')
-rw-r--r-- | src/vfs/sftpfs/internal.c | 623 |
1 files changed, 623 insertions, 0 deletions
diff --git a/src/vfs/sftpfs/internal.c b/src/vfs/sftpfs/internal.c new file mode 100644 index 0000000..9c9581e --- /dev/null +++ b/src/vfs/sftpfs/internal.c @@ -0,0 +1,623 @@ +/* Virtual File System: SFTP file system. + The internal functions + + Copyright (C) 2011-2022 + Free Software Foundation, Inc. + + Written by: + Ilia Maslakov <il.smind@gmail.com>, 2011 + Slava Zanko <slavazanko@gmail.com>, 2011, 2012 + + This file is part of the Midnight Commander. + + The Midnight Commander is free software: you can redistribute it + and/or modify it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + The Midnight Commander is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <errno.h> + +#ifdef HAVE_SYS_SELECT_H +#include <sys/select.h> +#else +#include <sys/time.h> +#include <sys/types.h> +#include <unistd.h> +#endif + +#include "lib/global.h" +#include "lib/util.h" + +#include "internal.h" + +/*** global variables ****************************************************************************/ + +GString *sftpfs_filename_buffer = NULL; + +/*** file scope macro definitions ****************************************************************/ + +/*** file scope type declarations ****************************************************************/ + +/*** file scope variables ************************************************************************/ + +/* --------------------------------------------------------------------------------------------- */ +/*** file scope functions ************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +/* Adjust block size and number of blocks */ + +static void +sftpfs_blksize (struct stat *s) +{ +#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE + s->st_blksize = LIBSSH2_CHANNEL_WINDOW_DEFAULT; /* FIXME */ +#endif /* HAVE_STRUCT_STAT_ST_BLKSIZE */ + vfs_adjust_stat (s); +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Awaiting for any activity on socket. + * + * @param super extra data for SFTP connection + * @param mcerror pointer to the error object + * @return 0 if success, negative value otherwise + */ + +static int +sftpfs_internal_waitsocket (sftpfs_super_t * super, GError ** mcerror) +{ + struct timeval timeout = { 10, 0 }; + fd_set fd; + fd_set *writefd = NULL; + fd_set *readfd = NULL; + int dir, ret; + + mc_return_val_if_error (mcerror, -1); + + FD_ZERO (&fd); + FD_SET (super->socket_handle, &fd); + + /* now make sure we wait in the correct direction */ + dir = libssh2_session_block_directions (super->session); + + if ((dir & LIBSSH2_SESSION_BLOCK_INBOUND) != 0) + readfd = &fd; + + if ((dir & LIBSSH2_SESSION_BLOCK_OUTBOUND) != 0) + writefd = &fd; + + ret = select (super->socket_handle + 1, readfd, writefd, NULL, &timeout); + if (ret < 0) + { + int my_errno = errno; + + mc_propagate_error (mcerror, my_errno, _("sftp: socket error: %s"), + unix_error_string (my_errno)); + } + + return ret; +} + +/* --------------------------------------------------------------------------------------------- */ + +static gboolean +sftpfs_op_init (sftpfs_super_t ** super, const vfs_path_element_t ** path_element, + const vfs_path_t * vpath, GError ** mcerror) +{ + struct vfs_s_super *lc_super = NULL; + + mc_return_val_if_error (mcerror, FALSE); + + if (vfs_s_get_path (vpath, &lc_super, 0) == NULL) + return FALSE; + + if (lc_super == NULL) + return FALSE; + + *super = SFTP_SUPER (lc_super); + + if ((*super)->sftp_session == NULL) + return FALSE; + + *path_element = vfs_path_get_by_index (vpath, -1); + + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +sftpfs_stat_init (sftpfs_super_t ** super, const vfs_path_element_t ** path_element, + const vfs_path_t * vpath, GError ** mcerror, int stat_type, + LIBSSH2_SFTP_ATTRIBUTES * attrs) +{ + const GString *fixfname; + int res; + + if (!sftpfs_op_init (super, path_element, vpath, mcerror)) + return -1; + + fixfname = sftpfs_fix_filename ((*path_element)->path); + + do + { + res = libssh2_sftp_stat_ex ((*super)->sftp_session, fixfname->str, fixfname->len, + stat_type, attrs); + if (res >= 0) + break; + + if (sftpfs_is_sftp_error ((*super)->sftp_session, res, LIBSSH2_FX_PERMISSION_DENIED)) + return -EACCES; + + if (sftpfs_is_sftp_error ((*super)->sftp_session, res, LIBSSH2_FX_NO_SUCH_FILE)) + return -ENOENT; + + if (!sftpfs_waitsocket (*super, res, mcerror)) + return -1; + } + while (res == LIBSSH2_ERROR_EAGAIN); + + return res; +} + +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +gboolean +sftpfs_waitsocket (sftpfs_super_t * super, int sftp_res, GError ** mcerror) +{ + if (sftp_res != LIBSSH2_ERROR_EAGAIN) + { + sftpfs_ssherror_to_gliberror (super, sftp_res, mcerror); + return FALSE; + } + + sftpfs_internal_waitsocket (super, mcerror); + + return (mcerror == NULL || *mcerror == NULL); +} + +/* --------------------------------------------------------------------------------------------- */ + +gboolean +sftpfs_is_sftp_error (LIBSSH2_SFTP * sftp_session, int sftp_res, int sftp_error) +{ + return (sftp_res == LIBSSH2_ERROR_SFTP_PROTOCOL && + libssh2_sftp_last_error (sftp_session) == (unsigned long) sftp_error); +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Convert libssh error to GError object. + * + * @param super extra data for SFTP connection + * @param libssh_errno errno from libssh + * @param mcerror pointer to the error object + */ + +void +sftpfs_ssherror_to_gliberror (sftpfs_super_t * super, int libssh_errno, GError ** mcerror) +{ + char *err = NULL; + int err_len; + + mc_return_if_error (mcerror); + + libssh2_session_last_error (super->session, &err, &err_len, 1); + if (libssh_errno == LIBSSH2_ERROR_SFTP_PROTOCOL && super->sftp_session != NULL) + mc_propagate_error (mcerror, libssh_errno, "%s %lu", err, + libssh2_sftp_last_error (super->sftp_session)); + else + mc_propagate_error (mcerror, libssh_errno, "%s", err); + g_free (err); +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Fix filename for SFTP operations: add leading slash to file name. + * + * @param file_name file name + * @param length length of returned string + * + * @return pointer to string that contains the file name with leading slash + */ + +const GString * +sftpfs_fix_filename (const char *file_name) +{ + g_string_printf (sftpfs_filename_buffer, "%c%s", PATH_SEP, file_name); + return sftpfs_filename_buffer; +} + +/* --------------------------------------------------------------------------------------------- */ + +void +sftpfs_attr_to_stat (const LIBSSH2_SFTP_ATTRIBUTES * attrs, struct stat *s) +{ + if ((attrs->flags & LIBSSH2_SFTP_ATTR_UIDGID) != 0) + { + s->st_uid = attrs->uid; + s->st_gid = attrs->gid; + } + + if ((attrs->flags & LIBSSH2_SFTP_ATTR_ACMODTIME) != 0) + { + s->st_atime = attrs->atime; + s->st_mtime = attrs->mtime; + s->st_ctime = attrs->mtime; +#ifdef HAVE_STRUCT_STAT_ST_MTIM + s->st_atim.tv_nsec = s->st_mtim.tv_nsec = s->st_ctim.tv_nsec = 0; +#endif + } + + if ((attrs->flags & LIBSSH2_SFTP_ATTR_SIZE) != 0) + { + s->st_size = attrs->filesize; + sftpfs_blksize (s); + } + + if ((attrs->flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) != 0) + s->st_mode = attrs->permissions; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Getting information about a symbolic link. + * + * @param vpath path to file, directory or symbolic link + * @param buf buffer for store stat-info + * @param mcerror pointer to error object + * @return 0 if success, negative value otherwise + */ + +int +sftpfs_lstat (const vfs_path_t * vpath, struct stat *buf, GError ** mcerror) +{ + sftpfs_super_t *super = NULL; + const vfs_path_element_t *path_element = NULL; + LIBSSH2_SFTP_ATTRIBUTES attrs; + int res; + + res = sftpfs_stat_init (&super, &path_element, vpath, mcerror, LIBSSH2_SFTP_LSTAT, &attrs); + if (res >= 0) + { + sftpfs_attr_to_stat (&attrs, buf); + res = 0; + } + + return res; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Getting information about a file or directory. + * + * @param vpath path to file or directory + * @param buf buffer for store stat-info + * @param mcerror pointer to error object + * @return 0 if success, negative value otherwise + */ + +int +sftpfs_stat (const vfs_path_t * vpath, struct stat *buf, GError ** mcerror) +{ + sftpfs_super_t *super = NULL; + const vfs_path_element_t *path_element = NULL; + LIBSSH2_SFTP_ATTRIBUTES attrs; + int res; + + res = sftpfs_stat_init (&super, &path_element, vpath, mcerror, LIBSSH2_SFTP_STAT, &attrs); + if (res >= 0) + { + buf->st_nlink = 1; + sftpfs_attr_to_stat (&attrs, buf); + res = 0; + } + + return res; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Read value of a symbolic link. + * + * @param vpath path to file or directory + * @param buf buffer for store stat-info + * @param size buffer size + * @param mcerror pointer to error object + * @return 0 if success, negative value otherwise + */ + +int +sftpfs_readlink (const vfs_path_t * vpath, char *buf, size_t size, GError ** mcerror) +{ + sftpfs_super_t *super = NULL; + const vfs_path_element_t *path_element = NULL; + const GString *fixfname; + int res; + + if (!sftpfs_op_init (&super, &path_element, vpath, mcerror)) + return -1; + + fixfname = sftpfs_fix_filename (path_element->path); + + do + { + res = + libssh2_sftp_symlink_ex (super->sftp_session, fixfname->str, fixfname->len, buf, size, + LIBSSH2_SFTP_READLINK); + if (res >= 0) + break; + + if (!sftpfs_waitsocket (super, res, mcerror)) + return -1; + } + while (res == LIBSSH2_ERROR_EAGAIN); + + return res; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Create symlink to file or directory + * + * @param vpath1 path to file or directory + * @param vpath2 path to symlink + * @param mcerror pointer to error object + * @return 0 if success, negative value otherwise + */ + +int +sftpfs_symlink (const vfs_path_t * vpath1, const vfs_path_t * vpath2, GError ** mcerror) +{ + sftpfs_super_t *super = NULL; + const vfs_path_element_t *path_element1; + const vfs_path_element_t *path_element2 = NULL; + const char *path1; + size_t path1_len; + const GString *ctmp_path; + char *tmp_path; + unsigned int tmp_path_len; + int res; + + if (!sftpfs_op_init (&super, &path_element2, vpath2, mcerror)) + return -1; + + ctmp_path = sftpfs_fix_filename (path_element2->path); + tmp_path = g_strndup (ctmp_path->str, ctmp_path->len); + tmp_path_len = ctmp_path->len; + + path_element1 = vfs_path_get_by_index (vpath1, -1); + path1 = path_element1->path; + path1_len = strlen (path1); + + do + { + res = + libssh2_sftp_symlink_ex (super->sftp_session, path1, path1_len, tmp_path, tmp_path_len, + LIBSSH2_SFTP_SYMLINK); + if (res >= 0) + break; + + if (!sftpfs_waitsocket (super, res, mcerror)) + { + g_free (tmp_path); + return -1; + } + } + while (res == LIBSSH2_ERROR_EAGAIN); + g_free (tmp_path); + + return 0; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Changes the times of the file. + * + * @param vpath path to file or directory + * @param atime access time + * @param mtime modification time + * @param mcerror pointer to error object + * @return 0 if success, negative value otherwise + */ + +int +sftpfs_utime (const vfs_path_t * vpath, time_t atime, time_t mtime, GError ** mcerror) +{ + sftpfs_super_t *super = NULL; + const vfs_path_element_t *path_element = NULL; + LIBSSH2_SFTP_ATTRIBUTES attrs; + const GString *fixfname; + int res; + + res = sftpfs_stat_init (&super, &path_element, vpath, mcerror, LIBSSH2_SFTP_LSTAT, &attrs); + if (res < 0) + return res; + + attrs.flags = LIBSSH2_SFTP_ATTR_ACMODTIME; + attrs.atime = atime; + attrs.mtime = mtime; + + fixfname = sftpfs_fix_filename (path_element->path); + + do + { + res = + libssh2_sftp_stat_ex (super->sftp_session, fixfname->str, fixfname->len, + LIBSSH2_SFTP_SETSTAT, &attrs); + if (res >= 0) + break; + + if (sftpfs_is_sftp_error (super->sftp_session, res, LIBSSH2_FX_NO_SUCH_FILE)) + return -ENOENT; + + if (sftpfs_is_sftp_error (super->sftp_session, res, LIBSSH2_FX_FAILURE)) + { + res = 0; /* need something like ftpfs_ignore_chattr_errors */ + break; + } + + if (!sftpfs_waitsocket (super, res, mcerror)) + return -1; + } + while (res == LIBSSH2_ERROR_EAGAIN); + + return res; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Changes the permissions of the file. + * + * @param vpath path to file or directory + * @param mode mode (see man 2 open) + * @param mcerror pointer to error object + * @return 0 if success, negative value otherwise + */ + +int +sftpfs_chmod (const vfs_path_t * vpath, mode_t mode, GError ** mcerror) +{ + sftpfs_super_t *super = NULL; + const vfs_path_element_t *path_element = NULL; + LIBSSH2_SFTP_ATTRIBUTES attrs; + const GString *fixfname; + int res; + + res = sftpfs_stat_init (&super, &path_element, vpath, mcerror, LIBSSH2_SFTP_LSTAT, &attrs); + if (res < 0) + return res; + + attrs.flags = LIBSSH2_SFTP_ATTR_PERMISSIONS; + attrs.permissions = mode; + + fixfname = sftpfs_fix_filename (path_element->path); + + do + { + res = + libssh2_sftp_stat_ex (super->sftp_session, fixfname->str, fixfname->len, + LIBSSH2_SFTP_SETSTAT, &attrs); + if (res >= 0) + break; + + if (sftpfs_is_sftp_error (super->sftp_session, res, LIBSSH2_FX_NO_SUCH_FILE)) + return -ENOENT; + + if (sftpfs_is_sftp_error (super->sftp_session, res, LIBSSH2_FX_FAILURE)) + { + res = 0; /* need something like ftpfs_ignore_chattr_errors */ + break; + } + + if (!sftpfs_waitsocket (super, res, mcerror)) + return -1; + } + while (res == LIBSSH2_ERROR_EAGAIN); + + return res; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Delete a name from the file system. + * + * @param vpath path to file or directory + * @param mcerror pointer to error object + * @return 0 if success, negative value otherwise + */ + +int +sftpfs_unlink (const vfs_path_t * vpath, GError ** mcerror) +{ + sftpfs_super_t *super = NULL; + const vfs_path_element_t *path_element = NULL; + const GString *fixfname; + int res; + + if (!sftpfs_op_init (&super, &path_element, vpath, mcerror)) + return -1; + + fixfname = sftpfs_fix_filename (path_element->path); + + do + { + res = libssh2_sftp_unlink_ex (super->sftp_session, fixfname->str, fixfname->len); + if (res >= 0) + break; + + if (!sftpfs_waitsocket (super, res, mcerror)) + return -1; + } + while (res == LIBSSH2_ERROR_EAGAIN); + + return res; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Rename a file, moving it between directories if required. + * + * @param vpath1 path to source file or directory + * @param vpath2 path to destination file or directory + * @param mcerror pointer to error object + * @return 0 if success, negative value otherwise + */ + +int +sftpfs_rename (const vfs_path_t * vpath1, const vfs_path_t * vpath2, GError ** mcerror) +{ + sftpfs_super_t *super = NULL; + const vfs_path_element_t *path_element1; + const vfs_path_element_t *path_element2 = NULL; + const GString *ctmp_path; + char *tmp_path; + unsigned int tmp_path_len; + const GString *fixfname; + int res; + + if (!sftpfs_op_init (&super, &path_element2, vpath2, mcerror)) + return -1; + + ctmp_path = sftpfs_fix_filename (path_element2->path); + tmp_path = g_strndup (ctmp_path->str, ctmp_path->len); + tmp_path_len = ctmp_path->len; + + path_element1 = vfs_path_get_by_index (vpath1, -1); + + fixfname = sftpfs_fix_filename (path_element1->path); + + do + { + res = + libssh2_sftp_rename_ex (super->sftp_session, fixfname->str, fixfname->len, tmp_path, + tmp_path_len, LIBSSH2_SFTP_SYMLINK); + if (res >= 0) + break; + + if (!sftpfs_waitsocket (super, res, mcerror)) + { + g_free (tmp_path); + return -1; + } + } + while (res == LIBSSH2_ERROR_EAGAIN); + g_free (tmp_path); + + return 0; +} + +/* --------------------------------------------------------------------------------------------- */ |