/*
    cssh - cluster secure shell
    Copyright (C) 2015-2026  Andreas Baumann

    This program 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.

    This program 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/>.
*/

#define _FILE_OFFSET_BITS 64

#include "scp_engine.h"
#include "copy_common.h"
#include "progressbar.h"

#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

// ssh_scp_* has been deprecated wholesale since libssh 0.10.0 in favor of
// the SFTP subsystem (see sftp_engine.c), but it's still fully functional
// and is kept as the engine for pre-0.10.0 libssh / '--protocol scp'.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"

#define BUFSIZE 4096

typedef enum scp_read_state_e {
	CSSH_SCP_READ_STATE_IDLE,
	CSSH_SCP_READ_STATE_READING,
	CSSH_SCP_READ_STATE_EOF
} scp_read_state_e;

typedef enum scp_write_state_e {
	CSSH_SCP_WRITE_STATE_IDLE,
	CSSH_SCP_WRITE_STATE_WRITING,
	CSSH_SCP_WRITE_STATE_DONE
} scp_write_state_e;

typedef struct scp_data_t {
	ssh_scp scp;
	scp_read_state_e read_state;
	scp_write_state_e write_state;
	dir_stack_t dir_stack;         // download only: virtual local cwd
	local_walk_t *walk;            // upload only: local tree walker
	int fd;
	char *filename;
	char *buf;
	uint64_t size;
	uint64_t bytesTransferred;
	bool transfer_started;  // ssh_scp_read()/ssh_scp_write() must be called at
	                        // least once per file, even a zero-size one, or the
	                        // trailing status byte the wire protocol still sends
	                        // desyncs the next pull/push request
	cssh_progressbar_t progressbar;
} scp_data_t;

static void free_scp_host_data( scp_data_t *scp_data, unsigned int nof_sessions )
{
	for( unsigned int i = 0; i < nof_sessions; i++ ) {
		if( scp_data[i].scp != NULL ) {
			ssh_scp_close( scp_data[i].scp );
			ssh_scp_free( scp_data[i].scp );
			scp_data[i].scp = NULL;
		}
		free( scp_data[i].filename );
		scp_data[i].filename = NULL;
		free( scp_data[i].buf );
		scp_data[i].buf = NULL;
		free_dir_stack( &scp_data[i].dir_stack );
		if( scp_data[i].walk != NULL ) {
			walk_local_tree_close( scp_data[i].walk );
			scp_data[i].walk = NULL;
		}
	}
}

static void scp_fatal( ssh_session *session, scp_data_t *scp_data, unsigned int nof_sessions,
	char **host, unsigned int *port, bool verbose, cssh_progressbar_pool_t *pool )
{
	if( scp_data != NULL ) {
		free_scp_host_data( scp_data, nof_sessions );
		free( scp_data );
	}
	cssh_finalize( &session, NULL, host, port, nof_sessions, verbose, pool );
	exit( EXIT_FAILURE );
}

