1
0

proj: Add exam-2020W-2A

This commit is contained in:
2025-05-05 12:05:50 +02:00
parent 1e40a1b3c6
commit 57405421fa
9 changed files with 1006 additions and 0 deletions

View File

@@ -0,0 +1,37 @@
##
# Makefile for server and client libs.
#
# config
DEFS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_SVID_SOURCE -D_POSIX_C_SOURCE=200809 -D_XOPEN_SOURCE
override CFLAGS += -Wall -g -std=c99 -pedantic $(DEFS)
override LDFLAGS +=
override LIBS += -pthread -lrt
# objects for the server to build
OBJS = server_lib.o
# rules
all: $(OBJS) client osue-tool
make -f Makefile.student all
client: client.o
gcc $(LDFLAGS) -o $@ $^ $(LIBS)
osue-tool: osue-tool.c
gcc -o $@ $^
%.o: %.c
gcc $(CFLAGS) -c -o $@ $<
strip -S $@
clean:
make -f Makefile.student clean
rm -f client client.o $(OBJS) osue-tool
distclean: clean
rm -rf *.txt __pycache__

View File

@@ -0,0 +1,38 @@
##
# Makefile for server.
#
# config
DEFS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_SVID_SOURCE -D_POSIX_C_SOURCE=200809 -D_XOPEN_SOURCE
override CFLAGS += -Wall -g -std=c99 -pedantic $(DEFS)
override LDFLAGS +=
override LIBS += -pthread -lrt
# objects to build
OBJS = server.o
# objects to link (already built)
LDOBJS = server_lib.o
# rules
.PHONY : all clean
all: server
server: $(OBJS) $(LDOBJS)
gcc $(LDFLAGS) -o $@ $^ $(LIBS)
%.o: %.c
gcc $(CFLAGS) -c -o $@ $<
clean:
rm -f server $(OBJS)
# dependencies
server.o: server.c

View File

@@ -0,0 +1,114 @@
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <unistd.h>
#define BUFSIZE 1024
/** Name of the executable (for printing messages). */
char *program_name = "<not yet set>";
/** Structure for the arguments. */
struct arguments {
const char *port_str;
const char *argument;
};
/** Print a usage message and exit. */
void usage(const char *msg) {
fprintf(stderr, "Usage: %s [-p PORT] ARGUMENT\n%s\n", program_name, msg);
exit(EXIT_FAILURE);
}
void print_message(const char *msg) {
fprintf(stderr, "[%s] %s\n", program_name, msg);
}
/** Print an error message and exit with EXIT_FAILURE. */
void error_exit(const char *msg) {
if (errno == 0)
fprintf(stderr, "[%s] [ERROR] %s\n", program_name, msg);
else
fprintf(stderr, "[%s] [ERROR] %s - %s\n", program_name, msg,
strerror(errno));
exit(EXIT_FAILURE);
}
/** Parse arguments. */
void parse_arguments(int argc, char *argv[], struct arguments *args) {
memset(args, 0, sizeof(*args));
int opt;
while ((opt = getopt(argc, argv, "p:")) != -1) {
switch (opt) {
case 'p':
if (args->port_str)
usage("-p option allowed only once");
args->port_str = optarg;
break;
default:
usage("invalid option");
}
}
if (args->port_str == NULL)
args->port_str = "2020";
if (argc - optind != 1)
usage("invalid number of arguments");
args->argument = argv[optind];
}
FILE *setup_connection(const char *port_str) {
struct addrinfo hints, *ai;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
int res = getaddrinfo("localhost", port_str, &hints, &ai);
if (res != 0)
error_exit("getaddrinfo()");
int connfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (connfd < 0)
error_exit("socket()");
if (connect(connfd, ai->ai_addr, ai->ai_addrlen) < 0)
error_exit("connect()");
freeaddrinfo(ai);
return fdopen(connfd, "w+");
}
int main(int argc, char *argv[]) {
struct arguments args;
program_name = argv[0];
parse_arguments(argc, argv, &args);
FILE *fp = setup_connection(args.port_str);
fprintf(fp, "%s", args.argument);
fflush(fp);
shutdown(fileno(fp), SHUT_WR);
char *lineptr = NULL;
size_t n = 0;
print_message("reading response:");
while (getline(&lineptr, &n, fp) != -1) {
fprintf(stdout, "[%s] >>> %s", program_name, lineptr);
fflush(stdout);
}
fprintf(stderr, "\n");
print_message("exiting regularly");
return 0;
}

