其他杂项命令的实现
cwd命令(改变当前工作目录):
static void do_cwd(session_t *sess)
{
if (chdir(sess->arg) < 0)
{
ftp_reply(sess, FTP_NOPERM, "Failed to change directory.");
return;
}
ftp_reply(sess, FTP_CWDOK, "Directory successfully changed.");
}
系统函数:
头文件:#include<unistd.h>
定义函数:in chdir(const char* path)
函数说明:chdir()函数用来将当前的工作目录改变成以参数path所指的目录
返回值:成功返回0,失败返回-1,errno为错误代码
cdup命令(返回上一级目录):
static void do_cdup(session_t *sess)
{
if (chdir("..") < 0)
{
ftp_reply(sess, FTP_NOPERM, "Failed to change directory.");
return;
}
ftp_reply(sess, FTP_CWDOK, "Directory successfully changed.");
}
mkd命令(创建一个文件夹):
static void do_mkd(session_t *sess)
{
if(mkdir(sess->arg, 0755) < 0)
{
if(errno == EEXIST)
{
// 550 Create directory operation failed.
ftp_reply(sess, FTP_FILEFAIL, "Create directory operation failed.");
}
else
{
//550 Permission denied.
ftp_reply(sess, FTP_NOPERM, "Permission denied.");
}
return;
}
// 257 "/home/51cc/C12/Project/Test1" created
char buf[MAX_BUFFER_SIZE] = {0};
if(getcwd(buf, MAX_BUFFER_SIZE) == NULL)
{
//ftp_rely();
}
else
{
//printf("buf = %s\n",buf);
sprintf(buf, "\"%s\%s\" created",buf,sess->arg);
ftp_reply(sess, FTP_MKDIROK, buf);
}
}
系统函数:
int mkdir(const char *pathname, mode_t mode);
今天使用linux的mkdir创建目录。
函数说明:
mkdir()函数以mode方式创建一个以参数pathname命名的目录,mode定义新创建目录的权限。
返回值:
若目录创建成功,则返回0;否则返回-1,并将错误记录到全局变量errno中。
头文件:#include <unistd.h>
定义函数:char * getcwd(char * buf, size_t size);
函数说明:getcwd()会将当前的工作目录绝对路径复制到参数buf 所指的内存空间,参数size 为buf 的空间大小。
返回值:执行成功则将结果复制到参数buf 所指的内存空间, 或是返回自动配置的字符串指针. 失败返回NULL,错误代码存于errno.
rmd命令(删除目录):
static void do_rmd(session_t *sess)
{
if(rmdir(sess->arg) < 0)
{
//550 Remove directory operation failed.
ftp_reply(sess, FTP_NOPERM, "Remove directory operation failed.");
return;
}
//250 Remove directory operation successful.
ftp_reply(sess, FTP_RMDIROK, "Remove directory operation successful.");
}
系统函数:
函数:rmdir
函数原型:int rmdir(const char* dirname);
函数功能:删除一个目录,若成功返回0,否则返回-1
dele命令;
static void do_dele(session_t *sess)
{
if(unlink(sess->arg) < 0)
{
//550 Delete operation failed.
ftp_reply(sess, FTP_NOPERM, "Delete operation failed.");
return;
}
// 250 Delete operation successful.
ftp_reply(sess, FTP_DELEOK, "Delete operation successful.");
}
size命令:
static void do_size(session_t *sess)
{
struct stat sbuf;
if(stat(sess->arg, &sbuf) < 0)
{
// 550 Could not get file size.
ftp_reply(sess, FTP_FILEFAIL, "Could not get file size.");
return;
}
// 213 6
char buf[MAX_BUFFER_SIZE] = {0};
sprintf(buf, "%d", sbuf.st_size);
ftp_reply(sess, FTP_STATFILE_OK, buf);
}
系统函数:
头文件:#include<sys/stat.h> #include<unistd.h>
定义函数:int stat(const char* file_name, struct stat* buf);
函数说明:stat()用来将参数file_name所指的文件状态,复制到参数buf所指的结构中
返回值:执行成功返回0,失败返回-1,错误代码存在于errno中
rnfr命令和rnto命令(实现对文件的重命名):
static void do_rnfr(session_t *sess)
{
sess->rnfr_name = (char*)malloc(strlen(sess->arg)+1);
assert(sess->rnfr_name != NULL);
strcpy(sess->rnfr_name, sess->arg);
//350 Ready for RNTO.
ftp_reply(sess, FTP_RNFROK, "Ready for RNTO.");
}
static void do_rnto(session_t *sess)
{
if(sess->rnfr_name == 0)
{
//503 RNFR required first.
ftp_reply(sess, FTP_NEEDRNFR, "RNFR required first.");
return;
}
int ret = rename(sess->rnfr_name, sess->arg);
free(sess->rnfr_name);
sess->rnfr_name = 0;
if(ret < 0)
{
// 550 Rename failed.
ftp_reply(sess, FTP_FILEFAIL, "Rename failed.");
return;
}
//250 Rename successful.
ftp_reply(sess, FTP_RENAMEOK, "Rename successful.");
}
上传stor实现
static void do_retr(session_t *sess)
{
//1建立数据连接
if(get_transfer_fd(sess) == 0)
return;
struct stat sbuf;
stat(sess->arg, &sbuf);
char buf[MAX_BUFFER_SIZE] = {0};
//2判断传输模式
if(sess->is_ascii)
sprintf(buf, "Opening ASCII mode data connection for %s (%ld bytes)",
sess->arg, sbuf.st_size);//Ascii
else
sprintf(buf, "Opening BINARY mode data connection for %s (%ld bytes)",
sess->arg, sbuf.st_size);
//3回复150
ftp_reply(sess, FTP_DATACONN, buf);
//4传输数据
int fd = open(sess->arg, O_RDONLY);
if(fd < 0)
{
ftp_reply(sess, FTP_FILEFAIL, "Failed to open file.");
return;
}
long long file_total_size = sbuf.st_size;
long long offset = sess->restart_pos;
sess->restart_pos = 0;
if(offset >= file_total_size)
{
ftp_reply(sess, FTP_TRANSFEROK, "Transfer complete.");
return;
}
unsigned long left_bytes = file_total_size - offset;
lseek(fd, offset, SEEK_SET);
int count = 0;
int flag = 0, ret = 0;
while(left_bytes > 0)
{
memset(buf, 0, sizeof(buf));
count = read(fd, buf, MAX_BUFFER_SIZE);
if(count < 0)
{
flag = 1;
}
ret = write(sess->data_fd, buf, count);
if(ret < 0)
{
flag = 2;
}
else if(ret != count)
{
flag = 3;
}
left_bytes -= count;
}
close(fd);
close(sess->data_fd);
sess->data_fd = -1;
//5回复226
if(flag == 0)
ftp_reply(sess, FTP_TRANSFEROK, "Transfer complete.");
else if(flag == 1)
ftp_reply(sess, FTP_BADSENDFILE, "Failure reading from local file.");
else if(flag == 2)
ftp_reply(sess, FTP_BADSENDFILE, "Failure writing from net file.");
else if(flag == 3)
ftp_reply(sess, FTP_BADSENDNET, "Failure writting to network stream.");
}
系统函数:
头文件:#include <sys/types.h> #include<unistd.h>
定义函数:off_t lseek(int fildes, off_t offset, int whence);
函数说明:每一个已经打开的文件都有一个读写位置,当打开文件时通常其读写位置是指向文件开头,若是以附加的方式打开文件(如O_APPEND),则读写位置会指向文件尾。当read()或是write()时,读写位置会随之增加,lseek()便是用来控制该文件的读写位置,参数fildes为已打开的文件描述词,参数offset为根据参数wherece来移动读写位置位移数。
参数whence为下列其中一种:
-
SEEK_SET 参数offset 即为新的读写位置.
-
SEEK_CUR 以目前的读写位置往后增加offset 个位移量.
-
SEEK_END 将读写位置指向文件尾后再增加offset 个位移量.
PS:当whence 值为SEEK_CUR 或 SEEK_END 时, 参数offet 允许负值的出现
下面是一些特别的使用方式:
- 将读写位置移到文件开头时:lseek(int fildes,0,SEEK_SET);
- 将读写位置移到文件尾时:lseek(int fildes,0,SEEK_END);
- 取得文件目前的位置时:lseek(int fildes,0,SEEK_CUR);
返回值:当调用成功时则返回目前读写位置,也就是距离文件开头多少个字节。若有错误则返回-1,error会存放错误代码。
头文件:#include<unistd.h>
定义函数:ssize_t read(int fd, void* buf, size_t count);
函数说明:read函数会把参数fd所指的文件传送count个字节到buf指针所指的内存中。若参数count为0,则read函数不会有作用并返回0。返回值为实际读取到的字节数,如果返回0表示已经到达文件尾或是无可读取的数据,此外文件读写位置会随读到的字节移动。
返回值:当有错误发生的时候则返回-1,错误代码存入error中,而文件读写位置都无法预测
错误代码:
-
EINTR 此调用被信号所中断.
-
EAGAIN 当使用不可阻断I/O 时(O_NONBLOCK), 若无数据可读取则返回此值.
-
EBADF 参数fd 非有效的文件描述词, 或该文件已关闭
头文件:#include <unistd.h>
定义函数:ssize_t write (int fd, const void * buf, size_t count);
函数说明:write()会把参数buf 所指的内存写入count 个字节到参数fd 所指的文件内. 当然, 文件读写位置也会随之移动.
返回值:如果顺利write()会返回实际写入的字节数. 当有错误发生时则返回-1, 错误代码存入errno 中.
错误代码:
- EINTR 此调用被信号所中断.
- EAGAIN 当使用不可阻断I/O 时 (O_NONBLOCK), 若无数据可读取则返回此值.
- EADF 参数fd 非有效的文件描述词, 或该文件已关闭.
下载retr实现
static void do_stor(session_t *sess)
{
//1建立数据连接
if(get_transfer_fd(sess) == 0)
return;
//150 Ok to send data.
ftp_reply(sess, FTP_DATACONN, "Ok to send data.");
//4传输数据
int fd = open(sess->arg, O_CREAT|O_WRONLY, 0755);
if(fd < 0)
{
ftp_reply(sess, FTP_FILEFAIL, "Failed to open file.");
return;
}
unsigned long offset = sess->restart_pos;
sess->restart_pos = 0;
lseek(fd, offset, SEEK_SET);
char buf[MAX_BUFFER_SIZE];
int count = 0;
int flag = 0, ret = 0;
while(1)
{
memset(buf, 0, sizeof(buf));
count = read(sess->data_fd, buf, MAX_BUFFER_SIZE);
if(count == 0)
{
break;
}
else if(count < 0)
{
flag = 1;
break;
}
ret = write(fd, buf, count);
if(ret < 0)
{
flag = 2;
break;
}
else if(ret != count)
{
flag = 3;
break;
}
}
close(fd);
close(sess->data_fd);
sess->data_fd = -1;
//5回复226
if(flag == 0)
ftp_reply(sess, FTP_TRANSFEROK, "Transfer complete.");
else if(flag == 1)
ftp_reply(sess, FTP_BADSENDFILE, "Failure reading from local file.");
else if(flag == 2)
ftp_reply(sess, FTP_BADSENDFILE, "Failure writing from net file.");
else if(flag == 3)
ftp_reply(sess, FTP_BADSENDNET, "Failure writting to network stream.");
}
PS:上传和下载的实质就是从一端读文件,然后写到另一端的过程。
续传和续载实现
记住传输的位置是关键!
续传命令rest:
static void do_rest(session_t *sess)
{
sess->restart_pos = str_to_longlong(sess->arg);
char buf[MAX_BUFFER_SIZE] = {0};
sprintf(buf, "Restart position accepted (%u).", sess->restart_pos);
// 350 Restart position accepted (4161536).
ftp_reply(sess, FTP_RESTOK, buf);
}
配置项解析
做三张表,然后去查表解析config文件。
#include"parseconf.h"
static struct parseconf_bool_setting
{
const char *p_setting_name;
int *p_variable;
}
parseconf_bool_array[] =
{
{ "pasv_enable", &tunable_pasv_enable },
{ "port_enable", &tunable_port_enable },
{ NULL, NULL }
};
static struct parseconf_uint_setting
{
const char *p_setting_name;
unsigned int *p_variable;
}
parseconf_uint_array[] =
{
{ "listen_port", &tunable_listen_port },
{ "max_clients", &tunable_max_clients },
{ "max_per_ip", &tunable_max_per_ip },
{ "accept_timeout", &tunable_accept_timeout },
{ "connect_timeout", &tunable_connect_timeout },
{ "idle_session_timeout", &tunable_idle_session_timeout },
{ "data_connection_timeout", &tunable_data_connection_timeout },
{ "local_umask", &tunable_local_umask },
{ "upload_max_rate", &tunable_upload_max_rate },
{ "download_max_rate", &tunable_download_max_rate },
{ NULL, NULL }
};
static struct parseconf_str_setting
{
const char *p_setting_name;
const char **p_variable;//字符串本来就是char*,指向字符串要是char**
}
parseconf_str_array[] =
{
{ "listen_address", &tunable_listen_address },
{ NULL, NULL }
};
void parseconf_load_file(const char *path)//加载配置文件
{
FILE *fp = fopen(path, "r");
if(NULL == fp)
ERR_EXIT("fopen");
char setting_line[1024] = {0};
while(fgets(setting_line, sizeof(setting_line), fp) != NULL)
{
if(strlen(setting_line) == 0
|| setting_line[0] == '#')//读命令失败或者开头遇到'#'跳过
continue;
//key=value
str_trim_crlf(setting_line);
parseconf_load_setting(setting_line);//设置命令
memset(setting_line, 0, sizeof(setting_line));
}
fclose(fp);
}
void parseconf_load_setting(const char *setting)
{
char key[128] = {0};
char value[128] = {0};
str_split(setting, key, value, '=');
const struct parseconf_str_setting *p_str_setting = parseconf_str_array;
while (p_str_setting->p_setting_name != NULL)
{
if (strcmp(key, p_str_setting->p_setting_name) == 0)
{
const char **p_cur_setting = p_str_setting->p_variable;
if (*p_cur_setting)
free((char*)*p_cur_setting);
*p_cur_setting = strdup(value);//复制字符串,返回char*
return;
}
p_str_setting++;
}
const struct parseconf_bool_setting *p_bool_setting = parseconf_bool_array;
while (p_bool_setting->p_setting_name != NULL)
{
if (strcmp(key, p_bool_setting->p_setting_name) == 0)
{
//str_upper(value);
if (strcmp(value, "YES") == 0)
{
*(p_bool_setting->p_variable) = 1;
}
else if (strcmp(value, "NO") == 0)
{
*(p_bool_setting->p_variable) = 0;
}
else
{
fprintf(stderr, "bad bool value in config file for: %s\n", key);
exit(EXIT_FAILURE);
}
return;
}
p_bool_setting++;
}
const struct parseconf_uint_setting *p_uint_setting = parseconf_uint_array;
while (p_uint_setting->p_setting_name != NULL)
{
if (strcmp(key, p_uint_setting->p_setting_name) == 0)
{
if (value[0] == '0')
{
//*(p_uint_setting->p_variable) = str_octal_to_uint(value);
}
else
*(p_uint_setting->p_variable) = atoi(value);
return;
}
p_uint_setting++;
}
}