Ocelot 1.0

This commit is contained in:
Git
2015-01-27 17:36:18 +01:00
parent 61d445e247
commit 972c8d6659
38 changed files with 2836 additions and 1216 deletions

23
CHANGES
View File

@@ -1,3 +1,24 @@
-- 1.0 (2015-01-26)
NOTE: This version requires the following database change:
ALTER TABLE xbt_files_users DROP PRIMARY KEY, ADD PRIMARY KEY (peer_id,fid,uid)
Add a 'deleted' flag to user object to stop including removed users in returned peer lists
Add a prefix to the peer list keys for randomization and smaller chance of peer id collisions
Add HTTP Keep-Alive support and show request rate in the tracker stats
Add readonly mode for easier testing
Catch exceptions in the mysql::load_* functions
Configurable max/default numwant parameter
Don't disable binlogs in the peer flush sessions
Don't start a reaper thread if it's already running
Inline peer_is_visible and user functions
Mark nonchanging parameter as const
Mark users with IP = 127.0.0.1 as protected
Read settings from a config file instead of a compiled object
Reload torrent list, user list and client whitelist on SIGUSR1
Use atomic variables for stats
Use consistent integer widths where it matters
Use std::lock_guard instead of std::unique_lock for mutex
-- 0.8 (2014-03-27)
Add setting for maximum request size
Get client IP from the x-forwarded-for header if it is provided
@@ -22,7 +43,6 @@ Configurable site path
Expire multiple tokens in a single request to the web server
Less spammy output unless a -v switch is passed when starting Ocelot
Make flush queries slightly less spammy by moving them to the functions that invoke flush threads
Make Ocelot clear stale peer data out of the db on startup
Print warning instead of error if someone submits an IPv6 or otherwise invalid address
Remove unused logger class
Send special data to search engine crawlers (and other odd clients) in an attempt to prevent indexing of announce URLs
@@ -40,7 +60,6 @@ Track transfer stats from users without leech privs
Convert all headers to lowercase
Don't show users to themselves (patch by GrecKo)
Don't write peer updates to the binlog
Fix incorrect handling of "corrupt" stat
Search for peer in leecher list if not found in seeders list
Sort response dictionary according to BitTorrent specs

View File

@@ -1,15 +1,14 @@
ACLOCAL_AMFLAGS = -I m4
bin_PROGRAMS = ocelot
ocelot_SOURCES = config.h db.cpp db.h events.cpp events.h misc_functions.cpp misc_functions.h\
ocelot_SOURCES = config.cpp config.h db.cpp db.h events.cpp events.h misc_functions.cpp misc_functions.h \
ocelot.cpp ocelot.h report.cpp report.h response.cpp response.h \
schedule.cpp schedule.h site_comm.cpp site_comm.h user.cpp user.h worker.cpp worker.h
nodist_ocelot_SOURCES = config.cpp
AM_CPPFLAGS = -std=c++11 -march=native -O2 -fvisibility=hidden -fvisibility-inlines-hidden -fomit-frame-pointer -fno-ident -pthread -Wall -Wfatal-errors -Wl,O1 -Wl,--as-needed $(BOOST_CPPFLAGS)
ocelot_LDFLAGS = -pthread $(BOOST_LDFLAGS)
ocelot_LDADD = $(BOOST_IOSTREAMS_LIB) $(BOOST_SYSTEM_LIB) -lev -lmysqlpp
EXTRA_DIST = CHANGES LICENSE config.cpp.template
AM_CXXFLAGS = -std=c++11 -march=native -O2 -fvisibility=hidden -fvisibility-inlines-hidden -fomit-frame-pointer -fno-ident -Wall -Wfatal-errors $(PTHREAD_CFLAGS) $(BOOST_CPPFLAGS)
ocelot_LDADD = $(PTHREAD_LIBS) $(BOOST_IOSTREAMS_LIB) $(BOOST_SYSTEM_LIB)
AM_LDFLAGS = -Wl,-O1 -Wl,--as-needed
EXTRA_DIST = CHANGES LICENSE README.md ocelot.conf.dist
dist-hook:
touch ${distdir}/configure
patch -p2 -d ${distdir} --no-backup-if-mismatch < ../dist.patch

View File

@@ -1,7 +1,7 @@
# Makefile.in generated by automake 1.14.1 from Makefile.am.
# Makefile.in generated by automake 1.15 from Makefile.am.
# @configure_input@
# Copyright (C) 1994-2013 Free Software Foundation, Inc.
# Copyright (C) 1994-2014 Free Software Foundation, Inc.
# This Makefile.in is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
@@ -15,7 +15,17 @@
@SET_MAKE@
VPATH = @srcdir@
am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)'
am__is_gnu_make = { \
if test -z '$(MAKELEVEL)'; then \
false; \
elif test -n '$(MAKE_HOST)'; then \
true; \
elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
true; \
else \
false; \
fi; \
}
am__make_running_with_option = \
case $${target_option-} in \
?) ;; \
@@ -77,21 +87,21 @@ NORMAL_UNINSTALL = :
PRE_UNINSTALL = :
POST_UNINSTALL = :
build_triplet = @build@
host_triplet = @host@
bin_PROGRAMS = ocelot$(EXEEXT)
subdir = .
DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \
$(top_srcdir)/configure $(am__configure_deps) depcomp compile \
config.guess config.sub install-sh missing
ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
am__aclocal_m4_deps = $(top_srcdir)/m4/ax_boost_base.m4 \
$(top_srcdir)/m4/ax_boost_iostreams.m4 \
$(top_srcdir)/m4/ax_boost_system.m4 \
$(top_srcdir)/m4/ax_check_compile_flag.m4 \
$(top_srcdir)/m4/ev++.m4 $(top_srcdir)/m4/mysql++.m4 \
$(top_srcdir)/m4/mysql_loc.m4 $(top_srcdir)/m4/tcmalloc.m4 \
$(top_srcdir)/configure.ac
$(top_srcdir)/m4/ax_pthread.m4 $(top_srcdir)/m4/ev++.m4 \
$(top_srcdir)/m4/mysql++.m4 $(top_srcdir)/m4/mysql_loc.m4 \
$(top_srcdir)/m4/tcmalloc.m4 $(top_srcdir)/configure.ac
am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
$(ACLOCAL_M4)
DIST_COMMON = $(srcdir)/Makefile.am $(top_srcdir)/configure \
$(am__configure_deps) $(am__DIST_COMMON)
am__CONFIG_DISTCLEAN_FILES = config.status config.cache config.log \
configure.lineno config.status.lineno
mkinstalldirs = $(install_sh) -d
@@ -99,16 +109,14 @@ CONFIG_CLEAN_FILES =
CONFIG_CLEAN_VPATH_FILES =
am__installdirs = "$(DESTDIR)$(bindir)"
PROGRAMS = $(bin_PROGRAMS)
am_ocelot_OBJECTS = db.$(OBJEXT) events.$(OBJEXT) \
am_ocelot_OBJECTS = config.$(OBJEXT) db.$(OBJEXT) events.$(OBJEXT) \
misc_functions.$(OBJEXT) ocelot.$(OBJEXT) report.$(OBJEXT) \
response.$(OBJEXT) schedule.$(OBJEXT) site_comm.$(OBJEXT) \
user.$(OBJEXT) worker.$(OBJEXT)
nodist_ocelot_OBJECTS = config.$(OBJEXT)
ocelot_OBJECTS = $(am_ocelot_OBJECTS) $(nodist_ocelot_OBJECTS)
ocelot_OBJECTS = $(am_ocelot_OBJECTS)
am__DEPENDENCIES_1 =
ocelot_DEPENDENCIES = $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
ocelot_LINK = $(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) $(ocelot_LDFLAGS) \
$(LDFLAGS) -o $@
ocelot_DEPENDENCIES = $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
$(am__DEPENDENCIES_1)
AM_V_P = $(am__v_P_@AM_V@)
am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
am__v_P_0 = false
@@ -150,7 +158,7 @@ AM_V_CCLD = $(am__v_CCLD_@AM_V@)
am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
am__v_CCLD_0 = @echo " CCLD " $@;
am__v_CCLD_1 =
SOURCES = $(ocelot_SOURCES) $(nodist_ocelot_SOURCES)
SOURCES = $(ocelot_SOURCES)
DIST_SOURCES = $(ocelot_SOURCES)
am__can_run_installinfo = \
case $$AM_UPDATE_INFO_DIR in \
@@ -178,6 +186,8 @@ ETAGS = etags
CTAGS = ctags
CSCOPE = cscope
AM_RECURSIVE_TARGETS = cscope
am__DIST_COMMON = $(srcdir)/Makefile.in compile config.guess \
config.sub depcomp install-sh missing
DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
distdir = $(PACKAGE)-$(VERSION)
top_distdir = $(distdir)
@@ -250,6 +260,9 @@ PACKAGE_TARNAME = @PACKAGE_TARNAME@
PACKAGE_URL = @PACKAGE_URL@
PACKAGE_VERSION = @PACKAGE_VERSION@
PATH_SEPARATOR = @PATH_SEPARATOR@
PTHREAD_CC = @PTHREAD_CC@
PTHREAD_CFLAGS = @PTHREAD_CFLAGS@
PTHREAD_LIBS = @PTHREAD_LIBS@
SET_MAKE = @SET_MAKE@
SHELL = @SHELL@
STRIP = @STRIP@
@@ -265,6 +278,7 @@ am__leading_dot = @am__leading_dot@
am__quote = @am__quote@
am__tar = @am__tar@
am__untar = @am__untar@
ax_pthread_config = @ax_pthread_config@
bindir = @bindir@
build = @build@
build_alias = @build_alias@
@@ -277,7 +291,11 @@ datarootdir = @datarootdir@
docdir = @docdir@
dvidir = @dvidir@
exec_prefix = @exec_prefix@
host = @host@
host_alias = @host_alias@
host_cpu = @host_cpu@
host_os = @host_os@
host_vendor = @host_vendor@
htmldir = @htmldir@
includedir = @includedir@
infodir = @infodir@
@@ -302,15 +320,14 @@ top_build_prefix = @top_build_prefix@
top_builddir = @top_builddir@
top_srcdir = @top_srcdir@
ACLOCAL_AMFLAGS = -I m4
ocelot_SOURCES = config.h db.cpp db.h events.cpp events.h misc_functions.cpp misc_functions.h\
ocelot_SOURCES = config.cpp config.h db.cpp db.h events.cpp events.h misc_functions.cpp misc_functions.h \
ocelot.cpp ocelot.h report.cpp report.h response.cpp response.h \
schedule.cpp schedule.h site_comm.cpp site_comm.h user.cpp user.h worker.cpp worker.h
nodist_ocelot_SOURCES = config.cpp
AM_CPPFLAGS = -std=c++11 -march=native -O2 -fvisibility=hidden -fvisibility-inlines-hidden -fomit-frame-pointer -fno-ident -pthread -Wall -Wfatal-errors -Wl,O1 -Wl,--as-needed $(BOOST_CPPFLAGS) -I$(MYSQLPP_INC_DIR) -I$(EV_INC_DIR) -I$(MYSQL_C_INC_DIR)
ocelot_LDFLAGS = $(BOOST_IOSTREAMS_LIB) $(BOOST_SYSTEM_LIB) -L$(MYSQLPP_LIB_DIR) -pthread
ocelot_LDADD = $(BOOST_IOSTREAMS_LIB) $(BOOST_SYSTEM_LIB) -lev -lmysqlpp
EXTRA_DIST = CHANGES LICENSE config.cpp.template
AM_CXXFLAGS = -std=c++11 -march=native -O2 -fvisibility=hidden -fvisibility-inlines-hidden -fomit-frame-pointer -fno-ident -Wall -Wfatal-errors $(PTHREAD_CFLAGS) $(BOOST_CPPFLAGS)
ocelot_LDADD = $(PTHREAD_LIBS) $(BOOST_IOSTREAMS_LIB) $(BOOST_SYSTEM_LIB)
AM_LDFLAGS = -Wl,-O1 -Wl,--as-needed
EXTRA_DIST = CHANGES LICENSE README.md ocelot.conf.dist
all: all-am
.SUFFIXES:
@@ -330,7 +347,6 @@ $(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign Makefile'; \
$(am__cd) $(top_srcdir) && \
$(AUTOMAKE) --foreign Makefile
.PRECIOUS: Makefile
Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
@case '$?' in \
*config.status*) \
@@ -394,7 +410,7 @@ clean-binPROGRAMS:
ocelot$(EXEEXT): $(ocelot_OBJECTS) $(ocelot_DEPENDENCIES) $(EXTRA_ocelot_DEPENDENCIES)
@rm -f ocelot$(EXEEXT)
$(AM_V_CXXLD)$(ocelot_LINK) $(ocelot_OBJECTS) $(ocelot_LDADD) $(LIBS)
$(AM_V_CXXLD)$(CXXLINK) $(ocelot_OBJECTS) $(ocelot_LDADD) $(LIBS)
mostlyclean-compile:
-rm -f *.$(OBJEXT)
@@ -546,15 +562,15 @@ dist-xz: distdir
$(am__post_remove_distdir)
dist-tarZ: distdir
@echo WARNING: "Support for shar distribution archives is" \
"deprecated." >&2
@echo WARNING: "Support for distribution archives compressed with" \
"legacy program 'compress' is deprecated." >&2
@echo WARNING: "It will be removed altogether in Automake 2.0" >&2
tardir=$(distdir) && $(am__tar) | compress -c >$(distdir).tar.Z
$(am__post_remove_distdir)
dist-shar: distdir
@echo WARNING: "Support for distribution archives compressed with" \
"legacy program 'compress' is deprecated." >&2
@echo WARNING: "Support for shar distribution archives is" \
"deprecated." >&2
@echo WARNING: "It will be removed altogether in Automake 2.0" >&2
shar $(distdir) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).shar.gz
$(am__post_remove_distdir)
@@ -590,17 +606,17 @@ distcheck: dist
esac
chmod -R a-w $(distdir)
chmod u+w $(distdir)
mkdir $(distdir)/_build $(distdir)/_inst
mkdir $(distdir)/_build $(distdir)/_build/sub $(distdir)/_inst
chmod a-w $(distdir)
test -d $(distdir)/_build || exit 0; \
dc_install_base=`$(am__cd) $(distdir)/_inst && pwd | sed -e 's,^[^:\\/]:[\\/],/,'` \
&& dc_destdir="$${TMPDIR-/tmp}/am-dc-$$$$/" \
&& am__cwd=`pwd` \
&& $(am__cd) $(distdir)/_build \
&& ../configure \
&& $(am__cd) $(distdir)/_build/sub \
&& ../../configure \
$(AM_DISTCHECK_CONFIGURE_FLAGS) \
$(DISTCHECK_CONFIGURE_FLAGS) \
--srcdir=.. --prefix="$$dc_install_base" \
--srcdir=../.. --prefix="$$dc_install_base" \
&& $(MAKE) $(AM_MAKEFLAGS) \
&& $(MAKE) $(AM_MAKEFLAGS) dvi \
&& $(MAKE) $(AM_MAKEFLAGS) check \
@@ -781,6 +797,8 @@ uninstall-am: uninstall-binPROGRAMS
mostlyclean-generic pdf pdf-am ps ps-am tags tags-am uninstall \
uninstall-am uninstall-binPROGRAMS
.PRECIOUS: Makefile
dist-hook:
touch ${distdir}/configure
patch -p2 -d ${distdir} --no-backup-if-mismatch < ../dist.patch

45
README.md Normal file
View File

@@ -0,0 +1,45 @@
# Ocelot
Ocelot is a BitTorrent tracker written in C++ for the [Gazelle](http://whatcd.github.io/Gazelle/) project. It supports requests over TCP and can only track IPv4 peers.
## Ocelot Compile-time Dependencies
* [GCC/G++](http://gcc.gnu.org/) (4.7+ required; 4.8.1+ recommended)
* [Boost](http://www.boost.org/) (1.55.0+ required)
* [libev](http://software.schmorp.de/pkg/libev.html) (required)
* [MySQL++](http://tangentsoft.net/mysql++/) (3.2.0+ required)
* [TCMalloc](http://goog-perftools.sourceforge.net/doc/tcmalloc.html) (optional, but strongly recommended)
## Installation
The [Gazelle installation guides](https://github.com/WhatCD/Gazelle/wiki/Gazelle-installation) include instructions for installing Ocelot as a part of the Gazelle project.
### Standalone Installation
* Create the following tables according to the [Gazelle database schema](https://raw.githubusercontent.com/WhatCD/Gazelle/master/gazelle.sql):
- `torrents`
- `users_freeleeches`
- `users_main`
- `xbt_client_whitelist`
- `xbt_files_users`
- `xbt_snatched`
* Edit `ocelot.conf` to your liking.
* Build Ocelot:
./configure
make
make install
## Running Ocelot
### Run-time options:
* `-c <path/to/ocelot.conf>` - Path to config file. If unspecified, the current working directory is used.
* `-v` - Print queue status every time a flush is initiated.
### Signals
* `SIGHUP` - Reload config
* `SIGUSR1` - Reload torrent list, user list and client whitelist

66
aclocal.m4 vendored
View File

@@ -1,6 +1,6 @@
# generated automatically by aclocal 1.14.1 -*- Autoconf -*-
# generated automatically by aclocal 1.15 -*- Autoconf -*-
# Copyright (C) 1996-2013 Free Software Foundation, Inc.
# Copyright (C) 1996-2014 Free Software Foundation, Inc.
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
@@ -20,7 +20,7 @@ You have another version of autoconf. It may work, but is not guaranteed to.
If you have problems, you may need to regenerate the build system entirely.
To do so, use the procedure documented by the package, typically 'autoreconf'.])])
# Copyright (C) 2002-2013 Free Software Foundation, Inc.
# Copyright (C) 2002-2014 Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
@@ -32,10 +32,10 @@ To do so, use the procedure documented by the package, typically 'autoreconf'.])
# generated from the m4 files accompanying Automake X.Y.
# (This private macro should not be called outside this file.)
AC_DEFUN([AM_AUTOMAKE_VERSION],
[am__api_version='1.14'
[am__api_version='1.15'
dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to
dnl require some minimum version. Point them to the right macro.
m4_if([$1], [1.14.1], [],
m4_if([$1], [1.15], [],
[AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl
])
@@ -51,14 +51,14 @@ m4_define([_AM_AUTOCONF_VERSION], [])
# Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced.
# This function is AC_REQUIREd by AM_INIT_AUTOMAKE.
AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION],
[AM_AUTOMAKE_VERSION([1.14.1])dnl
[AM_AUTOMAKE_VERSION([1.15])dnl
m4_ifndef([AC_AUTOCONF_VERSION],
[m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl
_AM_AUTOCONF_VERSION(m4_defn([AC_AUTOCONF_VERSION]))])
# AM_AUX_DIR_EXPAND -*- Autoconf -*-
# Copyright (C) 2001-2013 Free Software Foundation, Inc.
# Copyright (C) 2001-2014 Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
@@ -103,15 +103,14 @@ _AM_AUTOCONF_VERSION(m4_defn([AC_AUTOCONF_VERSION]))])
# configured tree to be moved without reconfiguration.
AC_DEFUN([AM_AUX_DIR_EXPAND],
[dnl Rely on autoconf to set up CDPATH properly.
AC_PREREQ([2.50])dnl
# expand $ac_aux_dir to an absolute path
am_aux_dir=`cd $ac_aux_dir && pwd`
[AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT])dnl
# Expand $ac_aux_dir to an absolute path.
am_aux_dir=`cd "$ac_aux_dir" && pwd`
])
# AM_CONDITIONAL -*- Autoconf -*-
# Copyright (C) 1997-2013 Free Software Foundation, Inc.
# Copyright (C) 1997-2014 Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
@@ -142,7 +141,7 @@ AC_CONFIG_COMMANDS_PRE(
Usually this means the macro was only invoked conditionally.]])
fi])])
# Copyright (C) 1999-2013 Free Software Foundation, Inc.
# Copyright (C) 1999-2014 Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
@@ -333,7 +332,7 @@ _AM_SUBST_NOTMAKE([am__nodep])dnl
# Generate code to set up dependency tracking. -*- Autoconf -*-
# Copyright (C) 1999-2013 Free Software Foundation, Inc.
# Copyright (C) 1999-2014 Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
@@ -409,7 +408,7 @@ AC_DEFUN([AM_OUTPUT_DEPENDENCY_COMMANDS],
# Do all the work for Automake. -*- Autoconf -*-
# Copyright (C) 1996-2013 Free Software Foundation, Inc.
# Copyright (C) 1996-2014 Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
@@ -499,8 +498,8 @@ AC_REQUIRE([AC_PROG_MKDIR_P])dnl
# <http://lists.gnu.org/archive/html/automake/2012-07/msg00001.html>
# <http://lists.gnu.org/archive/html/automake/2012-07/msg00014.html>
AC_SUBST([mkdir_p], ['$(MKDIR_P)'])
# We need awk for the "check" target. The system "awk" is bad on
# some platforms.
# We need awk for the "check" target (and possibly the TAP driver). The
# system "awk" is bad on some platforms.
AC_REQUIRE([AC_PROG_AWK])dnl
AC_REQUIRE([AC_PROG_MAKE_SET])dnl
AC_REQUIRE([AM_SET_LEADING_DOT])dnl
@@ -573,7 +572,11 @@ to "yes", and re-run configure.
END
AC_MSG_ERROR([Your 'rm' program is bad, sorry.])
fi
fi])
fi
dnl The trailing newline in this macro's definition is deliberate, for
dnl backward compatibility and to allow trailing 'dnl'-style comments
dnl after the AM_INIT_AUTOMAKE invocation. See automake bug#16841.
])
dnl Hook into '_AC_COMPILER_EXEEXT' early to learn its expansion. Do not
dnl add the conditional right here, as _AC_COMPILER_EXEEXT may be further
@@ -602,7 +605,7 @@ for _am_header in $config_headers :; do
done
echo "timestamp for $_am_arg" >`AS_DIRNAME(["$_am_arg"])`/stamp-h[]$_am_stamp_count])
# Copyright (C) 2001-2013 Free Software Foundation, Inc.
# Copyright (C) 2001-2014 Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
@@ -613,7 +616,7 @@ echo "timestamp for $_am_arg" >`AS_DIRNAME(["$_am_arg"])`/stamp-h[]$_am_stamp_co
# Define $install_sh.
AC_DEFUN([AM_PROG_INSTALL_SH],
[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl
if test x"${install_sh}" != xset; then
if test x"${install_sh+set}" != xset; then
case $am_aux_dir in
*\ * | *\ *)
install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;;
@@ -623,7 +626,7 @@ if test x"${install_sh}" != xset; then
fi
AC_SUBST([install_sh])])
# Copyright (C) 2003-2013 Free Software Foundation, Inc.
# Copyright (C) 2003-2014 Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
@@ -644,7 +647,7 @@ AC_SUBST([am__leading_dot])])
# Check to see how 'make' treats includes. -*- Autoconf -*-
# Copyright (C) 2001-2013 Free Software Foundation, Inc.
# Copyright (C) 2001-2014 Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
@@ -694,7 +697,7 @@ rm -f confinc confmf
# Fake the existence of programs that GNU maintainers use. -*- Autoconf -*-
# Copyright (C) 1997-2013 Free Software Foundation, Inc.
# Copyright (C) 1997-2014 Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
@@ -733,7 +736,7 @@ fi
# Helper functions for option handling. -*- Autoconf -*-
# Copyright (C) 2001-2013 Free Software Foundation, Inc.
# Copyright (C) 2001-2014 Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
@@ -762,7 +765,7 @@ AC_DEFUN([_AM_SET_OPTIONS],
AC_DEFUN([_AM_IF_OPTION],
[m4_ifset(_AM_MANGLE_OPTION([$1]), [$2], [$3])])
# Copyright (C) 1999-2013 Free Software Foundation, Inc.
# Copyright (C) 1999-2014 Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
@@ -809,7 +812,7 @@ AC_LANG_POP([C])])
# For backward compatibility.
AC_DEFUN_ONCE([AM_PROG_CC_C_O], [AC_REQUIRE([AC_PROG_CC])])
# Copyright (C) 2001-2013 Free Software Foundation, Inc.
# Copyright (C) 2001-2014 Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
@@ -828,7 +831,7 @@ AC_DEFUN([AM_RUN_LOG],
# Check to make sure that the build environment is sane. -*- Autoconf -*-
# Copyright (C) 1996-2013 Free Software Foundation, Inc.
# Copyright (C) 1996-2014 Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
@@ -909,7 +912,7 @@ AC_CONFIG_COMMANDS_PRE(
rm -f conftest.file
])
# Copyright (C) 2009-2013 Free Software Foundation, Inc.
# Copyright (C) 2009-2014 Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
@@ -969,7 +972,7 @@ AC_SUBST([AM_BACKSLASH])dnl
_AM_SUBST_NOTMAKE([AM_BACKSLASH])dnl
])
# Copyright (C) 2001-2013 Free Software Foundation, Inc.
# Copyright (C) 2001-2014 Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
@@ -997,7 +1000,7 @@ fi
INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s"
AC_SUBST([INSTALL_STRIP_PROGRAM])])
# Copyright (C) 2006-2013 Free Software Foundation, Inc.
# Copyright (C) 2006-2014 Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
@@ -1016,7 +1019,7 @@ AC_DEFUN([AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE($@)])
# Check how to create a tarball. -*- Autoconf -*-
# Copyright (C) 2004-2013 Free Software Foundation, Inc.
# Copyright (C) 2004-2014 Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
@@ -1151,6 +1154,7 @@ m4_include([m4/ax_boost_base.m4])
m4_include([m4/ax_boost_iostreams.m4])
m4_include([m4/ax_boost_system.m4])
m4_include([m4/ax_check_compile_flag.m4])
m4_include([m4/ax_pthread.m4])
m4_include([m4/ev++.m4])
m4_include([m4/mysql++.m4])
m4_include([m4/mysql_loc.m4])

View File

@@ -3,7 +3,7 @@
scriptversion=2012-10-14.11; # UTC
# Copyright (C) 1999-2013 Free Software Foundation, Inc.
# Copyright (C) 1999-2014 Free Software Foundation, Inc.
# Written by Tom Tromey <tromey@cygnus.com>.
#
# This program is free software; you can redistribute it and/or modify

156
config.cpp Normal file
View File

@@ -0,0 +1,156 @@
#include <iostream>
#include <fstream>
#include <string>
#include "config.h"
#include "misc_functions.h"
confval::confval() {
bool_val = 0;
uint_val = 0;
str_val = "";
val_type = CONF_NONEXISTENT;
}
confval::confval(bool value) {
bool_val = value;
val_type = CONF_BOOL;
}
confval::confval(unsigned int value) {
uint_val = value;
val_type = CONF_UINT;
}
confval::confval(const char * value) {
str_val = value;
val_type = CONF_STR;
}
bool confval::get_bool() {
return bool_val;
}
unsigned int confval::get_uint() {
return uint_val;
}
std::string confval::get_str() {
return str_val;
}
void confval::set(const std::string &value) {
if (val_type == CONF_BOOL) {
bool_val = value == "1" || value == "true" || value == "yes";
} else if (val_type == CONF_UINT) {
uint_val = strtoint32(value);
} else if (val_type == CONF_STR) {
str_val = value;
}
}
config::config() {
init();
dummy_setting = new confval(); // Safety value to use if we're accessing nonexistent settings
}
void config::init() {
// Internal stuff
add("listen_port", 34000u);
add("max_connections", 1024u);
add("max_middlemen", 20000u);
add("max_read_buffer", 4096u);
add("connection_timeout", 10u);
add("keepalive_timeout", 0u);
// Tracker requests
add("announce_interval", 1800u);
add("max_request_size", 4096u);
add("numwant_limit", 50u);
add("request_log_size", 500u);
// Timers
add("del_reason_lifetime", 86400u);
add("peers_timeout", 7200u);
add("reap_peers_interval", 1800u);
add("schedule_interval", 3u);
// MySQL
add("mysql_db", "gazelle");
add("mysql_host", "localhost");
add("mysql_username", "");
add("mysql_password", "");
// Site communication
add("site_host", "127.0.0.1");
add("site_path", "");
add("site_password", "00000000000000000000000000000000");
add("report_password", "00000000000000000000000000000000");
// Debugging
add("readonly", false);
}
confval * config::get(const std::string &setting_name) {
const auto setting = settings.find(setting_name);
if (setting == settings.end()) {
std::cout << "WARNING: Unrecognized setting '" << setting_name << "'" << std::endl;
return dummy_setting;
}
return &setting->second;
}
bool config::get_bool(const std::string &setting_name) {
return get(setting_name)->get_bool();
}
unsigned int config::get_uint(const std::string &setting_name) {
return get(setting_name)->get_uint();
}
std::string config::get_str(const std::string &setting_name) {
return get(setting_name)->get_str();
}
void config::set(const std::string &setting_name, const std::string &value) {
get(setting_name)->set(value);
}
void config::load(const std::string &conf_file_path, std::istream &conf_file) {
load(conf_file);
add("conf_file_path", conf_file_path.c_str());
}
void config::load(std::istream &conf_file) {
std::string line;
while (getline(conf_file, line)) {
size_t pos;
if (line[0] != '#' && (pos = line.find('=')) != std::string::npos) {
std::string key(trim(line.substr(0, pos)));
std::string value(trim(line.substr(pos + 1)));
set(key, value);
}
}
}
void config::reload() {
const std::string conf_file_path(get_str("conf_file_path"));
std::ifstream conf_file(conf_file_path);
if (conf_file.fail()) {
std::cout << "Config file '" << conf_file_path << "' couldn't be opened" << std::endl;
} else {
init();
load(conf_file);
}
}
std::string config::trim(const std::string str) {
size_t ltrim = str.find_first_not_of(" \t");
if (ltrim == std::string::npos) {
ltrim = 0;
}
size_t rtrim = str.find_last_not_of(" \t");
if (ltrim != 0 || rtrim != str.length() - 1) {
return str.substr(ltrim, rtrim - ltrim + 1);
}
return str;
}

View File

@@ -1,32 +0,0 @@
#include "config.h"
config::config() {
host = "127.0.0.1";
port = 34000;
max_connections = 512;
max_read_buffer = 4096;
max_request_size = 4096;
timeout_interval = 20;
schedule_interval = 3;
max_middlemen = 5000;
announce_interval = 1800;
peers_timeout = 2700; //Announce interval * 1.5
reap_peers_interval = 1800;
del_reason_lifetime = 604800;
// MySQL
mysql_db = "gazelle";
mysql_host = "127.0.0.1:3306";
mysql_username = "***";
mysql_password = "***";
// Site communication
site_host = "localhost";
site_password = "********************************"; // MUST BE 32 CHARS
site_path = ""; // If the site is not running under the domain root
// Key to use for /report?get=stats and /report?get=user&key=<passkey> requests
report_password = "********************************"; // MUST BE 32 CHARS
}

View File

