1
0
mirror of https://git.libssh.org/projects/libssh.git synced 2025-11-26 01:03:15 +03:00

Introduce sftp async i/o (aio) api

The existing sftp async read api has two problems :

1. sftp_async_read() assumes that the value of the third
parameter count is same as the number of bytes requested
to read in the corresponding call to sftp_async_read_begin().

But the documentation of sftp_async_read() allows the value of
count parameter to be more than that requested length. If value
of count parameter is more than that requested length then
sftp_async_read() updates the file->offset incorrectly which
leads to further read/writes occuring from incorrect offsets.

The problem here is that sftp_async_read() doesn't know about
the number of bytes requested to read specified in the call to
sftp_async_read_begin(), and it wrongly assumes the value
of its count parameter (which is actually the size of the buffer
to store the read data) to be the same as the number of bytes
requested to read.

2. sftp_async_read_begin() returns an uint32_t type value type
casted to int as a request identifier, whereas sftp_async_read()
expects an uint32_t type value as a request identifier. Due to this
the user has to typecast the identifier returned by sftp_async_read_begin()
from int to uint32_t and then pass it to sftp_async_read(). This
type casting is cumbersome for the user and hence the approach is
not user-friendly.

This commit solves the above two problems by introducing a new
sftp aio api.

The sftp_aio_begin_*() functions in the api send an i/o request to
the sftp server and provide the caller a dynamically allocated
structure storing information about the sent request. Information
like number of bytes requested for i/o, id of sent request etc is
stored in the structure.

That structure should be provided to the sftp_aio_wait_*() functions
in the api which wait for the response corresponding to the request whose
info is stored in the provided structure.

The libssh user is supposed to handle that structure through an
opaque type sftp_aio.

Since the structure stores the number of bytes requested for i/o,
sftp_aio_wait_*() knows about the number of bytes requested for i/o
(specified in the call to sftp_aio_begin_*()) and hence updates the
file->offset correctly solving problem #1 present in the existing
async api.

Since the structure provided by sftp_aio_begin_*() (containing the
request id) is supplied to sftp_aio_wait_*(), no casting of id's
needs to be done by the user solving problem #2 of the existing
async api.

Signed-off-by: Eshan Kelkar <eshankelkar@galorithm.com>
Reviewed-by: Sahana Prasad <sahana@redhat.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
This commit is contained in:
Eshan Kelkar
2023-05-26 13:21:48 +05:30
committed by Sahana Prasad
parent 7455b6ae64
commit c1606da450
4 changed files with 707 additions and 0 deletions

View File

