diff options
Diffstat (limited to 'src/vfs/sftpfs/file.c')
-rw-r--r-- | src/vfs/sftpfs/file.c | 424 |
1 files changed, 424 insertions, 0 deletions
diff --git a/src/vfs/sftpfs/file.c b/src/vfs/sftpfs/file.c new file mode 100644 index 0000000..4146239 --- /dev/null +++ b/src/vfs/sftpfs/file.c @@ -0,0 +1,424 @@ +/* Virtual File System: SFTP file system. + The internal functions: files + + Copyright (C) 2011-2023 + 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> /* ENOENT, EACCES */ + +#include <libssh2.h> +#include <libssh2_sftp.h> + +#include "lib/global.h" +#include "lib/util.h" + +#include "internal.h" + +/*** global variables ****************************************************************************/ + +/*** file scope macro definitions ****************************************************************/ + +#define SFTP_FILE_HANDLER(a) ((sftpfs_file_handler_t *) a) + +/*** file scope type declarations ****************************************************************/ + +typedef struct +{ + vfs_file_handler_t base; /* base class */ + + LIBSSH2_SFTP_HANDLE *handle; + int flags; + mode_t mode; +} sftpfs_file_handler_t; + +/*** forward declarations (file scope functions) *************************************************/ + +/*** file scope variables ************************************************************************/ + +/* --------------------------------------------------------------------------------------------- */ +/*** file scope functions ************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ +/** + * Reopen file by file handle. + * + * @param fh the file handler + * @param mcerror pointer to the error handler + */ +static void +sftpfs_reopen (vfs_file_handler_t * fh, GError ** mcerror) +{ + sftpfs_file_handler_t *file = SFTP_FILE_HANDLER (fh); + int flags; + mode_t mode; + + g_return_if_fail (mcerror == NULL || *mcerror == NULL); + + flags = file->flags; + mode = file->mode; + + sftpfs_close_file (fh, mcerror); + sftpfs_open_file (fh, flags, mode, mcerror); +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +sftpfs_file__handle_error (sftpfs_super_t * super, int sftp_res, GError ** mcerror) +{ + if (sftpfs_is_sftp_error (super->sftp_session, sftp_res, LIBSSH2_FX_PERMISSION_DENIED)) + return -EACCES; + + if (sftpfs_is_sftp_error (super->sftp_session, sftp_res, LIBSSH2_FX_NO_SUCH_FILE)) + return -ENOENT; + + if (!sftpfs_waitsocket (super, sftp_res, mcerror)) + return -1; + + return 0; +} + +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +vfs_file_handler_t * +sftpfs_fh_new (struct vfs_s_inode * ino, gboolean changed) +{ + sftpfs_file_handler_t *fh; + + fh = g_new0 (sftpfs_file_handler_t, 1); + vfs_s_init_fh (VFS_FILE_HANDLER (fh), ino, changed); + + return VFS_FILE_HANDLER (fh); +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Open new SFTP file. + * + * @param fh the file handler + * @param flags flags (see man 2 open) + * @param mode mode (see man 2 open) + * @param mcerror pointer to the error handler + * @return TRUE if connection was created successfully, FALSE otherwise + */ + +gboolean +sftpfs_open_file (vfs_file_handler_t * fh, int flags, mode_t mode, GError ** mcerror) +{ + unsigned long sftp_open_flags = 0; + int sftp_open_mode = 0; + gboolean do_append = FALSE; + sftpfs_file_handler_t *file = SFTP_FILE_HANDLER (fh); + sftpfs_super_t *super = SFTP_SUPER (fh->ino->super); + char *name; + const GString *fixfname; + + (void) mode; + mc_return_val_if_error (mcerror, FALSE); + + name = vfs_s_fullpath (vfs_sftpfs_ops, fh->ino); + if (name == NULL) + return FALSE; + + if ((flags & O_CREAT) != 0 || (flags & O_WRONLY) != 0) + { + sftp_open_flags = (flags & O_WRONLY) != 0 ? LIBSSH2_FXF_WRITE : 0; + sftp_open_flags |= (flags & O_CREAT) != 0 ? LIBSSH2_FXF_CREAT : 0; + if ((flags & O_APPEND) != 0) + { + sftp_open_flags |= LIBSSH2_FXF_APPEND; + do_append = TRUE; + } + sftp_open_flags |= (flags & O_TRUNC) != 0 ? LIBSSH2_FXF_TRUNC : 0; + + sftp_open_mode = LIBSSH2_SFTP_S_IRUSR | + LIBSSH2_SFTP_S_IWUSR | LIBSSH2_SFTP_S_IRGRP | LIBSSH2_SFTP_S_IROTH; + } + else + sftp_open_flags = LIBSSH2_FXF_READ; + + fixfname = sftpfs_fix_filename (name); + + while (TRUE) + { + int libssh_errno; + + file->handle = + libssh2_sftp_open_ex (super->sftp_session, fixfname->str, fixfname->len, + sftp_open_flags, sftp_open_mode, LIBSSH2_SFTP_OPENFILE); + if (file->handle != NULL) + break; + + libssh_errno = libssh2_session_last_errno (super->session); + if (libssh_errno != LIBSSH2_ERROR_EAGAIN) + { + sftpfs_ssherror_to_gliberror (super, libssh_errno, mcerror); + g_free (name); + return FALSE; + } + } + + g_free (name); + + file->flags = flags; + file->mode = mode; + + if (do_append) + { + struct stat file_info = { + .st_dev = 0 + }; + /* In case of + + struct stat file_info = { 0 }; + + gcc < 4.7 [1] generates the following: + + error: missing initializer [-Werror=missing-field-initializers] + error: (near initialization for 'file_info.st_dev') [-Werror=missing-field-initializers] + + [1] http://stackoverflow.com/questions/13373695/how-to-remove-the-warning-in-gcc-4-6-missing-initializer-wmissing-field-initi/27461062#27461062 + */ + + if (sftpfs_fstat (fh, &file_info, mcerror) == 0) + libssh2_sftp_seek64 (file->handle, file_info.st_size); + } + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Stats the file specified by the file descriptor. + * + * @param data file handler + * @param buf buffer for store stat-info + * @param mcerror pointer to the error handler + * @return 0 if success, negative value otherwise + */ + +int +sftpfs_fstat (void *data, struct stat *buf, GError ** mcerror) +{ + int res; + LIBSSH2_SFTP_ATTRIBUTES attrs; + vfs_file_handler_t *fh = VFS_FILE_HANDLER (data); + sftpfs_file_handler_t *sftpfs_fh = (sftpfs_file_handler_t *) data; + struct vfs_s_super *super = VFS_FILE_HANDLER_SUPER (fh); + sftpfs_super_t *sftpfs_super = SFTP_SUPER (super); + + mc_return_val_if_error (mcerror, -1); + + if (sftpfs_fh->handle == NULL) + return -1; + + do + { + int err; + + res = libssh2_sftp_fstat_ex (sftpfs_fh->handle, &attrs, 0); + if (res >= 0) + break; + + err = sftpfs_file__handle_error (sftpfs_super, res, mcerror); + if (err < 0) + return err; + } + while (res == LIBSSH2_ERROR_EAGAIN); + + sftpfs_attr_to_stat (&attrs, buf); + + return 0; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Read up to 'count' bytes from the file descriptor 'fh' to the buffer starting at 'buffer'. + * + * @param fh file handler + * @param buffer buffer for data + * @param count data size + * @param mcerror pointer to the error handler + * + * @return 0 on success, negative value otherwise + */ + +ssize_t +sftpfs_read_file (vfs_file_handler_t * fh, char *buffer, size_t count, GError ** mcerror) +{ + ssize_t rc; + sftpfs_file_handler_t *file = SFTP_FILE_HANDLER (fh); + sftpfs_super_t *super; + + mc_return_val_if_error (mcerror, -1); + + if (fh == NULL) + { + mc_propagate_error (mcerror, 0, "%s", + _("sftp: No file handler data present for reading file")); + return -1; + } + + super = SFTP_SUPER (VFS_FILE_HANDLER_SUPER (fh)); + + do + { + int err; + + rc = libssh2_sftp_read (file->handle, buffer, count); + if (rc >= 0) + break; + + err = sftpfs_file__handle_error (super, (int) rc, mcerror); + if (err < 0) + return err; + } + while (rc == LIBSSH2_ERROR_EAGAIN); + + fh->pos = (off_t) libssh2_sftp_tell64 (file->handle); + + return rc; +} + +/* --------------------------------------------------------------------------------------------- */ + +/** + * Write up to 'count' bytes from the buffer starting at 'buffer' to the descriptor 'fh'. + * + * @param fh file handler + * @param buffer buffer for data + * @param count data size + * @param mcerror pointer to the error handler + * + * @return 0 on success, negative value otherwise + */ + +ssize_t +sftpfs_write_file (vfs_file_handler_t * fh, const char *buffer, size_t count, GError ** mcerror) +{ + ssize_t rc; + sftpfs_file_handler_t *file = SFTP_FILE_HANDLER (fh); + sftpfs_super_t *super = SFTP_SUPER (VFS_FILE_HANDLER_SUPER (fh)); + + mc_return_val_if_error (mcerror, -1); + + fh->pos = (off_t) libssh2_sftp_tell64 (file->handle); + + do + { + int err; + + rc = libssh2_sftp_write (file->handle, buffer, count); + if (rc >= 0) + break; + + err = sftpfs_file__handle_error (super, (int) rc, mcerror); + if (err < 0) + return err; + } + while (rc == LIBSSH2_ERROR_EAGAIN); + + return rc; +} + +/* --------------------------------------------------------------------------------------------- */ + +/** + * Close a file descriptor. + * + * @param fh file handler + * @param mcerror pointer to the error handler + * + * @return 0 on success, negative value otherwise + */ + +int +sftpfs_close_file (vfs_file_handler_t * fh, GError ** mcerror) +{ + int ret; + + mc_return_val_if_error (mcerror, -1); + + ret = libssh2_sftp_close (SFTP_FILE_HANDLER (fh)->handle); + + return ret == 0 ? 0 : -1; +} + +/* --------------------------------------------------------------------------------------------- */ + +/** + * Reposition the offset of the open file associated with the file descriptor. + * + * @param fh file handler + * @param offset file offset + * @param whence method of seek (at begin, at current, at end) + * @param mcerror pointer to the error handler + * + * @return 0 on success, negative value otherwise + */ + +off_t +sftpfs_lseek (vfs_file_handler_t * fh, off_t offset, int whence, GError ** mcerror) +{ + sftpfs_file_handler_t *file = SFTP_FILE_HANDLER (fh); + + mc_return_val_if_error (mcerror, 0); + + switch (whence) + { + case SEEK_SET: + /* Need reopen file because: + "You MUST NOT seek during writing or reading a file with SFTP, as the internals use + outstanding packets and changing the "file position" during transit will results in + badness." */ + if (fh->pos > offset || offset == 0) + { + sftpfs_reopen (fh, mcerror); + mc_return_val_if_error (mcerror, 0); + } + fh->pos = offset; + break; + case SEEK_CUR: + fh->pos += offset; + break; + case SEEK_END: + if (fh->pos > fh->ino->st.st_size - offset) + { + sftpfs_reopen (fh, mcerror); + mc_return_val_if_error (mcerror, 0); + } + fh->pos = fh->ino->st.st_size - offset; + break; + default: + break; + } + + libssh2_sftp_seek64 (file->handle, fh->pos); + fh->pos = (off_t) libssh2_sftp_tell64 (file->handle); + + return fh->pos; +} + +/* --------------------------------------------------------------------------------------------- */ |