From: alex Date: Wed, 7 Apr 2021 03:38:34 +0000 (-0700) Subject: initial design complete X-Git-Tag: v1.0.0~1 X-Git-Url: http://git.infiniteadaptability.org/?a=commitdiff_plain;h=239fab39974768b4d0ec22cf869e3f336ec48b46;p=seeder initial design complete created seederd created init scripts which interface with systemd created rss helper programs to generate feeds --- diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..92947ef --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# autoconf/make +autom4te.cache/ +autoscan.log +aclocal.m4 +build-aux/ +config.h.in +config.h +config.h.in~ +config.log +config.status +configure +configure.scan +*.in +*.in~ +**/.deps +**/.dirstamp +**/Makefile +**/Makefile.in +stamp-h1 + +# build objects +*.o +*.log +*.trs +*.tests + +# binaries +rss/rss-create +rss/*_tests +seederd/seederd diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 31d55c5..0000000 --- a/Dockerfile +++ /dev/null @@ -1,3 +0,0 @@ -FROM debian:latest - -ENTRYPOINT bash diff --git a/Makefile b/Makefile deleted file mode 100644 index d0f616a..0000000 --- a/Makefile +++ /dev/null @@ -1,22 +0,0 @@ -IMAGE_NAME=seeder -CONTAINER_NAME=seeder-test -build: - docker build -t $(IMAGE_NAME) . - -run: build - docker run -d \ - -p "8080:8080" \ - --cap-add=SYS_PTRACE --security-opt seccomp=unconfined \ - --name $(CONTAINER_NAME) \ - $(IMAGE_NAME) - -start: build - -docker run -it --rm \ - --cap-add=SYS_PTRACE --security-opt seccomp=unconfined \ - -p "8080:8080" \ - $(IMAGE_NAME) - -stop: - docker stop $(CONTAINER_NAME) - docker rm $(CONTAINER_NAME) - diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..eac46e1 --- /dev/null +++ b/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = rss seederd init diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..eb30f61 --- /dev/null +++ b/README.txt @@ -0,0 +1,24 @@ +Compile-Dependencies: +libtorrent-rasterbar-dev +inotify-tools + +Optional Dependencies: +systemd +nginx-light + +Build Dependencies: +autoconf +automake +gcc +g++ + +Testing Dependencies: +valgrind + +To Build+Install: +autoreconf -vif +./configure +make +make install +systemctl enable seederd +systemctl start seederd diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..29dfe87 --- /dev/null +++ b/configure.ac @@ -0,0 +1,83 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +AC_PREREQ([2.69]) +AC_INIT([seeder], [0.0.0]) + +# Store build files not in main directory +AC_CONFIG_AUX_DIR([build-aux]) +AC_CONFIG_MACRO_DIR([m4]) + +AM_INIT_AUTOMAKE([foreign subdir-objects nostdinc -Wall -Werror]) + +AC_ARG_ENABLE([tests], + [AS_HELP_STRING([--disable-tests], + [disable tests (enabled by default)])], + [enable_tests=$enableval], + [enable_tests=yes]) + +AC_ARG_ENABLE([memcheck], + [AS_HELP_STRING([--disable-memcheck], + [disable valgrind (enabled by default)])], + [enable_memcheck=$enableval], + [enable_memcheck=yes]) + +AC_PATH_PROG([VALGRIND], [valgrind]) +AM_CONDITIONAL([HAVE_VALGRIND], [test -n "$VALGRIND"]) +AC_PATH_PROG([NGINX], [nginx]) +AM_CONDITIONAL([HAVE_NGINX], [test -n "$NGINX"]) +AC_PATH_PROG([SYSTEMD], [systemd]) +AM_CONDITIONAL([HAVE_SYSTEMD], [test -n "$SYSTEMD"]) + +AC_PATH_PROG([INOTIFYWAIT], [inotifywait]) +if test -z "$INOTIFYWAIT"; then + AC_MSG_ERROR("inotifywait required"); +fi + +dnl disable memcheck if valgrind not found +if test "x$enable_memcheck" != "xno"; then + if test -z "$VALGRIND"; then + enable_memcheck=no + fi +fi + +AC_MSG_CHECKING([if memcheck should be enabled]) +if test x$enable_memcheck != xno; then + AC_MSG_RESULT(yes) +else + AC_MSG_RESULT(no) +fi + +AC_ARG_VAR(SEEDER_DOMAIN,[hostname from which seeder is serving]) + +AM_CONDITIONAL([ENABLE_MEMCHECK],[test x$enable_memcheck = xyes]) + + +# Checks for programs. +AC_PROG_CC +AC_PROG_CXX +AC_PROG_LN_S + +# Checks for libraries. +AC_CHECK_LIB([torrent-rasterbar], [main],,AC_MSG_ERROR(libtorrent-rasterbar required)) +AC_CHECK_LIB([boost_system], [main],,AC_MSG_ERROR(boost_system required)) +AC_CHECK_LIB([pthread], [main],,AC_MSG_ERROR(pthread required)) + +# Checks for header files. +AC_CHECK_HEADERS([stddef.h stdlib.h string.h]) + +# Checks for typedefs, structures, and compiler characteristics. +AC_CHECK_HEADER_STDBOOL +AC_TYPE_SIZE_T + +# Checks for library functions. +AC_FUNC_MALLOC +AC_CHECK_FUNCS([atexit mkdir strchr strpbrk strrchr]) + +AX_CXX_COMPILE_STDCXX([17], [noext], [mandatory]) + +AC_CONFIG_FILES([Makefile + init/Makefile + rss/Makefile + seederd/Makefile]) +AC_OUTPUT diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 0000000..3d53fcb --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +set -euo pipefail + +rm /etc/ssh/ssh_host_* +dpkg-reconfigure openssh-server + +service ssh start +service nginx start +service cron start + +monitor-seederd & + +seederd diff --git a/init/Makefile.am b/init/Makefile.am new file mode 100644 index 0000000..f21f601 --- /dev/null +++ b/init/Makefile.am @@ -0,0 +1,17 @@ +install-exec-local: + ./setup.sh $(bindir) $(localstatedir)/seeder $(SEEDER_DOMAIN) +if HAVE_NGINX + ./setup-nginx.sh $(localstatedir)/seeder +endif +if HAVE_SYSTEMD + -./setup-systemd.sh $(localstatedir)/seeder +endif + +uninstall-local: + -./remove.sh $(bindir) $(localstatedir)/seeder +if HAVE_NGINX + -./remove-nginx.sh $(localstatedir)/seeder +endif +if HAVE_SYSTEMD + -./remove-systemd.sh +endif diff --git a/init/remove-nginx.sh b/init/remove-nginx.sh new file mode 100755 index 0000000..3bc3470 --- /dev/null +++ b/init/remove-nginx.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +rm -f "$1/index.html" + +rm -f /etc/nginx/sites-enabled/feeds +rm -f /etc/nginx/sites-available/feeds + +systemctl reload nginx diff --git a/init/remove-systemd.sh b/init/remove-systemd.sh new file mode 100755 index 0000000..1ab32d8 --- /dev/null +++ b/init/remove-systemd.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +systemctl stop seederd +systemctl disable seederd +rm -f /lib/systemd/system/seederd.service + +systemctl stop seederd-monitor +systemctl disable seederd-monitor +rm -f /lib/systemd/system/seederd-monitor.service + +systemctl daemon-reload +systemctl reset-failed diff --git a/init/remove.sh b/init/remove.sh new file mode 100755 index 0000000..48f00e7 --- /dev/null +++ b/init/remove.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +rm -f "$1/update-feeds" + +rm -rf "$2" diff --git a/init/setup-nginx.sh b/init/setup-nginx.sh new file mode 100755 index 0000000..c07d180 --- /dev/null +++ b/init/setup-nginx.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash + +set -euo pipefail + +mkdir -p "$1" + +cat > "$1/index.html" << EOF + +

This is the homepage of seeder.

+

Feeds

+

Torrent Files