@@ -78,6 +78,7 @@ typedef struct sftp_session_struct* sftp_session;
typedef struct sftp_status_message_struct* sftp_status_message; typedef struct sftp_status_message_struct* sftp_status_message;
typedef struct sftp_statvfs_struct* sftp_statvfs_t; typedef struct sftp_statvfs_struct* sftp_statvfs_t;
typedef struct sftp_limits_struct* sftp_limits_t; typedef struct sftp_limits_struct* sftp_limits_t;
typedef struct sftp_aio_struct* sftp_aio;
struct sftp_session_struct { struct sftp_session_struct {
ssh_session session; ssh_session session;
@@ -580,6 +581,216 @@ LIBSSH_API int sftp_async_read(sftp_file file, void *data, uint32_t len, uint32_
*/ */
LIBSSH_API ssize_t sftp_write(sftp_file file, const void *buf, size_t count); LIBSSH_API ssize_t sftp_write(sftp_file file, const void *buf, size_t count);
/**
* @brief Deallocate memory corresponding to a sftp aio handle.
*
* This function deallocates memory corresponding to the aio handle returned
* by the sftp_aio_begin_*() functions. Users can use this function to free
* memory corresponding to an aio handle for an outstanding async i/o request
* on encountering some error.
*
* @param aio sftp aio handle corresponding to which memory has
* to be deallocated.
*
* @see sftp_aio_begin_read()
* @see sftp_aio_wait_read()
* @see sftp_aio_begin_write()
* @see sftp_aio_wait_write()
*/
LIBSSH_API void sftp_aio_free(sftp_aio aio);
#define SFTP_AIO_FREE(x) \
do { if(x != NULL) {sftp_aio_free(x); x = NULL;} } while(0)
/**
* @brief Start an asynchronous read from a file using an opened sftp
* file handle.
*
* Its goal is to avoid the slowdowns related to the request/response pattern
* of a synchronous read. To do so, you must call 2 functions :
*
* sftp_aio_begin_read() and sftp_aio_wait_read().
*
* - The first step is to call sftp_aio_begin_read(). This function sends a
* read request to the sftp server, dynamically allocates memory to store
* information about the sent request and provides the caller an sftp aio
* handle to that memory.
*
* - The second step is to call sftp_aio_wait_read() and pass it the address
* of a location storing the sftp aio handle provided by
* sftp_aio_begin_read().
*
* These two functions do not close the open sftp file handle passed to
* sftp_aio_begin_read() irrespective of whether they fail or not.
*
* It is the responsibility of the caller to ensure that the open sftp file
* handle passed to sftp_aio_begin_read() must not be closed before the
* corresponding call to sftp_aio_wait_read(). After sftp_aio_wait_read()
* returns, it is caller's decision whether to immediately close the file by
* calling sftp_close() or to keep it open and perform some more operations
* on it.
*
* @param file The opened sftp file handle to be read from.
*
* @param len Number of bytes to read.
*
* @param aio Pointer to a location where the sftp aio handle
* (corresponding to the sent request) should be stored.
*
* @returns SSH_OK on success, SSH_ERROR on error with sftp and ssh
* errors set.
*
* @warning When calling this function, the internal offset is
* updated corresponding to the len parameter.
*
* @warning A call to sftp_aio_begin_read() sends a request to
* the server. When the server answers, libssh allocates
* memory to store it until sftp_aio_wait_read() is called.
* Not calling sftp_aio_wait_read() will lead to memory
* leaks.
*
* @see sftp_aio_wait_read()
* @see sftp_aio_free()
* @see sftp_open()
* @see sftp_close()
* @see sftp_get_error()
* @see ssh_get_error()
*/
LIBSSH_API int sftp_aio_begin_read(sftp_file file,
size_t len,
sftp_aio *aio);
/**
* @brief Wait for an asynchronous read to complete and store the read data
* in the supplied buffer.
*
* A pointer to an sftp aio handle should be passed while calling
* this function. Except when the return value is SSH_AGAIN,
* this function releases the memory corresponding to the supplied
* aio handle and assigns NULL to that aio handle using the passed
* pointer to that handle.
*
* If the file is opened in non-blocking mode and the request hasn't been
* executed yet, this function returns SSH_AGAIN and must be called again
* using the same sftp aio handle.
*
* @param aio Pointer to the sftp aio handle returned by
* sftp_aio_begin_read().
*
* @param buf Pointer to the buffer in which read data will be stored.
*
* @param buf_size Size of the buffer in bytes. It should be bigger or
* equal to the length parameter of the
* sftp_aio_begin_read() call.
*
* @return Number of bytes read, 0 on EOF, SSH_ERROR if an error
* occurred, SSH_AGAIN if the file is opened in nonblocking
* mode and the request hasn't been executed yet.
*
* @warning A call to this function with an invalid sftp aio handle
* may never return.
*
* @see sftp_aio_begin_read()
* @see sftp_aio_free()
*/
LIBSSH_API ssize_t sftp_aio_wait_read(sftp_aio *aio,
void *buf,
size_t buf_size);
/**
* @brief Start an asynchronous write to a file using an opened sftp
* file handle.
*
* Its goal is to avoid the slowdowns related to the request/response pattern
* of a synchronous write. To do so, you must call 2 functions :
*
* sftp_aio_begin_write() and sftp_aio_wait_write().
*
* - The first step is to call sftp_aio_begin_write(). This function sends a
* write request to the sftp server, dynamically allocates memory to store
* information about the sent request and provides the caller an sftp aio
* handle to that memory.
*
* - The second step is to call sftp_aio_wait_write() and pass it the address
* of a location storing the sftp aio handle provided by
* sftp_aio_begin_write().
*
* These two functions do not close the open sftp file handle passed to
* sftp_aio_begin_write() irrespective of whether they fail or not.
*
* It is the responsibility of the caller to ensure that the open sftp file
* handle passed to sftp_aio_begin_write() must not be closed before the
* corresponding call to sftp_aio_wait_write(). After sftp_aio_wait_write()
* returns, it is caller's decision whether to immediately close the file by
* calling sftp_close() or to keep it open and perform some more operations
* on it.
*
* @param file The opened sftp file handle to write to.
*
* @param buf Pointer to the buffer containing data to write.
*
* @param len Number of bytes to write.
*
* @param aio Pointer to a location where the sftp aio handle
* (corresponding to the sent request) should be stored.
*
* @returns SSH_OK on success, SSH_ERROR with sftp and ssh errors
* set.
*
* @warning When calling this function, the internal offset is
* updated corresponding to the len parameter.
*
* @warning A call to sftp_aio_begin_write() sends a request to
* the server. When the server answers, libssh allocates
* memory to store it until sftp_aio_wait_write() is
* called. Not calling sftp_aio_wait_write() will lead to
* memory leaks.
*
* @see sftp_aio_wait_write()
* @see sftp_aio_free()
* @see sftp_open()
* @see sftp_close()
* @see sftp_get_error()
* @see ssh_get_error()
*/
LIBSSH_API int sftp_aio_begin_write(sftp_file file,
const void *buf,
size_t len,
sftp_aio *aio);
/**
* @brief Wait for an asynchronous write to complete.
*
* A pointer to an sftp aio handle should be passed while calling
* this function. Except when the return value is SSH_AGAIN,
* this function releases the memory corresponding to the supplied
* aio handle and assigns NULL to that aio handle using the passed
* pointer to that handle.
*
* If the file is opened in non-blocking mode and the request hasn't
* been executed yet, this function returns SSH_AGAIN and must be called
* again using the same sftp aio handle.
*
* On success, this function returns the number of bytes written.
* The SFTP protocol doesn't support partial writes to remote files,
* hence on success this returned value will always be equal to the
* len passed in the previous corresponding call to sftp_aio_begin_write().
*
* @param aio Pointer to the sftp aio handle returned by
* sftp_aio_begin_write().
*
* @return Number of bytes written on success, SSH_ERROR
* if an error occurred, SSH_AGAIN if the file is
* opened in nonblocking mode and the request hasn't
* been executed yet.
*
* @warning A call to this function with an invalid sftp aio handle
* may never return.
*
* @see sftp_aio_begin_write()
* @see sftp_aio_free()
*/
LIBSSH_API ssize_t sftp_aio_wait_write(sftp_aio *aio);
/** /**
* @brief Seek to a specific location in a file. * @brief Seek to a specific location in a file.
* *

View File

@@ -253,6 +253,7 @@ if (WITH_SFTP)
${libssh_SRCS} ${libssh_SRCS}
sftp.c sftp.c
sftp_common.c sftp_common.c
sftp_aio.c
) )
if (WITH_SERVER) if (WITH_SERVER)

View File

@@ -464,5 +464,10 @@ LIBSSH_AFTER_4_9_0
global: global:
sftp_channel_default_data_callback; sftp_channel_default_data_callback;
sftp_channel_default_subsystem_request; sftp_channel_default_subsystem_request;
sftp_aio_begin_read;
sftp_aio_begin_write;
sftp_aio_free;
sftp_aio_wait_read;
sftp_aio_wait_write;
} LIBSSH_4_9_0; } LIBSSH_4_9_0;

490
src/sftp_aio.c Normal file
View File

@@ -0,0 +1,490 @@
/*
* sftp_aio.c - Secure FTP functions for asynchronous i/o
*
* This file is part of the SSH Library
*
* Copyright (c) 2005-2008 by Aris Adamantiadis
* Copyright (c) 2008-2018 by Andreas Schneider <asn@cryptomilk.org>
* Copyright (c) 2023 by Eshan Kelkar <eshankelkar@galorithm.com>
*
* The SSH Library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at your
* option) any later version.
*
* The SSH Library 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 Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the SSH Library; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
* MA 02111-1307, USA.
*/
#include "config.h"
#include "libssh/sftp.h"
#include "libssh/sftp_priv.h"
#include "libssh/buffer.h"
#include "libssh/session.h"
#ifdef WITH_SFTP
struct sftp_aio_struct {
sftp_file file;
uint32_t id;
size_t len;
};
static sftp_aio sftp_aio_new(void)
{
sftp_aio aio = NULL;
aio = calloc(1, sizeof(struct sftp_aio_struct));
return aio;
}
void sftp_aio_free(sftp_aio aio)
{
SAFE_FREE(aio);
}
int sftp_aio_begin_read(sftp_file file, size_t len, sftp_aio *aio)
{
sftp_session sftp = NULL;
ssh_buffer buffer = NULL;
sftp_aio aio_handle = NULL;
uint32_t id;
int rc;
if (file == NULL ||
file->sftp == NULL ||
file->sftp->session == NULL) {
return SSH_ERROR;
}
sftp = file->sftp;
if (len == 0) {
ssh_set_error(sftp->session, SSH_FATAL,
"Invalid argument, 0 passed as the number of "
"bytes to read");
sftp_set_error(sftp, SSH_FX_FAILURE);
return SSH_ERROR;
}
if (aio == NULL) {
ssh_set_error(sftp->session, SSH_FATAL,
"Invalid argument, NULL passed instead of a pointer to "
"a location to store an sftp aio handle");
sftp_set_error(sftp, SSH_FX_FAILURE);
return SSH_ERROR;
}
buffer = ssh_buffer_new();
if (buffer == NULL) {
ssh_set_error_oom(sftp->session);
sftp_set_error(sftp, SSH_FX_FAILURE);
return SSH_ERROR;
}
id = sftp_get_new_id(sftp);
rc = ssh_buffer_pack(buffer,
"dSqd",
id,
file->handle,
file->offset,
len);
if (rc != SSH_OK) {
ssh_set_error_oom(sftp->session);
sftp_set_error(sftp, SSH_FX_FAILURE);
SSH_BUFFER_FREE(buffer);
return SSH_ERROR;
}
aio_handle = sftp_aio_new();
if (aio_handle == NULL) {
ssh_set_error_oom(sftp->session);
sftp_set_error(sftp, SSH_FX_FAILURE);
SSH_BUFFER_FREE(buffer);
return SSH_ERROR;
}
aio_handle->file = file;
aio_handle->id = id;
aio_handle->len = len;
rc = sftp_packet_write(sftp, SSH_FXP_READ, buffer);
SSH_BUFFER_FREE(buffer);
if (rc == SSH_ERROR) {
SFTP_AIO_FREE(aio_handle);
return SSH_ERROR;
}
/* Assume we read len bytes from the file */
file->offset += len;
*aio = aio_handle;
return SSH_OK;
}
ssize_t sftp_aio_wait_read(sftp_aio *aio,
void *buf,
size_t buf_size)
{
sftp_file file = NULL;
size_t bytes_requested;
sftp_session sftp = NULL;
sftp_message msg = NULL;
sftp_status_message status = NULL;
uint32_t string_len, host_len;
int rc, err;
/*
* This function releases the memory of the structure
* that (*aio) points to in all cases except when the
* return value is SSH_AGAIN.
*
* If the return value is SSH_AGAIN, the user should call this
* function again to get the response for the request corresponding
* to the structure that (*aio) points to, hence we don't release the
* structure's memory when SSH_AGAIN is returned.
*/
if (aio == NULL || *aio == NULL) {
return SSH_ERROR;
}
file = (*aio)->file;
bytes_requested = (*aio)->len;
if (file == NULL ||
file->sftp == NULL ||
file->sftp->session == NULL) {
SFTP_AIO_FREE(*aio);
return SSH_ERROR;
}
sftp = file->sftp;
if (bytes_requested == 0) {
/* should never happen */
ssh_set_error(sftp->session, SSH_FATAL,
"Invalid sftp aio, len for requested i/o is 0");
sftp_set_error(sftp, SSH_FX_FAILURE);
SFTP_AIO_FREE(*aio);
return SSH_ERROR;
}
if (buf == NULL) {
ssh_set_error(sftp->session, SSH_FATAL,
"Invalid argument, NULL passed "
"instead of a buffer's address");
sftp_set_error(sftp, SSH_FX_FAILURE);
SFTP_AIO_FREE(*aio);
return SSH_ERROR;
}
if (buf_size < bytes_requested) {
ssh_set_error(sftp->session, SSH_FATAL,
"Buffer size (%zu, passed by the caller) is "
"smaller than the number of bytes requested "
"to read (%zu, as per the supplied sftp aio)",
buf_size, bytes_requested);
sftp_set_error(sftp, SSH_FX_FAILURE);
SFTP_AIO_FREE(*aio);
return SSH_ERROR;
}
/* handle an existing request */
while (msg == NULL) {
if (file->nonblocking) {
if (ssh_channel_poll(sftp->channel, 0) == 0) {
/* we cannot block */
return SSH_AGAIN;
}
}
if (sftp_read_and_dispatch(sftp) < 0) {
/* something nasty has happened */
SFTP_AIO_FREE(*aio);
return SSH_ERROR;
}
msg = sftp_dequeue(sftp, (*aio)->id);
}
/*
* Release memory for the structure that (*aio) points to
* as all further points of return are for success or
* failure.
*/
SFTP_AIO_FREE(*aio);
switch (msg->packet_type) {
case SSH_FXP_STATUS:
status = parse_status_msg(msg);
sftp_message_free(msg);
if (status == NULL) {
return SSH_ERROR;
}
sftp_set_error(sftp, status->status);
if (status->status != SSH_FX_EOF) {
ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
"SFTP server : %s", status->errormsg);
err = SSH_ERROR;
} else {
file->eof = 1;
/* Update the offset correctly */
file->offset = file->offset - bytes_requested;
err = SSH_OK;
}
status_msg_free(status);
return err;
case SSH_FXP_DATA:
rc = ssh_buffer_get_u32(msg->payload, &string_len);
if (rc == 0) {
/* Insufficient data in the buffer */
ssh_set_error(sftp->session, SSH_FATAL,
"Received invalid DATA packet from sftp server");
sftp_set_error(sftp, SSH_FX_BAD_MESSAGE);
sftp_message_free(msg);
return SSH_ERROR;
}
host_len = ntohl(string_len);
if (host_len > buf_size) {
/*
* This should never happen, as according to the
* SFTP protocol the server reads bytes less than
* or equal to the number of bytes requested to read.
*
* And we have checked before that the buffer size is
* greater than or equal to the number of bytes requested
* to read, hence code of this if block should never
* get executed.
*/
ssh_set_error(sftp->session, SSH_FATAL,
"DATA packet (%u bytes) received from sftp server "
"cannot fit into the supplied buffer (%zu bytes)",
host_len, buf_size);
sftp_set_error(sftp, SSH_FX_FAILURE);
sftp_message_free(msg);
return SSH_ERROR;
}
string_len = ssh_buffer_get_data(msg->payload, buf, host_len);
if (string_len != host_len) {
/* should never happen */
ssh_set_error(sftp->session, SSH_FATAL,
"Received invalid DATA packet from sftp server");
sftp_set_error(sftp, SSH_FX_BAD_MESSAGE);
sftp_message_free(msg);
return SSH_ERROR;
}
/* Update the offset with the correct value */
file->offset = file->offset - (bytes_requested - string_len);
sftp_message_free(msg);
return string_len;
default:
ssh_set_error(sftp->session, SSH_FATAL,
"Received message %d during read!", msg->packet_type);
sftp_set_error(sftp, SSH_FX_BAD_MESSAGE);
sftp_message_free(msg);
return SSH_ERROR;
}
return SSH_ERROR; /* not reached */
}
int sftp_aio_begin_write(sftp_file file,
const void *buf,
size_t len,
sftp_aio *aio)
{
sftp_session sftp = NULL;
ssh_buffer buffer = NULL;
sftp_aio aio_handle = NULL;
uint32_t id;
int rc;
if (file == NULL ||
file->sftp == NULL ||
file->sftp->session == NULL) {
return SSH_ERROR;
}
sftp = file->sftp;
if (buf == NULL) {
ssh_set_error(sftp->session, SSH_FATAL,
"Invalid argument, NULL passed instead "
"of a buffer's address");
sftp_set_error(sftp, SSH_FX_FAILURE);
return SSH_ERROR;
}
if (len == 0) {
ssh_set_error(sftp->session, SSH_FATAL,
"Invalid argument, 0 passed as the number "
"of bytes to write");
sftp_set_error(sftp, SSH_FX_FAILURE);
return SSH_ERROR;
}
if (aio == NULL) {
ssh_set_error(sftp->session, SSH_FATAL,
"Invalid argument, NULL passed instead of a pointer to "
"a location to store an sftp aio handle");
sftp_set_error(sftp, SSH_FX_FAILURE);
return SSH_ERROR;
}
buffer = ssh_buffer_new();
if (buffer == NULL) {
ssh_set_error_oom(sftp->session);
sftp_set_error(sftp, SSH_FX_FAILURE);
return SSH_ERROR;
}
id = sftp_get_new_id(sftp);
rc = ssh_buffer_pack(buffer,
"dSqdP",
id,
file->handle,
file->offset,
len, /* len of datastring */
len, buf);
if (rc != SSH_OK) {
ssh_set_error_oom(sftp->session);
sftp_set_error(sftp, SSH_FX_FAILURE);
SSH_BUFFER_FREE(buffer);
return SSH_ERROR;
}
aio_handle = sftp_aio_new();
if (aio_handle == NULL) {
ssh_set_error_oom(sftp->session);
sftp_set_error(sftp, SSH_FX_FAILURE);
SSH_BUFFER_FREE(buffer);
return SSH_ERROR;
}
aio_handle->file = file;
aio_handle->id = id;
aio_handle->len = len;
rc = sftp_packet_write(sftp, SSH_FXP_WRITE, buffer);
SSH_BUFFER_FREE(buffer);
if (rc == SSH_ERROR) {
SFTP_AIO_FREE(aio_handle);
return SSH_ERROR;
}
/* Assume we wrote len bytes to the file */
file->offset += len;
*aio = aio_handle;
return SSH_OK;
}
ssize_t sftp_aio_wait_write(sftp_aio *aio)
{
sftp_file file = NULL;
size_t bytes_requested;
sftp_session sftp = NULL;
sftp_message msg = NULL;
sftp_status_message status = NULL;
/*
* This function releases the memory of the structure
* that (*aio) points to in all cases except when the
* return value is SSH_AGAIN.
*
* If the return value is SSH_AGAIN, the user should call this
* function again to get the response for the request corresponding
* to the structure that (*aio) points to, hence we don't release the
* structure's memory when SSH_AGAIN is returned.
*/
if (aio == NULL || *aio == NULL) {
return SSH_ERROR;
}
file = (*aio)->file;
bytes_requested = (*aio)->len;
if (file == NULL ||
file->sftp == NULL ||
file->sftp->session == NULL) {
SFTP_AIO_FREE(*aio);
return SSH_ERROR;
}
sftp = file->sftp;
if (bytes_requested == 0) {
/* This should never happen */
ssh_set_error(sftp->session, SSH_FATAL,
"Invalid sftp aio, len for requested i/o is 0");
sftp_set_error(sftp, SSH_FX_FAILURE);
SFTP_AIO_FREE(*aio);
return SSH_ERROR;
}
while (msg == NULL) {
if (file->nonblocking) {
if (ssh_channel_poll(sftp->channel, 0) == 0) {
/* we cannot block */
return SSH_AGAIN;
}
}
if (sftp_read_and_dispatch(sftp) < 0) {
/* something nasty has happened */
SFTP_AIO_FREE(*aio);
return SSH_ERROR;
}
msg = sftp_dequeue(sftp, (*aio)->id);
}
/*
* Release memory for the structure that (*aio) points to
* as all further points of return are for success or
* failure.
*/
SFTP_AIO_FREE(*aio);
if (msg->packet_type == SSH_FXP_STATUS) {
status = parse_status_msg(msg);
sftp_message_free(msg);
if (status == NULL) {
return SSH_ERROR;
}
sftp_set_error(sftp, status->status);
if (status->status == SSH_FX_OK) {
status_msg_free(status);
return bytes_requested;
}
ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
"SFTP server: %s", status->errormsg);
status_msg_free(status);
return SSH_ERROR;
}
ssh_set_error(sftp->session, SSH_FATAL,
"Received message %d during write!",
msg->packet_type);
sftp_message_free(msg);
sftp_set_error(sftp, SSH_FX_BAD_MESSAGE);
return SSH_ERROR;
}
#endif /* WITH_SFTP */