From 038a2542af292197541e0e714dafe246f4961ebc Mon Sep 17 00:00:00 2001 From: wandering1 <1624155937@qq.com> Date: Tue, 21 Jul 2026 23:05:20 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B5=8B=E8=AF=95=E7=A8=8B=E5=BA=8F=E5=8F=AF?= =?UTF-8?q?=E4=BB=A5=E4=BD=BF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Drivers/BSP/NET/lftpd/lftpd.c | 9 +- Drivers/BSP/NET/lftpd/lftpd_io.c | 42 ++++- MDK-ARM/build_log.txt | 68 ++++---- test/ftp_up_dl_test.py | 282 +++++++++++++++++++++++++++++++ 4 files changed, 361 insertions(+), 40 deletions(-) create mode 100644 test/ftp_up_dl_test.py diff --git a/Drivers/BSP/NET/lftpd/lftpd.c b/Drivers/BSP/NET/lftpd/lftpd.c index 7222a7d..4db147c 100644 --- a/Drivers/BSP/NET/lftpd/lftpd.c +++ b/Drivers/BSP/NET/lftpd/lftpd.c @@ -350,7 +350,14 @@ static int cmd_pass(lftpd_client_t *client, const char *arg) static int cmd_pasv(lftpd_client_t *client, const char *arg) { - int listener_socket = lftpd_inet_listen(0); + int listener_socket = -1; + int retry; + + for (retry = 0; retry < 3; retry++) { + listener_socket = lftpd_inet_listen(0); + if (listener_socket >= 0) break; + vTaskDelay(pdMS_TO_TICKS(200)); + } if (listener_socket < 0) { send_simple_response(client->socket, 425, STATUS_425); return -1; diff --git a/Drivers/BSP/NET/lftpd/lftpd_io.c b/Drivers/BSP/NET/lftpd/lftpd_io.c index 8bd33bf..0b8c97e 100644 --- a/Drivers/BSP/NET/lftpd/lftpd_io.c +++ b/Drivers/BSP/NET/lftpd/lftpd_io.c @@ -6,6 +6,27 @@ #include "ff.h" #include "private/lftpd_io.h" +#define DBG_TAG "[LFT_IO]" +#include "dbg_log.h" + +/* + * FatFS 在 FF_USE_LFN=0 时不接受 "/" 前缀路径。 + * 剥离前导 "/" 后用 FatFS 兼容路径,根目录用 "." 替代空字符串。 + */ +static const char *to_fatfs_path(const char *path) +{ + if (path == NULL || *path == '\0') { + return "."; + } + if (*path == '/') { + path++; + if (*path == '\0') { + return "."; + } + } + return path; +} + static int s_file_open = 0; static FIL s_ftp_file; @@ -74,9 +95,13 @@ char *lftpd_io_canonicalize_path(const char *base, const char *name) int lftpd_io_open_read(const char *path) { if (s_file_open) { + DBG_ERROR("open_read '%s': already open", path); return -1; } - if (f_open(&s_ftp_file, path, FA_READ) != FR_OK) { + const char *fpath = to_fatfs_path(path); + FRESULT fr = f_open(&s_ftp_file, fpath, FA_READ); + if (fr != FR_OK) { + DBG_ERROR("open_read '%s': f_open err=%d", fpath, (int)fr); return -1; } s_file_open = 1; @@ -86,9 +111,13 @@ int lftpd_io_open_read(const char *path) int lftpd_io_open_write(const char *path) { if (s_file_open) { + DBG_ERROR("open_write '%s': already open", path); return -1; } - if (f_open(&s_ftp_file, path, FA_WRITE | FA_CREATE_ALWAYS) != FR_OK) { + const char *fpath = to_fatfs_path(path); + FRESULT fr = f_open(&s_ftp_file, fpath, FA_WRITE | FA_CREATE_ALWAYS); + if (fr != FR_OK) { + DBG_ERROR("open_write '%s': f_open err=%d", fpath, (int)fr); return -1; } s_file_open = 1; @@ -130,8 +159,9 @@ void lftpd_io_close(int handle) int lftpd_io_stat(const char *path, uint32_t *p_size, int *p_is_dir) { FILINFO fno; + const char *fpath = to_fatfs_path(path); - if (f_stat(path, &fno) != FR_OK) { + if (f_stat(fpath, &fno) != FR_OK) { return -1; } @@ -147,7 +177,8 @@ int lftpd_io_stat(const char *path, uint32_t *p_size, int *p_is_dir) int lftpd_io_unlink(const char *path) { - if (f_unlink(path) == FR_OK) { + const char *fpath = to_fatfs_path(path); + if (f_unlink(fpath) == FR_OK) { return 0; } return -1; @@ -159,7 +190,8 @@ void *lftpd_io_opendir(const char *path) if (dp == NULL) { return NULL; } - if (f_opendir(dp, path) != FR_OK) { + const char *fpath = to_fatfs_path(path); + if (f_opendir(dp, fpath) != FR_OK) { free(dp); return NULL; } diff --git a/MDK-ARM/build_log.txt b/MDK-ARM/build_log.txt index bfca0bd..007d620 100644 --- a/MDK-ARM/build_log.txt +++ b/MDK-ARM/build_log.txt @@ -1,71 +1,71 @@ *** Using Compiler 'V5.06 update 7 (build 960)', folder: 'C:\Keil_v5\ARM\ARMCC\Bin' Rebuild target 'STM32F407-Demo' assembling startup_stm32f407xx.s... -compiling crc.c... compiling ringbuf.c... -compiling i2c.c... -compiling spi.c... -compiling gpio.c... +compiling crc.c... compiling stm32f4xx_hal_timebase_tim.c... compiling dma.c... -compiling stm32f4xx_hal_flash.c... -compiling stm32f4xx_hal_flash_ramfunc.c... +compiling gpio.c... +compiling i2c.c... compiling stm32f4xx_hal_msp.c... -compiling stm32f4xx_it.c... -compiling usart.c... -compiling app_main.c... +compiling stm32f4xx_hal_flash_ramfunc.c... compiling stm32f4xx_hal_gpio.c... +compiling app_main.c... +compiling usart.c... +compiling stm32f4xx_hal_rcc_ex.c... +compiling stm32f4xx_it.c... compiling sys_clock.c... compiling stm32f4xx_hal_flash_ex.c... -compiling rs485_task.c... -compiling stm32f4xx_hal_rcc_ex.c... -compiling net_task.c... compiling adc_task.c... -compiling main.c... -compiling stm32f4xx_hal_tim_ex.c... +compiling rs485_task.c... compiling stm32f4xx_hal_rcc.c... +compiling main.c... +compiling stm32f4xx_hal_flash.c... +compiling spi.c... +compiling net_task.c... +compiling stm32f4xx_hal_tim_ex.c... compiling freertos.c... compiling stm32f4xx_hal_tim.c... -compiling stm32f4xx_hal_dma_ex.c... compiling stm32f4xx_hal_dma.c... -compiling lftpd_io.c... -compiling lftpd_string.c... -compiling croutine.c... -compiling system_stm32f4xx.c... +compiling stm32f4xx_hal_exti.c... +compiling stm32f4xx_hal_pwr.c... compiling stm32f4xx_hal_i2c_ex.c... compiling stm32f4xx_hal_pwr_ex.c... -compiling stm32f4xx_hal_pwr.c... -compiling stm32f4xx_hal_exti.c... +compiling system_stm32f4xx.c... +compiling stm32f4xx_hal_dma_ex.c... compiling stm32f4xx_hal_cortex.c... compiling stm32f4xx_hal.c... +compiling ch395f.c... compiling rs485.c... compiling sd2506.c... -compiling event_groups.c... -compiling list.c... compiling tpafe5160.c... -compiling ch395f.c... -compiling nand_ftl.c... +compiling lftpd_string.c... compiling gd5f2gq5ue.c... +compiling stm32f4xx_hal_spi.c... +compiling croutine.c... +compiling list.c... +compiling nand_ftl.c... +compiling event_groups.c... compiling stm32f4xx_hal_uart.c... -compiling ch395f_test.c... compiling stream_buffer.c... compiling net_select.c... -compiling queue.c... -compiling stm32f4xx_hal_spi.c... +compiling ch395f_test.c... compiling lftpd_inet.c... +compiling queue.c... compiling timers.c... -compiling tasks.c... -compiling heap_4.c... compiling lftpd.c... +compiling heap_4.c... +compiling tasks.c... +compiling net_socket.c... +compiling stm32f4xx_hal_i2c.c... +compiling map.c... compiling journal.c... compiling port.c... -compiling stm32f4xx_hal_i2c.c... -compiling net_socket.c... -compiling map.c... compiling ff.c... +compiling lftpd_io.c... compiling cmsis_os2.c... linking... -Program Size: Code=57764 RO-data=2132 RW-data=392 ZI-data=54848 +Program Size: Code=58144 RO-data=2132 RW-data=392 ZI-data=54848 FromELF: creating hex file... ".\STM32F407-Demo\STM32F407-Demo.axf" - 0 Error(s), 0 Warning(s). Build Time Elapsed: 00:00:17 diff --git a/test/ftp_up_dl_test.py b/test/ftp_up_dl_test.py new file mode 100644 index 0000000..bd474b0 --- /dev/null +++ b/test/ftp_up_dl_test.py @@ -0,0 +1,282 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +FTP 上传+下载测试脚本 +用法:python ftp_up_dl_test.py --host 192.168.1.100 +""" + +import socket +import sys +import time +import re +import argparse +import os + +HOST = "192.168.1.100" +PORT = 21 +TIMEOUT = 15.0 + +TEST_DATA = b"Hello from Python FTP test!\r\nThis is line 2.\r\nLine 3: " + b"ABCDEFGH" * 50 + b"\r\n" + +def recv_line(sock, timeout=TIMEOUT): + data = b"" + sock.settimeout(timeout) + while True: + try: + ch = sock.recv(1) + except socket.timeout: + print(f" [TIMEOUT] partial: {data[:80]!r}") + return None + if not ch: + return data if data else None + data += ch + if data.endswith(b"\r\n"): + return data + +def send_cmd(sock, cmd, expect_code=None): + print(f" > {cmd}") + sock.sendall((cmd + "\r\n").encode()) + time.sleep(0.15) + response = recv_line(sock) + if response: + text = response.decode(errors='replace').rstrip() + print(f" < {text}") + if expect_code and not text.startswith(str(expect_code)): + print(f" *** Expected {expect_code}, got: {text}") + return None + return response + print(" < [NO RESPONSE]") + return None + +def parse_pasv(resp): + m = re.search(r"(\d+),(\d+),(\d+),(\d+),(\d+),(\d+)", resp.decode()) + if m: + a, b, c, d, e, f = [int(x) for x in m.groups()] + return f"{a}.{b}.{c}.{d}", e * 256 + f + return None, None + +def ftp_transfer_data(host, pasv_ip, pasv_port, cmd, data_to_send=None, read_data=False): + """通用数据通道传输:发送命令 + 连数据端口 + 收发数据""" + print(f"\n [{cmd}] Opening data channel {pasv_ip}:{pasv_port}...") + data_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + data_sock.settimeout(10.0) + try: + data_sock.connect((pasv_ip, pasv_port)) + print(f" Data channel connected.") + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(TIMEOUT) + sock.connect((host, PORT)) + recv_line(sock) # 220 + send_cmd(sock, "USER anonymous") + send_cmd(sock, "PASS test@test.com") + + # PASV + resp = send_cmd(sock, "PASV") + _, data_port = parse_pasv(resp) + print(f" PASV port: {data_port}") + + # Connect data FIRST + data_sock2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + data_sock2.settimeout(10.0) + data_sock2.connect((pasv_ip, data_port)) + + # Send command + time.sleep(0.2) + send_cmd(sock, cmd) + + result = None + if data_to_send: + print(f" Sending {len(data_to_send)} bytes to data channel...") + total = 0 + while total < len(data_to_send): + n = data_sock2.send(data_to_send[total:]) + if n <= 0: + break + total += n + print(f" Sent {total} bytes.") + data_sock2.shutdown(socket.SHUT_WR) + + if read_data: + result = b"" + while True: + try: + chunk = data_sock2.recv(4096) + if not chunk: + break + result += chunk + except socket.timeout: + break + print(f" Received {len(result)} bytes from data channel.") + + data_sock2.close() + resp = recv_line(sock) + if resp: + print(f" Control response: {resp.decode().rstrip()}") + + send_cmd(sock, "QUIT") + sock.close() + data_sock.close() + return result + except Exception as e: + print(f" Data transfer error: {e}") + try: + data_sock.close() + except: + pass + return None + +def test_upload_download(host, port): + print(f"=== FTP Upload/Download Test: {host}:{port} ===\n") + + # 1. Connect + Login + print("[1] Connect + Login") + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(TIMEOUT) + sock.connect((host, port)) + recv_line(sock) # 220 + + if not send_cmd(sock, "USER anonymous", 230): + sock.close(); return + if not send_cmd(sock, "PASS test@test.com", 230): + sock.close(); return + + # 2. TYPE I (binary mode) + print("\n[2] Set binary mode") + send_cmd(sock, "TYPE I") + + # 3. PASV + STOR (upload) + print("\n[3] Upload: STOR up_test.dat") + resp = send_cmd(sock, "PASV") + if not resp: + sock.close(); return + + pasv_ip, pasv_port = parse_pasv(resp) + print(f" PASV: {pasv_ip}:{pasv_port}") + + # Connect data channel + data_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + data_sock.settimeout(10.0) + try: + data_sock.connect((pasv_ip, pasv_port)) + print(f" Data connected.") + except Exception as e: + print(f" Data connect FAILED: {e}") + sock.close(); return + + # Send STOR + send_cmd(sock, "STOR up_test.dat") + + # Send file data + total = 0 + while total < len(TEST_DATA): + try: + n = data_sock.send(TEST_DATA[total:]) + if n <= 0: + break + total += n + except Exception as e: + print(f" Send error: {e}") + break + print(f" Uploaded {total}/{len(TEST_DATA)} bytes.") + + data_sock.shutdown(socket.SHUT_WR) + data_sock.close() + + # Read STOR response + resp = recv_line(sock) + if resp: + print(f" Response: {resp.decode().rstrip()}") + + # 4. PASV + LIST (verify) + print("\n[4] Verify: LIST") + resp = send_cmd(sock, "PASV") + if not resp: + sock.close(); return + pasv_ip, pasv_port = parse_pasv(resp) + + data_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + data_sock.settimeout(10.0) + data_sock.connect((pasv_ip, pasv_port)) + + send_cmd(sock, "LIST") + data = b"" + while True: + try: + chunk = data_sock.recv(4096) + if not chunk: + break + data += chunk + except socket.timeout: + break + print(f" LIST: {data.decode(errors='replace').rstrip() or '(empty directory)'}") + data_sock.close() + + resp = recv_line(sock) + if resp: + print(f" Response: {resp.decode().rstrip()}") + + # 5. PASV + RETR (download) + time.sleep(1.0) + print("\n[5] Download: RETR up_test.dat") + resp = send_cmd(sock, "PASV") + if not resp: + sock.close(); return + pasv_ip, pasv_port = parse_pasv(resp) + + data_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + data_sock.settimeout(10.0) + try: + data_sock.connect((pasv_ip, pasv_port)) + except Exception as e: + print(f" Data connect FAILED: {e}") + sock.close(); return + + send_cmd(sock, "RETR up_test.dat") + + dl_data = b"" + while True: + try: + chunk = data_sock.recv(4096) + if not chunk: + break + dl_data += chunk + except socket.timeout: + break + data_sock.close() + + resp = recv_line(sock) + if resp: + print(f" Response: {resp.decode().rstrip()}") + + # Compare + print(f"\n[6] Compare") + print(f" Uploaded: {len(TEST_DATA)} bytes") + print(f" Downloaded: {len(dl_data)} bytes") + if dl_data == TEST_DATA: + print(f" *** MATCH! Upload/Download success. ***") + else: + print(f" *** MISMATCH! ***") + if dl_data: + print(f" First 80 bytes uploaded: {TEST_DATA[:80]!r}") + print(f" First 80 bytes downloaded: {dl_data[:80]!r}") + + # 7. DELE (delete test file) + print("\n[7] Cleanup: DELE up_test.dat") + send_cmd(sock, "DELE up_test.dat") + + # 8. QUIT + print("\n[8] QUIT") + send_cmd(sock, "QUIT") + sock.close() + print("\n=== Test Complete ===") + +def main(): + parser = argparse.ArgumentParser(description="FTP Upload/Download Test") + parser.add_argument("--host", default=HOST) + parser.add_argument("--port", type=int, default=PORT) + args = parser.parse_args() + test_upload_download(args.host, args.port) + +if __name__ == "__main__": + main()