Browse Source

项目初始化

master v240428
453530270@qq.com 2 years ago
commit
ac59c0653a
  1. 17
      Makefile
  2. 2
      README.md
  3. 226
      client.c
  4. 42
      ftree.h
  5. 11
      hash.h
  6. 38
      hash_functions.c
  7. 28
      rcopy_client.c
  8. 76
      rcopy_server.c
  9. 380
      server.c

17
Makefile

@ -0,0 +1,17 @@
PORT=57653
CFLAGS = -DPORT=$(PORT) -g -Wall -std=gnu99
DEPENDENCIES = hash.h ftree.h
all: rcopy_server rcopy_client
rcopy_server: rcopy_server.o server.o hash_functions.o
gcc ${CFLAGS} -o $@ $^
rcopy_client: rcopy_client.o client.o hash_functions.o
gcc ${CFLAGS} -o $@ $^
%.o: %.c {DEPENDENCIES}
gcc ${CFLAGS} -c $<
clean:
rm -f *.o rcopy_server rcopy_client

2
README.md

@ -0,0 +1,2 @@
# File Synchronizer
Written in C, File Synchronizer is a program that copies files and/or directories from a host computer to a remote server. If the directories already exist on the server the program updates the files if any changes have been made. To check if any changes have been made the program compares the file sizes, permission, and their hash values. The hash value for a file is generated using a XOR hashing algorithm. The information is transferred byte by byte through a TCP socket.

226
client.c

@ -0,0 +1,226 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <libgen.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include "ftree.h"
#include "hash.h"
int depth = 0;
#define MAXDATASIZE 100 /* max number of bytes we can get at once */
/*
* Concatenates child path to parent path.
*/
void complete_path(const char *parent, const char *child, char *dest) {
strcpy(dest, parent);
strcat(dest, "/");
strcat(dest, child);
}
/*
* Creates and returns a new socket connection.
*/
int accept_connnection(char *host, unsigned short port) {
int sockfd;
struct hostent *he;
struct sockaddr_in their_addr; /* connector's address information */
if ((he=gethostbyname(host)) == NULL) { /* get the host info */
perror("gethostbyname");
exit(1);
}
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(1);
}
their_addr.sin_family = AF_INET; /* host byte order */
their_addr.sin_port = htons(port); /* short, network byte order */
their_addr.sin_addr = *((struct in_addr *)he->h_addr);
bzero(&(their_addr.sin_zero), 8); /* zero the rest of the struct */
if (connect(sockfd, (struct sockaddr *)&their_addr,
sizeof(struct sockaddr)) == -1) {
perror("connect");
exit(1);
}
return sockfd;
}
/*
* Copies file tree with root based at src to server.
* r_src is the root for the tree on the server.
*/
int rcopy_client(char *src, char *host, unsigned short port, char *r_src, int type) {
int sockfd = accept_connnection(host, port);
int fcalls = 0; // keep track of children.
int final = 0; // return value.
// 文件、文件夹的属性
struct stat properties;
// 请求结构体,包含路径信息等
struct request r;
// lstat 获取文件属性,比如文件权限,文件属主,文件大小等属性。
if (lstat(src, &properties) == -1) {
perror("client: lstat");
exit(1);
}
if (!S_ISLNK(properties.st_mode)) { // skip links.
strcpy(r.path, r_src);
r.mode = properties.st_mode;
r.size = properties.st_size;
// 判断是否目录
if (!S_ISDIR(properties.st_mode)) {
if (type == 0) {
r.type = REGFILE;
} else {
r.type = TRANSFILE;
}
FILE *in; // 文件指针
if ((in = fopen(src, "rb")) != NULL) {
hash(r.hash, in);
fclose(in);
} else {
perror("client: fopen");
exit(1);
}
} else {
r.type = REGDIR;
}
int t, m;
t = htonl(r.type);
m = htonl(r.mode);
if (write(sockfd, &t, sizeof(int)) == -1) {
perror("client: writing type");
exit(1);
}
if (write(sockfd, &r.path, MAXPATH) == -1) {
perror("client: writing path");
exit(1);
}
if (write(sockfd, &m, sizeof(int)) == -1) {
perror("client: writing mode");
exit(1);
}
if (write(sockfd, &r.size, sizeof(int)) == -1) {
perror("client: writing size");
exit(1);
}
if (write(sockfd, &r.hash, BLOCKSIZE) == -1) {
perror("client: writing hash");
exit(1);
}
if (r.type == TRANSFILE) {
FILE *out;
if ((out = fopen(src, "rb")) != NULL) {
int s = 0;
char byte;
while ((fread(&byte, 1, 1, out) == 1) && s++ < r.size) {
write(sockfd, &byte, 1);
}
fclose(out);
} else {
perror("client: fopen");
exit(1);
}
}
int status, len;
len = read(sockfd, &status, sizeof(int));
if (len != sizeof(int)) {
perror("client: reading from socket");
exit(1);
} else {
if (status == SENDFILE) {
pid_t pid = fork();
if (pid == 0) {
close(sockfd); // child will initate a new connection.
depth++;
rcopy_client(src, host, port, r_src, TRANSFILE);
} else {
fcalls++;
}
}
if (status == OK) {
// 如果是目录
if (S_ISDIR(properties.st_mode)) {
DIR *curr;
if ((curr = opendir(src)) == NULL) {
perror("client: opendir");
exit(1);
}
struct dirent *child;
// 递归调用目录
while ((child = readdir(curr)) != NULL) {
if (strncmp(child->d_name, ".", 1) != 0) {
pid_t pid = fork();
if (pid == 0) {
depth++;
char client_child_path[MAXPATH];
char server_child_path[MAXPATH];
complete_path(src, child->d_name, client_child_path);
complete_path(r_src, child->d_name, server_child_path);
close(sockfd);
rcopy_client(client_child_path, host, port, server_child_path, 0);
} else {
fcalls++;
}
}
}
}
// 文件的执行结果
printf("File:%s,size:%dkb has been send successfully.\n",r.path,r.size);
}
if (status == ERROR) {
fprintf(stderr, "client: error occured with %s\n", src);
final = 1;
}
}
}
close(sockfd);
int status;
char pid;
while (fcalls > 0) {
wait(&status);
if (WIFEXITED(status)) {
pid = WEXITSTATUS(status);
if (!final) {
final = pid;
}
} else {
perror("client: exit");
exit(1);
}
--fcalls;
}
if (depth == 0) {
return final;
} else {
exit(final);
}
}

