veeso / termscp

🖥 A feature rich terminal UI file transfer and explorer with support for SCP/SFTP/FTP/S3/SMB
https://termscp.veeso.dev
MIT License
1.61k stars 46 forks source link

[BUG] - SCP/FTP regex fails to parse when user or group contains symbols #78

Closed insinfo closed 2 years ago

insinfo commented 2 years ago

Description

I'm trying to use your lib/code to transfer a folder from the remote server to my local machine, and it's failing because there's a bug with the regular expression you use to parse the output of the "ls -la" command. the regex cannot handle this output "-rwxrwxrwx 1 www-data www-data 890524 Mar 14 2018 CIENTE.mdj"

Steps to reproduce

fn main() {
    //configure log
    CombinedLogger::init(
        vec![
            TermLogger::new(LevelFilter::Debug, Config::default(), TerminalMode::Mixed, ColorChoice::Auto),           
        ]
    ).unwrap();
    //configure host
    let localhost = Localhost::new(PathBuf::from(r"C:\MyRustProjects\fsbackup_engine")).unwrap();

    //connection setup
    let config = ProtocolParams::Generic(GenericProtocolParams {
        address: "192.168.133.13".to_string(),
        port: 22,
        username: Some(String::from("isaque.neves")),
        password: Some(String::from("Ins257257")),
    });

    let mut activity = FileTransferActivity::new(localhost, FileTransferProtocol::Scp, config);
    activity.connect();

    //let file_to_download = FsEntry::File(FsFile::from_str("/var/www/.profile")); //this work
    let dir_to_download = FsEntry::Directory(FsDirectory::from_str("/var/www/html/Alex"));
    let dest_dir_path = r"C:\MyRustProjects\fsbackup_engine\download";
    let destiny= PathBuf::from(dest_dir_path);
    //create directory if it doesn't exist
    std::fs::create_dir_all(dest_dir_path).expect(format!("Failed to create directory: {}", dest_dir_path).as_str());

    match activity.filetransfer_recv(TransferPayload::Any(dir_to_download), &destiny, None) {
        Ok(result) => {
            println!("result: {:?}", result);
        }
        Err(e) => {
            println!("error: {}", e);//eprintln!()
            process::exit(0);//1
        }
    }
}

Expected behaviour

the expected behavior should be copying the directory without any errors

Environment

Log

C:/rust/cargo/bin/cargo.exe run --color=always --package fsbackup_engine --bin fsbackup_engine
    Finished dev [unoptimized + debuginfo] target(s) in 0.12s
     Running `target\debug\fsbackup_engine.exe`
17:40:28 [DEBUG] (1) fsbackup_engine::host: Initializing localhost at C:\MyRustProjects\fsbackup_engine
17:40:28 [INFO] Localhost initialized with success
17:40:28 [INFO] Connecting to 192.168.133.13:22
17:40:28 [DEBUG] (1) fsbackup_engine::filetransfer::transfer::scp: Trying socket address 192.168.133.13:22
17:40:28 [DEBUG] (1) fsbackup_engine::filetransfer::transfer::scp: 192.168.133.13:22 succeded
17:40:28 [DEBUG] (1) fsbackup_engine::filetransfer::transfer::scp: Initializing handshake
17:40:28 [DEBUG] (1) fsbackup_engine::filetransfer::transfer::scp: Authenticating with username isaque.neves and password *********
17:40:28 [DEBUG] (1) fsbackup_engine::filetransfer::transfer::scp: Connection established: SSH-2.0-OpenSSH_7.9p1 Debian-10+deb10u2
17:40:28 [DEBUG] (1) fsbackup_engine::filetransfer::transfer::scp: Getting working directory...
17:40:28 [DEBUG] (1) fsbackup_engine::filetransfer::transfer::scp: Running command: pwd
17:40:28 [DEBUG] (1) fsbackup_engine::filetransfer::transfer::scp: Command output: /home/isaque.neves

