From 57405421fa7d6804ac4c9b0381fcb3c2e426e3a8 Mon Sep 17 00:00:00 2001 From: Lorenz Stechauner Date: Mon, 5 May 2025 12:05:50 +0200 Subject: [PATCH] proj: Add exam-2020W-2A --- .gitignore | 1 + proj/exam-2020W-2A/check.py | 327 ++++++++++++++++++++++++ proj/exam-2020W-2A/src/Makefile | 37 +++ proj/exam-2020W-2A/src/Makefile.student | 38 +++ proj/exam-2020W-2A/src/client.c | 114 +++++++++ proj/exam-2020W-2A/src/osue-tool.c | 25 ++ proj/exam-2020W-2A/src/server.c | 171 +++++++++++++ proj/exam-2020W-2A/src/server.h | 33 +++ proj/exam-2020W-2A/src/server_lib.c | 260 +++++++++++++++++++ 9 files changed, 1006 insertions(+) create mode 100644 proj/exam-2020W-2A/check.py create mode 100644 proj/exam-2020W-2A/src/Makefile create mode 100644 proj/exam-2020W-2A/src/Makefile.student create mode 100644 proj/exam-2020W-2A/src/client.c create mode 100644 proj/exam-2020W-2A/src/osue-tool.c create mode 100644 proj/exam-2020W-2A/src/server.c create mode 100644 proj/exam-2020W-2A/src/server.h create mode 100644 proj/exam-2020W-2A/src/server_lib.c diff --git a/.gitignore b/.gitignore index 6728c13..69a6186 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ bin/ *.so *.o *.log +*.pdf diff --git a/proj/exam-2020W-2A/check.py b/proj/exam-2020W-2A/check.py new file mode 100644 index 0000000..027fcfc --- /dev/null +++ b/proj/exam-2020W-2A/check.py @@ -0,0 +1,327 @@ +import contextlib +import logging +import multiprocessing +import os +import random +import signal +import socket +import string +import struct +import subprocess +import sys +import tempfile +import traceback + +from libosue import * +from libosue import CSnippet as C +from libosue import FunctionPrototype as FP + + +################################################################################ +# INITIALIZE THE EXAM OBJECT +################################################################################ + +# These functions are not allowed. +not_allowed = [ + "DEMO_setup_connection", + "DEMO_server_request_handler", + "DEMO_execute_command", +] + +prototypes = { + "parse_arguments": FP("void")("int argc", "char **argv", "struct arguments *args"), + "DEMO_setup_connection": FP("int")("const char *port_str"), + "DEMO_server_request_handler": FP("void")("int sockfd", "const char *command", "int flags"), + "DEMO_execute_command": FP("FILE *")("const char *command", "const char *argument"), +} + +PORT = 692 + +exam.initialize( + "./server", + server_port=7220, + not_allowed=not_allowed + exam.DEFAULT_RESTRICTED_FUNCTIONS, + call_limit=200, + prototypes=prototypes, + report_file="report.txt", + result_file="result.txt", + debug_file="libosue.dbg", + make_targets=["clean", "all"], + initialize_stack=["setup_connection", "server_request_handler", "execute_command"], +) + + +class FunctionAborted(EvalError): + """Raised when a function should be aborted.""" + + pass + + +# Kill programs on port 692. +# This is slow, so we only do it once every deliver call and not for every session. +utils.free_port(PORT) + + +################################################################################ +# TASK 1: SETUP CONNECTION +################################################################################ + + +def bind_cb(s, args): + """Call setsockopt(...) before binding.""" + reuse = Value.from_type("char") + reuse.value = 1 + s.c.setsockopt(args["sockfd"], socket.SOL_SOCKET, socket.SO_REUSEADDR, reuse.address, 1) + + +with exam.task("Task 1: setup connection", 10) as task: + with task.subtask("initialize server socket", 8) as st: + with exam.session([]) as s: + # Setup tracing for hints. + s.c.socket.tracing = True + s.c.bind.tracing = True + s.c.listen.tracing = True + + # Make sure setsockopt(...) is called before bind(). + s.c.bind.call_cb = bind_cb + + # Call the student's setup_connection connection function + sockfd = s.c.setup_connection(str(PORT).encode()) + + # Check if the server socket works and creates valid connection sockets. + utils.check_server_socket(s, sockfd, PORT) + + # Give the students some hints for their implementation + if st.points != 8: + if "socket" not in s.trace: + exam.log("[INFO] socket() was never called") + if "bind" not in s.trace: + exam.log("[INFO] bind() was never called") + if "listen" not in s.trace: + exam.log("[INFO] listen() was never called") + + exam.log("The following subtasks are skipped because of errors:") + exam.log("* error handling (2 points)") + task.skip_subtasks() + + with task.subtask("error handling", 2) as st: + with st.subtask("bind()", 1): + with exam.session([]) as s: + s.c.listen.tracing = True + s.c.bind.override_return = C("((int) -1)") + + with exam.raises(ExitError, "program did not exit after bind() failed") as excinfo: + sockfd = s.c.setup_connection(str(PORT).encode()) + assert excinfo.value.exitcode == 1, "wrong exit code" + assert "listen" not in s.trace, "listen() called after bind() failed" + + with st.subtask("listen()", 1): + with exam.session([]) as s: + s.c.listen.override_return = C("((int) -1)") + + with exam.raises(ExitError, "program did not exit after listen() failed") as excinfo: + sockfd = s.c.setup_connection(str(PORT).encode()) + assert excinfo.value.exitcode == 1, "wrong exit code" + + +with exam.task("Task 2: handle requests", 20) as task: + with task.subtask("read argument and execute command", 7) as st: + # Check if the program correctly accepts a connection and reads the argument + # from the connection socket. Execution stops when task 3 is called. + def accept_return_cb(s, rv): + rv = rv.convert() + if rv > 0: + s["connfd"] = rv + + def read_call_cb(s, args): + connfd = args.get("sockfd") or args.get("fd") + s["read"] = connfd.convert() == s["connfd"] + s["buffer"] = args.get("buf") + + def read_return_cb(s, rv): + if s["read"]: + n = rv.convert() + s["result"] += s["buffer"].convert(shape=n, cast="char *") + + def execute_command_cb(s, args): + s["ec_called"] = True + try: + s["command"] = args["command"].convert() + s["argument"] = args["argument"].convert() + except InvalidMemoryError: + pass + raise FunctionAborted("This is where execution ends.") + + with exam.session([]) as s: + s.c.accept.return_cb = accept_return_cb + s.c.read.call_cb = read_call_cb + s.c.read.return_cb = read_return_cb + s.c.recv.call_cb = read_call_cb + s.c.recv.return_cb = read_return_cb + + s.c.execute_command.call_cb = execute_command_cb + s.c.DEMO_execute_command.call_cb = execute_command_cb + s.c.DEMO_execute_command.allowed = True + + # Setup the tracing variables. + s["connfd"] = None + s["result"] = b"" + s["ec_called"] = False + s["command"] = None + s["argument"] = None + + argument = "".join(random.choices(string.ascii_letters, k=24)).encode() + sockfd = s.c.DEMO_setup_connection(str(PORT).encode()) + with utils.SingleShotClient(PORT, argument) as client: + with contextlib.suppress(FunctionAborted): + s.c.server_request_handler(sockfd, b"./osue-tool") + assert s["result"], "no argument read from client" + assert s["result"] == argument, "read wrong argument from client" + assert s["ec_called"], "execute_command() not called" + assert s["command"] == b"./osue-tool", "invalid command passt to execute_command()" + assert ( + s["argument"] == argument + ), "Invalid argument passed to execute_command()\nDid you terminate it?" + + with task.subtask("response", 8) as st: + # Execute the full function and check if the client receives the correct result. + def execute_command_cb(s, args): + with s.c.fork.save(): + s.c.fork.allowed = True + return s.c.DEMO_execute_command(args["command"], args["argument"]) + + with st.subtask("single line output", 4) as pt: + with exam.session([]) as s: + s.c.execute_command.call_cb = execute_command_cb + s.c.DEMO_execute_command.call_cb = execute_command_cb + s.c.DEMO_execute_command.allowed = True + + sockfd = s.c.DEMO_setup_connection(str(PORT).encode()) + + argument = "".join(random.choices(string.ascii_letters, k=24)).encode() + expected = argument + b"\n" + with utils.SingleShotClient(PORT, argument) as client: + s.c.server_request_handler(sockfd, b"./osue-tool") + assert client.response, "didn't receive a response for single-line output" + assert client.response == expected, "received wrong response for single-line output" + + with st.subtask("multi line output", 4) as pt: + with exam.session([]) as s: + s.c.execute_command.call_cb = execute_command_cb + s.c.DEMO_execute_command.call_cb = execute_command_cb + s.c.DEMO_execute_command.allowed = True + + sockfd = s.c.DEMO_setup_connection(str(PORT).encode()) + + argument = "".join(random.choices(string.ascii_letters, k=128)).encode() + expected = b"\n".join(argument[i : i + 32] for i in range(0, len(argument), 32)) + b"\n" + with utils.SingleShotClient(PORT, argument) as client: + s.c.server_request_handler(sockfd, b"./osue-tool") + assert client.response, "didn't receive a response for multi-line output" + assert client.response == expected, "received wrong response for multi-line output" + + if task.points != 15: + exam.log("The following subtasks are skipped because of errors:") + exam.log("* repeated write/send calls (5 points)") + task.skip_subtasks() + + with task.subtask("repeated write/send calls", 5) as st: + + def accept_return_cb(s, rv): + s["connfd"] = rv.convert() + + def write_cb(s, args): + fd = (args.get("fd") or args.get("sockfd")).convert() + + if fd == s["connfd"]: + n = args.get("count") or args.get("len") + buf = args.get("buf") + s["messages"].append(buf.convert(shape=n.convert(), cast="char *")) + return 1 + + with exam.session([]) as s: + s.c.execute_command.call_cb = execute_command_cb + s.c.DEMO_execute_command.call_cb = execute_command_cb + s.c.DEMO_execute_command.allowed = True + + s.c.accept.return_cb = accept_return_cb + s.c.write.call_cb = write_cb + s.c.send.call_cb = write_cb + + s["connfd"] = None + s["messages"] = [] + + argument = "".join(random.choices(string.ascii_letters, k=24)).encode() + expected = argument + b"\n" + sockfd = s.c.DEMO_setup_connection(str(PORT).encode()) + with utils.SingleShotClient(PORT, argument) as client: + s.c.server_request_handler(sockfd, b"./osue-tool") + + assert len(s["messages"]) > 1, "write/send not called multiple times" + message = b"".join(m[:1] for m in s["messages"]) + assert message == expected, "received wrong response" + + +with exam.task("Task 3: execute command", 20) as task: + with task.subtask("fork/exec pattern", 5) as st: + pattern = utils.ForkNode(parent=None, child=utils.ExecNode("./osue-tool")) + with utils.ForkExecTraces(pattern) as tracegen: + for fork_exec_run in tracegen: + with exam.session() as s: + with fork_exec_run(s): + s.c.execute_command(b"./osue-tool", b"just a random string") + + assert tracegen.correct, tracegen.default_message + + if task.points != 5: + exam.log("The following subtasks are skipped because of errors:") + exam.log("* wait for child process to finish (5 points)") + exam.log("* result (10 points)") + task.skip_subtasks() + + with task.subtask("wait for child process to finish", 5) as st: + # Correctly wait for the child process to finish. + # Follow the parent process and trace the return value of fork() and wait(). + def fork_return_cb(s, rv): + s["fork_result"] = rv.convert() + + def wait_return_cb(s, rv): + s["wait_result"] = rv.convert() + + with exam.session([]) as s: + s.c.fork.allowed = True + s.c.fork.return_cb = fork_return_cb + s.c.wait.return_cb = wait_return_cb + s.c.waitpid.return_cb = wait_return_cb + s.c.execute_command(b"./osue-tool", b"just a random string") + + assert "wait_result" in s, "wait never called" + assert s.get("wait_result") == s.get("fork_result"), "program did not wait for child process" + + if task.points != 10: + exam.log("The following subtasks are skipped because of errors:") + exam.log("* result (10 points)") + task.skip_subtasks() + + with task.subtask("result", 10) as st: + # Handle a single request and check the result. + with exam.session([]) as s: + s.c.fork.allowed = True + + argument = "".join(random.choices(string.ascii_letters, k=24)).encode() + expected = argument + b"\n" + + fp = s.c.execute_command(b"./osue-tool", argument) + assert fp.intaddr(), "returned file pointer not valid" + + bufsize = 8192 + try: + buf = s.c.malloc(bufsize, cast="char *") + n = s.c.fread(buf, 1, bufsize, fp) + except FunctionKilledError: + st.fail("reading from returned file pointer never terminated") + except SignalError: + st.fail("reading from returned file pointer failed") + + result = buf.convert(shape=n.convert()) + assert result == expected, "invalid response" diff --git a/proj/exam-2020W-2A/src/Makefile b/proj/exam-2020W-2A/src/Makefile new file mode 100644 index 0000000..d6993d7 --- /dev/null +++ b/proj/exam-2020W-2A/src/Makefile @@ -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__ diff --git a/proj/exam-2020W-2A/src/Makefile.student b/proj/exam-2020W-2A/src/Makefile.student new file mode 100644 index 0000000..f0dd943 --- /dev/null +++ b/proj/exam-2020W-2A/src/Makefile.student @@ -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 diff --git a/proj/exam-2020W-2A/src/client.c b/proj/exam-2020W-2A/src/client.c new file mode 100644 index 0000000..be0c99b --- /dev/null +++ b/proj/exam-2020W-2A/src/client.c @@ -0,0 +1,114 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BUFSIZE 1024 + +/** Name of the executable (for printing messages). */ +char *program_name = ""; + +/** 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; +} diff --git a/proj/exam-2020W-2A/src/osue-tool.c b/proj/exam-2020W-2A/src/osue-tool.c new file mode 100644 index 0000000..4a0a24a --- /dev/null +++ b/proj/exam-2020W-2A/src/osue-tool.c @@ -0,0 +1,25 @@ +/* + * Take a single string as an argument and format it into 32-character blocks. + */ + +#include +#include +#include + +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; +} diff --git a/proj/exam-2020W-2A/src/server.c b/proj/exam-2020W-2A/src/server.c new file mode 100644 index 0000000..6d69143 --- /dev/null +++ b/proj/exam-2020W-2A/src/server.c @@ -0,0 +1,171 @@ +#include "server.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +char *program_name = ""; + +/* + * 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; +} diff --git a/proj/exam-2020W-2A/src/server.h b/proj/exam-2020W-2A/src/server.h new file mode 100644 index 0000000..69f7c6d --- /dev/null +++ b/proj/exam-2020W-2A/src/server.h @@ -0,0 +1,33 @@ +/** + * @brief Declarations used by the server application. + **/ + +#ifndef _SERVE_H_ +#define _SERVE_H_ + +#include + +#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 diff --git a/proj/exam-2020W-2A/src/server_lib.c b/proj/exam-2020W-2A/src/server_lib.c new file mode 100644 index 0000000..fa0c0e7 --- /dev/null +++ b/proj/exam-2020W-2A/src/server_lib.c @@ -0,0 +1,260 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +}