【程序】在STM32單片機上用1700行代碼實現基於LwIP 2.1.2協議棧raw API和FatFs文件系統的FTP服務器(20200703版)

本程序在LwIP 2.1.2協議棧上用raw API實現了一個FTP服務器。文件存儲在Winbond的W25Q128 SPI Flash中,通過FatFs讀寫文件,建立了FAT文件系統,容量爲16MB。程序只有1700多行代碼,由頭文件ftp.h和源文件ftpd.c組成。

主要特點:
1. 採用lwip的raw API實現,可以在裸機環境下運行,支持IPv6
2. 兼容Windows文件管理器和FileZilla FTP客戶端
3. 實現了文件瀏覽、上傳、下載、重命名、新建文件夾、刪除文件夾和文件等基本FTP功能
4. 支持IPv4和IPv6的主動模式和被動模式。可以在FileZilla客戶端中強制使用主動模式,主動模式下電腦必須要關閉防火牆才能訪問FTP服務器
5. nettime.c實現了從互聯網獲取北京時間的功能,並保存到單片機的RTC實時時鐘,不用擔心通過FTP上傳的文件沒有日期和時間信息。時間服務器選擇的是utcnist.colorado.edu,端口號爲37

不足之處:
1. 沒有用戶權限配置功能,目前所有的用戶(包括匿名用戶)都能上傳和下載文件
2. 暫不支持FileZilla中的斷點續傳功能
3. 上傳文件時,不支持某些中文文件名(這是因爲Flash空間不夠,無法在ffconf.h中將FF_CODE_PAGE配置爲936 - Simplified Chinese (DBCS))
4. FileZilla無法自動檢測編碼,必須改爲強制UTF-8,文件名纔不會亂碼

【STM32F107VC+DP83848+W25Q128+FTP程序】

程序下載地址:https://pan.baidu.com/s/1KECwLCFMJIj4wO_WDz0o-A(提取碼:gk5t)

DP83848.c中的三個重要配置項:
(1)ETH_REMAP:若ETH部分引腳被重映射到了PD口,則應配置爲1,否則爲0
(2)USE_MII:如果DP83848以太網收發芯片使用的是MII接口則應配置爲1,使用RMII接口則應配置爲0
(3)RESET_N引腳:DP83848芯片的復位引腳,本例程中接的是PB15,可以改成其他引腳
請根據開發板的情況,正確配置DP83848.c中的這三個項目。

DP83848芯片的RESET引腳最好接上外部下拉電阻,避免單片機PA8沒有輸出時鐘時DP83848未處於復位狀態,干擾電路正常運行。
啓動文件startup_stm32f107xc.s中應該設置合適的棧大小Stack_Size,供FatFs使用。

HSE晶振的大小在項目屬性的C/C++選項卡中的Preprocessor Symbols中的HSE_VALUE上設置。
本例程設置的是25000000,即25MHz。若修改了晶振大小,則還需修改common.c的clock_init時鐘初始化函數。

【STM32F103RE+88W8801+W25Q128+FTP程序】

請參閱:https://blog.csdn.net/ZLK1214/article/details/107117885

【LwIP 2.0.3版本兼容性】

若要在2.0.3版本的lwip中運行,需要將2.1.2版本的pbuf_free_header和pbuf_remove_header函數複製過來,然後在ftpd.c頂部包含頭文件<ctype.h>。

【程序運行截圖】

1. 使用Windows文件管理器通過計算機名訪問FTP服務器(已登錄admin用戶,密碼爲123456)

2. FTP登錄界面

3. FileZilla訪問FTP服務器(必須要開啓強制UTF-8編碼才能防止中文文件名亂碼)

4. FileZilla上傳文件

6. 通過板子的IPv6地址訪問FTP服務器

【程序運行結果】