17:40:28 [INFO] Connection established; working directory: /home/isaque.neves
17:40:28 [INFO] Established connection with '192.168.133.13': "SSH-2.0-OpenSSH_7.9p1 Debian-10+deb10u2"
17:40:28 [INFO] Getting file entries in /var/www/html/Alex
17:40:28 [DEBUG] (1) fsbackup_engine::filetransfer::transfer::scp: Running command: cd "/home/isaque.neves"; unset LANG; ls -la "/var/www/html/Alex/"
17:40:29 [DEBUG] (1) fsbackup_engine::filetransfer::transfer::scp: Command output: total 2472
drwxrwxrwx  2 www-data www-data    4096 Nov 25 13:34 .
drwxrwxrwx 88 www-data www-data    4096 Nov 25 10:42 ..
-rwxrwxrwx  1 www-data www-data  890524 Mar 14  2018 CIENTE.mdj
-rwxrwxrwx  1 www-data www-data  149234 Aug  2  2017 permisao_cetil.xlsx
-rwxrwxrwx  1 www-data www-data 1477161 Mar  6  2018 pmro_padrao.mdj

17:40:29 [DEBUG] (1) fsbackup_engine::filetransfer::transfer::scp: parsed FsEntry:  Err(())
17:40:29 [DEBUG] (1) fsbackup_engine::filetransfer::transfer::scp: parsed FsEntry:  Err(())
17:40:29 [DEBUG] (1) fsbackup_engine::filetransfer::transfer::scp: parsed FsEntry:  Err(())
17:40:29 [DEBUG] (1) fsbackup_engine::filetransfer::transfer::scp: parsed FsEntry:  Err(())
17:40:29 [DEBUG] (1) fsbackup_engine::filetransfer::transfer::scp: parsed FsEntry:  Err(())
17:40:29 [DEBUG] (1) fsbackup_engine::filetransfer::transfer::scp: parsed FsEntry:  Err(())
17:40:29 [INFO] Found 0 out of 6 valid file entries
17:40:29 [DEBUG] (1) fsbackup_engine::activities::filetransfer_activiy: Get local_dir_path: C:\MyRustProjects\fsbackup_engine\download
17:40:29 [DEBUG] (1) fsbackup_engine::activities::filetransfer_activiy: Get dst_name: None
17:40:29 [INFO] Making directory C:\MyRustProjects\fsbackup_engine\download\Alex
17:40:29 [INFO] Created directory "C:\MyRustProjects\fsbackup_engine\download\Alex"
17:40:29 [INFO] Getting file entries in /var/www/html/Alex
17:40:29 [DEBUG] (1) fsbackup_engine::filetransfer::transfer::scp: Running command: cd "/home/isaque.neves"; unset LANG; ls -la "/var/www/html/Alex/"
17:40:29 [DEBUG] (1) fsbackup_engine::filetransfer::transfer::scp: Command output: total 2472
drwxrwxrwx  2 www-data www-data    4096 Nov 25 13:34 .
drwxrwxrwx 88 www-data www-data    4096 Nov 25 10:42 ..
-rwxrwxrwx  1 www-data www-data  890524 Mar 14  2018 CIENTE.mdj
-rwxrwxrwx  1 www-data www-data  149234 Aug  2  2017 permisao_cetil.xlsx
-rwxrwxrwx  1 www-data www-data 1477161 Mar  6  2018 pmro_padrao.mdj

17:40:29 [DEBUG] (1) fsbackup_engine::filetransfer::transfer::scp: parsed FsEntry:  Err(())
17:40:29 [DEBUG] (1) fsbackup_engine::filetransfer::transfer::scp: parsed FsEntry:  Err(())
17:40:29 [DEBUG] (1) fsbackup_engine::filetransfer::transfer::scp: parsed FsEntry:  Err(())
17:40:29 [DEBUG] (1) fsbackup_engine::filetransfer::transfer::scp: parsed FsEntry:  Err(())
17:40:29 [DEBUG] (1) fsbackup_engine::filetransfer::transfer::scp: parsed FsEntry:  Err(())
17:40:29 [DEBUG] (1) fsbackup_engine::filetransfer::transfer::scp: parsed FsEntry:  Err(())
17:40:29 [INFO] Found 0 out of 6 valid file entries
fn notify_transfer_completed
result: ()

Process finished with exit code 0

Additional information

veeso commented 2 years ago

Yep, you're right, thanks for reporting! There is a wrong clause in the current regex which checks the ls output, which doesn't allow any character which is not alphanumeric.

Anyway, termscp shouldn't be used as a library (it was possible until the latest version) and it it won't be possible anymore in the near future. Feel free to copy the code of the scp.rs file into your project, as long as it is compliant with the MIT license. I'm not going to patch this really soon, but in two months, since a release of the binary requires too much effort and I don't have the time right now.

In the next months I'm going to release this library https://github.com/veeso/remotefs-rs which will expose the entire filetransfer module of termscp, so you'll be able to re-import the original code.

For the moment, if you want to fix it by yourself, the correct regex should be:

^([\-ld])([\-rwxs]{9})\s+(\d+)\s+(.+)\s+(.+)\s+(\d+)\s+(\w{3}\s+\d{1,2}\s+(?:\d{1,2}:\d{1,2}|\d{4}))\s+(.+)$
insinfo commented 2 years ago

Thank you so much for your quick feedback,

"Feel free to copy the code of the scp.rs file into your project, as long as it is compliant with the MIT license."

I followed your suggestion and copied the relevant code and made the Regex change and it worked.

I'm developing an open source linux server backup utility application in flutter for desktop, and currently the entire implementation is written in dart with libssh(not libssh2) links to handle SCP/SFTP, but the file transfer is very slow, so I'm trying to re-implement in rust the part that handles file transfer.

"In the next months I'm going to release this library https://github.com/veeso/remotefs-rs which will expose the entire file transfer module of termscp, so you'll be able to re-import the original code. "

Great news to know that you will be releasing a specific library for file transfer, there are not many open source file transfer library options that implement various protocols like SFTP/SCP/FTP etc, it would be nice if this library you are developing already implement directory synchronization similar to rsync over SSH, maybe using ideas and parts of these projects: https://github.com/WanzenBug/rustsync https://github.com/wchang22/LuminS https://github.com/dropbox/fast_rsync

my work in progress:

https://github.com/insinfo/fsbackup https://github.com/insinfo/fsbackup_engine

insinfo commented 2 years ago

@veeso i found another problem, it seems that the "list_dir" function is falling into an infinite loop, i don't know exactly what is causing it but maybe a better option would be to use the "find" command instead of the "ls -la" to have more control , accuracy of file modification time and better optimization for when you want to do a "list_dir_recursively" to get the complete list of files before starting the copy.

find "$(cd '/var/www/'; pwd)" -maxdepth 1 -printf '%M|%u|%g|%s|%Ts|%p|%f|%l\n'

loop ...$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sd...

image