View File

@@ -0,0 +1,25 @@
/*
* Take a single string as an argument and format it into 32-character blocks.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv) {
if (argc != 2) {
fprintf(stderr, "invalid number of arguments\n");
exit(1);
}
int i;
for (i = 0; i < strlen(argv[1]); i++) {
int c = argv[1][i];
printf("%c", c);
if (i % 32 == 31)
printf("\n");
}
if (i % 32 != 0)
printf("\n");
return 0;
}

View File

@@ -0,0 +1,171 @@
#include "server.h"
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <netdb.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
char *program_name = "<not yet set>";
/*
* Signal handler and quit flag for synchronization loop.
* DO NOT CHANGE THIS!
*/
volatile sig_atomic_t quit = 0;
static void signal_handler(int sig) {
quit = 1;
}
/*
* Prints a usage message and exits.
* @param msg Additional output message.
*/
void usage(const char *msg) {
fprintf(stderr, "Usage: %s [-p PORT] COMMAND\n%s\n", program_name, msg);
exit(EXIT_FAILURE);
}
/*
* Print an error message and exit with EXIT_FAILURE.
* @param msg Additional output message.
*/
void error_exit(const char *msg) {
if (errno == 0)
fprintf(stderr, "[%s] [ERROR] %s\n", program_name, msg);
else
fprintf(stderr, "[%s] [ERROR] %s - %s\n", program_name, msg,
strerror(errno));
exit(EXIT_FAILURE);
}
/************************************************************************
* Task 1 - Setup the connection for the server
*
* Create a passive socket of domain AF_INET and type SOCK_STREAM.
* Listen for connections on the port given by the argument `port_str'.
* Return the file descriptor of the communication socket.
*
* WARNING: The communication socket is NOT the connection socket,
* DO NOT USE accept(2) IN THIS FUNCTION!
*
* @param port_str The port string.
* @return File descriptor of the communication socket.
*
* Hints: getaddrinfo(3), socket(2), listen(2)
************************************************************************/
int setup_connection(const char *port_str) {
// put your code here
// Replace with a meaningful return value
return -1;
}
/*******************************************************************************
* Task 2 - Handle client connections
*
* Wait for a connection on the communication socket and accept it.
*
* Read the argument transmitted by the client from the connection and save it
* to a buffer that can hold a C-string with up to MAX_ARGUMENT_LEN characters.
*
* Call execute_command() (or the respective DEMO function) with the command
* and the argument.
*
* Read the result from the file pointer returned by execute_command() and send
* it back to the client. Send the string "COMMAND_FAILED" if execute_command()
* fails.
*
* @param sockfd File descriptor of the communication socket.
* @param command Command to execute.
*
* Hints: accept(2), fdopen(3), fgets(3), fprintf(3), fclose(3)
******************************************************************************/
void server_request_handler(int sockfd, const char *command) {
// put your code here
}
/*******************************************************************************
* Task 3 - Execute command
*
* Create a pipe for redirection of standard output of the child process.
*
* Fork a child process, close stdin of the new process and redirect stdout to
* the pipe.
*
* Execute the command in the variable 'command' with the single argument in
* the variable 'argument'.
*
* Wait for the child process to finish. If the child process exits with
* exit-status 0, create a FILE object for the read-end of the pipe and return
* it. Return NULL if the child process fails.
*
* Do not read from the pipe in this function.
*
* @param command The command that will be executed.
* @param argument The argument for the command.
*
* @return Address of the FILE-object for the read end of the child's standard
* output pipe.
*
* Hints: pipe(2), dup2(2), fork(2), exec(3), fdopen(3), wait(3)
******************************************************************************/
FILE *execute_command(const char *command, const char *argument) {
// put your code here
// Replace with a meaningful return value
return NULL;
}
/*
* Main program
* @param argc Number of elements in argv
* @param argv Array of command line arguments
* @return EXIT_SUCCESS if everything is okay
*/
int main(int argc, char *argv[]) {
// DO NOT CHANGE THE FOLLOWING DECLARATIONS
int sockfd = -1;
struct arguments args; // struct for storing the parsed arguments
program_name = argv[0];
// Register signal handlers
struct sigaction s;
s.sa_handler = signal_handler;
s.sa_flags = 0; // no SA_RESTART!
if (sigfillset(&s.sa_mask) < 0) {
error_exit("sigfillset");
}
if (sigaction(SIGINT, &s, NULL) < 0) {
error_exit("sigaction SIGINT");
}
if (sigaction(SIGTERM, &s, NULL) < 0) {
error_exit("sigaction SIGTERM");
}
// register function to free resources at normal process termination
if (atexit(free_resources) == -1) {
error_exit("atexit failed");
}
parse_arguments(argc, argv, &args);
sockfd = setup_connection(args.port_str);
//sockfd = DEMO_setup_connection(args.port_str);
while (!quit) {
server_request_handler(sockfd, args.command);
//DEMO_server_request_handler(sockfd, args.command, 0); // internally calls execute_command()
//DEMO_server_request_handler(sockfd, args.command, USE_DEMO); // internally calls DEMO_execute_command()
}
(void)fprintf(stderr, "server exiting regularly\n");
return EXIT_SUCCESS;
}