STM32F107VC ETH
SystemCoreClock=72000000
LSI is ready!
Failed to start ETH!
MAC address: 00:80:E1:CD:9E:1A
IPv6 link-local address: FE80::280:E1FF:FECD:9E1A
SPI Flash: M=0xef, ID=0x17
SPI Flash: M=0xef, ID=0x4018
Disk is mounted!
DP83848 interrupt occurred! status=0x2c20
Link is up!
ETH is restarted!
[Send] len=350
[Send] len=86
[Send] len=78
[Send] len=86
[Recv] len=60
[Recv] len=60
[Recv] len=60
[Send] len=350
[Recv] len=60
[Recv] len=60
[Send] len=86
[Recv] len=208
[Send] len=70
[Recv] len=142
[Send] len=86
[Recv] len=86
[Send] len=78
[Recv] len=590
[Send] len=350
[Recv] len=590
[Send] len=42
[Send] len=42
[Send] len=42
[Send] len=42
[Send] len=86
DHCP supplied address!
IP address: 192.168.1.2
Subnet mask: 255.255.255.0
Default gateway: 192.168.1.1
IPv6 address 1: 2409:8A62:362:1050:280:E1FF:FECD:9E1A
DNS Server: 192.168.1.1
[Send] len=42
Time server IP: not in cache!
[Recv] len=60
[Send] len=80
[Recv] len=116
Time server IP: 128.138.140.44
Connecting to 128.138.140.44...
[Send] len=58
[Recv] len=60
Connected!
[Send] len=54
[Recv] len=208
[Recv] len=60
[Send] len=54
[Recv] len=60
Time from network: 2020-07-03 21:17:57
RTC is not updated!
Connection to the time server is closed!
[Send] len=54
[Recv] len=60
[Send] len=42
[Send] len=42
[Recv] len=60
[Send] len=42
[Recv] len=208
[Recv] len=60
[Recv] len=208
[Recv] len=208
[Recv] len=60
[Recv] len=140
[Recv] len=208
[Recv] len=140
[Recv] len=60
[Recv] len=208
[Recv] len=140
[Recv] len=208
[Recv] len=60
[Recv] len=208
[Recv] len=140
[Recv] len=140
[Recv] len=60
[Recv] len=208
[Recv] len=86
[Send] len=86
[Recv] len=86
[Send] len=78
[Recv] len=74
FTPD accepted [2409:8A62:362:1050:883B:55B0:939C:EE8]:54466!
220 LwIP FTP Service
[Send] len=96
[Recv] len=208
[Send] len=96
[Recv] len=74
ftpd_sent: 22 bytes of response sent
[Recv] len=90
ftpd_recv: received 16 bytes
USER anonymous
331 Anonymous access allowed, send identity (e-mail name) as password.
[Send] len=146
[Recv] len=74
ftpd_sent: 72 bytes of response sent
ftpd_sent: processed 16 bytes
[Recv] len=88
ftpd_recv: received 14 bytes
PASS IEUser@
230 Login successful.
[Send] len=97
[Recv] len=88
[Send] len=74
[Recv] len=60
[Send] len=97
[Recv] len=74
ftpd_sent: 23 bytes of response sent
ftpd_sent: processed 14 bytes
[Recv] len=88
ftpd_recv: received 14 bytes
opts utf8 on
200 Always in UTF8 mode.
[Send] len=100
[Recv] len=74
ftpd_sent: 26 bytes of response sent
ftpd_sent: processed 14 bytes
[Recv] len=80
ftpd_recv: received 6 bytes
syst
500 Unknown command.
[Send] len=96
[Recv] len=80
[Send] len=74
[Recv] len=208
[Send] len=96
[Recv] len=74
ftpd_sent: 22 bytes of response sent
ftpd_sent: processed 6 bytes
[Recv] len=85
ftpd_recv: received 11 bytes
site help
500 Unknown command.
[Send] len=96
[Recv] len=85
[Send] len=74
[Recv] len=92
[Send] len=96
[Recv] len=74
ftpd_sent: 22 bytes of response sent
ftpd_sent: processed 11 bytes
[Recv] len=79
ftpd_recv: received 5 bytes
PWD
257 "/" is the current directory.
[Send] len=109
[Recv] len=74
ftpd_sent: 35 bytes of response sent
ftpd_sent: processed 5 bytes
[Recv] len=82
ftpd_recv: received 8 bytes
TYPE A
200 Switching to ASCII mode.
[Send] len=104
[Recv] len=82
[Send] len=74
[Recv] len=92
[Recv] len=60
[Recv] len=208
[Recv] len=92
[Send] len=104
[Recv] len=86
[Send] len=86
[Recv] len=208
[Recv] len=140
[Send] len=104
[Recv] len=74
ftpd_sent: 30 bytes of response sent
ftpd_sent: processed 8 bytes
[Recv] len=80
ftpd_recv: received 6 bytes
EPSV
229 Entering Extended Passive Mode (|||57805|).
[Send] len=123
[Recv] len=80
[Send] len=74
[Recv] len=140
[Recv] len=60
[Send] len=123
[Recv] len=74
ftpd_sent: 49 bytes of response sent
ftpd_sent: processed 6 bytes
[Recv] len=86
[Send] len=78
[Recv] len=74
FTPD data connection to [2409:8A62:362:1050:883B:55B0:939C:EE8]:54468 is established!
[Recv] len=80
ftpd_recv: received 6 bytes
LIST
150 Here comes the directory listing.
[Send] len=113
[Recv] len=80
[Send] len=74
[Send] len=113
[Recv] len=74
ftpd_sent: 39 bytes of response sent
ftpd_sent: processed 6 bytes
04-04-2020  08:18PM                 9471 111110.xlsx
04-01-2020  11:26PM               934844 HeartOfCat_20200401.zip
05-18-2020  09:13PM                 9236 pt32.xlsx
04-27-2020  09:57PM                  917 test.c
FTPD data connection [2409:8A62:362:1050:883B:55B0:939C:EE8]:54468 is shutdown by the server!
[Send] len=295
226 Directory send OK.
[Send] len=98
[Recv] len=74
ftpd_sent: 24 bytes of response sent
[Recv] len=208
[Send] len=295
[Recv] len=74
[Recv] len=74
FTPD data connection [2409:8A62:362:1050:883B:55B0:939C:EE8]:54468 is closed by the client!
[Send] len=74
[Recv] len=74
[Send] len=74
[Recv] len=60
[Recv] len=208