16:35:21 [INFO] Getting file entries in /var/www/.pub-cache/hosted/pub.dartlang.org/devtools-0.1.1/build/pack/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk
16:35:21 [INFO] Getting file entries in /var/www/.pub-cache/hosted/pub.dartlang.org/devtools-0.1.1/build/pack/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk
16:35:21 [INFO] Getting file entries in /var/www/.pub-cache/hosted/pub.dartlang.org/devtools-0.1.1/build/pack/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk/$sdk
fn main() {
    //configure log
    CombinedLogger::init(
        vec![
            TermLogger::new(LevelFilter::Debug, Config::default(), TerminalMode::Mixed, ColorChoice::Auto),
            // WriteLogger::new(LevelFilter::Info, Config::default(), File::create("my_rust_binary.log").unwrap()),
        ]
    ).unwrap();
    //configure host
    let localhost = Localhost::new(PathBuf::from(r"C:\MyRustProjects\fsbackup_engine")).unwrap();

    //connection setup
    let config = ProtocolParams::Generic(GenericProtocolParams {
        address: "192.168.133.13".to_string(),
        port: 22,
        username: Some(String::from("isaque.neves")),
        password: Some(String::from("Ins257257")),
    });

    let mut activity = FileTransferActivity::new(localhost, FileTransferProtocol::Scp, config);
    activity.connect();

    //let file_to_download = FsEntry::File(FsFile::from_str("/var/www/.profile"));
    // /var/www/html/Alex
    let dir_to_download =FsDirectory::from_str("/var/www");
    let entry_to_download = FsEntry::Directory(dir_to_download.clone());
    let dest_dir_path = r"C:\MyRustProjects\fsbackup_engine\download";
    let destiny = PathBuf::from(dest_dir_path);
    //create directory if it doesn't exist
    std::fs::create_dir_all(dest_dir_path).expect(format!("Failed to create directory: {}", dest_dir_path).as_str());

    let start =  std::time::Instant::now();
    match activity.filetransfer_recv(TransferPayload::Any(entry_to_download), &destiny, None) {
    //match activity.filetransfer_recv_dir_as_zip(&dir_to_download, &destiny, "result.zip") {
        Ok(result) => {
            let duration = start.elapsed();
            info!("Time elapsed in filetransfer_recv: {:?}", duration);
            println!("result: {:?}", result);
        }
        Err(e) => {
            println!("error: {:?}", e);//eprintln!()
            process::exit(0);//1
        }
    }
}

possible alternative

/// ### list_dir_recursively
    ///
    /// List directory entries recursively
    fn list_dir_recursively(&mut self, base_path: &Path) -> FileTransferResult<Vec<FsEntry>> {
        match self.is_connected() {
            true => {
                info!("list dir recursively of: {}", base_path.display());
                let base_path: PathBuf = Self::resolve(base_path);
                let wrkdir: PathBuf = self.wrkdir.clone();

                match self.is_valid_file_name(&base_path.to_str().unwrap()) {
                    Ok(r) => {}
                    Err(err) => {
                        error!("{} : {}",err, base_path.display());
                        return Err(FileTransferError::new(
                            FileTransferErrorType::InvalidFilename,
                        ));
                    }
                }

                //  \0  ASCII NUL.
                //  \\  A literal backslash (`\')
                // -mindepth levels | -maxdepth 2
                //-xdev  Don't descend directories on other filesystems.
                //%a  Hora do último acesso ao arquivo no formato retornado pela função C ctime (3).
                //%Ak Hora do último acesso ao arquivo no formato especificado por k , que é `@ 'ou uma diretiva para a função C example: find . -printf '%A@ | %p\n'
                //%f  Print the basename; the file's name with any leading directories removed (only the last element).
                // For /, the result is `/'.
                //%P  File's name with the name of the starting-point under which it was found removed.
                //%t     File's last modification time in the format returned by the C ctime(3) function.
                //%T@    seconds since Jan. 1, 1970, 00:00 GMT, with fractional part.
                //%Ts    The number of seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC). (TZ) (Calculated from mktime(tm).)
                //%c     File's last status change
                //%d     File's depth in the directory tree
                //%h     Dirname; the Leading directories of the file's name (all but the last element).  If the file name contains no slashes (since it is in the current directory) the %h specifier expands to `.'.  For files which are themselves directories and contain a slash (including /), %h expands to the empty string.
                //%H     Starting-point under which file was found.
                //%Ck File's last status change time in the format specified by k, which is the same as for %A.
                //%G  File's numeric group ID.
                //%g  File's group name,
                //%m  File's permission bits (in octal).
                //%U     File's numeric user ID.
                //%u     File's user name
                //%y     File's type (like in ls -l), U=unknown type (shouldn't happen)
                //%l     Object of symbolic link (empty string if file is not a symbolic link).
                //A opção -depth, por exemplo, faz com que o  find percorra o sistema de arquivos em uma ordem de profundidade primeiro.
                // find "$(cd /home/sali; pwd)" -printf '%M|%u|%g|%s|%Ts|%p|%f\n'
                // com o caractere nulo \0  Isso permite que os nomes de arquivo que contêm novas linhas
                // ou outros tipos de espaço em branco sejam interpretados corretamente por programas que processam a saída
                // A configuração da  variável de ambiente LC_CTYPE é usada para determinar quais caracteres precisam ser  citados.
                match self.perform_shell_cmd_with_path(
                    wrkdir.as_path(),
                    format!("unset LANG; find \"$(cd '{}'; pwd)\" -printf '%M|%U|%G|%s|%Ts|%p|%f|%l\n'", base_path.display()).as_str(),
                ) {
                    Ok(output) => {
                        // Split output by (\r)\n
                        let lines: Vec<&str> = output.as_str().lines().collect();
                        let mut entries: Vec<FsEntry> = Vec::with_capacity(lines.len());
                        let mut index = 0;
                        for line in lines.iter() {
                            // First line must always be ignored
                            // Parse row, if ok push to entries

                            if index > 0 {
                                let parsed = self.parse_find_output(&base_path, line);
                                //if let Ok(mut entry) = parse
                                if let Ok( entry) = parsed {
                                    entries.push(entry)
                                }
                            }
                            index += 1;
                        }
                       // info!( "Found {} out of {} valid file entries", entries.len(), lines.len() );
                        Ok(entries)
                    }
                    Err(err) => Err(FileTransferError::new_ex(
                        FileTransferErrorType::ProtocolError,
                        err.to_string(),
                    )),
                }
            }
            false => Err(FileTransferError::new(
                FileTransferErrorType::UninitializedSession,
            )),
        }
    }
