--- /dev/null
+# 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
+++ /dev/null
-FROM debian:latest
-
-ENTRYPOINT bash
+++ /dev/null
-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)
-
--- /dev/null
+SUBDIRS = rss seederd init
--- /dev/null
+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
--- /dev/null
+# -*- 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
--- /dev/null
+#!/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
--- /dev/null
+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
--- /dev/null
+#!/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
--- /dev/null
+#!/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
--- /dev/null
+#!/usr/bin/env bash
+
+rm -f "$1/update-feeds"
+
+rm -rf "$2"
--- /dev/null
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+mkdir -p "$1"
+
+cat > "$1/index.html" << EOF
+<html>
+<p>This is the homepage of seeder.</p>
+<p><a href="feeds/">Feeds</a></p>
+<p><a href="torrents/">Torrent Files</a></p>
+</html>
+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;
--- /dev/null
+#!/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
--- /dev/null
+#!/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"
--- /dev/null
+# ===========================================================================
+# 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) 2014, 2015 Google Inc.; contributed by Alexey Sokolov <
[email protected]>
+#
+# 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 <typename T>
+ 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<void> single_type;
+ typedef check<check<void>> double_type;
+ typedef check<check<check<void>>> triple_type;
+ typedef check<check<check<check<void>>>> 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<T, T>
+ {
+ 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<int, decltype(0)>::value == true, "");
+ static_assert(is_same<int, decltype(c)>::value == false, "");
+ static_assert(is_same<int, decltype(v)>::value == false, "");
+ auto ac = c;
+ auto av = v;
+ auto sumi = ac + av + 'x';
+ auto sumf = ac + av + 1.0;
+ static_assert(is_same<int, decltype(ac)>::value == true, "");
+ static_assert(is_same<int, decltype(av)>::value == true, "");
+ static_assert(is_same<int, decltype(sumi)>::value == true, "");
+ static_assert(is_same<int, decltype(sumf)>::value == false, "");
+ static_assert(is_same<int, decltype(add(c, v))>::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 <int...>
+ struct sum;
+
+ template <int N0, int... N1toN>
+ struct sum<N0, N1toN...>
+ {
+ static constexpr auto value = N0 + sum<N1toN...>::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<typename T>
+ using member = typename T::member_type;
+
+ template<typename T>
+ void func(...) {}
+
+ template<typename T>
+ void func(member<T>*) {}
+
+ void test();
+
+ void test() { func<foo>(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<T, T>
+ {
+ static constexpr auto value = true;
+ };
+
+ int
+ test()
+ {
+ auto x = 0;
+ static_assert(is_same<int, decltype(f(x))>::value, "");
+ static_assert(is_same<int&, decltype(g(x))>::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 <initializer_list>
+#include <utility>
+#include <type_traits>
+
+namespace cxx17
+{
+
+ namespace test_constexpr_lambdas
+ {
+
+ constexpr int foo = [](){return 42;}();
+
+ }
+
+ namespace test::nested_namespace::definitions
+ {
+
+ }
+
+ namespace test_fold_expression
+ {
+
+ template<typename... Args>
+ int multiply(Args... args)
+ {
+ return (args * ... * 1);
+ }
+
+ template<typename... Args>
+ 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<std::initializer_list<int>, decltype(foo)>::value);
+ static_assert(std::is_same<int, decltype(bar)>::value);
+ }
+
+ namespace test_typename_in_template_template_parameter
+ {
+
+ template<template<typename> 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 <bool cond>
+ 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 <typename T1, typename T2>
+ 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 <auto n>
+ struct B
+ {};
+
+ B<5> b1;
+ B<'a'> b2;
+
+ }
+
+ namespace test_structured_bindings
+ {
+
+ int arr[2] = { 1, 2 };
+ std::pair<int, int> pr = { 1, 2 };
+
+ auto f1() -> int(&)[2]
+ {
+ return arr;
+ }
+
+ auto f2() -> std::pair<int, int>&
+ {
+ 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<typename T>
+ Bad
+ f(T*, T*);
+
+ template<typename T1, typename T2>
+ Good
+ f(T1*, T2*);
+
+ static_assert (std::is_same_v<Good, decltype(f(g1, g2))>);
+
+ }
+
+ namespace test_inline_variables
+ {
+
+ template<class T> void f(T)
+ {}
+
+ template<class T> inline T g(T)
+ {
+ return T{};
+ }
+
+ template<> inline void f<>(int)
+ {}
+
+ template<> int g<>(int)
+ {
+ return 5;
+ }
+
+ }
+
+} // namespace cxx17
+
+#endif // __cplusplus < 201703L
+
+]])
--- /dev/null
+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
--- /dev/null
+#ifndef __ADD_H_
+#define __ADD_H_
+
+#include<stdio.h>
+
+#include<log.h>
+#include<meta.h>
+#include<next.h>
+#include<opt.h>
+#include<rss.h>
+#include<util.h>
+
+#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
--- /dev/null
+#ifndef __DEFAULT_H_
+#define __DEFAULT_H_
+
+#include<stdlib.h>
+#include<string.h>
+
+#include<log.h>
+#include<next.h>
+#include<opt.h>
+#include<util.h>
+
+int defaults();
+
+#endif
--- /dev/null
+#ifndef __ESCAPE_H_
+#define __ESCAPE_H_
+
+#include<stdlib.h>
+#include<string.h>
+
+#include<log.h>
+
+int escape(char*,size_t);
+
+#endif
--- /dev/null
+#ifndef __INFO_H_
+#define __INFO_H_
+
+#include<log.h>
+#include<opt.h>
+#include<util.h>
+
+#define INFO_DEFAULT_DESCRIPTION "(no description)"
+#define INFO_DEFAULT_LANGUAGE "en-us"
+
+int info();
+
+#endif
--- /dev/null
+#ifndef __LOG_H_
+#define __LOG_H_
+
+#include<stdarg.h>
+#include<stdio.h>
+
+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
--- /dev/null
+#ifndef __MAIN_H_
+#define __MAIN_H_
+
+#include<getopt.h>
+#include<stdio.h>
+#include<stdlib.h>
+
+#include<add.h>
+#include<default.h>
+#include<info.h>
+#include<opt.h>
+
+int main(int,char**);
+
+#endif
--- /dev/null
+#ifndef __META_H_
+#define __META_H_
+
+#include<string.h>
+
+#include<escape.h>
+#include<log.h>
+#include<next.h>
+#include<opt.h>
+#include<rss.h>
+
+int meta(char*,char[FIELD_COUNT][BUF_SIZE]);
+int meta_filename(char*,char*,size_t);
+
+#endif
--- /dev/null
+#ifndef __NEXT_H_
+#define __NEXT_H_
+
+#include<stdio.h>
+
+#define BUF_SIZE 4096
+
+int next(char*,size_t);
+
+#endif
--- /dev/null
+#ifndef __OPT_H_
+#define __OPT_H_
+
+#include<stddef.h>
+#include<stdio.h>
+#include<stdlib.h>
+#include<string.h>
+
+#include<log.h>
+#include<meta.h>
+#include<rss.h>
+
+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
--- /dev/null
+#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 XML_END ">"
+
+#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
--- /dev/null
+#ifndef __UTIL_H_
+#define __UTIL_H_
+
+#include<stdlib.h>
+#include<time.h>
+
+int now_string(char**);
+
+#endif
--- /dev/null
+#include<add.h>
+
+char bufs[FIELD_COUNT][BUF_SIZE] = {0};
+
+int add() {
+ int i;
+
+ // clear bufs
+ for(i=0;i<FIELD_COUNT;i++) {
+ bufs[i][0] = '\0';
+ }
+
+ // place next file into FIELD_TITLE buffer
+ if((i = next(bufs[FIELD_TITLE],BUF_SIZE))<0) { return -1; }
+ if(i==0) { return 0; }
+
+ // try to get metadata
+ if(meta(NULL,bufs)<0) { return -1; }
+
+ out("<item>");
+ 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("</item>");
+ 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("<description>no description</description>");
+ break;
+ case FIELD_GUID:
+ break;
+ case FIELD_PUBDATE:
+ if(now_string(&timebuf)<0) { return -1; }
+ out("<pubDate>%s</pubDate>",timebuf);
+ free(timebuf);
+ break;
+ default:
+ log_err("unknown field type\n");
+ return -1;
+ }
+
+ return 1;
+}
--- /dev/null
+#include<default.h>
+
+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;
+}
--- /dev/null
+#include<escape.h>
+
+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;
+}
--- /dev/null
+#include<info.h>
+
+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("<title>%s</title>",global_opts.channel.title);
+ out("<link>%s</link>",global_opts.channel.link);
+
+ if(NULL==ch->description) {
+ out("<description>%s</description>",INFO_DEFAULT_DESCRIPTION);
+ } else {
+ out("<description>%s</description>",ch->description);
+ }
+
+ if(NULL==ch->language) {
+ out("<language>%s</language>",INFO_DEFAULT_LANGUAGE);
+ } else {
+ out("<language>%s</language>",global_opts.channel.language);
+ }
+
+ out("<lastBuildDate>%s</lastBuildDate>",global_opts.channel.last_build_date);
+
+ return 1;
+}
--- /dev/null
+#include<log.h>
+
+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);
+}
--- /dev/null
+#include<main.h>
+
+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("<rss version=\"2.0\">");
+ out("<channel>");
+
+ if(info()<0) { return EXIT_FAILURE; }
+
+ while((i = add())>0) { }
+
+ if(i<0) { return EXIT_FAILURE; }
+
+ out("</channel></rss>\n");
+
+ return EXIT_SUCCESS;
+}
--- /dev/null
+#include<meta.h>
+
+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<FIELD_COUNT;i++) {
+ bufs[i][0] = '\0';
+ }
+
+ while(fgets(tmp,BUF_SIZE,fp)!=NULL) {
+ p = strchr(tmp,'=');
+ if(NULL==p) { goto panic; }
+
+ *p = '\0';
+ p++;
+
+ p2 = p;
+ while(*p2!='\0') {
+ if('\n'==*p2) {
+ *p2 = '\0';
+ break;
+ }
+ p2++;
+ }
+
+ if(escape(p,BUF_SIZE-(p-tmp))<0) { goto panic; }
+
+ if(strcmp(tmp,FIELD_TITLE_STRING)==0) {
+ strcpy(bufs[FIELD_TITLE],p);
+ } else if(strcmp(tmp,FIELD_LINK_STRING)==0) {
+ if(NULL!=global_opts.feedurl) {
+ strcpy(bufs[FIELD_LINK],global_opts.feedurl);
+ strcat(bufs[FIELD_LINK],p);
+ } else {
+ strcpy(bufs[FIELD_LINK],p);
+ }
+ } else if(strcmp(tmp,FIELD_PUBDATE_STRING)==0) {
+ strcpy(bufs[FIELD_PUBDATE],p);
+ } else if(strcmp(tmp,FIELD_DESCRIPTION_STRING)==0) {
+ strcpy(bufs[FIELD_DESCRIPTION],p);
+ } else if(strcmp(tmp,FIELD_GUID_STRING)==0) {
+ strcpy(bufs[FIELD_GUID],p);
+ } else if(strcmp(tmp,FIELD_LANGUAGE_STRING)==0) {
+ strcpy(bufs[FIELD_LANGUAGE],p);
+ } else {
+ log_err("unknown field: %s\n",tmp);
+ goto panic;
+ }
+ }
+
+ fclose(fp);
+ return 1;
+ panic:
+ log_err("malformed .meta file: %s\n", meta_file);
+ fclose(fp);
+ return -1;
+}
--- /dev/null
+#include<next.h>
+
+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);
+}
--- /dev/null
+#include<opt.h>
+
+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;
+}
--- /dev/null
+#include<opt.h>
+
+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;
+}
--- /dev/null
+#include<opt.h>
+
+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;
+}
--- /dev/null
+#include<opt.h>
+
+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;
+}
--- /dev/null
+#include<opt.h>
+
+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;
+}
--- /dev/null
+#include<opt.h>
+
+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;
+}
--- /dev/null
+#include<util.h>
+
+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;
+}
--- /dev/null
+#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,"<item><title>/tmp/rss-create/test/test2.txt</title><link>0bc6c0de1ff1d26d84187a4d42fb9a2d0bc73a1d.torrent</link><pubDate>Sat, 15 May 2021 11:01:04 +0000</pubDate><description>no description</description></item><item><title>/tmp/rss-create/test/test.txt</title>",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;
+}
--- /dev/null
+#ifndef __ADD_TESTS_H_
+#define __ADD_TESTS_H_
+
+#include "test_utils.h"
+
+#include<add.h>
+
+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
--- /dev/null
+#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);
+}
--- /dev/null
+#ifndef __ESCAPE_TESTS_H_
+#define __ESCAPE_TESTS_H_
+
+#include "test_utils.h"
+
+#include<escape.h>
+
+int main();
+void escape_basic_tests();
+
+#endif
--- /dev/null
+#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,"<title>test title</title><link>https://test.com/test/feed.xml</link><description>(no description)</description><language>en-us</language><lastBuildDate>%s</lastBuildDate>",global_opts.channel.last_build_date);
+
+ assert(memcmp(buf,buf2,199)==0);
+}
--- /dev/null
+#ifndef __INFO_TESTS_H_
+#define __INFO_TESTS_H_
+
+#include "test_utils.h"
+
+#include<info.h>
+
+int main();
+void info_basic_tests();
+
+#endif
--- /dev/null
+#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);
+}
--- /dev/null
+#ifndef __META_TESTS_H_
+#define __META_TESTS_H_
+
+#include "test_utils.h"
+
+#include<meta.h>
+
+int main();
+void meta_basic_tests();
+void meta_feedurl_test();
+void meta_filename_basic_test();
+void meta_duplicate_field_test();
+
+#endif
--- /dev/null
+#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';
+ }
+}
--- /dev/null
+#ifndef __NEXT_TESTS_H_
+#define __NEXT_TESTS_H_
+
+#include "test_utils.h"
+
+#include<next.h>
+
+int main();
+void next_basic_tests();
+int getchar_dummy();
+
+#define getchar() getchar_dummy()
+#include "../src/next.c"
+
+#endif
--- /dev/null
+#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);
+}
--- /dev/null
+#ifndef __OPT_DESC_TESTS_H_
+#define __OPT_DESC_TESTS_H_
+
+#include "test_utils.h"
+
+#include<opt.h>
+
+int main();
+void opt_desc_basic_tests();
+
+#endif
--- /dev/null
+#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);
+}
--- /dev/null
+#ifndef __OPT_FEED_TESTS_H_
+#define __OPT_FEED_TESTS_H_
+
+#include "test_utils.h"
+
+#include<opt.h>
+
+int main();
+void opt_feed_basic_tests();
+
+#endif
--- /dev/null
+#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);
+}
--- /dev/null
+#ifndef __OPT_LINK_TESTS_H_
+#define __OPT_LINK_TESTS_H_
+
+#include "test_utils.h"
+
+#include<opt.h>
+
+int main();
+void opt_link_basic_tests();
+
+#endif
--- /dev/null
+#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);
+}
--- /dev/null
+#ifndef __OPT_META_TESTS_H_
+#define __OPT_META_TESTS_H_
+
+#include "test_utils.h"
+
+#include<opt.h>
+
+int main();
+void opt_set_and_parse_meta_basic_tests();
+
+#endif
--- /dev/null
+#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);
+}
--- /dev/null
+#ifndef __OPT_NAME_TESTS_H_
+#define __OPT_NAME_TESTS_H_
+
+#include "test_utils.h"
+
+#include<opt.h>
+
+int main();
+void opt_set_name_basic_tests();
+
+#endif
--- /dev/null
+#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);
+}
--- /dev/null
+#ifndef __OPT_OUT_TESTS_H_
+#define __OPT_OUT_TESTS_H_
+
+#include "test_utils.h"
+
+#include<opt.h>
+
+int main();
+void opt_out_basic_tests();
+
+#endif
--- /dev/null
+#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);
+}
--- /dev/null
+#ifndef __TEST_UTILS_H_
+#define __TEST_UTILS_H_
+
+#include<assert.h>
+#include<stdlib.h>
+#include<stdio.h>
+#include<string.h>
+#include<sys/stat.h>
+#include<sys/types.h>
+
+#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 a<Mactual>ly 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
--- /dev/null
+#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);
+}
--- /dev/null
+#ifndef __UTIL_NOW_TESTS_H_
+#define __UTIL_NOW_TESTS_H_
+
+#include "test_utils.h"
+
+#include<util.h>
+
+int main();
+void util_now_basic_tests();
+
+#endif
--- /dev/null
+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
--- /dev/null
+#ifndef __CONSTS_H_
+#define __CONSTS_H_
+
+#define DATADIR PREFIX "/data"
+#define TORRENTDIR PREFIX "/torrents"
+
+#endif
--- /dev/null
+#ifndef __FEED_HPP_
+#define __FEED_HPP_
+
+#include<torrent.hpp>
+
+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
--- /dev/null
+#ifndef __MAIN_H_
+#define __MAIN_H_
+
+#include<cstdlib>
+#include<filesystem>
+#include<iostream>
+#include<signal.h>
+
+#include<libtorrent/alert_types.hpp>
+#include<libtorrent/session.hpp>
+
+#include<consts.h>
+#include<feed.hpp>
+#include<torrent.hpp>
+
+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
--- /dev/null
+#ifndef __SEED_HPP_
+#define __SEED_HPP_
+
+#include<filesystem>
+
+#include<boost/make_shared.hpp>
+#include<libtorrent/session.hpp>
+#include<libtorrent/torrent_info.hpp>
+
+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<lt::torrent_info>(torrent_filepath,0);
+ params.flags = lt::add_torrent_params::flag_seed_mode;
+
+ session.async_add_torrent(params);
+ }
+ void static remove();
+};
+
+#endif
--- /dev/null
+#ifndef __TORRENT_HPP_
+#define __TORRENT_HPP_
+
+#include<filesystem>
+#include<fstream>
+#include<iostream>
+#include<string>
+
+#include<libtorrent/create_torrent.hpp>
+#include<libtorrent/magnet_uri.hpp>
+#include<libtorrent/torrent_info.hpp>
+
+#include<consts.h>
+#include<seed.hpp>
+
+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
--- /dev/null
+#include<feed.hpp>
+
+namespace fs = std::filesystem;
+
+void feed::save() {
+ throw std::runtime_error("not implemented\n");
+}
--- /dev/null
+#include<main.h>
+
+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<std::string,fs::path> 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<lt::torrent_handle> 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<lt::alert*> alerts;
+ session.pop_alerts(&alerts);
+
+ for (lt::alert const* a : alerts) {
+ std::cout << "[" << a->type() << "]: " << a->message() << std::endl;
+ if (lt::alert_cast<lt::torrent_error_alert>(a)) { break; }
+ }
+ }
+ std::cout << "error, shutting down..." << std::endl;
+}
+
+void signal_handler(int signo) {
+ reload = true;
+}
--- /dev/null
+#include<seed.hpp>
+
+void seed::remove() {
+ std::cerr << "not implemented\n";
+}
--- /dev/null
+#include<torrent.hpp>
+
+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<char>(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());
+}