void run_scp_download( ssh_session *session, char **host, unsigned int *port, unsigned int nof_sessions,
	const char *remote_directory, struct gengetopt_args_info *args_info )
{
	bool verbose = args_info->verbose_given > 0;
	int rc;

	scp_data_t *scp_data = (scp_data_t *)calloc( nof_sessions, sizeof( scp_data_t ) );
	if( scp_data == NULL ) {
		fprintf( stderr, "ERROR: Memory allocation failed for scp_data array\n" );
		scp_fatal( session, NULL, nof_sessions, host, port, verbose, NULL );
	}
	for( unsigned int i = 0; i < nof_sessions; i++ ) {
		scp_data[i].read_state = CSSH_SCP_READ_STATE_IDLE;
	}

	for( unsigned int i = 0; i < nof_sessions; i++ ) {
		scp_data[i].scp = ssh_scp_new( session[i],
			SSH_SCP_READ | ( ( args_info->recursive_given > 0 ) ? SSH_SCP_RECURSIVE : 0 ),
			remote_directory );
		if( scp_data[i].scp == NULL ) {
			fprintf( stderr, "ERROR: Unable to open SCP channel: %s\n",
				ssh_get_error( session[i] ) );
			scp_fatal( session, scp_data, nof_sessions, host, port, verbose, NULL );
		}
	}

	for( unsigned int i = 0; i < nof_sessions; i++ ) {
		rc = ssh_scp_init( scp_data[i].scp );
		if( rc != SSH_OK ) {
			fprintf( stderr, "ERROR: Unable to initialize SCP sessions: %s\n",
				ssh_get_error( session[i] ) );
			scp_fatal( session, scp_data, nof_sessions, host, port, verbose, NULL );
		}
	}

	for( unsigned int i = 0; i < nof_sessions; i++ ) {
		scp_data[i].buf = (char *)malloc( BUFSIZE );
		if( scp_data[i].buf == NULL ) {
			fprintf( stderr, "ERROR: Memory allocation failed for receiving buffers of ssh_scp\n" );
			scp_fatal( session, scp_data, nof_sessions, host, port, verbose, NULL );
		}
	}

	cssh_progressbar_pool_t progressbars;
	rc = create_progressbar_pool( &progressbars, nof_sessions );
	if( rc < 0 ) {
		fprintf( stderr, "ERROR: Creation of a pool of progress bars failed\n" );
		scp_fatal( session, scp_data, nof_sessions, host, port, verbose, NULL );
	}

	bool all_eof = false;

	for( unsigned int i = 0; i < nof_sessions; i++ ) {
		create_dir_stack( &scp_data[i].dir_stack );

		char *pwd = getcwd( NULL, 0 );
		if( pwd == NULL ) {
			fprintf( stderr, "ERROR: failed to determine working directory for SCP for host '%s': %s\n",
				host[i], strerror( errno ) );
			scp_fatal( session, scp_data, nof_sessions, host, port, verbose, &progressbars );
		}

		rc = push_dir_stack( &scp_data[i].dir_stack, pwd );
		if( rc < 0 ) {
			fprintf( stderr, "ERROR: failed to push current directory to stack for host '%s'\n",
				host[i] );
			free( pwd );
			scp_fatal( session, scp_data, nof_sessions, host, port, verbose, &progressbars );
		}

		if( nof_sessions > 1 ) {
			char *full_path = join_path( pwd, host[i] );
			if( full_path == NULL ) {
				fprintf( stderr, "ERROR: Memory allocation failed for full path of base directory for host '%s'\n",
					host[i] );
				free( pwd );
				scp_fatal( session, scp_data, nof_sessions, host, port, verbose, &progressbars );
			}
			rc = push_dir_stack( &scp_data[i].dir_stack, full_path );
			scp_data[i].filename = full_path;

			if( rc < 0 ) {
				fprintf( stderr, "ERROR: failed remember initial base directory '%s' in directory stack\n",
					pwd );
				free( pwd );
				scp_fatal( session, scp_data, nof_sessions, host, port, verbose, &progressbars );
			}
			free( pwd );

			rc = mkdir( full_path, 0750 );
			if( rc < 0 ) {
				if( errno != EEXIST ) {
					fprintf( stderr, "ERROR: failed to create base directory '%s' for host '%s': %s\n",
						full_path, host[i], strerror( errno ) );
					scp_fatal( session, scp_data, nof_sessions, host, port, verbose, &progressbars );
				}
			}
		} else {
			scp_data[i].filename = pwd;
		}
	}

	while( !all_eof ) {
		for( unsigned int i = 0; i < nof_sessions; i++ ) {
			switch( scp_data[i].read_state ) {
				case CSSH_SCP_READ_STATE_IDLE:
					rc = ssh_scp_pull_request( scp_data[i].scp );
					switch( rc ) {
						case SSH_SCP_REQUEST_NEWDIR: {
							char *dir = top_dir_stack( &scp_data[i].dir_stack );

							rc = chdir( dir );
							if( rc < 0 ) {
								fprintf( stderr, "ERROR: failed to change to directory '%s': %s\n",
									dir, strerror( errno ) );
								scp_fatal( session, scp_data, nof_sessions, host, port, verbose, &progressbars );
							}

							const char *filename = ssh_scp_request_get_filename( scp_data[i].scp );
							int mode = ssh_scp_request_get_permissions( scp_data[i].scp );
							if( verbose ) {
								fprintf( stderr, "Receiving directory '%s' with permissions '0%o'\n",
									filename, mode );
							}
							rc = ssh_scp_accept_request( scp_data[i].scp );
							if( rc != SSH_OK ) {
								fprintf( stderr, "ERROR: accepting request for directory '%s' failed: %s\n",
									filename, ssh_get_error( session[i] ) );
								scp_fatal( session, scp_data, nof_sessions, host, port, verbose, &progressbars );
							}

							rc = mkdir( filename, mode );
							if( rc < 0 && errno != EEXIST ) {
								fprintf( stderr, "ERROR: failed to create directory '%s': %s\n",
									filename, strerror( errno ) );
								scp_fatal( session, scp_data, nof_sessions, host, port, verbose, &progressbars );
							}
							char *pwd = getcwd( NULL, 0 );
							if( pwd == NULL ) {
								fprintf( stderr, "ERROR: failed to determine working directory for SCP for host '%s': %s\n",
									host[i], strerror( errno ) );
								scp_fatal( session, scp_data, nof_sessions, host, port, verbose, &progressbars );
							}
							char *full_path = join_path( pwd, filename );
							if( full_path == NULL ) {
								fprintf( stderr, "ERROR: Memory allocation failed for full path of directory '%s'\n",
									filename );
								free( pwd );
								scp_fatal( session, scp_data, nof_sessions, host, port, verbose, &progressbars );
							}

							rc = push_dir_stack( &scp_data[i].dir_stack, full_path );
							free( pwd );
							if( rc < 0 ) {
								fprintf( stderr, "ERROR: failed remember directory '%s' in directory stack\n",
									full_path );
								free( full_path );
								scp_fatal( session, scp_data, nof_sessions, host, port, verbose, &progressbars );
							}
							free( full_path );
							} break;

						case SSH_SCP_REQUEST_ENDDIR:
							pop_dir_stack( &scp_data[i].dir_stack );
							break;

						case SSH_SCP_REQUEST_NEWFILE: {
							const char *filename = ssh_scp_request_get_filename( scp_data[i].scp );
							int mode = ssh_scp_request_get_permissions( scp_data[i].scp );
							scp_data[i].size = ssh_scp_request_get_size64( scp_data[i].scp );
							if( verbose ) {
								fprintf( stderr, "Receiving file '%s' with permissions '0%o' of size '%"PRIu64"'\n",
									filename, mode, scp_data[i].size );
							}
							rc = ssh_scp_accept_request( scp_data[i].scp );
							if( rc != SSH_OK ) {
								fprintf( stderr, "ERROR: accepting request for file '%s' failed: %s\n",
									filename, ssh_get_error( session[i] ) );
								scp_fatal( session, scp_data, nof_sessions, host, port, verbose, &progressbars );
							}

							char *dir = top_dir_stack( &scp_data[i].dir_stack );
							rc = chdir( dir );
							if( rc < 0 ) {
								fprintf( stderr, "ERROR: failed to change to directory '%s': %s\n",
									dir, strerror( errno ) );
								scp_fatal( session, scp_data, nof_sessions, host, port, verbose, &progressbars );
							}

							char *full_path = join_path( dir, filename );
							if( full_path == NULL ) {
								fprintf( stderr, "ERROR: Memory allocation failed for full path of directory '%s'\n",
									filename );
								scp_fatal( session, scp_data, nof_sessions, host, port, verbose, &progressbars );
							}

							int fd = open( full_path, O_WRONLY | O_CREAT | O_TRUNC, mode );
							if( fd < 0 ) {
								fprintf( stderr, "ERROR: Unable to open file '%s': %s\n",
									full_path, strerror( errno ) );
								scp_fatal( session, scp_data, nof_sessions, host, port, verbose, &progressbars );
							}

							scp_data[i].fd = fd;
							scp_data[i].filename = full_path;
							scp_data[i].bytesTransferred = 0;
							scp_data[i].transfer_started = false;
							scp_data[i].read_state = CSSH_SCP_READ_STATE_READING;

							size_t len = strlen( host[i] ) + strlen( full_path ) + 5;
							rc = create_progressbar( &scp_data[i].progressbar, 0, scp_data[i].size, len,
								"[%s:%s]", host[i], full_path );
							if( rc < 0 ) {
								fprintf( stderr, "ERROR: Unable to create progress bar for host '%s' and file '%s'\n",
									host[i], full_path );
								scp_fatal( session, scp_data, nof_sessions, host, port, verbose, &progressbars );
							}
							append_progressbar_to_pool( &progressbars, &scp_data[i].progressbar );

							} break;

						case SSH_SCP_REQUEST_EOF:
							scp_data[i].read_state = CSSH_SCP_READ_STATE_EOF;
							all_eof = true;
							for( unsigned int j = 0; j < nof_sessions; j++ ) {
								if( scp_data[j].read_state != CSSH_SCP_READ_STATE_EOF ) {
									all_eof = false;
									break;
								}
							}
							break;

						case SSH_ERROR:
							fprintf( stderr, "ERROR: error from remote host '%s': %s\n",
								host[i], ssh_get_error( session[i] ) );
							scp_fatal( session, scp_data, nof_sessions, host, port, verbose, &progressbars );
							break;

						case SSH_SCP_REQUEST_WARNING:
							// a warning is advisory, not connection-terminating:
							// log it and keep going with this host's next request
							fprintf( stderr, "WARNING: remote host '%s': %s\n",
								host[i], ssh_scp_request_get_warning( scp_data[i].scp ) );
							break;
					}
					break;

				case CSSH_SCP_READ_STATE_READING:
					if( scp_data[i].bytesTransferred < scp_data[i].size || !scp_data[i].transfer_started ) {
						rc = ssh_scp_read( scp_data[i].scp, scp_data[i].buf, BUFSIZE );
						if( rc == SSH_ERROR ) {
							fprintf( stderr, "ERROR: reading data for file '%s' failed: %s\n",
								scp_data[i].filename, ssh_get_error( session[i] ) );
							scp_fatal( session, scp_data, nof_sessions, host, port, verbose, &progressbars );
						}
						scp_data[i].transfer_started = true;
						scp_data[i].bytesTransferred += rc;
						if( rc > 0 ) {
							ssize_t r = write( scp_data[i].fd, scp_data[i].buf, rc );
							if( r < 0 ) {
								fprintf( stderr, "ERROR: writing data to file '%s' failed: %s\n",
									scp_data[i].filename, strerror( errno ) );
								scp_fatal( session, scp_data, nof_sessions, host, port, verbose, &progressbars );
							}
						}
						if( args_info->progress_bar_given > 0 ) {
							set_value_of_progressbar( &scp_data[i].progressbar, scp_data[i].bytesTransferred );
						}
					} else {
						rc = close( scp_data[i].fd );
						if( rc < 0 ) {
							fprintf( stderr, "ERROR: Unable to close file '%s': %s\n",
								scp_data[i].filename, strerror( errno ) );
							scp_fatal( session, scp_data, nof_sessions, host, port, verbose, &progressbars );
						}
						scp_data[i].read_state = CSSH_SCP_READ_STATE_IDLE;
						scp_data[i].fd = 0;
						if( args_info->progress_bar_given > 0 ) {
							print_transfer_done( host[i], scp_data[i].filename, scp_data[i].size );
						}
						free( scp_data[i].filename );
						scp_data[i].filename = NULL;
						remove_progressbar_from_pool( &progressbars, &scp_data[i].progressbar );
						free_progressbar( &scp_data[i].progressbar );
					}
					break;

				case CSSH_SCP_READ_STATE_EOF:
					break;
			}
		}
		if( args_info->progress_bar_given ) {
			redraw_progressbars( &progressbars );
		}
	}

	free_scp_host_data( scp_data, nof_sessions );
	free( scp_data );
	cssh_finalize( &session, NULL, host, port, nof_sessions, verbose, &progressbars );
}