fn parse_find_output(&mut self, path: &Path, line: &str) -> Result<FsEntry, ()> {

        //debug!("parse_find_outputline: '{}'", line);
        let columns: Vec<&str> = line.split('|').collect();
        //0          1         2      3      4                        5      6      7
        //permission | user | group | size | last modification time | path | name | Object of symbolic link
        //-rwxrwxrwx|www-data|www-data|890524|1521033382|/var/www/html/Alex/CIENTE.mdj|CIENTE.mdj
        /*if columns.len() < 7 {
            return Err(());
        }*/
        if columns.len() != 8 {
            error!("Parse Error, possibly there is a file with an invalid name line: {:?}",line);
            return Err(());
        }

        //debug!("Parsing find line metadata: {:?}", columns);
        // Get if is directory and if is symlink
        let type_info = columns.get(0).unwrap().get(0..1).unwrap();
        //debug!("Parsing type_info: {:?}", type_info);
        let (mut is_dir, is_symlink): (bool, bool) = match type_info
        {
            "-" => (false, false),
            "l" => (false, true),
            "d" => (true, false),
            _ => return Err(()), // Ignore special files
        };

        let permition_info = &columns.get(0).unwrap()[1..];
        // Check string length (unix pex)
        if permition_info.len() < 9 {
            return Err(());
        }

        // this: rw-r--r--" to: (UnixPex { read: true, write: true, execute: false }, UnixPex { read: true, write: false, execute: false }, UnixPex { read: true, write: false, execute: false })
        // Get unix pex
        let unix_pex = parse_str_linux_permission(&permition_info);
        //debug!("Parsing permition_info: {:?} parsed: {:?}", permition_info, unix_pex);
        // Parse mtime and convert to SystemTime
        let mtime_info = columns.get(4).unwrap();
        //debug!("Parsing mtime_info: {:?}", mtime_info);
        let mtime: SystemTime = match str_unix_timestamp_to_system_time(mtime_info) {
            Ok(t) => t,
            Err(_) => { return Err(()); }
        };

        // Get uid
        let uid_info = columns.get(1).unwrap();
        let uid: Option<u32> = match uid_info.parse::<u32>() {
            Ok(uid) => Some(uid),
            Err(_) => None,
        };
        // Get gid
        let gid_info = columns.get(2).unwrap();
        let gid: Option<u32> = match gid_info.parse::<u32>() {
            Ok(gid) => Some(gid),
            Err(_) => None,
        };
        // Get filesize
        let filesize_info = columns.get(3).unwrap();
        let filesize: usize = filesize_info
            .parse::<usize>()
            .unwrap_or(0);

        // Get link and name
        let file_name_info = columns.get(6).unwrap();
        let symbolic_link_info = columns.get(7).unwrap();
        let (file_name, symlink_path): (String, Option<PathBuf>) = match is_symlink {
            true => (file_name_info.to_string(), Some(PathBuf::from(symbolic_link_info))),
            false => (file_name_info.to_string(), None),
        };

        if file_name.as_str() == "." || file_name.as_str() == ".." {
            //debug!("File name is {}; ignoring entry", file_name);
            return Err(());
        }

        // get absolute path
        let abs_path_info = columns.get(5).unwrap();
        let abs_path: PathBuf = PathBuf::from(abs_path_info);
        //debug!("Parsing abs_path: {:?}", abs_path);

        //Obtenha o link simbólico; PATH não deve ser igual a nome do arquivo
        // Get symlink; PATH mustn't be equal to filename
        let symlink: Option<Box<FsEntry>> = match symlink_path {
            None => None,
            Some(p) => match p.file_name().unwrap_or_else(|| std::ffi::OsStr::new(""))
                == file_name.as_str()
            {
                // If name is equal, don't stat path; otherwise it would get stuck
                // Se o nome for igual, não stat path; caso contrário, ficaria preso
                true => None,
                false => match self.stat2(p.as_path()) {
                    // If path match filename
                    Ok(e) => {
                        // If e is a directory, set is_dir to true
                        if e.is_dir() {
                            is_dir = true;
                        }
                        Some(Box::new(e))
                    }
                    Err(_) => None, // Ignore errors
                }
            },
        };
        //debug!("Parsing symlink: {:?}", symlink);

        // Get extension
        let extension: Option<String> = abs_path
            .as_path()
            .extension()
            .map(|s| String::from(s.to_string_lossy()));
        // debug!("Parsing extension: {:?}", extension);

        // Push to entries
        Ok(match is_dir {
            true => FsEntry::Directory(FsDirectory {
                name: file_name,
                abs_path,
                last_change_time: mtime,
                last_access_time: mtime,
                creation_time: mtime,
                symlink,
                user: uid,
                group: gid,
                unix_pex: Some(unix_pex),
            }),
            false => FsEntry::File(FsFile {
                name: file_name,
                abs_path,
                last_change_time: mtime,
                last_access_time: mtime,
                creation_time: mtime,
                size: filesize,
                ftype: extension,
                symlink,
                user: uid,
                group: gid,
                unix_pex: Some(unix_pex),
            }),
        })
    }

