I'm just showing the code used to try to upload a file in sftp:
# Check file size
def check_file_size(path):
try:
size = os.path.getsize(path)
print(f"Local file size: {size} bytes")
return size
except OSError as e:
print(f"Failed to get size of {path}: {e}")
return None
# Create an SFTP session
def create_sftp_session(hostname, port, username, password):
transport = paramiko.Transport((hostname, port))
transport.connect(username=username, password=password)
sftp = paramiko.SFTPClient.from_transport(transport)
return sftp, transport
# Upload a file to the remote server, overwriting if it exists
def upload_file(sftp, local_path, remote_path, retries=3):
try:
# Create a temporary file and copy content to it
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
with open(local_path, 'r') as file:
temp_file.write(file.read().encode())
temp_file_path = temp_file.name
local_size = os.path.getsize(temp_file_path)
sftp.put(temp_file_path, remote_path)
remote_size = sftp.stat(remote_path).st_size
print(f"Remote file size after upload: {remote_size} bytes")
if local_size != remote_size:
print(f"Warning: Size mismatch after upload!")
# Clean up temporary file
os.remove(temp_file_path)
except IOError as e:
print(f"Failed to upload {local_path} to {remote_path}: {e}")
# Delete the local file
def delete_local_file(path):
try:
os.remove(path)
print(f"Local file {path} deleted.")
except OSError as e:
print(f"Failed to delete {path}: {e}")
# Main execution
def main():
# Create the local file
create_local_file(local_file_path)
# Set up SFTP session and upload file
sftp, transport = create_sftp_session(hostname, port, username, password)
try:
# Upload and overwrite the file
upload_file(sftp, local_file_path, remote_file_path)
finally:
sftp.close()
transport.close()
# Delete the local file
delete_local_file(local_file_path)
if __name__ == "__main__":
main()
If run against a remote server it fails:
Failed to upload localfile.txt to /dir/removefile.csv: size mismatch in put! 94 != 141
I run the same code against a local sftp server with docker and it works. Same for the code using sshd-mina (not shown here). This is the unexpected. Why it doesn't work against the remote server?
Another thing that I noticed is that, if I change the upload_file function as:
# Upload a file to the remote server, overwriting if it exists
def upload_file(sftp, local_path, remote_path, retries=3):
for attempt in range(retries):
try:
# Create a temporary file and copy content to it
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
with open(local_path, 'r') as file:
temp_file.write(file.read().encode())
temp_file_path = temp_file.name
local_size = os.path.getsize(temp_file_path)
sftp.put(temp_file_path, remote_path)
remote_size = sftp.stat(remote_path).st_size
print(f"Remote file size after upload: {remote_size} bytes")
if local_size != remote_size:
print(f"Warning: Size mismatch after upload!")
# Clean up temporary file
os.remove(temp_file_path)
break # Exit loop if upload is successful
except IOError as e:
print(f"Attempt {attempt + 1} failed to upload {local_path} to {remote_path}: {e}")
if attempt < retries - 1:
print("Retrying...")
time.sleep(5) # Wait before retrying
It works at the second attempt! (the change consists in just retrying if an exception is raised).
Local file localfile.txt created with content. Attempt 1 failed to upload localfile.txt to /flex-selections/Portfolio.csv: size mismatch in put! 94 != 141 Retrying... Remote file size after upload: 141 bytes Local file localfile.txt deleted.
How is this possible?
The problem was on server side. The legacy S/FTP frontdoor used does not fully support all SFTP commands/features (It's a FTP server with a SFTP enhancement.).
The S/FTP frontdoor does not properly process the target directory from the "put" method and does not forward a "CWD" command to the FTP server.
Workaround: Adding "chdir()" or "listdir()" before the "put()" forces a "CWD" and solved the problem. This is what I think Filezilla does.