42
ftree.h

@ -0,0 +1,42 @@
#ifndef _FTREE_H_
#define _FTREE_H_
#include <sys/stat.h>
#include "hash.h"
#define MAXPATH 128
#define MAXDATA 256
// Input states
#define AWAITING_TYPE 0
#define AWAITING_PATH 1
#define AWAITING_SIZE 2
#define AWAITING_PERM 3
#define AWAITING_HASH 4
#define AWAITING_DATA 5
// Request types
#define REGFILE 1
#define REGDIR 2
#define TRANSFILE 3
#define OK 0
#define SENDFILE 1
#define ERROR 2
#ifndef PORT
#define PORT 30100
#endif
struct request {
int type; // Request type is REGFILE, REGDIR, TRANSFILE
char path[MAXPATH];
mode_t mode;
char hash[BLOCKSIZE];
int size;
};
int rcopy_client(char *source, char *host, unsigned short port, char *r_src, int type);
void rcopy_server(unsigned short port, char *dest);
#endif // _FTREE_H_

11
hash.h

@ -0,0 +1,11 @@
#ifndef _HASH_H_
#define _HASH_H_
#define BLOCKSIZE 8
// Hash manipulation helper functions
char *hash(char *hash_val, FILE *f);
void show_hash(char *hash_val);
int check_hash(const char *hash1, const char *hash2);
#endif // _HASH_H_

38
hash_functions.c