View File

@@ -0,0 +1,33 @@
/**
* @brief Declarations used by the server application.
**/
#ifndef _SERVE_H_
#define _SERVE_H_
#include <stdio.h>
#define USE_DEMO 0x01
#define MAX_ARGUMENT_LEN 1024
/** Structure for the arguments. */
struct arguments {
const char *port_str;
const char *command;
};
void parse_arguments(int argc, char *argv[], struct arguments *args);
void free_resources(void);
// forward declaration of student functions.
int setup_connection(const char *port_str);
void server_request_handler(int sockfd, const char *command);
FILE *execute_command(const char *command, const char *argument);
// demo solutions
int DEMO_setup_connection(const char *port_str);
void DEMO_server_request_handler(int sockfd, const char *command, int flags);
FILE *DEMO_execute_command(const char *command, const char *argument);
#endif

View File

@@ -0,0 +1,260 @@
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <netdb.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include "server.h"
extern char *program_name;
extern volatile sig_atomic_t quit;
/*
* Prints a usage message and exits.
* @param msg Additional output message.
*/
static void usage(const char *msg) {
fprintf(stderr, "Usage: %s [-p PORT] COMMAND\n%s\n", program_name, msg);
exit(EXIT_FAILURE);
}
/*
* Print an error message and exit with EXIT_FAILURE.
* @param msg Additional output message.
*/
static void error_exit(const char *msg) {
if (errno == 0)
fprintf(stderr, "[%s] [ERROR] %s\n", program_name, msg);
else
fprintf(stderr, "[%s] [ERROR] %s - %s\n", program_name, msg,
strerror(errno));
exit(EXIT_FAILURE);
}
static void print_error(const char *msg) {
fprintf(stderr, "[%s] [ERROR] %s - %s\n", program_name, msg,
strerror(errno));
}
static void print_message(const char *msg) {
fprintf(stderr, "[%s] %s\n", program_name, msg);
}
/**
* @brief Function freeing the resources.
*/
void free_resources(void) {
}
/*
* Parse Arguments
*/
void parse_arguments(int argc, char *argv[], struct arguments *args) {
memset(args, 0, sizeof(*args));
int opt;
while ((opt = getopt(argc, argv, "p:")) != -1) {
switch (opt) {
case 'p':
if (args->port_str)
usage("-p option allowed only once");
args->port_str = optarg;
break;
default:
usage("invalid option");
}
}
if (args->port_str == NULL)
args->port_str = "2020";
if (argc - optind != 1)
usage("invalid number of arguments");
args->command = argv[optind];
}
/*
* Custom getaddrinfo implementation for localhost only.
*/
int getaddrinfo(const char *node, const char *service,
const struct addrinfo *hints, struct addrinfo **res) {
struct in_addr localhost;
localhost.s_addr = htonl(0x7f000001); // 127.0.0.1
// check hostname (allow only `localhost' or any string which `inet_aton()'
// converts to 127.0.0.1)
struct in_addr node_addr;
if (node != NULL &&
(inet_aton(node, &node_addr) == 0 ||
node_addr.s_addr != localhost.s_addr) &&
strcmp(node, "localhost") != 0)
return EAI_NONAME;
// check port (allow only numerical ports)
char *endptr;
int port = strtol(service, &endptr, 10);
if (*endptr != '\0')
return EAI_NONAME;
// check hints (NULL is acceptable)
if (hints != NULL) {
// check family
if (hints->ai_family != AF_UNSPEC && hints->ai_family != AF_INET)
return EAI_FAMILY;
// check socket type
if (hints->ai_socktype != 0 && hints->ai_socktype != SOCK_STREAM)
return EAI_SOCKTYPE;
// check protocol
if (hints->ai_protocol != 0 && hints->ai_protocol != IPPROTO_TCP)
return EAI_SOCKTYPE;
}
// allocate memory for result
*res = malloc(sizeof(struct addrinfo));
struct sockaddr_in *sin = malloc(sizeof(struct sockaddr_in));
if (*res == NULL || sin == NULL)
return EAI_MEMORY;
memset(sin, 0, sizeof(struct sockaddr_in));
sin->sin_family = AF_INET;
sin->sin_addr = localhost;
sin->sin_port = htons(port);
(*res)->ai_flags = (AI_V4MAPPED | AI_ADDRCONFIG);
(*res)->ai_family = AF_INET;
(*res)->ai_socktype = SOCK_STREAM;
(*res)->ai_protocol = IPPROTO_TCP;
(*res)->ai_addrlen = sizeof(struct sockaddr_in);
(*res)->ai_addr = (struct sockaddr *)sin;
(*res)->ai_canonname = NULL;
(*res)->ai_next = NULL;
return 0;
}
/******************************************************************************
* DEMO FUNCTIONS
*****************************************************************************/
int DEMO_setup_connection(const char *port_str) {
struct addrinfo hints, *ai;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
int res = getaddrinfo(NULL, port_str, &hints, &ai);
if (res != 0)
error_exit("getaddrinfo() failed");
int sockfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (sockfd < 0)
error_exit("socket() failed");
int reuse = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
error_exit("setsockopt() failed");
if (bind(sockfd, ai->ai_addr, ai->ai_addrlen) < 0)
error_exit("bind() failed");
freeaddrinfo(ai);
if (listen(sockfd, 1) < 0)
error_exit("listen() failed");
return sockfd;
}
void DEMO_server_request_handler(int sockfd, const char *command, int flags) {
int connfd = accept(sockfd, NULL, NULL);
if (connfd < 0)
error_exit("accept() failed");
print_message("Accepted connection");
FILE *fps = fdopen(connfd, "w+");
char argument[MAX_ARGUMENT_LEN + 1] = {0};
fgets(argument, sizeof(argument), fps);
FILE *fpc = NULL;
if (flags & USE_DEMO) {
print_message("Calling DEMO_execute_command()");
fpc = DEMO_execute_command(command, argument);
} else {
print_message("Calling execute_command()");
fpc = execute_command(command, argument);
}
if (fpc) {
int c;
while ((c = fgetc(fpc)) != EOF)
fputc(c, fps);
fclose(fpc);
} else {
fputs("COMMAND_FAILED", fps);
}
fflush(fps);
fclose(fps);
}
FILE *DEMO_execute_command(const char *command, const char *argument) {
// file stream to read the output of the process
FILE *proc_output = NULL;
// file descriptors for the pipe for parent-child process communication
int c2p[2];
// create the pipes
if (pipe(c2p) == -1)
error_exit("cannot create pipe");
pid_t pid = fork();
switch (pid) {
case 0:
// duplicate write end to stdout
if (dup2(c2p[1], STDOUT_FILENO) == -1) {
error_exit("dup2() failed");
}
close(c2p[0]);
close(STDIN_FILENO);
// exec the command
if (execlp(command, command, argument, NULL) == -1) {
error_exit("exec() failed");
}
case -1:
error_exit("fork() failed");
default:
// parent process
proc_output = fdopen(c2p[0], "r");
if (proc_output == NULL) {
error_exit("fdopen() failed");
}
close(c2p[1]);
int stat = 0;
wait(&stat);
if (WIFEXITED(stat) && WEXITSTATUS(stat) == 0) {
print_message("child process exited normally");
} else {
print_error("child process exited abnormally");
proc_output = NULL;
}
}
return proc_output;
}