I'm also noticing very long copy time (6 hours to copy 10GB, 300000 files), I don't know if this is normal as I'm still doing tests to compare with my implementation done in dart, but I believe there should be some way to improve one bit of that, one thing I did in dart was calculate the total size very quickly and simply as I show below and also increase the copy buffer size but I'm not sure how to do this in rust with libssh2 because in dart I used libssh which is quite different from the ssh2 lib.

dart version of get Size Of Directory for calculate statistics progress bar

///return total size in bytes of each file inside folder ignoring linux directory metadata size
  int getSizeOfDirectory(Pointer<ssh_session_struct> session, String remoteDirectoryPath,
      {bool isThrowException = true}) {
    //windows = 1041090242
    //debian < 6 : find /var/www -type f -print0 | xargs -0 stat --format=%s |  paste -sd+ - | bc -l
    //ls -lAR | grep -v '^d' | awk '{total += $5} END {print "Total:", total}'
    //linux = 1041090469 -> find ./dart/  -type f -print | xargs -d '\n' du -bs | awk '{ sum+=$1} END {print sum}'
    var cmdToGetTotaSize =
        "find $remoteDirectoryPath -type f -print0 | xargs -0 stat --format=%s | awk '{s+=\$1} END {print s}'";
    String? cmdRes;
    try {
      cmdRes = execCommandSync(session, cmdToGetTotaSize);
      if (cmdRes.trim().isEmpty) {
        //verifica se é um link sinbolico file /var/run
        //var/run: symbolic link to /run
        cmdRes = execCommandSync(session, 'file $remoteDirectoryPath');
        //é um link sinbolico ?
        if (cmdRes.contains('symbolic')) {
          //obtem o caminho real do link sinbolico
          var cmdRe = execCommandSync(session, 'readlink -f $remoteDirectoryPath');
          if (cmdRe.trim().isNotEmpty) {
            cmdRes = execCommandSync(session,
                "find ${cmdRe.replaceAll(RegExp(r'\n'), '')} -type f -print0 | xargs -0 stat --format=%s | awk '{s+=\$1} END {print s}'");
          }
        }
      }
      //para debian antigos como debian 6
      //exeplo 1.57903e+10
      if (cmdRes.contains('e+')) {
        cmdRes = execCommandSync(
            session, 'find $remoteDirectoryPath -type f -print0 | xargs -0 stat --format=%s |  paste -sd+ - | bc -l');
      } else if (int.tryParse(cmdRes) == null) {
        //du -s -B1 /var/www
        //4096 * 23775 = 97382400
        //obter total de diretorios => find /var/www  -type d -print| wc -l
        // find /var/www -type f -print0 | du -scb -L --apparent-size --files0-from=- | tail -n 1
        //cmdRes = execCommandSync(session, 'du -sb --apparent-size --block-size=1 $remoteDirectoryPath');
        cmdRes = execCommandSync(session,
            'find $remoteDirectoryPath -type f -print0 | du -scb -L --apparent-size --files0-from=- | tail -n 1');
        return int.parse(cmdRes.split(' ').first.trim());
      }

      return int.parse(cmdRes);
    } catch (e) {
      print('getSizeOfDirectory: $e \r\n cmdToGetTotaSize: $cmdToGetTotaSize \r\n cmdRes: $cmdRes');
      if (isThrowException) {
        throw LibsshGetFileSizeException(
            'Unable to get the size of a directory in bytes, \r\n cmd: $cmdToGetTotaSize cmdResult: $cmdRes');
      }
    }
    return 0;
  }
veeso commented 2 years ago

The recursive problem is caused by the fact that you probably have something like this:

❯ /bin/ls -l
total 0
lrwxr-xr-x  1 christianvisintin  wheel  3 Dec  3 16:56 $sdk -> ../

which is causing the symlink resolver to recurse.

This is going to be solved with the deprecation of the current file transfer module in favour of the newest remotefs-rs library, which won't stat symlink but will just return the path pointed by them.

So not an issue for me.


Regarding this:

I'm also noticing very long copy time (6 hours to copy 10GB, 300000 files), I don't know if this is normal as I'm still doing tests to compare with my implementation done in dart, but I believe there should be some way to improve one bit of that, one thing I did in dart was calculate the total size very quickly and simply as I show below and also increase the copy buffer size but I'm not sure how to do this in rust with libssh2 because in dart I used libssh which is quite different from the ssh2 lib.

Don't know, but as far as I said before, termscp is not a library and the methods perform many operations other than writing data to the ssh session, so it's pretty much obvious. I can't even use your algo to calculate directory size since termscp must handle Aws s3 and FTP. Also, the buffer size in libssh2 is static and is set to 65536, so a change would not improve performance.

veeso commented 2 years ago

Regex fix merged into 0.8.0; fixed in remotefs.