void run_scp_upload( ssh_session *session, char **host, unsigned int *port, unsigned int nof_sessions,
	const char *local_directory, const char *remote_directory, struct gengetopt_args_info *args_info )
{
	bool verbose = args_info->verbose_given > 0;
	bool recursive = args_info->recursive_given > 0;
	int rc;

	scp_data_t *scp_data = (scp_data_t *)calloc( nof_sessions, sizeof( scp_data_t ) );
	if( scp_data == NULL ) {
		fprintf( stderr, "ERROR: Memory allocation failed for scp_data array\n" );
		scp_fatal( session, NULL, nof_sessions, host, port, verbose, NULL );
	}
	for( unsigned int i = 0; i < nof_sessions; i++ ) {
		scp_data[i].write_state = CSSH_SCP_WRITE_STATE_IDLE;
	}

	for( unsigned int i = 0; i < nof_sessions; i++ ) {
		scp_data[i].scp = ssh_scp_new( session[i],
			SSH_SCP_WRITE | ( recursive ? SSH_SCP_RECURSIVE : 0 ),
			remote_directory );
		if( scp_data[i].scp == NULL ) {
			fprintf( stderr, "ERROR: Unable to open SCP channel: %s\n",
				ssh_get_error( session[i] ) );
			scp_fatal( session, scp_data, nof_sessions, host, port, verbose, NULL );
		}
	}

	for( unsigned int i = 0; i < nof_sessions; i++ ) {
		rc = ssh_scp_init( scp_data[i].scp );
		if( rc != SSH_OK ) {
			fprintf( stderr, "ERROR: Unable to initialize SCP sessions: %s\n",
				ssh_get_error( session[i] ) );
			scp_fatal( session, scp_data, nof_sessions, host, port, verbose, NULL );
		}
	}

	for( unsigned int i = 0; i < nof_sessions; i++ ) {
		scp_data[i].buf = (char *)malloc( BUFSIZE );
		if( scp_data[i].buf == NULL ) {
			fprintf( stderr, "ERROR: Memory allocation failed for sending buffers of ssh_scp\n" );
			scp_fatal( session, scp_data, nof_sessions, host, port, verbose, NULL );
		}
		scp_data[i].walk = walk_local_tree_open( local_directory, recursive );
		if( scp_data[i].walk == NULL ) {
			fprintf( stderr, "ERROR: Memory allocation failed for local tree walk of '%s'\n", local_directory );
			scp_fatal( session, scp_data, nof_sessions, host, port, verbose, NULL );
		}
	}

	cssh_progressbar_pool_t progressbars;
	rc = create_progressbar_pool( &progressbars, nof_sessions );
	if( rc < 0 ) {
		fprintf( stderr, "ERROR: Creation of a pool of progress bars failed\n" );
		scp_fatal( session, scp_data, nof_sessions, host, port, verbose, NULL );
	}

	bool all_eof = false;

	while( !all_eof ) {
		for( unsigned int i = 0; i < nof_sessions; i++ ) {
			switch( scp_data[i].write_state ) {
				case CSSH_SCP_WRITE_STATE_IDLE: {
					walk_event_t ev;
					int wrc = walk_local_tree_next( scp_data[i].walk, &ev );
					if( wrc < 0 ) {
						scp_fatal( session, scp_data, nof_sessions, host, port, verbose, &progressbars );
					} else if( wrc == 0 ) {
						scp_data[i].write_state = CSSH_SCP_WRITE_STATE_DONE;
						break;
					}

					switch( ev.type ) {
						case CSSH_WALK_ENTER_DIR:
							if( verbose ) {
								fprintf( stderr, "Sending directory '%s' with permissions '0%o'\n",
									ev.name, ev.mode );
							}
							rc = ssh_scp_push_directory( scp_data[i].scp, ev.name, ev.mode );
							if( rc != SSH_OK ) {
								fprintf( stderr, "ERROR: pushing directory '%s' failed: %s\n",
									ev.name, ssh_get_error( session[i] ) );
								free_walk_event( &ev );
								scp_fatal( session, scp_data, nof_sessions, host, port, verbose, &progressbars );
							}
							free_walk_event( &ev );
							break;

						case CSSH_WALK_LEAVE_DIR:
							rc = ssh_scp_leave_directory( scp_data[i].scp );
							if( rc != SSH_OK ) {
								fprintf( stderr, "ERROR: leaving directory failed: %s\n",
									ssh_get_error( session[i] ) );
								free_walk_event( &ev );
								scp_fatal( session, scp_data, nof_sessions, host, port, verbose, &progressbars );
							}
							free_walk_event( &ev );
							break;

						case CSSH_WALK_FILE: {
							if( verbose ) {
								fprintf( stderr, "Sending file '%s' with permissions '0%o' of size '%"PRIu64"'\n",
									ev.name, ev.mode, ev.size );
							}

							int fd = open( ev.full_path, O_RDONLY );
							if( fd < 0 ) {
								fprintf( stderr, "ERROR: Unable to open file '%s': %s\n",
									ev.full_path, strerror( errno ) );
								free_walk_event( &ev );
								scp_fatal( session, scp_data, nof_sessions, host, port, verbose, &progressbars );
							}

							rc = ssh_scp_push_file64( scp_data[i].scp, ev.name, ev.size, ev.mode );
							if( rc != SSH_OK ) {
								fprintf( stderr, "ERROR: pushing file '%s' failed: %s\n",
									ev.name, ssh_get_error( session[i] ) );
								close( fd );
								free_walk_event( &ev );
								scp_fatal( session, scp_data, nof_sessions, host, port, verbose, &progressbars );
							}

							scp_data[i].fd = fd;
							scp_data[i].size = ev.size;
							scp_data[i].bytesTransferred = 0;
							scp_data[i].transfer_started = false;
							scp_data[i].filename = strdup( ev.full_path );
							scp_data[i].write_state = CSSH_SCP_WRITE_STATE_WRITING;

							size_t len = strlen( host[i] ) + strlen( ev.full_path ) + 5;
							rc = create_progressbar( &scp_data[i].progressbar, 0, ev.size, len,
								"[%s:%s]", host[i], ev.full_path );
							if( rc < 0 ) {
								fprintf( stderr, "ERROR: Unable to create progress bar for host '%s' and file '%s'\n",
									host[i], ev.full_path );
								free_walk_event( &ev );
								scp_fatal( session, scp_data, nof_sessions, host, port, verbose, &progressbars );
							}
							append_progressbar_to_pool( &progressbars, &scp_data[i].progressbar );
							free_walk_event( &ev );
							} break;
					}
					} break;

				case CSSH_SCP_WRITE_STATE_WRITING:
					if( scp_data[i].bytesTransferred < scp_data[i].size || !scp_data[i].transfer_started ) {
						uint64_t remaining = scp_data[i].size - scp_data[i].bytesTransferred;
						size_t want = ( remaining < BUFSIZE ) ? (size_t)remaining : BUFSIZE;
						ssize_t r = 0;
						if( want > 0 ) {
							r = read( scp_data[i].fd, scp_data[i].buf, want );
							if( r < 0 ) {
								fprintf( stderr, "ERROR: reading data from file '%s' failed: %s\n",
									scp_data[i].filename, strerror( errno ) );
								scp_fatal( session, scp_data, nof_sessions, host, port, verbose, &progressbars );
							}
						}
						// always issue at least one write, even with r==0 for a
						// zero-size file, to consume the trailing status byte
						// (see 'transfer_started' comment on scp_data_t)
						rc = ssh_scp_write( scp_data[i].scp, scp_data[i].buf, (size_t)r );
						if( rc != SSH_OK ) {
							fprintf( stderr, "ERROR: writing data for file '%s' failed: %s\n",
								scp_data[i].filename, ssh_get_error( session[i] ) );
							scp_fatal( session, scp_data, nof_sessions, host, port, verbose, &progressbars );
						}
						scp_data[i].transfer_started = true;
						scp_data[i].bytesTransferred += (uint64_t)r;
						if( args_info->progress_bar_given > 0 ) {
							set_value_of_progressbar( &scp_data[i].progressbar, scp_data[i].bytesTransferred );
						}
					} else {
						rc = close( scp_data[i].fd );
						if( rc < 0 ) {
							fprintf( stderr, "ERROR: Unable to close file '%s': %s\n",
								scp_data[i].filename, strerror( errno ) );
							scp_fatal( session, scp_data, nof_sessions, host, port, verbose, &progressbars );
						}
						scp_data[i].write_state = CSSH_SCP_WRITE_STATE_IDLE;
						scp_data[i].fd = 0;
						if( args_info->progress_bar_given > 0 ) {
							print_transfer_done( host[i], scp_data[i].filename, scp_data[i].size );
						}
						free( scp_data[i].filename );
						scp_data[i].filename = NULL;
						remove_progressbar_from_pool( &progressbars, &scp_data[i].progressbar );
						free_progressbar( &scp_data[i].progressbar );
					}
					break;

				case CSSH_SCP_WRITE_STATE_DONE:
					break;
			}
		}

		all_eof = true;
		for( unsigned int i = 0; i < nof_sessions; i++ ) {
			if( scp_data[i].write_state != CSSH_SCP_WRITE_STATE_DONE ) {
				all_eof = false;
				break;
			}
		}

		if( args_info->progress_bar_given ) {
			redraw_progressbars( &progressbars );
		}
	}

	free_scp_host_data( scp_data, nof_sessions );
	free( scp_data );
	cssh_finalize( &session, NULL, host, port, nof_sessions, verbose, &progressbars );
}

#pragma GCC diagnostic pop