@@ -2,38 +2,51 @@
#define OCELOT_CONFIG_H
#include <string>
#include <map>
class config {
class confval {
private:
bool bool_val;
uint32_t uint_val;
std::string str_val;
enum {
CONF_NONEXISTENT,
CONF_BOOL,
CONF_UINT,
CONF_STR,
} val_type;
public:
std::string host;
unsigned int port;
unsigned int max_connections;
unsigned int max_read_buffer;
unsigned int max_request_size;
unsigned int timeout_interval;
unsigned int schedule_interval;
unsigned int max_middlemen;
unsigned int announce_interval;
unsigned int peers_timeout;
unsigned int reap_peers_interval;
unsigned int del_reason_lifetime;
// MySQL
std::string mysql_db;
std::string mysql_host;
std::string mysql_username;
std::string mysql_password;
// Site communication
std::string site_host;
std::string site_password;
std::string site_path;
std::string report_password;
config();
confval();
confval(bool value);
confval(uint32_t value);
confval(const char * value);
uint32_t get_uint();
bool get_bool();
std::string get_str();
void set(const std::string &val);
};
class config {
private:
template <typename T> void add(const std::string &setting_name, T value);
std::string trim(const std::string str);
void init();
confval * get(const std::string &setting_name);
std::map<std::string, confval> settings;
confval * dummy_setting;
public:
config();
void load(std::istream &conf_file);
void load(const std::string &conf_file_path, std::istream &conf_file);
void reload();
bool get_bool(const std::string &setting_name);
uint32_t get_uint(const std::string &setting_name);
std::string get_str(const std::string &setting_name);
void set(const std::string &setting_name, const std::string &value);
};
template <typename T> void config::add(const std::string &setting_name, T value) {
confval setting(value);
settings[setting_name] = setting;
}
#endif

1660
configure vendored

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +1,18 @@
AC_INIT(ocelot, 0.8)
AC_INIT(ocelot, 1.0)
AM_INIT_AUTOMAKE([1.11 no-define foreign])
AC_PROG_CXX
AC_CONFIG_FILES([Makefile])
AC_LANG_PUSH([C++])
AX_CHECK_COMPILE_FLAG([-std=c++11], [], [AC_MSG_ERROR([Compiler does not support -std=c++11])])
AX_CHECK_COMPILE_FLAG([-fstack-protector-strong], [CXXFLAGS="-fstack-protector-strong $CXXFLAGS"])
AC_LANG_POP([C++])
AX_BOOST_BASE([1.37], [], [AC_MSG_ERROR(Need boost >= 1.37)])
AX_BOOST_IOSTREAMS
AX_BOOST_SYSTEM
MYSQL_C_API_LOCATION
AC_LANG_PUSH(C++)
MYSQLPP_DEVEL
AX_PTHREAD([], AC_MSG_FAILURE([pthread library is required]))
EV_DEVEL
AC_LANG_POP(C++)
MYSQL_C_API_LOCATION
MYSQLPP_DEVEL
TCMALLOC
AC_OUTPUT

290
db.cpp
View File

@@ -2,32 +2,52 @@
#include "db.h"
#include "user.h"
#include "misc_functions.h"
#include "config.h"
#include <string>
#include <iostream>
#include <queue>
#include <unistd.h>
#include <time.h>
#include <ctime>
#include <mutex>
#include <thread>
#include <unordered_set>
#define DB_LOCK_TIMEOUT 50
mysql::mysql(std::string mysql_db, std::string mysql_host, std::string username, std::string password) :
db(mysql_db), server(mysql_host), db_user(username), pw(password),
u_active(false), t_active(false), p_active(false), s_active(false), tok_active(false)
{
mysql::mysql(config * conf) : u_active(false), t_active(false), p_active(false), s_active(false), tok_active(false) {
load_config(conf);
if (mysql_db == "") {
std::cout << "No database selected" << std::endl;
return;
}
try {
conn.connect(mysql_db.c_str(), mysql_host.c_str(), username.c_str(), password.c_str(), 0);
mysqlpp::ReconnectOption reconnect(true);
conn.set_option(&reconnect);
conn.connect(mysql_db.c_str(), mysql_host.c_str(), mysql_username.c_str(), mysql_password.c_str(), 0);
} catch (const mysqlpp::Exception &er) {
std::cout << "Failed to connect to MySQL (" << er.what() << ')' << std::endl;
return;
}
std::cout << "Connected to MySQL" << std::endl;
std::cout << "Clearing xbt_files_users and resetting peer counts...";
std::cout.flush();
clear_peer_data();
std::cout << "done" << std::endl;
if (!readonly) {
std::cout << "Clearing xbt_files_users and resetting peer counts...";
std::cout.flush();
clear_peer_data();
std::cout << "done" << std::endl;
}
}
void mysql::load_config(config * conf) {
mysql_db = conf->get_str("mysql_db");
mysql_host = conf->get_str("mysql_host");
mysql_username = conf->get_str("mysql_username");
mysql_password = conf->get_str("mysql_password");
readonly = conf->get_bool("readonly");
}
void mysql::reload_config(config * conf) {
load_config(conf);
}
bool mysql::connected() {
@@ -45,104 +65,195 @@ void mysql::clear_peer_data() {
std::cerr << "Unable to reset seeder and leecher count!" << std::endl;
}
} catch (const mysqlpp::BadQuery &er) {
std::cerr << "Query error: " << er.what() << " in clear_peer_data" << std::endl;
std::cerr << "Query error in clear_peer_data: " << er.what() << std::endl;
} catch (const mysqlpp::Exception &er) {
std::cerr << "Query error: " << er.what() << " in clear_peer_data" << std::endl;
std::cerr << "Query error in clear_peer_data: " << er.what() << std::endl;
}
}
void mysql::load_torrents(torrent_list &torrents) {
mysqlpp::Query query = conn.query("SELECT ID, info_hash, freetorrent, Snatched FROM torrents ORDER BY ID;");
if (mysqlpp::StoreQueryResult res = query.store()) {
mysqlpp::String one("1"); // Hack to get around bug in mysql++3.0.0
mysqlpp::String two("2");
try {
mysqlpp::StoreQueryResult res = query.store();
std::unordered_set<std::string> cur_keys;
size_t num_rows = res.num_rows();
std::lock_guard<std::mutex> tl_lock(torrent_list_mutex);
if (torrents.size() == 0) {
torrents.reserve(num_rows * 1.05); // Reserve 5% extra space to prevent rehashing
} else {
// Create set with all currently known info hashes to remove nonexistent ones later
cur_keys.reserve(torrents.size());
for (auto const &it: torrents) {
cur_keys.insert(it.first);
}
}
for (size_t i = 0; i < num_rows; i++) {
std::string info_hash;
res[i][1].to_string(info_hash);
torrent t;
t.id = res[i][0];
if (res[i][2].compare(one) == 0) {
t.free_torrent = FREE;
} else if (res[i][2].compare(two) == 0) {
t.free_torrent = NEUTRAL;
} else {
t.free_torrent = NORMAL;
if (info_hash == "") {
continue;
}
mysqlpp::sql_enum free_torrent(res[i][2]);
torrent tmp_tor;
auto it = torrents.insert(std::pair<std::string, torrent>(info_hash, tmp_tor));
torrent &tor = (it.first)->second;
if (it.second) {
tor.id = res[i][0];
tor.balance = 0;
tor.completed = res[i][3];
tor.last_selected_seeder = "";
} else {
tor.tokened_users.clear();
cur_keys.erase(info_hash);
}
if (free_torrent == "1") {
tor.free_torrent = FREE;
} else if (free_torrent == "2") {
tor.free_torrent = NEUTRAL;
} else {
tor.free_torrent = NORMAL;
}
t.balance = 0;
t.completed = res[i][3];
t.last_selected_seeder = "";
torrents[info_hash] = t;
}
for (auto const &info_hash: cur_keys) {
// Remove tracked torrents that weren't found in the database
auto it = torrents.find(info_hash);
if (it != torrents.end()) {
torrent &tor = it->second;
stats.leechers -= tor.leechers.size();
stats.seeders -= tor.seeders.size();
for (auto &p: tor.leechers) {
p.second.user->decr_leeching();
}
for (auto &p: tor.seeders) {
p.second.user->decr_seeding();
}
torrents.erase(it);
}
}
} catch (const mysqlpp::BadQuery &er) {
std::cerr << "Query error in load_torrents: " << er.what() << std::endl;
return;
}
std::cout << "Loaded " << torrents.size() << " torrents" << std::endl;
load_tokens(torrents);
}
void mysql::load_users(user_list &users) {
mysqlpp::Query query = conn.query("SELECT ID, can_leech, torrent_pass, visible FROM users_main WHERE Enabled='1';");
if (mysqlpp::StoreQueryResult res = query.store()) {
mysqlpp::Query query = conn.query("SELECT ID, can_leech, torrent_pass, (Visible='0' OR IP='127.0.0.1') AS Protected FROM users_main WHERE Enabled='1';");
try {
mysqlpp::StoreQueryResult res = query.store();
size_t num_rows = res.num_rows();
for (size_t i = 0; i < num_rows; i++) {
std::string passkey;
res[i][2].to_string(passkey);
bool protect_ip = res[i][3].compare("1") != 0;
user_ptr u(new user(res[i][0], res[i][1], protect_ip));
users.insert(std::pair<std::string, user_ptr>(passkey, u));
std::unordered_set<std::string> cur_keys;
std::lock_guard<std::mutex> ul_lock(user_list_mutex);
if (users.size() == 0) {
users.reserve(num_rows * 1.05); // Reserve 5% extra space to prevent rehashing
} else {
// Create set with all currently known user keys to remove nonexistent ones later
cur_keys.reserve(users.size());
for (auto const &it: users) {
cur_keys.insert(it.first);
}
}
for (size_t i = 0; i < num_rows; i++) {
std::string passkey(res[i][2]);
bool protect_ip = res[i][3];
user_ptr tmp_user = std::make_shared<user>(res[i][0], res[i][1], protect_ip);
auto it = users.insert(std::pair<std::string, user_ptr>(passkey, tmp_user));
if (!it.second) {
user_ptr &u = (it.first)->second;
u->set_leechstatus(res[i][1]);
u->set_protected(protect_ip);
u->set_deleted(false);
cur_keys.erase(passkey);
}
}
for (auto const &passkey: cur_keys) {
// Remove users that weren't found in the database
auto it = users.find(passkey);
if (it != users.end()) {
it->second->set_deleted(true);
users.erase(it);
}
}
} catch (const mysqlpp::BadQuery &er) {
std::cerr << "Query error in load_users: " << er.what() << std::endl;
return;
}
std::cout << "Loaded " << users.size() << " users" << std::endl;
}
void mysql::load_tokens(torrent_list &torrents) {
mysqlpp::Query query = conn.query("SELECT uf.UserID, t.info_hash FROM users_freeleeches AS uf JOIN torrents AS t ON t.ID = uf.TorrentID WHERE uf.Expired = '0';");
if (mysqlpp::StoreQueryResult res = query.store()) {
int token_count = 0;
try {
mysqlpp::StoreQueryResult res = query.store();
size_t num_rows = res.num_rows();
std::lock_guard<std::mutex> tl_lock(torrent_list_mutex);
for (size_t i = 0; i < num_rows; i++) {
std::string info_hash;
res[i][1].to_string(info_hash);
torrent_list::iterator it = torrents.find(info_hash);
auto it = torrents.find(info_hash);
if (it != torrents.end()) {
torrent &tor = it->second;
tor.tokened_users.insert(res[i][0]);
++token_count;
}
}
} catch (const mysqlpp::BadQuery &er) {
std::cerr << "Query error in load_tokens: " << er.what() << std::endl;
return;
}
std::cout << "Loaded " << token_count << " tokens" << std::endl;
}
void mysql::load_whitelist(std::vector<std::string> &whitelist) {
mysqlpp::Query query = conn.query("SELECT peer_id FROM xbt_client_whitelist;");
if (mysqlpp::StoreQueryResult res = query.store()) {
try {
mysqlpp::StoreQueryResult res = query.store();
size_t num_rows = res.num_rows();
std::lock_guard<std::mutex> wl_lock(whitelist_mutex);
whitelist.clear();
for (size_t i = 0; i<num_rows; i++) {
whitelist.push_back(res[i][0].c_str());
std::string peer_id;
res[i][0].to_string(peer_id);
whitelist.push_back(peer_id);
}
} catch (const mysqlpp::BadQuery &er) {
std::cerr << "Query error in load_whitelist: " << er.what() << std::endl;
return;
}
if (whitelist.size() == 0) {
std::cout << "Assuming no whitelist desired, disabling" << std::endl;
} else {
std::cout << "Loaded " << whitelist.size() << " clients into the whitelist" << std::endl;
}
}
void mysql::record_token(std::string &record) {
void mysql::record_token(const std::string &record) {
if (update_token_buffer != "") {
update_token_buffer += ",";
}
update_token_buffer += record;
}
void mysql::record_user(std::string &record) {
void mysql::record_user(const std::string &record) {
if (update_user_buffer != "") {
update_user_buffer += ",";
}
update_user_buffer += record;
}
void mysql::record_torrent(std::string &record) {
std::unique_lock<std::mutex> tb_lock(torrent_buffer_lock);
void mysql::record_torrent(const std::string &record) {
std::lock_guard<std::mutex> tb_lock(torrent_buffer_lock);
if (update_torrent_buffer != "") {
update_torrent_buffer += ",";
}
update_torrent_buffer += record;
}
void mysql::record_peer(std::string &record, std::string &ip, std::string &peer_id, std::string &useragent) {
void mysql::record_peer(const std::string &record, const std::string &ip, const std::string &peer_id, const std::string &useragent) {
if (update_heavy_peer_buffer != "") {
update_heavy_peer_buffer += ",";
}
@@ -151,7 +262,7 @@ void mysql::record_peer(std::string &record, std::string &ip, std::string &peer_
update_heavy_peer_buffer += q.str();
}
void mysql::record_peer(std::string &record, std::string &peer_id) {
void mysql::record_peer(const std::string &record, const std::string &peer_id) {
if (update_light_peer_buffer != "") {
update_light_peer_buffer += ",";
}
@@ -161,7 +272,7 @@ void mysql::record_peer(std::string &record, std::string &peer_id) {
update_light_peer_buffer += q.str();
}
void mysql::record_snatch(std::string &record, std::string &ip) {
void mysql::record_snatch(const std::string &record, const std::string &ip) {
if (update_snatch_buffer != "") {
update_snatch_buffer += ",";
}
@@ -183,11 +294,15 @@ void mysql::flush() {
}
void mysql::flush_users() {
if (readonly) {
update_user_buffer.clear();
return;
}
std::string sql;
std::unique_lock<std::mutex> uq_lock(user_queue_lock);
std::lock_guard<std::mutex> uq_lock(user_queue_lock);
size_t qsize = user_queue.size();
if (verbose_flush || qsize > 0) {
std::cout << "User flush queue size: " << qsize << std::endl;
std::cout << "User flush queue size: " << qsize << ", next query length: " << user_queue.front().size() << std::endl;
}
if (update_user_buffer == "") {
return;
@@ -203,12 +318,16 @@ void mysql::flush_users() {
}
void mysql::flush_torrents() {
std::lock_guard<std::mutex> tb_lock(torrent_buffer_lock);
if (readonly) {
update_torrent_buffer.clear();
return;
}
std::string sql;
std::unique_lock<std::mutex> tq_lock(torrent_queue_lock);
std::unique_lock<std::mutex> tb_lock(torrent_buffer_lock);
std::lock_guard<std::mutex> tq_lock(torrent_queue_lock);
size_t qsize = torrent_queue.size();
if (verbose_flush || qsize > 0) {
std::cout << "Torrent flush queue size: " << qsize << std::endl;
std::cout << "Torrent flush queue size: " << qsize << ", next query length: " << torrent_queue.front().size() << std::endl;
}
if (update_torrent_buffer == "") {
return;
@@ -229,11 +348,15 @@ void mysql::flush_torrents() {
}
void mysql::flush_snatches() {
if (readonly) {
update_snatch_buffer.clear();
return;
}
std::string sql;
std::unique_lock<std::mutex> sq_lock(snatch_queue_lock);
std::lock_guard<std::mutex> sq_lock(snatch_queue_lock);
size_t qsize = snatch_queue.size();
if (verbose_flush || qsize > 0) {
std::cout << "Snatch flush queue size: " << qsize << std::endl;
std::cout << "Snatch flush queue size: " << qsize << ", next query length: " << snatch_queue.front().size() << std::endl;
}
if (update_snatch_buffer == "" ) {
return;
@@ -248,11 +371,16 @@ void mysql::flush_snatches() {
}
void mysql::flush_peers() {
if (readonly) {
update_light_peer_buffer.clear();
update_heavy_peer_buffer.clear();
return;
}
std::string sql;
std::unique_lock<std::mutex> pq_lock(peer_queue_lock);
std::lock_guard<std::mutex> pq_lock(peer_queue_lock);
size_t qsize = peer_queue.size();
if (verbose_flush || qsize > 0) {
std::cout << "Peer flush queue size: " << qsize << std::endl;
std::cout << "Peer flush queue size: " << qsize << ", next query length: " << peer_queue.front().size() << std::endl;
}
// Nothing to do
@@ -260,12 +388,6 @@ void mysql::flush_peers() {
return;
}
if (qsize == 0) {
sql = "SET session sql_log_bin = 0";
peer_queue.push(sql);
sql.clear();
}
if (update_heavy_peer_buffer != "") {
// Because xfu inserts are slow and ram is not infinite we need to
// limit this queue's size
@@ -290,7 +412,7 @@ void mysql::flush_peers() {
if (qsize >= 1000) {
peer_queue.pop();
}
sql = "INSERT INTO xbt_files_users (fid,timespent,announced,peer_id,mtime) VALUES " +
sql = "INSERT INTO xbt_files_users (uid,fid,timespent,announced,peer_id,mtime) VALUES " +
update_light_peer_buffer +
" ON DUPLICATE KEY UPDATE upspeed=0, downspeed=0, timespent=VALUES(timespent), " +
"announced=VALUES(announced), mtime=VALUES(mtime)";
@@ -306,11 +428,15 @@ void mysql::flush_peers() {
}
void mysql::flush_tokens() {
if (readonly) {
update_token_buffer.clear();
return;
}
std::string sql;
std::unique_lock<std::mutex> tq_lock(token_queue_lock);
std::lock_guard<std::mutex> tq_lock(token_queue_lock);
size_t qsize = token_queue.size();
if (verbose_flush || qsize > 0) {
std::cout << "Token flush queue size: " << qsize << std::endl;
std::cout << "Token flush queue size: " << qsize << ", next query length: " << token_queue.front().size() << std::endl;
}
if (update_token_buffer == "") {
return;
@@ -328,7 +454,7 @@ void mysql::flush_tokens() {
void mysql::do_flush_users() {
u_active = true;
try {
mysqlpp::Connection c(db.c_str(), server.c_str(), db_user.c_str(), pw.c_str(), 0);
mysqlpp::Connection c(mysql_db.c_str(), mysql_host.c_str(), mysql_username.c_str(), mysql_password.c_str(), 0);
while (user_queue.size() > 0) {
try {
std::string sql = user_queue.front();
@@ -338,7 +464,7 @@ void mysql::do_flush_users() {
sleep(3);
continue;
} else {
std::unique_lock<std::mutex> uq_lock(user_queue_lock);
std::lock_guard<std::mutex> uq_lock(user_queue_lock);
user_queue.pop();
}
}
@@ -355,8 +481,6 @@ void mysql::do_flush_users() {
}
catch (const mysqlpp::Exception &er) {
std::cerr << "MySQL error in flush_users: " << er.what() << std::endl;
u_active = false;
return;
}
u_active = false;
}
@@ -364,7 +488,7 @@ void mysql::do_flush_users() {
void mysql::do_flush_torrents() {
t_active = true;
try {
mysqlpp::Connection c(db.c_str(), server.c_str(), db_user.c_str(), pw.c_str(), 0);
mysqlpp::Connection c(mysql_db.c_str(), mysql_host.c_str(), mysql_username.c_str(), mysql_password.c_str(), 0);
while (torrent_queue.size() > 0) {
try {
std::string sql = torrent_queue.front();
@@ -378,7 +502,7 @@ void mysql::do_flush_torrents() {
sleep(3);
continue;
} else {
std::unique_lock<std::mutex> tq_lock(torrent_queue_lock);
std::lock_guard<std::mutex> tq_lock(torrent_queue_lock);
torrent_queue.pop();
}
}
@@ -395,8 +519,6 @@ void mysql::do_flush_torrents() {
}
catch (const mysqlpp::Exception &er) {
std::cerr << "MySQL error in flush_torrents: " << er.what() << std::endl;
t_active = false;
return;
}
t_active = false;
}
@@ -404,7 +526,7 @@ void mysql::do_flush_torrents() {
void mysql::do_flush_peers() {
p_active = true;
try {
mysqlpp::Connection c(db.c_str(), server.c_str(), db_user.c_str(), pw.c_str(), 0);
mysqlpp::Connection c(mysql_db.c_str(), mysql_host.c_str(), mysql_username.c_str(), mysql_password.c_str(), 0);
while (peer_queue.size() > 0) {
try {
std::string sql = peer_queue.front();
@@ -414,7 +536,7 @@ void mysql::do_flush_peers() {
sleep(3);
continue;
} else {
std::unique_lock<std::mutex> pq_lock(peer_queue_lock);
std::lock_guard<std::mutex> pq_lock(peer_queue_lock);
peer_queue.pop();
}
}
@@ -431,8 +553,6 @@ void mysql::do_flush_peers() {
}
catch (const mysqlpp::Exception &er) {
std::cerr << "MySQL error in flush_peers: " << er.what() << std::endl;
p_active = false;
return;
}
p_active = false;
}
@@ -440,7 +560,7 @@ void mysql::do_flush_peers() {
void mysql::do_flush_snatches() {
s_active = true;
try {
mysqlpp::Connection c(db.c_str(), server.c_str(), db_user.c_str(), pw.c_str(), 0);
mysqlpp::Connection c(mysql_db.c_str(), mysql_host.c_str(), mysql_username.c_str(), mysql_password.c_str(), 0);
while (snatch_queue.size() > 0) {
try {
std::string sql = snatch_queue.front();
@@ -450,7 +570,7 @@ void mysql::do_flush_snatches() {
sleep(3);
continue;
} else {
std::unique_lock<std::mutex> sq_lock(snatch_queue_lock);
std::lock_guard<std::mutex> sq_lock(snatch_queue_lock);
snatch_queue.pop();
}
}
@@ -467,8 +587,6 @@ void mysql::do_flush_snatches() {
}
catch (const mysqlpp::Exception &er) {
std::cerr << "MySQL error in flush_snatches: " << er.what() << std::endl;
s_active = false;
return;
}
s_active = false;
}
@@ -476,7 +594,7 @@ void mysql::do_flush_snatches() {
void mysql::do_flush_tokens() {
tok_active = true;
try {
mysqlpp::Connection c(db.c_str(), server.c_str(), db_user.c_str(), pw.c_str(), 0);
mysqlpp::Connection c(mysql_db.c_str(), mysql_host.c_str(), mysql_username.c_str(), mysql_password.c_str(), 0);
while (token_queue.size() > 0) {
try {
std::string sql = token_queue.front();
@@ -486,7 +604,7 @@ void mysql::do_flush_tokens() {
sleep(3);
continue;
} else {
std::unique_lock<std::mutex> tq_lock(token_queue_lock);
std::lock_guard<std::mutex> tq_lock(token_queue_lock);
token_queue.pop();
}
}
@@ -503,8 +621,6 @@ void mysql::do_flush_tokens() {
}
catch (const mysqlpp::Exception &er) {
std::cerr << "MySQL error in flush_tokens: " << er.what() << std::endl;
tok_active = false;
return;
}
tok_active = false;
}

25
db.h
View File

@@ -6,6 +6,7 @@
#include <unordered_map>
#include <queue>
#include <mutex>
#include "config.h"
class mysql {
private:
@@ -23,8 +24,9 @@ class mysql {
std::queue<std::string> snatch_queue;
std::queue<std::string> token_queue;
std::string db, server, db_user, pw;
std::string mysql_db, mysql_host, mysql_username, mysql_password;
bool u_active, t_active, p_active, s_active, tok_active;
bool readonly;
// These locks prevent more than one thread from reading/writing the buffers.
// These should be held for the minimum time possible.
@@ -35,6 +37,9 @@ class mysql {
std::mutex snatch_queue_lock;
std::mutex token_queue_lock;
void load_config(config * conf);
void load_tokens(torrent_list &torrents);
void do_flush_users();
void do_flush_torrents();
void do_flush_snatches();
@@ -51,25 +56,27 @@ class mysql {
public:
bool verbose_flush;
mysql(std::string mysql_db, std::string mysql_host, std::string username, std::string password);
mysql(config * conf);
void reload_config(config * conf);
bool connected();
void load_torrents(torrent_list &torrents);
void load_users(user_list &users);
void load_tokens(torrent_list &torrents);
void load_whitelist(std::vector<std::string> &whitelist);
void record_user(std::string &record); // (id,uploaded_change,downloaded_change)
void record_torrent(std::string &record); // (id,seeders,leechers,snatched_change,balance)
void record_snatch(std::string &record, std::string &ip); // (uid,fid,tstamp)
void record_peer(std::string &record, std::string &ip, std::string &peer_id, std::string &useragent); // (uid,fid,active,peerid,useragent,ip,uploaded,downloaded,upspeed,downspeed,left,timespent,announces,tstamp)
void record_peer(std::string &record, std::string &peer_id); // (fid,peerid,timespent,announces,tstamp)
void record_token(std::string &record);
void record_user(const std::string &record); // (id,uploaded_change,downloaded_change)
void record_torrent(const std::string &record); // (id,seeders,leechers,snatched_change,balance)
void record_snatch(const std::string &record, const std::string &ip); // (uid,fid,tstamp)
void record_peer(const std::string &record, const std::string &ip, const std::string &peer_id, const std::string &useragent); // (uid,fid,active,peerid,useragent,ip,uploaded,downloaded,upspeed,downspeed,left,timespent,announces,tstamp)
void record_peer(const std::string &record, const std::string &peer_id); // (fid,peerid,timespent,announces,tstamp)
void record_token(const std::string &record);
void flush();
bool all_clear();
std::mutex torrent_list_mutex;
std::mutex user_list_mutex;
std::mutex whitelist_mutex;
};
#pragma GCC visibility pop

View File

@@ -1,12 +1,11 @@
#include <cerrno>
#include "ocelot.h"
#include "config.h"
#include "db.h"
#include "worker.h"
#include "events.h"
#include "schedule.h"
#include "response.h"
#include <cerrno>
#include <mutex>
#include "events.h"
// Define the connection mother (first half) and connection middlemen (second half)
@@ -14,68 +13,104 @@
//---------- Connection mother - spawns middlemen and lets them deal with the connection
connection_mother::connection_mother(worker * worker_obj, config * config_obj, mysql * db_obj, site_comm * sc_obj) : work(worker_obj), conf(config_obj), db(db_obj), sc(sc_obj) {
memset(&address, 0, sizeof(address));
addr_len = sizeof(address);
connection_mother::connection_mother(config * conf, worker * worker_obj, mysql * db_obj, site_comm * sc_obj, schedule * sched) : work(worker_obj), db(db_obj) {
// Handle config stuff first
load_config(conf);
listen_socket = socket(AF_INET, SOCK_STREAM, 0);
listen_socket = create_listen_socket();
listen_event.set<connection_mother, &connection_mother::handle_connect>(this);
listen_event.start(listen_socket, ev::READ);
// Create libev timer
schedule_event.set<schedule, &schedule::handle>(sched);
schedule_event.start(sched->schedule_interval, sched->schedule_interval); // After interval, every interval
}
void connection_mother::load_config(config * conf) {
listen_port = conf->get_uint("listen_port");
max_connections = conf->get_uint("max_connections");
max_middlemen = conf->get_uint("max_middlemen");
connection_timeout = conf->get_uint("connection_timeout");
keepalive_timeout = conf->get_uint("keepalive_timeout");
max_read_buffer = conf->get_uint("max_read_buffer");
max_request_size = conf->get_uint("max_request_size");
}
void connection_mother::reload_config(config * conf) {
unsigned int old_listen_port = listen_port;
unsigned int old_max_connections = max_connections;
load_config(conf);
if (old_listen_port != listen_port) {
std::cout << "Changing listen port from " << old_listen_port << " to " << listen_port << std::endl;
int new_listen_socket = create_listen_socket();
if (new_listen_socket != 0) {
listen_event.stop();
listen_event.start(new_listen_socket, ev::READ);
close(listen_socket);
listen_socket = new_listen_socket;
} else {
std::cout << "Couldn't create new listen socket when reloading config" << std::endl;
}
} else if (old_max_connections != max_connections) {
listen(listen_socket, max_connections);
}
}
int connection_mother::create_listen_socket() {
sockaddr_in address;
memset(&address, 0, sizeof(address));
int new_listen_socket = socket(AF_INET, SOCK_STREAM, 0);
// Stop old sockets from hogging the port
int yes = 1;
if (setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
std::cout << "Could not reuse socket" << std::endl;
if (setsockopt(new_listen_socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
std::cout << "Could not reuse socket: " << strerror(errno) << std::endl;
return 0;
}
// Create libev event loop
ev::io event_loop_watcher;
event_loop_watcher.set<connection_mother, &connection_mother::handle_connect>(this);
event_loop_watcher.start(listen_socket, ev::READ);
// Get ready to bind
address.sin_family = AF_INET;
//address.sin_addr.s_addr = inet_addr(conf->host.c_str()); // htonl(INADDR_ANY)
address.sin_addr.s_addr = htonl(INADDR_ANY);
address.sin_port = htons(conf->port);
address.sin_port = htons(listen_port);
// Bind
if (bind(listen_socket, (sockaddr *) &address, sizeof(address)) == -1) {
std::cout << "Bind failed " << errno << std::endl;
if (bind(new_listen_socket, (sockaddr *) &address, sizeof(address)) == -1) {
std::cout << "Bind failed: " << strerror(errno) << std::endl;
return 0;
}
// Listen
if (listen(listen_socket, conf->max_connections) == -1) {
std::cout << "Listen failed" << std::endl;
if (listen(new_listen_socket, max_connections) == -1) {
std::cout << "Listen failed: " << strerror(errno) << std::endl;
return 0;
}
// Set non-blocking
int flags = fcntl(listen_socket, F_GETFL);
int flags = fcntl(new_listen_socket, F_GETFL);
if (flags == -1) {
std::cout << "Could not get socket flags" << std::endl;
std::cout << "Could not get socket flags: " << strerror(errno) << std::endl;
return 0;
}
if (fcntl(listen_socket, F_SETFL, flags | O_NONBLOCK) == -1) {
std::cout << "Could not set non-blocking" << std::endl;
if (fcntl(new_listen_socket, F_SETFL, flags | O_NONBLOCK) == -1) {
std::cout << "Could not set non-blocking: " << strerror(errno) << std::endl;
return 0;
}
// Create libev timer
schedule timer(this, worker_obj, conf, db, sc);
return new_listen_socket;
}
schedule_event.set<schedule, &schedule::handle>(&timer);
schedule_event.set(conf->schedule_interval, conf->schedule_interval); // After interval, every interval
schedule_event.start();
std::cout << "Sockets up, starting event loop!" << std::endl;
void connection_mother::run() {
std::cout << "Sockets up on port " << listen_port << ", starting event loop!" << std::endl;
ev_loop(ev_default_loop(0), 0);
}
void connection_mother::handle_connect(ev::io &watcher, int events_flags) {
// Spawn a new middleman
if (stats.open_connections < conf->max_middlemen) {
std::unique_lock<std::mutex> lock(stats.mutex);
if (stats.open_connections < max_middlemen) {
stats.opened_connections++;
lock.unlock();
new connection_middleman(listen_socket, address, addr_len, work, this, conf);
stats.open_connections++;
new connection_middleman(listen_socket, work, this);
}
}
@@ -92,15 +127,13 @@ connection_mother::~connection_mother()
//---------- Connection middlemen - these little guys live until their connection is closed
connection_middleman::connection_middleman(int &listen_socket, sockaddr_in &address, socklen_t &addr_len, worker * new_work, connection_mother * mother_arg, config * config_obj) :
conf(config_obj), mother (mother_arg), work(new_work) {
connect_sock = accept(listen_socket, (sockaddr *) &address, &addr_len);
connection_middleman::connection_middleman(int &listen_socket, worker * new_work, connection_mother * mother_arg) :
written(0), mother(mother_arg), work(new_work)
{
connect_sock = accept(listen_socket, NULL, NULL);
if (connect_sock == -1) {
std::cout << "Accept failed, errno " << errno << ": " << strerror(errno) << std::endl;
delete this;
std::unique_lock<std::mutex> lock(stats.mutex);
stats.open_connections++; // destructor decrements open connections
return;
}
@@ -114,10 +147,7 @@ connection_middleman::connection_middleman(int &listen_socket, sockaddr_in &addr
}
// Get their info
if (getpeername(connect_sock, (sockaddr *) &client_addr, &addr_len) == -1) {
//std::cout << "Could not get client info" << std::endl;
}
request.reserve(conf->max_read_buffer);
request.reserve(mother->max_read_buffer);
written = 0;
read_event.set<connection_middleman, &connection_middleman::handle_read>(this);
@@ -125,47 +155,50 @@ connection_middleman::connection_middleman(int &listen_socket, sockaddr_in &addr
// Let the socket timeout in timeout_interval seconds
timeout_event.set<connection_middleman, &connection_middleman::handle_timeout>(this);
timeout_event.set(conf->timeout_interval, 0);
timeout_event.set(mother->connection_timeout, mother->keepalive_timeout);
timeout_event.start();
std::unique_lock<std::mutex> lock(stats.mutex);
stats.open_connections++;
}
connection_middleman::~connection_middleman() {
close(connect_sock);
std::unique_lock<std::mutex> lock(stats.mutex);
stats.open_connections--;
}
// Handler to read data from the socket, called by event loop when socket is readable
void connection_middleman::handle_read(ev::io &watcher, int events_flags) {
char buffer[conf->max_read_buffer + 1];
memset(buffer, 0, conf->max_read_buffer + 1);
int ret = recv(connect_sock, &buffer, conf->max_read_buffer, 0);
char buffer[mother->max_read_buffer + 1];
memset(buffer, 0, mother->max_read_buffer + 1);
int ret = recv(connect_sock, &buffer, mother->max_read_buffer, 0);
if (ret <= 0) {
delete this;
return;
}
std::unique_lock<std::mutex> lock(stats.mutex);
stats.bytes_read += ret;
lock.unlock();
request.append(buffer, ret);
size_t request_size = request.size();
if (request_size > conf->max_request_size || (request_size >= 4 && request.compare(request_size - 4, std::string::npos, "\r\n\r\n") == 0)) {
if (request_size > mother->max_request_size || (request_size >= 4 && request.compare(request_size - 4, std::string::npos, "\r\n\r\n") == 0)) {
stats.requests++;
read_event.stop();
client_opts.gzip = false;
client_opts.html = false;
client_opts.http_close = true;
if (request_size > conf->max_request_size) {
if (request_size > mother->max_request_size) {
shutdown(connect_sock, SHUT_RD);
response = error("GET string too long");
response = error("GET string too long", client_opts);
} else {
char ip[INET_ADDRSTRLEN];
sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
getpeername(connect_sock, (sockaddr *) &client_addr, &addr_len);
inet_ntop(AF_INET, &(client_addr.sin_addr), ip, INET_ADDRSTRLEN);
std::string ip_str = ip;
//--- CALL WORKER
response = work->work(request, ip_str);
response = work->work(request, ip_str, client_opts);
request.clear();
request_size = 0;
}
// Find out when the socket is writeable.
@@ -178,14 +211,22 @@ void connection_middleman::handle_read(ev::io &watcher, int events_flags) {
// Handler to write data to the socket, called by event loop when socket is writeable
void connection_middleman::handle_write(ev::io &watcher, int events_flags) {
int ret = send(connect_sock, response.c_str()+written, response.size()-written, MSG_NOSIGNAL);
written += ret;
std::unique_lock<std::mutex> lock(stats.mutex);
if (ret == -1) {
return;
}
stats.bytes_written += ret;
lock.unlock();
written += ret;
if (written == response.size()) {
write_event.stop();
timeout_event.stop();
delete this;
if (client_opts.http_close) {
timeout_event.stop();
delete this;
return;
}
timeout_event.again();
read_event.start();
response.clear();
written = 0;
}
}

View File

@@ -11,7 +11,11 @@
#include <fcntl.h>
#include <unistd.h>
#include "config.h"
#include "worker.h"
#include "schedule.h"
#include "db.h"
#include "site_comm.h"
/*
TODO find out what these do
@@ -47,19 +51,29 @@ THE WORKER
// THE MOTHER - Spawns connection middlemen
class connection_mother {
private:
void load_config(config * conf);
unsigned int listen_port;
unsigned int max_connections;
int listen_socket;
sockaddr_in address;
socklen_t addr_len;
worker * work;
config * conf;
mysql * db;
site_comm * sc;
ev::io listen_event;
ev::timer schedule_event;
public:
connection_mother(worker * worker_obj, config * config_obj, mysql * db_obj, site_comm * sc_obj);
void handle_connect(ev::io &watcher, int events_flags);
connection_mother(config * conf, worker * worker_obj, mysql * db_obj, site_comm * sc_obj, schedule * sched_obj);
~connection_mother();
void reload_config(config * conf);
int create_listen_socket();
void run();
void handle_connect(ev::io &watcher, int events_flags);
unsigned int max_middlemen;
unsigned int connection_timeout;
unsigned int keepalive_timeout;
unsigned int max_read_buffer;
unsigned int max_request_size;
};
// THE MIDDLEMAN
@@ -68,6 +82,7 @@ class connection_mother {
class connection_middleman {
private:
int connect_sock;
client_opts_t client_opts;
unsigned int written;
ev::io read_event;
ev::io write_event;
@@ -75,13 +90,11 @@ class connection_middleman {
std::string request;
std::string response;
config * conf;
connection_mother * mother;
worker * work;
sockaddr_in client_addr;
public:
connection_middleman(int &listen_socket, sockaddr_in &address, socklen_t &addr_len, worker* work, connection_mother * mother_arg, config * config_obj);
connection_middleman(int &listen_socket, worker* work, connection_mother * mother_arg);
~connection_middleman();
void handle_read(ev::io &watcher, int events_flags);

332
m4/ax_pthread.m4 Normal file
View File

@@ -0,0 +1,332 @@
# ===========================================================================
# http://www.gnu.org/software/autoconf-archive/ax_pthread.html
# ===========================================================================
#
# SYNOPSIS
#
# AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]])
#
# DESCRIPTION
#
# This macro figures out how to build C programs using POSIX threads. It
# sets the PTHREAD_LIBS output variable to the threads library and linker
# flags, and the PTHREAD_CFLAGS output variable to any special C compiler
# flags that are needed. (The user can also force certain compiler
# flags/libs to be tested by setting these environment variables.)
#
# Also sets PTHREAD_CC to any special C compiler that is needed for
# multi-threaded programs (defaults to the value of CC otherwise). (This
# is necessary on AIX to use the special cc_r compiler alias.)
#
# NOTE: You are assumed to not only compile your program with these flags,
# but also link it with them as well. e.g. you should link with
# $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS
#
# If you are only building threads programs, you may wish to use these
# variables in your default LIBS, CFLAGS, and CC:
#
# LIBS="$PTHREAD_LIBS $LIBS"
# CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
# CC="$PTHREAD_CC"
#
# In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant
# has a nonstandard name, defines PTHREAD_CREATE_JOINABLE to that name
# (e.g. PTHREAD_CREATE_UNDETACHED on AIX).
#
# Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the
# PTHREAD_PRIO_INHERIT symbol is defined when compiling with
# PTHREAD_CFLAGS.
#
# ACTION-IF-FOUND is a list of shell commands to run if a threads library
# is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it
# is not found. If ACTION-IF-FOUND is not specified, the default action
# will define HAVE_PTHREAD.
#
# Please let the authors know if this macro fails on any platform, or if
# you have any other suggestions or comments. This macro was based on work
# by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help
# from M. Frigo), as well as ac_pthread and hb_pthread macros posted by
# Alejandro Forero Cuervo to the autoconf macro repository. We are also
# grateful for the helpful feedback of numerous users.
#
# Updated for Autoconf 2.68 by Daniel Richard G.
#
# LICENSE
#
# Copyright (c) 2008 Steven G. Johnson <stevenj@alum.mit.edu>
# Copyright (c) 2011 Daniel Richard G. <skunk@iSKUNK.ORG>
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
# As a special exception, the respective Autoconf Macro's copyright owner
# gives unlimited permission to copy, distribute and modify the configure
# scripts that are the output of Autoconf when processing the Macro. You
# need not follow the terms of the GNU General Public License when using
# or distributing such scripts, even though portions of the text of the
# Macro appear in them. The GNU General Public License (GPL) does govern
# all other use of the material that constitutes the Autoconf Macro.
#
# This special exception to the GPL applies to versions of the Autoconf
# Macro released by the Autoconf Archive. When you make and distribute a
# modified version of the Autoconf Macro, you may extend this special
# exception to the GPL to apply to your modified version as well.
#serial 21
AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD])
AC_DEFUN([AX_PTHREAD], [
AC_REQUIRE([AC_CANONICAL_HOST])
AC_LANG_PUSH([C])
ax_pthread_ok=no
# We used to check for pthread.h first, but this fails if pthread.h
# requires special compiler flags (e.g. on True64 or Sequent).
# It gets checked for in the link test anyway.
# First of all, check if the user has set any of the PTHREAD_LIBS,
# etcetera environment variables, and if threads linking works using
# them:
if test x"$PTHREAD_LIBS$PTHREAD_CFLAGS" != x; then
save_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
save_LIBS="$LIBS"
LIBS="$PTHREAD_LIBS $LIBS"
AC_MSG_CHECKING([for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS])
AC_TRY_LINK_FUNC([pthread_join], [ax_pthread_ok=yes])
AC_MSG_RESULT([$ax_pthread_ok])
if test x"$ax_pthread_ok" = xno; then
PTHREAD_LIBS=""
PTHREAD_CFLAGS=""
fi
LIBS="$save_LIBS"
CFLAGS="$save_CFLAGS"
fi
# We must check for the threads library under a number of different
# names; the ordering is very important because some systems
# (e.g. DEC) have both -lpthread and -lpthreads, where one of the
# libraries is broken (non-POSIX).
# Create a list of thread flags to try. Items starting with a "-" are
# C compiler flags, and other items are library names, except for "none"
# which indicates that we try without any flags at all, and "pthread-config"
# which is a program returning the flags for the Pth emulation library.
ax_pthread_flags="pthreads none -Kthread -kthread lthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config"
# The ordering *is* (sometimes) important. Some notes on the
# individual items follow:
# pthreads: AIX (must check this before -lpthread)
# none: in case threads are in libc; should be tried before -Kthread and
# other compiler flags to prevent continual compiler warnings
# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h)
# -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able)
# lthread: LinuxThreads port on FreeBSD (also preferred to -pthread)
# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads)
# -pthreads: Solaris/gcc
# -mthreads: Mingw32/gcc, Lynx/gcc
# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it
# doesn't hurt to check since this sometimes defines pthreads too;
# also defines -D_REENTRANT)
# ... -mt is also the pthreads flag for HP/aCC
# pthread: Linux, etcetera
# --thread-safe: KAI C++
# pthread-config: use pthread-config program (for GNU Pth library)
case ${host_os} in
solaris*)
# On Solaris (at least, for some versions), libc contains stubbed
# (non-functional) versions of the pthreads routines, so link-based
# tests will erroneously succeed. (We need to link with -pthreads/-mt/
# -lpthread.) (The stubs are missing pthread_cleanup_push, or rather
# a function called by this macro, so we could check for that, but
# who knows whether they'll stub that too in a future libc.) So,
# we'll just look for -pthreads and -lpthread first:
ax_pthread_flags="-pthreads pthread -mt -pthread $ax_pthread_flags"
;;
darwin*)
ax_pthread_flags="-pthread $ax_pthread_flags"
;;
esac
# Clang doesn't consider unrecognized options an error unless we specify
# -Werror. We throw in some extra Clang-specific options to ensure that
# this doesn't happen for GCC, which also accepts -Werror.
AC_MSG_CHECKING([if compiler needs -Werror to reject unknown flags])
save_CFLAGS="$CFLAGS"
ax_pthread_extra_flags="-Werror"
CFLAGS="$CFLAGS $ax_pthread_extra_flags -Wunknown-warning-option -Wsizeof-array-argument"
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([int foo(void);],[foo()])],
[AC_MSG_RESULT([yes])],
[ax_pthread_extra_flags=
AC_MSG_RESULT([no])])
CFLAGS="$save_CFLAGS"
if test x"$ax_pthread_ok" = xno; then
for flag in $ax_pthread_flags; do
case $flag in
none)
AC_MSG_CHECKING([whether pthreads work without any flags])
;;
-*)
AC_MSG_CHECKING([whether pthreads work with $flag])
PTHREAD_CFLAGS="$flag"
;;
pthread-config)
AC_CHECK_PROG([ax_pthread_config], [pthread-config], [yes], [no])
if test x"$ax_pthread_config" = xno; then continue; fi
PTHREAD_CFLAGS="`pthread-config --cflags`"
PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`"
;;
*)
AC_MSG_CHECKING([for the pthreads library -l$flag])
PTHREAD_LIBS="-l$flag"
;;
esac
save_LIBS="$LIBS"
save_CFLAGS="$CFLAGS"
LIBS="$PTHREAD_LIBS $LIBS"
CFLAGS="$CFLAGS $PTHREAD_CFLAGS $ax_pthread_extra_flags"
# Check for various functions. We must include pthread.h,
# since some functions may be macros. (On the Sequent, we
# need a special flag -Kthread to make this header compile.)
# We check for pthread_join because it is in -lpthread on IRIX
# while pthread_create is in libc. We check for pthread_attr_init
# due to DEC craziness with -lpthreads. We check for
# pthread_cleanup_push because it is one of the few pthread
# functions on Solaris that doesn't have a non-functional libc stub.
# We try pthread_create on general principles.
AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <pthread.h>
static void routine(void *a) { a = 0; }
static void *start_routine(void *a) { return a; }],
[pthread_t th; pthread_attr_t attr;
pthread_create(&th, 0, start_routine, 0);
pthread_join(th, 0);
pthread_attr_init(&attr);
pthread_cleanup_push(routine, 0);
pthread_cleanup_pop(0) /* ; */])],
[ax_pthread_ok=yes],
[])
LIBS="$save_LIBS"
CFLAGS="$save_CFLAGS"
AC_MSG_RESULT([$ax_pthread_ok])
if test "x$ax_pthread_ok" = xyes; then
break;
fi
PTHREAD_LIBS=""
PTHREAD_CFLAGS=""
done
fi
# Various other checks:
if test "x$ax_pthread_ok" = xyes; then
save_LIBS="$LIBS"
LIBS="$PTHREAD_LIBS $LIBS"
save_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
# Detect AIX lossage: JOINABLE attribute is called UNDETACHED.
AC_MSG_CHECKING([for joinable pthread attribute])
attr_name=unknown
for attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do
AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <pthread.h>],
[int attr = $attr; return attr /* ; */])],
[attr_name=$attr; break],
[])
done
AC_MSG_RESULT([$attr_name])
if test "$attr_name" != PTHREAD_CREATE_JOINABLE; then
AC_DEFINE_UNQUOTED([PTHREAD_CREATE_JOINABLE], [$attr_name],
[Define to necessary symbol if this constant
uses a non-standard name on your system.])
fi
AC_MSG_CHECKING([if more special flags are required for pthreads])
flag=no
case ${host_os} in
aix* | freebsd* | darwin*) flag="-D_THREAD_SAFE";;
osf* | hpux*) flag="-D_REENTRANT";;
solaris*)
if test "$GCC" = "yes"; then
flag="-D_REENTRANT"
else
# TODO: What about Clang on Solaris?
flag="-mt -D_REENTRANT"
fi
;;
esac
AC_MSG_RESULT([$flag])
if test "x$flag" != xno; then
PTHREAD_CFLAGS="$flag $PTHREAD_CFLAGS"
fi
AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT],
[ax_cv_PTHREAD_PRIO_INHERIT], [
AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <pthread.h>]],
[[int i = PTHREAD_PRIO_INHERIT;]])],
[ax_cv_PTHREAD_PRIO_INHERIT=yes],
[ax_cv_PTHREAD_PRIO_INHERIT=no])
])
AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes"],
[AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], [1], [Have PTHREAD_PRIO_INHERIT.])])
LIBS="$save_LIBS"
CFLAGS="$save_CFLAGS"
# More AIX lossage: compile with *_r variant
if test "x$GCC" != xyes; then
case $host_os in
aix*)
AS_CASE(["x/$CC"],
[x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6],
[#handle absolute path differently from PATH based program lookup
AS_CASE(["x$CC"],
[x/*],
[AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"])],
[AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC])])])
;;
esac
fi
fi
test -n "$PTHREAD_CC" || PTHREAD_CC="$CC"
AC_SUBST([PTHREAD_LIBS])
AC_SUBST([PTHREAD_CFLAGS])
AC_SUBST([PTHREAD_CC])
# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND:
if test x"$ax_pthread_ok" = xyes; then
ifelse([$1],,[AC_DEFINE([HAVE_PTHREAD],[1],[Define if you have POSIX threads libraries and header files.])],[$1])
:
else
ax_pthread_ok=no
$2
fi
AC_LANG_POP
])dnl AX_PTHREAD

View File

@@ -47,12 +47,17 @@ AC_DEFUN([EV_DEVEL],
if test -z "$ac_cv_ev_lib"
then
AC_MSG_RESULT([no])
AC_MSG_ERROR([Didn't find the libev library dir in '$EV_lib_check'])
fi
case "$ac_cv_ev_lib" in
/* ) ;;
* ) AC_MSG_ERROR([The libev library directory ($ac_cv_ev_lib) must be an absolute path.]) ;;
/* )
;;
* )
AC_MSG_RESULT([])
AC_MSG_ERROR([The libev library directory ($ac_cv_ev_lib) must be an absolute path.])
;;
esac
])
AC_SUBST([EV_LIB_DIR],[$ac_cv_ev_lib])
@@ -73,12 +78,17 @@ AC_DEFUN([EV_DEVEL],
if test -z "$ac_cv_ev_inc"
then
AC_MSG_RESULT([no])
AC_MSG_ERROR([Didn't find the libev header dir in '$EV_inc_check'])
fi
case "$ac_cv_ev_inc" in
/* ) ;;
* ) AC_MSG_ERROR([The libev header directory ($ac_cv_ev_inc) must be an absolute path.]) ;;
/* )
;;
* )
AC_MSG_RESULT([])
AC_MSG_ERROR([The libev header directory ($ac_cv_ev_inc) must be an absolute path.])
;;
esac
])
AC_SUBST([EV_INC_DIR],[$ac_cv_ev_inc])
@@ -88,15 +98,38 @@ AC_DEFUN([EV_DEVEL],
dnl let us build actual programs against libev.
dnl
case "$ac_cv_ev_lib" in
/usr/lib) ;;
*) LDFLAGS="$LDFLAGS -L${ac_cv_ev_lib}" ;;
/usr/lib)
;;
*)
LDFLAGS="$LDFLAGS -L${ac_cv_ev_lib}"
;;
esac
CPPFLAGS="$CPPFLAGS -I${ac_cv_ev_inc}"
AC_MSG_CHECKING([that we can build libev programs])
AC_COMPILE_IFELSE(
[AC_LANG_PROGRAM([#include <ev++.h>],
[ev::io i])],
AC_MSG_RESULT([yes]),
AC_MSG_ERROR([no]))
AC_EGREP_CPP(
[bad_gcc_libev_ver],
[
#include <ev.h>
#if __GNUC__ && (__GNUC__ < 4 || __GNUC__ == 4 && __GNUC_MINOR__ < 9) && EV_VERSION_MAJOR == 4 && EV_VERSION_MINOR == 18
bad_gcc_libev_ver
#endif
],
[AC_MSG_RESULT([no])
AC_MSG_ERROR([GCC versions prior to 4.9 cannot compile libev 4.18])],
[],
)
LIBS="-lev $LIBS"
AC_LANG_PUSH([C++])
AC_LINK_IFELSE(
[AC_LANG_PROGRAM(
[#include <ev++.h>],
[ev::io i])
],
[AC_MSG_RESULT([yes])],
[AC_MSG_RESULT([no])
AC_MSG_FAILURE([Cannot build libev programs])]
)
AC_LANG_POP([C++])
]) dnl End EV_DEVEL

View File

@@ -76,12 +76,17 @@ AC_DEFUN([MYSQLPP_DEVEL],
if test -z "$ac_cv_mysqlpp_lib"
then
AC_MSG_RESULT([no])
AC_MSG_ERROR([Didn't find the MySQL++ library dir in '$MYSQLPP_lib_check'])
fi
case "$ac_cv_mysqlpp_lib" in
/* ) ;;
* ) AC_MSG_ERROR([The MySQL++ library directory ($ac_cv_mysqlpp_lib) must be an absolute path.]) ;;
/* )
;;
* )
AC_MSG_RESULT([no])
AC_MSG_ERROR([The MySQL++ library directory ($ac_cv_mysqlpp_lib) must be an absolute path.])
;;
esac
])
AC_SUBST([MYSQLPP_LIB_DIR],[$ac_cv_mysqlpp_lib])
@@ -102,12 +107,17 @@ AC_DEFUN([MYSQLPP_DEVEL],
if test -z "$ac_cv_mysqlpp_inc"
then
AC_MSG_RESULT([no])
AC_MSG_ERROR([Didn't find the MySQL++ header dir in '$MYSQLPP_inc_check'])
fi
case "$ac_cv_mysqlpp_inc" in
/* ) ;;
* ) AC_MSG_ERROR([The MySQL++ header directory ($ac_cv_mysqlpp_inc) must be an absolute path.]) ;;
/* )
;;
* )
AC_MSG_RESULT([no])
AC_MSG_ERROR([The MySQL++ header directory ($ac_cv_mysqlpp_inc) must be an absolute path.])
;;
esac
])
AC_SUBST([MYSQLPP_INC_DIR],[$ac_cv_mysqlpp_inc])
@@ -117,15 +127,24 @@ AC_DEFUN([MYSQLPP_DEVEL],
dnl let us build actual programs against MySQL++.
dnl
case "$ac_cv_mysqlpp_lib" in
/usr/lib) ;;
*) LDFLAGS="$LDFLAGS -L${ac_cv_mysqlpp_lib}" ;;
/usr/lib)
;;
*)
LDFLAGS="$LDFLAGS -L${ac_cv_mysqlpp_lib}"
;;
esac
CPPFLAGS="$CPPFLAGS -I${ac_cv_mysqlpp_inc} -I${MYSQL_C_INC_DIR}"
CPPFLAGS="$CPPFLAGS -I${ac_cv_mysqlpp_inc}"
LIBS="-lmysqlpp $LIBS"
AC_LANG_PUSH([C++])
AC_MSG_CHECKING([that we can build MySQL++ programs])
AC_COMPILE_IFELSE(
[AC_LANG_PROGRAM([#include <mysql++.h>],
[mysqlpp::Connection c(false)])],
AC_MSG_RESULT([yes]),
AC_MSG_ERROR([no]))
AC_LINK_IFELSE(
[AC_LANG_PROGRAM(
[#include <mysql++.h>],
[mysqlpp::Connection c(false)]
)],
[AC_MSG_RESULT([yes])],
[AC_MSG_RESULT([no])
AC_MSG_ERROR([Cannot build MySQL++ programs])]
)
AC_LANG_POP([C++])
]) dnl End MYSQLPP_DEVEL

View File

@@ -6,7 +6,7 @@ dnl We define the following configure script flags:
dnl
dnl --with-mysql: Give prefix for both library and headers, and try
dnl to guess subdirectory names for each by tacking common
dnl suffixes on like /lib and /include.
dnl suffixes on like /lib and /include.
dnl --with-mysql-lib: Same as --with-mysql, but for library only.
dnl --with-mysql-include: Same as --with-mysql, but for headers only.
dnl
@@ -58,23 +58,27 @@ AC_DEFUN([MYSQL_C_API_LOCATION],
if test -z "$MYSQL_C_LIB_DIR"
then
AC_MSG_RESULT([no])
AC_MSG_ERROR([Didn't find $MYSQL_C_LIB_NAME library in '$MYSQL_lib_check'])
fi
case "$MYSQL_C_LIB_DIR" in
/* ) ;;
* ) AC_MSG_ERROR([The MySQL library directory ($MYSQL_C_LIB_DIR) must be an absolute path.]) ;;
/* )
;;
* )
AC_MSG_RESULT([])
AC_MSG_ERROR([The MySQL library directory ($MYSQL_C_LIB_DIR) must be an absolute path.]) ;;
esac
AC_MSG_RESULT([$MYSQL_C_LIB_DIR])
case "$MYSQL_C_LIB_DIR" in
/usr/lib)
MYSQL_C_LIB_DIR=
;;
*)
LDFLAGS="$LDFLAGS -L${MYSQL_C_LIB_DIR}"
;;
/usr/lib)
MYSQL_C_LIB_DIR=
;;
*)
LDFLAGS="$LDFLAGS -L${MYSQL_C_LIB_DIR}"
;;
esac
@@ -94,45 +98,48 @@ AC_DEFUN([MYSQL_C_API_LOCATION],
if test -z "$MYSQL_C_INC_DIR"
then
AC_MSG_RESULT([no])
AC_MSG_ERROR([Didn't find the MySQL include dir in '$MYSQL_inc_check'])
fi
case "$MYSQL_C_INC_DIR" in
/* ) ;;
* ) AC_MSG_ERROR([The MySQL include directory ($MYSQL_C_INC_DIR) must be an absolute path.]) ;;
/* )
;;
* )
AC_MSG_RESULT([])
AC_MSG_ERROR([The MySQL include directory ($MYSQL_C_INC_DIR) must be an absolute path.]) ;;
esac
AC_MSG_RESULT([$MYSQL_C_INC_DIR])
CPPFLAGS="$CPPFLAGS -I${MYSQL_C_INC_DIR}"
AC_MSG_CHECKING([if we can link to MySQL C API library directly])
AC_MSG_CHECKING([if we can link to MySQL C API library directly])
save_LIBS=$LIBS
LIBS="$LIBS -l$MYSQL_C_LIB_NAME $MYSQLPP_EXTRA_LIBS"
AC_TRY_LINK(
[ #include <mysql.h> ],
[ mysql_store_result(0); ],
AC_MSG_RESULT([yes]),
[ AC_MSG_RESULT([no])
LIBS="$save_LIBS"
AC_CHECK_HEADERS(zlib.h, AC_CHECK_LIB(z, gzread, [],
[ AC_MSG_ERROR([zlib not found]) ]))
AC_MSG_CHECKING([whether adding -lz will let MySQL C API link succeed])
MYSQLPP_EXTRA_LIBS="$MYSQLPP_EXTRA_LIBS -lz"
LIBS="$save_LIBS -l$MYSQL_C_LIB_NAME $MYSQLPP_EXTRA_LIBS"
AC_TRY_LINK(
[ #include <mysql.h> ],
[ mysql_store_result(0); ],
AC_MSG_RESULT([yes]),
[ AC_MSG_RESULT([no])
AC_MSG_ERROR([Unable to link to MySQL client library!])
]
)
])
LIBS=$save_LIBS
LIBS="-l$MYSQL_C_LIB_NAME $LIBS"
AC_LINK_IFELSE(
[AC_LANG_PROGRAM(
[#include <mysql.h>],
[mysql_store_result(0);])],
[AC_MSG_RESULT([yes])],
[AC_MSG_RESULT([no])
LIBS="$save_LIBS"
AC_CHECK_HEADERS(zlib.h, AC_CHECK_LIB(z, gzread, [],
[AC_MSG_ERROR([zlib not found])]))
AC_MSG_CHECKING([whether adding -lz will let MySQL C API link succeed])
LIBS="-l$MYSQL_C_LIB_NAME -lz $save_LIBS"
AC_LINK_IFELSE(
[AC_LANG_PROGRAM(
[#include <mysql.h>],
[mysql_store_result(0);]
)],
[AC_MSG_RESULT([yes])],
[AC_MSG_RESULT([no])
AC_MSG_ERROR([Unable to link to MySQL client library!])]
)]
)
AC_SUBST(MYSQL_C_INC_DIR)
AC_SUBST(MYSQL_C_LIB_DIR)
AC_SUBST(MYSQL_C_LIB_NAME)
]) dnl MYSQL_C_API_LOCATION

View File

@@ -6,7 +6,6 @@ AC_DEFUN([TCMALLOC],
[with_tcmalloc=yes])
AS_IF([test "x$with_tcmalloc" != "xno"],
[AC_CHECK_LIB([tcmalloc], [tc_free], [],
[AC_MSG_NOTICE([tcmalloc libraries not installed])]
)])
])
[AC_CHECK_LIB([tcmalloc], [tc_free])]
)]
)

View File

@@ -2,15 +2,15 @@
#include <iostream>
#include <sstream>
long strtolong(const std::string& str) {
std::istringstream stream (str);
long i = 0;
int32_t strtoint32(const std::string& str) {
std::istringstream stream(str);
int32_t i = 0;
stream >> i;
return i;
}
int64_t strtolonglong(const std::string& str) {
std::istringstream stream (str);
int64_t strtoint64(const std::string& str) {
std::istringstream stream(str);
int64_t i = 0;
stream >> i;
return i;
@@ -59,17 +59,17 @@ std::string hex_decode(const std::string &in) {
std::string bintohex(const std::string &in) {
std::string out;
out.reserve(40);
size_t length = in.length();
out.reserve(2*length);
for (unsigned int i = 0; i < length; i++) {
unsigned char x = (unsigned char)in[i] >> 4;
unsigned char x = static_cast<unsigned char>((in[i] & 0xF0) >> 4);
if (x > 9) {
x += 'a' - 10;
} else {
x += '0';
}
out.push_back(x);
x = in[i] & 0xF;
x = in[i] & 0x0F;
if (x > 9) {
x += 'a' - 10;
} else {

View File

@@ -1,12 +1,11 @@
#ifndef MISC_FUNCTIONS__H
#define MISC_FUNCTIONS__H
#include <string>
#include <cstdlib>
long strtolong(const std::string& str);
int64_t strtolonglong(const std::string& str);
int32_t strtoint32(const std::string& str);
int64_t strtoint64(const std::string& str);
std::string inttostr(int i);
std::string hex_decode(const std::string &in);
std::string bintohex(const std::string &in);
int timeval_subtract (timeval* result, timeval* x, timeval* y);
#endif

View File

@@ -3,7 +3,7 @@
scriptversion=2013-10-28.13; # UTC
# Copyright (C) 1996-2013 Free Software Foundation, Inc.
# Copyright (C) 1996-2014 Free Software Foundation, Inc.
# Originally written by Fran,cois Pinard <pinard@iro.umontreal.ca>, 1996.
# This program is free software; you can redistribute it and/or modify

32
ocelot.conf.dist Normal file
View File

@@ -0,0 +1,32 @@
# Ocelot config file
# Lines starting with a # are ignored
# A # anywhere else is treated like any other character
listen_port = 34000
max_connections = 128
max_middlemen = 20000
max_read_buffer = 4096
connection_timeout = 10
# Keepalive is mostly useful if the tracker runs behind reverse proxies
keepalive_timeout = 0
announce_interval = 1800
max_request_size = 4096
numwant_limit = 50
request_log_size = 500
mysql_host =
mysql_username =
mysql_password =
mysql_db =
# The passwords must be 32 characters and match the Gazelle config
report_password = 00000000000000000000000000000000
site_password = 00000000000000000000000000000000
peers_timeout = 7200
del_reason_lifetime = 86400
reap_peers_interval = 1800
schedule_interval = 3
readonly = false

View File

@@ -1,3 +1,7 @@
#include <fstream>
#include <iostream>
#include <csignal>
#include <thread>
#include "ocelot.h"
#include "config.h"
#include "db.h"
@@ -8,13 +12,33 @@
static connection_mother *mother;
static worker *work;
struct stats stats;
static mysql *db;
static site_comm *sc;
static config *conf;
static schedule *sched;
static void sig_handler(int sig)
{
std::cout << "Caught SIGINT/SIGTERM" << std::endl;
if (work->signal(sig)) {
exit(0);
struct stats_t stats;
static void sig_handler(int sig) {
if (sig == SIGINT || sig == SIGTERM) {
std::cout << "Caught SIGINT/SIGTERM" << std::endl;
if (work->shutdown()) {
exit(0);
}
} else if (sig == SIGHUP) {
std::cout << "Reloading config" << std::endl;
std::cout.flush();
conf->reload();
db->reload_config(conf);
mother->reload_config(conf);
sc->reload_config(conf);
sched->reload_config(conf);
work->reload_config(conf);
std::cout << "Done reloading config" << std::endl;
} else if (sig == SIGUSR1) {
std::cout << "Reloading from database" << std::endl;
std::thread w_thread(&worker::reload_lists, work);
w_thread.detach();
}
}
@@ -22,48 +46,55 @@ int main(int argc, char **argv) {
// we don't use printf so make cout/cerr a little bit faster
std::ios_base::sync_with_stdio(false);
config conf;
conf = new config();
signal(SIGINT, sig_handler);
signal(SIGTERM, sig_handler);
bool verbose = false;
for (int i = argc; i > 1; i--) {
if (!strcmp(argv[1], "-v")) {
bool verbose = false, conf_arg = false;
std::string conf_file_path("./ocelot.conf");
for (int i = 1; i < argc; i++) {
if (!strcmp(argv[i], "-v")) {
verbose = true;
} else if (!strcmp(argv[i], "-c") && i < argc - 1) {
conf_arg = true;
conf_file_path = argv[++i];
} else {
std::cout << "Usage: " << argv[0] << " [-v] [-c configfile]" << std::endl;
return 0;
}
}
mysql db(conf.mysql_db, conf.mysql_host, conf.mysql_username, conf.mysql_password);
if (!db.connected()) {
std::ifstream conf_file(conf_file_path);
if (conf_file.fail()) {
std::cout << "Using default config because '" << conf_file_path << "' couldn't be opened" << std::endl;
if (!conf_arg) {
std::cout << "Start Ocelot with -c <path> to specify config file if necessary" << std::endl;
}
} else {
conf->load(conf_file_path, conf_file);
}
db = new mysql(conf);
if (!db->connected()) {
std::cout << "Exiting" << std::endl;
return 0;
}
db.verbose_flush = verbose;
db->verbose_flush = verbose;
site_comm sc(conf);
sc.verbose_flush = verbose;
std::vector<std::string> whitelist;
db.load_whitelist(whitelist);
std::cout << "Loaded " << whitelist.size() << " clients into the whitelist" << std::endl;
if (whitelist.size() == 0) {
std::cout << "Assuming no whitelist desired, disabling" << std::endl;
}
sc = new site_comm(conf);
sc->verbose_flush = verbose;
user_list users_list;
db.load_users(users_list);
std::cout << "Loaded " << users_list.size() << " users" << std::endl;
torrent_list torrents_list;
db.load_torrents(torrents_list);
std::cout << "Loaded " << torrents_list.size() << " torrents" << std::endl;
db.load_tokens(torrents_list);
std::vector<std::string> whitelist;
db->load_users(users_list);
db->load_torrents(torrents_list);
db->load_whitelist(whitelist);
stats.open_connections = 0;
stats.opened_connections = 0;
stats.connection_rate = 0;
stats.requests = 0;
stats.request_rate = 0;
stats.leechers = 0;
stats.seeders = 0;
stats.announcements = 0;
@@ -74,10 +105,28 @@ int main(int argc, char **argv) {
stats.start_time = time(NULL);
// Create worker object, which handles announces and scrapes and all that jazz
work = new worker(torrents_list, users_list, whitelist, &conf, &db, &sc);
work = new worker(conf, torrents_list, users_list, whitelist, db, sc);
// Create schedule object
sched = new schedule(conf, work, db, sc);
// Create connection mother, which binds to its socket and handles the event stuff
mother = new connection_mother(work, &conf, &db, &sc);
mother = new connection_mother(conf, work, db, sc, sched);
// Add signal handlers now that all objects have been created
struct sigaction handler, ignore;
ignore.sa_handler = SIG_IGN;
handler.sa_handler = sig_handler;
sigemptyset(&handler.sa_mask);
handler.sa_flags = 0;
sigaction(SIGINT, &handler, NULL);
sigaction(SIGTERM, &handler, NULL);
sigaction(SIGHUP, &handler, NULL);
sigaction(SIGUSR1, &handler, NULL);
sigaction(SIGUSR2, &ignore, NULL);
mother->run();
return 0;
}

View File

@@ -1,26 +1,29 @@
#ifndef OCELOT_H
#define OCELOT_H
#include <string>
#include <map>
#include <vector>
#include <unordered_map>
#include <set>
#include <memory>
#include <mutex>
#include <atomic>
#ifndef OCELOT_H
#define OCELOT_H
typedef uint32_t torid_t;
typedef uint32_t userid_t;
class user;
typedef std::shared_ptr<user> user_ptr;
typedef struct {
unsigned int port;
int64_t uploaded;
int64_t downloaded;
int64_t corrupt;
int64_t left;
time_t last_announced;
time_t first_announced;
unsigned int announces;
uint32_t announces;
uint16_t port;
bool visible;
bool invalid_ip;
user_ptr user;
@@ -33,15 +36,15 @@ typedef std::map<std::string, peer> peer_list;
enum freetype { NORMAL, FREE, NEUTRAL };
typedef struct {
int id;
torid_t id;
uint32_t completed;
int64_t balance;
int completed;
freetype free_torrent;
time_t last_flushed;
peer_list seeders;
peer_list leechers;
std::string last_selected_seeder;
std::set<int> tokened_users;
std::set<userid_t> tokened_users;
} torrent;
enum {
@@ -75,23 +78,30 @@ typedef struct {
time_t time;
} del_message;
typedef struct {
bool gzip;
bool html;
bool http_close;
} client_opts_t;
typedef std::unordered_map<std::string, torrent> torrent_list;
typedef std::unordered_map<std::string, user_ptr> user_list;
typedef std::unordered_map<std::string, std::string> params_type;
struct stats {
std::mutex mutex;
unsigned int open_connections;
uint64_t opened_connections;
uint64_t connection_rate;
unsigned int leechers;
unsigned int seeders;
uint64_t announcements;
uint64_t succ_announcements;
uint64_t scrapes;
uint64_t bytes_read;
uint64_t bytes_written;
struct stats_t {
std::atomic<uint32_t> open_connections;
std::atomic<uint64_t> opened_connections;
std::atomic<uint64_t> connection_rate;
std::atomic<uint32_t> leechers;
std::atomic<uint32_t> seeders;
std::atomic<uint64_t> requests;
std::atomic<uint64_t> request_rate;
std::atomic<uint64_t> announcements;
std::atomic<uint64_t> succ_announcements;
std::atomic<uint64_t> scrapes;
std::atomic<uint64_t> bytes_read;
std::atomic<uint64_t> bytes_written;
time_t start_time;
};
extern struct stats stats;
extern struct stats_t stats;
#endif

View File

@@ -7,7 +7,7 @@
#include "response.h"
#include "user.h"
std::string report(params_type &params, user_list &users_list) {
std::string report(params_type &params, user_list &users_list, client_opts_t &client_opts) {
std::stringstream output;
std::string action = params["get"];
if (action == "") {
@@ -20,14 +20,16 @@ std::string report(params_type &params, user_list &users_list) {
uptime -= up_h * 3600;
int up_m = uptime / 60;
int up_s = uptime - up_m * 60;
std::string up_ht = up_h <= 9 ? '0' + std::to_string(up_h) : std::to_string(up_h);
std::string up_mt = up_m <= 9 ? '0' + std::to_string(up_m) : std::to_string(up_m);
std::string up_st = up_s <= 9 ? '0' + std::to_string(up_s) : std::to_string(up_s);
std::string up_ht = up_h <= 9 ? '0' + inttostr(up_h) : inttostr(up_h);
std::string up_mt = up_m <= 9 ? '0' + inttostr(up_m) : inttostr(up_m);
std::string up_st = up_s <= 9 ? '0' + inttostr(up_s) : inttostr(up_s);
output << "Uptime: " << up_d << " days, " << up_ht << ':' << up_mt << ':' << up_st << '\n'
<< stats.opened_connections << " connections opened\n"
<< stats.open_connections << " open connections\n"
<< stats.connection_rate << " connections/s\n"
<< stats.requests << " requests handled\n"
<< stats.request_rate << " requests/s\n"
<< stats.succ_announcements << " successful announcements\n"
<< (stats.announcements - stats.succ_announcements) << " failed announcements\n"
<< stats.scrapes << " scrapes\n"
@@ -50,5 +52,5 @@ std::string report(params_type &params, user_list &users_list) {
output << "Invalid action\n";
}
output << "success";
return response(output.str(), false, false);
return response(output.str(), client_opts);
}

View File

@@ -1,4 +1,4 @@
#include <string>
#include "ocelot.h"
std::string report(params_type &params, user_list &users_list);
std::string report(params_type &params, user_list &users_list, client_opts_t &client_opts);

View File

@@ -1,19 +1,18 @@
#include "response.h"
#include "misc_functions.h"
#include <sstream>
#include <boost/iostreams/filtering_streambuf.hpp>
#include <boost/iostreams/copy.hpp>
#include <boost/iostreams/filter/gzip.hpp>
#include "response.h"
#include "misc_functions.h"
std::string response(const std::string &body, bool gzip, bool html) {
const std::string head = response_head(gzip, html);
const std::string response(const std::string &body, client_opts_t &client_opts) {
std::string out;
bool processed = false;
if (html) {
if (client_opts.html) {
out = "<html><head><meta name=\"robots\" content=\"noindex, nofollow\" /></head><body>" + body + "</body></html>";
processed = true;
}
if (gzip) {
if (client_opts.gzip) {
std::stringstream ss, zss;
ss << body;
boost::iostreams::filtering_streambuf<boost::iostreams::input> in;
@@ -24,26 +23,29 @@ std::string response(const std::string &body, bool gzip, bool html) {
processed = true;
}
if (processed) {
return head + out;
return response_head(out.length(), client_opts) + out;
}
return head + body;
return response_head(body.length(), client_opts) + body;
}
std::string response_head(bool gzip, bool html) {
const std::string content_type = html ? "text/html" : "text/plain";
const std::string response_head(size_t content_length, client_opts_t &client_opts) {
const std::string content_type = client_opts.html ? "text/html" : "text/plain";
std::string head = "HTTP/1.1 200 OK\r\nServer: Ocelot 1.0";
head += "\r\nContent-Type: " + content_type;
if (gzip) {
if (client_opts.gzip) {
head += "\r\nContent-Encoding: gzip";
}
head += "\r\nConnection: close\r\n\r\n";
if (client_opts.http_close) {
head += "\r\nConnection: Close";
}
head += "\r\nContent-Length: " + inttostr(content_length) + "\r\n\r\n";
return head;
}
std::string error(const std::string &err) {
return response("d14:failure reason" + inttostr(err.length()) + ':' + err + "12:min intervali5400e8:intervali5400ee", false, false);
const std::string error(const std::string &err, client_opts_t &client_opts) {
return response("d14:failure reason" + inttostr(err.length()) + ':' + err + "12:min intervali5400e8:intervali5400ee", client_opts);
}
std::string warning(const std::string &msg) {
const std::string warning(const std::string &msg) {
return "15:warning message" + inttostr(msg.length()) + ':' + msg;
}

View File

@@ -1,6 +1,11 @@
#include <string>
#ifndef RESPONSE_H
#define REPSONSE_H
std::string response(const std::string &body, bool gzip, bool html);
std::string response_head(bool gzip, bool html);
std::string error(const std::string &err);
std::string warning(const std::string &msg);
#include <string>
#include "ocelot.h"
const std::string response(const std::string &body, client_opts_t &client_opts);
const std::string response_head(size_t content_length, client_opts_t &client_opts);
const std::string error(const std::string &err, client_opts_t &client_opts);
const std::string warning(const std::string &msg);
#endif

View File

@@ -1,24 +1,37 @@
#include <iostream>
#include "ocelot.h"
#include "config.h"
#include "db.h"
#include "worker.h"
#include "events.h"
#include "schedule.h"
schedule::schedule(connection_mother * mother_obj, worker* worker_obj, config* conf_obj, mysql * db_obj, site_comm * sc_obj) : mother(mother_obj), work(worker_obj), conf(conf_obj), db(db_obj), sc(sc_obj) {
schedule::schedule(config * conf, worker * worker_obj, mysql * db_obj, site_comm * sc_obj) : work(worker_obj), db(db_obj), sc(sc_obj) {
load_config(conf);
counter = 0;
last_opened_connections = 0;
next_reap_peers = time(NULL) + conf->reap_peers_interval + 40;
last_request_count = 0;
next_reap_peers = reap_peers_interval;
}
void schedule::load_config(config * conf) {
reap_peers_interval = conf->get_uint("reap_peers_interval");
schedule_interval = conf->get_uint("schedule_interval");
}
void schedule::reload_config(config * conf) {
load_config(conf);
}
//---------- Schedule - gets called every schedule_interval seconds
void schedule::handle(ev::timer &watcher, int events_flags) {
stats.connection_rate = (stats.opened_connections - last_opened_connections) / conf->schedule_interval;
unsigned int cur_schedule_interval = watcher.repeat;
stats.connection_rate = (stats.opened_connections - last_opened_connections) / cur_schedule_interval;
stats.request_rate = (stats.requests - last_request_count) / cur_schedule_interval;
if (counter % 20 == 0) {
std::cout << "Schedule run #" << counter << " - open: " << stats.open_connections << ", opened: "
<< stats.opened_connections << ", speed: "
<< stats.connection_rate << "/s" << std::endl;
std::cout << stats.open_connections << " open, "
<< stats.opened_connections << " connections (" << stats.connection_rate << "/s), "
<< stats.requests << " requests (" << stats.request_rate << "/s)" << std::endl;
}
if (work->get_status() == CLOSING && db->all_clear() && sc->all_clear()) {
@@ -27,16 +40,19 @@ void schedule::handle(ev::timer &watcher, int events_flags) {
}
last_opened_connections = stats.opened_connections;
last_request_count = stats.requests;
db->flush();
sc->flush_tokens();
time_t cur_time = time(NULL);
if (cur_time > next_reap_peers) {
next_reap_peers -= cur_schedule_interval;
if (next_reap_peers <= 0) {
work->start_reaper();
next_reap_peers = cur_time + conf->reap_peers_interval;
next_reap_peers = reap_peers_interval;
}
counter++;
if (schedule_interval != cur_schedule_interval) {
watcher.set(schedule_interval, schedule_interval);
}
}

View File

@@ -1,20 +1,24 @@
#include <ev++.h>
#include <string>
#include <iostream>
#ifndef SCHEDULE_H
#define SCHEDULE_H
#include <ev++.h>
class schedule {
private:
connection_mother * mother;
void load_config(config * conf);
unsigned int reap_peers_interval;
worker * work;
config * conf;
mysql * db;
site_comm * sc;
uint64_t last_opened_connections;
int counter;
time_t next_flush;
time_t next_reap_peers;
uint64_t last_request_count;
unsigned int counter;
int next_reap_peers;
public:
schedule(connection_mother * mother_obj, worker * worker_obj, config* conf_obj, mysql * db_obj, site_comm * sc_obj);
schedule(config * conf, worker * worker_obj, mysql * db_obj, site_comm * sc_obj);
void reload_config(config * conf);
void handle(ev::timer &watcher, int events_flags);
unsigned int schedule_interval;
};
#endif

View File

@@ -13,18 +13,26 @@
using boost::asio::ip::tcp;
site_comm::site_comm(config &config)
{
conf = config;
t_active = false;
site_comm::site_comm(config * conf) : t_active(false) {
load_config(conf);
}
void site_comm::load_config(config * conf) {
site_host = conf->get_str("site_host");
site_path = conf->get_str("site_path");
site_password = conf->get_str("site_password");
readonly = conf->get_bool("readonly");
}
void site_comm::reload_config(config * conf) {
load_config(conf);
}
bool site_comm::all_clear() {
return (token_queue.size() == 0);
}
void site_comm::expire_token(int torrent, int user)
{
void site_comm::expire_token(int torrent, int user) {
std::stringstream token_pair;
token_pair << user << ':' << torrent;
if (expire_token_buffer != "") {
@@ -33,15 +41,21 @@ void site_comm::expire_token(int torrent, int user)
expire_token_buffer += token_pair.str();
if (expire_token_buffer.length() > 350) {
std::cout << "Flushing overloaded token buffer" << std::endl;
std::unique_lock<std::mutex> lock(expire_queue_lock);
token_queue.push(expire_token_buffer);
if (!readonly) {
std::lock_guard<std::mutex> lock(expire_queue_lock);
token_queue.push(expire_token_buffer);
}
expire_token_buffer.clear();
}
}
void site_comm::flush_tokens()
{
std::unique_lock<std::mutex> lock(expire_queue_lock);
if (readonly) {
expire_token_buffer.clear();
return;
}
std::lock_guard<std::mutex> lock(expire_queue_lock);
size_t qsize = token_queue.size();
if (verbose_flush || qsize > 0) {
std::cout << "Token expire queue size: " << qsize << std::endl;
@@ -65,7 +79,7 @@ void site_comm::do_flush_tokens()
boost::asio::io_service io_service;
tcp::resolver resolver(io_service);
tcp::resolver::query query(conf.site_host, "http");
tcp::resolver::query query(site_host, "http");
tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
tcp::resolver::iterator end;
@@ -81,11 +95,11 @@ void site_comm::do_flush_tokens()
boost::asio::streambuf request;
std::ostream request_stream(&request);
request_stream << "GET " << conf.site_path << "/tools.php?key=" << conf.site_password;
request_stream << "&type=expiretoken&action=ocelot&tokens=" << token_queue.front() << " HTTP/1.0\r\n";
request_stream << "Host: " << conf.site_host << "\r\n";
request_stream << "Accept: */*\r\n";
request_stream << "Connection: close\r\n\r\n";
request_stream << "GET " << site_path << "/tools.php?key=" << site_password
<< "&type=expiretoken&action=ocelot&tokens=" << token_queue.front() << " HTTP/1.0\r\n"
<< "Host: " << site_host << "\r\n"
<< "Accept: */*\r\n"
<< "Connection: close\r\n\r\n";
boost::asio::write(socket, request);
@@ -106,7 +120,7 @@ void site_comm::do_flush_tokens()
}
if (status_code == 200) {
std::unique_lock<std::mutex> lock(expire_queue_lock);
std::lock_guard<std::mutex> lock(expire_queue_lock);
token_queue.pop();
} else {
std::cout << "Response returned with status code " << status_code << " when trying to expire a token!" << std::endl;;

View File

@@ -1,8 +1,5 @@
#ifndef OCELOT_SITE_COMM_H
#define OCELOT_SITE_COMM_H
#include <iostream>
#include <istream>
#include <ostream>
#include <string>
#include <boost/asio.hpp>
#include <queue>
@@ -14,19 +11,24 @@ using boost::asio::ip::tcp;
class site_comm {
private:
config conf;
std::string site_host;
std::string site_path;
std::string site_password;
std::mutex expire_queue_lock;
std::string expire_token_buffer;
std::queue<std::string> token_queue;
bool readonly;
bool t_active;
void load_config(config * conf);
void do_flush_tokens();
public:
bool verbose_flush;
site_comm(config &conf);
site_comm(config * conf);
void reload_config(config * conf);
bool all_clear();
void expire_token(int torrent, int user);
void flush_tokens();
void do_flush_tokens();
~site_comm();
};
#endif

View File

@@ -1,51 +1,6 @@
#include "user.h"
user::user(int uid, bool leech, bool protect) : id(uid), leechstatus(leech), protect_ip(protect) {
user::user(userid_t uid, bool leech, bool protect) : id(uid), deleted(false), leechstatus(leech), protect_ip(protect) {
stats.leeching = 0;
stats.seeding = 0;
}
int user::get_id() {
return id;
}
bool user::is_protected() {
return protect_ip;
}
void user::set_protected(bool status) {
protect_ip = status;
}
bool user::can_leech() {
return leechstatus;
}
void user::set_leechstatus(bool status) {
leechstatus = status;
}
// Stats methods
unsigned int user::get_leeching() {
return stats.leeching;
}
unsigned int user::get_seeding() {
return stats.seeding;
}
void user::decr_leeching() {
stats.leeching--;
}
void user::decr_seeding() {
stats.seeding--;
}
void user::incr_leeching() {
stats.leeching++;
}
void user::incr_seeding() {
stats.seeding++;
}

40
user.h
View File

@@ -1,23 +1,33 @@
#ifndef USER_H
#define USER_H
#include <atomic>
#include "ocelot.h"
class user {
private:
int id;
userid_t id;
bool deleted;
bool leechstatus;
bool protect_ip;
struct {
unsigned int leeching;
unsigned int seeding;
std::atomic<uint32_t> leeching;
std::atomic<uint32_t> seeding;
} stats;
public:
user(int uid, bool leech, bool protect);
int get_id();
bool is_protected();
void set_protected(bool status);
bool can_leech();
void set_leechstatus(bool status);
void decr_leeching();
void decr_seeding();
void incr_leeching();
void incr_seeding();
unsigned int get_leeching();
unsigned int get_seeding();
user(userid_t uid, bool leech, bool protect);
userid_t get_id() { return id; }
bool is_deleted() { return deleted; }
void set_deleted(bool status) { deleted = status; }
bool is_protected() { return protect_ip; }
void set_protected(bool status) { protect_ip = status; }
bool can_leech() { return leechstatus; }
void set_leechstatus(bool status) { leechstatus = status; }
void decr_leeching() { --stats.leeching; }
void decr_seeding() { --stats.seeding; }
void incr_leeching() { ++stats.leeching; }
void incr_seeding() { ++stats.seeding; }
uint32_t get_leeching() { return stats.leeching; }
uint32_t get_seeding() { return stats.seeding; }
};
#endif

View File

@@ -1,7 +1,4 @@
#include <cmath>
#include <cstdlib>
#include <iostream>
#include <fstream>
#include <string>
#include <map>
#include <sstream>
@@ -12,9 +9,6 @@
#include <mutex>
#include <thread>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "ocelot.h"
#include "config.h"
#include "db.h"
@@ -26,11 +20,35 @@
#include "user.h"
//---------- Worker - does stuff with input
worker::worker(torrent_list &torrents, user_list &users, std::vector<std::string> &_whitelist, config * conf_obj, mysql * db_obj, site_comm * sc) : torrents_list(torrents), users_list(users), whitelist(_whitelist), conf(conf_obj), db(db_obj), s_comm(sc)
worker::worker(config * conf_obj, torrent_list &torrents, user_list &users, std::vector<std::string> &_whitelist, mysql * db_obj, site_comm * sc) :
conf(conf_obj), db(db_obj), s_comm(sc), torrents_list(torrents), users_list(users), whitelist(_whitelist), status(OPEN), reaper_active(false)
{
load_config(conf);
}
void worker::load_config(config * conf) {
announce_interval = conf->get_uint("announce_interval");
del_reason_lifetime = conf->get_uint("del_reason_lifetime");
peers_timeout = conf->get_uint("peers_timeout");
numwant_limit = conf->get_uint("numwant_limit");
keepalive_enabled = conf->get_uint("keepalive_timeout") != 0;
site_password = conf->get_str("site_password");
report_password = conf->get_str("report_password");
}
void worker::reload_config(config * conf) {
load_config(conf);
}
void worker::reload_lists() {
status = PAUSED;
db->load_torrents(torrents_list);
db->load_users(users_list);
db->load_whitelist(whitelist);
status = OPEN;
}
bool worker::signal(int sig) {
bool worker::shutdown() {
if (status == OPEN) {
status = CLOSING;
std::cout << "closing tracker... press Ctrl-C again to terminate" << std::endl;
@@ -42,12 +60,13 @@ bool worker::signal(int sig) {
return false;
}
}
std::string worker::work(std::string &input, std::string &ip) {
std::string worker::work(const std::string &input, std::string &ip, client_opts_t &client_opts) {
unsigned int input_length = input.length();
//---------- Parse request - ugly but fast. Using substr exploded.
if (input_length < 60) { // Way too short to be anything useful
return error("GET string too short");
return error("GET string too short", client_opts);
}
size_t pos = 5; // skip 'GET /'
@@ -56,7 +75,7 @@ std::string worker::work(std::string &input, std::string &ip) {
std::string passkey;
passkey.reserve(32);
if (input[37] != '/') {
return error("Malformed announce");
return error("Malformed announce", client_opts);
}
for (; pos < 37; pos++) {
@@ -71,7 +90,6 @@ std::string worker::work(std::string &input, std::string &ip) {
};
action_t action = INVALID;
std::unique_lock<std::mutex> lock(stats.mutex);
switch (input[pos]) {
case 'a':
stats.announcements++;
@@ -92,19 +110,11 @@ std::string worker::work(std::string &input, std::string &ip) {
pos += 6;
break;
}
lock.unlock();
if (input[pos] != '?') {
// No parameters given. Probably means we're not talking to a torrent client
return response("Nothing to see here", false, true);
}
if (status != OPEN && action != UPDATE) {
return error("The tracker is temporarily unavailable.");
}
if (action == INVALID) {
return error("Invalid action");
client_opts.html = true;
return response("Nothing to see here", client_opts);
}
// Parse URL params
@@ -115,7 +125,7 @@ std::string worker::work(std::string &input, std::string &ip) {
std::string value;
bool parsing_key = true; // true = key, false = value
pos++; // Skip the '?'
++pos; // Skip the '?'
for (; pos < input_length; ++pos) {
if (input[pos] == '=') {
parsing_key = false;
@@ -139,8 +149,19 @@ std::string worker::work(std::string &input, std::string &ip) {
}
}
}
++pos;
pos += 10; // skip 'HTTP/1.1' - should probably be +=11, but just in case a client doesn't send \r
if (input.compare(pos, 5, "HTTP/") != 0) {
return error("Malformed HTTP request", client_opts);
}
std::string http_version;
pos += 5;
while (input[pos] != '\r' && input[pos] != '\n') {
http_version.push_back(input[pos]);
++pos;
}
++pos; // skip line break - should probably be += 2, but just in case a client doesn't send \r
// Parse headers
params_type headers;
@@ -171,66 +192,88 @@ std::string worker::work(std::string &input, std::string &ip) {
}
}
if (action == UPDATE) {
if (passkey == conf->site_password) {
return update(params);
if (keepalive_enabled) {
auto hdr_http_close = headers.find("connection");
if (hdr_http_close == headers.end()) {
client_opts.http_close = (http_version == "1.0");
} else {
return error("Authentication failure");
client_opts.http_close = (hdr_http_close->second != "Keep-Alive");
}
} else {
client_opts.http_close = true;
}
if (status != OPEN) {
return error("The tracker is temporarily unavailable.", client_opts);
}
if (action == INVALID) {
return error("Invalid action", client_opts);
}
if (action == UPDATE) {
if (passkey == site_password) {
return update(params, client_opts);
} else {
return error("Authentication failure", client_opts);
}
}
if (action == REPORT) {
if (passkey == conf->report_password) {
return report(params, users_list);
if (passkey == report_password) {
std::lock_guard<std::mutex> ul_lock(db->user_list_mutex);
return report(params, users_list, client_opts);
} else {
return error("Authentication failure");
return error("Authentication failure", client_opts);
}
}
// Either a scrape or an announce
user_list::iterator u = users_list.find(passkey);
if (u == users_list.end()) {
return error("Passkey not found");
std::unique_lock<std::mutex> ul_lock(db->user_list_mutex);
auto user_it = users_list.find(passkey);
if (user_it == users_list.end()) {
return error("Passkey not found", client_opts);
}
user_ptr u = user_it->second;
ul_lock.unlock();
if (action == ANNOUNCE) {
std::unique_lock<std::mutex> tl_lock(db->torrent_list_mutex);
// Let's translate the infohash into something nice
// info_hash is a url encoded (hex) base 20 number
std::string info_hash_decoded = hex_decode(params["info_hash"]);
torrent_list::iterator tor = torrents_list.find(info_hash_decoded);
std::lock_guard<std::mutex> tl_lock(db->torrent_list_mutex);
auto tor = torrents_list.find(info_hash_decoded);
if (tor == torrents_list.end()) {
std::unique_lock<std::mutex> dr_lock(del_reasons_lock);
std::lock_guard<std::mutex> dr_lock(del_reasons_lock);
auto msg = del_reasons.find(info_hash_decoded);
if (msg != del_reasons.end()) {
if (msg->second.reason != -1) {
return error("Unregistered torrent: " + get_del_reason(msg->second.reason));
return error("Unregistered torrent: " + get_del_reason(msg->second.reason), client_opts);
} else {
return error("Unregistered torrent");
return error("Unregistered torrent", client_opts);
}
} else {
return error("Unregistered torrent");
return error("Unregistered torrent", client_opts);
}
}
return announce(tor->second, u->second, params, headers, ip);
return announce(input, tor->second, u, params, headers, ip, client_opts);
} else {
return scrape(infohashes, headers);
return scrape(infohashes, headers, client_opts);
}
}
std::string worker::announce(torrent &tor, user_ptr &u, params_type &params, params_type &headers, std::string &ip) {
std::string worker::announce(const std::string &input, torrent &tor, user_ptr &u, params_type &params, params_type &headers, std::string &ip, client_opts_t &client_opts) {
cur_time = time(NULL);
if (params["compact"] != "1") {
return error("Your client does not support compact announces");
return error("Your client does not support compact announces", client_opts);
}
bool gzip = false;
int64_t left = std::max((int64_t)0, strtolonglong(params["left"]));
int64_t uploaded = std::max((int64_t)0, strtolonglong(params["uploaded"]));
int64_t downloaded = std::max((int64_t)0, strtolonglong(params["downloaded"]));
int64_t corrupt = std::max((int64_t)0, strtolonglong(params["corrupt"]));
int64_t left = std::max((int64_t)0, strtoint64(params["left"]));
int64_t uploaded = std::max((int64_t)0, strtoint64(params["uploaded"]));
int64_t downloaded = std::max((int64_t)0, strtoint64(params["downloaded"]));
int64_t corrupt = std::max((int64_t)0, strtoint64(params["corrupt"]));
int snatched = 0; // This is the value that gets sent to the database on a snatch
int active = 1; // This is the value that marks a peer as active/inactive in the database
@@ -242,26 +285,37 @@ std::string worker::announce(torrent &tor, user_ptr &u, params_type &params, par
bool peer_changed = false; // Whether or not the peer is new or has changed since the last announcement
bool invalid_ip = false;
bool inc_l = false, inc_s = false, dec_l = false, dec_s = false;
userid_t userid = u->get_id();
params_type::const_iterator peer_id_iterator = params.find("peer_id");
if (peer_id_iterator == params.end()) {
return error("No peer ID");
return error("No peer ID", client_opts);
}
const std::string peer_id = hex_decode(peer_id_iterator->second);
if (peer_id.length() != 20) {
return error("Invalid peer ID", client_opts);
}
std::string peer_id = peer_id_iterator->second;
peer_id = hex_decode(peer_id);
std::unique_lock<std::mutex> wl_lock(db->whitelist_mutex);
if (whitelist.size() > 0) {
bool found = false; // Found client in whitelist?
for (unsigned int i = 0; i < whitelist.size(); i++) {
if (peer_id.find(whitelist[i]) == 0) {
if (peer_id.compare(0, whitelist[i].length(), whitelist[i]) == 0) {
found = true;
break;
}
}
if (!found) {
return error("Your client is not on the whitelist");
return error("Your client is not on the whitelist", client_opts);
}
}
wl_lock.unlock();
std::stringstream peer_key_stream;
peer_key_stream << peer_id[12 + (tor.id & 7)] // "Randomize" the element order in the peer map by prefixing with a peer id byte
<< userid // Include user id in the key to lower chance of peer id collisions
<< peer_id;
const std::string peer_key(peer_key_stream.str());
if (params["event"] == "completed") {
// Don't update <snatched> here as we may decide to use other conditions later on
@@ -272,45 +326,44 @@ std::string worker::announce(torrent &tor, user_ptr &u, params_type &params, par
update_torrent = true;
active = 0;
}
int userid = u->get_id();
peer * p;
peer_list::iterator peer_it;
// Insert/find the peer in the torrent list
if (left > 0) {
peer_it = tor.leechers.find(peer_id);
peer_it = tor.leechers.find(peer_key);
if (peer_it == tor.leechers.end()) {
// We could search the seed list as well, but the peer reaper will sort things out eventually
peer_it = add_peer(tor.leechers, peer_id);
peer_it = add_peer(tor.leechers, peer_key);
inserted = true;
inc_l = true;
}
} else if (completed_torrent) {
peer_it = tor.leechers.find(peer_id);
peer_it = tor.leechers.find(peer_key);
if (peer_it == tor.leechers.end()) {
peer_it = tor.seeders.find(peer_id);
peer_it = tor.seeders.find(peer_key);
if (peer_it == tor.seeders.end()) {
peer_it = add_peer(tor.seeders, peer_id);
peer_it = add_peer(tor.seeders, peer_key);
inserted = true;
inc_s = true;
} else {
completed_torrent = false;
}
} else if (tor.seeders.find(peer_id) != tor.seeders.end()) {
} else if (tor.seeders.find(peer_key) != tor.seeders.end()) {
// If the peer exists in both peer lists, just decrement the seed count.
// Should be cheaper than searching the seed list in the left > 0 case
dec_s = true;
}
} else {
peer_it = tor.seeders.find(peer_id);
peer_it = tor.seeders.find(peer_key);
if (peer_it == tor.seeders.end()) {
peer_it = tor.leechers.find(peer_id);
peer_it = tor.leechers.find(peer_key);
if (peer_it == tor.leechers.end()) {
peer_it = add_peer(tor.seeders, peer_id);
peer_it = add_peer(tor.seeders, peer_key);
inserted = true;
} else {
p = &peer_it->second;
std::pair<peer_list::iterator, bool> insert
= tor.seeders.insert(std::pair<std::string, peer>(peer_id, *p));
= tor.seeders.insert(std::pair<std::string, peer>(peer_key, *p));
tor.leechers.erase(peer_it);
peer_it = insert.first;
peer_changed = true;
@@ -373,7 +426,7 @@ std::string worker::announce(torrent &tor, user_ptr &u, params_type &params, par
upspeed = uploaded_change / (cur_time - p->last_announced);
downspeed = downloaded_change / (cur_time - p->last_announced);
}
std::set<int>::iterator sit = tor.tokened_users.find(userid);
auto sit = tor.tokened_users.find(userid);
if (tor.free_torrent == NEUTRAL) {
downloaded_change = 0;
uploaded_change = 0;
@@ -415,7 +468,7 @@ std::string worker::announce(torrent &tor, user_ptr &u, params_type &params, par
}
}
unsigned int port = strtolong(params["port"]);
uint16_t port = strtoint32(params["port"]) & 0xFFFF;
// Generate compact ip/port string
if (inserted || port != p->port || ip != p->ip) {
p->port = port;
@@ -464,18 +517,18 @@ std::string worker::announce(torrent &tor, user_ptr &u, params_type &params, par
}
db->record_peer(record_str, record_ip, peer_id, headers["user-agent"]);
} else {
record << '(' << tor.id << ',' << (cur_time - p->first_announced) << ',' << p->announces << ',';
record << '(' << userid << ',' << tor.id << ',' << (cur_time - p->first_announced) << ',' << p->announces << ',';
std::string record_str = record.str();
db->record_peer(record_str, peer_id);
}
// Select peers!
unsigned int numwant;
uint32_t numwant;
params_type::const_iterator param_numwant = params.find("numwant");
if (param_numwant == params.end()) {
numwant = 50;
numwant = numwant_limit;
} else {
numwant = std::min(50l, strtolong(param_numwant->second));
numwant = std::min((int32_t)numwant_limit, strtoint32(param_numwant->second));
}
if (stopped_torrent) {
@@ -504,7 +557,7 @@ std::string worker::announce(torrent &tor, user_ptr &u, params_type &params, par
// User is a seeder now!
if (!inserted) {
std::pair<peer_list::iterator, bool> insert
= tor.seeders.insert(std::pair<std::string, peer>(peer_id, *p));
= tor.seeders.insert(std::pair<std::string, peer>(peer_key, *p));
tor.leechers.erase(peer_it);
peer_it = insert.first;
p = &peer_it->second;
@@ -555,7 +608,7 @@ std::string worker::announce(torrent &tor, user_ptr &u, params_type &params, par
i = tor.seeders.begin();
}
// Don't show users themselves
if (i->second.user->get_id() == userid || !i->second.visible) {
if (i->second.user->is_deleted() || i->second.user->get_id() == userid || !i->second.visible) {
++i;
continue;
}
@@ -569,7 +622,7 @@ std::string worker::announce(torrent &tor, user_ptr &u, params_type &params, par
if (found_peers < numwant && tor.leechers.size() > 1) {
for (peer_list::const_iterator i = tor.leechers.begin(); i != tor.leechers.end() && found_peers < numwant; ++i) {
// Don't show users themselves or leech disabled users
if (i->second.ip_port == p->ip_port || i->second.user->get_id() == userid || !i->second.visible) {
if (i->second.user->is_deleted() || i->second.ip_port == p->ip_port || i->second.user->get_id() == userid || !i->second.visible) {
continue;
}
found_peers++;
@@ -590,10 +643,8 @@ std::string worker::announce(torrent &tor, user_ptr &u, params_type &params, par
}
// Update the stats
std::unique_lock<std::mutex> lock(stats.mutex);
stats.succ_announcements++;
if (dec_l || dec_s || inc_l || inc_s) {
std::unique_lock<std::mutex> us_lock(ustats_lock);
if (inc_l) {
p->user->incr_leeching();
stats.leechers++;
@@ -611,12 +662,10 @@ std::string worker::announce(torrent &tor, user_ptr &u, params_type &params, par
stats.seeders--;
}
}
lock.unlock();
// Correct the stats for the old user if the peer's user link has changed
if (p->user != u) {
if (!stopped_torrent) {
std::unique_lock<std::mutex> us_lock(ustats_lock);
if (left > 0) {
u->incr_leeching();
p->user->decr_leeching();
@@ -648,7 +697,7 @@ std::string worker::announce(torrent &tor, user_ptr &u, params_type &params, par
}
if (!u->can_leech() && left > 0) {
return error("Access denied, leeching forbidden");
return error("Access denied, leeching forbidden", client_opts);
}
std::string output = "d8:completei";
@@ -659,9 +708,9 @@ std::string worker::announce(torrent &tor, user_ptr &u, params_type &params, par
output += "e10:incompletei";
output += inttostr(tor.leechers.size());
output += "e8:intervali";
output += inttostr(conf->announce_interval+std::min((size_t)600, tor.seeders.size())); // ensure a more even distribution of announces/second
output += inttostr(announce_interval + std::min((size_t)600, tor.seeders.size())); // ensure a more even distribution of announces/second
output += "e12:min intervali";
output += inttostr(conf->announce_interval);
output += inttostr(announce_interval);
output += "e5:peers";
if (peers.length() == 0) {
output += "0:";
@@ -680,13 +729,12 @@ std::string worker::announce(torrent &tor, user_ptr &u, params_type &params, par
* possibly inflated return size
*/
/*if (headers["accept-encoding"].find("gzip") != std::string::npos) {
gzip = true;
client_opts.gzip = true;
}*/
return response(output, gzip, false);
return response(output, client_opts);
}
std::string worker::scrape(const std::list<std::string> &infohashes, params_type &headers) {
bool gzip = false;
std::string worker::scrape(const std::list<std::string> &infohashes, params_type &headers, client_opts_t &client_opts) {
std::string output = "d5:filesd";
for (std::list<std::string>::const_iterator i = infohashes.begin(); i != infohashes.end(); ++i) {
std::string infohash = *i;
@@ -711,16 +759,17 @@ std::string worker::scrape(const std::list<std::string> &infohashes, params_type
}
output += "ee";
if (headers["accept-encoding"].find("gzip") != std::string::npos) {
gzip = true;
client_opts.gzip = true;
}
return response(output, gzip, false);
return response(output, client_opts);
}
//TODO: Restrict to local IPs
std::string worker::update(params_type &params) {
std::string worker::update(params_type &params, client_opts_t &client_opts) {
if (params["action"] == "change_passkey") {
std::string oldpasskey = params["oldpasskey"];
std::string newpasskey = params["newpasskey"];
std::lock_guard<std::mutex> ul_lock(db->user_list_mutex);
auto u = users_list.find(oldpasskey);
if (u == users_list.end()) {
std::cout << "No user with passkey " << oldpasskey << " exists when attempting to change passkey to " << newpasskey << std::endl;
@@ -733,10 +782,11 @@ std::string worker::update(params_type &params) {
torrent *t;
std::string info_hash = params["info_hash"];
info_hash = hex_decode(info_hash);
std::lock_guard<std::mutex> tl_lock(db->torrent_list_mutex);
auto i = torrents_list.find(info_hash);
if (i == torrents_list.end()) {
t = &torrents_list[info_hash];
t->id = strtolong(params["id"]);
t->id = strtoint32(params["id"]);
t->balance = 0;
t->completed = 0;
t->last_selected_seeder = "";
@@ -762,6 +812,7 @@ std::string worker::update(params_type &params) {
} else {
fl = NEUTRAL;
}
std::lock_guard<std::mutex> tl_lock(db->torrent_list_mutex);
auto torrent_it = torrents_list.find(info_hash);
if (torrent_it != torrents_list.end()) {
torrent_it->second.free_torrent = fl;
@@ -781,6 +832,7 @@ std::string worker::update(params_type &params) {
} else {
fl = NEUTRAL;
}
std::lock_guard<std::mutex> tl_lock(db->torrent_list_mutex);
for (unsigned int pos = 0; pos < info_hashes.length(); pos += 20) {
std::string info_hash = info_hashes.substr(pos, 20);
auto torrent_it = torrents_list.find(info_hash);
@@ -794,6 +846,7 @@ std::string worker::update(params_type &params) {
} else if (params["action"] == "add_token") {
std::string info_hash = hex_decode(params["info_hash"]);
int userid = atoi(params["userid"].c_str());
std::lock_guard<std::mutex> tl_lock(db->torrent_list_mutex);
auto torrent_it = torrents_list.find(info_hash);
if (torrent_it != torrents_list.end()) {
torrent_it->second.tokened_users.insert(userid);
@@ -803,6 +856,7 @@ std::string worker::update(params_type &params) {
} else if (params["action"] == "remove_token") {
std::string info_hash = hex_decode(params["info_hash"]);
int userid = atoi(params["userid"].c_str());
std::lock_guard<std::mutex> tl_lock(db->torrent_list_mutex);
auto torrent_it = torrents_list.find(info_hash);
if (torrent_it != torrents_list.end()) {
torrent_it->second.tokened_users.erase(userid);
@@ -817,22 +871,19 @@ std::string worker::update(params_type &params) {
if (reason_it != params.end()) {
reason = atoi(params["reason"].c_str());
}
std::lock_guard<std::mutex> tl_lock(db->torrent_list_mutex);
auto torrent_it = torrents_list.find(info_hash);
if (torrent_it != torrents_list.end()) {
std::cout << "Deleting torrent " << torrent_it->second.id << " for the reason '" << get_del_reason(reason) << "'" << std::endl;
std::unique_lock<std::mutex> stats_lock(stats.mutex);
stats.leechers -= torrent_it->second.leechers.size();
stats.seeders -= torrent_it->second.seeders.size();
stats_lock.unlock();
std::unique_lock<std::mutex> us_lock(ustats_lock);
for (auto p = torrent_it->second.leechers.begin(); p != torrent_it->second.leechers.end(); ++p) {
p->second.user->decr_leeching();
for (auto &p: torrent_it->second.leechers) {
p.second.user->decr_leeching();
}
for (auto p = torrent_it->second.seeders.begin(); p != torrent_it->second.seeders.end(); ++p) {
p->second.user->decr_seeding();
for (auto &p: torrent_it->second.seeders) {
p.second.user->decr_seeding();
}
us_lock.unlock();
std::unique_lock<std::mutex> dr_lock(del_reasons_lock);
std::lock_guard<std::mutex> dr_lock(del_reasons_lock);
del_message msg;
msg.reason = reason;
msg.time = time(NULL);
@@ -843,31 +894,37 @@ std::string worker::update(params_type &params) {
}
} else if (params["action"] == "add_user") {
std::string passkey = params["passkey"];
unsigned int userid = strtolong(params["id"]);
userid_t userid = strtoint32(params["id"]);
std::lock_guard<std::mutex> ul_lock(db->user_list_mutex);
auto u = users_list.find(passkey);
if (u == users_list.end()) {
bool protect_ip = params["visible"] == "0";
user_ptr u(new user(userid, true, protect_ip));
users_list.insert(std::pair<std::string, user_ptr>(passkey, u));
user_ptr tmp_user = std::make_shared<user>(userid, true, protect_ip);
users_list.insert(std::pair<std::string, user_ptr>(passkey, tmp_user));
std::cout << "Added user " << passkey << " with id " << userid << std::endl;
} else {
std::cout << "Tried to add already known user " << passkey << " with id " << userid << std::endl;
u->second->set_deleted(false);
}
} else if (params["action"] == "remove_user") {
std::string passkey = params["passkey"];
std::lock_guard<std::mutex> ul_lock(db->user_list_mutex);
auto u = users_list.find(passkey);
if (u != users_list.end()) {
std::cout << "Removed user " << passkey << " with id " << u->second->get_id() << std::endl;
u->second->set_deleted(true);
users_list.erase(u);
}
} else if (params["action"] == "remove_users") {
// Each passkey is exactly 32 characters long.
std::string passkeys = params["passkeys"];
std::lock_guard<std::mutex> ul_lock(db->user_list_mutex);
for (unsigned int pos = 0; pos < passkeys.length(); pos += 32) {
std::string passkey = passkeys.substr(pos, 32);
auto u = users_list.find(passkey);
if (u != users_list.end()) {
std::cout << "Removed user " << passkey << std::endl;
u->second->set_deleted(true);
users_list.erase(passkey);
}
}
@@ -881,6 +938,7 @@ std::string worker::update(params_type &params) {
if (params["visible"] == "0") {
protect_ip = true;
}
std::lock_guard<std::mutex> ul_lock(db->user_list_mutex);
user_list::iterator i = users_list.find(passkey);
if (i == users_list.end()) {
std::cout << "No user with passkey " << passkey << " found when attempting to change leeching status!" << std::endl;
@@ -891,10 +949,12 @@ std::string worker::update(params_type &params) {
}
} else if (params["action"] == "add_whitelist") {
std::string peer_id = params["peer_id"];
std::lock_guard<std::mutex> wl_lock(db->whitelist_mutex);
whitelist.push_back(peer_id);
std::cout << "Whitelisted " << peer_id << std::endl;
} else if (params["action"] == "remove_whitelist") {
std::string peer_id = params["peer_id"];
std::lock_guard<std::mutex> wl_lock(db->whitelist_mutex);
for (unsigned int i = 0; i < whitelist.size(); i++) {
if (whitelist[i].compare(peer_id) == 0) {
whitelist.erase(whitelist.begin() + i);
@@ -905,6 +965,7 @@ std::string worker::update(params_type &params) {
} else if (params["action"] == "edit_whitelist") {
std::string new_peer_id = params["new_peer_id"];
std::string old_peer_id = params["old_peer_id"];
std::lock_guard<std::mutex> wl_lock(db->whitelist_mutex);
for (unsigned int i = 0; i < whitelist.size(); i++) {
if (whitelist[i].compare(old_peer_id) == 0) {
whitelist.erase(whitelist.begin() + i);
@@ -914,13 +975,15 @@ std::string worker::update(params_type &params) {
whitelist.push_back(new_peer_id);
std::cout << "Edited whitelist item from " << old_peer_id << " to " << new_peer_id << std::endl;
} else if (params["action"] == "update_announce_interval") {
unsigned int interval = strtolong(params["new_announce_interval"]);
conf->announce_interval = interval;
std::cout << "Edited announce interval to " << interval << std::endl;
const std::string interval = params["new_announce_interval"];
conf->set("announce_interval", interval);
announce_interval = conf->get_uint("announce_interval");
std::cout << "Edited announce interval to " << announce_interval << std::endl;
} else if (params["action"] == "info_torrent") {
std::string info_hash_hex = params["info_hash"];
std::string info_hash = hex_decode(info_hash_hex);
std::cout << "Info for torrent '" << info_hash_hex << "'" << std::endl;
std::lock_guard<std::mutex> tl_lock(db->torrent_list_mutex);
auto torrent_it = torrents_list.find(info_hash);
if (torrent_it != torrents_list.end()) {
std::cout << "Torrent " << torrent_it->second.id
@@ -929,24 +992,27 @@ std::string worker::update(params_type &params) {
std::cout << "Failed to find torrent " << info_hash_hex << std::endl;
}
}
return response("success", false, false);
return response("success", client_opts);
}
peer_list::iterator worker::add_peer(peer_list &peer_list, std::string &peer_id) {
peer_list::iterator worker::add_peer(peer_list &peer_list, const std::string &peer_key) {
peer new_peer;
std::pair<peer_list::iterator, bool> insert
= peer_list.insert(std::pair<std::string, peer>(peer_id, new_peer));
return insert.first;
auto it = peer_list.insert(std::pair<std::string, peer>(peer_key, new_peer));
return it.first;
}
void worker::start_reaper() {
std::thread thread(&worker::do_start_reaper, this);
thread.detach();
if (!reaper_active) {
std::thread thread(&worker::do_start_reaper, this);
thread.detach();
}
}
void worker::do_start_reaper() {
reaper_active = true;
reap_peers();
reap_del_reasons();
reaper_active = false;
}
void worker::reap_peers() {
@@ -959,12 +1025,10 @@ void worker::reap_peers() {
auto p = t->second.leechers.begin();
peer_list::iterator del_p;
while (p != t->second.leechers.end()) {
if (p->second.last_announced + conf->peers_timeout < cur_time) {
if (p->second.last_announced + peers_timeout < cur_time) {
std::lock_guard<std::mutex> tl_lock(db->torrent_list_mutex);
del_p = p++;
std::unique_lock<std::mutex> us_lock(ustats_lock);
del_p->second.user->decr_leeching();
us_lock.unlock();
std::unique_lock<std::mutex> tl_lock(db->torrent_list_mutex);
t->second.leechers.erase(del_p);
reaped_this = true;
reaped_l++;
@@ -974,12 +1038,10 @@ void worker::reap_peers() {
}
p = t->second.seeders.begin();
while (p != t->second.seeders.end()) {
if (p->second.last_announced + conf->peers_timeout < cur_time) {
if (p->second.last_announced + peers_timeout < cur_time) {
std::lock_guard<std::mutex> tl_lock(db->torrent_list_mutex);
del_p = p++;
std::unique_lock<std::mutex> us_lock(ustats_lock);
del_p->second.user->decr_seeding();
us_lock.unlock();
std::unique_lock<std::mutex> tl_lock(db->torrent_list_mutex);
t->second.seeders.erase(del_p);
reaped_this = true;
reaped_s++;
@@ -996,7 +1058,6 @@ void worker::reap_peers() {
}
}
if (reaped_l || reaped_s) {
std::unique_lock<std::mutex> lock(stats.mutex);
stats.leechers -= reaped_l;
stats.seeders -= reaped_s;
}
@@ -1006,13 +1067,13 @@ void worker::reap_peers() {
void worker::reap_del_reasons()
{
std::cout << "Starting del reason reaper" << std::endl;
time_t max_time = time(NULL) - conf->del_reason_lifetime;
time_t max_time = time(NULL) - del_reason_lifetime;
auto it = del_reasons.begin();
unsigned int reaped = 0;
for (; it != del_reasons.end(); ) {
if (it->second.time <= max_time) {
auto del_it = it++;
std::unique_lock<std::mutex> dr_lock(del_reasons_lock);
std::lock_guard<std::mutex> dr_lock(del_reasons_lock);
del_reasons.erase(del_it);
reaped++;
continue;

View File

@@ -1,12 +1,13 @@
#ifndef WORKER_H
#define WORKER_H
#include <string>
#include <vector>
#include <list>
#include <unordered_map>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <iostream>
#include <fstream>
#include <mutex>
#include <ctime>
#include "site_comm.h"
#include "ocelot.h"
@@ -14,35 +15,47 @@ enum tracker_status { OPEN, PAUSED, CLOSING }; // tracker status
class worker {
private:
torrent_list torrents_list;
user_list users_list;
std::vector<std::string> whitelist;
std::unordered_map<std::string, del_message> del_reasons;
config * conf;
mysql * db;
tracker_status status;
time_t cur_time;
site_comm * s_comm;
torrent_list &torrents_list;
user_list &users_list;
std::vector<std::string> &whitelist;
std::unordered_map<std::string, del_message> del_reasons;
tracker_status status;
bool reaper_active;
time_t cur_time;
unsigned int announce_interval;
unsigned int del_reason_lifetime;
unsigned int peers_timeout;
unsigned int numwant_limit;
bool keepalive_enabled;
std::string site_password;
std::string report_password;
std::mutex del_reasons_lock;
std::mutex ustats_lock;
void load_config(config * conf);
void do_start_reaper();
void reap_peers();
void reap_del_reasons();
std::string get_del_reason(int code);
peer_list::iterator add_peer(peer_list &peer_list, std::string &peer_id);
bool peer_is_visible(user_ptr &u, peer *p);
peer_list::iterator add_peer(peer_list &peer_list, const std::string &peer_id);
inline bool peer_is_visible(user_ptr &u, peer *p);
public:
worker(torrent_list &torrents, user_list &users, std::vector<std::string> &_whitelist, config * conf_obj, mysql * db_obj, site_comm * sc);
std::string work(std::string &input, std::string &ip);
std::string announce(torrent &tor, user_ptr &u, params_type &params, params_type &headers, std::string &ip);
std::string scrape(const std::list<std::string> &infohashes, params_type &headers);
std::string update(params_type &params);
worker(config * conf_obj, torrent_list &torrents, user_list &users, std::vector<std::string> &_whitelist, mysql * db_obj, site_comm * sc);
void reload_config(config * conf);
std::string work(const std::string &input, std::string &ip, client_opts_t &client_opts);
std::string announce(const std::string &input, torrent &tor, user_ptr &u, params_type &params, params_type &headers, std::string &ip, client_opts_t &client_opts);
std::string scrape(const std::list<std::string> &infohashes, params_type &headers, client_opts_t &client_opts);
std::string update(params_type &params, client_opts_t &client_opts);
bool signal(int sig);
void reload_lists();
bool shutdown();
tracker_status get_status() { return status; }
void start_reaper();
};
#endif