+ +EOF + +# setup nginx +rm -f /etc/nginx/sites-enabled/default +cat > /etc/nginx/sites-available/feeds << EOF +server { + listen 80 default_server; + listen [::]:80; + + root $1; + index index.html; + + location /feeds { + autoindex on; + } + + location /torrents { + autoindex on; + } +} +EOF +ln -sf /etc/nginx/sites-available/feeds /etc/nginx/sites-enabled/feeds + +set +e; systemctl reload nginx; set -e; diff --git a/init/setup-systemd.sh b/init/setup-systemd.sh new file mode 100755 index 0000000..917a7ea --- /dev/null +++ b/init/setup-systemd.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +set -euo pipefail + +cat > /lib/systemd/system/seederd.service << EOF +[Unit] +Description=Seeder daemon +After=network.target + +[Service] +ExecStartPre=mkdir -p $1 +ExecStart=seederd +ExecReload=kill -HUP \$MAINPID + +Type=simple +Restart=always +TimeoutStopSec=60 + +# Hardening measures +PrivateTmp=true +ProtectSystem=full +ProtectHome=true +NoNewPrivileges=true +PrivateDevices=true +ReadWritePaths=$1 + +[Install] +WantedBy=multi-user.target +EOF + +cat > /lib/systemd/system/seederd-monitor.service << EOF +[Unit] +Description=Seeder Monitor Service +After=seederd.service + +[Service] +ExecStartPre=mkdir -p "$1/data" +ExecStart=inotifywait -e modify,create,delete,move -r "$1/data" +ExecStopPost=systemctl reload seederd +Type=simple +Restart=on-success +RestartSec=500ms + +[Install] +RequiredBy=seederd.service +EOF diff --git a/init/setup.sh b/init/setup.sh new file mode 100755 index 0000000..38f3e6b --- /dev/null +++ b/init/setup.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +set -euo pipefail + +mkdir -p "$1" +mkdir -p "$2" + +cat > "$1/update-feeds" << EOF +#!/usr/bin/env bash + +set -euo pipefail + +mkdir -p $2/feeds + +FEEDS=(\$(find $2/data -maxdepth 1 -mindepth 1 -type d)) + +for feed in "\${FEEDS[@]}"; do + NAME=\$(basename \$feed) + echo "building feed: \$NAME" + (cd \$feed; find . -mindepth 1 -maxdepth 1 -not -name ".*" -not -path "**/.*" | sed "s|./||" | sort | rss-create -o $2/feeds/\$NAME.xml -l "$3" -f "$3/torrents/" -n \$NAME -m "../.\$NAME.meta") +done +EOF + +chmod +x "$1/update-feeds" diff --git a/m4/m4_ax_cxx_compile_stdcxx.m4 b/m4/m4_ax_cxx_compile_stdcxx.m4 new file mode 100644 index 0000000..9413da6 --- /dev/null +++ b/m4/m4_ax_cxx_compile_stdcxx.m4 @@ -0,0 +1,962 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CXX_COMPILE_STDCXX(VERSION, [ext|noext], [mandatory|optional]) +# +# DESCRIPTION +# +# Check for baseline language coverage in the compiler for the specified +# version of the C++ standard. If necessary, add switches to CXX and +# CXXCPP to enable support. VERSION may be '11' (for the C++11 standard) +# or '14' (for the C++14 standard). +# +# The second argument, if specified, indicates whether you insist on an +# extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g. +# -std=c++11). If neither is specified, you get whatever works, with +# preference for no added switch, and then for an extended mode. +# +# The third argument, if specified 'mandatory' or if left unspecified, +# indicates that baseline support for the specified C++ standard is +# required and that the macro should error out if no mode with that +# support is found. If specified 'optional', then configuration proceeds +# regardless, after defining HAVE_CXX${VERSION} if and only if a +# supporting mode is found. +# +# LICENSE +# +# Copyright (c) 2008 Benjamin Kosnik +# Copyright (c) 2012 Zack Weinberg +# Copyright (c) 2013 Roy Stogner +# Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov +# Copyright (c) 2015 Paul Norman +# Copyright (c) 2015 Moritz Klammler +# Copyright (c) 2016, 2018 Krzesimir Nowak +# Copyright (c) 2019 Enji Cooper +# Copyright (c) 2020 Jason Merrill +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 12 + +dnl This macro is based on the code from the AX_CXX_COMPILE_STDCXX_11 macro +dnl (serial version number 13). + +AC_DEFUN([AX_CXX_COMPILE_STDCXX], [dnl + m4_if([$1], [11], [ax_cxx_compile_alternatives="11 0x"], + [$1], [14], [ax_cxx_compile_alternatives="14 1y"], + [$1], [17], [ax_cxx_compile_alternatives="17 1z"], + [m4_fatal([invalid first argument `$1' to AX_CXX_COMPILE_STDCXX])])dnl + m4_if([$2], [], [], + [$2], [ext], [], + [$2], [noext], [], + [m4_fatal([invalid second argument `$2' to AX_CXX_COMPILE_STDCXX])])dnl + m4_if([$3], [], [ax_cxx_compile_cxx$1_required=true], + [$3], [mandatory], [ax_cxx_compile_cxx$1_required=true], + [$3], [optional], [ax_cxx_compile_cxx$1_required=false], + [m4_fatal([invalid third argument `$3' to AX_CXX_COMPILE_STDCXX])]) + AC_LANG_PUSH([C++])dnl + ac_success=no + + m4_if([$2], [], [dnl + AC_CACHE_CHECK(whether $CXX supports C++$1 features by default, + ax_cv_cxx_compile_cxx$1, + [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], + [ax_cv_cxx_compile_cxx$1=yes], + [ax_cv_cxx_compile_cxx$1=no])]) + if test x$ax_cv_cxx_compile_cxx$1 = xyes; then + ac_success=yes + fi]) + + m4_if([$2], [noext], [], [dnl + if test x$ac_success = xno; then + for alternative in ${ax_cxx_compile_alternatives}; do + switch="-std=gnu++${alternative}" + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) + AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, + $cachevar, + [ac_save_CXX="$CXX" + CXX="$CXX $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], + [eval $cachevar=yes], + [eval $cachevar=no]) + CXX="$ac_save_CXX"]) + if eval test x\$$cachevar = xyes; then + CXX="$CXX $switch" + if test -n "$CXXCPP" ; then + CXXCPP="$CXXCPP $switch" + fi + ac_success=yes + break + fi + done + fi]) + + m4_if([$2], [ext], [], [dnl + if test x$ac_success = xno; then + dnl HP's aCC needs +std=c++11 according to: + dnl http://h21007.www2.hp.com/portal/download/files/unprot/aCxx/PDF_Release_Notes/769149-001.pdf + dnl Cray's crayCC needs "-h std=c++11" + for alternative in ${ax_cxx_compile_alternatives}; do + for switch in -std=c++${alternative} +std=c++${alternative} "-h std=c++${alternative}"; do + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) + AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, + $cachevar, + [ac_save_CXX="$CXX" + CXX="$CXX $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], + [eval $cachevar=yes], + [eval $cachevar=no]) + CXX="$ac_save_CXX"]) + if eval test x\$$cachevar = xyes; then + CXX="$CXX $switch" + if test -n "$CXXCPP" ; then + CXXCPP="$CXXCPP $switch" + fi + ac_success=yes + break + fi + done + if test x$ac_success = xyes; then + break + fi + done + fi]) + AC_LANG_POP([C++]) + if test x$ax_cxx_compile_cxx$1_required = xtrue; then + if test x$ac_success = xno; then + AC_MSG_ERROR([*** A compiler with support for C++$1 language features is required.]) + fi + fi + if test x$ac_success = xno; then + HAVE_CXX$1=0 + AC_MSG_NOTICE([No compiler with C++$1 support was found]) + else + HAVE_CXX$1=1 + AC_DEFINE(HAVE_CXX$1,1, + [define if the compiler supports basic C++$1 syntax]) + fi + AC_SUBST(HAVE_CXX$1) +]) + + +dnl Test body for checking C++11 support + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_11], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 +) + + +dnl Test body for checking C++14 support + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_14], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 +) + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_17], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_17 +) + +dnl Tests for new features in C++11 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_11], [[ + +// If the compiler admits that it is not ready for C++11, why torture it? +// Hopefully, this will speed up the test. + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +#elif __cplusplus < 201103L + +#error "This is not a C++11 compiler" + +#else + +namespace cxx11 +{ + + namespace test_static_assert + { + + template + struct check + { + static_assert(sizeof(int) <= sizeof(T), "not big enough"); + }; + + } + + namespace test_final_override + { + + struct Base + { + virtual ~Base() {} + virtual void f() {} + }; + + struct Derived : public Base + { + virtual ~Derived() override {} + virtual void f() override {} + }; + + } + + namespace test_double_right_angle_brackets + { + + template < typename T > + struct check {}; + + typedef check single_type; + typedef check> double_type; + typedef check>> triple_type; + typedef check>>> quadruple_type; + + } + + namespace test_decltype + { + + int + f() + { + int a = 1; + decltype(a) b = 2; + return a + b; + } + + } + + namespace test_type_deduction + { + + template < typename T1, typename T2 > + struct is_same + { + static const bool value = false; + }; + + template < typename T > + struct is_same + { + static const bool value = true; + }; + + template < typename T1, typename T2 > + auto + add(T1 a1, T2 a2) -> decltype(a1 + a2) + { + return a1 + a2; + } + + int + test(const int c, volatile int v) + { + static_assert(is_same::value == true, ""); + static_assert(is_same::value == false, ""); + static_assert(is_same::value == false, ""); + auto ac = c; + auto av = v; + auto sumi = ac + av + 'x'; + auto sumf = ac + av + 1.0; + static_assert(is_same::value == true, ""); + static_assert(is_same::value == true, ""); + static_assert(is_same::value == true, ""); + static_assert(is_same::value == false, ""); + static_assert(is_same::value == true, ""); + return (sumf > 0.0) ? sumi : add(c, v); + } + + } + + namespace test_noexcept + { + + int f() { return 0; } + int g() noexcept { return 0; } + + static_assert(noexcept(f()) == false, ""); + static_assert(noexcept(g()) == true, ""); + + } + + namespace test_constexpr + { + + template < typename CharT > + unsigned long constexpr + strlen_c_r(const CharT *const s, const unsigned long acc) noexcept + { + return *s ? strlen_c_r(s + 1, acc + 1) : acc; + } + + template < typename CharT > + unsigned long constexpr + strlen_c(const CharT *const s) noexcept + { + return strlen_c_r(s, 0UL); + } + + static_assert(strlen_c("") == 0UL, ""); + static_assert(strlen_c("1") == 1UL, ""); + static_assert(strlen_c("example") == 7UL, ""); + static_assert(strlen_c("another\0example") == 7UL, ""); + + } + + namespace test_rvalue_references + { + + template < int N > + struct answer + { + static constexpr int value = N; + }; + + answer<1> f(int&) { return answer<1>(); } + answer<2> f(const int&) { return answer<2>(); } + answer<3> f(int&&) { return answer<3>(); } + + void + test() + { + int i = 0; + const int c = 0; + static_assert(decltype(f(i))::value == 1, ""); + static_assert(decltype(f(c))::value == 2, ""); + static_assert(decltype(f(0))::value == 3, ""); + } + + } + + namespace test_uniform_initialization + { + + struct test + { + static const int zero {}; + static const int one {1}; + }; + + static_assert(test::zero == 0, ""); + static_assert(test::one == 1, ""); + + } + + namespace test_lambdas + { + + void + test1() + { + auto lambda1 = [](){}; + auto lambda2 = lambda1; + lambda1(); + lambda2(); + } + + int + test2() + { + auto a = [](int i, int j){ return i + j; }(1, 2); + auto b = []() -> int { return '0'; }(); + auto c = [=](){ return a + b; }(); + auto d = [&](){ return c; }(); + auto e = [a, &b](int x) mutable { + const auto identity = [](int y){ return y; }; + for (auto i = 0; i < a; ++i) + a += b--; + return x + identity(a + b); + }(0); + return a + b + c + d + e; + } + + int + test3() + { + const auto nullary = [](){ return 0; }; + const auto unary = [](int x){ return x; }; + using nullary_t = decltype(nullary); + using unary_t = decltype(unary); + const auto higher1st = [](nullary_t f){ return f(); }; + const auto higher2nd = [unary](nullary_t f1){ + return [unary, f1](unary_t f2){ return f2(unary(f1())); }; + }; + return higher1st(nullary) + higher2nd(nullary)(unary); + } + + } + + namespace test_variadic_templates + { + + template + struct sum; + + template + struct sum + { + static constexpr auto value = N0 + sum::value; + }; + + template <> + struct sum<> + { + static constexpr auto value = 0; + }; + + static_assert(sum<>::value == 0, ""); + static_assert(sum<1>::value == 1, ""); + static_assert(sum<23>::value == 23, ""); + static_assert(sum<1, 2>::value == 3, ""); + static_assert(sum<5, 5, 11>::value == 21, ""); + static_assert(sum<2, 3, 5, 7, 11, 13>::value == 41, ""); + + } + + // http://stackoverflow.com/questions/13728184/template-aliases-and-sfinae + // Clang 3.1 fails with headers of libstd++ 4.8.3 when using std::function + // because of this. + namespace test_template_alias_sfinae + { + + struct foo {}; + + template + using member = typename T::member_type; + + template + void func(...) {} + + template + void func(member*) {} + + void test(); + + void test() { func(0); } + + } + +} // namespace cxx11 + +#endif // __cplusplus >= 201103L + +]]) + + +dnl Tests for new features in C++14 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_14], [[ + +// If the compiler admits that it is not ready for C++14, why torture it? +// Hopefully, this will speed up the test. + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +#elif __cplusplus < 201402L + +#error "This is not a C++14 compiler" + +#else + +namespace cxx14 +{ + + namespace test_polymorphic_lambdas + { + + int + test() + { + const auto lambda = [](auto&&... args){ + const auto istiny = [](auto x){ + return (sizeof(x) == 1UL) ? 1 : 0; + }; + const int aretiny[] = { istiny(args)... }; + return aretiny[0]; + }; + return lambda(1, 1L, 1.0f, '1'); + } + + } + + namespace test_binary_literals + { + + constexpr auto ivii = 0b0000000000101010; + static_assert(ivii == 42, "wrong value"); + + } + + namespace test_generalized_constexpr + { + + template < typename CharT > + constexpr unsigned long + strlen_c(const CharT *const s) noexcept + { + auto length = 0UL; + for (auto p = s; *p; ++p) + ++length; + return length; + } + + static_assert(strlen_c("") == 0UL, ""); + static_assert(strlen_c("x") == 1UL, ""); + static_assert(strlen_c("test") == 4UL, ""); + static_assert(strlen_c("another\0test") == 7UL, ""); + + } + + namespace test_lambda_init_capture + { + + int + test() + { + auto x = 0; + const auto lambda1 = [a = x](int b){ return a + b; }; + const auto lambda2 = [a = lambda1(x)](){ return a; }; + return lambda2(); + } + + } + + namespace test_digit_separators + { + + constexpr auto ten_million = 100'000'000; + static_assert(ten_million == 100000000, ""); + + } + + namespace test_return_type_deduction + { + + auto f(int& x) { return x; } + decltype(auto) g(int& x) { return x; } + + template < typename T1, typename T2 > + struct is_same + { + static constexpr auto value = false; + }; + + template < typename T > + struct is_same + { + static constexpr auto value = true; + }; + + int + test() + { + auto x = 0; + static_assert(is_same::value, ""); + static_assert(is_same::value, ""); + return x; + } + + } + +} // namespace cxx14 + +#endif // __cplusplus >= 201402L + +]]) + + +dnl Tests for new features in C++17 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_17], [[ + +// If the compiler admits that it is not ready for C++17, why torture it? +// Hopefully, this will speed up the test. + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +#elif __cplusplus < 201703L + +#error "This is not a C++17 compiler" + +#else + +#include +#include +#include + +namespace cxx17 +{ + + namespace test_constexpr_lambdas + { + + constexpr int foo = [](){return 42;}(); + + } + + namespace test::nested_namespace::definitions + { + + } + + namespace test_fold_expression + { + + template + int multiply(Args... args) + { + return (args * ... * 1); + } + + template + bool all(Args... args) + { + return (args && ...); + } + + } + + namespace test_extended_static_assert + { + + static_assert (true); + + } + + namespace test_auto_brace_init_list + { + + auto foo = {5}; + auto bar {5}; + + static_assert(std::is_same, decltype(foo)>::value); + static_assert(std::is_same::value); + } + + namespace test_typename_in_template_template_parameter + { + + template typename X> struct D; + + } + + namespace test_fallthrough_nodiscard_maybe_unused_attributes + { + + int f1() + { + return 42; + } + + [[nodiscard]] int f2() + { + [[maybe_unused]] auto unused = f1(); + + switch (f1()) + { + case 17: + f1(); + [[fallthrough]]; + case 42: + f1(); + } + return f1(); + } + + } + + namespace test_extended_aggregate_initialization + { + + struct base1 + { + int b1, b2 = 42; + }; + + struct base2 + { + base2() { + b3 = 42; + } + int b3; + }; + + struct derived : base1, base2 + { + int d; + }; + + derived d1 {{1, 2}, {}, 4}; // full initialization + derived d2 {{}, {}, 4}; // value-initialized bases + + } + + namespace test_general_range_based_for_loop + { + + struct iter + { + int i; + + int& operator* () + { + return i; + } + + const int& operator* () const + { + return i; + } + + iter& operator++() + { + ++i; + return *this; + } + }; + + struct sentinel + { + int i; + }; + + bool operator== (const iter& i, const sentinel& s) + { + return i.i == s.i; + } + + bool operator!= (const iter& i, const sentinel& s) + { + return !(i == s); + } + + struct range + { + iter begin() const + { + return {0}; + } + + sentinel end() const + { + return {5}; + } + }; + + void f() + { + range r {}; + + for (auto i : r) + { + [[maybe_unused]] auto v = i; + } + } + + } + + namespace test_lambda_capture_asterisk_this_by_value + { + + struct t + { + int i; + int foo() + { + return [*this]() + { + return i; + }(); + } + }; + + } + + namespace test_enum_class_construction + { + + enum class byte : unsigned char + {}; + + byte foo {42}; + + } + + namespace test_constexpr_if + { + + template + int f () + { + if constexpr(cond) + { + return 13; + } + else + { + return 42; + } + } + + } + + namespace test_selection_statement_with_initializer + { + + int f() + { + return 13; + } + + int f2() + { + if (auto i = f(); i > 0) + { + return 3; + } + + switch (auto i = f(); i + 4) + { + case 17: + return 2; + + default: + return 1; + } + } + + } + + namespace test_template_argument_deduction_for_class_templates + { + + template + struct pair + { + pair (T1 p1, T2 p2) + : m1 {p1}, + m2 {p2} + {} + + T1 m1; + T2 m2; + }; + + void f() + { + [[maybe_unused]] auto p = pair{13, 42u}; + } + + } + + namespace test_non_type_auto_template_parameters + { + + template + struct B + {}; + + B<5> b1; + B<'a'> b2; + + } + + namespace test_structured_bindings + { + + int arr[2] = { 1, 2 }; + std::pair pr = { 1, 2 }; + + auto f1() -> int(&)[2] + { + return arr; + } + + auto f2() -> std::pair& + { + return pr; + } + + struct S + { + int x1 : 2; + volatile double y1; + }; + + S f3() + { + return {}; + } + + auto [ x1, y1 ] = f1(); + auto& [ xr1, yr1 ] = f1(); + auto [ x2, y2 ] = f2(); + auto& [ xr2, yr2 ] = f2(); + const auto [ x3, y3 ] = f3(); + + } + + namespace test_exception_spec_type_system + { + + struct Good {}; + struct Bad {}; + + void g1() noexcept; + void g2(); + + template + Bad + f(T*, T*); + + template + Good + f(T1*, T2*); + + static_assert (std::is_same_v); + + } + + namespace test_inline_variables + { + + template void f(T) + {} + + template inline T g(T) + { + return T{}; + } + + template<> inline void f<>(int) + {} + + template<> int g<>(int) + { + return 5; + } + + } + +} // namespace cxx17 + +#endif // __cplusplus < 201703L + +]]) diff --git a/rss/Makefile.am b/rss/Makefile.am new file mode 100644 index 0000000..2602499 --- /dev/null +++ b/rss/Makefile.am @@ -0,0 +1,126 @@ +AM_CPPFLAGS = \ + -I$(builddir)/inc/ \ + -I$(srcdir)/inc/ + +bin_PROGRAMS = rss-create + +rss_create_SOURCES = \ + src/add.c \ + src/default.c \ + src/escape.c \ + src/info.c \ + src/log.c \ + src/main.c \ + src/meta.c \ + src/next.c \ + src/opt/desc.c \ + src/opt/feed.c \ + src/opt/link.c \ + src/opt/meta.c \ + src/opt/name.c \ + src/opt/out.c \ + src/util/now.c + +rss_create_SOURCES += \ + inc/add.h \ + inc/default.h \ + inc/escape.h \ + inc/info.h \ + inc/log.h \ + inc/main.h \ + inc/meta.h \ + inc/next.h \ + inc/opt.h \ + inc/util.h + +EXTRA_DIST = \ + test/add.tests.h \ + test/escape.tests.h \ + test/info.tests.h \ + test/meta.tests.h \ + test/next.tests.h \ + test/opt.desc.tests.h \ + test/opt.feed.tests.h \ + test/opt.link.tests.h \ + test/opt.meta.tests.h \ + test/opt.name.tests.h \ + test/opt.out.tests.h \ + test/util.now.tests.h \ + test/test_utils.h + +check_PROGRAMS = add_tests escape_tests info_tests meta_tests next_tests opt_description_tests opt_feed_tests opt_link_tests opt_meta_tests opt_name_tests opt_out_tests util_now_tests + +TESTS = $(check_PROGRAMS) + +if ENABLE_MEMCHECK +LOG_COMPILER = $(VALGRIND) +AM_LOG_FLAGS = --leak-check=full -v --track-origins=yes --error-exitcode=1 +endif + +common_SOURCES = \ + src/default.c \ + src/log.c \ + src/opt/out.c \ + src/util/now.c \ + test/test_utils.c + +add_tests_SOURCES = \ + $(common_SOURCES) \ + src/escape.c \ + src/meta.c \ + test/add.tests.c + +escape_tests_SOURCES = \ + $(common_SOURCES) \ + src/escape.c \ + test/escape.tests.c + +info_tests_SOURCES = \ + $(common_SOURCES) \ + src/info.c \ + test/info.tests.c + +meta_tests_SOURCES = \ + $(common_SOURCES) \ + src/escape.c \ + src/meta.c \ + test/meta.tests.c + +next_tests_SOURCES = \ + $(common_SOURCES) \ + test/next.tests.c + +opt_description_tests_SOURCES = \ + $(common_SOURCES) \ + src/opt/desc.c \ + test/opt.desc.tests.c + +opt_feed_tests_SOURCES = \ + $(common_SOURCES) \ + src/opt/feed.c \ + test/opt.feed.tests.c + +opt_link_tests_SOURCES = \ + $(common_SOURCES) \ + src/opt/link.c \ + test/opt.link.tests.c + +opt_meta_tests_SOURCES = \ + $(common_SOURCES) \ + src/escape.c \ + src/meta.c \ + src/opt/meta.c \ + test/opt.meta.tests.c + +opt_name_tests_SOURCES = \ + $(common_SOURCES) \ + src/opt/name.c \ + test/opt.name.tests.c + +opt_out_tests_SOURCES = \ + $(common_SOURCES) \ + test/opt.out.tests.c + +util_now_tests_SOURCES = \ + $(common_SOURCES) \ + test/util.now.tests.c diff --git a/rss/inc/add.h b/rss/inc/add.h new file mode 100644 index 0000000..2324c22 --- /dev/null +++ b/rss/inc/add.h @@ -0,0 +1,25 @@ +#ifndef __ADD_H_ +#define __ADD_H_ + +#include + +#include +#include +#include +#include +#include +#include + +#define FIELD_ADDER(x) add_field(x,FIELD_PREFIX(FIELD_STRING_LOADER(x)),FIELD_SUFFIX(FIELD_STRING_LOADER(x))) + +#define add_title() FIELD_ADDER(FIELD_TITLE) +#define add_link() FIELD_ADDER(FIELD_LINK) +#define add_pubdate() FIELD_ADDER(FIELD_PUBDATE) +#define add_description() FIELD_ADDER(FIELD_DESCRIPTION) +#define add_guid() FIELD_ADDER(FIELD_GUID) + +int add(); +int add_default(enum field); +int add_field(enum field,const char*,const char*); + +#endif diff --git a/rss/inc/default.h b/rss/inc/default.h new file mode 100644 index 0000000..4fe7fa9 --- /dev/null +++ b/rss/inc/default.h @@ -0,0 +1,14 @@ +#ifndef __DEFAULT_H_ +#define __DEFAULT_H_ + +#include +#include + +#include +#include +#include +#include + +int defaults(); + +#endif diff --git a/rss/inc/escape.h b/rss/inc/escape.h new file mode 100644 index 0000000..8b6f24a --- /dev/null +++ b/rss/inc/escape.h @@ -0,0 +1,11 @@ +#ifndef __ESCAPE_H_ +#define __ESCAPE_H_ + +#include +#include + +#include + +int escape(char*,size_t); + +#endif diff --git a/rss/inc/info.h b/rss/inc/info.h new file mode 100644 index 0000000..f6ed0cb --- /dev/null +++ b/rss/inc/info.h @@ -0,0 +1,13 @@ +#ifndef __INFO_H_ +#define __INFO_H_ + +#include +#include +#include + +#define INFO_DEFAULT_DESCRIPTION "(no description)" +#define INFO_DEFAULT_LANGUAGE "en-us" + +int info(); + +#endif diff --git a/rss/inc/log.h b/rss/inc/log.h new file mode 100644 index 0000000..dfd9c09 --- /dev/null +++ b/rss/inc/log.h @@ -0,0 +1,15 @@ +#ifndef __LOG_H_ +#define __LOG_H_ + +#include +#include + +extern FILE *output_stream; + +#define log_err(...) log_message(stderr,__VA_ARGS__) +#define out(...) log_message(output_stream,__VA_ARGS__); + +void log_close(); +void log_message(FILE*,const char*,...); + +#endif diff --git a/rss/inc/main.h b/rss/inc/main.h new file mode 100644 index 0000000..504c00c --- /dev/null +++ b/rss/inc/main.h @@ -0,0 +1,15 @@ +#ifndef __MAIN_H_ +#define __MAIN_H_ + +#include +#include +#include + +#include +#include +#include +#include + +int main(int,char**); + +#endif diff --git a/rss/inc/meta.h b/rss/inc/meta.h new file mode 100644 index 0000000..b43fc56 --- /dev/null +++ b/rss/inc/meta.h @@ -0,0 +1,15 @@ +#ifndef __META_H_ +#define __META_H_ + +#include + +#include +#include +#include +#include +#include + +int meta(char*,char[FIELD_COUNT][BUF_SIZE]); +int meta_filename(char*,char*,size_t); + +#endif diff --git a/rss/inc/next.h b/rss/inc/next.h new file mode 100644 index 0000000..7125b6c --- /dev/null +++ b/rss/inc/next.h @@ -0,0 +1,10 @@ +#ifndef __NEXT_H_ +#define __NEXT_H_ + +#include + +#define BUF_SIZE 4096 + +int next(char*,size_t); + +#endif diff --git a/rss/inc/opt.h b/rss/inc/opt.h new file mode 100644 index 0000000..bf000a9 --- /dev/null +++ b/rss/inc/opt.h @@ -0,0 +1,35 @@ +#ifndef __OPT_H_ +#define __OPT_H_ + +#include +#include +#include +#include + +#include +#include +#include + +struct channel_options { + char *title; + char *link; + char *description; + char *language; + char *last_build_date; +}; + +struct global_options { + struct channel_options channel; + char *feedurl; +}; + +extern struct global_options global_opts; + +int opt_set_and_parse_meta(char*); +int opt_set_description(char*); +int opt_set_feedurl(char*); +int opt_set_link(char*); +int opt_set_name(char*); +int opt_set_output(char*); + +#endif diff --git a/rss/inc/rss.h b/rss/inc/rss.h new file mode 100644 index 0000000..97c20cd --- /dev/null +++ b/rss/inc/rss.h @@ -0,0 +1,29 @@ +#ifndef __RSS_H_ +#define __RSS_H_ + +enum field { + FIELD_TITLE = 0, + FIELD_LINK, + FIELD_PUBDATE, + FIELD_DESCRIPTION, + FIELD_GUID, + FIELD_LANGUAGE, + FIELD_COUNT +}; + +#define XML_START "<" +#define XML_CLOSE "" + +#define FIELD_TITLE_STRING "title" +#define FIELD_LINK_STRING "link" +#define FIELD_PUBDATE_STRING "pubDate" +#define FIELD_DESCRIPTION_STRING "description" +#define FIELD_GUID_STRING "guid" +#define FIELD_LANGUAGE_STRING "language" + +#define FIELD_STRING_LOADER(x) x ## _STRING +#define FIELD_PREFIX(x) XML_START x XML_END +#define FIELD_SUFFIX(x) XML_CLOSE x XML_END + +#endif diff --git a/rss/inc/util.h b/rss/inc/util.h new file mode 100644 index 0000000..448c56d --- /dev/null +++ b/rss/inc/util.h @@ -0,0 +1,9 @@ +#ifndef __UTIL_H_ +#define __UTIL_H_ + +#include +#include + +int now_string(char**); + +#endif diff --git a/rss/src/add.c b/rss/src/add.c new file mode 100644 index 0000000..e0b9e2d --- /dev/null +++ b/rss/src/add.c @@ -0,0 +1,65 @@ +#include + +char bufs[FIELD_COUNT][BUF_SIZE] = {0}; + +int add() { + int i; + + // clear bufs + for(i=0;i"); + if(add_title()<0) { return -1; } + if(add_link()<0) { return -1; } + if(add_pubdate()<0) { return -1; } + if(add_description()<0) { return -1; } + if(add_guid()<0) { return -1; } + out(""); + return 1; +} + +int add_field(enum field field, const char *prefix, const char *suffix) { + if(strlen(bufs[field])>0) { + out("%s",prefix); + out("%s",bufs[field]); + out("%s",suffix); + } else { + if(add_default(field)<0) { return -1; } + } + + return 1; +} + +int add_default(enum field type) { + char *timebuf; + switch(type) { + case FIELD_TITLE: + case FIELD_LINK: + log_err("missing required field...aborting...\n"); + return -1; + case FIELD_DESCRIPTION: + out("no description"); + break; + case FIELD_GUID: + break; + case FIELD_PUBDATE: + if(now_string(&timebuf)<0) { return -1; } + out("%s",timebuf); + free(timebuf); + break; + default: + log_err("unknown field type\n"); + return -1; + } + + return 1; +} diff --git a/rss/src/default.c b/rss/src/default.c new file mode 100644 index 0000000..860d6a3 --- /dev/null +++ b/rss/src/default.c @@ -0,0 +1,16 @@ +#include + +struct global_options global_opts = { { NULL }, NULL }; + +int defaults() { + struct channel_options *ch; + + output_stream = stdout; + if(atexit(log_close)!=0) { return -1; } + + ch = &(global_opts.channel); + + if(now_string(&(global_opts.channel.last_build_date))<0) { return -1; } + + return 1; +} diff --git a/rss/src/escape.c b/rss/src/escape.c new file mode 100644 index 0000000..2e7a3f9 --- /dev/null +++ b/rss/src/escape.c @@ -0,0 +1,84 @@ +#include + +int escape(char *buf, size_t buf_size) { + char *p, *p2, *tmpbuf; + char c; + size_t len; + int left; + + if(NULL==buf) { return -1; } + if(buf_size<=0) { return -1; } + if(strlen(buf)>buf_size) { return -1; } + + tmpbuf = NULL; + + char to_escape[] = "<\\>&"; + + p = strpbrk(buf,to_escape); + p2 = buf; + while(NULL!=p) { + if(NULL==tmpbuf) { + tmpbuf = malloc(buf_size); + if(NULL==tmpbuf) { + log_err("allocation error\n"); + return -1; + } + tmpbuf[0] = '\0'; + } + + c = p[0]; + p[0] = '\0'; + p++; + + strcat(tmpbuf,p2); + + left = buf_size - strlen(tmpbuf); + + switch(c) { + case '<': + if(left<=4) { goto panic; } + strcat(tmpbuf,"<"); + break; + case '\\': + if(p[0]=='n') { + if(left<1) { goto panic; } + strcat(tmpbuf,"\n"); + p++; + } + break; + case '>': + if(left<=4) { goto panic; } + strcat(tmpbuf,">"); + break; + case '&': + if(left<=5) { goto panic; } + strcat(tmpbuf,"&"); + break; + default: + log_err("unknown escape character: %c\n",c); + goto panic; + } + + p2 = p; + p = strpbrk(p,to_escape); + } + + if(tmpbuf!=NULL) { + strcat(tmpbuf,p2); + len = strlen(tmpbuf); + if(len>buf_size) { + log_err("escaped string larger than buffer...\n"); + goto panic; + } + + strcpy(buf,tmpbuf); + free(tmpbuf); + } + + return 1; + panic: + if(tmpbuf!=NULL) { + free(tmpbuf); + } + return -1; +} diff --git a/rss/src/info.c b/rss/src/info.c new file mode 100644 index 0000000..8dc9d5e --- /dev/null +++ b/rss/src/info.c @@ -0,0 +1,36 @@ +#include + +int info() { + struct channel_options *ch; + + ch = &(global_opts.channel); + + if(NULL==ch->title) { + log_err("missing channel title\n"); + return -1; + } + + if(NULL==ch->link) { + log_err("missing channel link\n"); + return -1; + } + + out("%s",global_opts.channel.title); + out("%s",global_opts.channel.link); + + if(NULL==ch->description) { + out("%s",INFO_DEFAULT_DESCRIPTION); + } else { + out("%s",ch->description); + } + + if(NULL==ch->language) { + out("%s",INFO_DEFAULT_LANGUAGE); + } else { + out("%s",global_opts.channel.language); + } + + out("%s",global_opts.channel.last_build_date); + + return 1; +} diff --git a/rss/src/log.c b/rss/src/log.c new file mode 100644 index 0000000..1a6da1b --- /dev/null +++ b/rss/src/log.c @@ -0,0 +1,16 @@ +#include + +FILE *output_stream; + +void log_close() { + if(output_stream!=stdout) { + fclose(output_stream); + } +} + +void log_message(FILE *out_stream, const char *format,...) { + va_list args; + va_start(args,format); + vfprintf(out_stream,format,args); + va_end(args); +} diff --git a/rss/src/main.c b/rss/src/main.c new file mode 100644 index 0000000..cf82e47 --- /dev/null +++ b/rss/src/main.c @@ -0,0 +1,69 @@ +#include + +static struct option long_options[] = { + {"description", required_argument, 0, 'd'}, + {"feedurl", required_argument, 0, 'f'}, + {"link", required_argument, 0, 'l'}, + {"meta", required_argument, 0, 'm'}, + {"name", required_argument, 0, 'n'}, + {"output", required_argument, 0, 'o'}, + {0,0,0,0} +}; + +int main(int argc, char **argv) { + int c,i; + + if(defaults()<0) { return EXIT_FAILURE; } + + while(1) { + int option_index = 0; + + if((c = getopt_long(argc,argv,"d:f:l:m:n:o:",long_options,&option_index))==-1) { break; } + switch(c) { + case 0: + if(long_options[option_index].flag!=0) { break; } + log_err("option %s", long_options[option_index].name); + if(optarg) { + log_err(" with arg %s",optarg); + } + log_err("\n"); + return EXIT_FAILURE; + + break; + case 'd': + if(opt_set_description(optarg)<0) { return EXIT_FAILURE; } + break; + case 'f': + if(opt_set_feedurl(optarg)<0) { return EXIT_FAILURE; } + break; + case 'l': + if(opt_set_link(optarg)<0) { return EXIT_FAILURE; } + break; + case 'm': + if(opt_set_and_parse_meta(optarg)<0) { return EXIT_FAILURE; } + break; + case 'n': + if(opt_set_name(optarg)<0) { return EXIT_FAILURE; } + break; + case 'o': + if(opt_set_output(optarg)<0) { return EXIT_FAILURE; } + break; + case '?': + default: + return EXIT_FAILURE; + } + } + + out(""); + out(""); + + if(info()<0) { return EXIT_FAILURE; } + + while((i = add())>0) { } + + if(i<0) { return EXIT_FAILURE; } + + out("\n"); + + return EXIT_SUCCESS; +} diff --git a/rss/src/meta.c b/rss/src/meta.c new file mode 100644 index 0000000..9cb2bdc --- /dev/null +++ b/rss/src/meta.c @@ -0,0 +1,100 @@ +#include + +int meta_filename(char *file, char *out, size_t out_len) { + char *p; + size_t len; + + p = strrchr(file,'/'); + if(NULL==p) { + p = file; + } else { + p++; + } + + len = p-file; + // prefix length, "." [1], suffix length, ".meta", '\0' + if((len+1+strlen(p)+5+1)>out_len) { + return -1; + } + + if(len>0) { + strncpy(out,file,len); + } + + out[len] = '.'; + out[len+1] = '\0'; + strcat(out,p); + strcat(out,".meta"); + + return 1; +} + +int meta(char *meta_file, char bufs[FIELD_COUNT][BUF_SIZE]) { + char tmp[BUF_SIZE]; + char *p,*p2; + FILE *fp; + int i; + + if(NULL==meta_file) { + if(meta_filename(bufs[FIELD_TITLE],tmp,BUF_SIZE)<0) { return -1; } + fp = fopen(tmp,"r"); + } else { + fp = fopen(meta_file,"r"); + bufs[FIELD_TITLE][0] = '\0'; + } + + if(NULL==fp) { return 0; } + + // FIELD_TITLE=0 + for(i=1;i + +int next(char *buf, size_t buf_size) { + int i; + + if(NULL==buf) { return -1; } + + i = 0; + while((buf[i] = getchar())!=EOF) { + if(buf[i]=='\n') { break; } + if(i>=buf_size) { return -1; } + i++; + } + + buf[i] = '\0'; + + return (i>0); +} diff --git a/rss/src/opt/desc.c b/rss/src/opt/desc.c new file mode 100644 index 0000000..00129df --- /dev/null +++ b/rss/src/opt/desc.c @@ -0,0 +1,19 @@ +#include + +int opt_set_description(char *desc) { + char *p; + size_t len; + + if(NULL==desc) { return -1; } + + len = strlen(desc); + if(len==0) { return -1; } + + p = malloc(sizeof(char)*(len+1)); + if(NULL==p) { return -1; } + + strcpy(p,desc); + global_opts.channel.description = p; + + return 1; +} diff --git a/rss/src/opt/feed.c b/rss/src/opt/feed.c new file mode 100644 index 0000000..321e084 --- /dev/null +++ b/rss/src/opt/feed.c @@ -0,0 +1,26 @@ +#include + +int opt_set_feedurl(char *url) { + char *p; + size_t len; + + if(NULL==url) { return -1; } + + len = strlen(url); + if(len==0) { return -1; } + + if(url[len-1]!='/') { len++; } + + p = malloc(sizeof(char)*(len+1)); + if(NULL==p) { return -1; } + + strcpy(p,url); + + if(url[len-1]!='/') { + strcat(p,"/"); + } + + global_opts.feedurl = p; + + return 1; +} diff --git a/rss/src/opt/link.c b/rss/src/opt/link.c new file mode 100644 index 0000000..8d2d4e1 --- /dev/null +++ b/rss/src/opt/link.c @@ -0,0 +1,19 @@ +#include + +int opt_set_link(char *link) { + char *p; + size_t len; + + if(NULL==link) { return -1; } + + len = strlen(link); + if(len==0) { return -1; } + + p = malloc(sizeof(char)*(len+1)); + if(NULL==p) { return -1; } + + strcpy(p,link); + global_opts.channel.link = p; + + return 1; +} diff --git a/rss/src/opt/meta.c b/rss/src/opt/meta.c new file mode 100644 index 0000000..cc7e21d --- /dev/null +++ b/rss/src/opt/meta.c @@ -0,0 +1,74 @@ +#include + +int opt_set_and_parse_meta(char *filename) { + char bufs[FIELD_COUNT][BUF_SIZE] = {0}; + char *p; + size_t len, title_len, desc_len, link_len, guid_len, lan_len; + struct channel_options *ch; + int i; + + if(NULL==filename) { return -1; } + + if((i = meta(filename,bufs))<0) { return -1; } + if(i==0) { + log_err("missing meta file: %s\n",filename); + return -1; + } + + ch = &(global_opts.channel); + + title_len = strlen(bufs[FIELD_TITLE]); + desc_len = strlen(bufs[FIELD_DESCRIPTION]); + link_len = strlen(bufs[FIELD_LINK]); + guid_len = strlen(bufs[FIELD_GUID]); + lan_len = strlen(bufs[FIELD_LANGUAGE]); + len = title_len+desc_len+link_len+guid_len+lan_len+5; + + if(len>0) { + p = malloc(sizeof(char)*(len)); + if(NULL==p) { return -1; } + } + + if(title_len>0) { + ch->title = p; + strcpy(ch->title,bufs[FIELD_TITLE]); + p += title_len+1; + } + + if(desc_len>0) { + ch->description = p; + strcpy(ch->description,bufs[FIELD_DESCRIPTION]); + p += desc_len+1; + } + + if(link_len>0) { + if(NULL==ch->description) { + ch->description = p; + ch->description[0] = '\0'; + } else { + strcat(ch->description,"\n"); + } + + strcat(ch->description,bufs[FIELD_LINK]); + p += link_len+1; + } + + if(guid_len>0) { + if(NULL==ch->description) { + ch->description = p; + ch->description[0] = '\0'; + } else { + strcat(ch->description,"\n"); + } + + strcat(ch->description,bufs[FIELD_GUID]); + p += guid_len+1; + } + + if(lan_len>0) { + ch->language = p; + strcpy(ch->language,bufs[FIELD_LANGUAGE]); + } + + return 1; +} diff --git a/rss/src/opt/name.c b/rss/src/opt/name.c new file mode 100644 index 0000000..9883836 --- /dev/null +++ b/rss/src/opt/name.c @@ -0,0 +1,19 @@ +#include + +int opt_set_name(char *name) { + char *p; + size_t len; + + if(NULL==name) { return -1; } + + len = strlen(name); + if(len==0) { return -1; } + + p = malloc(sizeof(char)*(len+1)); + if(NULL==p) { return -1; } + + strcpy(p,name); + global_opts.channel.title = p; + + return 1; +} diff --git a/rss/src/opt/out.c b/rss/src/opt/out.c new file mode 100644 index 0000000..7d1956d --- /dev/null +++ b/rss/src/opt/out.c @@ -0,0 +1,16 @@ +#include + +int opt_set_output(char *target) { + FILE *fp; + + if(NULL==target) { return -1; } + + fp = fopen(target,"w"); + if(NULL==fp) { + log_err("unable to open %s for writing\n",target); + return -1; + } + + output_stream = fp; + return 1; +} diff --git a/rss/src/util/now.c b/rss/src/util/now.c new file mode 100644 index 0000000..b761e07 --- /dev/null +++ b/rss/src/util/now.c @@ -0,0 +1,15 @@ +#include + +int now_string(char **timebuf) { + if(NULL==timebuf) { return -1; } + + *timebuf = malloc(sizeof(char)*70); + if(NULL==(*timebuf)) { return -1; } + + struct tm now; + + now = *localtime(&(time_t){time(NULL)}); + if(!(strftime((*timebuf),sizeof(char)*70, "%a, %d %b %Y %H:%M:%S %z", &now))) { return -1; } + + return 1; +} diff --git a/rss/test/add.tests.c b/rss/test/add.tests.c new file mode 100644 index 0000000..513fbb0 --- /dev/null +++ b/rss/test/add.tests.c @@ -0,0 +1,52 @@ +#include "add.tests.h" + +int main() { + setup_env(); + + add_basic_tests(); + + clean_env(); + + return EXIT_SUCCESS; +} + +static int i = 0; + +void add_basic_tests() { + FILE *fp; + char buf[1024]; + + assert(opt_set_output(DATADIR "feed.xml")==1); + + assert(add()==1); + assert(add()==-1); + + log_close(); + + fp = fopen(DATADIR "feed.xml","r"); + assert(NULL!=fp); + + assert(fread(buf,sizeof(char),1024,fp)==260); + assert(fclose(fp)==0); + + assert(memcmp(buf,"/tmp/rss-create/test/test2.txt0bc6c0de1ff1d26d84187a4d42fb9a2d0bc73a1d.torrentSat, 15 May 2021 11:01:04 +0000no description/tmp/rss-create/test/test.txt",260)==0); +} + +int next_dummy(char *buf, size_t size) { + switch(i) { + case 0: + snprintf(buf,size,FILE_2); + break; + case 1: + snprintf(buf,size,FILE_1); + break; + case 2: + snprintf(buf,size,FILE_3); + break; + default: + snprintf(buf,size,"notarealfile"); + break; + } + i++; + return 1; +} diff --git a/rss/test/add.tests.h b/rss/test/add.tests.h new file mode 100644 index 0000000..d3985f7 --- /dev/null +++ b/rss/test/add.tests.h @@ -0,0 +1,15 @@ +#ifndef __ADD_TESTS_H_ +#define __ADD_TESTS_H_ + +#include "test_utils.h" + +#include + +int main(); +void add_basic_tests(); +int next_dummy(char*,size_t); + +#define next(buf,size) next_dummy(buf,size) +#include "../src/add.c" + +#endif diff --git a/rss/test/escape.tests.c b/rss/test/escape.tests.c new file mode 100644 index 0000000..83de5c1 --- /dev/null +++ b/rss/test/escape.tests.c @@ -0,0 +1,55 @@ +#include "escape.tests.h" + +int main() { + setup_env(); + + escape_basic_tests(); + + clean_env(); + + return EXIT_SUCCESS; +} + +#define BUF_SIZE 1024 +void escape_basic_tests() { + char buf[BUF_SIZE]; + + assert(escape(NULL,0)==-1); + assert(escape(buf,0)==-1); + + strcpy(buf,"<"); + assert(escape(buf,BUF_SIZE)==1); + assert(strcmp(buf,"<")==0); + + strcpy(buf,">"); + assert(escape(buf,BUF_SIZE)==1); + assert(strcmp(buf,">")==0); + + strcpy(buf,"\\n"); + assert(escape(buf,BUF_SIZE)==1); + assert(strcmp(buf,"\n")==0); + + strcpy(buf,"&"); + assert(escape(buf,BUF_SIZE)==1); + assert(strcmp(buf,"&")==0); + + strcpy(buf,"this"); + assert(escape(buf,6)==1); + + strcpy(buf,"this&"); + assert(escape(buf,6)==-1); + + strcpy(buf,"this<"); + assert(escape(buf,6)==-1); + + strcpy(buf,"this>"); + assert(escape(buf,6)==-1); + + strcpy(buf,"this\\n"); + assert(escape(buf,4)==-1); + + strcpy(buf,"lskjdflaksjdfl<>slkdfjlsdf&lsdjkfsldfjsldkfj\\nsldkjfslkdjfk<<<<>>>>"); + assert(escape(buf,10)==-1); + assert(escape(buf,1024)==1); + assert(strcmp(buf,"lskjdflaksjdfl<>slkdfjlsdf&lsdjkfsldfjsldkfj\nsldkjfslkdjfk<<<<>>>>")==0); +} diff --git a/rss/test/escape.tests.h b/rss/test/escape.tests.h new file mode 100644 index 0000000..d2eb9b3 --- /dev/null +++ b/rss/test/escape.tests.h @@ -0,0 +1,11 @@ +#ifndef __ESCAPE_TESTS_H_ +#define __ESCAPE_TESTS_H_ + +#include "test_utils.h" + +#include + +int main(); +void escape_basic_tests(); + +#endif diff --git a/rss/test/info.tests.c b/rss/test/info.tests.c new file mode 100644 index 0000000..9dafb6b --- /dev/null +++ b/rss/test/info.tests.c @@ -0,0 +1,41 @@ +#include "info.tests.h" + +int main() { + setup_env(); + + info_basic_tests(); + + clean_env(); + + return EXIT_SUCCESS; +} + +void info_basic_tests() { + FILE *fp; + char buf[1024]; + char buf2[1024]; + + assert(opt_set_output(DATADIR "feed.xml")==1); + + assert(info()==-1); + + global_opts.channel.title = "test title"; + assert(info()==-1); + + global_opts.channel.link = "https://test.com/test/feed.xml"; + assert(now_string(&(global_opts.channel.last_build_date))==1); + + assert(info()==1); + + log_close(); + + fp = fopen(DATADIR "feed.xml","r"); + assert(NULL!=fp); + + assert(fread(buf,sizeof(char),1024,fp)==199); + assert(fclose(fp)==0); + + sprintf(buf2,"test titlehttps://test.com/test/feed.xml(no description)en-us%s",global_opts.channel.last_build_date); + + assert(memcmp(buf,buf2,199)==0); +} diff --git a/rss/test/info.tests.h b/rss/test/info.tests.h new file mode 100644 index 0000000..8fbc311 --- /dev/null +++ b/rss/test/info.tests.h @@ -0,0 +1,11 @@ +#ifndef __INFO_TESTS_H_ +#define __INFO_TESTS_H_ + +#include "test_utils.h" + +#include + +int main(); +void info_basic_tests(); + +#endif diff --git a/rss/test/meta.tests.c b/rss/test/meta.tests.c new file mode 100644 index 0000000..05f3b8e --- /dev/null +++ b/rss/test/meta.tests.c @@ -0,0 +1,219 @@ +#include "meta.tests.h" + +int main() { + setup_env(); + + meta_basic_tests(); + meta_feedurl_test(); + meta_filename_basic_test(); + meta_duplicate_field_test(); + + clean_env(); + + return EXIT_SUCCESS; +} + +void meta_basic_tests() { + char bufs[FIELD_COUNT][BUF_SIZE]; + + assert(meta("nonexistentfile",bufs)==0); + strcpy(bufs[FIELD_TITLE],"nonexistentfile"); + assert(meta(NULL,bufs)==0); + + assert(meta(FILE_1,bufs)==-1); + strcpy(bufs[FIELD_TITLE],FILE_1); + assert(meta(NULL,bufs)==0); + + assert(meta(FILE_2,bufs)==-1); + strcpy(bufs[FIELD_TITLE],FILE_2); + assert(meta(NULL,bufs)==1); + assert(strcmp(bufs[FIELD_TITLE],FILE_2)==0); + assert(strcmp(bufs[FIELD_LINK],"0bc6c0de1ff1d26d84187a4d42fb9a2d0bc73a1d.torrent")==0); + assert(strcmp(bufs[FIELD_PUBDATE],"Sat, 15 May 2021 11:01:04 +0000")==0); + assert(bufs[FIELD_DESCRIPTION][0]=='\0'); + assert(bufs[FIELD_GUID][0]=='\0'); + assert(bufs[FIELD_LANGUAGE][0]=='\0'); + + assert(meta(FILE_3,bufs)==-1); + strcpy(bufs[FIELD_TITLE],FILE_3); + assert(meta(NULL,bufs)==0); + + assert(meta(FILE_4,bufs)==1); + assert(bufs[FIELD_TITLE][0]=='\0'); + assert(bufs[FIELD_LINK][0]=='\0'); + assert(strcmp(bufs[FIELD_PUBDATE],"Sun, 16 May 2021 02:55:07 +0000")==0); + assert(strcmp(bufs[FIELD_DESCRIPTION],"this a<Mactual>ly show\na new line")==0); + assert(bufs[FIELD_GUID][0]=='\0'); + assert(bufs[FIELD_LANGUAGE][0]=='\0'); + strcpy(bufs[FIELD_TITLE],FILE_4); + assert(meta(NULL,bufs)==0); + + assert(meta(FILE_5,bufs)==1); + assert(bufs[FIELD_TITLE][0]=='\0'); + assert(strcmp(bufs[FIELD_LINK],"0bc6c0de1ff1d26d84187a4d42fb9a2d0bc73a1d.torrent")==0); + assert(strcmp(bufs[FIELD_PUBDATE],"Sat, 15 May 2021 11:01:04 +0000")==0); + assert(bufs[FIELD_DESCRIPTION][0]=='\0'); + assert(bufs[FIELD_GUID][0]=='\0'); + assert(bufs[FIELD_LANGUAGE][0]=='\0'); + strcpy(bufs[FIELD_TITLE],FILE_5); + assert(meta(NULL,bufs)==0); + + assert(meta(FILE_6,bufs)==-1); + strcpy(bufs[FIELD_TITLE],FILE_6); + assert(meta(NULL,bufs)==0); + + assert(meta(FILE_7,bufs)==1); + assert(bufs[FIELD_TITLE][0]=='\0'); + assert(bufs[FIELD_LINK][0]=='\0'); + assert(bufs[FIELD_PUBDATE][0]=='\0'); + assert(bufs[FIELD_DESCRIPTION][0]=='\0'); + assert(strcmp(bufs[FIELD_GUID],"hello")==0); + assert(bufs[FIELD_LANGUAGE][0]=='\0'); + strcpy(bufs[FIELD_TITLE],FILE_7); + assert(meta(NULL,bufs)==0); + + assert(meta(FILE_8,bufs)==-1); + strcpy(bufs[FIELD_TITLE],FILE_8); + assert(meta(NULL,bufs)==1); + assert(strcmp(bufs[FIELD_TITLE],FILE_8)==0); + assert(bufs[FIELD_LINK][0]=='\0'); + assert(bufs[FIELD_PUBDATE][0]=='\0'); + assert(bufs[FIELD_DESCRIPTION][0]=='\0'); + assert(strcmp(bufs[FIELD_GUID],"hello")==0); + assert(bufs[FIELD_LANGUAGE][0]=='\0'); + + assert(meta(FILE_9,bufs)==1); + assert(bufs[FIELD_TITLE][0]=='\0'); + assert(bufs[FIELD_LINK][0]=='\0'); + assert(bufs[FIELD_PUBDATE][0]=='\0'); + assert(strcmp(bufs[FIELD_DESCRIPTION],"this is a test description")==0); + assert(bufs[FIELD_GUID][0]=='\0'); + assert(bufs[FIELD_LANGUAGE][0]=='\0'); + strcpy(bufs[FIELD_TITLE],FILE_9); + assert(meta(NULL,bufs)==0); + + assert(meta(FILE_10,bufs)==1); + assert(bufs[FIELD_TITLE][0]=='\0'); + assert(bufs[FIELD_LINK][0]=='\0'); + assert(bufs[FIELD_PUBDATE][0]=='\0'); + assert(bufs[FIELD_DESCRIPTION][0]=='\0'); + assert(bufs[FIELD_GUID][0]=='\0'); + assert(strcmp(bufs[FIELD_LANGUAGE],"en-uk")==0); + strcpy(bufs[FIELD_TITLE],FILE_10); + assert(meta(NULL,bufs)==0); + + assert(meta(DIRECTORY_1,bufs)==1); + assert(bufs[FIELD_TITLE][0]=='\0'); + assert(bufs[FIELD_LINK][0]=='\0'); + assert(bufs[FIELD_PUBDATE][0]=='\0'); + assert(bufs[FIELD_DESCRIPTION][0]=='\0'); + assert(bufs[FIELD_GUID][0]=='\0'); + assert(bufs[FIELD_LANGUAGE][0]=='\0'); + + assert(meta(DIRECTORY_2,bufs)==1); + assert(bufs[FIELD_TITLE][0]=='\0'); + assert(bufs[FIELD_LINK][0]=='\0'); + assert(bufs[FIELD_PUBDATE][0]=='\0'); + assert(bufs[FIELD_DESCRIPTION][0]=='\0'); + assert(bufs[FIELD_GUID][0]=='\0'); + assert(bufs[FIELD_LANGUAGE][0]=='\0'); + + assert(meta(DIRECTORY_3,bufs)==1); + assert(bufs[FIELD_TITLE][0]=='\0'); + assert(bufs[FIELD_LINK][0]=='\0'); + assert(bufs[FIELD_PUBDATE][0]=='\0'); + assert(bufs[FIELD_DESCRIPTION][0]=='\0'); + assert(bufs[FIELD_GUID][0]=='\0'); + assert(bufs[FIELD_LANGUAGE][0]=='\0'); + + assert(meta(DIRECTORY_4,bufs)==1); + assert(bufs[FIELD_TITLE][0]=='\0'); + assert(bufs[FIELD_LINK][0]=='\0'); + assert(bufs[FIELD_PUBDATE][0]=='\0'); + assert(bufs[FIELD_DESCRIPTION][0]=='\0'); + assert(bufs[FIELD_GUID][0]=='\0'); + assert(bufs[FIELD_LANGUAGE][0]=='\0'); + + strcpy(bufs[FIELD_TITLE],DIRECTORY_1); + assert(meta(NULL,bufs)==1); + assert(strcmp(bufs[FIELD_TITLE],DIRECTORY_1)==0); + assert(bufs[FIELD_LINK][0]=='\0'); + assert(bufs[FIELD_PUBDATE][0]=='\0'); + assert(strcmp(bufs[FIELD_DESCRIPTION],"this is a test description")==0); + assert(bufs[FIELD_GUID][0]=='\0'); + assert(bufs[FIELD_LANGUAGE][0]=='\0'); + + strcpy(bufs[FIELD_TITLE],DIRECTORY_2); + assert(meta(NULL,bufs)==1); + assert(strcmp(bufs[FIELD_TITLE],DIRECTORY_2)==0); + assert(bufs[FIELD_LINK][0]=='\0'); + assert(bufs[FIELD_PUBDATE][0]=='\0'); + assert(bufs[FIELD_DESCRIPTION][0]=='\0'); + assert(bufs[FIELD_GUID][0]=='\0'); + assert(strcmp(bufs[FIELD_LANGUAGE],"en-uk")==0); + + strcpy(bufs[FIELD_TITLE],DIRECTORY_3); + assert(meta(NULL,bufs)==1); + assert(strcmp(bufs[FIELD_TITLE],DIRECTORY_3)==0); + assert(bufs[FIELD_LINK][0]=='\0'); + assert(strcmp(bufs[FIELD_PUBDATE],"Sun, 16 May 2021 02:55:07 +0000")==0); + assert(strcmp(bufs[FIELD_DESCRIPTION],"this a<Mactual>ly show\na new line")==0); + assert(bufs[FIELD_GUID][0]=='\0'); + assert(bufs[FIELD_LANGUAGE][0]=='\0'); + + strcpy(bufs[FIELD_TITLE],DIRECTORY_4); + assert(meta(NULL,bufs)==0); +} + +void meta_feedurl_test() { + char bufs[FIELD_COUNT][BUF_SIZE]; + + strcpy(bufs[FIELD_TITLE],FILE_2); + assert(meta(NULL,bufs)==1); + assert(strcmp(bufs[FIELD_LINK],"0bc6c0de1ff1d26d84187a4d42fb9a2d0bc73a1d.torrent")==0); + + assert(meta(FILE_5,bufs)==1); + assert(strcmp(bufs[FIELD_LINK],"0bc6c0de1ff1d26d84187a4d42fb9a2d0bc73a1d.torrent")==0); + + // opt_set_feedurl() should make sure that feedurl has '/' at end + char testurl[] = "https://example.com/feeds/"; + global_opts.feedurl = testurl; + + strcpy(bufs[FIELD_TITLE],FILE_2); + assert(meta(NULL,bufs)==1); + assert(strcmp(bufs[FIELD_LINK],"https://example.com/feeds/0bc6c0de1ff1d26d84187a4d42fb9a2d0bc73a1d.torrent")==0); + + assert(meta(FILE_5,bufs)==1); + assert(strcmp(bufs[FIELD_LINK],"https://example.com/feeds/0bc6c0de1ff1d26d84187a4d42fb9a2d0bc73a1d.torrent")==0); + + global_opts.feedurl = NULL; +} + +void meta_filename_basic_test() { + char tmp[BUF_SIZE]; + char tmp2[BUF_SIZE]; + + strcpy(tmp,"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + + assert(meta_filename(tmp,tmp2,10)==-1); + assert(meta_filename(tmp,tmp2,BUF_SIZE)==1); + assert(strcmp(tmp2,".aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.meta")==0); + + strcpy(tmp,"/this/is/a/test/path"); + assert(meta_filename(tmp,tmp2,10)==-1); + assert(meta_filename(tmp,tmp2,26)==-1); + assert(meta_filename(tmp,tmp2,27)==1); + assert(strcmp(tmp2,"/this/is/a/test/.path.meta")==0); +} + +void meta_duplicate_field_test() { + char bufs[FIELD_COUNT][BUF_SIZE]; + + assert(meta(FILE_11,bufs)==1); + assert(strcmp(bufs[FIELD_TITLE],"test2")==0); + assert(strcmp(bufs[FIELD_LINK],"link2")==0); + assert(strcmp(bufs[FIELD_PUBDATE],"date2")==0); + assert(strcmp(bufs[FIELD_DESCRIPTION],"desc2")==0); + assert(strcmp(bufs[FIELD_GUID],"guid2")==0); + assert(strcmp(bufs[FIELD_LANGUAGE],"lan2")==0); +} diff --git a/rss/test/meta.tests.h b/rss/test/meta.tests.h new file mode 100644 index 0000000..e15cbe9 --- /dev/null +++ b/rss/test/meta.tests.h @@ -0,0 +1,14 @@ +#ifndef __META_TESTS_H_ +#define __META_TESTS_H_ + +#include "test_utils.h" + +#include + +int main(); +void meta_basic_tests(); +void meta_feedurl_test(); +void meta_filename_basic_test(); +void meta_duplicate_field_test(); + +#endif diff --git a/rss/test/next.tests.c b/rss/test/next.tests.c new file mode 100644 index 0000000..39c1540 --- /dev/null +++ b/rss/test/next.tests.c @@ -0,0 +1,32 @@ +#include "next.tests.h" + +int main() { + setup_env(); + + next_basic_tests(); + + clean_env(); + + return EXIT_SUCCESS; +} + +static int i = 0; + +void next_basic_tests() { + char buf[1024]; + + assert(next(NULL,0)==-1); + assert(next(buf,10)==-1); + i = 0; + assert(next(buf,1024)==1); + assert(strcmp(buf,"aaaaaaaaaaaaaaaaaaaa")==0); +} + +int getchar_dummy() { + if(i<20) { + i++; + return 'a'; + } else { + return '\n'; + } +} diff --git a/rss/test/next.tests.h b/rss/test/next.tests.h new file mode 100644 index 0000000..2d3a430 --- /dev/null +++ b/rss/test/next.tests.h @@ -0,0 +1,15 @@ +#ifndef __NEXT_TESTS_H_ +#define __NEXT_TESTS_H_ + +#include "test_utils.h" + +#include + +int main(); +void next_basic_tests(); +int getchar_dummy(); + +#define getchar() getchar_dummy() +#include "../src/next.c" + +#endif diff --git a/rss/test/opt.desc.tests.c b/rss/test/opt.desc.tests.c new file mode 100644 index 0000000..65c49e5 --- /dev/null +++ b/rss/test/opt.desc.tests.c @@ -0,0 +1,19 @@ +#include "opt.desc.tests.h" + +int main() { + setup_env(); + + opt_desc_basic_tests(); + + clean_env(); + + return EXIT_SUCCESS; +} + +void opt_desc_basic_tests() { + assert(opt_set_description(NULL)==-1); + assert(opt_set_description("")==-1); + + assert(opt_set_description("test")==1); + assert(strcmp(global_opts.channel.description,"test")==0); +} diff --git a/rss/test/opt.desc.tests.h b/rss/test/opt.desc.tests.h new file mode 100644 index 0000000..f5642d8 --- /dev/null +++ b/rss/test/opt.desc.tests.h @@ -0,0 +1,11 @@ +#ifndef __OPT_DESC_TESTS_H_ +#define __OPT_DESC_TESTS_H_ + +#include "test_utils.h" + +#include + +int main(); +void opt_desc_basic_tests(); + +#endif diff --git a/rss/test/opt.feed.tests.c b/rss/test/opt.feed.tests.c new file mode 100644 index 0000000..ad675a6 --- /dev/null +++ b/rss/test/opt.feed.tests.c @@ -0,0 +1,25 @@ +#include "opt.feed.tests.h" + +int main() { + setup_env(); + + opt_feed_basic_tests(); + + clean_env(); + + return EXIT_SUCCESS; +} + +void opt_feed_basic_tests() { + assert(opt_set_feedurl(NULL)==-1); + assert(opt_set_feedurl("")==-1); + + assert(opt_set_feedurl("https://test.com")==1); + assert(strcmp(global_opts.feedurl,"https://test.com/")==0); + + free(global_opts.feedurl); + global_opts.feedurl = NULL; + + assert(opt_set_feedurl("http://test.com/")==1); + assert(strcmp(global_opts.feedurl,"http://test.com/")==0); +} diff --git a/rss/test/opt.feed.tests.h b/rss/test/opt.feed.tests.h new file mode 100644 index 0000000..a0ff551 --- /dev/null +++ b/rss/test/opt.feed.tests.h @@ -0,0 +1,11 @@ +#ifndef __OPT_FEED_TESTS_H_ +#define __OPT_FEED_TESTS_H_ + +#include "test_utils.h" + +#include + +int main(); +void opt_feed_basic_tests(); + +#endif diff --git a/rss/test/opt.link.tests.c b/rss/test/opt.link.tests.c new file mode 100644 index 0000000..7542289 --- /dev/null +++ b/rss/test/opt.link.tests.c @@ -0,0 +1,19 @@ +#include "opt.link.tests.h" + +int main() { + setup_env(); + + opt_link_basic_tests(); + + clean_env(); + + return EXIT_SUCCESS; +} + +void opt_link_basic_tests() { + assert(opt_set_link(NULL)==-1); + assert(opt_set_link("")==-1); + + assert(opt_set_link("alksdjflasdjf")==1); + assert(strcmp(global_opts.channel.link,"alksdjflasdjf")==0); +} diff --git a/rss/test/opt.link.tests.h b/rss/test/opt.link.tests.h new file mode 100644 index 0000000..b45c32b --- /dev/null +++ b/rss/test/opt.link.tests.h @@ -0,0 +1,11 @@ +#ifndef __OPT_LINK_TESTS_H_ +#define __OPT_LINK_TESTS_H_ + +#include "test_utils.h" + +#include + +int main(); +void opt_link_basic_tests(); + +#endif diff --git a/rss/test/opt.meta.tests.c b/rss/test/opt.meta.tests.c new file mode 100644 index 0000000..6ca9118 --- /dev/null +++ b/rss/test/opt.meta.tests.c @@ -0,0 +1,21 @@ +#include "opt.meta.tests.h" + +int main() { + setup_env(); + + opt_set_and_parse_meta_basic_tests(); + + clean_env(); + + return EXIT_SUCCESS; +} + +void opt_set_and_parse_meta_basic_tests() { + assert(opt_set_and_parse_meta(NULL)==-1); + assert(opt_set_and_parse_meta("notarealfile")==-1); + + assert(opt_set_and_parse_meta(FILE_9)==1); + assert(NULL==global_opts.channel.title); + assert(strcmp(global_opts.channel.description,"this is a test description")==0); + assert(NULL==global_opts.channel.language); +} diff --git a/rss/test/opt.meta.tests.h b/rss/test/opt.meta.tests.h new file mode 100644 index 0000000..f7ae8e7 --- /dev/null +++ b/rss/test/opt.meta.tests.h @@ -0,0 +1,11 @@ +#ifndef __OPT_META_TESTS_H_ +#define __OPT_META_TESTS_H_ + +#include "test_utils.h" + +#include + +int main(); +void opt_set_and_parse_meta_basic_tests(); + +#endif diff --git a/rss/test/opt.name.tests.c b/rss/test/opt.name.tests.c new file mode 100644 index 0000000..f40d0d0 --- /dev/null +++ b/rss/test/opt.name.tests.c @@ -0,0 +1,19 @@ +#include "opt.name.tests.h" + +int main() { + setup_env(); + + opt_set_name_basic_tests(); + + clean_env(); + + return EXIT_SUCCESS; +} + +void opt_set_name_basic_tests() { + assert(opt_set_name(NULL)==-1); + assert(opt_set_name("")==-1); + + assert(opt_set_name("feedname")==1); + assert(strcmp(global_opts.channel.title,"feedname")==0); +} diff --git a/rss/test/opt.name.tests.h b/rss/test/opt.name.tests.h new file mode 100644 index 0000000..9d0fc2f --- /dev/null +++ b/rss/test/opt.name.tests.h @@ -0,0 +1,11 @@ +#ifndef __OPT_NAME_TESTS_H_ +#define __OPT_NAME_TESTS_H_ + +#include "test_utils.h" + +#include + +int main(); +void opt_set_name_basic_tests(); + +#endif diff --git a/rss/test/opt.out.tests.c b/rss/test/opt.out.tests.c new file mode 100644 index 0000000..3389841 --- /dev/null +++ b/rss/test/opt.out.tests.c @@ -0,0 +1,16 @@ +#include "opt.out.tests.h" + +int main() { + setup_env(); + + opt_out_basic_tests(); + + clean_env(); + + return EXIT_SUCCESS; +} + +void opt_out_basic_tests() { + assert(opt_set_output(NULL)==-1); + assert(opt_set_output("/tmp/tempoutfile")==1); +} diff --git a/rss/test/opt.out.tests.h b/rss/test/opt.out.tests.h new file mode 100644 index 0000000..2f3cf65 --- /dev/null +++ b/rss/test/opt.out.tests.h @@ -0,0 +1,11 @@ +#ifndef __OPT_OUT_TESTS_H_ +#define __OPT_OUT_TESTS_H_ + +#include "test_utils.h" + +#include + +int main(); +void opt_out_basic_tests(); + +#endif diff --git a/rss/test/test_utils.c b/rss/test/test_utils.c new file mode 100644 index 0000000..1b2e705 --- /dev/null +++ b/rss/test/test_utils.c @@ -0,0 +1,46 @@ +#include "test_utils.h" + +void clean_env() { + system("rm -rf " DATADIR); +} + +void reset_env() { + clean_env(); + setup_env(); +} + +void setup_env() { + clean_env(); + + create_test_directory(DATADIR); + create_test_directory(DIRECTORY_1); + create_test_directory(DIRECTORY_2); + create_test_directory(DIRECTORY_3); + create_test_directory(DIRECTORY_4); + CREATE(1); + CREATE(2); + CREATE(3); + CREATE(4); + CREATE(5); + CREATE(6); + CREATE(7); + CREATE(8); + CREATE(9); + CREATE(10); + CREATE(11); +} + +void create_test_directory(const char *directory) { + assert(mkdir(directory,0700)==0); +} + +void create_test_file(const char *filename, const char *contents) { + FILE *fp; + + printf("creating %s\n",filename); + fp = fopen(filename,"w+"); + assert(NULL!=fp); + + assert(strlen(contents)==fwrite(contents,sizeof(char),strlen(contents),fp)); + assert(fclose(fp)==0); +} diff --git a/rss/test/test_utils.h b/rss/test/test_utils.h new file mode 100644 index 0000000..5e3aff7 --- /dev/null +++ b/rss/test/test_utils.h @@ -0,0 +1,54 @@ +#ifndef __TEST_UTILS_H_ +#define __TEST_UTILS_H_ + +#include +#include +#include +#include +#include +#include + +#define DATADIR "/tmp/rss-create" + +#define DIRECTORY_1 DATADIR "/test" +#define DIRECTORY_2 DATADIR "/test2" +#define DIRECTORY_3 DATADIR "/test/test3" +#define DIRECTORY_4 DATADIR "/test/.dotfiles" + +#define FILE_1 DATADIR "/test/test.txt" +#define FILE_1_CONTENTS "test" +#define FILE_2 DATADIR "/test/test2.txt" +#define FILE_2_CONTENTS "test2" +#define FILE_3 DATADIR "/test/test3/hello.txt" +#define FILE_3_CONTENTS "test3" +#define FILE_4 DATADIR "/test/.test3.meta" +#define FILE_4_CONTENTS "description=nope\ndescription=this aly show\\na new line\npubDate=Sun, 16 May 2021 02:55:07 +0000\n" +#define FILE_5 DATADIR "/test/.test2.txt.meta" +#define FILE_5_CONTENTS "pubDate=Sat, 15 May 2021 11:01:04 +0000\nlink=0bc6c0de1ff1d26d84187a4d42fb9a2d0bc73a1d.torrent" +#define FILE_6 DATADIR "/test/.dotfiles/haha.txt" +#define FILE_6_CONTENTS "hahaha" + +#define FILE_7 DATADIR "/test2/.nope.txt.meta" +#define FILE_7_CONTENTS "guid=hello" +#define FILE_8 DATADIR "/test2/nope.txt" +#define FILE_8_CONTENTS "hahahasldkfjasldkfjaslkdfj" + +#define FILE_9 DATADIR "/.test.meta" +#define FILE_9_CONTENTS "description=this is a test description" +#define FILE_10 DATADIR "/.test2.meta" +#define FILE_10_CONTENTS "language=en-uk" + +#define FILE_11 DATADIR "/test/test4.txt" +#define FILE_11_CONTENTS "title=test\ntitle=test2\nlink=link1\nlink=link2\npubDate=date1\npubDate=date2\ndescription=desc1\ndescription=desc2\nguid=guid1\nguid=guid2\nlanguage=lan1\nlanguage=lan2" + +#define CREATE_FILE_NAME_LOADER(x) FILE_ ## x +#define CREATE_FILE_CONTENTS_LOADER(x) FILE_ ## x ## _CONTENTS +#define CREATE(x) create_test_file(CREATE_FILE_NAME_LOADER(x),CREATE_FILE_CONTENTS_LOADER(x)) + +void clean_env(); +void reset_env(); +void setup_env(); +void create_test_directory(const char*); +void create_test_file(const char*,const char*); + +#endif diff --git a/rss/test/util.now.tests.c b/rss/test/util.now.tests.c new file mode 100644 index 0000000..0ff4c9f --- /dev/null +++ b/rss/test/util.now.tests.c @@ -0,0 +1,20 @@ +#include "util.now.tests.h" + +int main() { + setup_env(); + + util_now_basic_tests(); + + clean_env(); + + return EXIT_SUCCESS; +} + +void util_now_basic_tests() { + char *p; + assert(now_string(NULL)==-1); + + assert(now_string(&p)==1); + + free(p); +} diff --git a/rss/test/util.now.tests.h b/rss/test/util.now.tests.h new file mode 100644 index 0000000..d9453b4 --- /dev/null +++ b/rss/test/util.now.tests.h @@ -0,0 +1,11 @@ +#ifndef __UTIL_NOW_TESTS_H_ +#define __UTIL_NOW_TESTS_H_ + +#include "test_utils.h" + +#include + +int main(); +void util_now_basic_tests(); + +#endif diff --git a/seederd/Makefile.am b/seederd/Makefile.am new file mode 100644 index 0000000..b5e4835 --- /dev/null +++ b/seederd/Makefile.am @@ -0,0 +1,21 @@ +AM_CXXFLAGS = \ + -DPREFIX=\"$(localstatedir)/seeder\" \ + -I$(builddir)/inc/ \ + -I$(srcdir)/inc/ + +bin_PROGRAMS = seederd + +seederd_SOURCES = \ + src/feed.cpp \ + src/main.cpp \ + src/seed.cpp \ + src/torrent.cpp + +seederd_SOURCES += \ + inc/feed.hpp \ + inc/main.h \ + inc/seed.hpp \ + inc/torrent.hpp + +seederd_LDFLAGS = -ltorrent-rasterbar -lboost_system -lpthread +seederd_LDADD = -lstdc++fs diff --git a/seederd/inc/consts.h b/seederd/inc/consts.h new file mode 100644 index 0000000..757788b --- /dev/null +++ b/seederd/inc/consts.h @@ -0,0 +1,7 @@ +#ifndef __CONSTS_H_ +#define __CONSTS_H_ + +#define DATADIR PREFIX "/data" +#define TORRENTDIR PREFIX "/torrents" + +#endif diff --git a/seederd/inc/feed.hpp b/seederd/inc/feed.hpp new file mode 100644 index 0000000..34cb8fa --- /dev/null +++ b/seederd/inc/feed.hpp @@ -0,0 +1,22 @@ +#ifndef __FEED_HPP_ +#define __FEED_HPP_ + +#include + +class feed { + public: + feed(lt::session& session, fs::path feed_path) : m_path(feed_path) { + for(auto &p: fs::directory_iterator(feed_path)) { + if(torrent::file_filter(p.path().string())) { + torrent t(session,p); + } + } + + torrent t(session,feed_path); + } + void save(); + private: + fs::path m_path; +}; + +#endif diff --git a/seederd/inc/main.h b/seederd/inc/main.h new file mode 100644 index 0000000..dcdcb49 --- /dev/null +++ b/seederd/inc/main.h @@ -0,0 +1,23 @@ +#ifndef __MAIN_H_ +#define __MAIN_H_ + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +int main(int,char**); +void init(lt::session&); +void handle_alerts(lt::session&); +void remove_missing(lt::session&); +int setup_signal_handler(); +void signal_handler(int); + +#endif diff --git a/seederd/inc/seed.hpp b/seederd/inc/seed.hpp new file mode 100644 index 0000000..85b99dc --- /dev/null +++ b/seederd/inc/seed.hpp @@ -0,0 +1,25 @@ +#ifndef __SEED_HPP_ +#define __SEED_HPP_ + +#include + +#include +#include +#include + +namespace fs = std::filesystem; + +class seed { + public: + seed(lt::session& session, std::string torrent_filepath, std::string data_path) { + lt::add_torrent_params params; + params.save_path = data_path; + params.ti = boost::make_shared(torrent_filepath,0); + params.flags = lt::add_torrent_params::flag_seed_mode; + + session.async_add_torrent(params); + } + void static remove(); +}; + +#endif diff --git a/seederd/inc/torrent.hpp b/seederd/inc/torrent.hpp new file mode 100644 index 0000000..2b61b79 --- /dev/null +++ b/seederd/inc/torrent.hpp @@ -0,0 +1,37 @@ +#ifndef __TORRENT_HPP_ +#define __TORRENT_HPP_ + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace fs = std::filesystem; + +class torrent { + public: + torrent(lt::session &session, fs::path p) : m_path(p) { + lt::add_files(m_fs,p,file_filter); + if(m_fs.num_files()>0) { + save(session); + } else { + std::cerr << "WARNING: no files found in " << p << "; skipping"<< std::endl; + } + }; + void add(fs::path); + void add_link_to_meta(std::string,std::string); + bool static file_filter(std::string const&); + void save(lt::session&); + private: + fs::path m_path; + lt::file_storage m_fs; +}; + +#endif diff --git a/seederd/src/feed.cpp b/seederd/src/feed.cpp new file mode 100644 index 0000000..562ae36 --- /dev/null +++ b/seederd/src/feed.cpp @@ -0,0 +1,7 @@ +#include + +namespace fs = std::filesystem; + +void feed::save() { + throw std::runtime_error("not implemented\n"); +} diff --git a/seederd/src/main.cpp b/seederd/src/main.cpp new file mode 100644 index 0000000..99103e1 --- /dev/null +++ b/seederd/src/main.cpp @@ -0,0 +1,124 @@ +#include + +namespace fs = std::filesystem; + +int setup_signal_handler() { + struct sigaction action; + + action.sa_handler = &signal_handler; + action.sa_flags = 0; + if(sigemptyset(&action.sa_mask)==-1) { + perror("sigemptyset"); + return -1; + } + + if(sigaddset(&action.sa_mask,SIGHUP)==-1) { + perror("sigaddset"); + return -1; + } + + if(sigaction(SIGHUP, &action, NULL)==-1) { + perror("sigaction"); + return -1; + } + + return 1; +} + +int main(int argc, char **argv) try { + if(setup_signal_handler()<0) { return EXIT_FAILURE; } + + std::cout << "creating required directories" << std::endl; + fs::create_directories(DATADIR); + std::cout << "created " << DATADIR << std::endl; + fs::create_directories(TORRENTDIR); + std::cout << "created " << TORRENTDIR << std::endl; + + lt::settings_pack settings; + lt::high_performance_seed(settings); + settings.set_int(lt::settings_pack::active_seeds,-1); + settings.set_int(lt::settings_pack::active_limit,-1); +// settings.set_int(lt::settings_pack::alert_mask,(lt::alert::session_log_notification + lt::alert::torrent_log_notification)); + lt::session session(settings); + + init(session); + + handle_alerts(session); + + return EXIT_SUCCESS; +} +catch(std::exception const& e) { + std::cerr << "ERROR: " << e.what() << std::endl; + return EXIT_FAILURE; +} + +void init(lt::session& session) { + for(auto &p: fs::directory_iterator(DATADIR)) { + if(p.is_directory()) { + if(!torrent::file_filter(p.path().string())) { continue; } + + std::cout << "creating torrents in " << p.path() << "..." << std::endl; + + feed fd(session,p.path()); + + std::cout<< "done!" << std::endl; + } + } + + remove_missing(session); + + system("update-feeds"); +} + +void remove_missing(lt::session& session) { + // add all .torrent files + std::unordered_map found; + for(auto &p: fs::directory_iterator(TORRENTDIR)) { + if(p.is_regular_file()) { + fs::path path = p.path(); + found.insert({path.stem().string(),path}); + } + } + + std::vector current = session.get_torrents(); + for(auto t: current) { + std::stringstream hash; + hash << t.info_hash(); + + // remove torrent if .torrent file no longer exists + auto search = found.find(hash.str()); + if(search==found.end()) { + session.remove_torrent(t); + } + } +} + + +bool reload = false; + +void handle_alerts(lt::session& session) { + for (;;) { + if(reload) { + reload = false; + std::cout << "reloading configuration..." << std::endl; + init(session); + std::cout << "configuration reloaded" << std::endl; + } + + lt::alert const* a = session.wait_for_alert(lt::seconds(10)); + if(nullptr==a) { continue; } + + std::vector alerts; + session.pop_alerts(&alerts); + + for (lt::alert const* a : alerts) { + std::cout << "[" << a->type() << "]: " << a->message() << std::endl; + if (lt::alert_cast(a)) { break; } + } + } + std::cout << "error, shutting down..." << std::endl; +} + +void signal_handler(int signo) { + reload = true; +} diff --git a/seederd/src/seed.cpp b/seederd/src/seed.cpp new file mode 100644 index 0000000..f644630 --- /dev/null +++ b/seederd/src/seed.cpp @@ -0,0 +1,5 @@ +#include + +void seed::remove() { + std::cerr << "not implemented\n"; +} diff --git a/seederd/src/torrent.cpp b/seederd/src/torrent.cpp new file mode 100644 index 0000000..e106315 --- /dev/null +++ b/seederd/src/torrent.cpp @@ -0,0 +1,92 @@ +#include + +void torrent::add(fs::path p) { + lt::add_files(m_fs,p,file_filter); +} + +void torrent::add_link_to_meta(std::string link, std::string magnet_uri) { + std::fstream meta_file; + + fs::path meta_filepath = m_path.parent_path(); + meta_filepath /= "." + m_path.filename().string() + ".meta"; + + bool hasLink = false; + bool hasGuid = false; + + meta_file.open(meta_filepath.string(), std::ios_base::in); + if(meta_file.is_open()) { + std::string line, searchLink, searchGuid; + searchLink = "link=" + link; + searchGuid = "guid=" + magnet_uri; + while(getline(meta_file,line)) { + if(line==searchLink) { hasLink = true; } + if(line==searchGuid) { hasGuid = true; } + } + + meta_file.close(); + } + + meta_file.open(meta_filepath.string(), std::ios_base::out | std::ios_base::app); + if(!meta_file.is_open()) { + std::cerr << "something went wrong" << std::endl; + } + + if(!hasLink) { + meta_file << "link=" << link << "\n"; + } + + if(!hasGuid) { + meta_file << "guid=" << magnet_uri << "\n"; + } + + meta_file.close(); +} + +bool torrent::file_filter(std::string const& f) { + if(f.empty()) { return false; } + + char const *first = f.c_str(); + char const *sep = strrchr(first, '/'); + + if(nullptr==sep) { + sep = first; + } else { + sep++; + } + + if(sep[0]=='.') { + std::cerr << "skipped: " << f << std::endl; + return false; + } + + std::cerr << "added: " << f << std::endl; + return true; +} + +void torrent::save(lt::session &session) { + fs::path temp_path = PREFIX; + temp_path /= ".tmp.torrent"; + std::cerr << "temp path: " << temp_path << std::endl; + + fs::path parent_path = m_path.parent_path(); + lt::create_torrent t(m_fs); + lt::set_piece_hashes(t, parent_path); + + std::ofstream out(temp_path, std::ios_base::binary); + lt::bencode(std::ostream_iterator(out), t.generate()); + out.close(); + + lt::torrent_info info(temp_path); + + std::stringstream filename; + filename << info.info_hash() << ".torrent"; + fs::path out_path(TORRENTDIR); + out_path /= filename.str(); + + add_link_to_meta(filename.str(),lt::make_magnet_uri(info)); + + if(!fs::exists(out_path)) { rename(temp_path,out_path); } + remove(temp_path); + + seed sd(session,out_path.string(),parent_path.string()); +}