proj: Add exam-2020W-2A
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,3 +3,4 @@ bin/
|
||||
*.so
|
||||
*.o
|
||||
*.log
|
||||
*.pdf
|
||||
|
||||
327
proj/exam-2020W-2A/check.py
Normal file
327
proj/exam-2020W-2A/check.py
Normal file
@@ -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"
|
||||
37
proj/exam-2020W-2A/src/Makefile
Normal file
37
proj/exam-2020W-2A/src/Makefile
Normal 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__
|
||||
38
proj/exam-2020W-2A/src/Makefile.student
Normal file
38
proj/exam-2020W-2A/src/Makefile.student
Normal 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
|
||||
114
proj/exam-2020W-2A/src/client.c
Normal file
114
proj/exam-2020W-2A/src/client.c
Normal 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;
|
||||
}
|
||||
25
proj/exam-2020W-2A/src/osue-tool.c
Normal file
25
proj/exam-2020W-2A/src/osue-tool.c
Normal 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;
|
||||
}
|
||||
171
proj/exam-2020W-2A/src/server.c
Normal file
171
proj/exam-2020W-2A/src/server.c
Normal 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;
|
||||
}
|
||||
33
proj/exam-2020W-2A/src/server.h
Normal file
33
proj/exam-2020W-2A/src/server.h
Normal 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
|
||||
260
proj/exam-2020W-2A/src/server_lib.c
Normal file
260
proj/exam-2020W-2A/src/server_lib.c
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user