#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <stdbool.h>
#define PORT 9000
#define HOST "127.0.0.1"
#define BUF_SIZE 2048
#define BREAK 1
#define CONTINUE 2
char *get_current_dir_name(void);
int server_sock;
pid_t pid;
/* Signal Handler for SIGINT */
void sigintHandler(int sig_num);
static inline void die(char const *msg);
int write_all(int target_sock, char *buffer, size_t size);
void send_file(char *path);
int parse(char *buffer, ssize_t length);
void read_stdin();
_Noreturn void read_server_sock();
int main() {
setvbuf(stdout, NULL, _IONBF, 0);
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons(PORT),
.sin_addr.s_addr = inet_addr(HOST)
};
if ((server_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
die("Could not open socket");
if (connect(server_sock, (struct sockaddr *) &addr, sizeof(addr)) < 0)
die("Could not connect to socket");
signal(SIGINT, &sigintHandler);
pid = fork();
if (pid == 0) {
read_stdin();
} else {
read_server_sock();
}
close(server_sock);
kill(pid, SIGKILL);
return 0;
}
void receive_file(int recv_sock, char *path, size_t size, char *start_buf, size_t start_buf_len) {
char buffer[BUF_SIZE];
int file_fd;
int received;
if ((file_fd = open(path, (O_WRONLY | O_CREAT | O_TRUNC), 0644)) == -1) { perror("Open"); }
if (size == 0) {
// just touch
close(file_fd);
return;
}
received = 0;
if (start_buf) {
// there was part of the file already in the "packet" which was supposed to contain only the header
// put that into the file too
if (write_all(file_fd, start_buf, start_buf_len) != start_buf_len) {
perror("write start buf");
printf("\n");
return;
}
received += start_buf_len;
}
/* Read all data */
while (received < size) {
int nread;
if ((nread = read(recv_sock, buffer, BUF_SIZE)) > 0) {
if (write_all(file_fd, buffer, nread) != nread) {
break;
}
received += nread;
}
}
if (received == size) {
printf("successfully got remote file \"%s/%s\"", get_current_dir_name(), path);
printf("\n");
} else {
printf("error getting remote file \"%s/%s\"", get_current_dir_name(), path);
printf("\n");
}
close(file_fd);
}
bool check_get(char *buffer) {
// "<<!bytecount!name"
char cmd[4];
memcpy(cmd, buffer, 3);
cmd[3] = 0;
if (strcmp(cmd, "<<!") == 0) {
//printf("[c]: \"%s\"\n", buffer);
int offset = 3;
int i = offset;
char rest[BUF_SIZE - 3];
size_t file_size;
for (; i < BUF_SIZE; ++i) {
if (buffer[i] == '!')
break;
rest[i - offset] = buffer[i];
}
rest[i - offset] = 0;
file_size = atoi(rest);
offset = ++i;
for (; i < BUF_SIZE; ++i) {
if (buffer[i] == '!')
break;
rest[i - offset] = buffer[i];
}
rest[i - offset] = 0;
char path[strlen(rest) + 1];
memcpy(path, rest, strlen(rest));
path[strlen(rest)] = 0;
receive_file(server_sock, path, file_size, &(buffer[i+2]), strlen(&(buffer[i+2])));
return true;
}
return false;
}
void read_server_sock() {
char server_buffer[BUF_SIZE];
while (1) {
ssize_t length;
if ((length = read(server_sock, server_buffer, BUF_SIZE)) > 0) {
server_buffer[length] = '\0';
// get issued, as it seems
if (check_get(server_buffer)) {
printf("$> ");
fflush(stdout);
continue;
}
printf("%s", server_buffer);
if (length == 1 && server_buffer[length - 1] == '\n') {
printf("$> ");
fflush(stdout);
}
}
}
}
void read_stdin() {
char stdin_buffer[BUF_SIZE];
bool done = false;
while (!done) {
ssize_t length;
memset(stdin_buffer, 0, BUF_SIZE);
if ((length = read(STDIN_FILENO, stdin_buffer, BUF_SIZE)) > 0) {
switch (parse(stdin_buffer, length)) {
case CONTINUE:
continue;
case BREAK:
done = true;
break;
default:
break;
}
}
}
}
void sigintHandler(int sig_num) {
errno = 0;
if (fcntl(server_sock, F_GETFD) != -1 || errno != EBADF) {
sig_num = write(server_sock, "exit\n", 6);
(void) sig_num;
close(server_sock);
}
errno = 0;
die("Terminating client\n");
}
static inline void die(char const *msg) {
perror(msg);
kill(pid, SIGKILL);
exit(-1);
}
int write_all(int target_sock, char *buffer, size_t size) {
int sent = 0;
while (sent < size) {
int nwrite;
if ((nwrite = write(target_sock, buffer + sent, size - sent)) < 0) {
die("Write");
}
sent += nwrite;
}
return sent;
}
void send_file(char *path) {
printf("Trying to send \"%s\" to remote...\n", path);
char buffer[BUF_SIZE];
memset(buffer, 0, BUF_SIZE);
int tmp_fd, file_fd;
if ((tmp_fd = open(path, O_RDONLY)) == -1) {
perror("Open");
return;
}
FILE *file = fdopen(tmp_fd, "r");
fseek(file, 0L, SEEK_END);
long int sz = ftell(file);
fseek(file, 0L, SEEK_SET);
fclose(file);
close(tmp_fd);
if ((file_fd = open(path, O_RDONLY)) == -1) {
perror("Open");
return;
}
int length = snprintf(NULL, 0, "%ld", sz);
sprintf(buffer, "<<!%ld!", sz);
sprintf(&(buffer[3 + length + 1]), "%s!\n", path);
//printf("[c] \"%s\"\n", buffer);
if (write(server_sock, buffer, strlen(buffer)) != strlen(buffer)) {
perror("write header");
return;
}
memset(buffer, 0, BUF_SIZE);
errno = 0;
size_t count;
while ((count = read(file_fd, buffer, sizeof(buffer))) > 0) {
printf("Sent %i bytes\n", write_all(server_sock, buffer, count));
}
if (errno)
perror("wad");
close(file_fd);
printf("done.\n");
}
int parse(char *buffer, ssize_t length) {
char cmd[4];
char path[BUF_SIZE - 3];
memcpy(cmd, buffer, 3);
cmd[3] = 0;
memcpy(path, buffer + 4, BUF_SIZE - 4);
path[strlen(path) - 1] = 0;
if (strcmp(cmd, "put") == 0) {
if (strlen(path) == 0) {
fprintf(stderr, "path missing\n");
} else {
send_file(path);
}
return CONTINUE;
}
buffer[length] = '\0';
if (strspn(buffer, " \n\t") == strlen(buffer)) {
// skip empty lines - empty being just spaces or tabs
printf("$> ");
return CONTINUE;
}
if (write(server_sock, buffer, strlen(buffer)) < 0)
die("Could not send message");
// TODO: exit should only close the connection (cleanly), NOT terminate the server
if (strcmp(buffer, "exit\n") == 0) {
return BREAK;
}
return 0;
}