【主要代碼】

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命令

// 命令處理過程中的異常情況
#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];
  
  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);
int ftpd_init(void);
void *ftpd_memrchr(const void *s, int c, size_t n);
int ftpd_simplify_path(char *path, int basepos);
char *ftpd_strdup(const char *s);

#endif

ftpd.c:

/*************************** 基於LwIP raw API的FTP服務器*****************************
** 注意事項:
** 1. 使用FileZilla客戶端連接FTP服務器時, 字符集應該選擇"強制UTF-8"
**    不能選擇"自動檢測", 這樣文件名纔不會亂碼
** 2. 若想要移動文件, 可在文件管理器中使用"xxx/", "../"這樣的語法重命名文件
**    例如想要把"abc.txt"移動到當前目錄的123目錄下, 則可以將文件重命名爲"123/abc.txt"
**    將"def.doc"移動到父目錄的456文件夾下, 則應該重命名爲"../456/def.doc"
** 3. 服務器使用FatFs讀寫磁盤文件
**    如果出現HardFault錯誤, 則可能是startup_stm32*.s啓動文件裏面的Stack_Size值太小
**    將其改大就可以解決問題
************************************************************************************/
#include <ff.h>
#include <lwip/tcp.h>
#include <string.h>
#include <time.h>
#include "ftpd.h"

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;

/* 控制連接收到新請求 */
static err_t ftpd_accept(void *arg, struct tcp_pcb *newpcb, err_t err)
{
  struct ftpd_state *state;
  
  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 (!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)
      {
        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;
  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;
    }
    
    ftpd_filetime(state->finfo->fdate, state->finfo->ftime, &tm);
    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, "%14u ", 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年的年份, 可以放心使用
time_t ftpd_filetime(WORD fdate, WORD ftime, struct tm *ptm)
{
  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;
  
  // 如果ptm中的日期有誤, 則這個函數能自動修正
  // 否則如果輸出了錯誤的日期, 那麼Windows的文件管理器會錯誤地顯示快捷方式圖標
  return mktime(ptm);
}

/* 關閉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;
}

/* 判斷輸入的用戶名和密碼是否正確 */
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)
  {
    ftpd_send_msg(state, "425 Use PORT or PASV first.\r\n");
    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)
  {
    ret = ftpd_fullpath(state, buffer, MAX_PATH, state->cmdarg, &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;
  long size;
  FIL *fp = 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 %ld\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, " (%ld bytes).\r\n", size);
      ftpd_send_msg(state, buffer);
      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;
      }
    }
    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)
  {
    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;
      }
      
      fr = f_open(fp, buffer, FA_CREATE_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");
      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;
      }
    }
    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
    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
    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;
}

/* 去除路徑中的"./"和"../"以及"//" */
// 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;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章