@ -0,0 +1,38 @@
#include <stdio.h>
#include <stdlib.h>
#define BLOCK_SIZE 8
char *hash(char *hash_val, FILE *f) {
char ch;
int hash_index = 0;
for (int index = 0; index < BLOCK_SIZE; index++) {
hash_val[index] = '\0';
}
while(fread(&ch, 1, 1, f) != 0) {
hash_val[hash_index] ^= ch;
hash_index = (hash_index + 1) % BLOCK_SIZE;
}
return hash_val;
}
int check_hash(const char *hash1, const char *hash2) {
for (long i = 0; i < BLOCK_SIZE; i++) {
if (hash1[i] != hash2[i]) {
// printf("Index %ld: %c\n", i, hash1[i]);
return 1;
}
}
return 0;
}
void show_hash(char *hash_val) {
for(int i = 0; i < BLOCK_SIZE; i++) {
printf("%.2hhx ", hash_val[i]);
}
printf("\n");
}

28
rcopy_client.c

@ -0,0 +1,28 @@
#include <stdio.h>
#include <libgen.h>
#include <string.h>
#include "ftree.h"
#ifndef PORT
#define PORT 30000
#endif
int main(int argc, char **argv) {
/* Note: In most cases, you'll want HOST to be localhost or 127.0.0.1, so
* you can test on your local machine.*/
if (argc != 3) {
printf("Usage:\n\trcopy_client SRC HOST\n");
printf("\t SRC - The file or directory to copy to the server\n");
printf("\t HOST - The hostname of the server\n");
return 1;
}
if (rcopy_client(argv[1], argv[2], PORT, basename(argv[1]), 0) != 0) {
printf("Errors encountered during copy\n");
return 1;
} else {
printf("Copy completed successfully\n");
return 0;
}
}

76
rcopy_server.c

@ -0,0 +1,76 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <libgen.h>
#include <string.h>
#include <sys/stat.h>
#include <errno.h>
#include "ftree.h"
#ifndef PORT
#define PORT 30000
#endif
int main(int argc, char **argv) {
if(argc != 2) {
printf("Usage:\n\t%s rcopy_server PATH_PREFIX\n", argv[0]);
printf("\t PATH_PREFIX - The absolute path on the server that is used as the path prefix\n");
printf("\t for the destination in which to copy files and directories.\n");
exit(1);
}
/* NOTE: The directory PATH_PREFIX/sandbox/dest will be the directory in
* which the source files and directories will be copied. It therefore
* needs rwx permissions. The directory PATH_PREFIX/sandbox will have
* write and execute permissions removed to prevent clients from trying
* to create files and directories above the dest directory.
*/
// create the sandbox directory
char path[MAXPATH];
strncpy(path, argv[1], MAXPATH);
strncat(path, "/", MAXPATH - strlen(path) + 1);
strncat(path, "sandbox", MAXPATH - strlen(path) + 1);
if(mkdir(path, 0700) == -1){
if(errno != EEXIST) {
fprintf(stderr, "couldn't open %s\n", path);
perror("mkdir");
exit(1);
}
}
// create the dest directory
strncat(path, "/", MAXPATH - strlen(path) + 1);
strncat(path, "dest", MAXPATH - strlen(path) + 1);
if(mkdir(path, 0700) == -1){
if(errno != EEXIST) {
fprintf(stderr, "couldn't open %s\n", path);
perror("mkdir");
exit(1);
}
}
// 输出同步服务设置
printf("serve port:%d\n",PORT);
// change into the dest directory.
chdir(path);
// remove write and access perissions for sandbox
if(chmod("..", 0400) == -1) {
perror("chmod");
exit(1);
}
/* IMPORTANT: All path operations in rcopy_server must be relative to
* the current working directory.
*/
rcopy_server(PORT, path);
// Should never get here!
fprintf(stderr, "Server reached exit point.");
printf("here\n");
return 1;
}

380
server.c

@ -0,0 +1,380 @@
#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);
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);
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);
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 = &top; *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;
}
}
}
Loading…
Cancel
Save