【更新记录】
本程序基于20200703版的程序,作出了如下更新:
1. 解决了当accept函数的参数err!=ERR_OK时,程序出现HardFault错误的bug。
2. 当lwip MEM_SIZE<10240时,强制编译不通过,以免ftp服务器内存不足。(之前评论区里面可以看到很多人都犯了这个错误)
3. 解决了使用FileZilla客户端连接FTP服务器时,文件名乱码的问题。(不用再去手动选择"强制UTF-8"了)
4. 支持FileZilla断点续传。(REST和APPE命令)
5. 解决安卓AndFTP APP能连接服务器但无法显示文件列表的问题。(也就是要支持SYST命令)
6. 解决SIZE命令不能正确输出大文件大小的问题。
7. 增加MDTM命令,解决Windows文件管理器和FileZilla显示文件时间不一致的问题。
请注意:磁盘中存储的时间,STM32单片机的RTC时间,以及MDTM命令输出的时间全都是UTC时间,但LIST命令显示的时间是带时区的时间。有了MDTM命令,FileZilla能按当前电脑系统设置的时区显示时间,而Windows的文件管理器则是按单片机里设置的时区(也就是LIST命令输出的时间)显示时间。
8. 增加CDUP命令,此命令相当于CWD ..命令,在FileZilla中有时会用到。
9. 获取网络时间的程序改成了使用lwip自带的sntp例程。
Linux系统下模拟lwip运行ftpd:百度网盘 请输入提取码
STM32F107VC+DP83848+W25Q128+FTP程序:https://pan.baidu.com/s/18jbCxDMaiw2axjK1vO0Mmw?pwd=tpow
在此顺便再推荐一下本人的另一个作品:【程序】在STM32单片机上实现基于LwIP 2.1.3协议栈raw API的DHCP服务器,为其他设备分配IPv4地址(20220122版)
【使用方法】
使用起来非常简单,在main函数初始化完lwip后,执行ftpd_init()即可。工程中要有FATFS。
使用ftpd_set_timezone()函数设置LIST命令用的时区,UTC+8是28800。
在STM32单片机上使用时,因为没有gmtime_r函数,所以必须要在lwip-2.1.2/include/arch/cc.h里面加入:#define gmtime_r localtime_r。
【重要提示】
1. FatFs里面必须要定义C盘盘符,否则会获取目录失败。
在ffconf.h里面将FF_STR_VOLUME_ID改成1,FF_VOLUME_STRS改成"C",就可以用C盘了。
2. 这个ftpd是用raw API写的,项目如果使用FreeRTOS操作系统的话,ftpd_init()要么放到tcpip_init指定的回调函数里面调用,要么在其他线程里面调用但前后要用LOCK_TCPIP_CORE()和UNLOCK_TCPIP_CORE()加解锁。
LOCK_TCPIP_CORE();
ftpd_init();
UNLOCK_TCPIP_CORE();
【代码】
ftpd.h:
#ifndef _FTPD_H
#define _FTPD_H
#ifndef FTPD_DEBUG
#define FTPD_DEBUG LWIP_DBG_OFF
#endif
#define FTPD_PORT 21 // FTP服务器端口号
#define FTPD_PASV 1 // 是否允许使用PASV命令
// LIST命令使用的时区 (0: UTC时区, 1: C库时区--不考虑夏令时, 2: ftpd_set_timezone函数自定义的时区)
#define FTPD_SYSTEM_TIMEZONE 2
// 命令处理过程中的异常情况
#define FTPD_CMDSTEP_CONNFAILED 0x1000 // 连接建立失败
#define FTPD_CMDSTEP_CONNABORTED 0x2000 // 连接建立成功但异常中止
#define FTPD_CMDSTEP_CONNSHUTDOWN 0x4000 // 连接建立成功后被客户端关闭
// FTP客户端状态位
#if FTPD_PASV
#define FTPD_FLAG_PASSIVE 0x01 // 当前是否为被动模式
#endif
#define FTPD_FLAG_CLOSE 0x02 // 收到客户端的TCP第一次挥手后, 请求发送第三次挥手
#define FTPD_FLAG_SHUTDOWN 0x04 // 请求发送TCP第一次挥手, 然后接收客户端第三次挥手
#define FTPD_FLAG_RENAME 0x08 // 是否正在重命名文件
#define FTPD_FLAG_AGAIN 0x10 // 当前FTP命令还没有执行完毕, 控制连接上的数据发送完毕后应继续回来处理
#define FTPD_FLAG_NEWDATACONN 0x20 // 数据连接已创建但还未连接上
#define FTPD_FLAG_TCPERROR 0x40 // TCP发送数据出错
// 数据连接关闭方式
#define FTPD_FREEDATA_ABORT 0 // 强行中止数据连接
#define FTPD_FREEDATA_CLOSE 1 // 关闭数据连接 (客户端已关闭)
#define FTPD_FREEDATA_SHUTDOWN 2 // 关闭数据连接 (客户端未关闭)
#ifndef MAX_PATH
#define MAX_PATH 260
#endif
struct ftpd_user
{
char *name;
char *password;
};
struct ftpd_account
{
struct ftpd_user user;
char *rootpath;
};
#ifdef FF_DEFINED
struct ftpd_state
{
struct tcp_pcb *ctrlconn;
struct tcp_pcb *dataconn;
int dataport;
char cmd[MAX_PATH + 20];
int cmdlen;
char *cmdarg;
int cmdstep;
char last;
char type;
char path[MAX_PATH];
char rename[MAX_PATH];
uint64_t rest;
struct ftpd_user user;
int userid;
int flags;
int sent; // 未收到确认的已发送字节数
struct pbuf *queue; // 数据接收队列
void *dataout;
int dataout_len;
DIR *dp;
FIL *fp;
FILINFO *finfo;
};
#else
struct ftpd_state;
#endif
int ftpd_concat_path(char *buffer, int bufsize, const char *filename);
int ftpd_file_exists(const char *path);
#ifdef FF_DEFINED
time_t ftpd_filetime(WORD fdate, WORD ftime, struct tm *ptm);
#endif
int ftpd_fullpath(const struct ftpd_state *state, char *buffer, int bufsize, const char *filename, char **puserpath);
#if FTPD_SYSTEM_TIMEZONE == 2
time_t ftpd_get_timezone(void);
#endif
time_t ftpd_get_timezone_libc(void);
int ftpd_init(void);
void *ftpd_memrchr(const void *s, int c, size_t n);
#if FTPD_SYSTEM_TIMEZONE == 2
void ftpd_set_timezone(time_t timezone);
#endif
int ftpd_simplify_path(char *path, int basepos);
char *ftpd_strdup(const char *s);
#endif
ftpd.c:
/*************************** 基于LwIP raw API的FTP服务器*****************************
** 注意事项:
** 1. 若想要移动文件, 可在文件管理器中使用"xxx/", "../"这样的语法重命名文件
** 例如想要把"abc.txt"移动到当前目录的123目录下, 则可以将文件重命名为"123/abc.txt"
** 将"def.doc"移动到父目录的456文件夹下, 则应该重命名为"../456/def.doc"
** 2. 服务器使用FatFs读写磁盘文件
** 如果出现HardFault错误, 则可能是startup_stm32*.s启动文件里面的Stack_Size值太小
** 将其改大就可以解决问题
************************************************************************************/
#include <ff.h>
#include <lwip/tcp.h>
#include <string.h>
#include <time.h>
#include "ftpd.h"
#if !MEM_LIBC_MALLOC && !MEM_USE_POOLS && MEM_SIZE < 10240L
#error "MEM_SIZE is too small to run FTP server"
#endif
static err_t ftpd_accept(void *arg, struct tcp_pcb *newpcb, err_t err);
static void ftpd_change_user(struct ftpd_state *state, const char *newuser);
static int ftpd_copy_cmd(struct ftpd_state *state);
#if FTPD_PASV
static err_t ftpd_data_accept(void *arg, struct tcp_pcb *newpcb, err_t err);
#endif
static void ftpd_data_check(struct ftpd_state *state);
static err_t ftpd_data_connected(void *arg, struct tcp_pcb *tpcb, err_t err);
static void ftpd_data_err(void *arg, err_t err);
static err_t ftpd_data_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err);
static err_t ftpd_data_sent(void *arg, struct tcp_pcb *tpcb, u16_t len);
static err_t ftpd_data_sent_list(void *arg, struct tcp_pcb *tpcb, u16_t len);
static err_t ftpd_data_sent_retr(void *arg, struct tcp_pcb *tpcb, u16_t len);
static void ftpd_err(void *arg, err_t err);
static void ftpd_free(struct ftpd_state *state);
static err_t ftpd_free_data(struct ftpd_state *state, int option);
static int ftpd_is_valid_user(struct ftpd_user *user, int *pid);
static int ftpd_prepare_data(struct ftpd_state *state);
static void ftpd_process_cmd(struct ftpd_state *state);
static int ftpd_process_data_cmd(struct ftpd_state *state);
static int ftpd_process_directory_cmd(struct ftpd_state *state);
static int ftpd_process_file_cmd(struct ftpd_state *state);
static int ftpd_process_opt_cmd(struct ftpd_state *state);
static int ftpd_process_user_cmd(struct ftpd_state *state);
static err_t ftpd_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err);
static int ftpd_send_msg(struct ftpd_state *state, const char *s);
static err_t ftpd_sent(void *arg, struct tcp_pcb *tpcb, u16_t len);
// 用户列表以及对应的根目录
static const struct ftpd_account ftpd_users[] = {
{{"anonymous", NULL}, "C:/public"}, // 匿名用户
{{"admin", "123456"}, "C:/"},
{{"test", "789123"}, "C:/test"}
};
// 盘符可在ffconf.h中的FF_VOLUME_STRS处指定
static struct tcp_pcb *ftpd_tpcb;
#if FTPD_SYSTEM_TIMEZONE == 2
static time_t ftpd_timezone;
#endif
/* 控制连接收到新请求 */
static err_t ftpd_accept(void *arg, struct tcp_pcb *newpcb, err_t err)
{
struct ftpd_state *state;
if (err != ERR_OK)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("FTPD failed to accept a client. err=%d\n", err));
return err;
}
state = mem_malloc(sizeof(struct ftpd_state));
if (state == NULL)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("FTPD failed to accept [%s]:%d\n", ipaddr_ntoa(&newpcb->remote_ip), newpcb->remote_port));
tcp_abort(newpcb);
return ERR_ABRT;
}
memset(state, 0, sizeof(struct ftpd_state));
state->ctrlconn = newpcb;
state->dataport = -1;
state->type = 'A';
strcpy(state->path, "/");
state->userid = -1;
LWIP_DEBUGF(FTPD_DEBUG, ("FTPD accepted [%s]:%d\n", ipaddr_ntoa(&newpcb->remote_ip), newpcb->remote_port));
ftpd_send_msg(state, "220 LwIP FTP Service\r\n");
tcp_arg(newpcb, state);
tcp_err(newpcb, ftpd_err);
tcp_recv(newpcb, ftpd_recv);
tcp_sent(newpcb, ftpd_sent);
return ERR_OK;
}
/* 改变用户名并清空密码 */
static void ftpd_change_user(struct ftpd_state *state, const char *newuser)
{
if (state->user.name != NULL)
{
mem_free(state->user.name);
state->user.name = NULL;
}
if (state->user.password != NULL)
{
mem_free(state->user.password);
state->user.password = NULL;
}
if (newuser != NULL)
state->user.name = ftpd_strdup(newuser);
}
/* 将文件夹和文件名连接在一起形成新路径 */
// 将buffer和filename连接起来, 保存到buffer中, 同时保证字符串末尾不带斜杠(根目录除外); buffer的最大容量为bufsize
// 成功时返回字符串的长度; 失败时返回-1且buffer中的内容不变
int ftpd_concat_path(char *buffer, int bufsize, const char *filename)
{
char *p;
int addslash, fileabs, folderlen, namelen, len;
// 找出字符串的连接位置, 并去掉buffer的尾斜杠和filename的首斜杠
if (filename != NULL && filename[0] == '/')
{
// 如果文件名是绝对路径, 则需要把文件夹路径改为根目录
fileabs = 1;
filename++; // 去掉首斜杠
if (buffer[0] == '/')
folderlen = 1; // 文件夹路径不带盘符时只保留根目录符号 (首斜杠)
else
{
p = strchr(buffer, ':');
if (p != NULL)
folderlen = p + 1 - buffer; // 文件夹路径带盘符时只保留盘符
else
folderlen = 0; // 如果buffer是相对路径, 清空字符串
}
}
else
{
// 如果文件名不是绝对路径, 则可以直接在文件夹路径末尾连接上文件名
fileabs = 0;
folderlen = strlen(buffer);
if (folderlen > 1 && buffer[folderlen - 1] == '/')
folderlen--;
}
// 去掉filename的尾斜杠
if (filename != NULL)
namelen = strlen(filename);
else
namelen = 0;
if (namelen != 0 && filename[namelen - 1] == '/')
namelen--;
// 计算字符串连接在一起后需要的缓冲区大小
if (folderlen == 0)
addslash = fileabs; // 路径为空时加不加斜杠取决于文件名是不是绝对路径
else if (folderlen == 1 && buffer[0] == '/')
addslash = 0; // 路径为斜杠时不加斜杠
else if (folderlen != 0 && buffer[folderlen - 1] == ':')
addslash = 1; // 路径最后一个字符为冒号时要加斜杠
else if (namelen == 0)
addslash = 0; // 文件名为空时不加斜杠
else
addslash = 1; // 其他情况都要加斜杠
len = folderlen + addslash + namelen; // 连接后的长度
if (len >= bufsize)
return -1; // 缓冲区不够
// 连接字符串
if (addslash)
buffer[folderlen] = '/';
if (namelen != 0)
memcpy(buffer + folderlen + addslash, filename, namelen);
buffer[len] = '\0';
return len;
}
/* 将数据接收队列queue中的FTP命令字符串提取到state->cmd中, 并释放占用的pbuf内存 */
// 返回值: 0表示还没有收到完整命令; 1表示收到了完整命令; 2表示收到了完整命令, 但超过了缓冲区最大长度
// state->cmdlen表示已收到了当前命令多少个字符 (包括\r\n)
static int ftpd_copy_cmd(struct ftpd_state *state)
{
char *c;
int complete = 0; // 是否收到完整命令
int cnt = 0; // 本次复制的字符数
int i;
struct pbuf *p;
for (p = state->queue; p != NULL && complete == 0; p = p->next)
{
c = p->payload;
for (i = 0; i < p->len && complete == 0; i++)
{
if (state->last == '\r' && *c == '\n')
{
if (state->cmdlen <= sizeof(state->cmd))
{
state->cmd[state->cmdlen - 1] = '\0'; // 把\r替换成\0
complete = 1;
}
else
complete = 2;
}
else
{
if (state->cmdlen < sizeof(state->cmd))
state->cmd[state->cmdlen] = *c;
}
state->cmdlen++;
state->last = *c;
c++;
cnt++;
}
}
state->queue = pbuf_free_header(state->queue, cnt);
return complete;
}
#if FTPD_PASV
/* 数据连接被动模式连接建立成功 */
static err_t ftpd_data_accept(void *arg, struct tcp_pcb *newpcb, err_t err)
{
struct ftpd_state *state = arg;
if (err != ERR_OK)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("FTPD data failed to accept a client. err=%d\n", err));
return err;
}
if (!ip_addr_cmp(&newpcb->remote_ip, &state->ctrlconn->remote_ip))
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_WARNING, ("%s: IP address mismatch\n", __FUNCTION__));
tcp_abort(newpcb);
return ERR_ABRT;
}
tcp_close(state->dataconn); // 关闭端口监听
state->dataconn = newpcb;
tcp_err(newpcb, ftpd_data_err);
return ftpd_data_connected(arg, newpcb, err);
}
#endif
/* 检查数据连接是否未开始发送数据 */
// 这个函数应该在控制连接发送完开始信息后调用一次
static void ftpd_data_check(struct ftpd_state *state)
{
#if FTPD_PASV
if (state->flags & FTPD_FLAG_PASSIVE)
{
// 在PASV模式下, 连接可能会在PASV命令执行完毕的时候就建立成功
// 但必须要等到数据传输命令(如LIST命令)的响应(如150响应)发送完毕后, 才能开始发送数据
if ((state->flags & FTPD_FLAG_NEWDATACONN) == 0)
ftpd_data_sent(state, state->dataconn, 0);
// PORT模式下不存在这个问题, 因为连接建立后就可以立即开始发送数据
}
#endif
}
/* 数据连接主动模式建立成功 */
static err_t ftpd_data_connected(void *arg, struct tcp_pcb *tpcb, err_t err)
{
struct ftpd_state *state = arg;
LWIP_DEBUGF(FTPD_DEBUG, ("FTPD data connection to [%s]:%d is established\n", ipaddr_ntoa(&tpcb->remote_ip), tpcb->remote_port));
state->flags &= ~FTPD_FLAG_NEWDATACONN;
tcp_recv(tpcb, ftpd_data_recv);
tcp_sent(tpcb, ftpd_data_sent);
return ftpd_data_sent(arg, tpcb, 0);
}
/* 数据连接出错 */
static void ftpd_data_err(void *arg, err_t err)
{
struct ftpd_state *state = arg;
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_WARNING, ("FTPD data error. err=%d\n", err));
if (state != NULL)
{
state->dataconn = NULL; // 调用err回调函数时, tpcb已经被LwIP释放了, 所以不需要再次释放
ftpd_free_data(state, FTPD_FREEDATA_ABORT);
if (state->flags & FTPD_FLAG_NEWDATACONN)
{
state->flags &= ~FTPD_FLAG_NEWDATACONN;
state->cmdstep |= FTPD_CMDSTEP_CONNFAILED;
ftpd_send_msg(state, "425 Failed to establish connection.\r\n");
}
else
{
state->cmdstep |= FTPD_CMDSTEP_CONNABORTED;
ftpd_process_cmd(state);
}
}
}
/* 数据连接收到数据 */
static err_t ftpd_data_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
{
struct ftpd_state *state = arg;
struct pbuf *q;
FRESULT fr;
UINT bw;
if (p != NULL)
{
if (state != NULL)
{
if (strcasecmp(state->cmd, "STOR") == 0 || strcasecmp(state->cmd, "APPE") == 0)
{
LWIP_DEBUGF(FTPD_DEBUG, ("%s: %d bytes received\n", __FUNCTION__, p->tot_len));
for (q = p; q != NULL; q = q->next)
{
fr = f_write(state->fp, q->payload, q->len, &bw);
if (bw != q->len)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: f_write() failed. fr=%d, q->len=%u, bw=%u\n", __FUNCTION__, fr, q->len, bw));
pbuf_free(p);
err = ftpd_free_data(state, FTPD_FREEDATA_ABORT);
state->cmdstep = FTPD_CMDSTEP_CONNABORTED;
ftpd_process_cmd(state);
return err;
}
}
}
}
tcp_recved(tpcb, p->tot_len);
pbuf_free(p);
}
else
{
if (state != NULL)
{
LWIP_DEBUGF(FTPD_DEBUG, ("FTPD data connection [%s]:%d is shutdown by the client\n", ipaddr_ntoa(&tpcb->remote_ip), tpcb->remote_port));
ftpd_free_data(state, FTPD_FREEDATA_CLOSE);
// 通知命令处理函数, 数据连接已被客户端关闭
state->cmdstep |= FTPD_CMDSTEP_CONNSHUTDOWN;
ftpd_process_cmd(state);
}
else
LWIP_DEBUGF(FTPD_DEBUG, ("FTPD data connection [%s]:%d is closed by the client\n", ipaddr_ntoa(&tpcb->remote_ip), tpcb->remote_port));
}
return ERR_OK;
}
static err_t ftpd_data_sent(void *arg, struct tcp_pcb *tpcb, u16_t len)
{
err_t err = ERR_OK;
struct ftpd_state *state = arg;
if (state != NULL)
{
if (strcasecmp(state->cmd, "LIST") == 0)
err = ftpd_data_sent_list(arg, tpcb, len);
else if (strcasecmp(state->cmd, "RETR") == 0)
err = ftpd_data_sent_retr(arg, tpcb, len);
}
return err;
}
/* 发送文件列表 */
static err_t ftpd_data_sent_list(void *arg, struct tcp_pcb *tpcb, u16_t len)
{
char buffer[MAX_PATH + 100];
err_t err;
int bufsize, loop, slen;
struct ftpd_state *state = arg;
struct tm tm;
time_t t;
FRESULT fr;
if (state->finfo == NULL)
{
loop = 2;
state->finfo = mem_malloc(sizeof(FILINFO));
if (state->finfo == NULL)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: mem_malloc() failed\n", __FUNCTION__));
goto err;
}
}
else
loop = 1;
while (loop)
{
if (loop == 2)
{
// 读取下一个文件的信息
fr = f_readdir(state->dp, state->finfo);
if (fr != FR_OK || state->finfo->fname[0] == '\0')
{
if (fr != FR_OK)
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: f_readdir() failed. fr=%d\n", __FUNCTION__, fr)); // 读取文件信息失败
// 列表发送完毕
ftpd_free_data(state, FTPD_FREEDATA_SHUTDOWN);
state->cmdstep = 2;
ftpd_process_cmd(state);
break;
}
}
if (strcmp(state->finfo->fname, ".") == 0 || strcmp(state->finfo->fname, "..") == 0)
{
LWIP_DEBUGF(FTPD_DEBUG, ("%s: jumping over \"%s\"\n", __FUNCTION__, state->finfo->fname));
continue;
}
// LIST命令必须输出带时区的时间 (如UTC+8)
t = ftpd_filetime(state->finfo->fdate, state->finfo->ftime, &tm); // fdate+ftime(UTC) -> t(UTC), tm(UTC)
#if FTPD_SYSTEM_TIMEZONE == 0
LWIP_UNUSED_ARG(t);
#elif FTPD_SYSTEM_TIMEZONE == 1
localtime_r(&t, &tm); // t(UTC) -> tm(UTC+8)
#elif FTPD_SYSTEM_TIMEZONE == 2
t += ftpd_get_timezone(); // t(UTC) -> t(UTC+8)
gmtime_r(&t, &tm); // t(UTC+8) -> tm(UTC+8)
#endif
slen = strftime(buffer, sizeof(buffer), "%m-%d-%Y %I:%M%p ", &tm);
if (state->finfo->fattrib & AM_DIR)
strcpy(buffer + slen, "<DIR> ");
else
sprintf(buffer + slen, "%14llu ", (uint64_t)state->finfo->fsize);
slen += 15;
slen += sprintf(buffer + slen, "%s\r\n", state->finfo->fname);
LWIP_ASSERT("slen < sizeof(buffer)", slen < sizeof(buffer));
bufsize = tcp_sndbuf(tpcb);
if (bufsize >= slen)
{
LWIP_DEBUGF(FTPD_DEBUG, ("%s", buffer));
err = tcp_write(tpcb, buffer, slen, TCP_WRITE_FLAG_COPY);
if (err != ERR_OK)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: tcp_write() failed. err=%d\n", __FUNCTION__, err));
goto err;
}
loop = 2;
}
else
{
// TCP滑动窗口不够了, 暂时退出, 等待前面的数据发送完毕
LWIP_DEBUGF(FTPD_DEBUG, ("%s: paused. sndbuf=%d, slen=%d\n", __FUNCTION__, bufsize, slen));
loop = 0;
}
}
return ERR_OK;
err:
err = ftpd_free_data(state, FTPD_FREEDATA_ABORT);
state->cmdstep = FTPD_CMDSTEP_CONNABORTED;
ftpd_process_cmd(state);
return err;
}
/* 发送文件内容 */
static err_t ftpd_data_sent_retr(void *arg, struct tcp_pcb *tpcb, u16_t len)
{
char buffer[30];
err_t err;
struct ftpd_state *state = arg;
unsigned int size;
FRESULT fr;
UINT br;
// 等待上一段数据发送完毕
state->dataout_len -= len;
if (state->dataout_len != 0)
return ERR_OK;
// 释放上一段数据占用的内存
if (state->dataout != NULL)
{
mem_free(state->dataout);
state->dataout = NULL;
}
size = tcp_sndbuf(tpcb);
LWIP_ASSERT("sndbuf != 0", size != 0);
state->dataout = mem_malloc(size);
if (state->dataout == NULL)
{
// 内存分配失败时改用buffer缓冲区
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: mem_malloc() failed\n", __FUNCTION__));
if (size > sizeof(buffer))
size = sizeof(buffer);
}
if (state->dataout != NULL)
fr = f_read(state->fp, state->dataout, size, &br);
else
fr = f_read(state->fp, buffer, size, &br);
if (fr != FR_OK)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: failed to read file. fr=%d\n", __FUNCTION__, fr));
goto err;
}
if (br < size)
size = br;
if (size > 0)
{
state->dataout_len = size;
if (state->dataout != NULL)
err = tcp_write(tpcb, state->dataout, size, 0);
else
err = tcp_write(tpcb, buffer, size, TCP_WRITE_FLAG_COPY);
if (err != ERR_OK)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: tcp_write() failed. err=%d\n", __FUNCTION__, err));
goto err;
}
}
if (f_eof(state->fp))
{
// 文件发送完毕
ftpd_free_data(state, FTPD_FREEDATA_SHUTDOWN);
state->cmdstep = 2;
ftpd_process_cmd(state);
}
return ERR_OK;
err:
err = ftpd_free_data(state, FTPD_FREEDATA_ABORT);
state->cmdstep = FTPD_CMDSTEP_CONNABORTED;
ftpd_process_cmd(state);
return err;
}
/* 控制连接出错 */
static void ftpd_err(void *arg, err_t err)
{
struct ftpd_state *state = arg;
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_WARNING, ("FTPD error. err=%d\n", err));
if (state != NULL)
{
state->ctrlconn = NULL;
ftpd_free(state);
}
}
/* 判断指定文件是否存在 */
// 如果是判断文件夹是否存在, 则字符串末尾不能有斜杠
int ftpd_file_exists(const char *path)
{
FRESULT fr;
if (strcmp(path + 1, ":") == 0 || strcmp(path + 1, ":/") == 0)
return 1;
fr = f_stat(path, NULL);
return fr == FR_OK;
}
/* 将文件时间转换为C标准格式 */
// 在STM32中, time_t是32位的无符号整数 (unsigned int)
// 因为没有符号位, 所以time_t支持超过2038年的年份, 可以放心使用
// 输出参数ptm和返回值time_t都是UTC时间
time_t ftpd_filetime(WORD fdate, WORD ftime, struct tm *ptm)
{
time_t t;
memset(ptm, 0, sizeof(struct tm));
ptm->tm_year = ((fdate >> 9) & 0x7f) + 80;
ptm->tm_mon = ((fdate >> 5) & 0x0f) - 1;
ptm->tm_mday = fdate & 0x1f;
ptm->tm_hour = (ftime >> 11) & 0x1f;
ptm->tm_min = (ftime >> 5) & 0x3f;
ptm->tm_sec = (ftime & 0x1f) << 1;
// 如果输出了错误的日期, 那么Windows的文件管理器会错误显示快捷方式图标
// mktime函数能自动修正ptm结构体中有误的值
// mktime函数是把带时区的struct tm转换为不带时区的time_t,也就是mktime(UTC+8)=UTC
t = mktime(ptm); // UTC-8=mktime(UTC)
t += ftpd_get_timezone_libc(); // (UTC-8)+8=UTC
return t;
}
/* 关闭FTP控制连接和数据连接, 释放state结构体以及里面的成员占用的内存 */
static void ftpd_free(struct ftpd_state *state)
{
if (state == NULL)
return;
ftpd_free_data(state, FTPD_FREEDATA_ABORT); // 如果数据连接尚未关闭, 则强行中止
if (state->ctrlconn != NULL)
{
if (state->flags & FTPD_FLAG_CLOSE)
LWIP_DEBUGF(FTPD_DEBUG, ("FTPD connection [%s]:%d is closed by the server\n", ipaddr_ntoa(&state->ctrlconn->remote_ip), state->ctrlconn->remote_port));
else
LWIP_DEBUGF(FTPD_DEBUG, ("FTPD connection [%s]:%d is shutdown by the server\n", ipaddr_ntoa(&state->ctrlconn->remote_ip), state->ctrlconn->remote_port));
tcp_arg(state->ctrlconn, NULL);
tcp_close(state->ctrlconn);
state->ctrlconn = NULL;
}
ftpd_change_user(state, NULL);
mem_free(state);
}
/* 关闭FTP数据连接并释放相关内存 */
// 关闭连接时通常将option设为FTPD_FREEDATA_SHUTDOWN
// 只有在ftpd_data_recv(p=NULL)中才使用FTPD_FREEDATA_CLOSE
static err_t ftpd_free_data(struct ftpd_state *state, int option)
{
err_t err = ERR_OK;
if (state == NULL)
return err;
state->dataport = -1;
if (state->dataconn != NULL)
{
tcp_arg(state->dataconn, NULL); // 连接关闭后, 回调函数仍有可能触发, 所以必须和state彻底脱离关系
#if FTPD_PASV
if ((state->flags & FTPD_FLAG_PASSIVE) && (state->flags & FTPD_FLAG_NEWDATACONN))
{
LWIP_DEBUGF(FTPD_DEBUG, ("FTPD data pcb is removed\n"));
tcp_close(state->dataconn);
}
else
{
#endif
if (option == FTPD_FREEDATA_ABORT)
{
LWIP_DEBUGF(FTPD_DEBUG, ("FTPD data connection [%s]:%d is aborted\n", ipaddr_ntoa(&state->dataconn->remote_ip), state->dataconn->remote_port));
tcp_abort(state->dataconn);
err = ERR_ABRT;
}
else
{
if (option == FTPD_FREEDATA_CLOSE)
LWIP_DEBUGF(FTPD_DEBUG, ("FTPD data connection [%s]:%d is closed by the server\n", ipaddr_ntoa(&state->dataconn->remote_ip), state->dataconn->remote_port));
else
LWIP_DEBUGF(FTPD_DEBUG, ("FTPD data connection [%s]:%d is shutdown by the server\n", ipaddr_ntoa(&state->dataconn->remote_ip), state->dataconn->remote_port));
tcp_close(state->dataconn);
}
#if FTPD_PASV
}
#endif
state->dataconn = NULL;
}
if (state->dataout != NULL)
{
mem_free(state->dataout);
state->dataout = NULL;
state->dataout_len = 0;
}
// 只有文件夹成功打开了之后, 才可以将指针赋给state->dp
if (state->dp != NULL)
{
f_closedir(state->dp);
mem_free(state->dp);
state->dp = NULL;
}
// 只有文件成功打开了之后, 才可以将指针赋给state->fp
if (state->fp != NULL)
{
f_close(state->fp);
mem_free(state->fp);
state->fp = NULL;
}
if (state->finfo != NULL)
{
mem_free(state->finfo);
state->finfo = NULL;
}
return err;
}
/* 将用户根文件夹路径(rootpath)、当前文件夹路径(state->path)和文件名(filename)连接起来, 放入buffer缓冲区中 */
// buffer的原有内容会被忽略并清空, bufsize为缓冲区的大小
// puserpath为输出参数, 其内容是以用户文件夹为根目录的文件路径
int ftpd_fullpath(const struct ftpd_state *state, char *buffer, int bufsize, const char *filename, char **puserpath)
{
int basepos, ret;
// 在缓冲区中准备好用户文件夹的路径
if (state->userid == -1)
return -1; // 未登录, 连接失败
basepos = strlen(ftpd_users[state->userid].rootpath);
if (basepos + 1 > bufsize)
return -1; // 缓冲区不够
strcpy(buffer, ftpd_users[state->userid].rootpath);
// 获取相对于用户文件夹的文件路径
if (buffer[basepos - 1] == '/')
basepos--; // 使userpath的第一个字符为斜杠
// 如果没有斜杠, 则userpath指向\0, 下面连接路径后可能会变成斜杠
if (puserpath != NULL)
*puserpath = buffer + basepos;
// 连接state->path字符串
if (filename == NULL || filename[0] != '/')
{
// filename不是以用户文件夹为根目录的绝对路径, 而是相对于state->path的相对路径
// 需要将rootpath, state->path和filename这三个字符串连在一起
// filename == NULL的情况可视为空字符串, 是相对路径
LWIP_ASSERT("state->path[0] == '/'", state->path[0] == '/'); // state->path的首字符始终为斜杠
ret = ftpd_concat_path(buffer, bufsize, state->path + 1);
if (ret == -1)
return -1;
}
else
{
// filename是以用户文件夹为根目录的绝对路径
// 跳过斜杠字符, 只将rootpath和不带首斜杠的filename连起来
filename++;
}
// 连接filename字符串
ret = ftpd_concat_path(buffer, bufsize, filename);
if (ret == -1)
return -1;
ret = ftpd_simplify_path(buffer, basepos);
if (puserpath != NULL && **puserpath == '\0')
*puserpath = "/"; // 如果最终结果就是用户根目录, 那么应该用正斜杠表示, 而不是空字符串
return ret;
}
#if FTPD_SYSTEM_TIMEZONE == 2
/* 获取自定义时区 */
time_t ftpd_get_timezone(void)
{
return ftpd_timezone;
}
#endif
/* 获取C库设置的时区 */
// C库mktime函数的参数是带时区的struct tm结构, 返回值为不带时区的time_t时间戳
// 本函数的作用是抵消掉mktime函数带来的时差
// 本函数不考虑夏令时, 不然太复杂了
time_t ftpd_get_timezone_libc(void)
{
struct tm tm;
time_t t = 0;
gmtime_r(&t, &tm);
t = mktime(&tm);
return -t;
}
/* 判断输入的用户名和密码是否正确 */
static int ftpd_is_valid_user(struct ftpd_user *user, int *pid)
{
int i;
int n = LWIP_ARRAYSIZE(ftpd_users);
if (user->name == NULL)
return 0; // 未输入用户名
for (i = 0; i < n; i++)
{
if (strcasecmp(user->name, ftpd_users[i].user.name) == 0)
{
if (pid != NULL)
*pid = i;
if (ftpd_users[i].user.password == NULL)
return 1; // 任何密码都可以
else if (user->password != NULL && strcmp(user->password, ftpd_users[i].user.password) == 0)
return 1; // 密码正确
else
return 0; // 密码错误
}
}
return 0; // 用户名不存在
}
/* 启动ftpd服务器 */
int ftpd_init(void)
{
err_t err;
struct tcp_pcb *temp;
if (ftpd_tpcb != NULL)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_WARNING, ("%s: FTPD server is already started\n", __FUNCTION__));
return -1;
}
temp = tcp_new();
if (temp == NULL)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: tcp_new() failed\n", __FUNCTION__));
return -1;
}
err = tcp_bind(temp, IP_ANY_TYPE, FTPD_PORT);
if (err != ERR_OK)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: tcp_bind() failed. err=%d\n", __FUNCTION__, err));
tcp_close(temp);
return -1;
}
ftpd_tpcb = tcp_listen(temp);
if (ftpd_tpcb == NULL)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: tcp_listen() failed\n", __FUNCTION__));
tcp_close(temp);
return -1;
}
temp = NULL;
tcp_accept(ftpd_tpcb, ftpd_accept);
return 0;
}
/* memchr的反向版本 */
void *ftpd_memrchr(const void *s, int c, size_t n)
{
const char *p = s;
int i;
for (i = n - 1; i >= 0; i--)
{
if (p[i] == c)
return (void *)&p[i];
}
return NULL;
}
/* 准备好数据连接 */
// 若函数返回-1, 则表示连接建立失败, 此时已发送了425消息, 不用再发送其他错误消息
static int ftpd_prepare_data(struct ftpd_state *state)
{
err_t err;
int ret = -1;
if (state->dataport == -1)
{
#if LWIP_IPV6
if (IP_IS_V4_VAL(state->ctrlconn->remote_ip))
#endif
ftpd_send_msg(state, "425 Use PORT or PASV first.\r\n");
#if LWIP_IPV6
else if (IP_IS_V6_VAL(state->ctrlconn->remote_ip))
ftpd_send_msg(state, "425 Use EPRT or EPSV first.\r\n");
#endif
return -1;
}
#if FTPD_PASV
if (state->flags & FTPD_FLAG_PASSIVE)
{
LWIP_ASSERT("state->dataconn != NULL", state->dataconn != NULL);
ret = 0;
}
else
{
#endif
LWIP_ASSERT("state->dataconn == NULL", state->dataconn == NULL);
state->dataconn = tcp_new();
if (state->dataconn == NULL)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: tcp_new() failed\n", __FUNCTION__));
goto end;
}
tcp_arg(state->dataconn, state);
err = tcp_connect(state->dataconn, &state->ctrlconn->remote_ip, state->dataport, ftpd_data_connected);
if (err == ERR_OK)
{
// 使用PORT模式时, 最好将电脑的防火墙关闭, 以免板子连不上电脑而出错
LWIP_DEBUGF(FTPD_DEBUG, ("FTPD is connecting to [%s]:%d...\n", ipaddr_ntoa(&state->ctrlconn->remote_ip), state->dataport));
tcp_err(state->dataconn, ftpd_data_err);
state->flags |= FTPD_FLAG_NEWDATACONN;
ret = 0;
}
else
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: tcp_connect() failed. err=%d\n", __FUNCTION__, err));
#if FTPD_PASV
}
#endif
end:
if (ret == -1)
{
ftpd_send_msg(state, "425 Failed to establish connection.\r\n");
if (state->dataconn != NULL)
{
tcp_arg(state->dataconn, NULL);
tcp_close(state->dataconn);
state->dataconn = NULL;
state->dataport = -1;
}
}
return ret;
}
/* 处理命令 */
static void ftpd_process_cmd(struct ftpd_state *state)
{
int ret;
// 只有当上一个命令的所有回应都发送完毕时, 才开始处理下一条命令
if (state->sent != 0)
return;
else if (state->flags & FTPD_FLAG_TCPERROR)
goto end;
if ((state->flags & FTPD_FLAG_AGAIN) == 0)
{
// 上一条命令已执行完毕
if (state->flags & (FTPD_FLAG_CLOSE | FTPD_FLAG_SHUTDOWN))
{
ftpd_free(state);
return;
}
// 从接收队列中取出一条新命令
ret = ftpd_copy_cmd(state);
if (ret == 0)
return; // 命令不完整
LWIP_DEBUGF(FTPD_DEBUG, ("%s\n", state->cmd));
state->cmdstep = 0; // 当前是命令的第一步操作
if (ret == 2)
{
ftpd_send_msg(state, "500 Syntax error, command unrecognized.\r\n");
goto end;
}
// 提取出命令参数
state->cmdarg = strchr(state->cmd, ' ');
if (state->cmdarg != NULL)
*state->cmdarg++ = '\0';
else
state->cmdarg = "";
}
else
{
// 上一条命令未执行完毕, 虽然state->sent==0, 但还需要继续发送更多数据
// cmd和cmdarg不变, 命令可根据cmdstep的值决定当前是第几步操作
state->flags &= ~FTPD_FLAG_AGAIN;
}
// 处理各种命令
if (ftpd_process_user_cmd(state)) // 这个必须第一个处理
;
else if (ftpd_process_data_cmd(state))
;
else if (ftpd_process_directory_cmd(state))
;
else if (ftpd_process_file_cmd(state))
;
else if (ftpd_process_opt_cmd(state))
;
else
ftpd_send_msg(state, "500 Unknown command.\r\n");
end:
// TCP无法发送数据时, 强制关闭连接
if (state->sent == 0 && (state->flags & FTPD_FLAG_TCPERROR))
{
state->flags = (state->flags & ~FTPD_FLAG_AGAIN) | FTPD_FLAG_SHUTDOWN;
ftpd_free(state);
}
}
/* 处理与数据连接有关的命令 */
static int ftpd_process_data_cmd(struct ftpd_state *state)
{
char ip[IPADDR_STRLEN_MAX];
int i, j, ret;
int isport = 0;
ip_addr_t ipaddr;
#if FTPD_PASV
char buffer[100];
err_t err;
int ispasv = 0;
struct tcp_pcb *newpcb;
#endif
#if LWIP_IPV6
if (IP_IS_V4_VAL(state->ctrlconn->remote_ip))
{
#endif
if (strcasecmp(state->cmd, "PORT") == 0)
isport = 4;
#if FTPD_PASV
else if (strcasecmp(state->cmd, "PASV") == 0)
ispasv = 4;
#endif
#if LWIP_IPV6
}
else if (IP_IS_V6_VAL(state->ctrlconn->remote_ip))
{
if (strcasecmp(state->cmd, "EPRT") == 0)
isport = 6;
#if FTPD_PASV
else if (strcasecmp(state->cmd, "EPSV") == 0)
ispasv = 6;
#endif
}
#endif
if (isport)
{
// 如果之前启动了PASV模式, 则关闭创建的监听连接
state->dataport = -1;
#if FTPD_PASV
if (state->flags & FTPD_FLAG_PASSIVE)
{
state->flags &= ~FTPD_FLAG_PASSIVE;
if (state->dataconn != NULL)
{
tcp_close(state->dataconn);
state->dataconn = NULL;
}
}
#endif
// 提取出IP地址
#if LWIP_IPV6
if (isport == 4)
{
#endif
for (i = j = 0; i < sizeof(ip) && j < 4; i++)
{
if (isdigit(state->cmdarg[i]))
ip[i] = state->cmdarg[i];
else if (state->cmdarg[i] == ',')
{
ip[i] = '.';
j++;
}
else
break;
}
if (j != 4)
goto porterr;
ip[i - 1] = '\0';
#if LWIP_IPV6
}
else
{
if (memcmp(state->cmdarg, "|2|", 3) != 0)
goto porterr;
for (i = 0; i < sizeof(ip); i++)
{
if (state->cmdarg[3 + i] == '|')
break;
ip[i] = state->cmdarg[3 + i];
}
if (i == sizeof(ip))
goto porterr;
ip[i] = '\0';
}
#endif
ret = ipaddr_aton(ip, &ipaddr);
if (ret == 0 || !ip_addr_cmp(&ipaddr, &state->ctrlconn->remote_ip))
goto porterr;
// 提取出端口号
#if LWIP_IPV6
if (isport == 4)
{
#endif
ret = sscanf(state->cmdarg + i, "%d,%d", &i, &j);
if (ret != 2)
goto porterr;
ret = i * 256 + j;
#if LWIP_IPV6
}
else
{
i = sscanf(state->cmdarg + 4 + i, "%d", &ret);
if (i != 1)
goto porterr;
}
#endif
if (ret != 0 && ret < 65536)
{
state->dataport = ret;
#if LWIP_IPV6
if (isport == 4)
{
#endif
#if FTPD_PASV
ftpd_send_msg(state, "200 PORT command successful. Consider using PASV.\r\n");
#else
ftpd_send_msg(state, "200 PORT command successful.\r\n");
#endif
#if LWIP_IPV6
}
else
ftpd_send_msg(state, "200 EPRT command successful.\r\n");
#endif
return 1;
}
porterr:
#if LWIP_IPV6
if (isport == 4)
#endif
ftpd_send_msg(state, "500 Illegal PORT command.\r\n");
#if LWIP_IPV6
else
ftpd_send_msg(state, "500 Illegal EPRT command.\r\n");
#endif
}
#if FTPD_PASV
else if (ispasv)
{
if (state->dataconn == NULL)
{
state->dataconn = tcp_new();
if (state->dataconn == NULL)
goto pasverr;
#if LWIP_IPV6
if (ispasv == 4)
#endif
err = tcp_bind(state->dataconn, IP_ADDR_ANY, 0);
#if LWIP_IPV6
else
err = tcp_bind(state->dataconn, IP6_ADDR_ANY, 0);
#endif
if (err != ERR_OK)
goto pasverr;
newpcb = tcp_listen(state->dataconn);
if (newpcb == NULL)
goto pasverr;
state->dataconn = newpcb;
tcp_arg(state->dataconn, state);
tcp_accept(state->dataconn, ftpd_data_accept);
state->dataport = state->dataconn->local_port;
state->flags |= FTPD_FLAG_NEWDATACONN | FTPD_FLAG_PASSIVE;
}
#if LWIP_IPV6
if (ispasv == 4)
{
#endif
ipaddr_ntoa_r(&state->ctrlconn->local_ip, ip, sizeof(ip));
for (i = 0; ip[i] != '\0'; i++)
{
if (ip[i] == '.')
ip[i] = ',';
}
sprintf(buffer, "227 Entering Passive Mode (%s,%d,%d).\r\n", ip, (state->dataport >> 8) & 0xff, state->dataport & 0xff);
#if LWIP_IPV6
}
else
sprintf(buffer, "229 Entering Extended Passive Mode (|||%d|).\r\n", state->dataport);
#endif
ftpd_send_msg(state, buffer);
return 1;
pasverr:
#if LWIP_IPV6
if (ispasv == 4)
#endif
ftpd_send_msg(state, "500 PASV command failed.\r\n");
#if LWIP_IPV6
else
ftpd_send_msg(state, "500 EPSV command failed.\r\n");
#endif
if (state->dataconn != NULL)
{
tcp_close(state->dataconn);
state->dataconn = NULL;
}
}
#endif
else
return 0;
return 1;
}
static int ftpd_process_directory_cmd(struct ftpd_state *state)
{
char buffer[MAX_PATH];
char *path;
int ret;
DIR *dp = NULL;
FRESULT fr;
if (strcasecmp(state->cmd, "PWD") == 0)
{
ftpd_send_msg(state, "257 \"");
ftpd_send_msg(state, state->path);
ftpd_send_msg(state, "\" is the current directory.\r\n");
}
else if (strcasecmp(state->cmd, "CWD") == 0 || strcasecmp(state->cmd, "CDUP") == 0)
{
if (strcasecmp(state->cmd, "CWD") == 0)
ret = ftpd_fullpath(state, buffer, MAX_PATH, state->cmdarg, &path);
else
ret = ftpd_fullpath(state, buffer, MAX_PATH, "..", &path);
if (ret == -1)
goto cwderr;
else if (!ftpd_file_exists(buffer))
goto cwderr;
strcpy(state->path, path);
ftpd_send_msg(state, "250 Directory successfully changed.\r\n");
return 1;
cwderr:
ftpd_send_msg(state, "550 Failed to change directory.\r\n");
}
else if (strcasecmp(state->cmd, "LIST") == 0)
{
if (state->cmdstep == 0)
{
ret = ftpd_fullpath(state, buffer, MAX_PATH, NULL, NULL);
if (ret == -1)
goto listerr;
LWIP_ASSERT("state->dp == NULL", state->dp == NULL);
dp = mem_malloc(sizeof(DIR));
if (dp == NULL)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: mem_malloc(sizeof(DIR)) failed\n", __FUNCTION__));
goto listerr;
}
fr = f_opendir(dp, buffer);
if (fr != FR_OK)
goto listerr;
state->dp = dp; // 文件夹打开了之后才能赋给state->dp
ret = ftpd_prepare_data(state);
if (ret == -1)
goto listerr2;
state->cmdstep = 1;
state->flags |= FTPD_FLAG_AGAIN;
ftpd_send_msg(state, "150 Here comes the directory listing.\r\n");
return 1;
listerr:
state->cmdstep = FTPD_CMDSTEP_CONNABORTED;
listerr2:
// 这里涉及到两个不同的操作
// 一个是关闭文件夹, 另一个是释放存储文件夹信息的内存
if (state->dp != NULL)
{
f_closedir(state->dp);
state->dp = NULL;
}
if (dp != NULL)
{
mem_free(dp);
dp = NULL;
}
}
else if (state->cmdstep == 1)
{
state->flags |= FTPD_FLAG_AGAIN;
ftpd_data_check(state);
}
else if (state->cmdstep == 2)
ftpd_send_msg(state, "226 Directory send OK.\r\n");
if (state->cmdstep & (FTPD_CMDSTEP_CONNABORTED | FTPD_CMDSTEP_CONNSHUTDOWN))
ftpd_send_msg(state, "450 Failed to list the folder.\r\n");
}
else if (strcasecmp(state->cmd, "MKD") == 0)
{
ret = ftpd_fullpath(state, buffer, MAX_PATH, state->cmdarg, &path);
if (ret != -1)
{
fr = f_mkdir(buffer);
if (fr == FR_OK)
{
ftpd_send_msg(state, "257 \"");
ftpd_send_msg(state, path);
ftpd_send_msg(state, "\" created.\r\n");
return 1;
}
}
ftpd_send_msg(state, "550 Create directory operation failed.\r\n");
}
else if (strcasecmp(state->cmd, "RMD") == 0)
{
ret = ftpd_fullpath(state, buffer, MAX_PATH, state->cmdarg, NULL);
if (ret != -1)
{
fr = f_unlink(buffer);
if (fr == FR_OK)
{
ftpd_send_msg(state, "250 Remove directory operation successful.\r\n");
return 1;
}
}
ftpd_send_msg(state, "550 Remove directory operation failed.\r\n");
}
else
return 0;
return 1;
}
/* 处理与文件有关的命令 */
static int ftpd_process_file_cmd(struct ftpd_state *state)
{
char buffer[MAX_PATH];
int ret;
struct tm tm;
uint64_t size;
FIL *fp = NULL;
FILINFO* finfo = NULL;
FRESULT fr;
if (strcasecmp(state->cmd, "SIZE") == 0)
{
ret = ftpd_fullpath(state, buffer, MAX_PATH, state->cmdarg, NULL);
if (ret == -1)
goto sizeerr;
fp = mem_malloc(sizeof(FIL));
if (fp == NULL)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: mem_malloc(sizeof(FIL)) failed\n", __FUNCTION__));
goto sizeerr;
}
fr = f_open(fp, buffer, FA_READ);
if (fr != FR_OK)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: f_open() failed. fr=%d\n", __FUNCTION__, fr));
goto sizeerr;
}
size = f_size(fp);
f_close(fp);
mem_free(fp);
sprintf(buffer, "213 %llu\r\n", size);
ftpd_send_msg(state, buffer);
return 1;
sizeerr:
ftpd_send_msg(state, "550 Could not get file size.\r\n");
if (fp != NULL)
mem_free(fp);
}
else if (strcasecmp(state->cmd, "RETR") == 0)
{
if (state->cmdstep == 0)
{
ret = ftpd_fullpath(state, buffer, MAX_PATH, state->cmdarg, NULL);
if (ret == -1)
goto retrerr;
LWIP_ASSERT("state->fp == NULL", state->fp == NULL);
fp = mem_malloc(sizeof(FIL));
if (fp == NULL)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: mem_malloc(sizeof(FIL)) failed\n", __FUNCTION__));
goto retrerr;
}
fr = f_open(fp, buffer, FA_READ);
if (fr != FR_OK)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: f_open() failed. fr=%d\n", __FUNCTION__, fr));
goto retrerr;
}
state->fp = fp; // 文件打开了之后才能赋给state->fp
ret = ftpd_prepare_data(state);
if (ret == -1)
goto retrerr2;
state->cmdstep = 1;
state->flags |= FTPD_FLAG_AGAIN;
sprintf(buffer, "150 Opening %s mode data connection for ", (state->type == 'I') ? "BINARY" : "ASCII");
ftpd_send_msg(state, buffer);
ftpd_send_msg(state, state->cmdarg);
size = f_size(state->fp);
sprintf(buffer, " (%llu bytes).\r\n", size);
ftpd_send_msg(state, buffer);
if (state->rest != 0)
{
f_lseek(state->fp, state->rest);
state->rest = 0;
}
return 1;
retrerr:
ftpd_send_msg(state, "550 Failed to open file.\r\n");
retrerr2:
if (state->fp != NULL)
{
f_close(state->fp);
state->fp = NULL;
}
if (fp != NULL)
{
mem_free(fp);
fp = NULL;
}
state->rest = 0;
}
else if (state->cmdstep == 1)
{
state->flags |= FTPD_FLAG_AGAIN;
ftpd_data_check(state);
}
else if (state->cmdstep == 2)
ftpd_send_msg(state, "226 Transfer complete.\r\n");
else if (state->cmdstep & (FTPD_CMDSTEP_CONNSHUTDOWN | FTPD_CMDSTEP_CONNABORTED))
ftpd_send_msg(state, "451 Requested action aborted: local error in processing.\r\n");
}
else if (strcasecmp(state->cmd, "STOR") == 0 || strcasecmp(state->cmd, "APPE") == 0)
{
if (state->cmdstep == 0)
{
ret = ftpd_fullpath(state, buffer, MAX_PATH, state->cmdarg, NULL);
if (ret == -1)
goto storerr;
LWIP_ASSERT("state->fp == NULL", state->fp == NULL);
fp = mem_malloc(sizeof(FIL));
if (fp == NULL)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: mem_malloc(sizeof(FIL)) failed\n", __FUNCTION__));
goto storerr;
}
if (strcasecmp(state->cmd, "STOR") == 0)
fr = f_open(fp, buffer, FA_CREATE_ALWAYS | FA_WRITE);
else
fr = f_open(fp, buffer, FA_OPEN_ALWAYS | FA_WRITE);
if (fr != FR_OK)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: f_open() failed. fr=%d, path=\"%s\"\n", __FUNCTION__, fr, buffer));
goto storerr;
}
state->fp = fp; // 文件打开了之后才能赋给state->fp
ret = ftpd_prepare_data(state);
if (ret == -1)
goto storerr2;
state->cmdstep = 1;
state->flags |= FTPD_FLAG_AGAIN;
ftpd_send_msg(state, "150 Ok to send data.\r\n");
if (strcasecmp(state->cmd, "APPE") == 0 && state->rest != 0)
{
f_lseek(state->fp, state->rest);
state->rest = 0;
}
return 1;
storerr:
ftpd_send_msg(state, "550 Failed to open file.\r\n");
storerr2:
if (state->fp != NULL)
{
f_close(state->fp);
state->fp = NULL;
}
if (fp != NULL)
{
mem_free(fp);
fp = NULL;
}
state->rest = 0;
}
else if (state->cmdstep == 1)
{
state->flags |= FTPD_FLAG_AGAIN;
ftpd_data_check(state);
}
else if (state->cmdstep & FTPD_CMDSTEP_CONNSHUTDOWN)
ftpd_send_msg(state, "226 Transfer complete.\r\n");
else if (state->cmdstep & FTPD_CMDSTEP_CONNABORTED)
ftpd_send_msg(state, "451 Requested action aborted: local error in processing.\r\n");
}
else if (strcasecmp(state->cmd, "DELE") == 0)
{
ret = ftpd_fullpath(state, buffer, MAX_PATH, state->cmdarg, NULL);
if (ret != -1)
{
ret = f_unlink(buffer);
if (ret == 0)
{
ftpd_send_msg(state, "250 Delete operation successful.\r\n");
return 1;
}
}
ftpd_send_msg(state, "550 Delete operation failed.\r\n");
}
else if (strcasecmp(state->cmd, "RNFR") == 0)
{
ret = ftpd_fullpath(state, state->rename, sizeof(state->rename), state->cmdarg, NULL);
if (ret != -1)
{
state->flags |= FTPD_FLAG_RENAME;
ftpd_send_msg(state, "350 Ready for RNTO.\r\n");
}
else
{
state->flags &= ~FTPD_FLAG_RENAME;
ftpd_send_msg(state, "550 RNFR command failed.\r\n");
}
}
else if (strcasecmp(state->cmd, "RNTO") == 0)
{
if ((state->flags & FTPD_FLAG_RENAME) == 0)
{
ftpd_send_msg(state, "503 RNFR required first.\r\n");
return 1;
}
state->flags &= ~FTPD_FLAG_RENAME;
ret = ftpd_fullpath(state, buffer, MAX_PATH, state->cmdarg, NULL);
if (ret != -1)
{
ret = f_rename(state->rename, buffer);
if (ret == 0)
{
ftpd_send_msg(state, "250 Rename successful.\r\n");
return 1;
}
}
ftpd_send_msg(state, "550 Rename failed.\r\n");
}
else if (strcasecmp(state->cmd, "REST") == 0)
{
state->rest = strtoull(state->cmdarg, NULL, 10);
sprintf(buffer, "350 Restart position accepted (%llu).\r\n", state->rest);
ftpd_send_msg(state, buffer);
}
else if (strcasecmp(state->cmd, "MDTM") == 0)
{
ret = ftpd_fullpath(state, buffer, MAX_PATH, state->cmdarg, NULL);
if (ret == -1)
goto mdtmerr;
finfo = mem_malloc(sizeof(FILINFO));
if (finfo == NULL)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: mem_malloc(sizeof(FILINFO)) failed\n", __FUNCTION__));
goto mdtmerr;
}
fr = f_stat(buffer, finfo);
if (fr != FR_OK)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: f_stat() failed. fr=%d\n", __FUNCTION__, fr));
goto mdtmerr;
}
ftpd_filetime(finfo->fdate, finfo->ftime, &tm);
mem_free(finfo);
strftime(buffer, sizeof(buffer), "213 %Y%m%d%H%M%S\r\n", &tm); // MDTM命令必须输出UTC时间
ftpd_send_msg(state, buffer);
return 1;
mdtmerr:
ftpd_send_msg(state, "550 Could not get modification time.\r\n");
if (finfo != NULL)
mem_free(finfo);
}
else
return 0;
return 1;
}
/* 处理与服务器选项有关的命令 */
static int ftpd_process_opt_cmd(struct ftpd_state *state)
{
if (strcasecmp(state->cmd, "opts") == 0)
{
if (strcasecmp(state->cmdarg, "utf8 on") == 0)
ftpd_send_msg(state, "200 Always in UTF8 mode.\r\n");
else
ftpd_send_msg(state, "501 Option not understood.\r\n");
}
else if (strcasecmp(state->cmd, "TYPE") == 0)
{
if (strcasecmp(state->cmdarg, "A") == 0)
ftpd_send_msg(state, "200 Switching to ASCII mode.\r\n");
else if (strcasecmp(state->cmdarg, "I") == 0)
ftpd_send_msg(state, "200 Switching to Binary mode.\r\n");
else
{
ftpd_send_msg(state, "500 Unrecognised TYPE command.\r\n");
return 1;
}
state->type = state->cmdarg[0];
}
else if (strcasecmp(state->cmd, "noop") == 0)
ftpd_send_msg(state, "200 NOOP ok.\r\n");
else if (strcasecmp(state->cmd, "FEAT") == 0)
{
ftpd_send_msg(state, "211-Features:\r\n");
ftpd_send_msg(state, " EPRT\r\n");
ftpd_send_msg(state, " EPSV\r\n");
ftpd_send_msg(state, " MDTM\r\n");
ftpd_send_msg(state, " PASV\r\n");
ftpd_send_msg(state, " REST STREAM\r\n");
ftpd_send_msg(state, " SIZE\r\n");
ftpd_send_msg(state, " UTF8\r\n");
ftpd_send_msg(state, "211 End\r\n");
}
else if (strcasecmp(state->cmd, "SYST") == 0)
ftpd_send_msg(state, "215 Windows_NT\r\n");
else
return 0;
return 1;
}
/* 处理与用户有关的命令 */
static int ftpd_process_user_cmd(struct ftpd_state *state)
{
int userid;
if (strcasecmp(state->cmd, "USER") == 0)
{
if (state->userid != -1)
ftpd_send_msg(state, "530 Can't change to another user.\r\n");
else
{
ftpd_change_user(state, state->cmdarg);
if (strcasecmp(state->cmdarg, "ANONYMOUS") == 0 && ftpd_is_valid_user(&state->user, NULL))
ftpd_send_msg(state, "331 Anonymous access allowed, send identity (e-mail name) as password.\r\n");
else
ftpd_send_msg(state, "331 Please specify the password.\r\n");
}
}
else if (strcasecmp(state->cmd, "PASS") == 0)
{
if (state->userid != -1)
ftpd_send_msg(state, "230 Already logged in.\r\n");
else if (state->user.name == NULL)
ftpd_send_msg(state, "503 Login with USER first.\r\n");
else
{
state->user.password = ftpd_strdup(state->cmdarg);
if (ftpd_is_valid_user(&state->user, &userid))
{
if (ftpd_file_exists(ftpd_users[userid].rootpath))
{
state->userid = userid;
ftpd_send_msg(state, "230 Login successful.\r\n");
}
else
{
ftpd_change_user(state, NULL);
ftpd_send_msg(state, "530 Please create the home directory \"");
ftpd_send_msg(state, ftpd_users[userid].rootpath);
ftpd_send_msg(state, "\" before logging in.\r\n");
}
}
else
{
ftpd_change_user(state, NULL);
ftpd_send_msg(state, "530 Login incorrect.\r\n");
}
}
}
else if (strcasecmp(state->cmd, "QUIT") == 0)
{
ftpd_send_msg(state, "221 Goodbye.\r\n");
state->flags |= FTPD_FLAG_SHUTDOWN;
}
else if (state->userid == -1)
ftpd_send_msg(state, "530 Please login with USER and PASS.\r\n");
else
return 0;
return 1;
}
static err_t ftpd_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
{
struct ftpd_state *state = arg;
if (p != NULL)
{
LWIP_DEBUGF(FTPD_DEBUG, ("%s: received %d bytes\n", __FUNCTION__, p->tot_len));
if (state->queue == NULL)
state->queue = p;
else
pbuf_cat(state->queue, p);
}
else
{
if (state != NULL)
{
LWIP_DEBUGF(FTPD_DEBUG, ("FTPD connection [%s]:%d is shutdown by the client\n", ipaddr_ntoa(&tpcb->remote_ip), tpcb->remote_port));
state->flags |= FTPD_FLAG_CLOSE;
}
else
{
LWIP_DEBUGF(FTPD_DEBUG, ("FTPD connection [%s]:%d is closed by the client\n", ipaddr_ntoa(&tpcb->remote_ip), tpcb->remote_port));
return ERR_OK;
}
}
ftpd_process_cmd(state);
return ERR_OK;
}
/* 发送回应 */
static int ftpd_send_msg(struct ftpd_state *state, const char *s)
{
err_t err;
int len;
if (state->flags & FTPD_FLAG_TCPERROR)
return -1;
len = strlen(s);
LWIP_DEBUGF(FTPD_DEBUG, ("%s", s));
LWIP_ASSERT("sndbuf >= len", tcp_sndbuf(state->ctrlconn) >= len);
err = tcp_write(state->ctrlconn, s, len, TCP_WRITE_FLAG_COPY);
if (err != ERR_OK)
{
state->flags |= FTPD_FLAG_TCPERROR;
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: tcp_write() failed. err=%d\n", __FUNCTION__, err));
return -1;
}
state->sent += len;
return len;
}
static err_t ftpd_sent(void *arg, struct tcp_pcb *tpcb, u16_t len)
{
struct ftpd_state *state = arg;
LWIP_DEBUGF(FTPD_DEBUG, ("%s: %d bytes of response sent\n", __FUNCTION__, len));
if (state != NULL)
{
state->sent -= len;
if (state->sent == 0)
{
if (state->cmdlen != 0)
{
LWIP_DEBUGF(FTPD_DEBUG, ("%s: processed %d bytes\n", __FUNCTION__, state->cmdlen));
tcp_recved(state->ctrlconn, state->cmdlen);
state->cmdlen = 0;
}
ftpd_process_cmd(state);
}
}
return ERR_OK;
}
#if FTPD_SYSTEM_TIMEZONE == 2
/* 设置自定义时区 */
void ftpd_set_timezone(time_t timezone)
{
ftpd_timezone = timezone;
}
#endif
/* 去除路径中的"./"和"../"以及"//" */
// path必须为绝对路径 (可以带盘符也可以不带盘符), 不允许为相对路径
// basepos是字符串中用户根目录末尾的斜杠的位置, 用于保证"../"在后退时不会退到用户根目录以外
// 比如"C:/foo/bar", 如果用户根目录是C:/foo, 那么basepos应该为6
int ftpd_simplify_path(char *path, int basepos)
{
char *base, *p, *pp, *q;
int len;
// 检查path是否为绝对路径
if (*path != '/')
{
p = strchr(path, '/');
if (p == NULL || *(p - 1) != ':')
return -1; // path不允许为相对路径
}
// 检查并修正basepos参数
len = strlen(path);
if (basepos < 0)
basepos = 0;
else if (basepos > len)
basepos = len;
base = path + basepos;
if (*base != '/' && *base != '\0')
{
base = strchr(base, '/');
if (base == NULL)
base = path + len;
basepos = base - path;
}
p = base; // 当前目录
pp = base; // 父目录
do
{
q = strchr(p + 1, '/');
if (q != NULL)
{
len = q - p;
if (len == 1 || (len == 2 && memcmp(p, "/.", 2) == 0))
memmove(p, q, strlen(q) + 1);
else if (len == 3 && memcmp(p, "/..", 3) == 0)
{
memmove(pp, q, strlen(q) + 1);
p = pp;
pp = ftpd_memrchr(base, '/', pp - base);
if (pp == NULL)
pp = p;
}
else
{
pp = p;
p = q;
}
}
else
{
len = strlen(p);
if (len == 1 || (len == 2 && memcmp(p, "/.", 2) == 0))
{
if (p == path || *(p - 1) == ':')
p++;
*p = '\0';
}
else if (len == 3 && memcmp(p, "/..", 3) == 0)
{
if (pp == path || *(pp - 1) == ':')
pp++;
*pp = '\0';
}
}
} while (q != NULL);
return 0;
}
/* 开辟一块内存空间, 用于长期保存局部变量中的字符串, 避免函数退出时局部变量失效 */
char *ftpd_strdup(const char *s)
{
char *p;
int len;
len = strlen(s) + 1;
p = mem_malloc(len);
if (p != NULL)
memcpy(p, s, len);
return p;
}
lwipopts.h:
#ifndef LWIP_LWIPOPTS_H
#define LWIP_LWIPOPTS_H
#define NO_SYS 1 // 无操作系统
#define SYS_LIGHTWEIGHT_PROT 0 // 不进行临界区保护
#define LWIP_NETCONN 0
#define LWIP_SOCKET 0
#define MEM_ALIGNMENT 4 // STM32单片机是32位的单片机, 因此是4字节对齐的
#define MEM_SIZE 1024000 // lwip的mem_malloc函数使用的堆内存的大小
// 广播包过滤器
// 如果打开了这个过滤器, 那么就需要在套接字上设置SOF_BROADCAST选项才能收发广播数据包
#define IP_SOF_BROADCAST 1
#define IP_SOF_BROADCAST_RECV 1
// 配置TCP
#define TCP_MSS 1500
#define LWIP_TCP_SACK_OUT 1 // 允许选择性确认
#define TCP_WND 60000
#define TCP_SND_BUF 60000
#define MEMP_NUM_PBUF 1000
#define MEMP_NUM_TCP_PCB 100
#define MEMP_NUM_TCP_PCB_LISTEN 100
#define MEMP_NUM_TCP_SEG 1000
#define PBUF_POOL_SIZE 1000
// 配置DHCP
#define LWIP_DHCP 1
#define LWIP_NETIF_HOSTNAME 1
// 配置DNS
#define LWIP_DNS 1
// 配置IPv6
#define LWIP_IPV6 1
#define LWIP_ND6_RDNSS_MAX_DNS_SERVERS LWIP_DNS // 允许SLAAC获取DNS服务器的地址
// 配置调试信息
#define LWIP_DEBUG
#define FTPD_DEBUG LWIP_DBG_ON
#endif
ffconf.h:
/*---------------------------------------------------------------------------/
/ FatFs Functional Configurations
/---------------------------------------------------------------------------*/
#define FFCONF_DEF 86604 /* Revision ID */
/*---------------------------------------------------------------------------/
/ Function Configurations
/---------------------------------------------------------------------------*/
#define FF_FS_READONLY 0
/* This option switches read-only configuration. (0:Read/Write or 1:Read-only)
/ Read-only configuration removes writing API functions, f_write(), f_sync(),
/ f_unlink(), f_mkdir(), f_chmod(), f_rename(), f_truncate(), f_getfree()
/ and optional writing functions as well. */
#define FF_FS_MINIMIZE 0
/* This option defines minimization level to remove some basic API functions.
/
/ 0: Basic functions are fully enabled.
/ 1: f_stat(), f_getfree(), f_unlink(), f_mkdir(), f_truncate() and f_rename()
/ are removed.
/ 2: f_opendir(), f_readdir() and f_closedir() are removed in addition to 1.
/ 3: f_lseek() function is removed in addition to 2. */
#define FF_USE_STRFUNC 0
/* This option switches string functions, f_gets(), f_putc(), f_puts() and f_printf().
/
/ 0: Disable string functions.
/ 1: Enable without LF-CRLF conversion.
/ 2: Enable with LF-CRLF conversion. */
#define FF_USE_FIND 0
/* This option switches filtered directory read functions, f_findfirst() and
/ f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */
#define FF_USE_MKFS 1 // 允许格式化磁盘
/* This option switches f_mkfs() function. (0:Disable or 1:Enable) */
#define FF_USE_FASTSEEK 0
/* This option switches fast seek function. (0:Disable or 1:Enable) */
#define FF_USE_EXPAND 0
/* This option switches f_expand function. (0:Disable or 1:Enable) */
#define FF_USE_CHMOD 0
/* This option switches attribute manipulation functions, f_chmod() and f_utime().
/ (0:Disable or 1:Enable) Also FF_FS_READONLY needs to be 0 to enable this option. */
#define FF_USE_LABEL 0
/* This option switches volume label functions, f_getlabel() and f_setlabel().
/ (0:Disable or 1:Enable) */
#define FF_USE_FORWARD 0
/* This option switches f_forward() function. (0:Disable or 1:Enable) */
/*---------------------------------------------------------------------------/
/ Locale and Namespace Configurations
/---------------------------------------------------------------------------*/
#define FF_CODE_PAGE 437 // 设置语言
/* This option specifies the OEM code page to be used on the target system.
/ Incorrect code page setting can cause a file open failure.
/
/ 437 - U.S.
/ 720 - Arabic
/ 737 - Greek
/ 771 - KBL
/ 775 - Baltic
/ 850 - Latin 1
/ 852 - Latin 2
/ 855 - Cyrillic
/ 857 - Turkish
/ 860 - Portuguese
/ 861 - Icelandic
/ 862 - Hebrew
/ 863 - Canadian French
/ 864 - Arabic
/ 865 - Nordic
/ 866 - Russian
/ 869 - Greek 2
/ 932 - Japanese (DBCS)
/ 936 - Simplified Chinese (DBCS)
/ 949 - Korean (DBCS)
/ 950 - Traditional Chinese (DBCS)
/ 0 - Include all code pages above and configured by f_setcp()
*/
#define FF_USE_LFN 2 // 允许使用长文件名
#define FF_MAX_LFN 255
/* The FF_USE_LFN switches the support for LFN (long file name).
/
/ 0: Disable LFN. FF_MAX_LFN has no effect.
/ 1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe.
/ 2: Enable LFN with dynamic working buffer on the STACK.
/ 3: Enable LFN with dynamic working buffer on the HEAP.
/
/ To enable the LFN, ffunicode.c needs to be added to the project. The LFN function
/ requiers certain internal working buffer occupies (FF_MAX_LFN + 1) * 2 bytes and
/ additional (FF_MAX_LFN + 44) / 15 * 32 bytes when exFAT is enabled.
/ The FF_MAX_LFN defines size of the working buffer in UTF-16 code unit and it can
/ be in range of 12 to 255. It is recommended to be set 255 to fully support LFN
/ specification.
/ When use stack for the working buffer, take care on stack overflow. When use heap
/ memory for the working buffer, memory management functions, ff_memalloc() and
/ ff_memfree() in ffsystem.c, need to be added to the project. */
#define FF_LFN_UNICODE 2 // 使用UTF-8编码
/* This option switches the character encoding on the API when LFN is enabled.
/
/ 0: ANSI/OEM in current CP (TCHAR = char)
/ 1: Unicode in UTF-16 (TCHAR = WCHAR)
/ 2: Unicode in UTF-8 (TCHAR = char)
/ 3: Unicode in UTF-32 (TCHAR = DWORD)
/
/ Also behavior of string I/O functions will be affected by this option.
/ When LFN is not enabled, this option has no effect. */
#define FF_LFN_BUF 255
#define FF_SFN_BUF 12
/* This set of options defines size of file name members in the FILINFO structure
/ which is used to read out directory items. These values should be suffcient for
/ the file names to read. The maximum possible length of the read file name depends
/ on character encoding. When LFN is not enabled, these options have no effect. */
#define FF_STRF_ENCODE 3
/* When FF_LFN_UNICODE >= 1 with LFN enabled, string I/O functions, f_gets(),
/ f_putc(), f_puts and f_printf() convert the character encoding in it.
/ This option selects assumption of character encoding ON THE FILE to be
/ read/written via those functions.
/
/ 0: ANSI/OEM in current CP
/ 1: Unicode in UTF-16LE
/ 2: Unicode in UTF-16BE
/ 3: Unicode in UTF-8
*/
#define FF_FS_RPATH 0
/* This option configures support for relative path.
/
/ 0: Disable relative path and remove related functions.
/ 1: Enable relative path. f_chdir() and f_chdrive() are available.
/ 2: f_getcwd() function is available in addition to 1.
*/
/*---------------------------------------------------------------------------/
/ Drive/Volume Configurations
/---------------------------------------------------------------------------*/
#define FF_VOLUMES 1
/* Number of volumes (logical drives) to be used. (1-10) */
#define FF_STR_VOLUME_ID 1 // 允许使用盘符
#define FF_VOLUME_STRS "C"
/* FF_STR_VOLUME_ID switches support for volume ID in arbitrary strings.
/ When FF_STR_VOLUME_ID is set to 1 or 2, arbitrary strings can be used as drive
/ number in the path name. FF_VOLUME_STRS defines the volume ID strings for each
/ logical drives. Number of items must not be less than FF_VOLUMES. Valid
/ characters for the volume ID strings are A-Z, a-z and 0-9, however, they are
/ compared in case-insensitive. If FF_STR_VOLUME_ID >= 1 and FF_VOLUME_STRS is
/ not defined, a user defined volume string table needs to be defined as:
/
/ const char* VolumeStr[FF_VOLUMES] = {"ram","flash","sd","usb",...
*/
#define FF_MULTI_PARTITION 0
/* This option switches support for multiple volumes on the physical drive.
/ By default (0), each logical drive number is bound to the same physical drive
/ number and only an FAT volume found on the physical drive will be mounted.
/ When this function is enabled (1), each logical drive number can be bound to
/ arbitrary physical drive and partition listed in the VolToPart[]. Also f_fdisk()
/ funciton will be available. */
#define FF_MIN_SS 4096 // 扇区大小
#define FF_MAX_SS 4096
/* This set of options configures the range of sector size to be supported. (512,
/ 1024, 2048 or 4096) Always set both 512 for most systems, generic memory card and
/ harddisk. But a larger value may be required for on-board flash memory and some
/ type of optical media. When FF_MAX_SS is larger than FF_MIN_SS, FatFs is configured
/ for variable sector size mode and disk_ioctl() function needs to implement
/ GET_SECTOR_SIZE command. */
#define FF_USE_TRIM 0
/* This option switches support for ATA-TRIM. (0:Disable or 1:Enable)
/ To enable Trim function, also CTRL_TRIM command should be implemented to the
/ disk_ioctl() function. */
#define FF_FS_NOFSINFO 0
/* If you need to know correct free space on the FAT32 volume, set bit 0 of this
/ option, and f_getfree() function at first time after volume mount will force
/ a full FAT scan. Bit 1 controls the use of last allocated cluster number.
/
/ bit0=0: Use free cluster count in the FSINFO if available.
/ bit0=1: Do not trust free cluster count in the FSINFO.
/ bit1=0: Use last allocated cluster number in the FSINFO if available.
/ bit1=1: Do not trust last allocated cluster number in the FSINFO.
*/
/*---------------------------------------------------------------------------/
/ System Configurations
/---------------------------------------------------------------------------*/
#define FF_FS_TINY 0
/* This option switches tiny buffer configuration. (0:Normal or 1:Tiny)
/ At the tiny configuration, size of file object (FIL) is shrinked FF_MAX_SS bytes.
/ Instead of private sector buffer eliminated from the file object, common sector
/ buffer in the filesystem object (FATFS) is used for the file data transfer. */
#define FF_FS_EXFAT 0
/* This option switches support for exFAT filesystem. (0:Disable or 1:Enable)
/ To enable exFAT, also LFN needs to be enabled. (FF_USE_LFN >= 1)
/ Note that enabling exFAT discards ANSI C (C89) compatibility. */
#define FF_FS_NORTC 0
#define FF_NORTC_MON 1
#define FF_NORTC_MDAY 1
#define FF_NORTC_YEAR 2018
/* The option FF_FS_NORTC switches timestamp functiton. If the system does not have
/ any RTC function or valid timestamp is not needed, set FF_FS_NORTC = 1 to disable
/ the timestamp function. Every object modified by FatFs will have a fixed timestamp
/ defined by FF_NORTC_MON, FF_NORTC_MDAY and FF_NORTC_YEAR in local time.
/ To enable timestamp function (FF_FS_NORTC = 0), get_fattime() function need to be
/ added to the project to read current time form real-time clock. FF_NORTC_MON,
/ FF_NORTC_MDAY and FF_NORTC_YEAR have no effect.
/ These options have no effect at read-only configuration (FF_FS_READONLY = 1). */
#define FF_FS_LOCK 10 // 启用文件锁定功能
/* The option FF_FS_LOCK switches file lock function to control duplicated file open
/ and illegal operation to open objects. This option must be 0 when FF_FS_READONLY
/ is 1.
/
/ 0: Disable file lock function. To avoid volume corruption, application program
/ should avoid illegal open, remove and rename to the open objects.
/ >0: Enable file lock function. The value defines how many files/sub-directories
/ can be opened simultaneously under file lock control. Note that the file
/ lock control is independent of re-entrancy. */
/* #include <somertos.h> // O/S definitions */
#define FF_FS_REENTRANT 0
#define FF_FS_TIMEOUT 1000
#define FF_SYNC_t HANDLE
/* The option FF_FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs
/ module itself. Note that regardless of this option, file access to different
/ volume is always re-entrant and volume control functions, f_mount(), f_mkfs()
/ and f_fdisk() function, are always not re-entrant. Only file/directory access
/ to the same volume is under control of this function.
/
/ 0: Disable re-entrancy. FF_FS_TIMEOUT and FF_SYNC_t have no effect.
/ 1: Enable re-entrancy. Also user provided synchronization handlers,
/ ff_req_grant(), ff_rel_grant(), ff_del_syncobj() and ff_cre_syncobj()
/ function, must be added to the project. Samples are available in
/ option/syscall.c.
/
/ The FF_FS_TIMEOUT defines timeout period in unit of time tick.
/ The FF_SYNC_t defines O/S dependent sync object type. e.g. HANDLE, ID, OS_EVENT*,
/ SemaphoreHandle_t and etc. A header file for O/S definitions needs to be
/ included somewhere in the scope of ff.h. */
/*--- End of configuration options ---*/