You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
412 lines
10 KiB
412 lines
10 KiB
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/time.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
|
|
#include "ftree.h"
|
|
#include "hash.h"
|
|
|
|
struct client {
|
|
int fd;
|
|
int state; //状态
|
|
struct in_addr ipaddr;
|
|
struct request *info;
|
|
struct client *next;
|
|
};
|
|
|
|
static struct client *addclient(struct client *top, int fd, struct in_addr addr);
|
|
static struct client *removeclient(struct client *top, int fd);
|
|
struct request *createrequest(void);
|
|
void complete_path(const char *child, char *s);
|
|
int handleclient(struct client *p);
|
|
int checkfile(struct client *p);
|
|
// 百分比进度条
|
|
void procbar(double total,double current);
|
|
|
|
|
|
int bindandlisten(unsigned short port);
|
|
|
|
char src[MAXPATH]; // source of file tree on server.
|
|
|
|
/*
|
|
* Copies files sent from the client rooted at dest.
|
|
*/
|
|
void rcopy_server(unsigned short port, char *dest) {
|
|
int clientfd, maxfd, nready;
|
|
struct client *p;
|
|
struct client *head = NULL;
|
|
socklen_t len;
|
|
// IP地址结构体
|
|
struct sockaddr_in q;
|
|
fd_set allset;
|
|
fd_set rset;
|
|
strcpy(src, dest);
|
|
|
|
int i;
|
|
|
|
|
|
int listenfd = bindandlisten(port);
|
|
// initialize allset and add listenfd to the
|
|
// set of file descriptors passed into select
|
|
FD_ZERO(&allset);
|
|
FD_SET(listenfd, &allset);
|
|
// maxfd identifies how far into the set to search
|
|
maxfd = listenfd;
|
|
|
|
while (1) {
|
|
// make a copy of the set before we pass it into select
|
|
rset = allset;
|
|
|
|
nready = select(maxfd + 1, &rset, NULL, NULL, NULL);
|
|
if (nready == 0) {
|
|
printf("No response from clients\n");
|
|
continue;
|
|
}
|
|
|
|
if (nready == -1) {
|
|
perror("select");
|
|
continue;
|
|
}
|
|
|
|
if (FD_ISSET(listenfd, &rset)){
|
|
// printf("a new client is connecting\n");
|
|
len = sizeof(q);
|
|
if ((clientfd = accept(listenfd, (struct sockaddr *)&q, &len)) < 0) {
|
|
perror("accept");
|
|
exit(1);
|
|
}
|
|
FD_SET(clientfd, &allset);
|
|
if (clientfd > maxfd) {
|
|
maxfd = clientfd;
|
|
}
|
|
// printf("connection from %s\n", inet_ntoa(q.sin_addr));
|
|
head = addclient(head, clientfd, q.sin_addr);
|
|
}
|
|
|
|
for(i = 0; i <= maxfd; i++) {
|
|
if (FD_ISSET(i, &rset)) {
|
|
for (p = head; p != NULL; p = p->next) {
|
|
if (p->fd == i) {
|
|
int result = handleclient(p);
|
|
// 返回-1,正常状态
|
|
if (result == -1) {
|
|
int tmp_fd = p->fd;
|
|
head = removeclient(head, p->fd);
|
|
FD_CLR(tmp_fd, &allset);
|
|
close(tmp_fd);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Based on the state of the client complete task.
|
|
✔ ✖
|
|
*/
|
|
int handleclient(struct client *p) {
|
|
|
|
if (p->state == AWAITING_TYPE) {
|
|
int t;
|
|
int len = read(p->fd, &t, sizeof(int));
|
|
p->info->type = ntohl(t);
|
|
if (len != sizeof(int)) {
|
|
perror("server: reading error");
|
|
exit(1);
|
|
}
|
|
// printf("type %d read %d bytes\n", p->info->type, len);
|
|
p->state = AWAITING_PATH;
|
|
return 1;
|
|
}
|
|
|
|
if (p->state == AWAITING_PATH) {
|
|
int len = read(p->fd, &(p->info->path), MAXPATH);
|
|
if (len != MAXPATH) {
|
|
perror("server: reading error");
|
|
exit(1);
|
|
}
|
|
// printf("path is %s read in %d bytes\n", p->info->path, len);
|
|
p->state = AWAITING_PERM;
|
|
return 1;
|
|
}
|
|
|
|
if (p->state == AWAITING_PERM) {
|
|
int perm;
|
|
int len = read(p->fd, &perm, sizeof(int));
|
|
p->info->mode = ntohl(perm);
|
|
if (len != sizeof(int)) {
|
|
perror("server line 135: reading error");
|
|
exit(-1);
|
|
}
|
|
// printf("mode %d read in %d bytes\n", p->info->mode, len);
|
|
p->state = AWAITING_SIZE;
|
|
return 1;
|
|
}
|
|
|
|
if(p->state == AWAITING_SIZE) {
|
|
int len = read(p->fd, &(p->info->size), sizeof(int));
|
|
if (len != sizeof(int)) {
|
|
perror("server: reading error");
|
|
exit(-1);
|
|
}
|
|
// printf("size %d read in %d bytes\n", p->info->size, len);
|
|
procbar(p->info->size,len);
|
|
p->state = AWAITING_HASH;
|
|
return 1;
|
|
}
|
|
|
|
if (p->state == AWAITING_HASH) {
|
|
int status;
|
|
if (p->info->type == REGFILE) {
|
|
int len = read(p->fd, &(p->info->hash), BLOCKSIZE);
|
|
if (len != BLOCKSIZE) {
|
|
perror("server: reading error");
|
|
}
|
|
// show_hash(p->info->hash);
|
|
// printf("hash read in %d bytes\n", len);
|
|
status = checkfile(p);
|
|
write(p->fd, &status, sizeof(int));
|
|
return -1;
|
|
}
|
|
if (p->info->type == REGDIR) {
|
|
status = OK;
|
|
int dir = mkdir(p->info->path, ((S_IRWXU | S_IRWXG | S_IRWXO) & (p->info->mode)));
|
|
if (dir == -1) {
|
|
if (errno != EEXIST) {
|
|
perror("server: mkdir");
|
|
}
|
|
}
|
|
write(p->fd, &status, sizeof(int));
|
|
return -1;
|
|
}
|
|
// 读取发送来的socket数据包
|
|
int len = read(p->fd, &(p->info->hash), BLOCKSIZE);
|
|
if (len != BLOCKSIZE) {
|
|
perror("server: reading error");
|
|
}
|
|
p->state = AWAITING_DATA;
|
|
return 1;
|
|
}
|
|
|
|
if (p->state == AWAITING_DATA) {
|
|
int status = OK;
|
|
FILE *in;
|
|
char path[MAXPATH];
|
|
// 拼装路径
|
|
complete_path(p->info->path, path);
|
|
if ((in = fopen(path, "w")) != NULL) {
|
|
int len;
|
|
int s = 0;
|
|
char byte;
|
|
// 文件读写
|
|
do {
|
|
len = read(p->fd, &byte, 1);
|
|
if (fwrite(&byte, 1, 1, in) != 1) {
|
|
perror("server: fwrite");
|
|
status = ERROR;
|
|
}
|
|
} while((len == 1) && ++s < p->info->size);
|
|
//close file handle
|
|
fclose(in);
|
|
if (len != 1) {
|
|
perror("server: reading file");
|
|
status = ERROR;
|
|
}
|
|
write(p->fd, &status, sizeof(int));
|
|
return -1;
|
|
} else {
|
|
status = ERROR;
|
|
perror("server: fopen");
|
|
printf("error with %s\n", p->info->path);
|
|
write(p->fd, &status, sizeof(int));
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Returns FD if success, exit if error.
|
|
*/
|
|
int bindandlisten(unsigned short port) {
|
|
struct sockaddr_in r;
|
|
int listenfd;
|
|
|
|
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
|
|
perror("socket");
|
|
exit(1);
|
|
}
|
|
int yes = 1;
|
|
if ((setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int))) == -1) {
|
|
perror("setsockopt");
|
|
}
|
|
memset(&r, '\0', sizeof(r));
|
|
r.sin_family = AF_INET;
|
|
r.sin_addr.s_addr = INADDR_ANY;
|
|
r.sin_port = htons(port);
|
|
|
|
if (bind(listenfd, (struct sockaddr *)&r, sizeof r)) {
|
|
perror("bind");
|
|
exit(1);
|
|
}
|
|
|
|
if (listen(listenfd, 5)) {
|
|
perror("listen");
|
|
exit(1);
|
|
}
|
|
return listenfd;
|
|
}
|
|
|
|
/*
|
|
* Adds client to list of clients.
|
|
*/
|
|
static struct client *addclient(struct client *top, int fd, struct in_addr addr) {
|
|
struct client *p = malloc(sizeof(struct client));
|
|
if (!p) {
|
|
perror("malloc");
|
|
exit(1);
|
|
}
|
|
|
|
struct request *r = malloc(sizeof(struct request));
|
|
if (!r) {
|
|
perror("malloc");
|
|
exit(1);
|
|
}
|
|
|
|
// printf("Adding client %s\n", inet_ntoa(addr));
|
|
|
|
p->fd = fd;
|
|
p->state = AWAITING_TYPE;
|
|
p->ipaddr = addr;
|
|
p->info = r;
|
|
p->next = top;
|
|
top = p;
|
|
return top;
|
|
}
|
|
|
|
/*
|
|
* Remove client from the list of clients.
|
|
* 链表中 移除客户端节点
|
|
*/
|
|
static struct client *removeclient(struct client *top, int fd) {
|
|
struct client **p;
|
|
|
|
for (p = ⊤ *p && (*p)->fd != fd; p = &(*p)->next)
|
|
;
|
|
// Now, p points to (1) top, or (2) a pointer to another client
|
|
// This avoids a special case for removing the head of the list
|
|
if (*p) {
|
|
struct client *t = (*p)->next;
|
|
// printf("Removing client %d %s\n", fd, inet_ntoa((*p)->ipaddr));
|
|
free((*p)->info);
|
|
free(*p);
|
|
*p = t;
|
|
} else {
|
|
fprintf(stderr, "Trying to remove fd %d, but I don't know about it\n",
|
|
fd);
|
|
}
|
|
return top;
|
|
}
|
|
|
|
/*
|
|
* Concatenates child path to src.
|
|
* 完整路径
|
|
*/
|
|
void complete_path(const char *child, char *s) {
|
|
strcpy(s, src);
|
|
strcat(s, "/");
|
|
strcat(s, child);
|
|
}
|
|
|
|
|
|
/*
|
|
* Returns SENDFILE if transfer if needed, OK otherwise.
|
|
* 如果需要同步 就返回 SENDFILE
|
|
*/
|
|
int checkfile(struct client *p) {
|
|
char target_path[MAXPATH];
|
|
// 拼装路径
|
|
complete_path(p->info->path, target_path);
|
|
|
|
// 对比文件是否被删,文件删除后再重新生成新的cache
|
|
|
|
struct stat properties;
|
|
|
|
// 文件属性读取失败
|
|
if (lstat(target_path, &properties) != -1) {
|
|
//如果是目录
|
|
if (S_ISDIR(properties.st_mode) && !S_ISDIR(p->info->mode)) {
|
|
fprintf(stderr, "server: file type mismatch %s\n", target_path);
|
|
return ERROR;
|
|
}
|
|
//
|
|
if (!S_ISDIR(properties.st_mode) && S_ISDIR(p->info->mode)) {
|
|
fprintf(stderr, "server: file type mismatch %s\n", target_path);
|
|
return ERROR;
|
|
}
|
|
if (properties.st_size != p->info->size) {
|
|
return SENDFILE;
|
|
}
|
|
// 判断hash 值
|
|
FILE *target_stream = fopen(target_path, "rb");
|
|
char target_hash[BLOCKSIZE];
|
|
hash(target_hash, target_stream);
|
|
if (check_hash(target_hash, p->info->hash)) {
|
|
return SENDFILE;
|
|
}
|
|
// 文件权限
|
|
int new_permissions = (S_IRWXU | S_IRWXG | S_IRWXO) & (p->info->mode);
|
|
int old_permissions = (S_IRWXU | S_IRWXG | S_IRWXO) & (properties.st_mode);
|
|
if (new_permissions != old_permissions) { // update permissions.
|
|
// 更改文件的权限
|
|
if (chmod(target_path, new_permissions) != 0) {
|
|
perror("server: chmod");
|
|
}
|
|
}
|
|
return OK;
|
|
} else {
|
|
// 处理返回
|
|
if (ENOENT == errno) {
|
|
return SENDFILE;
|
|
} else {
|
|
perror("server: lstat error"); // 文件/夹 权限信息错误
|
|
printf("error occured on %s\n", target_path);
|
|
return ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 进度条
|
|
*/
|
|
void procbar(double total,double current){
|
|
char bar[PROCBAR_LEN];
|
|
memset(bar,'\0',sizeof(bar));
|
|
// label
|
|
const char *label= "|/_\\";
|
|
//
|
|
int cnt=0;
|
|
int len= strlen(label);
|
|
// 占比
|
|
double rate = (100*current)/total;
|
|
//循环次数,必须为整数
|
|
int loop_count = (int) rate;
|
|
//loop print
|
|
while(cnt<=loop_count){
|
|
printf("[%-100s][%.11f%%][%c]\r",bar,rate,label[cnt%len]);
|
|
// 直接输出不,非缓冲区
|
|
fflush(stdout);
|
|
bar[cnt++] = PROC_STYLE;
|
|
usleep(2000);
|
|
}
|
|
// 输出换行,反正被覆盖
|
|
printf("\n");
|
|
}
|