/* Virtual File System: SFTP file system. The internal functions: files Copyright (C) 2011-2023 Free Software Foundation, Inc. Written by: Ilia Maslakov , 2011 Slava Zanko , 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 . */ #include #include /* ENOENT, EACCES */ #include #include #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; } /* --------------------------------------------------------------------------------------------- */