From d52d4fc3a9e88a74806174fbd195a5444d209cbc Mon Sep 17 00:00:00 2001 From: Athanasios Xygkis Date: Mon, 14 Sep 2020 08:55:52 +0200 Subject: [PATCH] Add C/C++ template --- template_cpp/.gitignore | 82 ++++++ template_cpp/CMakeLists.txt | 72 ++++++ template_cpp/bin/README | 1 + template_cpp/bin/deploy/README | 1 + template_cpp/bin/logs/README | 1 + template_cpp/build.sh | 13 + template_cpp/cleanup.sh | 7 + template_cpp/run.sh | 9 + template_cpp/src/CMakeLists.txt | 7 + template_cpp/src/include/barrier.hpp | 32 +++ template_cpp/src/include/parser.hpp | 357 +++++++++++++++++++++++++++ template_cpp/src/src/main.cpp | 85 +++++++ 12 files changed, 667 insertions(+) create mode 100644 template_cpp/.gitignore create mode 100644 template_cpp/CMakeLists.txt create mode 100644 template_cpp/bin/README create mode 100644 template_cpp/bin/deploy/README create mode 100644 template_cpp/bin/logs/README create mode 100755 template_cpp/build.sh create mode 100755 template_cpp/cleanup.sh create mode 100755 template_cpp/run.sh create mode 100644 template_cpp/src/CMakeLists.txt create mode 100644 template_cpp/src/include/barrier.hpp create mode 100644 template_cpp/src/include/parser.hpp create mode 100644 template_cpp/src/src/main.cpp diff --git a/template_cpp/.gitignore b/template_cpp/.gitignore new file mode 100644 index 0000000..b3b985b --- /dev/null +++ b/template_cpp/.gitignore @@ -0,0 +1,82 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/c,c++ +# Edit at https://www.toptal.com/developers/gitignore?templates=c,c++ +# + +bin/da_proc +target/ + +### C ### +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf + +### C++ ### +# Prerequisites + +# Compiled Object files +*.slo + +# Precompiled Headers + +# Compiled Dynamic libraries + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai + +# Executables + +# End of https://www.toptal.com/developers/gitignore/api/c,c++ diff --git a/template_cpp/CMakeLists.txt b/template_cpp/CMakeLists.txt new file mode 100644 index 0000000..139beec --- /dev/null +++ b/template_cpp/CMakeLists.txt @@ -0,0 +1,72 @@ +cmake_minimum_required(VERSION 3.9) +project(da_project) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +string(CONCAT CMAKE_CXX_FLAGS_COMMON_STR "" + "-Werror -Wall -Wconversion -Wfloat-equal " + "-Wpedantic -Wpointer-arith -Wswitch-default " + "-Wpacked -Wextra -Winvalid-pch " + "-Wmissing-field-initializers " + "-Wunreachable-code -Wcast-align -Wcast-qual " + "-Wdisabled-optimization -Wformat=2 " + "-Wformat-nonliteral -Wuninitialized " + "-Wformat-security -Wformat-y2k -Winit-self " + "-Wmissing-declarations -Wmissing-include-dirs " + "-Wredundant-decls -Wstrict-overflow=5 -Wundef " + "-Wno-unused -Wctor-dtor-privacy -Wsign-promo " + "-Woverloaded-virtual -Wold-style-cast") + +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + string(CONCAT CMAKE_CXX_FLAGS_STR "${CMAKE_CXX_FLAGS_COMMON_STR} " + "-Wlogical-op -Wstrict-null-sentinel -Wnoexcept") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS_STR}") +elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + string(CONCAT CMAKE_CXX_FLAGS_STR "${CMAKE_CXX_FLAGS_COMMON_STR} ") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS_STR}") +endif() + +string(CONCAT CMAKE_C_FLAGS_COMMON_STR "" + "-Werror -Wall -Wconversion -Wfloat-equal " + "-Wpedantic -Wpointer-arith -Wswitch-default " + "-Wpacked -Wextra -Winvalid-pch " + "-Wmissing-field-initializers -Wunreachable-code " + "-Wcast-align -Wcast-qual -Wdisabled-optimization " + "-Wformat=2 -Wformat-nonliteral -Wuninitialized " + "-Wformat-security -Wformat-y2k -Winit-self " + "-Wmissing-declarations -Wmissing-include-dirs " + "-Wredundant-decls -Wstrict-overflow=5 " + "-Wundef -Wno-unused") + +if (CMAKE_C_COMPILER_ID STREQUAL "GNU") + string(CONCAT CMAKE_C_FLAGS_STR "${CMAKE_C_FLAGS_COMMON_STR} " + "-Wlogical-op") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS_STR}") +elseif (CMAKE_C_COMPILER_ID MATCHES "Clang") + string(CONCAT CMAKE_C_FLAGS_STR "${CMAKE_C_FLAGS_COMMON_STR} ") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS_STR}") +endif() + + + +set(CMAKE_C_FLAGS_DEBUG "-Winline -g") +set(CMAKE_C_FLAGS_RELEASE "-O3 -DNDEBUG") +set(CMAKE_C_FLAGS_RELWITHDEBINFO "-O2 -g -DNDEBUG") +set(CMAKE_CXX_FLAGS_MINSIZEREL "-Os -DNDEBUG") + +set(CMAKE_CXX_FLAGS_DEBUG "-Winline -g") +set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG") +set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g -DNDEBUG") +set(CMAKE_CXX_FLAGS_MINSIZEREL "-Os -DNDEBUG") + +# MESSAGE( STATUS "CMAKE_C_FLAGS: " ${CMAKE_C_FLAGS} ) +# MESSAGE( STATUS "CMAKE_CXX_FLAGS: " ${CMAKE_CXX_FLAGS} ) +# MESSAGE( STATUS "CMAKE_BUILD_TYPE: " ${CMAKE_BUILD_TYPE} ) + +add_subdirectory(src) diff --git a/template_cpp/bin/README b/template_cpp/bin/README new file mode 100644 index 0000000..ced5c3b --- /dev/null +++ b/template_cpp/bin/README @@ -0,0 +1 @@ +This is a reserved directory name! Store the binary generated by `build.sh` in this directory diff --git a/template_cpp/bin/deploy/README b/template_cpp/bin/deploy/README new file mode 100644 index 0000000..1a2f94d --- /dev/null +++ b/template_cpp/bin/deploy/README @@ -0,0 +1 @@ +This is a reserved directory name, do not delete or use in your application! diff --git a/template_cpp/bin/logs/README b/template_cpp/bin/logs/README new file mode 100644 index 0000000..1a2f94d --- /dev/null +++ b/template_cpp/bin/logs/README @@ -0,0 +1 @@ +This is a reserved directory name, do not delete or use in your application! diff --git a/template_cpp/build.sh b/template_cpp/build.sh new file mode 100755 index 0000000..e3dd10b --- /dev/null +++ b/template_cpp/build.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +set -e + +# Change the current working directory to the location of the present file +cd "$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +rm -rf target +mkdir target +cd target +cmake -DCMAKE_BUILD_TYPE=Release .. +cmake --build . +mv src/da_proc ../bin diff --git a/template_cpp/cleanup.sh b/template_cpp/cleanup.sh new file mode 100755 index 0000000..4faeca6 --- /dev/null +++ b/template_cpp/cleanup.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# Change the current working directory to the location of the present file +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +rm -f "$DIR"/bin/da_proc +rm -rf "$DIR"/target diff --git a/template_cpp/run.sh b/template_cpp/run.sh new file mode 100755 index 0000000..3cbd2b8 --- /dev/null +++ b/template_cpp/run.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# Change the current working directory to the location of the present file +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +ret=0 +exec 3>&1; $("$DIR"/bin/da_proc "$@" >&3); ret=$?; exec 3>&- + +exit $ret diff --git a/template_cpp/src/CMakeLists.txt b/template_cpp/src/CMakeLists.txt new file mode 100644 index 0000000..8659af1 --- /dev/null +++ b/template_cpp/src/CMakeLists.txt @@ -0,0 +1,7 @@ +# DO NAME THE SYMBOLIC VARIABLE `SOURCES` + +include_directories(include) +set(SOURCES src/main.cpp) + +# DO NOT EDIT THE FOLLOWING LINE +add_executable(da_proc ${SOURCES}) diff --git a/template_cpp/src/include/barrier.hpp b/template_cpp/src/include/barrier.hpp new file mode 100644 index 0000000..78a1909 --- /dev/null +++ b/template_cpp/src/include/barrier.hpp @@ -0,0 +1,32 @@ +#pragma once +#include "parser.hpp" + +void waitOnBarrier(Parser::Host const &barrier); + +void waitOnBarrier(Parser::Host const &barrier) { + struct sockaddr_in server; + std::memset(&server, 0, sizeof(server)); + + int fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) { + throw std::runtime_error("Could not create the barrier socket: " + + std::string(std::strerror(errno))); + } + + server.sin_family = AF_INET; + server.sin_addr.s_addr = barrier.ip; + server.sin_port = barrier.port; + if (connect(fd, reinterpret_cast(&server), + sizeof(server)) < 0) { + throw std::runtime_error("Could not connect to the barrier: " + + std::string(std::strerror(errno))); + } + + char dummy; + if (recv(fd, &dummy, sizeof(dummy), 0) < 0) { + throw std::runtime_error("Could not read from the barrier socket: " + + std::string(std::strerror(errno))); + } + + close(fd); +} diff --git a/template_cpp/src/include/parser.hpp b/template_cpp/src/include/parser.hpp new file mode 100644 index 0000000..dbcb601 --- /dev/null +++ b/template_cpp/src/include/parser.hpp @@ -0,0 +1,357 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +class Parser { +public: + struct Host { + Host() {} + Host(size_t id, std::string &ip_or_hostname, unsigned short port) + : id{id}, port{htons(port)} { + + if (isValidIpAddress(ip_or_hostname.c_str())) { + ip = inet_addr(ip_or_hostname.c_str()); + } else { + ip = ipLookup(ip_or_hostname.c_str()); + } + } + + std::string ipReadable() const { + in_addr tmp_ip; + tmp_ip.s_addr = ip; + return std::string(inet_ntoa(tmp_ip)); + } + + unsigned short portReadable() const { return ntohs(port); } + + unsigned long id; + in_addr_t ip; + unsigned short port; + + private: + bool isValidIpAddress(const char *ipAddress) { + struct sockaddr_in sa; + int result = inet_pton(AF_INET, ipAddress, &(sa.sin_addr)); + return result != 0; + } + + in_addr_t ipLookup(const char *host) { + struct addrinfo hints, *res; + char addrstr[128]; + void *ptr; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags |= AI_CANONNAME; + + if (getaddrinfo(host, NULL, &hints, &res) != 0) { + throw std::runtime_error( + "Could not resolve host `" + std::string(host) + + "` to IP: " + std::string(std::strerror(errno))); + } + + while (res) { + inet_ntop(res->ai_family, res->ai_addr->sa_data, addrstr, 128); + + switch (res->ai_family) { + case AF_INET: + ptr = + &(reinterpret_cast(res->ai_addr))->sin_addr; + inet_ntop(res->ai_family, ptr, addrstr, 128); + return inet_addr(addrstr); + break; + // case AF_INET6: + // ptr = &((struct sockaddr_in6 *) res->ai_addr)->sin6_addr; + // break; + default: + break; + } + res = res->ai_next; + } + + throw std::runtime_error("No host resolves to IPv4"); + } + }; + +public: + Parser(const int argc, char const *const *argv, bool withConfig) + : argc{argc}, argv{argv}, withConfig{withConfig}, parsed{false} {} + + void parse() { + if (!parseInternal()) { + help(argc, argv); + } + + parsed = true; + } + + unsigned long id() const { + checkParsed(); + return id_; + } + + const char *hostsPath() const { + checkParsed(); + return hostsPath_.c_str(); + } + + Host barrier() const { + checkParsed(); + return barrier_; + } + + const char *outputPath() const { + checkParsed(); + return outputPath_.c_str(); + } + + const char *configPath() const { + checkParsed(); + if (!withConfig) { + throw std::runtime_error("Parser is configure to ignore the config path"); + } + + return configPath_.c_str(); + } + + std::vector hosts() { + std::ifstream hostsFile(hostsPath()); + std::vector hosts; + + if (!hostsFile.is_open()) { + std::ostringstream os; + os << "`" << hostsPath() << "` does not exist."; + throw std::invalid_argument(os.str()); + } + + std::string line; + int lineNum = 0; + while (std::getline(hostsFile, line)) { + lineNum += 1; + + std::istringstream iss(line); + + trim(line); + if (line.empty()) { + continue; + } + + unsigned long id; + std::string ip; + unsigned short port; + + if (!(iss >> id >> ip >> port)) { + std::ostringstream os; + os << "Parsing for `" << hostsPath() << "` failed at line " << lineNum; + throw std::invalid_argument(os.str()); + } + + hosts.push_back(Host(id, ip, port)); + } + + if (hosts.size() < 2UL) { + std::ostringstream os; + os << "`" << hostsPath() << "` must contain at least two hosts"; + throw std::invalid_argument(os.str()); + } + + auto comp = [](const Host &x, const Host &y) { return x.id < y.id; }; + auto result = std::minmax_element(hosts.begin(), hosts.end(), comp); + size_t minID = (*result.first).id; + size_t maxID = (*result.second).id; + if (minID != 1UL || maxID != static_cast(hosts.size())) { + std::ostringstream os; + os << "In `" << hostsPath() + << "` IDs of processes have to start from 1 and be compact"; + throw std::invalid_argument(os.str()); + } + + std::sort(hosts.begin(), hosts.end(), + [](const Host &a, const Host &b) -> bool { return a.id < b.id; }); + + return hosts; + } + +private: + bool parseInternal() { + if (!parseID()) { + return false; + } + + if (!parseHostPath()) { + return false; + } + + if (!parseBarrier()) { + return false; + } + + if (!parseOutputPath()) { + return false; + } + + if (!parseConfigPath()) { + return false; + } + + return true; + } + + void help(const int, char const *const *argv) { + auto configStr = "CONFIG"; + std::cerr << "Usage: " << argv[0] + << " --id ID --hosts HOSTS --barrier NAME:PORT --output OUTPUT"; + + if (!withConfig) { + std::cerr << "\n"; + } else { + std::cerr << " CONFIG\n"; + } + + exit(EXIT_FAILURE); + } + + bool parseID() { + if (argc < 3) { + return false; + } + + if (std::strcmp(argv[1], "--id") == 0) { + if (isPositiveNumber(argv[2])) { + try { + id_ = std::stoul(argv[2]); + } catch (std::invalid_argument const &e) { + return false; + } catch (std::out_of_range const &e) { + return false; + } + + return true; + } + } + + return false; + } + + bool parseHostPath() { + if (argc < 5) { + return false; + } + + if (std::strcmp(argv[3], "--hosts") == 0) { + hostsPath_ = std::string(argv[4]); + return true; + } + + return false; + } + + bool parseBarrier() { + if (argc < 7) { + return false; + } + + if (std::strcmp(argv[5], "--barrier") == 0) { + std::string barrier_addr = argv[6]; + std::replace(barrier_addr.begin(), barrier_addr.end(), ':', ' '); + std::stringstream ss(barrier_addr); + + std::string barrier_name; + unsigned short barrier_port; + + ss >> barrier_name; + ss >> barrier_port; + + barrier_ = Host(0, barrier_name, barrier_port); + return true; + } + + return false; + } + + bool parseOutputPath() { + if (argc < 9) { + return false; + } + + if (std::strcmp(argv[7], "--output") == 0) { + outputPath_ = std::string(argv[8]); + return true; + } + + return false; + } + + bool parseConfigPath() { + if (!withConfig) { + return true; + } + + if (argc < 10) { + return false; + } + + configPath_ = std::string(argv[9]); + return true; + } + + bool isPositiveNumber(const std::string &s) const { + return !s.empty() && std::find_if(s.begin(), s.end(), [](unsigned char c) { + return !std::isdigit(c); + }) == s.end(); + } + + void checkParsed() const { + if (!parsed) { + throw std::runtime_error("Invoke parse() first"); + } + } + + void ltrim(std::string &s) { + s.erase(s.begin(), std::find_if(s.begin(), s.end(), + [](int ch) { return !std::isspace(ch); })); + } + + void rtrim(std::string &s) { + s.erase(std::find_if(s.rbegin(), s.rend(), + [](int ch) { return !std::isspace(ch); }) + .base(), + s.end()); + } + + void trim(std::string &s) { + ltrim(s); + rtrim(s); + } + +private: + const int argc; + char const *const *argv; + bool withConfig; + + bool parsed; + + unsigned long id_; + std::string hostsPath_; + Host barrier_; + std::string outputPath_; + std::string configPath_; +}; diff --git a/template_cpp/src/src/main.cpp b/template_cpp/src/src/main.cpp new file mode 100644 index 0000000..52a5f16 --- /dev/null +++ b/template_cpp/src/src/main.cpp @@ -0,0 +1,85 @@ +#include +#include +#include + +#include "barrier.hpp" +#include "parser.hpp" +#include + +static void stop(int) { + // reset signal handlers to default + signal(SIGTERM, SIG_DFL); + signal(SIGINT, SIG_DFL); + + // immediately stop network packet processing + std::cout << "Immediately stopping network packet processing.\n"; + + // write/flush output file if necessary + std::cout << "Writing output.\n"; + + // exit directly from signal handler + exit(0); +} + +int main(int argc, char **argv) { + signal(SIGTERM, stop); + signal(SIGINT, stop); + + // `true` means that a config file is required. + // Call with `false` if no config file is necessary. + bool requireConfig = true; + + Parser parser(argc, argv, requireConfig); + parser.parse(); + + std::cout << "My PID: " << getpid() << "\n"; + std::cout << "Use `kill -SIGINT " << getpid() << "` or `kill -SIGTERM " + << getpid() << "` to stop processing packets\n\n"; + + std::cout << "My ID: " << parser.id() << "\n\n"; + + std::cout << "Path to hosts:\n"; + std::cout << "==============\n"; + std::cout << parser.hostsPath() << "\n\n"; + + std::cout << "List of resolved hosts is:\n"; + std::cout << "==========================\n"; + auto hosts = parser.hosts(); + for (auto &host : hosts) { + std::cout << host.id << "\n"; + std::cout << "Human-readable IP: " << host.ipReadable() << "\n"; + std::cout << "Machine-readable IP: " << host.ip << "\n"; + std::cout << "Human-readbale Port: " << host.portReadable() << "\n"; + std::cout << "Machine-readbale Port: " << host.port << "\n"; + std::cout << "\n"; + } + std::cout << "\n"; + + std::cout << "Barrier:\n"; + std::cout << "========\n"; + auto barrier = parser.barrier(); + std::cout << "Human-readable IP: " << barrier.ipReadable() << "\n"; + std::cout << "Machine-readable IP: " << barrier.ip << "\n"; + std::cout << "Human-readbale Port: " << barrier.portReadable() << "\n"; + std::cout << "Machine-readbale Port: " << barrier.port << "\n"; + std::cout << "\n"; + + std::cout << "Path to output:\n"; + std::cout << "===============\n"; + std::cout << parser.outputPath() << "\n\n"; + + if (requireConfig) { + std::cout << "Path to config:\n"; + std::cout << "===============\n"; + std::cout << parser.configPath() << "\n\n"; + } + + std::cout << "Doing some initialization...\n\n"; + + std::cout << "Waiting for all processes to finish initialization\n\n"; + waitOnBarrier(barrier); + + std::cout << "Broadcasting messages...\n\n"; + + return 0; +}