ncdc-1.23.1/0000755000175000017500000000000014314550104007503 500000000000000ncdc-1.23.1/install-sh0000755000175000017500000003577614314535761011465 00000000000000#!/bin/sh # install - install a program, script, or datafile scriptversion=2020-11-14.01; # UTC # This originates from X11R5 (mit/util/scripts/install.sh), which was # later released in X11R6 (xc/config/util/install.sh) with the # following copyright and license. # # Copyright (C) 1994 X Consortium # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to # deal in the Software without restriction, including without limitation the # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or # sell copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN # AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC- # TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # # Except as contained in this notice, the name of the X Consortium shall not # be used in advertising or otherwise to promote the sale, use or other deal- # ings in this Software without prior written authorization from the X Consor- # tium. # # # FSF changes to this file are in the public domain. # # Calling this script install-sh is preferred over install.sh, to prevent # 'make' implicit rules from creating a file called install from it # when there is no Makefile. # # This script is compatible with the BSD install script, but was written # from scratch. tab=' ' nl=' ' IFS=" $tab$nl" # Set DOITPROG to "echo" to test this script. doit=${DOITPROG-} doit_exec=${doit:-exec} # Put in absolute file names if you don't have them in your path; # or use environment vars. chgrpprog=${CHGRPPROG-chgrp} chmodprog=${CHMODPROG-chmod} chownprog=${CHOWNPROG-chown} cmpprog=${CMPPROG-cmp} cpprog=${CPPROG-cp} mkdirprog=${MKDIRPROG-mkdir} mvprog=${MVPROG-mv} rmprog=${RMPROG-rm} stripprog=${STRIPPROG-strip} posix_mkdir= # Desired mode of installed file. mode=0755 # Create dirs (including intermediate dirs) using mode 755. # This is like GNU 'install' as of coreutils 8.32 (2020). mkdir_umask=22 backupsuffix= chgrpcmd= chmodcmd=$chmodprog chowncmd= mvcmd=$mvprog rmcmd="$rmprog -f" stripcmd= src= dst= dir_arg= dst_arg= copy_on_change=false is_target_a_directory=possibly usage="\ Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE or: $0 [OPTION]... SRCFILES... DIRECTORY or: $0 [OPTION]... -t DIRECTORY SRCFILES... or: $0 [OPTION]... -d DIRECTORIES... In the 1st form, copy SRCFILE to DSTFILE. In the 2nd and 3rd, copy all SRCFILES to DIRECTORY. In the 4th, create DIRECTORIES. Options: --help display this help and exit. --version display version info and exit. -c (ignored) -C install only if different (preserve data modification time) -d create directories instead of installing files. -g GROUP $chgrpprog installed files to GROUP. -m MODE $chmodprog installed files to MODE. -o USER $chownprog installed files to USER. -p pass -p to $cpprog. -s $stripprog installed files. -S SUFFIX attempt to back up existing files, with suffix SUFFIX. -t DIRECTORY install into DIRECTORY. -T report an error if DSTFILE is a directory. Environment variables override the default commands: CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG RMPROG STRIPPROG By default, rm is invoked with -f; when overridden with RMPROG, it's up to you to specify -f if you want it. If -S is not specified, no backups are attempted. Email bug reports to bug-automake@gnu.org. Automake home page: https://www.gnu.org/software/automake/ " while test $# -ne 0; do case $1 in -c) ;; -C) copy_on_change=true;; -d) dir_arg=true;; -g) chgrpcmd="$chgrpprog $2" shift;; --help) echo "$usage"; exit $?;; -m) mode=$2 case $mode in *' '* | *"$tab"* | *"$nl"* | *'*'* | *'?'* | *'['*) echo "$0: invalid mode: $mode" >&2 exit 1;; esac shift;; -o) chowncmd="$chownprog $2" shift;; -p) cpprog="$cpprog -p";; -s) stripcmd=$stripprog;; -S) backupsuffix="$2" shift;; -t) is_target_a_directory=always dst_arg=$2 # Protect names problematic for 'test' and other utilities. case $dst_arg in -* | [=\(\)!]) dst_arg=./$dst_arg;; esac shift;; -T) is_target_a_directory=never;; --version) echo "$0 $scriptversion"; exit $?;; --) shift break;; -*) echo "$0: invalid option: $1" >&2 exit 1;; *) break;; esac shift done # We allow the use of options -d and -T together, by making -d # take the precedence; this is for compatibility with GNU install. if test -n "$dir_arg"; then if test -n "$dst_arg"; then echo "$0: target directory not allowed when installing a directory." >&2 exit 1 fi fi if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then # When -d is used, all remaining arguments are directories to create. # When -t is used, the destination is already specified. # Otherwise, the last argument is the destination. Remove it from $@. for arg do if test -n "$dst_arg"; then # $@ is not empty: it contains at least $arg. set fnord "$@" "$dst_arg" shift # fnord fi shift # arg dst_arg=$arg # Protect names problematic for 'test' and other utilities. case $dst_arg in -* | [=\(\)!]) dst_arg=./$dst_arg;; esac done fi if test $# -eq 0; then if test -z "$dir_arg"; then echo "$0: no input file specified." >&2 exit 1 fi # It's OK to call 'install-sh -d' without argument. # This can happen when creating conditional directories. exit 0 fi if test -z "$dir_arg"; then if test $# -gt 1 || test "$is_target_a_directory" = always; then if test ! -d "$dst_arg"; then echo "$0: $dst_arg: Is not a directory." >&2 exit 1 fi fi fi if test -z "$dir_arg"; then do_exit='(exit $ret); exit $ret' trap "ret=129; $do_exit" 1 trap "ret=130; $do_exit" 2 trap "ret=141; $do_exit" 13 trap "ret=143; $do_exit" 15 # Set umask so as not to create temps with too-generous modes. # However, 'strip' requires both read and write access to temps. case $mode in # Optimize common cases. *644) cp_umask=133;; *755) cp_umask=22;; *[0-7]) if test -z "$stripcmd"; then u_plus_rw= else u_plus_rw='% 200' fi cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;; *) if test -z "$stripcmd"; then u_plus_rw= else u_plus_rw=,u+rw fi cp_umask=$mode$u_plus_rw;; esac fi for src do # Protect names problematic for 'test' and other utilities. case $src in -* | [=\(\)!]) src=./$src;; esac if test -n "$dir_arg"; then dst=$src dstdir=$dst test -d "$dstdir" dstdir_status=$? # Don't chown directories that already exist. if test $dstdir_status = 0; then chowncmd="" fi else # Waiting for this to be detected by the "$cpprog $src $dsttmp" command # might cause directories to be created, which would be especially bad # if $src (and thus $dsttmp) contains '*'. if test ! -f "$src" && test ! -d "$src"; then echo "$0: $src does not exist." >&2 exit 1 fi if test -z "$dst_arg"; then echo "$0: no destination specified." >&2 exit 1 fi dst=$dst_arg # If destination is a directory, append the input filename. if test -d "$dst"; then if test "$is_target_a_directory" = never; then echo "$0: $dst_arg: Is a directory" >&2 exit 1 fi dstdir=$dst dstbase=`basename "$src"` case $dst in */) dst=$dst$dstbase;; *) dst=$dst/$dstbase;; esac dstdir_status=0 else dstdir=`dirname "$dst"` test -d "$dstdir" dstdir_status=$? fi fi case $dstdir in */) dstdirslash=$dstdir;; *) dstdirslash=$dstdir/;; esac obsolete_mkdir_used=false if test $dstdir_status != 0; then case $posix_mkdir in '') # With -d, create the new directory with the user-specified mode. # Otherwise, rely on $mkdir_umask. if test -n "$dir_arg"; then mkdir_mode=-m$mode else mkdir_mode= fi posix_mkdir=false # The $RANDOM variable is not portable (e.g., dash). Use it # here however when possible just to lower collision chance. tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$ trap ' ret=$? rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" 2>/dev/null exit $ret ' 0 # Because "mkdir -p" follows existing symlinks and we likely work # directly in world-writeable /tmp, make sure that the '$tmpdir' # directory is successfully created first before we actually test # 'mkdir -p'. if (umask $mkdir_umask && $mkdirprog $mkdir_mode "$tmpdir" && exec $mkdirprog $mkdir_mode -p -- "$tmpdir/a/b") >/dev/null 2>&1 then if test -z "$dir_arg" || { # Check for POSIX incompatibilities with -m. # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or # other-writable bit of parent directory when it shouldn't. # FreeBSD 6.1 mkdir -m -p sets mode of existing directory. test_tmpdir="$tmpdir/a" ls_ld_tmpdir=`ls -ld "$test_tmpdir"` case $ls_ld_tmpdir in d????-?r-*) different_mode=700;; d????-?--*) different_mode=755;; *) false;; esac && $mkdirprog -m$different_mode -p -- "$test_tmpdir" && { ls_ld_tmpdir_1=`ls -ld "$test_tmpdir"` test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1" } } then posix_mkdir=: fi rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" else # Remove any dirs left behind by ancient mkdir implementations. rmdir ./$mkdir_mode ./-p ./-- "$tmpdir" 2>/dev/null fi trap '' 0;; esac if $posix_mkdir && ( umask $mkdir_umask && $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir" ) then : else # mkdir does not conform to POSIX, # or it failed possibly due to a race condition. Create the # directory the slow way, step by step, checking for races as we go. case $dstdir in /*) prefix='/';; [-=\(\)!]*) prefix='./';; *) prefix='';; esac oIFS=$IFS IFS=/ set -f set fnord $dstdir shift set +f IFS=$oIFS prefixes= for d do test X"$d" = X && continue prefix=$prefix$d if test -d "$prefix"; then prefixes= else if $posix_mkdir; then (umask $mkdir_umask && $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break # Don't fail if two instances are running concurrently. test -d "$prefix" || exit 1 else case $prefix in *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;; *) qprefix=$prefix;; esac prefixes="$prefixes '$qprefix'" fi fi prefix=$prefix/ done if test -n "$prefixes"; then # Don't fail if two instances are running concurrently. (umask $mkdir_umask && eval "\$doit_exec \$mkdirprog $prefixes") || test -d "$dstdir" || exit 1 obsolete_mkdir_used=true fi fi fi if test -n "$dir_arg"; then { test -z "$chowncmd" || $doit $chowncmd "$dst"; } && { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } && { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false || test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1 else # Make a couple of temp file names in the proper directory. dsttmp=${dstdirslash}_inst.$$_ rmtmp=${dstdirslash}_rm.$$_ # Trap to clean up those temp files at exit. trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0 # Copy the file name to the temp name. (umask $cp_umask && { test -z "$stripcmd" || { # Create $dsttmp read-write so that cp doesn't create it read-only, # which would cause strip to fail. if test -z "$doit"; then : >"$dsttmp" # No need to fork-exec 'touch'. else $doit touch "$dsttmp" fi } } && $doit_exec $cpprog "$src" "$dsttmp") && # and set any options; do chmod last to preserve setuid bits. # # If any of these fail, we abort the whole thing. If we want to # ignore errors from any of these, just make sure not to ignore # errors from the above "$doit $cpprog $src $dsttmp" command. # { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } && { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } && { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } && { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } && # If -C, don't bother to copy if it wouldn't change the file. if $copy_on_change && old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` && new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` && set -f && set X $old && old=:$2:$4:$5:$6 && set X $new && new=:$2:$4:$5:$6 && set +f && test "$old" = "$new" && $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1 then rm -f "$dsttmp" else # If $backupsuffix is set, and the file being installed # already exists, attempt a backup. Don't worry if it fails, # e.g., if mv doesn't support -f. if test -n "$backupsuffix" && test -f "$dst"; then $doit $mvcmd -f "$dst" "$dst$backupsuffix" 2>/dev/null fi # Rename the file to the real destination. $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null || # The rename failed, perhaps because mv can't rename something else # to itself, or perhaps because mv is so ancient that it does not # support -f. { # Now remove or move aside any old file at destination location. # We try this two ways since rm can't unlink itself on some # systems and the destination file might be busy for other # reasons. In this case, the final cleanup might fail but the new # file should still install successfully. { test ! -f "$dst" || $doit $rmcmd "$dst" 2>/dev/null || { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null && { $doit $rmcmd "$rmtmp" 2>/dev/null; :; } } || { echo "$0: cannot unlink or rename $dst" >&2 (exit 1); exit 1 } } && # Now rename the file to the real destination. $doit $mvcmd "$dsttmp" "$dst" } fi || exit 1 trap '' 0 fi done # Local variables: # eval: (add-hook 'before-save-hook 'time-stamp) # time-stamp-start: "scriptversion=" # time-stamp-format: "%:y-%02m-%02d.%02H" # time-stamp-time-zone: "UTC0" # time-stamp-end: "; # UTC" # End: ncdc-1.23.1/Makefile.am0000644000175000017500000000772013455057462011503 00000000000000EXTRA_DIST=ChangeLog noinst_PROGRAMS= AM_CFLAGS=${NCURSES_CFLAGS} $(GLIB_CFLAGS) $(GNUTLS_CFLAGS) $(SQLITE_CFLAGS) AM_CPPFLAGS=-I$(builddir)/src -I$(srcdir)/deps -I$(srcdir)/deps/ylib if INSTALL_MANPAGE man_MANS=doc/ncdc.1 endif EXTRA_DIST+=doc/ncdc.1 doc/ncdc.pod.in if USE_POD2MAN noinst_PROGRAMS+=gendoc gendoc_SOURCES=doc/gendoc.c CLEANFILES=doc/ncdc.1 doc/ncdc.pod doc/ncdc.pod: $(srcdir)/doc/ncdc.pod.in gendoc$(EXEEXT) $(AM_V_GEN)./gendoc$(EXEEXT) <"$(srcdir)/doc/ncdc.pod.in" >doc/ncdc.pod doc/ncdc.1: doc/ncdc.pod $(AM_V_GEN)pod2man --center "ncdc manual" --release "@PACKAGE@-@VERSION@" doc/ncdc.pod >doc/ncdc.1 endif if HAVE_MH mkhdr=makeheaders mkhdr_dep= else mkhdr=./makeheaders$(EXEEXT) mkhdr_dep=makeheaders$(EXEEXT) noinst_PROGRAMS+=makeheaders endif makeheaders_SOURCES=deps/makeheaders.c noinst_LIBRARIES=libdeps.a libdeps_a_SOURCES=deps/ylib/yuri.c deps/yxml.c EXTRA_DIST+=deps/ylib/yuri.h deps/yxml.h bin_PROGRAMS=ncdc ncdc_SOURCES=\ src/bloom.c\ src/cc.c\ src/commands.c\ src/db.c\ src/dl.c\ src/dlfile.c\ src/fl_load.c\ src/fl_local.c\ src/fl_save.c\ src/fl_util.c\ src/geoip.c\ src/hub.c\ src/listen.c\ src/main.c\ src/net.c\ src/proto.c\ src/search.c\ src/strutil.c\ src/tth.c\ src/ui.c\ src/ui_colors.c\ src/ui_listing.c\ src/ui_logwindow.c\ src/ui_textinput.c\ src/uit_conn.c\ src/uit_dl.c\ src/uit_fl.c\ src/uit_hub.c\ src/uit_main.c\ src/uit_msg.c\ src/uit_search.c\ src/uit_userlist.c\ src/util.c\ src/vars.c auto_headers=$(ncdc_SOURCES:.c=.h) noinst_HEADERS=src/doc.h src/ncdc.h ncdc_LDADD=libdeps.a -lm $(NCURSES_LIBS) $(Z_LIBS) $(BZ2_LIBS) $(GLIB_LIBS) $(GNUTLS_LIBS) $(GCRYPT_LIBS) $(SQLITE_LIBS) $(GEOIP_LIBS) MOSTLYCLEANFILES=$(auto_headers) src/version.h mkhdr.done # Create a separate version.h and make sure only main.c depends on it. This # avoids the need to recompile everything on each commit. if USE_GIT_VERSION src/version.h: $(srcdir)/.git/logs/HEAD $(AM_V_GEN)echo '"'"`git describe --abbrev=4 --dirty=-d | sed s/^v//`"'"' >src/version.h else src/version.h: Makefile $(AM_V_GEN)echo '"'"@VERSION@"'"' >src/version.h endif src/main.$(OBJEXT): src/version.h $(auto_headers): mkhdr.done mkhdr.done: $(mkhdr_dep) $(ncdc_SOURCES) $(AM_V_GEN)$(mkhdr) `echo $(ncdc_SOURCES) | sed 's#\([^ ]*\)\.c#$(srcdir)/\1.c:$(builddir)/\1.h#g'` && touch mkhdr.done # Regenerate the header dependencies below, should be run every time # ncdc_SOURCES is modified. update-headerdeps: cd $(srcdir) &&\ perl -le 'print "$$_.\$$(OBJEXT): $$_.h" for grep s/\.c//, @ARGV' -- $(ncdc_SOURCES) |\ perl -e 'open(I, "Makefile.am~") or die $$!; while() { print O $$_; last if /^# HEADER_DEPS/ }; print O $$_ while(<>);' mv $(srcdir)/Makefile.am~ $(srcdir)/Makefile.am # !! Do not write anything below this line !! # HEADER_DEPS src/bloom.$(OBJEXT): src/bloom.h src/cc.$(OBJEXT): src/cc.h src/commands.$(OBJEXT): src/commands.h src/db.$(OBJEXT): src/db.h src/dl.$(OBJEXT): src/dl.h src/dlfile.$(OBJEXT): src/dlfile.h src/fl_load.$(OBJEXT): src/fl_load.h src/fl_local.$(OBJEXT): src/fl_local.h src/fl_save.$(OBJEXT): src/fl_save.h src/fl_util.$(OBJEXT): src/fl_util.h src/geoip.$(OBJEXT): src/geoip.h src/hub.$(OBJEXT): src/hub.h src/listen.$(OBJEXT): src/listen.h src/main.$(OBJEXT): src/main.h src/net.$(OBJEXT): src/net.h src/proto.$(OBJEXT): src/proto.h src/search.$(OBJEXT): src/search.h src/strutil.$(OBJEXT): src/strutil.h src/tth.$(OBJEXT): src/tth.h src/ui.$(OBJEXT): src/ui.h src/ui_colors.$(OBJEXT): src/ui_colors.h src/ui_listing.$(OBJEXT): src/ui_listing.h src/ui_logwindow.$(OBJEXT): src/ui_logwindow.h src/ui_textinput.$(OBJEXT): src/ui_textinput.h src/uit_conn.$(OBJEXT): src/uit_conn.h src/uit_dl.$(OBJEXT): src/uit_dl.h src/uit_fl.$(OBJEXT): src/uit_fl.h src/uit_hub.$(OBJEXT): src/uit_hub.h src/uit_main.$(OBJEXT): src/uit_main.h src/uit_msg.$(OBJEXT): src/uit_msg.h src/uit_search.$(OBJEXT): src/uit_search.h src/uit_userlist.$(OBJEXT): src/uit_userlist.h src/util.$(OBJEXT): src/util.h src/vars.$(OBJEXT): src/vars.h ncdc-1.23.1/deps/0000755000175000017500000000000014314550104010436 500000000000000ncdc-1.23.1/deps/makeheaders.c0000644000175000017500000030166511534161271013012 00000000000000static const char ident[] = "@(#) $Header: /cvstrac/cvstrac/makeheaders.c,v 1.4 2005/03/16 22:17:51 drh Exp $"; /* ** This program is free software; you can redistribute it and/or ** modify it under the terms of the Simplified BSD License (also ** known as the "2-Clause License" or "FreeBSD License".) ** ** Copyright 1993 D. Richard Hipp. All rights reserved. ** ** Redistribution and use in source and binary forms, with or ** without modification, are permitted provided that the following ** conditions are met: ** ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in ** the documentation and/or other materials provided with the ** distribution. ** ** This software is provided "as is" and any express or implied warranties, ** including, but not limited to, the implied warranties of merchantability ** and fitness for a particular purpose are disclaimed. In no event shall ** the author or contributors be liable for any direct, indirect, incidental, ** special, exemplary, or consequential damages (including, but not limited ** to, procurement of substitute goods or services; loss of use, data or ** profits; or business interruption) however caused and on any theory of ** liability, whether in contract, strict liability, or tort (including ** negligence or otherwise) arising in any way out of the use of this ** software, even if advised of the possibility of such damage. ** ** 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. ** appropriate header files. */ #include #include #include #include #include #include #if defined( __MINGW32__) || defined(__DMC__) || defined(_MSC_VER) || defined(__POCC__) # ifndef WIN32 # define WIN32 # endif # include #else # include #endif /* ** Macros for debugging. */ #ifdef DEBUG static int debugMask = 0; # define debug0(F,M) if( (F)&debugMask ){ fprintf(stderr,M); } # define debug1(F,M,A) if( (F)&debugMask ){ fprintf(stderr,M,A); } # define debug2(F,M,A,B) if( (F)&debugMask ){ fprintf(stderr,M,A,B); } # define debug3(F,M,A,B,C) if( (F)&debugMask ){ fprintf(stderr,M,A,B,C); } # define PARSER 0x00000001 # define DECL_DUMP 0x00000002 # define TOKENIZER 0x00000004 #else # define debug0(Flags, Format) # define debug1(Flags, Format, A) # define debug2(Flags, Format, A, B) # define debug3(Flags, Format, A, B, C) #endif /* ** The following macros are purely for the purpose of testing this ** program on itself. They don't really contribute to the code. */ #define INTERFACE 1 #define EXPORT_INTERFACE 1 #define EXPORT /* ** Each token in a source file is represented by an instance of ** the following structure. Tokens are collected onto a list. */ typedef struct Token Token; struct Token { const char *zText; /* The text of the token */ int nText; /* Number of characters in the token's text */ int eType; /* The type of this token */ int nLine; /* The line number on which the token starts */ Token *pComment; /* Most recent block comment before this token */ Token *pNext; /* Next token on the list */ Token *pPrev; /* Previous token on the list */ }; /* ** During tokenization, information about the state of the input ** stream is held in an instance of the following structure */ typedef struct InStream InStream; struct InStream { const char *z; /* Complete text of the input */ int i; /* Next character to read from the input */ int nLine; /* The line number for character z[i] */ }; /* ** Each declaration in the C or C++ source files is parsed out and stored as ** an instance of the following structure. ** ** A "forward declaration" is a declaration that an object exists that ** doesn't tell about the objects structure. A typical forward declaration ** is: ** ** struct Xyzzy; ** ** Not every object has a forward declaration. If it does, thought, the ** forward declaration will be contained in the zFwd field for C and ** the zFwdCpp for C++. The zDecl field contains the complete ** declaration text. */ typedef struct Decl Decl; struct Decl { char *zName; /* Name of the object being declared. The appearance ** of this name is a source file triggers the declaration ** to be added to the header for that file. */ char *zFile; /* File from which extracted. */ char *zIf; /* Surround the declaration with this #if */ char *zFwd; /* A forward declaration. NULL if there is none. */ char *zFwdCpp; /* Use this forward declaration for C++. */ char *zDecl; /* A full declaration of this object */ char *zExtra; /* Extra declaration text inserted into class objects */ int extraType; /* Last public:, protected: or private: in zExtraDecl */ struct Include *pInclude; /* #includes that come before this declaration */ int flags; /* See the "Properties" below */ Token *pComment; /* A block comment associated with this declaration */ Token tokenCode; /* Implementation of functions and procedures */ Decl *pSameName; /* Next declaration with the same "zName" */ Decl *pSameHash; /* Next declaration with same hash but different zName */ Decl *pNext; /* Next declaration with a different name */ }; /* ** Properties associated with declarations. ** ** DP_Forward and DP_Declared are used during the generation of a single ** header file in order to prevent duplicate declarations and definitions. ** DP_Forward is set after the object has been given a forward declaration ** and DP_Declared is set after the object gets a full declarations. ** (Example: A forward declaration is "typedef struct Abc Abc;" and the ** full declaration is "struct Abc { int a; float b; };".) ** ** The DP_Export and DP_Local flags are more permanent. They mark objects ** that have EXPORT scope and LOCAL scope respectively. If both of these ** marks are missing, then the object has library scope. The meanings of ** the scopes are as follows: ** ** LOCAL scope The object is only usable within the file in ** which it is declared. ** ** library scope The object is visible and usable within other ** files in the same project. By if the project is ** a library, then the object is not visible to users ** of the library. (i.e. the object does not appear ** in the output when using the -H option.) ** ** EXPORT scope The object is visible and usable everywhere. ** ** The DP_Flag is a temporary use flag that is used during processing to ** prevent an infinite loop. It's use is localized. ** ** The DP_Cplusplus, DP_ExternCReqd and DP_ExternReqd flags are permanent ** and are used to specify what type of declaration the object requires. */ #define DP_Forward 0x001 /* Has a forward declaration in this file */ #define DP_Declared 0x002 /* Has a full declaration in this file */ #define DP_Export 0x004 /* Export this declaration */ #define DP_Local 0x008 /* Declare in its home file only */ #define DP_Flag 0x010 /* Use to mark a subset of a Decl list ** for special processing */ #define DP_Cplusplus 0x020 /* Has C++ linkage and cannot appear in a ** C header file */ #define DP_ExternCReqd 0x040 /* Prepend 'extern "C"' in a C++ header. ** Prepend nothing in a C header */ #define DP_ExternReqd 0x080 /* Prepend 'extern "C"' in a C++ header if ** DP_Cplusplus is not also set. If DP_Cplusplus ** is set or this is a C header then ** prepend 'extern' */ /* ** Convenience macros for dealing with declaration properties */ #define DeclHasProperty(D,P) (((D)->flags&(P))==(P)) #define DeclHasAnyProperty(D,P) (((D)->flags&(P))!=0) #define DeclSetProperty(D,P) (D)->flags |= (P) #define DeclClearProperty(D,P) (D)->flags &= ~(P) /* ** These are state properties of the parser. Each of the values is ** distinct from the DP_ values above so that both can be used in ** the same "flags" field. ** ** Be careful not to confuse PS_Export with DP_Export or ** PS_Local with DP_Local. Their names are similar, but the meanings ** of these flags are very different. */ #define PS_Extern 0x000800 /* "extern" has been seen */ #define PS_Export 0x001000 /* If between "#if EXPORT_INTERFACE" ** and "#endif" */ #define PS_Export2 0x002000 /* If "EXPORT" seen */ #define PS_Typedef 0x004000 /* If "typedef" has been seen */ #define PS_Static 0x008000 /* If "static" has been seen */ #define PS_Interface 0x010000 /* If within #if INTERFACE..#endif */ #define PS_Method 0x020000 /* If "::" token has been seen */ #define PS_Local 0x040000 /* If within #if LOCAL_INTERFACE..#endif */ #define PS_Local2 0x080000 /* If "LOCAL" seen. */ #define PS_Public 0x100000 /* If "PUBLIC" seen. */ #define PS_Protected 0x200000 /* If "PROTECTED" seen. */ #define PS_Private 0x400000 /* If "PRIVATE" seen. */ #define PS_PPP 0x700000 /* If any of PUBLIC, PRIVATE, PROTECTED */ /* ** The following set of flags are ORed into the "flags" field of ** a Decl in order to identify what type of object is being ** declared. */ #define TY_Class 0x00100000 #define TY_Subroutine 0x00200000 #define TY_Macro 0x00400000 #define TY_Typedef 0x00800000 #define TY_Variable 0x01000000 #define TY_Structure 0x02000000 #define TY_Union 0x04000000 #define TY_Enumeration 0x08000000 #define TY_Defunct 0x10000000 /* Used to erase a declaration */ /* ** Each nested #if (or #ifdef or #ifndef) is stored in a stack of ** instances of the following structure. */ typedef struct Ifmacro Ifmacro; struct Ifmacro { int nLine; /* Line number where this macro occurs */ char *zCondition; /* Text of the condition for this macro */ Ifmacro *pNext; /* Next down in the stack */ int flags; /* Can hold PS_Export, PS_Interface or PS_Local flags */ }; /* ** When parsing a file, we need to keep track of what other files have ** be #include-ed. For each #include found, we create an instance of ** the following structure. */ typedef struct Include Include; struct Include { char *zFile; /* The name of file include. Includes "" or <> */ char *zIf; /* If not NULL, #include should be enclosed in #if */ char *zLabel; /* A unique label used to test if this #include has * appeared already in a file or not */ Include *pNext; /* Previous include file, or NULL if this is the first */ }; /* ** Identifiers found in a source file that might be used later to provoke ** the copying of a declaration into the corresponding header file are ** stored in a hash table as instances of the following structure. */ typedef struct Ident Ident; struct Ident { char *zName; /* The text of this identifier */ Ident *pCollide; /* Next identifier with the same hash */ Ident *pNext; /* Next identifier in a list of them all */ }; /* ** A complete table of identifiers is stored in an instance of ** the next structure. */ #define IDENT_HASH_SIZE 2237 typedef struct IdentTable IdentTable; struct IdentTable { Ident *pList; /* List of all identifiers in this table */ Ident *apTable[IDENT_HASH_SIZE]; /* The hash table */ }; /* ** The following structure holds all information for a single ** source file named on the command line of this program. */ typedef struct InFile InFile; struct InFile { char *zSrc; /* Name of input file */ char *zHdr; /* Name of the generated .h file for this input. ** Will be NULL if input is to be scanned only */ int flags; /* One or more DP_, PS_ and/or TY_ flags */ InFile *pNext; /* Next input file in the list of them all */ IdentTable idTable; /* All identifiers in this input file */ }; /* ** An unbounded string is able to grow without limit. We use these ** to construct large in-memory strings from lots of smaller components. */ typedef struct String String; struct String { int nAlloc; /* Number of bytes allocated */ int nUsed; /* Number of bytes used (not counting null terminator) */ char *zText; /* Text of the string */ }; /* ** The following structure contains a lot of state information used ** while generating a .h file. We put the information in this structure ** and pass around a pointer to this structure, rather than pass around ** all of the information separately. This helps reduce the number of ** arguments to generator functions. */ typedef struct GenState GenState; struct GenState { String *pStr; /* Write output to this string */ IdentTable *pTable; /* A table holding the zLabel of every #include that * has already been generated. Used to avoid * generating duplicate #includes. */ const char *zIf; /* If not NULL, then we are within a #if with * this argument. */ int nErr; /* Number of errors */ const char *zFilename; /* Name of the source file being scanned */ int flags; /* Various flags (DP_ and PS_ flags above) */ }; /* ** The following text line appears at the top of every file generated ** by this program. By recognizing this line, the program can be sure ** never to read a file that it generated itself. */ const char zTopLine[] = "/* \aThis file was automatically generated. Do not edit! */\n"; #define nTopLine (sizeof(zTopLine)-1) /* ** The name of the file currently being parsed. */ static char *zFilename; /* ** The stack of #if macros for the file currently being parsed. */ static Ifmacro *ifStack = 0; /* ** A list of all files that have been #included so far in a file being ** parsed. */ static Include *includeList = 0; /* ** The last block comment seen. */ static Token *blockComment = 0; /* ** The following flag is set if the -doc flag appears on the ** command line. */ static int doc_flag = 0; /* ** If the following flag is set, then makeheaders will attempt to ** generate prototypes for static functions and procedures. */ static int proto_static = 0; /* ** A list of all declarations. The list is held together using the ** pNext field of the Decl structure. */ static Decl *pDeclFirst; /* First on the list */ static Decl *pDeclLast; /* Last on the list */ /* ** A hash table of all declarations */ #define DECL_HASH_SIZE 3371 static Decl *apTable[DECL_HASH_SIZE]; /* ** The TEST macro must be defined to something. Make sure this is the ** case. */ #ifndef TEST # define TEST 0 #endif #ifdef NOT_USED /* ** We do our own assertion macro so that we can have more control ** over debugging. */ #define Assert(X) if(!(X)){ CantHappen(__LINE__); } #define CANT_HAPPEN CantHappen(__LINE__) static void CantHappen(int iLine){ fprintf(stderr,"Assertion failed on line %d\n",iLine); *(char*)1 = 0; /* Force a core-dump */ } #endif /* ** Memory allocation functions that are guaranteed never to return NULL. */ static void *SafeMalloc(int nByte){ void *p = malloc( nByte ); if( p==0 ){ fprintf(stderr,"Out of memory. Can't allocate %d bytes.\n",nByte); exit(1); } return p; } static void SafeFree(void *pOld){ if( pOld ){ free(pOld); } } static void *SafeRealloc(void *pOld, int nByte){ void *p; if( pOld==0 ){ p = SafeMalloc(nByte); }else{ p = realloc(pOld, nByte); if( p==0 ){ fprintf(stderr, "Out of memory. Can't enlarge an allocation to %d bytes\n",nByte); exit(1); } } return p; } static char *StrDup(const char *zSrc, int nByte){ char *zDest; if( nByte<=0 ){ nByte = strlen(zSrc); } zDest = SafeMalloc( nByte + 1 ); strncpy(zDest,zSrc,nByte); zDest[nByte] = 0; return zDest; } /* ** Return TRUE if the character X can be part of an identifier */ #define ISALNUM(X) ((X)=='_' || isalnum(X)) /* ** Routines for dealing with unbounded strings. */ static void StringInit(String *pStr){ pStr->nAlloc = 0; pStr->nUsed = 0; pStr->zText = 0; } static void StringReset(String *pStr){ SafeFree(pStr->zText); StringInit(pStr); } static void StringAppend(String *pStr, const char *zText, int nByte){ if( nByte<=0 ){ nByte = strlen(zText); } if( pStr->nUsed + nByte >= pStr->nAlloc ){ if( pStr->nAlloc==0 ){ pStr->nAlloc = nByte + 100; pStr->zText = SafeMalloc( pStr->nAlloc ); }else{ pStr->nAlloc = pStr->nAlloc*2 + nByte; pStr->zText = SafeRealloc(pStr->zText, pStr->nAlloc); } } strncpy(&pStr->zText[pStr->nUsed],zText,nByte); pStr->nUsed += nByte; pStr->zText[pStr->nUsed] = 0; } #define StringGet(S) ((S)->zText?(S)->zText:"") /* ** Compute a hash on a string. The number returned is a non-negative ** value between 0 and 2**31 - 1 */ static int Hash(const char *z, int n){ int h = 0; if( n<=0 ){ n = strlen(z); } while( n-- ){ h = h ^ (h<<5) ^ *z++; } return h & 0x7fffffff; } /* ** Given an identifier name, try to find a declaration for that ** identifier in the hash table. If found, return a pointer to ** the Decl structure. If not found, return 0. */ static Decl *FindDecl(const char *zName, int len){ int h; Decl *p; if( len<=0 ){ len = strlen(zName); } h = Hash(zName,len) % DECL_HASH_SIZE; p = apTable[h]; while( p && (strncmp(p->zName,zName,len)!=0 || p->zName[len]!=0) ){ p = p->pSameHash; } return p; } /* ** Install the given declaration both in the hash table and on ** the list of all declarations. */ static void InstallDecl(Decl *pDecl){ int h; Decl *pOther; h = Hash(pDecl->zName,0) % DECL_HASH_SIZE; pOther = apTable[h]; while( pOther && strcmp(pDecl->zName,pOther->zName)!=0 ){ pOther = pOther->pSameHash; } if( pOther ){ pDecl->pSameName = pOther->pSameName; pOther->pSameName = pDecl; }else{ pDecl->pSameName = 0; pDecl->pSameHash = apTable[h]; apTable[h] = pDecl; } pDecl->pNext = 0; if( pDeclFirst==0 ){ pDeclFirst = pDeclLast = pDecl; }else{ pDeclLast->pNext = pDecl; pDeclLast = pDecl; } } /* ** Look at the current ifStack. If anything declared at the current ** position must be surrounded with ** ** #if STUFF ** #endif ** ** Then this routine computes STUFF and returns a pointer to it. Memory ** to hold the value returned is obtained from malloc(). */ static char *GetIfString(void){ Ifmacro *pIf; char *zResult = 0; int hasIf = 0; String str; for(pIf = ifStack; pIf; pIf=pIf->pNext){ if( pIf->zCondition==0 || *pIf->zCondition==0 ) continue; if( !hasIf ){ hasIf = 1; StringInit(&str); }else{ StringAppend(&str," && ",4); } StringAppend(&str,pIf->zCondition,0); } if( hasIf ){ zResult = StrDup(StringGet(&str),0); StringReset(&str); }else{ zResult = 0; } return zResult; } /* ** Create a new declaration and put it in the hash table. Also ** return a pointer to it so that we can fill in the zFwd and zDecl ** fields, and so forth. */ static Decl *CreateDecl( const char *zName, /* Name of the object being declared. */ int nName /* Length of the name */ ){ Decl *pDecl; pDecl = SafeMalloc( sizeof(Decl) + nName + 1); memset(pDecl,0,sizeof(Decl)); pDecl->zName = (char*)&pDecl[1]; sprintf(pDecl->zName,"%.*s",nName,zName); pDecl->zFile = zFilename; pDecl->pInclude = includeList; pDecl->zIf = GetIfString(); InstallDecl(pDecl); return pDecl; } /* ** Insert a new identifier into an table of identifiers. Return TRUE if ** a new identifier was inserted and return FALSE if the identifier was ** already in the table. */ static int IdentTableInsert( IdentTable *pTable, /* The table into which we will insert */ const char *zId, /* Name of the identifiers */ int nId /* Length of the identifier name */ ){ int h; Ident *pId; if( nId<=0 ){ nId = strlen(zId); } h = Hash(zId,nId) % IDENT_HASH_SIZE; for(pId = pTable->apTable[h]; pId; pId=pId->pCollide){ if( strncmp(zId,pId->zName,nId)==0 && pId->zName[nId]==0 ){ /* printf("Already in table: %.*s\n",nId,zId); */ return 0; } } pId = SafeMalloc( sizeof(Ident) + nId + 1 ); pId->zName = (char*)&pId[1]; sprintf(pId->zName,"%.*s",nId,zId); pId->pNext = pTable->pList; pTable->pList = pId; pId->pCollide = pTable->apTable[h]; pTable->apTable[h] = pId; /* printf("Add to table: %.*s\n",nId,zId); */ return 1; } /* ** Check to see if the given value is in the given IdentTable. Return ** true if it is and false if it is not. */ static int IdentTableTest( IdentTable *pTable, /* The table in which to search */ const char *zId, /* Name of the identifiers */ int nId /* Length of the identifier name */ ){ int h; Ident *pId; if( nId<=0 ){ nId = strlen(zId); } h = Hash(zId,nId) % IDENT_HASH_SIZE; for(pId = pTable->apTable[h]; pId; pId=pId->pCollide){ if( strncmp(zId,pId->zName,nId)==0 && pId->zName[nId]==0 ){ return 1; } } return 0; } /* ** Remove every identifier from the given table. Reset the table to ** its initial state. */ static void IdentTableReset(IdentTable *pTable){ Ident *pId, *pNext; for(pId = pTable->pList; pId; pId = pNext){ pNext = pId->pNext; SafeFree(pId); } memset(pTable,0,sizeof(IdentTable)); } #ifdef DEBUG /* ** Print the name of every identifier in the given table, one per line */ static void IdentTablePrint(IdentTable *pTable, FILE *pOut){ Ident *pId; for(pId = pTable->pList; pId; pId = pId->pNext){ fprintf(pOut,"%s\n",pId->zName); } } #endif /* ** Read an entire file into memory. Return a pointer to the memory. ** ** The memory is obtained from SafeMalloc and must be freed by the ** calling function. ** ** If the read fails for any reason, 0 is returned. */ static char *ReadFile(const char *zFilename){ struct stat sStat; FILE *pIn; char *zBuf; int n; if( stat(zFilename,&sStat)!=0 #ifndef WIN32 || !S_ISREG(sStat.st_mode) #endif ){ return 0; } pIn = fopen(zFilename,"r"); if( pIn==0 ){ return 0; } zBuf = SafeMalloc( sStat.st_size + 1 ); n = fread(zBuf,1,sStat.st_size,pIn); zBuf[n] = 0; fclose(pIn); return zBuf; } /* ** Write the contents of a string into a file. Return the number of ** errors */ static int WriteFile(const char *zFilename, const char *zOutput){ FILE *pOut; pOut = fopen(zFilename,"w"); if( pOut==0 ){ return 1; } fwrite(zOutput,1,strlen(zOutput),pOut); fclose(pOut); return 0; } /* ** Major token types */ #define TT_Space 1 /* Contiguous white space */ #define TT_Id 2 /* An identifier */ #define TT_Preprocessor 3 /* Any C preprocessor directive */ #define TT_Comment 4 /* Either C or C++ style comment */ #define TT_Number 5 /* Any numeric constant */ #define TT_String 6 /* String or character constants. ".." or '.' */ #define TT_Braces 7 /* All text between { and a matching } */ #define TT_EOF 8 /* End of file */ #define TT_Error 9 /* An error condition */ #define TT_BlockComment 10 /* A C-Style comment at the left margin that * spans multple lines */ #define TT_Other 0 /* None of the above */ /* ** Get a single low-level token from the input file. Update the ** file pointer so that it points to the first character beyond the ** token. ** ** A "low-level token" is any token except TT_Braces. A TT_Braces token ** consists of many smaller tokens and is assembled by a routine that ** calls this one. ** ** The function returns the number of errors. An error is an ** unterminated string or character literal or an unterminated ** comment. ** ** Profiling shows that this routine consumes about half the ** CPU time on a typical run of makeheaders. */ static int GetToken(InStream *pIn, Token *pToken){ int i; const char *z; int cStart; int c; int startLine; /* Line on which a structure begins */ int nlisc = 0; /* True if there is a new-line in a ".." or '..' */ int nErr = 0; /* Number of errors seen */ z = pIn->z; i = pIn->i; pToken->nLine = pIn->nLine; pToken->zText = &z[i]; switch( z[i] ){ case 0: pToken->eType = TT_EOF; pToken->nText = 0; break; case '#': if( i==0 || z[i-1]=='\n' || (i>1 && z[i-1]=='\r' && z[i-2]=='\n')){ /* We found a preprocessor statement */ pToken->eType = TT_Preprocessor; i++; while( z[i]!=0 && z[i]!='\n' ){ if( z[i]=='\\' ){ i++; if( z[i]=='\n' ) pIn->nLine++; } i++; } pToken->nText = i - pIn->i; }else{ /* Just an operator */ pToken->eType = TT_Other; pToken->nText = 1; } break; case ' ': case '\t': case '\r': case '\f': case '\n': while( isspace(z[i]) ){ if( z[i]=='\n' ) pIn->nLine++; i++; } pToken->eType = TT_Space; pToken->nText = i - pIn->i; break; case '\\': pToken->nText = 2; pToken->eType = TT_Other; if( z[i+1]=='\n' ){ pIn->nLine++; pToken->eType = TT_Space; }else if( z[i+1]==0 ){ pToken->nText = 1; } break; case '\'': case '\"': cStart = z[i]; startLine = pIn->nLine; do{ i++; c = z[i]; if( c=='\n' ){ if( !nlisc ){ fprintf(stderr, "%s:%d: (warning) Newline in string or character literal.\n", zFilename, pIn->nLine); nlisc = 1; } pIn->nLine++; } if( c=='\\' ){ i++; c = z[i]; if( c=='\n' ){ pIn->nLine++; } }else if( c==cStart ){ i++; c = 0; }else if( c==0 ){ fprintf(stderr, "%s:%d: Unterminated string or character literal.\n", zFilename, startLine); nErr++; } }while( c ); pToken->eType = TT_String; pToken->nText = i - pIn->i; break; case '/': if( z[i+1]=='/' ){ /* C++ style comment */ while( z[i] && z[i]!='\n' ){ i++; } pToken->eType = TT_Comment; pToken->nText = i - pIn->i; }else if( z[i+1]=='*' ){ /* C style comment */ int isBlockComment = i==0 || z[i-1]=='\n'; i += 2; startLine = pIn->nLine; while( z[i] && (z[i]!='*' || z[i+1]!='/') ){ if( z[i]=='\n' ){ pIn->nLine++; if( isBlockComment ){ if( z[i+1]=='*' || z[i+2]=='*' ){ isBlockComment = 2; }else{ isBlockComment = 0; } } } i++; } if( z[i] ){ i += 2; }else{ isBlockComment = 0; fprintf(stderr,"%s:%d: Unterminated comment\n", zFilename, startLine); nErr++; } pToken->eType = isBlockComment==2 ? TT_BlockComment : TT_Comment; pToken->nText = i - pIn->i; }else{ /* A divide operator */ pToken->eType = TT_Other; pToken->nText = 1 + (z[i+1]=='+'); } break; case '0': if( z[i+1]=='x' || z[i+1]=='X' ){ /* A hex constant */ i += 2; while( isxdigit(z[i]) ){ i++; } }else{ /* An octal constant */ while( isdigit(z[i]) ){ i++; } } pToken->eType = TT_Number; pToken->nText = i - pIn->i; break; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': while( isdigit(z[i]) ){ i++; } if( (c=z[i])=='.' ){ i++; while( isdigit(z[i]) ){ i++; } c = z[i]; if( c=='e' || c=='E' ){ i++; if( ((c=z[i])=='+' || c=='-') && isdigit(z[i+1]) ){ i++; } while( isdigit(z[i]) ){ i++; } c = z[i]; } if( c=='f' || c=='F' || c=='l' || c=='L' ){ i++; } }else if( c=='e' || c=='E' ){ i++; if( ((c=z[i])=='+' || c=='-') && isdigit(z[i+1]) ){ i++; } while( isdigit(z[i]) ){ i++; } }else if( c=='L' || c=='l' ){ i++; c = z[i]; if( c=='u' || c=='U' ){ i++; } }else if( c=='u' || c=='U' ){ i++; c = z[i]; if( c=='l' || c=='L' ){ i++; } } pToken->eType = TT_Number; pToken->nText = i - pIn->i; break; case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '_': while( isalnum(z[i]) || z[i]=='_' ){ i++; }; pToken->eType = TT_Id; pToken->nText = i - pIn->i; break; case ':': pToken->eType = TT_Other; pToken->nText = 1 + (z[i+1]==':'); break; case '=': case '<': case '>': case '+': case '-': case '*': case '%': case '^': case '&': case '|': pToken->eType = TT_Other; pToken->nText = 1 + (z[i+1]=='='); break; default: pToken->eType = TT_Other; pToken->nText = 1; break; } pIn->i += pToken->nText; return nErr; } /* ** This routine recovers the next token from the input file which is ** not a space or a comment or any text between an "#if 0" and "#endif". ** ** This routine returns the number of errors encountered. An error ** is an unterminated token or unmatched "#if 0". ** ** Profiling shows that this routine uses about a quarter of the ** CPU time in a typical run. */ static int GetNonspaceToken(InStream *pIn, Token *pToken){ int nIf = 0; int inZero = 0; const char *z; int value; int startLine; int nErr = 0; startLine = pIn->nLine; while( 1 ){ nErr += GetToken(pIn,pToken); /* printf("%04d: Type=%d nIf=%d [%.*s]\n", pToken->nLine,pToken->eType,nIf,pToken->nText, pToken->eType!=TT_Space ? pToken->zText : ""); */ pToken->pComment = blockComment; switch( pToken->eType ){ case TT_Comment: case TT_Space: break; case TT_BlockComment: if( doc_flag ){ blockComment = SafeMalloc( sizeof(Token) ); *blockComment = *pToken; } break; case TT_EOF: if( nIf ){ fprintf(stderr,"%s:%d: Unterminated \"#if\"\n", zFilename, startLine); nErr++; } return nErr; case TT_Preprocessor: z = &pToken->zText[1]; while( *z==' ' || *z=='\t' ) z++; if( sscanf(z,"if %d",&value)==1 && value==0 ){ nIf++; inZero = 1; }else if( inZero ){ if( strncmp(z,"if",2)==0 ){ nIf++; }else if( strncmp(z,"endif",5)==0 ){ nIf--; if( nIf==0 ) inZero = 0; } }else{ return nErr; } break; default: if( !inZero ){ return nErr; } break; } } /* NOT REACHED */ } /* ** This routine looks for identifiers (strings of contiguous alphanumeric ** characters) within a preprocessor directive and adds every such string ** found to the given identifier table */ static void FindIdentifiersInMacro(Token *pToken, IdentTable *pTable){ Token sToken; InStream sIn; int go = 1; sIn.z = pToken->zText; sIn.i = 1; sIn.nLine = 1; while( go && sIn.i < pToken->nText ){ GetToken(&sIn,&sToken); switch( sToken.eType ){ case TT_Id: IdentTableInsert(pTable,sToken.zText,sToken.nText); break; case TT_EOF: go = 0; break; default: break; } } } /* ** This routine gets the next token. Everything contained within ** {...} is collapsed into a single TT_Braces token. Whitespace is ** omitted. ** ** If pTable is not NULL, then insert every identifier seen into the ** IdentTable. This includes any identifiers seen inside of {...}. ** ** The number of errors encountered is returned. An error is an ** unterminated token. */ static int GetBigToken(InStream *pIn, Token *pToken, IdentTable *pTable){ const char *z, *zStart; int iStart; int nBrace; int c; int nLine; int nErr; nErr = GetNonspaceToken(pIn,pToken); switch( pToken->eType ){ case TT_Id: if( pTable!=0 ){ IdentTableInsert(pTable,pToken->zText,pToken->nText); } return nErr; case TT_Preprocessor: if( pTable!=0 ){ FindIdentifiersInMacro(pToken,pTable); } return nErr; case TT_Other: if( pToken->zText[0]=='{' ) break; return nErr; default: return nErr; } z = pIn->z; iStart = pIn->i; zStart = pToken->zText; nLine = pToken->nLine; nBrace = 1; while( nBrace ){ nErr += GetNonspaceToken(pIn,pToken); /* printf("%04d: nBrace=%d [%.*s]\n",pToken->nLine,nBrace, pToken->nText,pToken->zText); */ switch( pToken->eType ){ case TT_EOF: fprintf(stderr,"%s:%d: Unterminated \"{\"\n", zFilename, nLine); nErr++; pToken->eType = TT_Error; return nErr; case TT_Id: if( pTable ){ IdentTableInsert(pTable,pToken->zText,pToken->nText); } break; case TT_Preprocessor: if( pTable!=0 ){ FindIdentifiersInMacro(pToken,pTable); } break; case TT_Other: if( (c = pToken->zText[0])=='{' ){ nBrace++; }else if( c=='}' ){ nBrace--; } break; default: break; } } pToken->eType = TT_Braces; pToken->nText = 1 + pIn->i - iStart; pToken->zText = zStart; pToken->nLine = nLine; return nErr; } /* ** This routine frees up a list of Tokens. The pComment tokens are ** not cleared by this. So we leak a little memory when using the -doc ** option. So what. */ static void FreeTokenList(Token *pList){ Token *pNext; while( pList ){ pNext = pList->pNext; SafeFree(pList); pList = pNext; } } /* ** Tokenize an entire file. Return a pointer to the list of tokens. ** ** Space for each token is obtained from a separate malloc() call. The ** calling function is responsible for freeing this space. ** ** If pTable is not NULL, then fill the table with all identifiers seen in ** the input file. */ static Token *TokenizeFile(const char *zFile, IdentTable *pTable){ InStream sIn; Token *pFirst = 0, *pLast = 0, *pNew; int nErr = 0; sIn.z = zFile; sIn.i = 0; sIn.nLine = 1; blockComment = 0; while( sIn.z[sIn.i]!=0 ){ pNew = SafeMalloc( sizeof(Token) ); nErr += GetBigToken(&sIn,pNew,pTable); debug3(TOKENIZER, "Token on line %d: [%.*s]\n", pNew->nLine, pNew->nText<50 ? pNew->nText : 50, pNew->zText); if( pFirst==0 ){ pFirst = pLast = pNew; pNew->pPrev = 0; }else{ pLast->pNext = pNew; pNew->pPrev = pLast; pLast = pNew; } if( pNew->eType==TT_EOF ) break; } if( pLast ) pLast->pNext = 0; blockComment = 0; if( nErr ){ FreeTokenList(pFirst); pFirst = 0; } return pFirst; } #if TEST==1 /* ** Use the following routine to test or debug the tokenizer. */ void main(int argc, char **argv){ char *zFile; Token *pList, *p; IdentTable sTable; if( argc!=2 ){ fprintf(stderr,"Usage: %s filename\n",*argv); exit(1); } memset(&sTable,0,sizeof(sTable)); zFile = ReadFile(argv[1]); if( zFile==0 ){ fprintf(stderr,"Can't read file \"%s\"\n",argv[1]); exit(1); } pList = TokenizeFile(zFile,&sTable); for(p=pList; p; p=p->pNext){ int j; switch( p->eType ){ case TT_Space: printf("%4d: Space\n",p->nLine); break; case TT_Id: printf("%4d: Id %.*s\n",p->nLine,p->nText,p->zText); break; case TT_Preprocessor: printf("%4d: Preprocessor %.*s\n",p->nLine,p->nText,p->zText); break; case TT_Comment: printf("%4d: Comment\n",p->nLine); break; case TT_BlockComment: printf("%4d: Block Comment\n",p->nLine); break; case TT_Number: printf("%4d: Number %.*s\n",p->nLine,p->nText,p->zText); break; case TT_String: printf("%4d: String %.*s\n",p->nLine,p->nText,p->zText); break; case TT_Other: printf("%4d: Other %.*s\n",p->nLine,p->nText,p->zText); break; case TT_Braces: for(j=0; jnText && j<30 && p->zText[j]!='\n'; j++){} printf("%4d: Braces %.*s...}\n",p->nLine,j,p->zText); break; case TT_EOF: printf("%4d: End of file\n",p->nLine); break; default: printf("%4d: type %d\n",p->nLine,p->eType); break; } } FreeTokenList(pList); SafeFree(zFile); IdentTablePrint(&sTable,stdout); } #endif #ifdef DEBUG /* ** For debugging purposes, write out a list of tokens. */ static void PrintTokens(Token *pFirst, Token *pLast){ int needSpace = 0; int c; pLast = pLast->pNext; while( pFirst!=pLast ){ switch( pFirst->eType ){ case TT_Preprocessor: printf("\n%.*s\n",pFirst->nText,pFirst->zText); needSpace = 0; break; case TT_Id: case TT_Number: printf("%s%.*s", needSpace ? " " : "", pFirst->nText, pFirst->zText); needSpace = 1; break; default: c = pFirst->zText[0]; printf("%s%.*s", (needSpace && (c=='*' || c=='{')) ? " " : "", pFirst->nText, pFirst->zText); needSpace = pFirst->zText[0]==','; break; } pFirst = pFirst->pNext; } } #endif /* ** Convert a sequence of tokens into a string and return a pointer ** to that string. Space to hold the string is obtained from malloc() ** and must be freed by the calling function. ** ** Certain keywords (EXPORT, PRIVATE, PUBLIC, PROTECTED) are always ** skipped. ** ** If pSkip!=0 then skip over nSkip tokens beginning with pSkip. ** ** If zTerm!=0 then append the text to the end. */ static char *TokensToString( Token *pFirst, /* First token in the string */ Token *pLast, /* Last token in the string */ char *zTerm, /* Terminate the string with this text if not NULL */ Token *pSkip, /* Skip this token if not NULL */ int nSkip /* Skip a total of this many tokens */ ){ char *zReturn; String str; int needSpace = 0; int c; int iSkip = 0; int skipOne = 0; StringInit(&str); pLast = pLast->pNext; while( pFirst!=pLast ){ if( pFirst==pSkip ){ iSkip = nSkip; } if( iSkip>0 ){ iSkip--; pFirst=pFirst->pNext; continue; } switch( pFirst->eType ){ case TT_Preprocessor: StringAppend(&str,"\n",1); StringAppend(&str,pFirst->zText,pFirst->nText); StringAppend(&str,"\n",1); needSpace = 0; break; case TT_Id: switch( pFirst->zText[0] ){ case 'E': if( pFirst->nText==6 && strncmp(pFirst->zText,"EXPORT",6)==0 ){ skipOne = 1; } break; case 'P': switch( pFirst->nText ){ case 6: skipOne = !strncmp(pFirst->zText,"PUBLIC", 6); break; case 7: skipOne = !strncmp(pFirst->zText,"PRIVATE",7); break; case 9: skipOne = !strncmp(pFirst->zText,"PROTECTED",9); break; default: break; } break; default: break; } if( skipOne ){ pFirst = pFirst->pNext; continue; } /* Fall thru to the next case */ case TT_Number: if( needSpace ){ StringAppend(&str," ",1); } StringAppend(&str,pFirst->zText,pFirst->nText); needSpace = 1; break; default: c = pFirst->zText[0]; if( needSpace && (c=='*' || c=='{') ){ StringAppend(&str," ",1); } StringAppend(&str,pFirst->zText,pFirst->nText); /* needSpace = pFirst->zText[0]==','; */ needSpace = 0; break; } pFirst = pFirst->pNext; } if( zTerm && *zTerm ){ StringAppend(&str,zTerm,strlen(zTerm)); } zReturn = StrDup(StringGet(&str),0); StringReset(&str); return zReturn; } /* ** This routine is called when we see one of the keywords "struct", ** "enum", "union" or "class". This might be the beginning of a ** type declaration. This routine will process the declaration and ** remove the declaration tokens from the input stream. ** ** If this is a type declaration that is immediately followed by a ** semicolon (in other words it isn't also a variable definition) ** then set *pReset to ';'. Otherwise leave *pReset at 0. The ** *pReset flag causes the parser to skip ahead to the next token ** that begins with the value placed in the *pReset flag, if that ** value is different from 0. */ static int ProcessTypeDecl(Token *pList, int flags, int *pReset){ Token *pName, *pEnd; Decl *pDecl; String str; int need_to_collapse = 1; int type = 0; *pReset = 0; if( pList==0 || pList->pNext==0 || pList->pNext->eType!=TT_Id ){ return 0; } pName = pList->pNext; /* Catch the case of "struct Foo;" and skip it. */ if( pName->pNext && pName->pNext->zText[0]==';' ){ *pReset = ';'; return 0; } for(pEnd=pName->pNext; pEnd && pEnd->eType!=TT_Braces; pEnd=pEnd->pNext){ switch( pEnd->zText[0] ){ case '(': case '*': case '[': case '=': case ';': return 0; } } if( pEnd==0 ){ return 0; } /* ** At this point, we know we have a type declaration that is bounded ** by pList and pEnd and has the name pName. */ /* ** If the braces are followed immedately by a semicolon, then we are ** dealing a type declaration only. There is not variable definition ** following the type declaration. So reset... */ if( pEnd->pNext==0 || pEnd->pNext->zText[0]==';' ){ *pReset = ';'; need_to_collapse = 0; }else{ need_to_collapse = 1; } if( proto_static==0 && (flags & (PS_Local|PS_Export|PS_Interface))==0 ){ /* Ignore these objects unless they are explicitly declared as interface, ** or unless the "-local" command line option was specified. */ *pReset = ';'; return 0; } #ifdef DEBUG if( debugMask & PARSER ){ printf("**** Found type: %.*s %.*s...\n", pList->nText, pList->zText, pName->nText, pName->zText); PrintTokens(pList,pEnd); printf(";\n"); } #endif /* ** Create a new Decl object for this definition. Actually, if this ** is a C++ class definition, then the Decl object might already exist, ** so check first for that case before creating a new one. */ switch( *pList->zText ){ case 'c': type = TY_Class; break; case 's': type = TY_Structure; break; case 'e': type = TY_Enumeration; break; case 'u': type = TY_Union; break; default: /* Can't Happen */ break; } if( type!=TY_Class ){ pDecl = 0; }else{ pDecl = FindDecl(pName->zText, pName->nText); if( pDecl && (pDecl->flags & type)!=type ) pDecl = 0; } if( pDecl==0 ){ pDecl = CreateDecl(pName->zText,pName->nText); } if( (flags & PS_Static) || !(flags & (PS_Interface|PS_Export)) ){ DeclSetProperty(pDecl,DP_Local); } DeclSetProperty(pDecl,type); /* The object has a full declaration only if it is contained within ** "#if INTERFACE...#endif" or "#if EXPORT_INTERFACE...#endif" or ** "#if LOCAL_INTERFACE...#endif". Otherwise, we only give it a ** forward declaration. */ if( flags & (PS_Local | PS_Export | PS_Interface) ){ pDecl->zDecl = TokensToString(pList,pEnd,";\n",0,0); }else{ pDecl->zDecl = 0; } pDecl->pComment = pList->pComment; StringInit(&str); StringAppend(&str,"typedef ",0); StringAppend(&str,pList->zText,pList->nText); StringAppend(&str," ",0); StringAppend(&str,pName->zText,pName->nText); StringAppend(&str," ",0); StringAppend(&str,pName->zText,pName->nText); StringAppend(&str,";\n",2); pDecl->zFwd = StrDup(StringGet(&str),0); StringReset(&str); StringInit(&str); StringAppend(&str,pList->zText,pList->nText); StringAppend(&str," ",0); StringAppend(&str,pName->zText,pName->nText); StringAppend(&str,";\n",2); pDecl->zFwdCpp = StrDup(StringGet(&str),0); StringReset(&str); if( flags & PS_Export ){ DeclSetProperty(pDecl,DP_Export); }else if( flags & PS_Local ){ DeclSetProperty(pDecl,DP_Local); } /* Here's something weird. ANSI-C doesn't allow a forward declaration ** of an enumeration. So we have to build the typedef into the ** definition. */ if( pDecl->zDecl && DeclHasProperty(pDecl, TY_Enumeration) ){ StringInit(&str); StringAppend(&str,pDecl->zDecl,0); StringAppend(&str,pDecl->zFwd,0); SafeFree(pDecl->zDecl); SafeFree(pDecl->zFwd); pDecl->zFwd = 0; pDecl->zDecl = StrDup(StringGet(&str),0); StringReset(&str); } if( pName->pNext->zText[0]==':' ){ DeclSetProperty(pDecl,DP_Cplusplus); } if( pName->nText==5 && strncmp(pName->zText,"class",5)==0 ){ DeclSetProperty(pDecl,DP_Cplusplus); } /* ** Remove all but pList and pName from the input stream. */ if( need_to_collapse ){ while( pEnd!=pName ){ Token *pPrev = pEnd->pPrev; pPrev->pNext = pEnd->pNext; pEnd->pNext->pPrev = pPrev; SafeFree(pEnd); pEnd = pPrev; } } return 0; } /* ** Given a list of tokens that declare something (a function, procedure, ** variable or typedef) find the token which contains the name of the ** thing being declared. ** ** Algorithm: ** ** The name is: ** ** 1. The first identifier that is followed by a "[", or ** ** 2. The first identifier that is followed by a "(" where the ** "(" is followed by another identifier, or ** ** 3. The first identifier followed by "::", or ** ** 4. If none of the above, then the last identifier. ** ** In all of the above, certain reserved words (like "char") are ** not considered identifiers. */ static Token *FindDeclName(Token *pFirst, Token *pLast){ Token *pName = 0; Token *p; int c; if( pFirst==0 || pLast==0 ){ return 0; } pLast = pLast->pNext; for(p=pFirst; p && p!=pLast; p=p->pNext){ if( p->eType==TT_Id ){ static IdentTable sReserved; static int isInit = 0; static char *aWords[] = { "char", "class", "const", "double", "enum", "extern", "EXPORT", "ET_PROC", "float", "int", "long", "PRIVATE", "PROTECTED", "PUBLIC", "register", "static", "struct", "sizeof", "signed", "typedef", "union", "volatile", "virtual", "void", }; if( !isInit ){ int i; for(i=0; izText,p->nText) ){ pName = p; } }else if( p==pFirst ){ continue; }else if( (c=p->zText[0])=='[' && pName ){ break; }else if( c=='(' && p->pNext && p->pNext->eType==TT_Id && pName ){ break; }else if( c==':' && p->zText[1]==':' && pName ){ break; } } return pName; } /* ** This routine is called when we see a method for a class that begins ** with the PUBLIC, PRIVATE, or PROTECTED keywords. Such methods are ** added to their class definitions. */ static int ProcessMethodDef(Token *pFirst, Token *pLast, int flags){ Token *pCode; Token *pClass; char *zDecl; Decl *pDecl; String str; int type; pCode = pLast; pLast = pLast->pPrev; while( pFirst->zText[0]=='P' ){ int rc = 1; switch( pFirst->nText ){ case 6: rc = strncmp(pFirst->zText,"PUBLIC",6); break; case 7: rc = strncmp(pFirst->zText,"PRIVATE",7); break; case 9: rc = strncmp(pFirst->zText,"PROTECTED",9); break; default: break; } if( rc ) break; pFirst = pFirst->pNext; } pClass = FindDeclName(pFirst,pLast); if( pClass==0 ){ fprintf(stderr,"%s:%d: Unable to find the class name for this method\n", zFilename, pFirst->nLine); return 1; } pDecl = FindDecl(pClass->zText, pClass->nText); if( pDecl==0 || (pDecl->flags & TY_Class)!=TY_Class ){ pDecl = CreateDecl(pClass->zText, pClass->nText); DeclSetProperty(pDecl, TY_Class); } StringInit(&str); if( pDecl->zExtra ){ StringAppend(&str, pDecl->zExtra, 0); SafeFree(pDecl->zExtra); pDecl->zExtra = 0; } type = flags & PS_PPP; if( pDecl->extraType!=type ){ if( type & PS_Public ){ StringAppend(&str, "public:\n", 0); pDecl->extraType = PS_Public; }else if( type & PS_Protected ){ StringAppend(&str, "protected:\n", 0); pDecl->extraType = PS_Protected; }else if( type & PS_Private ){ StringAppend(&str, "private:\n", 0); pDecl->extraType = PS_Private; } } StringAppend(&str, " ", 0); zDecl = TokensToString(pFirst, pLast, ";\n", pClass, 2); StringAppend(&str, zDecl, 0); SafeFree(zDecl); pDecl->zExtra = StrDup(StringGet(&str), 0); StringReset(&str); return 0; } /* ** This routine is called when we see a function or procedure definition. ** We make an entry in the declaration table that is a prototype for this ** function or procedure. */ static int ProcessProcedureDef(Token *pFirst, Token *pLast, int flags){ Token *pName; Decl *pDecl; Token *pCode; if( pFirst==0 || pLast==0 ){ return 0; } if( flags & PS_Method ){ if( flags & PS_PPP ){ return ProcessMethodDef(pFirst, pLast, flags); }else{ return 0; } } if( (flags & PS_Static)!=0 && !proto_static ){ return 0; } pCode = pLast; while( pLast && pLast!=pFirst && pLast->zText[0]!=')' ){ pLast = pLast->pPrev; } if( pLast==0 || pLast==pFirst || pFirst->pNext==pLast ){ fprintf(stderr,"%s:%d: Unrecognized syntax.\n", zFilename, pFirst->nLine); return 1; } if( flags & (PS_Interface|PS_Export|PS_Local) ){ fprintf(stderr,"%s:%d: Missing \"inline\" on function or procedure.\n", zFilename, pFirst->nLine); return 1; } pName = FindDeclName(pFirst,pLast); if( pName==0 ){ fprintf(stderr,"%s:%d: Malformed function or procedure definition.\n", zFilename, pFirst->nLine); return 1; } /* ** At this point we've isolated a procedure declaration between pFirst ** and pLast with the name pName. */ #ifdef DEBUG if( debugMask & PARSER ){ printf("**** Found routine: %.*s on line %d...\n", pName->nText, pName->zText, pFirst->nLine); PrintTokens(pFirst,pLast); printf(";\n"); } #endif pDecl = CreateDecl(pName->zText,pName->nText); pDecl->pComment = pFirst->pComment; if( pCode && pCode->eType==TT_Braces ){ pDecl->tokenCode = *pCode; } DeclSetProperty(pDecl,TY_Subroutine); pDecl->zDecl = TokensToString(pFirst,pLast,";\n",0,0); if( (flags & (PS_Static|PS_Local2))!=0 ){ DeclSetProperty(pDecl,DP_Local); }else if( (flags & (PS_Export2))!=0 ){ DeclSetProperty(pDecl,DP_Export); } if( flags & DP_Cplusplus ){ DeclSetProperty(pDecl,DP_Cplusplus); }else{ DeclSetProperty(pDecl,DP_ExternCReqd); } return 0; } /* ** This routine is called whenever we see the "inline" keyword. We ** need to seek-out the inline function or procedure and make a ** declaration out of the entire definition. */ static int ProcessInlineProc(Token *pFirst, int flags, int *pReset){ Token *pName; Token *pEnd; Decl *pDecl; for(pEnd=pFirst; pEnd; pEnd = pEnd->pNext){ if( pEnd->zText[0]=='{' || pEnd->zText[0]==';' ){ *pReset = pEnd->zText[0]; break; } } if( pEnd==0 ){ *pReset = ';'; fprintf(stderr,"%s:%d: incomplete inline procedure definition\n", zFilename, pFirst->nLine); return 1; } pName = FindDeclName(pFirst,pEnd); if( pName==0 ){ fprintf(stderr,"%s:%d: malformed inline procedure definition\n", zFilename, pFirst->nLine); return 1; } #ifdef DEBUG if( debugMask & PARSER ){ printf("**** Found inline routine: %.*s on line %d...\n", pName->nText, pName->zText, pFirst->nLine); PrintTokens(pFirst,pEnd); printf("\n"); } #endif pDecl = CreateDecl(pName->zText,pName->nText); pDecl->pComment = pFirst->pComment; DeclSetProperty(pDecl,TY_Subroutine); pDecl->zDecl = TokensToString(pFirst,pEnd,";\n",0,0); if( (flags & (PS_Static|PS_Local|PS_Local2)) ){ DeclSetProperty(pDecl,DP_Local); }else if( flags & (PS_Export|PS_Export2) ){ DeclSetProperty(pDecl,DP_Export); } if( flags & DP_Cplusplus ){ DeclSetProperty(pDecl,DP_Cplusplus); }else{ DeclSetProperty(pDecl,DP_ExternCReqd); } return 0; } /* ** Determine if the tokens between pFirst and pEnd form a variable ** definition or a function prototype. Return TRUE if we are dealing ** with a variable defintion and FALSE for a prototype. ** ** pEnd is the token that ends the object. It can be either a ';' or ** a '='. If it is '=', then assume we have a variable definition. ** ** If pEnd is ';', then the determination is more difficult. We have ** to search for an occurance of an ID followed immediately by '('. ** If found, we have a prototype. Otherwise we are dealing with a ** variable definition. */ static int isVariableDef(Token *pFirst, Token *pEnd){ if( pEnd && pEnd->zText[0]=='=' && (pEnd->pPrev->nText!=8 || strncmp(pEnd->pPrev->zText,"operator",8)!=0) ){ return 1; } while( pFirst && pFirst!=pEnd && pFirst->pNext && pFirst->pNext!=pEnd ){ if( pFirst->eType==TT_Id && pFirst->pNext->zText[0]=='(' ){ return 0; } pFirst = pFirst->pNext; } return 1; } /* ** This routine is called whenever we encounter a ";" or "=". The stuff ** between pFirst and pLast constitutes either a typedef or a global ** variable definition. Do the right thing. */ static int ProcessDecl(Token *pFirst, Token *pEnd, int flags){ Token *pName; Decl *pDecl; int isLocal = 0; int isVar; int nErr = 0; if( pFirst==0 || pEnd==0 ){ return 0; } if( flags & PS_Typedef ){ if( (flags & (PS_Export2|PS_Local2))!=0 ){ fprintf(stderr,"%s:%d: \"EXPORT\" or \"LOCAL\" ignored before typedef.\n", zFilename, pFirst->nLine); nErr++; } if( (flags & (PS_Interface|PS_Export|PS_Local|DP_Cplusplus))==0 ){ /* It is illegal to duplicate a typedef in C (but OK in C++). ** So don't record typedefs that aren't within a C++ file or ** within #if INTERFACE..#endif */ return nErr; } if( (flags & (PS_Interface|PS_Export|PS_Local))==0 && proto_static==0 ){ /* Ignore typedefs that are not with "#if INTERFACE..#endif" unless ** the "-local" command line option is used. */ return nErr; } if( (flags & (PS_Interface|PS_Export))==0 ){ /* typedefs are always local, unless within #if INTERFACE..#endif */ isLocal = 1; } }else if( flags & (PS_Static|PS_Local2) ){ if( proto_static==0 && (flags & PS_Local2)==0 ){ /* Don't record static variables unless the "-local" command line ** option was specified or the "LOCAL" keyword is used. */ return nErr; } while( pFirst!=0 && pFirst->pNext!=pEnd && ((pFirst->nText==6 && strncmp(pFirst->zText,"static",6)==0) || (pFirst->nText==5 && strncmp(pFirst->zText,"LOCAL",6)==0)) ){ /* Lose the initial "static" or local from local variables. ** We'll prepend "extern" later. */ pFirst = pFirst->pNext; isLocal = 1; } if( pFirst==0 || !isLocal ){ return nErr; } }else if( flags & PS_Method ){ /* Methods are declared by their class. Don't declare separately. */ return nErr; } isVar = (flags & (PS_Typedef|PS_Method))==0 && isVariableDef(pFirst,pEnd); if( isVar && (flags & (PS_Interface|PS_Export|PS_Local))!=0 && (flags & PS_Extern)==0 ){ fprintf(stderr,"%s:%d: Can't define a variable in this context\n", zFilename, pFirst->nLine); nErr++; } pName = FindDeclName(pFirst,pEnd->pPrev); if( pName==0 ){ fprintf(stderr,"%s:%d: Can't find a name for the object declared here.\n", zFilename, pFirst->nLine); return nErr+1; } #ifdef DEBUG if( debugMask & PARSER ){ if( flags & PS_Typedef ){ printf("**** Found typedef %.*s at line %d...\n", pName->nText, pName->zText, pName->nLine); }else if( isVar ){ printf("**** Found variable %.*s at line %d...\n", pName->nText, pName->zText, pName->nLine); }else{ printf("**** Found prototype %.*s at line %d...\n", pName->nText, pName->zText, pName->nLine); } PrintTokens(pFirst,pEnd->pPrev); printf(";\n"); } #endif pDecl = CreateDecl(pName->zText,pName->nText); if( (flags & PS_Typedef) ){ DeclSetProperty(pDecl, TY_Typedef); }else if( isVar ){ DeclSetProperty(pDecl,DP_ExternReqd | TY_Variable); if( !(flags & DP_Cplusplus) ){ DeclSetProperty(pDecl,DP_ExternCReqd); } }else{ DeclSetProperty(pDecl, TY_Subroutine); if( !(flags & DP_Cplusplus) ){ DeclSetProperty(pDecl,DP_ExternCReqd); } } pDecl->pComment = pFirst->pComment; pDecl->zDecl = TokensToString(pFirst,pEnd->pPrev,";\n",0,0); if( isLocal || (flags & (PS_Local|PS_Local2))!=0 ){ DeclSetProperty(pDecl,DP_Local); }else if( flags & (PS_Export|PS_Export2) ){ DeclSetProperty(pDecl,DP_Export); } if( flags & DP_Cplusplus ){ DeclSetProperty(pDecl,DP_Cplusplus); } return nErr; } /* ** Push an if condition onto the if stack */ static void PushIfMacro( const char *zPrefix, /* A prefix, like "define" or "!" */ const char *zText, /* The condition */ int nText, /* Number of characters in zText */ int nLine, /* Line number where this macro occurs */ int flags /* Either 0, PS_Interface, PS_Export or PS_Local */ ){ Ifmacro *pIf; int nByte; nByte = sizeof(Ifmacro); if( zText ){ if( zPrefix ){ nByte += strlen(zPrefix) + 2; } nByte += nText + 1; } pIf = SafeMalloc( nByte ); if( zText ){ pIf->zCondition = (char*)&pIf[1]; if( zPrefix ){ sprintf(pIf->zCondition,"%s(%.*s)",zPrefix,nText,zText); }else{ sprintf(pIf->zCondition,"%.*s",nText,zText); } }else{ pIf->zCondition = 0; } pIf->nLine = nLine; pIf->flags = flags; pIf->pNext = ifStack; ifStack = pIf; } /* ** This routine is called to handle all preprocessor directives. ** ** This routine will recompute the value of *pPresetFlags to be the ** logical or of all flags on all nested #ifs. The #ifs that set flags ** are as follows: ** ** conditional flag set ** ------------------------ -------------------- ** #if INTERFACE PS_Interface ** #if EXPORT_INTERFACE PS_Export ** #if LOCAL_INTERFACE PS_Local ** ** For example, if after processing the preprocessor token given ** by pToken there is an "#if INTERFACE" on the preprocessor ** stack, then *pPresetFlags will be set to PS_Interface. */ static int ParsePreprocessor(Token *pToken, int flags, int *pPresetFlags){ const char *zCmd; int nCmd; const char *zArg; int nArg; int nErr = 0; Ifmacro *pIf; zCmd = &pToken->zText[1]; while( isspace(*zCmd) && *zCmd!='\n' ){ zCmd++; } if( !isalpha(*zCmd) ){ return 0; } nCmd = 1; while( isalpha(zCmd[nCmd]) ){ nCmd++; } if( nCmd==5 && strncmp(zCmd,"endif",5)==0 ){ /* ** Pop the if stack */ pIf = ifStack; if( pIf==0 ){ fprintf(stderr,"%s:%d: extra '#endif'.\n",zFilename,pToken->nLine); return 1; } ifStack = pIf->pNext; SafeFree(pIf); }else if( nCmd==6 && strncmp(zCmd,"define",6)==0 ){ /* ** Record a #define if we are in PS_Interface or PS_Export */ Decl *pDecl; if( !(flags & (PS_Local|PS_Interface|PS_Export)) ){ return 0; } zArg = &zCmd[6]; while( *zArg && isspace(*zArg) && *zArg!='\n' ){ zArg++; } if( *zArg==0 || *zArg=='\n' ){ return 0; } for(nArg=0; ISALNUM(zArg[nArg]); nArg++){} if( nArg==0 ){ return 0; } pDecl = CreateDecl(zArg,nArg); pDecl->pComment = pToken->pComment; DeclSetProperty(pDecl,TY_Macro); pDecl->zDecl = SafeMalloc( pToken->nText + 2 ); sprintf(pDecl->zDecl,"%.*s\n",pToken->nText,pToken->zText); if( flags & PS_Export ){ DeclSetProperty(pDecl,DP_Export); }else if( flags & PS_Local ){ DeclSetProperty(pDecl,DP_Local); } }else if( nCmd==7 && strncmp(zCmd,"include",7)==0 ){ /* ** Record an #include if we are in PS_Interface or PS_Export */ Include *pInclude; char *zIf; if( !(flags & (PS_Interface|PS_Export)) ){ return 0; } zArg = &zCmd[7]; while( *zArg && isspace(*zArg) ){ zArg++; } for(nArg=0; !isspace(zArg[nArg]); nArg++){} if( (zArg[0]=='"' && zArg[nArg-1]!='"') ||(zArg[0]=='<' && zArg[nArg-1]!='>') ){ fprintf(stderr,"%s:%d: malformed #include statement.\n", zFilename,pToken->nLine); return 1; } zIf = GetIfString(); if( zIf ){ pInclude = SafeMalloc( sizeof(Include) + nArg*2 + strlen(zIf) + 10 ); pInclude->zFile = (char*)&pInclude[1]; pInclude->zLabel = &pInclude->zFile[nArg+1]; sprintf(pInclude->zFile,"%.*s",nArg,zArg); sprintf(pInclude->zLabel,"%.*s:%s",nArg,zArg,zIf); pInclude->zIf = &pInclude->zLabel[nArg+1]; SafeFree(zIf); }else{ pInclude = SafeMalloc( sizeof(Include) + nArg + 1 ); pInclude->zFile = (char*)&pInclude[1]; sprintf(pInclude->zFile,"%.*s",nArg,zArg); pInclude->zIf = 0; pInclude->zLabel = pInclude->zFile; } pInclude->pNext = includeList; includeList = pInclude; }else if( nCmd==2 && strncmp(zCmd,"if",2)==0 ){ /* ** Push an #if. Watch for the special cases of INTERFACE ** and EXPORT_INTERFACE and LOCAL_INTERFACE */ zArg = &zCmd[2]; while( *zArg && isspace(*zArg) && *zArg!='\n' ){ zArg++; } if( *zArg==0 || *zArg=='\n' ){ return 0; } nArg = pToken->nText + (int)(pToken->zText - zArg); if( nArg==9 && strncmp(zArg,"INTERFACE",9)==0 ){ PushIfMacro(0,0,0,pToken->nLine,PS_Interface); }else if( nArg==16 && strncmp(zArg,"EXPORT_INTERFACE",16)==0 ){ PushIfMacro(0,0,0,pToken->nLine,PS_Export); }else if( nArg==15 && strncmp(zArg,"LOCAL_INTERFACE",15)==0 ){ PushIfMacro(0,0,0,pToken->nLine,PS_Local); }else{ PushIfMacro(0,zArg,nArg,pToken->nLine,0); } }else if( nCmd==5 && strncmp(zCmd,"ifdef",5)==0 ){ /* ** Push an #ifdef. */ zArg = &zCmd[5]; while( *zArg && isspace(*zArg) && *zArg!='\n' ){ zArg++; } if( *zArg==0 || *zArg=='\n' ){ return 0; } nArg = pToken->nText + (int)(pToken->zText - zArg); PushIfMacro("defined",zArg,nArg,pToken->nLine,0); }else if( nCmd==6 && strncmp(zCmd,"ifndef",6)==0 ){ /* ** Push an #ifndef. */ zArg = &zCmd[6]; while( *zArg && isspace(*zArg) && *zArg!='\n' ){ zArg++; } if( *zArg==0 || *zArg=='\n' ){ return 0; } nArg = pToken->nText + (int)(pToken->zText - zArg); PushIfMacro("!defined",zArg,nArg,pToken->nLine,0); }else if( nCmd==4 && strncmp(zCmd,"else",4)==0 ){ /* ** Invert the #if on the top of the stack */ if( ifStack==0 ){ fprintf(stderr,"%s:%d: '#else' without an '#if'\n",zFilename, pToken->nLine); return 1; } pIf = ifStack; if( pIf->zCondition ){ ifStack = ifStack->pNext; PushIfMacro("!",pIf->zCondition,strlen(pIf->zCondition),pIf->nLine,0); SafeFree(pIf); }else{ pIf->flags = 0; } }else{ /* ** This directive can be safely ignored */ return 0; } /* ** Recompute the preset flags */ *pPresetFlags = 0; for(pIf = ifStack; pIf; pIf=pIf->pNext){ *pPresetFlags |= pIf->flags; } return nErr; } /* ** Parse an entire file. Return the number of errors. ** ** pList is a list of tokens in the file. Whitespace tokens have been ** eliminated, and text with {...} has been collapsed into a ** single TT_Brace token. ** ** initFlags are a set of parse flags that should always be set for this ** file. For .c files this is normally 0. For .h files it is PS_Interface. */ static int ParseFile(Token *pList, int initFlags){ int nErr = 0; Token *pStart = 0; int flags = initFlags; int presetFlags = initFlags; int resetFlag = 0; includeList = 0; while( pList ){ switch( pList->eType ){ case TT_EOF: goto end_of_loop; case TT_Preprocessor: nErr += ParsePreprocessor(pList,flags,&presetFlags); pStart = 0; presetFlags |= initFlags; flags = presetFlags; break; case TT_Other: switch( pList->zText[0] ){ case ';': nErr += ProcessDecl(pStart,pList,flags); pStart = 0; flags = presetFlags; break; case '=': if( pList->pPrev->nText==8 && strncmp(pList->pPrev->zText,"operator",8)==0 ){ break; } nErr += ProcessDecl(pStart,pList,flags); pStart = 0; while( pList && pList->zText[0]!=';' ){ pList = pList->pNext; } if( pList==0 ) goto end_of_loop; flags = presetFlags; break; case ':': if( pList->zText[1]==':' ){ flags |= PS_Method; } break; default: break; } break; case TT_Braces: nErr += ProcessProcedureDef(pStart,pList,flags); pStart = 0; flags = presetFlags; break; case TT_Id: if( pStart==0 ){ pStart = pList; flags = presetFlags; } resetFlag = 0; switch( pList->zText[0] ){ case 'c': if( pList->nText==5 && strncmp(pList->zText,"class",5)==0 ){ nErr += ProcessTypeDecl(pList,flags,&resetFlag); } break; case 'E': if( pList->nText==6 && strncmp(pList->zText,"EXPORT",6)==0 ){ flags |= PS_Export2; /* pStart = 0; */ } break; case 'e': if( pList->nText==4 && strncmp(pList->zText,"enum",4)==0 ){ if( pList->pNext && pList->pNext->eType==TT_Braces ){ pList = pList->pNext; }else{ nErr += ProcessTypeDecl(pList,flags,&resetFlag); } }else if( pList->nText==6 && strncmp(pList->zText,"extern",6)==0 ){ pList = pList->pNext; if( pList && pList->nText==3 && strncmp(pList->zText,"\"C\"",3)==0 ){ pList = pList->pNext; flags &= ~DP_Cplusplus; }else{ flags |= PS_Extern; } pStart = pList; } break; case 'i': if( pList->nText==6 && strncmp(pList->zText,"inline",6)==0 ){ nErr += ProcessInlineProc(pList,flags,&resetFlag); } break; case 'L': if( pList->nText==5 && strncmp(pList->zText,"LOCAL",5)==0 ){ flags |= PS_Local2; pStart = pList; } break; case 'P': if( pList->nText==6 && strncmp(pList->zText, "PUBLIC",6)==0 ){ flags |= PS_Public; pStart = pList; }else if( pList->nText==7 && strncmp(pList->zText, "PRIVATE",7)==0 ){ flags |= PS_Private; pStart = pList; }else if( pList->nText==9 && strncmp(pList->zText,"PROTECTED",9)==0 ){ flags |= PS_Protected; pStart = pList; } break; case 's': if( pList->nText==6 && strncmp(pList->zText,"struct",6)==0 ){ if( pList->pNext && pList->pNext->eType==TT_Braces ){ pList = pList->pNext; }else{ nErr += ProcessTypeDecl(pList,flags,&resetFlag); } }else if( pList->nText==6 && strncmp(pList->zText,"static",6)==0 ){ flags |= PS_Static; } break; case 't': if( pList->nText==7 && strncmp(pList->zText,"typedef",7)==0 ){ flags |= PS_Typedef; } break; case 'u': if( pList->nText==5 && strncmp(pList->zText,"union",5)==0 ){ if( pList->pNext && pList->pNext->eType==TT_Braces ){ pList = pList->pNext; }else{ nErr += ProcessTypeDecl(pList,flags,&resetFlag); } } break; default: break; } if( resetFlag!=0 ){ while( pList && pList->zText[0]!=resetFlag ){ pList = pList->pNext; } if( pList==0 ) goto end_of_loop; pStart = 0; flags = presetFlags; } break; case TT_String: case TT_Number: break; default: pStart = pList; flags = presetFlags; break; } pList = pList->pNext; } end_of_loop: /* Verify that all #ifs have a matching "#endif" */ while( ifStack ){ Ifmacro *pIf = ifStack; ifStack = pIf->pNext; fprintf(stderr,"%s:%d: This '#if' has no '#endif'\n",zFilename, pIf->nLine); SafeFree(pIf); } return nErr; } /* ** If the given Decl object has a non-null zExtra field, then the text ** of that zExtra field needs to be inserted in the middle of the ** zDecl field before the last "}" in the zDecl. This routine does that. ** If the zExtra is NULL, this routine is a no-op. ** ** zExtra holds extra method declarations for classes. The declarations ** have to be inserted into the class definition. */ static void InsertExtraDecl(Decl *pDecl){ int i; String str; if( pDecl==0 || pDecl->zExtra==0 || pDecl->zDecl==0 ) return; i = strlen(pDecl->zDecl) - 1; while( i>0 && pDecl->zDecl[i]!='}' ){ i--; } StringInit(&str); StringAppend(&str, pDecl->zDecl, i); StringAppend(&str, pDecl->zExtra, 0); StringAppend(&str, &pDecl->zDecl[i], 0); SafeFree(pDecl->zDecl); SafeFree(pDecl->zExtra); pDecl->zDecl = StrDup(StringGet(&str), 0); StringReset(&str); pDecl->zExtra = 0; } /* ** Reset the DP_Forward and DP_Declared flags on all Decl structures. ** Set both flags for anything that is tagged as local and isn't ** in the file zFilename so that it won't be printing in other files. */ static void ResetDeclFlags(char *zFilename){ Decl *pDecl; for(pDecl = pDeclFirst; pDecl; pDecl = pDecl->pNext){ DeclClearProperty(pDecl,DP_Forward|DP_Declared); if( DeclHasProperty(pDecl,DP_Local) && pDecl->zFile!=zFilename ){ DeclSetProperty(pDecl,DP_Forward|DP_Declared); } } } /* ** Forward declaration of the ScanText() function. */ static void ScanText(const char*, GenState *pState); /* ** The output in pStr is currently within an #if CONTEXT where context ** is equal to *pzIf. (*pzIf might be NULL to indicate that we are ** not within any #if at the moment.) We are getting ready to output ** some text that needs to be within the context of "#if NEW" where ** NEW is zIf. Make an appropriate change to the context. */ static void ChangeIfContext( const char *zIf, /* The desired #if context */ GenState *pState /* Current state of the code generator */ ){ if( zIf==0 ){ if( pState->zIf==0 ) return; StringAppend(pState->pStr,"#endif\n",0); pState->zIf = 0; }else{ if( pState->zIf ){ if( strcmp(zIf,pState->zIf)==0 ) return; StringAppend(pState->pStr,"#endif\n",0); pState->zIf = 0; } ScanText(zIf, pState); if( pState->zIf!=0 ){ StringAppend(pState->pStr,"#endif\n",0); } StringAppend(pState->pStr,"#if ",0); StringAppend(pState->pStr,zIf,0); StringAppend(pState->pStr,"\n",0); pState->zIf = zIf; } } /* ** Add to the string pStr a #include of every file on the list of ** include files pInclude. The table pTable contains all files that ** have already been #included at least once. Don't add any ** duplicates. Update pTable with every new #include that is added. */ static void AddIncludes( Include *pInclude, /* Write every #include on this list */ GenState *pState /* Current state of the code generator */ ){ if( pInclude ){ if( pInclude->pNext ){ AddIncludes(pInclude->pNext,pState); } if( IdentTableInsert(pState->pTable,pInclude->zLabel,0) ){ ChangeIfContext(pInclude->zIf,pState); StringAppend(pState->pStr,"#include ",0); StringAppend(pState->pStr,pInclude->zFile,0); StringAppend(pState->pStr,"\n",1); } } } /* ** Add to the string pStr a declaration for the object described ** in pDecl. ** ** If pDecl has already been declared in this file, detect that ** fact and abort early. Do not duplicate a declaration. ** ** If the needFullDecl flag is false and this object has a forward ** declaration, then supply the forward declaration only. A later ** call to CompleteForwardDeclarations() will finish the declaration ** for us. But if needFullDecl is true, we must supply the full ** declaration now. Some objects do not have a forward declaration. ** For those objects, we must print the full declaration now. ** ** Because it is illegal to duplicate a typedef in C, care is taken ** to insure that typedefs for the same identifier are only issued once. */ static void DeclareObject( Decl *pDecl, /* The thing to be declared */ GenState *pState, /* Current state of the code generator */ int needFullDecl /* Must have the full declaration. A forward * declaration isn't enough */ ){ Decl *p; /* The object to be declared */ int flag; int isCpp; /* True if generating C++ */ int doneTypedef = 0; /* True if a typedef has been done for this object */ /* printf("BEGIN %s of %s\n",needFullDecl?"FULL":"PROTOTYPE",pDecl->zName);*/ /* ** For any object that has a forward declaration, go ahead and do the ** forward declaration first. */ isCpp = (pState->flags & DP_Cplusplus) != 0; for(p=pDecl; p; p=p->pSameName){ if( p->zFwd ){ if( !DeclHasProperty(p,DP_Forward) ){ DeclSetProperty(p,DP_Forward); if( strncmp(p->zFwd,"typedef",7)==0 ){ if( doneTypedef ) continue; doneTypedef = 1; } ChangeIfContext(p->zIf,pState); StringAppend(pState->pStr,isCpp ? p->zFwdCpp : p->zFwd,0); } } } /* ** Early out if everything is already suitably declared. ** ** This is a very important step because it prevents us from ** executing the code the follows in a recursive call to this ** function with the same value for pDecl. */ flag = needFullDecl ? DP_Declared|DP_Forward : DP_Forward; for(p=pDecl; p; p=p->pSameName){ if( !DeclHasProperty(p,flag) ) break; } if( p==0 ){ return; } /* ** Make sure we have all necessary #includes */ for(p=pDecl; p; p=p->pSameName){ AddIncludes(p->pInclude,pState); } /* ** Go ahead an mark everything as being declared, to prevent an ** infinite loop thru the ScanText() function. At the same time, ** we decide which objects need a full declaration and mark them ** with the DP_Flag bit. We are only able to use DP_Flag in this ** way because we know we'll never execute this far into this ** function on a recursive call with the same pDecl. Hence, recursive ** calls to this function (through ScanText()) can never change the ** value of DP_Flag out from under us. */ for(p=pDecl; p; p=p->pSameName){ if( !DeclHasProperty(p,DP_Declared) && (p->zFwd==0 || needFullDecl) && p->zDecl!=0 ){ DeclSetProperty(p,DP_Forward|DP_Declared|DP_Flag); }else{ DeclClearProperty(p,DP_Flag); } } /* ** Call ScanText() recusively (this routine is called from ScanText()) ** to include declarations required to come before these declarations. */ for(p=pDecl; p; p=p->pSameName){ if( DeclHasProperty(p,DP_Flag) ){ if( p->zDecl[0]=='#' ){ ScanText(&p->zDecl[1],pState); }else{ InsertExtraDecl(p); ScanText(p->zDecl,pState); } } } /* ** Output the declarations. Do this in two passes. First ** output everything that isn't a typedef. Then go back and ** get the typedefs by the same name. */ for(p=pDecl; p; p=p->pSameName){ if( DeclHasProperty(p,DP_Flag) && !DeclHasProperty(p,TY_Typedef) ){ if( DeclHasAnyProperty(p,TY_Enumeration) ){ if( doneTypedef ) continue; doneTypedef = 1; } ChangeIfContext(p->zIf,pState); if( !isCpp && DeclHasAnyProperty(p,DP_ExternReqd) ){ StringAppend(pState->pStr,"extern ",0); }else if( isCpp && DeclHasProperty(p,DP_Cplusplus|DP_ExternReqd) ){ StringAppend(pState->pStr,"extern ",0); }else if( isCpp && DeclHasAnyProperty(p,DP_ExternCReqd|DP_ExternReqd) ){ StringAppend(pState->pStr,"extern \"C\" ",0); } InsertExtraDecl(p); StringAppend(pState->pStr,p->zDecl,0); if( !isCpp && DeclHasProperty(p,DP_Cplusplus) ){ fprintf(stderr, "%s: C code ought not reference the C++ object \"%s\"\n", pState->zFilename, p->zName); pState->nErr++; } DeclClearProperty(p,DP_Flag); } } for(p=pDecl; p && !doneTypedef; p=p->pSameName){ if( DeclHasProperty(p,DP_Flag) ){ /* This has to be a typedef */ doneTypedef = 1; ChangeIfContext(p->zIf,pState); InsertExtraDecl(p); StringAppend(pState->pStr,p->zDecl,0); } } } /* ** This routine scans the input text given, and appends to the ** string in pState->pStr the text of any declarations that must ** occur before the text in zText. ** ** If an identifier in zText is immediately followed by '*', then ** only forward declarations are needed for that identifier. If the ** identifier name is not followed immediately by '*', we must supply ** a full declaration. */ static void ScanText( const char *zText, /* The input text to be scanned */ GenState *pState /* Current state of the code generator */ ){ int nextValid = 0; /* True is sNext contains valid data */ InStream sIn; /* The input text */ Token sToken; /* The current token being examined */ Token sNext; /* The next non-space token */ /* printf("BEGIN SCAN TEXT on %s\n", zText); */ sIn.z = zText; sIn.i = 0; sIn.nLine = 1; while( sIn.z[sIn.i]!=0 ){ if( nextValid ){ sToken = sNext; nextValid = 0; }else{ GetNonspaceToken(&sIn,&sToken); } if( sToken.eType==TT_Id ){ int needFullDecl; /* True if we need to provide the full declaration, ** not just the forward declaration */ Decl *pDecl; /* The declaration having the name in sToken */ /* ** See if there is a declaration in the database with the name given ** by sToken. */ pDecl = FindDecl(sToken.zText,sToken.nText); if( pDecl==0 ) continue; /* ** If we get this far, we've found an identifier that has a ** declaration in the database. Now see if we the full declaration ** or just a forward declaration. */ GetNonspaceToken(&sIn,&sNext); if( sNext.zText[0]=='*' ){ needFullDecl = 0; }else{ needFullDecl = 1; nextValid = sNext.eType==TT_Id; } /* ** Generate the needed declaration. */ DeclareObject(pDecl,pState,needFullDecl); }else if( sToken.eType==TT_Preprocessor ){ sIn.i -= sToken.nText - 1; } } /* printf("END SCANTEXT\n"); */ } /* ** Provide a full declaration to any object which so far has had only ** a foward declaration. */ static void CompleteForwardDeclarations(GenState *pState){ Decl *pDecl; int progress; do{ progress = 0; for(pDecl=pDeclFirst; pDecl; pDecl=pDecl->pNext){ if( DeclHasProperty(pDecl,DP_Forward) && !DeclHasProperty(pDecl,DP_Declared) ){ DeclareObject(pDecl,pState,1); progress = 1; assert( DeclHasProperty(pDecl,DP_Declared) ); } } }while( progress ); } /* ** Generate an include file for the given source file. Return the number ** of errors encountered. ** ** if nolocal_flag is true, then we do not generate declarations for ** objected marked DP_Local. */ static int MakeHeader(InFile *pFile, FILE *report, int nolocal_flag){ int nErr = 0; GenState sState; String outStr; IdentTable includeTable; Ident *pId; char *zNewVersion; char *zOldVersion; if( pFile->zHdr==0 || *pFile->zHdr==0 ) return 0; sState.pStr = &outStr; StringInit(&outStr); StringAppend(&outStr,zTopLine,nTopLine); sState.pTable = &includeTable; memset(&includeTable,0,sizeof(includeTable)); sState.zIf = 0; sState.nErr = 0; sState.zFilename = pFile->zSrc; sState.flags = pFile->flags & DP_Cplusplus; ResetDeclFlags(nolocal_flag ? "no" : pFile->zSrc); for(pId = pFile->idTable.pList; pId; pId=pId->pNext){ Decl *pDecl = FindDecl(pId->zName,0); if( pDecl ){ DeclareObject(pDecl,&sState,1); } } CompleteForwardDeclarations(&sState); ChangeIfContext(0,&sState); nErr += sState.nErr; zOldVersion = ReadFile(pFile->zHdr); zNewVersion = StringGet(&outStr); if( report ) fprintf(report,"%s: ",pFile->zHdr); if( zOldVersion==0 ){ if( report ) fprintf(report,"updated\n"); if( WriteFile(pFile->zHdr,zNewVersion) ){ fprintf(stderr,"%s: Can't write to file\n",pFile->zHdr); nErr++; } }else if( strncmp(zOldVersion,zTopLine,nTopLine)!=0 ){ if( report ) fprintf(report,"error!\n"); fprintf(stderr, "%s: Can't overwrite this file because it wasn't previously\n" "%*s generated by 'makeheaders'.\n", pFile->zHdr, (int)strlen(pFile->zHdr), ""); nErr++; }else if( strcmp(zOldVersion,zNewVersion)!=0 ){ if( report ) fprintf(report,"updated\n"); if( WriteFile(pFile->zHdr,zNewVersion) ){ fprintf(stderr,"%s: Can't write to file\n",pFile->zHdr); nErr++; } }else if( report ){ fprintf(report,"unchanged\n"); } SafeFree(zOldVersion); IdentTableReset(&includeTable); StringReset(&outStr); return nErr; } /* ** Generate a global header file -- a header file that contains all ** declarations. If the forExport flag is true, then only those ** objects that are exported are included in the header file. */ static int MakeGlobalHeader(int forExport){ GenState sState; String outStr; IdentTable includeTable; Decl *pDecl; sState.pStr = &outStr; StringInit(&outStr); /* StringAppend(&outStr,zTopLine,nTopLine); */ sState.pTable = &includeTable; memset(&includeTable,0,sizeof(includeTable)); sState.zIf = 0; sState.nErr = 0; sState.zFilename = "(all)"; sState.flags = 0; ResetDeclFlags(0); for(pDecl=pDeclFirst; pDecl; pDecl=pDecl->pNext){ if( forExport==0 || DeclHasProperty(pDecl,DP_Export) ){ DeclareObject(pDecl,&sState,1); } } ChangeIfContext(0,&sState); printf("%s",StringGet(&outStr)); IdentTableReset(&includeTable); StringReset(&outStr); return 0; } #ifdef DEBUG /* ** Return the number of characters in the given string prior to the ** first newline. */ static int ClipTrailingNewline(char *z){ int n = strlen(z); while( n>0 && (z[n-1]=='\n' || z[n-1]=='\r') ){ n--; } return n; } /* ** Dump the entire declaration list for debugging purposes */ static void DumpDeclList(void){ Decl *pDecl; for(pDecl = pDeclFirst; pDecl; pDecl=pDecl->pNext){ printf("**** %s from file %s ****\n",pDecl->zName,pDecl->zFile); if( pDecl->zIf ){ printf("If: [%.*s]\n",ClipTrailingNewline(pDecl->zIf),pDecl->zIf); } if( pDecl->zFwd ){ printf("Decl: [%.*s]\n",ClipTrailingNewline(pDecl->zFwd),pDecl->zFwd); } if( pDecl->zDecl ){ InsertExtraDecl(pDecl); printf("Def: [%.*s]\n",ClipTrailingNewline(pDecl->zDecl),pDecl->zDecl); } if( pDecl->flags ){ static struct { int mask; char *desc; } flagSet[] = { { TY_Class, "class" }, { TY_Enumeration, "enum" }, { TY_Structure, "struct" }, { TY_Union, "union" }, { TY_Variable, "variable" }, { TY_Subroutine, "function" }, { TY_Typedef, "typedef" }, { TY_Macro, "macro" }, { DP_Export, "export" }, { DP_Local, "local" }, { DP_Cplusplus, "C++" }, }; int i; printf("flags:"); for(i=0; iflags ){ printf(" %s", flagSet[i].desc); } } printf("\n"); } if( pDecl->pInclude ){ Include *p; printf("includes:"); for(p=pDecl->pInclude; p; p=p->pNext){ printf(" %s",p->zFile); } printf("\n"); } } } #endif /* ** When the "-doc" command-line option is used, this routine is called ** to print all of the database information to standard output. */ static void DocumentationDump(void){ Decl *pDecl; static struct { int mask; char flag; } flagSet[] = { { TY_Class, 'c' }, { TY_Enumeration, 'e' }, { TY_Structure, 's' }, { TY_Union, 'u' }, { TY_Variable, 'v' }, { TY_Subroutine, 'f' }, { TY_Typedef, 't' }, { TY_Macro, 'm' }, { DP_Export, 'x' }, { DP_Local, 'l' }, { DP_Cplusplus, '+' }, }; for(pDecl = pDeclFirst; pDecl; pDecl=pDecl->pNext){ int i; int nLabel = 0; char *zDecl; char zLabel[50]; for(i=0; izDecl; if( zDecl==0 ) zDecl = pDecl->zFwd; printf("%s %s %s %p %d %d %d %d %d\n", pDecl->zName, zLabel, pDecl->zFile, pDecl->pComment, pDecl->pComment ? pDecl->pComment->nText+1 : 0, pDecl->zIf ? (int)strlen(pDecl->zIf)+1 : 0, zDecl ? (int)strlen(zDecl) : 0, pDecl->pComment ? pDecl->pComment->nLine : 0, pDecl->tokenCode.nText ? pDecl->tokenCode.nText+1 : 0 ); if( pDecl->pComment ){ printf("%.*s\n",pDecl->pComment->nText, pDecl->pComment->zText); } if( pDecl->zIf ){ printf("%s\n",pDecl->zIf); } if( zDecl ){ printf("%s",zDecl); } if( pDecl->tokenCode.nText ){ printf("%.*s\n",pDecl->tokenCode.nText, pDecl->tokenCode.zText); } } } /* ** Given the complete text of an input file, this routine prints a ** documentation record for the header comment at the beginning of the ** file (if the file has a header comment.) */ void PrintModuleRecord(const char *zFile, const char *zFilename){ int i; static int addr = 5; while( isspace(*zFile) ){ zFile++; } if( *zFile!='/' || zFile[1]!='*' ) return; for(i=2; zFile[i] && (zFile[i-1]!='/' || zFile[i-2]!='*'); i++){} if( zFile[i]==0 ) return; printf("%s M %s %d %d 0 0 0 0\n%.*s\n", zFilename, zFilename, addr, i+1, i, zFile); addr += 4; } /* ** Given an input argument to the program, construct a new InFile ** object. */ static InFile *CreateInFile(char *zArg, int *pnErr){ int nSrc; char *zSrc; InFile *pFile; int i; /* ** Get the name of the input file to be scanned. The input file is ** everything before the first ':' or the whole file if no ':' is seen. ** ** Except, on windows, ignore any ':' that occurs as the second character ** since it might be part of the drive specifier. So really, the ":' has ** to be the 3rd or later character in the name. This precludes 1-character ** file names, which really should not be a problem. */ zSrc = zArg; for(nSrc=2; zSrc[nSrc] && zArg[nSrc]!=':'; nSrc++){} pFile = SafeMalloc( sizeof(InFile) ); memset(pFile,0,sizeof(InFile)); pFile->zSrc = StrDup(zSrc,nSrc); /* Figure out if we are dealing with C or C++ code. Assume any ** file with ".c" or ".h" is C code and all else is C++. */ if( nSrc>2 && zSrc[nSrc-2]=='.' && (zSrc[nSrc-1]=='c' || zSrc[nSrc-1]=='h')){ pFile->flags &= ~DP_Cplusplus; }else{ pFile->flags |= DP_Cplusplus; } /* ** If a separate header file is specified, use it */ if( zSrc[nSrc]==':' ){ int nHdr; char *zHdr; zHdr = &zSrc[nSrc+1]; for(nHdr=0; zHdr[nHdr] && zHdr[nHdr]!=':'; nHdr++){} pFile->zHdr = StrDup(zHdr,nHdr); } /* Look for any 'c' or 'C' in the suffix of the file name and change ** that character to 'h' or 'H' respectively. If no 'c' or 'C' is found, ** then assume we are dealing with a header. */ else{ int foundC = 0; pFile->zHdr = StrDup(zSrc,nSrc); for(i = nSrc-1; i>0 && pFile->zHdr[i]!='.'; i--){ if( pFile->zHdr[i]=='c' ){ foundC = 1; pFile->zHdr[i] = 'h'; }else if( pFile->zHdr[i]=='C' ){ foundC = 1; pFile->zHdr[i] = 'H'; } } if( !foundC ){ SafeFree(pFile->zHdr); pFile->zHdr = 0; } } /* ** If pFile->zSrc contains no 'c' or 'C' in its extension, it ** must be a header file. In that case, we need to set the ** PS_Interface flag. */ pFile->flags |= PS_Interface; for(i=nSrc-1; i>0 && zSrc[i]!='.'; i--){ if( zSrc[i]=='c' || zSrc[i]=='C' ){ pFile->flags &= ~PS_Interface; break; } } /* Done! */ return pFile; } /* MS-Windows and MS-DOS both have the following serious OS bug: the ** length of a command line is severely restricted. But this program ** occasionally requires long command lines. Hence the following ** work around. ** ** If the parameters "-f FILENAME" appear anywhere on the command line, ** then the named file is scanned for additional command line arguments. ** These arguments are substituted in place of the "FILENAME" argument ** in the original argument list. ** ** This first parameter to this routine is the index of the "-f" ** parameter in the argv[] array. The argc and argv are passed by ** pointer so that they can be changed. ** ** Parsing of the parameters in the file is very simple. Parameters ** can be separated by any amount of white-space (including newlines ** and carriage returns.) There are now quoting characters of any ** kind. The length of a token is limited to about 1000 characters. */ static void AddParameters(int index, int *pArgc, char ***pArgv){ int argc = *pArgc; /* The original argc value */ char **argv = *pArgv; /* The original argv value */ int newArgc; /* Value for argc after inserting new arguments */ char **zNew = 0; /* The new argv after this routine is done */ char *zFile; /* Name of the input file */ int nNew = 0; /* Number of new entries in the argv[] file */ int nAlloc = 0; /* Space allocated for zNew[] */ int i; /* Loop counter */ int n; /* Number of characters in a new argument */ int c; /* Next character of input */ int startOfLine = 1; /* True if we are where '#' can start a comment */ FILE *in; /* The input file */ char zBuf[1000]; /* A single argument is accumulated here */ if( index+1==argc ) return; zFile = argv[index+1]; in = fopen(zFile,"r"); if( in==0 ){ fprintf(stderr,"Can't open input file \"%s\"\n",zFile); exit(1); } c = ' '; while( c!=EOF ){ while( c!=EOF && isspace(c) ){ if( c=='\n' ){ startOfLine = 1; } c = getc(in); if( startOfLine && c=='#' ){ while( c!=EOF && c!='\n' ){ c = getc(in); } } } n = 0; while( c!=EOF && !isspace(c) ){ if( n0 ){ nNew++; if( nNew + argc > nAlloc ){ if( nAlloc==0 ){ nAlloc = 100 + argc; zNew = malloc( sizeof(char*) * nAlloc ); }else{ nAlloc *= 2; zNew = realloc( zNew, sizeof(char*) * nAlloc ); } } if( zNew ){ int j = nNew + index; zNew[j] = malloc( n + 1 ); if( zNew[j] ){ strcpy( zNew[j], zBuf ); } } } } newArgc = argc + nNew - 1; for(i=0; i<=index; i++){ zNew[i] = argv[i]; } for(i=nNew + index + 1; ipNext = pFile; pTail = pFile; }else{ pFileList = pTail = pFile; } } } } if( h_flag && H_flag ){ h_flag = 0; } if( v_flag ){ report = (h_flag || H_flag) ? stderr : stdout; }else{ report = 0; } if( nErr>0 ){ return nErr; } for(pFile=pFileList; pFile; pFile=pFile->pNext){ char *zFile; zFilename = pFile->zSrc; if( zFilename==0 ) continue; zFile = ReadFile(zFilename); if( zFile==0 ){ fprintf(stderr,"Can't read input file \"%s\"\n",zFilename); nErr++; continue; } if( strncmp(zFile,zTopLine,nTopLine)==0 ){ pFile->zSrc = 0; }else{ if( report ) fprintf(report,"Reading %s...\n",zFilename); pList = TokenizeFile(zFile,&pFile->idTable); if( pList ){ nErr += ParseFile(pList,pFile->flags); FreeTokenList(pList); }else if( zFile[0]==0 ){ fprintf(stderr,"Input file \"%s\" is empty.\n", zFilename); nErr++; }else{ fprintf(stderr,"Errors while processing \"%s\"\n", zFilename); nErr++; } } if( !doc_flag ) SafeFree(zFile); if( doc_flag ) PrintModuleRecord(zFile,zFilename); } if( nErr>0 ){ return nErr; } #ifdef DEBUG if( debugMask & DECL_DUMP ){ DumpDeclList(); return nErr; } #endif if( doc_flag ){ DocumentationDump(); return nErr; } zFilename = "--internal--"; pList = TokenizeFile(zInit,0); if( pList==0 ){ return nErr+1; } ParseFile(pList,PS_Interface); FreeTokenList(pList); if( h_flag || H_flag ){ nErr += MakeGlobalHeader(H_flag); }else{ for(pFile=pFileList; pFile; pFile=pFile->pNext){ if( pFile->zSrc==0 ) continue; nErr += MakeHeader(pFile,report,0); } } return nErr; } #endif ncdc-1.23.1/deps/yxml.h0000644000175000017500000001252712352226640011534 00000000000000/* Copyright (c) 2013 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef YXML_H #define YXML_H #include #include /* Full API documentation for this library can be found in the "yxml.pod" file * in the yxml git repository, or online at http://dev.yorhel.nl/yxml/man */ typedef enum { YXML_EEOF = -5, /* Unexpected EOF */ YXML_EREF = -4, /* Invalid character or entity reference (&whatever;) */ YXML_ECLOSE = -3, /* Close tag does not match open tag ( .. ) */ YXML_ESTACK = -2, /* Stack overflow (too deeply nested tags or too long element/attribute name) */ YXML_ESYN = -1, /* Syntax error (unexpected byte) */ YXML_OK = 0, /* Character consumed, no new token present */ YXML_ELEMSTART = 1, /* Start of an element: '' or '' */ YXML_ATTRSTART = 4, /* Attribute: 'Name=..' */ YXML_ATTRVAL = 5, /* Attribute value */ YXML_ATTREND = 6, /* End of attribute '.."' */ YXML_PISTART = 7, /* Start of a processing instruction */ YXML_PICONTENT = 8, /* Content of a PI */ YXML_PIEND = 9 /* End of a processing instruction */ } yxml_ret_t; /* When, exactly, are tokens returned? * * ' ELEMSTART * '/' ELEMSTART, '>' ELEMEND * ' ' ELEMSTART * '>' * '/', '>' ELEMEND * Attr * '=' ATTRSTART * "X ATTRVAL * 'Y' ATTRVAL * 'Z' ATTRVAL * '"' ATTREND * '>' * '/', '>' ELEMEND * * ' ELEMEND */ typedef struct { /* PUBLIC (read-only) */ /* Name of the current element, zero-length if not in any element. Changed * after YXML_ELEMSTART. The pointer will remain valid up to and including * the next non-YXML_ATTR* token, the pointed-to buffer will remain valid * up to and including the YXML_ELEMCLOSE for the corresponding element. */ char *elem; /* The last read character(s) of an attribute value (YXML_ATTRVAL), element * data (YXML_CONTENT), or processing instruction (YXML_PICONTENT). Changed * after one of the respective YXML_ values is returned, and only valid * until the next yxml_parse() call. Usually, this string only consists of * a single byte, but multiple bytes are returned in the following cases: * - "": The two characters "?x" * - "": The two characters "]x" * - "": The three characters "]]x" * - "&#N;" and "&#xN;", where dec(n) > 127. The referenced Unicode * character is then encoded in multiple UTF-8 bytes. */ char data[8]; /* Name of the current attribute. Changed after YXML_ATTRSTART, valid up to * and including the next YXML_ATTREND. */ char *attr; /* Name/target of the current processing instruction, zero-length if not in * a PI. Changed after YXML_PISTART, valid up to (but excluding) * the next YXML_PIEND. */ char *pi; /* Line number, byte offset within that line, and total bytes read. These * values refer to the position _after_ the last byte given to * yxml_parse(). These are useful for debugging and error reporting. */ uint64_t byte; uint64_t total; uint32_t line; /* PRIVATE */ int state; unsigned char *stack; /* Stack of element names + attribute/PI name, separated by \0. Also starts with a \0. */ size_t stacksize, stacklen; unsigned reflen; unsigned quote; int nextstate; /* Used for '@' state remembering and for the "string" consuming state */ unsigned ignore; unsigned char *string; } yxml_t; void yxml_init(yxml_t *, void *, size_t); yxml_ret_t yxml_parse(yxml_t *, int); /* May be called after the last character has been given to yxml_parse(). * Returns YXML_OK if the XML document is valid, YXML_EEOF otherwise. Using * this function isn't really necessary, but can be used to detect documents * that don't end correctly. In particular, an error is returned when the XML * document did not contain a (complete) root element, or when the document * ended while in a comment or processing instruction. */ yxml_ret_t yxml_eof(yxml_t *); #endif /* vim: set noet sw=4 ts=4: */ ncdc-1.23.1/deps/lean.m40000644000175000017500000001046012173213542011543 00000000000000# autotools lean macros # hg 2012-09-01 05a8d3fa4611 # Copyright (c) 2012 Gregor Richards # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD # TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND # FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, # OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF # USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE # OF THIS SOFTWARE. # These macros make auto* tests faster by removing some of autoconf's most # absurd defaults. The basic principle is to not check for things that have no # alternatives. That is, don't perform a test if it's either going to pass and # affect nothing, or fail and just prevent you from building. These tests # provide very little real value since modern systems that they fail on are few # and far between. # automake's sanity checks provide nothing useful, since all they can do is # fail, sometimes spuriously, and prevent builds which may otherwise have # succeeded. AC_DEFUN([AM_SANITY_CHECK], [ ]) # Checking for C89 compliance nowadays is just plain silly. AC_DEFUN([_AC_PROG_CC_C89], [ ]) # For the same reason, checking for C standard headers is usually stupid. # However, we simply avoid checking for them in the most ridiculous cases. m4_define([ACX_PRELEAN_AC_CHECK_HEADER], m4_defn([AC_CHECK_HEADER])) AC_DEFUN([AC_CHECK_HEADER], [ m4_case([$4], [], [ACX_PRELEAN_AC_CHECK_HEADER([$1], [$2], [$3], [ ])], [m4_indir([ACX_PRELEAN_AC_CHECK_HEADER], $@)]) ]) m4_define([_AC_HEADERS_EXPANSION], [ m4_divert_text([DEFAULTS], [ac_header_list=]) AC_CHECK_HEADERS([$ac_header_list], [], [], [ ]) m4_define([_AC_HEADERS_EXPANSION], []) ]) m4_define([ACX_PRELEAN_AC_CHECK_SIZEOF], m4_defn([AC_CHECK_SIZEOF])) AC_DEFUN([AC_CHECK_SIZEOF], [ m4_case([$3], [], [ACX_PRELEAN_AC_CHECK_SIZEOF([$1], [], [ ])], [m4_indir([ACX_PRELEAN_AC_CHECK_SIZEOF])], $@)]) # And add warnings for known-nasty builtin checks m4_define([ACX_UNLEAN_AC_FUNC_MMAP], m4_defn([AC_FUNC_MMAP])) AC_DEFUN([AC_FUNC_MMAP], [ AC_DIAGNOSE([syntax], [$0: AC_FUNC_MMAP does not work in cross environments and incurs high costs. Check for mmap directly if you aren't concerned about enormously-broken implementations. Use ACX_LEAN_AC_FUNC_MMAP to silence this warning.]) ACX_LEAN_AC_FUNC_MMAP ]) # POSIX says that make sets $(MAKE). That's good enough for me. AC_DEFUN([AC_PROG_MAKE_SET], [ ac_cv_prog_make_make_set=yes SET_MAKE= AC_SUBST([SET_MAKE]) ]) # configure will simply fail, often spuriously, if you don't tell it that # you're cross compiling, so there's very little reason to explicitly check. AC_DEFUN([_AC_COMPILER_EXEEXT_CROSS], [ if test "$cross_compiling" = "maybe"; then cross_compiling=yes fi ]) # Allow the default GCC-and-compatible CFLAGS to be changed GCC_DEFAULT_CFLAGS="-g -O2" # The builtin -g test is simplified by avoiding rechecks for GCC (of course GCC # supports -g) m4_define([ACX_PRELEAN__AC_PROG_CC_G], m4_defn([_AC_PROG_CC_G])) m4_define([_AC_PROG_CC_G], [ if test "$GCC" = "yes"; then acx_lean_CFLAGS_set=${CFLAGS+set} ac_cv_prog_cc_g=yes if test "$acx_lean_CFLAGS_set" != "set"; then CFLAGS="$GCC_DEFAULT_CFLAGS" fi else ACX_PRELEAN__AC_PROG_CC_G fi ]) # Option to force caching AC_DEFUN([ACX_LEAN_FORCE_CACHE], [ m4_define([acx_lean_forced_cache], [yes]) if test "$cache_file" = "/dev/null"; then cache_file=config.cache touch config.cache fi ]) # Force the use of a cache file if we use subdirectories, as otherwise we # retest things in the subdirs. m4_define([ACX_PRELEAN_AC_CONFIG_SUBDIRS], m4_defn([AC_CONFIG_SUBDIRS])) AC_DEFUN([AC_CONFIG_SUBDIRS], [ m4_ifdef([acx_lean_forced_cache], [], [ AC_DIAGNOSE([syntax], [$0: Use ACX_LEAN_FORCE_CACHE after initialization to avoid extra costs with configure subdirs.]) ]) ACX_PRELEAN_AC_CONFIG_SUBDIRS($1) ]) ncdc-1.23.1/deps/ylib/0000755000175000017500000000000014314550104011375 500000000000000ncdc-1.23.1/deps/ylib/yuri.c0000644000175000017500000001716712173213542012470 00000000000000/* Copyright (c) 2012-2013 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "yuri.h" #include #include #include #include #include /* The ctype.h functions are locale-dependent. We don't want that. */ #define y_isalpha(x) (((x) >= 'a' && (x) <= 'z') || ((x) >= 'A' && (x) <= 'Z')) #define y_isnum(x) ((x) >= '0' && (x) <= '9') #define y_isalnum(x) (y_isalpha(x) || y_isnum(x)) #define y_tolower(x) ((x) < 'A' || (x) > 'Z' ? (x) : (x)+0x20) #define y_ishex(x) (((x) >= 'a' && (x) <= 'f') || ((x) >= 'A' && (x) <= 'F') || y_isnum(x)) #define y_isscheme(x) ((x) == '+' || (x) == '-' || (x) == '.' || y_isalnum(x)) #define y_isdomain(x) ((x) == '-' || y_isalnum(x)) #define y_hexval(x) ((x) >= '0' && (x) <= '9' ? (x)-'0' : (x) >= 'A' && (x) <= 'F' ? (x)-'A'+10 : (x)-'a'+10) /* Parses the "://" part, if it exists, and advances the buf pointer. */ static void yuri__scheme(char **buf, yuri_t *out) { const char *end = *buf; if(!y_isalpha(**buf)) { out->scheme = *buf + strlen(*buf); return; } do ++end; while(end <= *buf+15 && y_isscheme(*end)); if(end > *buf+15 || *end != ':' || end[1] != '/' || end[2] != '/') { out->scheme = *buf + strlen(*buf); return; } /* Valid scheme, lowercase it and advance *buf. */ out->scheme = *buf; while(*buf != end) { **buf = y_tolower(**buf); (*buf)++; } **buf = 0; *buf += 3; } /* Parses the ":" part in buf and, if it exists, sets the ':' to zero to * ensure that buf is a complete host string. */ static void yuri__port(char *buf, size_t len, yuri_t *out) { uint32_t res = 0, mul = 1; out->port = 0; if(!len) return; /* Read backwards */ while(--len > 0 && y_isnum(buf[len])) { if(mul >= 100000) return; res += mul * (buf[len]-'0'); if(res > 65535) return; mul *= 10; } if(!res || !len || buf[len] != ':' || buf[len+1] == '0') return; out->port = res; buf[len] = 0; } /* RFC1034 section 3.5 has an explanation of a (commonly used) domain syntax, * but I suspect it may be overly strict. This implementation will suffice, I * suppose. */ static int yuri__validate_domain(const char *str, int len) { int haslabel = 0, /* whether we've seen a label */ lastishyp = 0, /* whether the last seen character in the label is a hyphen */ startdig = 0, /* whether the last seen label starts with a digit (Not allowed per RFC1738, a sensible restriction IMO) */ llen = 0; /* length of the current label */ /* In the case of percent encoding, the length of the domain may be much * larger. But this implementation (currently) does not accept percent * encoding in the domain name. Similarly, the character limit applies to * the ASCII form of the domain, in the case of an IDN, this check doesn't * really work either. This implementation (currently) does not support * IDN. In fact, this function should be "validate-and-normalize" instead * of just validate in such a case. */ if(len > 255) return -1; for(; len > 0; str++, len--) { if(*str == '.') { if(!llen || lastishyp) return -1; llen = 0; continue; } else if(llen >= 63) return -1; if(!y_isdomain(*str)) return -1; lastishyp = *str == '-'; if(llen == 0) { if(lastishyp) /* That is, don't start with a hyphen */ return -1; startdig = y_isnum(*str); } haslabel = 1; llen++; } return haslabel && !startdig ? 0 : -1; } int yuri__host(char *buf, yuri_t *out) { char addrbuf[16]; /* IPv6 */ if(*buf == '[') { if(buf[strlen(buf)-1] != ']') return -1; buf++; buf[strlen(buf)-1] = 0; if(inet_pton(AF_INET6, buf, addrbuf) != 1) return -1; out->hosttype = YURI_IPV6; out->host = buf; return 0; } /* IPv4 */ if(inet_pton(AF_INET, buf, addrbuf) == 1) { out->hosttype = YURI_IPV4; out->host = buf; return 0; } /* Domain */ out->hosttype = YURI_DOMAIN; out->host = buf; return yuri__validate_domain(buf, strlen(buf)); } int yuri_parse(char *buf, yuri_t *out) { char *end, endc; out->buf = buf; yuri__scheme(&buf, out); /* Find the end of the authority component (RFC3986, section 3.2) */ end = buf; while(*end && *end != '/' && *end != '?' && *end != '#') end++; endc = *end; *end = 0; yuri__port(buf, end-buf, out); if(yuri__host(buf, out)) return -1; /* path */ if(endc == '/') { out->path = ++end; while(*end && *end != '?' && *end != '#') end++; endc = *end; *end = 0; if(yuri_validate_escape(out->path)) return -1; } else out->path = end; /* query */ if(endc == '?') { out->query = ++end; while(*end && *end != '#') end++; endc = *end; *end = 0; if(yuri_validate_escape(out->query)) return -1; } else out->query = end; /* fragment */ if(endc == '#') { out->fragment = ++end; while(*end) if(*(end++) == '#') return -1; if(yuri_validate_escape(out->fragment)) return -1; } else out->fragment = end; return 0; } int yuri_parse_copy(const char *str, yuri_t *out) { char *buf = strdup(str); if(!buf) return -2; if(yuri_parse(buf, out)) { free(buf); return -1; } return 0; } int yuri_validate_escape(const char *str) { while(*str) { if(*str != '%') { str++; continue; } if(!y_ishex(str[1]) || !y_ishex(str[2]) || (str[1] == '0' && str[2] == '0')) return -1; str += 3; } return 0; } char *yuri_unescape(char *str) { unsigned char *src = (unsigned char *)str, *dest = (unsigned char *)str; if(!str) return NULL; while(*src) { if(*src != '%') { *(dest++) = *(src++); continue; } *(dest++) = (y_hexval(src[1])<<4) | y_hexval(src[2]); src += 3; } *dest = 0; return str; } /* Special unescape function for the query string. Differs from yuri_unescape() * in that it also converts '+' to a space. */ static char *yuri__query_unescape(char *str) { unsigned char *src = (unsigned char *)str, *dest = (unsigned char *)str; while(*src) { if(*src == '+') { *(dest++) = ' '; src++; continue; } if(*src != '%') { *(dest++) = *(src++); continue; } *(dest++) = (y_hexval(src[1])<<4) | y_hexval(src[2]); src += 3; } *dest = 0; return str; } int yuri_query_parse(char **str, char **key, char **value) { if(!str || !*str || !**str) return 0; /* Key */ char *sep = *str; while(*sep && *sep != '=' && *sep != ';' && *sep != '&') sep++; if(!*sep || *sep == ';' || *sep == '&') { /* No value */ *key = *str; *value = sep; *str = *sep ? sep+1 : sep; *sep = 0; yuri__query_unescape(*key); return 1; } *(sep++) = 0; *key = *str; yuri__query_unescape(*key); /* Value */ *value = sep; while(*sep && *sep != ';' && *sep != '&') sep++; *str = *sep ? sep+1 : sep; *sep = 0; yuri__query_unescape(*value); return 1; } /* vim: set noet sw=4 ts=4: */ ncdc-1.23.1/deps/ylib/yuri.h0000644000175000017500000002205412173213542012464 00000000000000/* Copyright (c) 2012-2013 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* This is a URI parser and validator that supports the following formats: * - * - : * - :// * - ://: * - <#fragment> * * must match /^[a-zA-Z][a-zA-Z0-9\.+-]{0,14}$/ * is either: * - A full IPv4 address (1.2.3.4) * - An IPv6 address within square brackets * - A domain name. That is, something like /^([a-zA-Z0-9-]{1,63}\.)+$/, with * a maximum length of 255 characters. Actual parser is a bit more strict * than the above regex. * must be a decimal number between 1 and 65535 (both inclusive) * is an escaped string not containing '?' or '#' * is an escaped string not containing '#' * <#fragment> is an escaped string not containing '#' * Any , and/or <#fragment> components may be absent. * * The format of the part is highly dependent on the application and * is therefore not automatically parsed. However, a simple parser is available * for the common key=param style. * * Not supported (yet): * - Username / password parts * - Symbolic port names * - Internationalized domain names. Parsing only succeeds when the address * is in the ASCII form. * - Relative references (Protocol relative URLs), e.g. "//domain.com/" * - Percent encoding in and is not handled. Even though the * RFC's seem to imply that this is allowed. * * URI unescaping is supported, but the %00 escape is explicitely NOT allowed * and will cause parsing to fail with a validation error. This makes this * library unsuitable for schemes that use URI escaping to send binary data, * such as BitTorrent tracker announcements. Non-standard %uxxxx escapes are * not supported, either. * * RFC1738 and RFC3986 have been used as reference, but strict adherence to * those specifications isn't a direct goal. In particular, this parser allows * to be absent and requires to be present and limited to an * IPv4/IPv6/DNS address. This makes the parser suitable for schemes like * irc://, http://, ftp:// and adc://, but unsuitable for stuff like mailto: * and magnet:. */ #ifndef YURI_H #define YURI_H #include #include typedef enum { YURI_IPV6, YURI_IPV4, YURI_DOMAIN } yuri_hosttype_t; /* See description above for the supported URI formats. */ typedef struct { /* Pointer to the start of the buffer. This is the buffer given to * yuri_parse(), or a newly created buffer in the case of * yuri_parse_copy(). */ char *buf; /* All the pointers below point into the *buf memory area. */ /* Empty string if there was no scheme in the URI. Uppercase characters * (A-Z) are automatically converted to lowercase (a-z). */ char *scheme; /* Hostname part of the URI. hosttype indicates what kind of hostname this * is (IPv4, IPv6 or a domain name). * No normalization or case modification on the host is performed. Any * square brackets around the IPv6 address in the URI are not considered * part of the hostname. E.g. the URI "http://[::]/" has YURI_IPV6 and * host = "::". */ char *host; yuri_hosttype_t hosttype; /* 0 if no port was included in the URI. */ uint16_t port; /* Unmodified path, query and fragment parts of the URI, not including the * first '/', '?' or '#' character, respectively. If a part was missing * from the URI, its value here is set to an empty string. Note that it is * not possible to distinguish between a missing part, or a present but * empty part. For example, both "http://blicky.net" and * "http://blicky.net/?#" will have all the fields below set to an empty * string. * * These parts are passed through in the same form as they are present in * the URI. Unescaping is not automatically performed by yuri_parse() * because these components may include schema-specific delimiters and * encoding rules. If you just want their unescaped string representation, * you can always use yuri_unescape() on these fields. If you know that the * query string is in key=value format (most common), use the * yuri_query_parse() function to parse it. */ char *path; char *query; char *fragment; } yuri_t; /* Returns -1 if the URI isn't valid, 0 on success. The given string should be * zero-terminated and will be modified in-place. * * If the URI is invalid, both the `str' and `out' arguments may have been * partially written to and may therefore contain rubbish. * * This function attempts to do as much (sane) validation as possible. */ int yuri_parse(char *str, yuri_t *out); /* Similar to yuri_parse(), but makes an internal copy of the string before * processing. Returns -2 on OOM. * * When this function returns 0, you must call free(out->buf) after you're done * with the parsed results. */ int yuri_parse_copy(const char *str, yuri_t *out); /* Validates whether a string has been correctly escaped. This function should * be used before calling yuri_unescape() on a string obtained from an * untrusted source. Note that validation on the 'path', 'query' and 'fragment' * fields in the yuri_t struct is not necessary, as yuri_parse() will do this * already. * A string is considered valid if any % characters are followed by two hex * characters and there is no %00 escape. */ int yuri_validate_escape(const char *str); /* Unescapes the given string in-place. That is, it converts %XX escapes into * their byte representation. Returns the string given as first argument, so * you can use it as yuri_unescape(strdup(str)) if you want to allocate a new * string. This function simply passes through NULL if str is NULL. * * IMPORTANT: This function does not perform any validation. Behaviour is * undefined when used on an invalid string. Use yuri_validate_escape() if you * do not know whether the string is valid or not. * * IMPORTANT#2: You should only call this function on the same string once. For * example, you can do a: * char *unescaped_path = yuri_unscape(uri->path); * to get the path once. After that you can access the unescaped path directly * through uri->path. The original path is then not available anymore, and * calling yuri_unescape(uri->path) another time is an error. * * IMPORTANT#3: You can't expect the returned string to be valid UTF-8 or to * not contain any weird (e.g. control) characters. If you want to do any * further validation on the strings obtained from a URI, you must do so AFTER * calling this function. */ char *yuri_unescape(char *str); /* Simple query string parser. Parses both "a=b&c=d", "a=b;c=d" and a mixture * of the two styles. This function is used as follows: * * yuri_t uri; * if(yuri_parse(str, &uri)) * // handle error * * char *key, *value; * while(yuri_query_parse(&uri.query, &key, &value)) { * // Do something with key and value * } * * This function takes a pointer to a query string buffer as argument, parses * one key/value pair, stores pointers into this buffer in *key and *value, and * advances the *str pointer to the next pair, or to the end of the string if * there is no next pair. Returns 1 if a key/value pair has been extracted, 0 * if !*str || !**str. * * The given *str is modified in-place. If you wish to re-use the query string * later on or want to iterate multiple times over the same query string, you * need to make a copy of the string (e.g. with strdup()) and iterate over * that. * * The strings returned in *key and *value are unescaped, as in * yuri_unescape(). Additionally, the '+' character is converted into a space * as well. Both the key and value can be set to an empty string. This happens * for empty pairs ("&&" or "&=&"), empty keys ("=abc") or empty/absent values * ("abc" or "abc="). * * IMPORTANT: The given string is assumed to contain valid URI escapes, as in * yuri_validate_escape(), so run that function first if the string comes from * an untrusted source. * * The IMPORTANT#3 note of yuri_unescape() applies here, too. */ int yuri_query_parse(char **str, char **key, char **value); #endif /* vim: set noet sw=4 ts=4: */ ncdc-1.23.1/deps/yxml.c0000644000175000017500000005646312352226640011536 00000000000000/* THIS FILE IS AUTOMATICALLY GENERATED, DO NOT EDIT! */ /* Copyright (c) 2013 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include typedef enum { YXMLS_string, YXMLS_attr0, YXMLS_attr1, YXMLS_attr2, YXMLS_attr3, YXMLS_attr4, YXMLS_cd0, YXMLS_cd1, YXMLS_cd2, YXMLS_comment0, YXMLS_comment1, YXMLS_comment2, YXMLS_comment3, YXMLS_comment4, YXMLS_dt0, YXMLS_dt1, YXMLS_dt2, YXMLS_dt3, YXMLS_dt4, YXMLS_elem0, YXMLS_elem1, YXMLS_elem2, YXMLS_elem3, YXMLS_enc0, YXMLS_enc1, YXMLS_enc2, YXMLS_enc3, YXMLS_etag0, YXMLS_etag1, YXMLS_etag2, YXMLS_init, YXMLS_le0, YXMLS_le1, YXMLS_le2, YXMLS_le3, YXMLS_lee1, YXMLS_lee2, YXMLS_leq0, YXMLS_misc0, YXMLS_misc1, YXMLS_misc2, YXMLS_misc2a, YXMLS_misc3, YXMLS_pi0, YXMLS_pi1, YXMLS_pi2, YXMLS_pi3, YXMLS_pi4, YXMLS_std0, YXMLS_std1, YXMLS_std2, YXMLS_std3, YXMLS_ver0, YXMLS_ver1, YXMLS_ver2, YXMLS_ver3, YXMLS_xmldecl0, YXMLS_xmldecl1, YXMLS_xmldecl2, YXMLS_xmldecl3, YXMLS_xmldecl4, YXMLS_xmldecl5, YXMLS_xmldecl6, YXMLS_xmldecl7 } yxml_state_t; #define yxml_isChar(c) 1 /* 0xd should be part of SP, too, but yxml_parse() already normalizes that into 0xa */ #define yxml_isSP(c) (c == 0x20 || c == 0x09 || c == 0x0a) #define yxml_isAlpha(c) ((c|32)-'a' < 26) #define yxml_isNum(c) (c-'0' < 10) #define yxml_isHex(c) (yxml_isNum(c) || (c|32)-'a' < 6) #define yxml_isEncName(c) (yxml_isAlpha(c) || yxml_isNum(c) || c == '.' || c == '_' || c == '-') #define yxml_isNameStart(c) (yxml_isAlpha(c) || c == ':' || c == '_' || c >= 128) #define yxml_isName(c) (yxml_isNameStart(c) || yxml_isNum(c) || c == '-' || c == '.') /* XXX: The valid characters are dependent on the quote char, hence the access to x->quote */ #define yxml_isAttValue(c) (yxml_isChar(c) && c != x->quote && c != '<' && c != '&') /* Anything between '&' and ';', the yxml_ref* functions will do further * validation. Strictly speaking, this is "yxml_isName(c) || c == '#'", but * this parser doesn't understand entities with '.', ':', etc, anwyay. */ #define yxml_isRef(c) (yxml_isNum(c) || yxml_isAlpha(c) || c == '#') #define INTFROM5CHARS(a, b, c, d, e) ((((uint64_t)(a))<<32) | (((uint64_t)(b))<<24) | (((uint64_t)(c))<<16) | (((uint64_t)(d))<<8) | (uint64_t)(e)) /* Set the given char value to ch (0<=ch<=255). * This can't be done with simple assignment because char may be signed, and * unsigned-to-signed overflow is implementation defined in C. This function * /looks/ inefficient, but gcc compiles it down to a single movb instruction * on x86, even with -O0. */ static inline void yxml_setchar(char *dest, unsigned ch) { unsigned char _ch = ch; memcpy(dest, &_ch, 1); } /* Similar to yxml_setchar(), but will convert ch (any valid unicode point) to * UTF-8 and appends a '\0'. dest must have room for at least 5 bytes. */ static void yxml_setutf8(char *dest, unsigned ch) { if(ch <= 0x007F) yxml_setchar(dest++, ch); else if(ch <= 0x07FF) { yxml_setchar(dest++, 0xC0 | (ch>>6)); yxml_setchar(dest++, 0x80 | (ch & 0x3F)); } else if(ch <= 0xFFFF) { yxml_setchar(dest++, 0xE0 | (ch>>12)); yxml_setchar(dest++, 0x80 | ((ch>>6) & 0x3F)); yxml_setchar(dest++, 0x80 | (ch & 0x3F)); } else { yxml_setchar(dest++, 0xF0 | (ch>>18)); yxml_setchar(dest++, 0x80 | ((ch>>12) & 0x3F)); yxml_setchar(dest++, 0x80 | ((ch>>6) & 0x3F)); yxml_setchar(dest++, 0x80 | (ch & 0x3F)); } *dest = 0; } static inline int yxml_datacontent(yxml_t *x, unsigned ch) { yxml_setchar(x->data, ch); x->data[1] = 0; return YXML_CONTENT; } static inline int yxml_datapi1(yxml_t *x, unsigned ch) { yxml_setchar(x->data, ch); x->data[1] = 0; return YXML_PICONTENT; } static inline int yxml_datapi2(yxml_t *x, unsigned ch) { x->data[0] = '?'; yxml_setchar(x->data+1, ch); x->data[2] = 0; return YXML_PICONTENT; } static inline int yxml_datacd1(yxml_t *x, unsigned ch) { x->data[0] = ']'; yxml_setchar(x->data+1, ch); x->data[2] = 0; return YXML_CONTENT; } static inline int yxml_datacd2(yxml_t *x, unsigned ch) { x->data[0] = ']'; x->data[1] = ']'; yxml_setchar(x->data+2, ch); x->data[3] = 0; return YXML_CONTENT; } static inline int yxml_dataattr(yxml_t *x, unsigned ch) { /* Normalize attribute values according to the XML spec section 3.3.3. */ yxml_setchar(x->data, ch == 0x9 || ch == 0xa ? 0x20 : ch); x->data[1] = 0; return YXML_ATTRVAL; } static int yxml_pushstack(yxml_t *x, char **res, unsigned ch) { if(x->stacklen+2 >= x->stacksize) return YXML_ESTACK; x->stacklen++; *res = (char *)x->stack+x->stacklen; x->stack[x->stacklen] = ch; x->stacklen++; x->stack[x->stacklen] = 0; return YXML_OK; } static int yxml_pushstackc(yxml_t *x, unsigned ch) { if(x->stacklen+1 >= x->stacksize) return YXML_ESTACK; x->stack[x->stacklen] = ch; x->stacklen++; x->stack[x->stacklen] = 0; return YXML_OK; } static void yxml_popstack(yxml_t *x) { do x->stacklen--; while(x->stack[x->stacklen]); } static inline int yxml_elemstart (yxml_t *x, unsigned ch) { return yxml_pushstack(x, &x->elem, ch); } static inline int yxml_elemname (yxml_t *x, unsigned ch) { return yxml_pushstackc(x, ch); } static inline int yxml_elemnameend(yxml_t *x, unsigned ch) { return YXML_ELEMSTART; } /* Also used in yxml_elemcloseend(), since this function just removes the last * element from the stack and returns ELEMEND. */ static int yxml_selfclose(yxml_t *x, unsigned ch) { yxml_popstack(x); if(x->stacklen) { x->elem = (char *)x->stack+x->stacklen-1; while(*(x->elem-1)) x->elem--; return YXML_ELEMEND; } x->elem = (char *)x->stack; x->state = YXMLS_misc3; return YXML_ELEMEND; } static inline int yxml_elemclose(yxml_t *x, unsigned ch) { if(*((unsigned char *)x->elem) != ch) return YXML_ECLOSE; x->elem++; return YXML_OK; } static inline int yxml_elemcloseend(yxml_t *x, unsigned ch) { if(*x->elem) return YXML_ECLOSE; return yxml_selfclose(x, ch); } static inline int yxml_attrstart (yxml_t *x, unsigned ch) { return yxml_pushstack(x, &x->attr, ch); } static inline int yxml_attrname (yxml_t *x, unsigned ch) { return yxml_pushstackc(x, ch); } static inline int yxml_attrnameend(yxml_t *x, unsigned ch) { return YXML_ATTRSTART; } static inline int yxml_attrvalend (yxml_t *x, unsigned ch) { yxml_popstack(x); return YXML_ATTREND; } static inline int yxml_pistart (yxml_t *x, unsigned ch) { return yxml_pushstack(x, &x->pi, ch); } static inline int yxml_piname (yxml_t *x, unsigned ch) { return yxml_pushstackc(x, ch); } static inline int yxml_pinameend(yxml_t *x, unsigned ch) { return (x->pi[0]|32) == 'x' && (x->pi[1]|32) == 'm' && (x->pi[2]|32) == 'l' && !x->pi[3] ? YXML_ESYN : YXML_PISTART; } static inline int yxml_pivalend (yxml_t *x, unsigned ch) { yxml_popstack(x); x->pi = (char *)x->stack; return YXML_PIEND; } static inline int yxml_refstart(yxml_t *x, unsigned ch) { memset(x->data, 0, sizeof(x->data)); x->reflen = 0; return YXML_OK; } static int yxml_ref(yxml_t *x, unsigned ch) { if(x->reflen >= sizeof(x->data)-1) return YXML_EREF; yxml_setchar(x->data+x->reflen, ch); x->reflen++; return YXML_OK; } static int yxml_refend(yxml_t *x, int ret) { unsigned char *r = (unsigned char *)x->data; unsigned ch = 0; if(*r == '#') { if(r[1] == 'x') for(r += 2; yxml_isHex((unsigned)*r); r++) ch = (ch<<4) + (*r <= '9' ? *r-'0' : (*r|32)-'a' + 10); else for(r++; yxml_isNum((unsigned)*r); r++) ch = (ch*10) + (*r-'0'); if(*r) ch = 0; } else { uint64_t i = INTFROM5CHARS(r[0], r[1], r[2], r[3], r[4]); ch = i == INTFROM5CHARS('l','t', 0, 0, 0) ? '<' : i == INTFROM5CHARS('g','t', 0, 0, 0) ? '>' : i == INTFROM5CHARS('a','m','p', 0, 0) ? '&' : i == INTFROM5CHARS('a','p','o','s',0) ? '\'': i == INTFROM5CHARS('q','u','o','t',0) ? '"' : 0; } /* Codepoints not allowed in the XML 1.1 definition of a Char */ if(!ch || ch > 0x10FFFF || ch == 0xFFFE || ch == 0xFFFF || (ch-0xDFFF) < 0x7FF) return YXML_EREF; yxml_setutf8(x->data, ch); return ret; } static inline int yxml_refcontent(yxml_t *x, unsigned ch) { return yxml_refend(x, YXML_CONTENT); } static inline int yxml_refattrval(yxml_t *x, unsigned ch) { return yxml_refend(x, YXML_ATTRVAL); } void yxml_init(yxml_t *x, void *stack, size_t stacksize) { memset(x, 0, sizeof(*x)); x->line = 1; x->stack = stack; x->stacksize = stacksize; *x->stack = 0; x->elem = x->pi = (char *)x->stack; x->state = YXMLS_init; } yxml_ret_t yxml_parse(yxml_t *x, int _ch) { /* Ensure that characters are in the range of 0..255 rather than -126..125. * All character comparisons are done with positive integers. */ unsigned ch = (unsigned)(_ch+256) & 0xff; if(!ch) return YXML_ESYN; x->total++; /* End-of-Line normalization, "\rX", "\r\n" and "\n" are recognized and * normalized to a single '\n' as per XML 1.0 section 2.11. XML 1.1 adds * some non-ASCII character sequences to this list, but we can only handle * ASCII here without making assumptions about the input encoding. */ if(x->ignore == ch) { x->ignore = 0; return YXML_OK; } x->ignore = (ch == 0xd) * 0xa; if(ch == 0xa || ch == 0xd) { ch = 0xa; x->line++; x->byte = 0; } x->byte++; switch((yxml_state_t)x->state) { case YXMLS_string: if(ch == *x->string) { x->string++; if(!*x->string) x->state = x->nextstate; return YXML_OK; } break; case YXMLS_attr0: if(yxml_isName(ch)) return yxml_attrname(x, ch); if(yxml_isSP(ch)) { x->state = YXMLS_attr1; return yxml_attrnameend(x, ch); } if(ch == (unsigned char)'=') { x->state = YXMLS_attr2; return yxml_attrnameend(x, ch); } break; case YXMLS_attr1: if(yxml_isSP(ch)) return YXML_OK; if(ch == (unsigned char)'=') { x->state = YXMLS_attr2; return YXML_OK; } break; case YXMLS_attr2: if(yxml_isSP(ch)) return YXML_OK; if(ch == (unsigned char)'\'' || ch == (unsigned char)'"') { x->state = YXMLS_attr3; x->quote = ch; return YXML_OK; } break; case YXMLS_attr3: if(yxml_isAttValue(ch)) return yxml_dataattr(x, ch); if(ch == (unsigned char)'&') { x->state = YXMLS_attr4; return yxml_refstart(x, ch); } if(x->quote == ch) { x->state = YXMLS_elem2; return yxml_attrvalend(x, ch); } break; case YXMLS_attr4: if(yxml_isRef(ch)) return yxml_ref(x, ch); if(ch == (unsigned char)'\x3b') { x->state = YXMLS_attr3; return yxml_refattrval(x, ch); } break; case YXMLS_cd0: if(ch == (unsigned char)']') { x->state = YXMLS_cd1; return YXML_OK; } if(yxml_isChar(ch)) return yxml_datacontent(x, ch); break; case YXMLS_cd1: if(ch == (unsigned char)']') { x->state = YXMLS_cd2; return YXML_OK; } if(yxml_isChar(ch)) { x->state = YXMLS_cd0; return yxml_datacd1(x, ch); } break; case YXMLS_cd2: if(ch == (unsigned char)']') return yxml_datacontent(x, ch); if(ch == (unsigned char)'>') { x->state = YXMLS_misc2; return YXML_OK; } if(yxml_isChar(ch)) { x->state = YXMLS_cd0; return yxml_datacd2(x, ch); } break; case YXMLS_comment0: if(ch == (unsigned char)'-') { x->state = YXMLS_comment1; return YXML_OK; } break; case YXMLS_comment1: if(ch == (unsigned char)'-') { x->state = YXMLS_comment2; return YXML_OK; } break; case YXMLS_comment2: if(ch == (unsigned char)'-') { x->state = YXMLS_comment3; return YXML_OK; } if(yxml_isChar(ch)) return YXML_OK; break; case YXMLS_comment3: if(ch == (unsigned char)'-') { x->state = YXMLS_comment4; return YXML_OK; } if(yxml_isChar(ch)) { x->state = YXMLS_comment2; return YXML_OK; } break; case YXMLS_comment4: if(ch == (unsigned char)'>') { x->state = x->nextstate; return YXML_OK; } break; case YXMLS_dt0: if(ch == (unsigned char)'>') { x->state = YXMLS_misc1; return YXML_OK; } if(ch == (unsigned char)'\'' || ch == (unsigned char)'"') { x->state = YXMLS_dt1; x->quote = ch; x->nextstate = YXMLS_dt0; return YXML_OK; } if(ch == (unsigned char)'<') { x->state = YXMLS_dt2; return YXML_OK; } if(yxml_isChar(ch)) return YXML_OK; break; case YXMLS_dt1: if(x->quote == ch) { x->state = x->nextstate; return YXML_OK; } if(yxml_isChar(ch)) return YXML_OK; break; case YXMLS_dt2: if(ch == (unsigned char)'?') { x->state = YXMLS_pi0; x->nextstate = YXMLS_dt0; return YXML_OK; } if(ch == (unsigned char)'!') { x->state = YXMLS_dt3; return YXML_OK; } break; case YXMLS_dt3: if(ch == (unsigned char)'-') { x->state = YXMLS_comment1; x->nextstate = YXMLS_dt0; return YXML_OK; } if(yxml_isChar(ch)) { x->state = YXMLS_dt4; return YXML_OK; } break; case YXMLS_dt4: if(ch == (unsigned char)'\'' || ch == (unsigned char)'"') { x->state = YXMLS_dt1; x->quote = ch; x->nextstate = YXMLS_dt4; return YXML_OK; } if(ch == (unsigned char)'>') { x->state = YXMLS_dt0; return YXML_OK; } if(yxml_isChar(ch)) return YXML_OK; break; case YXMLS_elem0: if(yxml_isName(ch)) return yxml_elemname(x, ch); if(yxml_isSP(ch)) { x->state = YXMLS_elem1; return yxml_elemnameend(x, ch); } if(ch == (unsigned char)'/') { x->state = YXMLS_elem3; return yxml_elemnameend(x, ch); } if(ch == (unsigned char)'>') { x->state = YXMLS_misc2; return yxml_elemnameend(x, ch); } break; case YXMLS_elem1: if(yxml_isSP(ch)) return YXML_OK; if(ch == (unsigned char)'/') { x->state = YXMLS_elem3; return YXML_OK; } if(ch == (unsigned char)'>') { x->state = YXMLS_misc2; return YXML_OK; } if(yxml_isNameStart(ch)) { x->state = YXMLS_attr0; return yxml_attrstart(x, ch); } break; case YXMLS_elem2: if(yxml_isSP(ch)) { x->state = YXMLS_elem1; return YXML_OK; } if(ch == (unsigned char)'/') { x->state = YXMLS_elem3; return YXML_OK; } if(ch == (unsigned char)'>') { x->state = YXMLS_misc2; return YXML_OK; } break; case YXMLS_elem3: if(ch == (unsigned char)'>') { x->state = YXMLS_misc2; return yxml_selfclose(x, ch); } break; case YXMLS_enc0: if(yxml_isSP(ch)) return YXML_OK; if(ch == (unsigned char)'=') { x->state = YXMLS_enc1; return YXML_OK; } break; case YXMLS_enc1: if(yxml_isSP(ch)) return YXML_OK; if(ch == (unsigned char)'\'' || ch == (unsigned char)'"') { x->state = YXMLS_enc2; x->quote = ch; return YXML_OK; } break; case YXMLS_enc2: if(yxml_isAlpha(ch)) { x->state = YXMLS_enc3; return YXML_OK; } break; case YXMLS_enc3: if(yxml_isEncName(ch)) return YXML_OK; if(x->quote == ch) { x->state = YXMLS_xmldecl4; return YXML_OK; } break; case YXMLS_etag0: if(yxml_isNameStart(ch)) { x->state = YXMLS_etag1; return yxml_elemclose(x, ch); } break; case YXMLS_etag1: if(yxml_isName(ch)) return yxml_elemclose(x, ch); if(yxml_isSP(ch)) { x->state = YXMLS_etag2; return yxml_elemcloseend(x, ch); } if(ch == (unsigned char)'>') { x->state = YXMLS_misc2; return yxml_elemcloseend(x, ch); } break; case YXMLS_etag2: if(yxml_isSP(ch)) return YXML_OK; if(ch == (unsigned char)'>') { x->state = YXMLS_misc2; return YXML_OK; } break; case YXMLS_init: if(ch == (unsigned char)'\xef') { x->state = YXMLS_string; x->nextstate = YXMLS_misc0; x->string = (unsigned char *)"\xbb\xbf"; return YXML_OK; } if(yxml_isSP(ch)) { x->state = YXMLS_misc0; return YXML_OK; } if(ch == (unsigned char)'<') { x->state = YXMLS_le0; return YXML_OK; } break; case YXMLS_le0: if(ch == (unsigned char)'!') { x->state = YXMLS_lee1; return YXML_OK; } if(ch == (unsigned char)'?') { x->state = YXMLS_leq0; return YXML_OK; } if(yxml_isNameStart(ch)) { x->state = YXMLS_elem0; return yxml_elemstart(x, ch); } break; case YXMLS_le1: if(ch == (unsigned char)'!') { x->state = YXMLS_lee1; return YXML_OK; } if(ch == (unsigned char)'?') { x->state = YXMLS_pi0; x->nextstate = YXMLS_misc1; return YXML_OK; } if(yxml_isNameStart(ch)) { x->state = YXMLS_elem0; return yxml_elemstart(x, ch); } break; case YXMLS_le2: if(ch == (unsigned char)'!') { x->state = YXMLS_lee2; return YXML_OK; } if(ch == (unsigned char)'?') { x->state = YXMLS_pi0; x->nextstate = YXMLS_misc2; return YXML_OK; } if(ch == (unsigned char)'/') { x->state = YXMLS_etag0; return YXML_OK; } if(yxml_isNameStart(ch)) { x->state = YXMLS_elem0; return yxml_elemstart(x, ch); } break; case YXMLS_le3: if(ch == (unsigned char)'!') { x->state = YXMLS_comment0; x->nextstate = YXMLS_misc3; return YXML_OK; } if(ch == (unsigned char)'?') { x->state = YXMLS_pi0; x->nextstate = YXMLS_misc3; return YXML_OK; } break; case YXMLS_lee1: if(ch == (unsigned char)'-') { x->state = YXMLS_comment1; x->nextstate = YXMLS_misc1; return YXML_OK; } if(ch == (unsigned char)'D') { x->state = YXMLS_string; x->nextstate = YXMLS_dt0; x->string = (unsigned char *)"OCTYPE"; return YXML_OK; } break; case YXMLS_lee2: if(ch == (unsigned char)'-') { x->state = YXMLS_comment1; x->nextstate = YXMLS_misc2; return YXML_OK; } if(ch == (unsigned char)'[') { x->state = YXMLS_string; x->nextstate = YXMLS_cd0; x->string = (unsigned char *)"CDATA["; return YXML_OK; } break; case YXMLS_leq0: if(ch == (unsigned char)'x') { x->state = YXMLS_string; x->nextstate = YXMLS_xmldecl0; x->string = (unsigned char *)"ml"; return YXML_OK; } if(yxml_isNameStart(ch)) { x->state = YXMLS_pi1; x->nextstate = YXMLS_misc1; return yxml_pistart(x, ch); } break; case YXMLS_misc0: if(yxml_isSP(ch)) return YXML_OK; if(ch == (unsigned char)'<') { x->state = YXMLS_le0; return YXML_OK; } break; case YXMLS_misc1: if(yxml_isSP(ch)) return YXML_OK; if(ch == (unsigned char)'<') { x->state = YXMLS_le1; return YXML_OK; } break; case YXMLS_misc2: if(ch == (unsigned char)'<') { x->state = YXMLS_le2; return YXML_OK; } if(ch == (unsigned char)'&') { x->state = YXMLS_misc2a; return yxml_refstart(x, ch); } if(yxml_isChar(ch)) return yxml_datacontent(x, ch); break; case YXMLS_misc2a: if(yxml_isRef(ch)) return yxml_ref(x, ch); if(ch == (unsigned char)'\x3b') { x->state = YXMLS_misc2; return yxml_refcontent(x, ch); } break; case YXMLS_misc3: if(yxml_isSP(ch)) return YXML_OK; if(ch == (unsigned char)'<') { x->state = YXMLS_le3; return YXML_OK; } break; case YXMLS_pi0: if(yxml_isNameStart(ch)) { x->state = YXMLS_pi1; return yxml_pistart(x, ch); } break; case YXMLS_pi1: if(yxml_isName(ch)) return yxml_piname(x, ch); if(ch == (unsigned char)'?') { x->state = YXMLS_pi4; return yxml_pinameend(x, ch); } if(yxml_isSP(ch)) { x->state = YXMLS_pi2; return yxml_pinameend(x, ch); } break; case YXMLS_pi2: if(ch == (unsigned char)'?') { x->state = YXMLS_pi3; return YXML_OK; } if(yxml_isChar(ch)) return yxml_datapi1(x, ch); break; case YXMLS_pi3: if(ch == (unsigned char)'>') { x->state = x->nextstate; return yxml_pivalend(x, ch); } if(yxml_isChar(ch)) { x->state = YXMLS_pi2; return yxml_datapi2(x, ch); } break; case YXMLS_pi4: if(ch == (unsigned char)'>') { x->state = x->nextstate; return yxml_pivalend(x, ch); } break; case YXMLS_std0: if(yxml_isSP(ch)) return YXML_OK; if(ch == (unsigned char)'=') { x->state = YXMLS_std1; return YXML_OK; } break; case YXMLS_std1: if(yxml_isSP(ch)) return YXML_OK; if(ch == (unsigned char)'\'' || ch == (unsigned char)'"') { x->state = YXMLS_std2; x->quote = ch; return YXML_OK; } break; case YXMLS_std2: if(ch == (unsigned char)'y') { x->state = YXMLS_string; x->nextstate = YXMLS_std3; x->string = (unsigned char *)"es"; return YXML_OK; } if(ch == (unsigned char)'n') { x->state = YXMLS_string; x->nextstate = YXMLS_std3; x->string = (unsigned char *)"o"; return YXML_OK; } break; case YXMLS_std3: if(x->quote == ch) { x->state = YXMLS_xmldecl6; return YXML_OK; } break; case YXMLS_ver0: if(yxml_isSP(ch)) return YXML_OK; if(ch == (unsigned char)'=') { x->state = YXMLS_ver1; return YXML_OK; } break; case YXMLS_ver1: if(yxml_isSP(ch)) return YXML_OK; if(ch == (unsigned char)'\'' || ch == (unsigned char)'"') { x->state = YXMLS_string; x->quote = ch; x->nextstate = YXMLS_ver2; x->string = (unsigned char *)"1."; return YXML_OK; } break; case YXMLS_ver2: if(yxml_isNum(ch)) { x->state = YXMLS_ver3; return YXML_OK; } break; case YXMLS_ver3: if(yxml_isNum(ch)) return YXML_OK; if(x->quote == ch) { x->state = YXMLS_xmldecl2; return YXML_OK; } break; case YXMLS_xmldecl0: if(yxml_isSP(ch)) { x->state = YXMLS_xmldecl1; return YXML_OK; } break; case YXMLS_xmldecl1: if(yxml_isSP(ch)) return YXML_OK; if(ch == (unsigned char)'v') { x->state = YXMLS_string; x->nextstate = YXMLS_ver0; x->string = (unsigned char *)"ersion"; return YXML_OK; } break; case YXMLS_xmldecl2: if(yxml_isSP(ch)) { x->state = YXMLS_xmldecl3; return YXML_OK; } if(ch == (unsigned char)'?') { x->state = YXMLS_xmldecl7; return YXML_OK; } break; case YXMLS_xmldecl3: if(yxml_isSP(ch)) return YXML_OK; if(ch == (unsigned char)'?') { x->state = YXMLS_xmldecl7; return YXML_OK; } if(ch == (unsigned char)'e') { x->state = YXMLS_string; x->nextstate = YXMLS_enc0; x->string = (unsigned char *)"ncoding"; return YXML_OK; } if(ch == (unsigned char)'s') { x->state = YXMLS_string; x->nextstate = YXMLS_std0; x->string = (unsigned char *)"tandalone"; return YXML_OK; } break; case YXMLS_xmldecl4: if(yxml_isSP(ch)) { x->state = YXMLS_xmldecl5; return YXML_OK; } if(ch == (unsigned char)'?') { x->state = YXMLS_xmldecl7; return YXML_OK; } break; case YXMLS_xmldecl5: if(yxml_isSP(ch)) return YXML_OK; if(ch == (unsigned char)'?') { x->state = YXMLS_xmldecl7; return YXML_OK; } if(ch == (unsigned char)'s') { x->state = YXMLS_string; x->nextstate = YXMLS_std0; x->string = (unsigned char *)"tandalone"; return YXML_OK; } break; case YXMLS_xmldecl6: if(yxml_isSP(ch)) return YXML_OK; if(ch == (unsigned char)'?') { x->state = YXMLS_xmldecl7; return YXML_OK; } break; case YXMLS_xmldecl7: if(ch == (unsigned char)'>') { x->state = YXMLS_misc1; return YXML_OK; } break; } return YXML_ESYN; } yxml_ret_t yxml_eof(yxml_t *x) { if(x->state != YXMLS_misc3) return YXML_EEOF; return YXML_OK; } /* vim: set noet sw=4 ts=4: */ ncdc-1.23.1/aclocal.m40000644000175000017500000014616214314535760011310 00000000000000# generated automatically by aclocal 1.16.5 -*- Autoconf -*- # Copyright (C) 1996-2021 Free Software Foundation, Inc. # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. m4_ifndef([AC_CONFIG_MACRO_DIRS], [m4_defun([_AM_CONFIG_MACRO_DIRS], [])m4_defun([AC_CONFIG_MACRO_DIRS], [_AM_CONFIG_MACRO_DIRS($@)])]) m4_ifndef([AC_AUTOCONF_VERSION], [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl m4_if(m4_defn([AC_AUTOCONF_VERSION]), [2.71],, [m4_warning([this file was generated for autoconf 2.71. 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'.])]) # pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*- # serial 11 (pkg-config-0.29.1) dnl Copyright © 2004 Scott James Remnant . dnl Copyright © 2012-2015 Dan Nicholson dnl dnl This program is free software; you can redistribute it and/or modify dnl it under the terms of the GNU General Public License as published by dnl the Free Software Foundation; either version 2 of the License, or dnl (at your option) any later version. dnl dnl This program is distributed in the hope that it will be useful, but dnl WITHOUT ANY WARRANTY; without even the implied warranty of dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU dnl General Public License for more details. dnl dnl You should have received a copy of the GNU General Public License dnl along with this program; if not, write to the Free Software dnl Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA dnl 02111-1307, USA. dnl dnl As a special exception to the GNU General Public License, if you dnl distribute this file as part of a program that contains a dnl configuration script generated by Autoconf, you may include it under dnl the same distribution terms that you use for the rest of that dnl program. dnl PKG_PREREQ(MIN-VERSION) dnl ----------------------- dnl Since: 0.29 dnl dnl Verify that the version of the pkg-config macros are at least dnl MIN-VERSION. Unlike PKG_PROG_PKG_CONFIG, which checks the user's dnl installed version of pkg-config, this checks the developer's version dnl of pkg.m4 when generating configure. dnl dnl To ensure that this macro is defined, also add: dnl m4_ifndef([PKG_PREREQ], dnl [m4_fatal([must install pkg-config 0.29 or later before running autoconf/autogen])]) dnl dnl See the "Since" comment for each macro you use to see what version dnl of the macros you require. m4_defun([PKG_PREREQ], [m4_define([PKG_MACROS_VERSION], [0.29.1]) m4_if(m4_version_compare(PKG_MACROS_VERSION, [$1]), -1, [m4_fatal([pkg.m4 version $1 or higher is required but ]PKG_MACROS_VERSION[ found])]) ])dnl PKG_PREREQ dnl PKG_PROG_PKG_CONFIG([MIN-VERSION]) dnl ---------------------------------- dnl Since: 0.16 dnl dnl Search for the pkg-config tool and set the PKG_CONFIG variable to dnl first found in the path. Checks that the version of pkg-config found dnl is at least MIN-VERSION. If MIN-VERSION is not specified, 0.9.0 is dnl used since that's the first version where most current features of dnl pkg-config existed. AC_DEFUN([PKG_PROG_PKG_CONFIG], [m4_pattern_forbid([^_?PKG_[A-Z_]+$]) m4_pattern_allow([^PKG_CONFIG(_(PATH|LIBDIR|SYSROOT_DIR|ALLOW_SYSTEM_(CFLAGS|LIBS)))?$]) m4_pattern_allow([^PKG_CONFIG_(DISABLE_UNINSTALLED|TOP_BUILD_DIR|DEBUG_SPEW)$]) AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility]) AC_ARG_VAR([PKG_CONFIG_PATH], [directories to add to pkg-config's search path]) AC_ARG_VAR([PKG_CONFIG_LIBDIR], [path overriding pkg-config's built-in search path]) if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then AC_PATH_TOOL([PKG_CONFIG], [pkg-config]) fi if test -n "$PKG_CONFIG"; then _pkg_min_version=m4_default([$1], [0.9.0]) AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version]) if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) PKG_CONFIG="" fi fi[]dnl ])dnl PKG_PROG_PKG_CONFIG dnl PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) dnl ------------------------------------------------------------------- dnl Since: 0.18 dnl dnl Check to see whether a particular set of modules exists. Similar to dnl PKG_CHECK_MODULES(), but does not set variables or print errors. dnl dnl Please remember that m4 expands AC_REQUIRE([PKG_PROG_PKG_CONFIG]) dnl only at the first occurence in configure.ac, so if the first place dnl it's called might be skipped (such as if it is within an "if", you dnl have to call PKG_CHECK_EXISTS manually AC_DEFUN([PKG_CHECK_EXISTS], [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl if test -n "$PKG_CONFIG" && \ AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then m4_default([$2], [:]) m4_ifvaln([$3], [else $3])dnl fi]) dnl _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES]) dnl --------------------------------------------- dnl Internal wrapper calling pkg-config via PKG_CONFIG and setting dnl pkg_failed based on the result. m4_define([_PKG_CONFIG], [if test -n "$$1"; then pkg_cv_[]$1="$$1" elif test -n "$PKG_CONFIG"; then PKG_CHECK_EXISTS([$3], [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes ], [pkg_failed=yes]) else pkg_failed=untried fi[]dnl ])dnl _PKG_CONFIG dnl _PKG_SHORT_ERRORS_SUPPORTED dnl --------------------------- dnl Internal check to see if pkg-config supports short errors. AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED], [AC_REQUIRE([PKG_PROG_PKG_CONFIG]) if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi[]dnl ])dnl _PKG_SHORT_ERRORS_SUPPORTED dnl PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], dnl [ACTION-IF-NOT-FOUND]) dnl -------------------------------------------------------------- dnl Since: 0.4.0 dnl dnl Note that if there is a possibility the first call to dnl PKG_CHECK_MODULES might not happen, you should be sure to include an dnl explicit call to PKG_PROG_PKG_CONFIG in your configure.ac AC_DEFUN([PKG_CHECK_MODULES], [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl pkg_failed=no AC_MSG_CHECKING([for $1]) _PKG_CONFIG([$1][_CFLAGS], [cflags], [$2]) _PKG_CONFIG([$1][_LIBS], [libs], [$2]) m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS and $1[]_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details.]) if test $pkg_failed = yes; then AC_MSG_RESULT([no]) _PKG_SHORT_ERRORS_SUPPORTED if test $_pkg_short_errors_supported = yes; then $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$2" 2>&1` else $1[]_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$2" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD m4_default([$4], [AC_MSG_ERROR( [Package requirements ($2) were not met: $$1_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. _PKG_TEXT])[]dnl ]) elif test $pkg_failed = untried; then AC_MSG_RESULT([no]) m4_default([$4], [AC_MSG_FAILURE( [The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. _PKG_TEXT To get pkg-config, see .])[]dnl ]) else $1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS $1[]_LIBS=$pkg_cv_[]$1[]_LIBS AC_MSG_RESULT([yes]) $3 fi[]dnl ])dnl PKG_CHECK_MODULES dnl PKG_CHECK_MODULES_STATIC(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], dnl [ACTION-IF-NOT-FOUND]) dnl --------------------------------------------------------------------- dnl Since: 0.29 dnl dnl Checks for existence of MODULES and gathers its build flags with dnl static libraries enabled. Sets VARIABLE-PREFIX_CFLAGS from --cflags dnl and VARIABLE-PREFIX_LIBS from --libs. dnl dnl Note that if there is a possibility the first call to dnl PKG_CHECK_MODULES_STATIC might not happen, you should be sure to dnl include an explicit call to PKG_PROG_PKG_CONFIG in your dnl configure.ac. AC_DEFUN([PKG_CHECK_MODULES_STATIC], [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl _save_PKG_CONFIG=$PKG_CONFIG PKG_CONFIG="$PKG_CONFIG --static" PKG_CHECK_MODULES($@) PKG_CONFIG=$_save_PKG_CONFIG[]dnl ])dnl PKG_CHECK_MODULES_STATIC dnl PKG_INSTALLDIR([DIRECTORY]) dnl ------------------------- dnl Since: 0.27 dnl dnl Substitutes the variable pkgconfigdir as the location where a module dnl should install pkg-config .pc files. By default the directory is dnl $libdir/pkgconfig, but the default can be changed by passing dnl DIRECTORY. The user can override through the --with-pkgconfigdir dnl parameter. AC_DEFUN([PKG_INSTALLDIR], [m4_pushdef([pkg_default], [m4_default([$1], ['${libdir}/pkgconfig'])]) m4_pushdef([pkg_description], [pkg-config installation directory @<:@]pkg_default[@:>@]) AC_ARG_WITH([pkgconfigdir], [AS_HELP_STRING([--with-pkgconfigdir], pkg_description)],, [with_pkgconfigdir=]pkg_default) AC_SUBST([pkgconfigdir], [$with_pkgconfigdir]) m4_popdef([pkg_default]) m4_popdef([pkg_description]) ])dnl PKG_INSTALLDIR dnl PKG_NOARCH_INSTALLDIR([DIRECTORY]) dnl -------------------------------- dnl Since: 0.27 dnl dnl Substitutes the variable noarch_pkgconfigdir as the location where a dnl module should install arch-independent pkg-config .pc files. By dnl default the directory is $datadir/pkgconfig, but the default can be dnl changed by passing DIRECTORY. The user can override through the dnl --with-noarch-pkgconfigdir parameter. AC_DEFUN([PKG_NOARCH_INSTALLDIR], [m4_pushdef([pkg_default], [m4_default([$1], ['${datadir}/pkgconfig'])]) m4_pushdef([pkg_description], [pkg-config arch-independent installation directory @<:@]pkg_default[@:>@]) AC_ARG_WITH([noarch-pkgconfigdir], [AS_HELP_STRING([--with-noarch-pkgconfigdir], pkg_description)],, [with_noarch_pkgconfigdir=]pkg_default) AC_SUBST([noarch_pkgconfigdir], [$with_noarch_pkgconfigdir]) m4_popdef([pkg_default]) m4_popdef([pkg_description]) ])dnl PKG_NOARCH_INSTALLDIR dnl PKG_CHECK_VAR(VARIABLE, MODULE, CONFIG-VARIABLE, dnl [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) dnl ------------------------------------------- dnl Since: 0.28 dnl dnl Retrieves the value of the pkg-config variable for the given module. AC_DEFUN([PKG_CHECK_VAR], [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl AC_ARG_VAR([$1], [value of $3 for $2, overriding pkg-config])dnl _PKG_CONFIG([$1], [variable="][$3]["], [$2]) AS_VAR_COPY([$1], [pkg_cv_][$1]) AS_VAR_IF([$1], [""], [$5], [$4])dnl ])dnl PKG_CHECK_VAR dnl PKG_WITH_MODULES(VARIABLE-PREFIX, MODULES, dnl [ACTION-IF-FOUND],[ACTION-IF-NOT-FOUND], dnl [DESCRIPTION], [DEFAULT]) dnl ------------------------------------------ dnl dnl Prepare a "--with-" configure option using the lowercase dnl [VARIABLE-PREFIX] name, merging the behaviour of AC_ARG_WITH and dnl PKG_CHECK_MODULES in a single macro. AC_DEFUN([PKG_WITH_MODULES], [ m4_pushdef([with_arg], m4_tolower([$1])) m4_pushdef([description], [m4_default([$5], [build with ]with_arg[ support])]) m4_pushdef([def_arg], [m4_default([$6], [auto])]) m4_pushdef([def_action_if_found], [AS_TR_SH([with_]with_arg)=yes]) m4_pushdef([def_action_if_not_found], [AS_TR_SH([with_]with_arg)=no]) m4_case(def_arg, [yes],[m4_pushdef([with_without], [--without-]with_arg)], [m4_pushdef([with_without],[--with-]with_arg)]) AC_ARG_WITH(with_arg, AS_HELP_STRING(with_without, description[ @<:@default=]def_arg[@:>@]),, [AS_TR_SH([with_]with_arg)=def_arg]) AS_CASE([$AS_TR_SH([with_]with_arg)], [yes],[PKG_CHECK_MODULES([$1],[$2],$3,$4)], [auto],[PKG_CHECK_MODULES([$1],[$2], [m4_n([def_action_if_found]) $3], [m4_n([def_action_if_not_found]) $4])]) m4_popdef([with_arg]) m4_popdef([description]) m4_popdef([def_arg]) ])dnl PKG_WITH_MODULES dnl PKG_HAVE_WITH_MODULES(VARIABLE-PREFIX, MODULES, dnl [DESCRIPTION], [DEFAULT]) dnl ----------------------------------------------- dnl dnl Convenience macro to trigger AM_CONDITIONAL after PKG_WITH_MODULES dnl check._[VARIABLE-PREFIX] is exported as make variable. AC_DEFUN([PKG_HAVE_WITH_MODULES], [ PKG_WITH_MODULES([$1],[$2],,,[$3],[$4]) AM_CONDITIONAL([HAVE_][$1], [test "$AS_TR_SH([with_]m4_tolower([$1]))" = "yes"]) ])dnl PKG_HAVE_WITH_MODULES dnl PKG_HAVE_DEFINE_WITH_MODULES(VARIABLE-PREFIX, MODULES, dnl [DESCRIPTION], [DEFAULT]) dnl ------------------------------------------------------ dnl dnl Convenience macro to run AM_CONDITIONAL and AC_DEFINE after dnl PKG_WITH_MODULES check. HAVE_[VARIABLE-PREFIX] is exported as make dnl and preprocessor variable. AC_DEFUN([PKG_HAVE_DEFINE_WITH_MODULES], [ PKG_HAVE_WITH_MODULES([$1],[$2],[$3],[$4]) AS_IF([test "$AS_TR_SH([with_]m4_tolower([$1]))" = "yes"], [AC_DEFINE([HAVE_][$1], 1, [Enable ]m4_tolower([$1])[ support])]) ])dnl PKG_HAVE_DEFINE_WITH_MODULES # Copyright (C) 2002-2021 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # AM_AUTOMAKE_VERSION(VERSION) # ---------------------------- # Automake X.Y traces this macro to ensure aclocal.m4 has been # 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.16' 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.16.5], [], [AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl ]) # _AM_AUTOCONF_VERSION(VERSION) # ----------------------------- # aclocal traces this macro to find the Autoconf version. # This is a private macro too. Using m4_define simplifies # the logic in aclocal, which can simply ignore this definition. m4_define([_AM_AUTOCONF_VERSION], []) # AM_SET_CURRENT_AUTOMAKE_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.16.5])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-2021 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # For projects using AC_CONFIG_AUX_DIR([foo]), Autoconf sets # $ac_aux_dir to '$srcdir/foo'. In other projects, it is set to # '$srcdir', '$srcdir/..', or '$srcdir/../..'. # # Of course, Automake must honor this variable whenever it calls a # tool from the auxiliary directory. The problem is that $srcdir (and # therefore $ac_aux_dir as well) can be either absolute or relative, # depending on how configure is run. This is pretty annoying, since # it makes $ac_aux_dir quite unusable in subdirectories: in the top # source directory, any form will work fine, but in subdirectories a # relative path needs to be adjusted first. # # $ac_aux_dir/missing # fails when called from a subdirectory if $ac_aux_dir is relative # $top_srcdir/$ac_aux_dir/missing # fails if $ac_aux_dir is absolute, # fails when called from a subdirectory in a VPATH build with # a relative $ac_aux_dir # # The reason of the latter failure is that $top_srcdir and $ac_aux_dir # are both prefixed by $srcdir. In an in-source build this is usually # harmless because $srcdir is '.', but things will broke when you # start a VPATH build or use an absolute $srcdir. # # So we could use something similar to $top_srcdir/$ac_aux_dir/missing, # iff we strip the leading $srcdir from $ac_aux_dir. That would be: # am_aux_dir='\$(top_srcdir)/'`expr "$ac_aux_dir" : "$srcdir//*\(.*\)"` # and then we would define $MISSING as # MISSING="\${SHELL} $am_aux_dir/missing" # This will work as long as MISSING is not called from configure, because # unfortunately $(top_srcdir) has no meaning in configure. # However there are other variables, like CC, which are often used in # configure, and could therefore not use this "fixed" $ac_aux_dir. # # Another solution, used here, is to always expand $ac_aux_dir to an # absolute PATH. The drawback is that using absolute paths prevent a # configured tree to be moved without reconfiguration. AC_DEFUN([AM_AUX_DIR_EXPAND], [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-2021 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # AM_CONDITIONAL(NAME, SHELL-CONDITION) # ------------------------------------- # Define a conditional. AC_DEFUN([AM_CONDITIONAL], [AC_PREREQ([2.52])dnl m4_if([$1], [TRUE], [AC_FATAL([$0: invalid condition: $1])], [$1], [FALSE], [AC_FATAL([$0: invalid condition: $1])])dnl AC_SUBST([$1_TRUE])dnl AC_SUBST([$1_FALSE])dnl _AM_SUBST_NOTMAKE([$1_TRUE])dnl _AM_SUBST_NOTMAKE([$1_FALSE])dnl m4_define([_AM_COND_VALUE_$1], [$2])dnl if $2; then $1_TRUE= $1_FALSE='#' else $1_TRUE='#' $1_FALSE= fi AC_CONFIG_COMMANDS_PRE( [if test -z "${$1_TRUE}" && test -z "${$1_FALSE}"; then AC_MSG_ERROR([[conditional "$1" was never defined. Usually this means the macro was only invoked conditionally.]]) fi])]) # Copyright (C) 1999-2021 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # There are a few dirty hacks below to avoid letting 'AC_PROG_CC' be # written in clear, in which case automake, when reading aclocal.m4, # will think it sees a *use*, and therefore will trigger all it's # C support machinery. Also note that it means that autoscan, seeing # CC etc. in the Makefile, will ask for an AC_PROG_CC use... # _AM_DEPENDENCIES(NAME) # ---------------------- # See how the compiler implements dependency checking. # NAME is "CC", "CXX", "OBJC", "OBJCXX", "UPC", or "GJC". # We try a few techniques and use that to set a single cache variable. # # We don't AC_REQUIRE the corresponding AC_PROG_CC since the latter was # modified to invoke _AM_DEPENDENCIES(CC); we would have a circular # dependency, and given that the user is not expected to run this macro, # just rely on AC_PROG_CC. AC_DEFUN([_AM_DEPENDENCIES], [AC_REQUIRE([AM_SET_DEPDIR])dnl AC_REQUIRE([AM_OUTPUT_DEPENDENCY_COMMANDS])dnl AC_REQUIRE([AM_MAKE_INCLUDE])dnl AC_REQUIRE([AM_DEP_TRACK])dnl m4_if([$1], [CC], [depcc="$CC" am_compiler_list=], [$1], [CXX], [depcc="$CXX" am_compiler_list=], [$1], [OBJC], [depcc="$OBJC" am_compiler_list='gcc3 gcc'], [$1], [OBJCXX], [depcc="$OBJCXX" am_compiler_list='gcc3 gcc'], [$1], [UPC], [depcc="$UPC" am_compiler_list=], [$1], [GCJ], [depcc="$GCJ" am_compiler_list='gcc3 gcc'], [depcc="$$1" am_compiler_list=]) AC_CACHE_CHECK([dependency style of $depcc], [am_cv_$1_dependencies_compiler_type], [if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then # We make a subdir and do the tests there. Otherwise we can end up # making bogus files that we don't know about and never remove. For # instance it was reported that on HP-UX the gcc test will end up # making a dummy file named 'D' -- because '-MD' means "put the output # in D". rm -rf conftest.dir mkdir conftest.dir # Copy depcomp to subdir because otherwise we won't find it if we're # using a relative directory. cp "$am_depcomp" conftest.dir cd conftest.dir # We will build objects and dependencies in a subdirectory because # it helps to detect inapplicable dependency modes. For instance # both Tru64's cc and ICC support -MD to output dependencies as a # side effect of compilation, but ICC will put the dependencies in # the current directory while Tru64 will put them in the object # directory. mkdir sub am_cv_$1_dependencies_compiler_type=none if test "$am_compiler_list" = ""; then am_compiler_list=`sed -n ['s/^#*\([a-zA-Z0-9]*\))$/\1/p'] < ./depcomp` fi am__universal=false m4_case([$1], [CC], [case " $depcc " in #( *\ -arch\ *\ -arch\ *) am__universal=true ;; esac], [CXX], [case " $depcc " in #( *\ -arch\ *\ -arch\ *) am__universal=true ;; esac]) for depmode in $am_compiler_list; do # Setup a source with many dependencies, because some compilers # like to wrap large dependency lists on column 80 (with \), and # we should not choose a depcomp mode which is confused by this. # # We need to recreate these files for each test, as the compiler may # overwrite some of them when testing with obscure command lines. # This happens at least with the AIX C compiler. : > sub/conftest.c for i in 1 2 3 4 5 6; do echo '#include "conftst'$i'.h"' >> sub/conftest.c # Using ": > sub/conftst$i.h" creates only sub/conftst1.h with # Solaris 10 /bin/sh. echo '/* dummy */' > sub/conftst$i.h done echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf # We check with '-c' and '-o' for the sake of the "dashmstdout" # mode. It turns out that the SunPro C++ compiler does not properly # handle '-M -o', and we need to detect this. Also, some Intel # versions had trouble with output in subdirs. am__obj=sub/conftest.${OBJEXT-o} am__minus_obj="-o $am__obj" case $depmode in gcc) # This depmode causes a compiler race in universal mode. test "$am__universal" = false || continue ;; nosideeffect) # After this tag, mechanisms are not by side-effect, so they'll # only be used when explicitly requested. if test "x$enable_dependency_tracking" = xyes; then continue else break fi ;; msvc7 | msvc7msys | msvisualcpp | msvcmsys) # This compiler won't grok '-c -o', but also, the minuso test has # not run yet. These depmodes are late enough in the game, and # so weak that their functioning should not be impacted. am__obj=conftest.${OBJEXT-o} am__minus_obj= ;; none) break ;; esac if depmode=$depmode \ source=sub/conftest.c object=$am__obj \ depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \ >/dev/null 2>conftest.err && grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 && grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && grep $am__obj sub/conftest.Po > /dev/null 2>&1 && ${MAKE-make} -s -f confmf > /dev/null 2>&1; then # icc doesn't choke on unknown options, it will just issue warnings # or remarks (even with -Werror). So we grep stderr for any message # that says an option was ignored or not supported. # When given -MP, icc 7.0 and 7.1 complain thusly: # icc: Command line warning: ignoring option '-M'; no argument required # The diagnosis changed in icc 8.0: # icc: Command line remark: option '-MP' not supported if (grep 'ignoring option' conftest.err || grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else am_cv_$1_dependencies_compiler_type=$depmode break fi fi done cd .. rm -rf conftest.dir else am_cv_$1_dependencies_compiler_type=none fi ]) AC_SUBST([$1DEPMODE], [depmode=$am_cv_$1_dependencies_compiler_type]) AM_CONDITIONAL([am__fastdep$1], [ test "x$enable_dependency_tracking" != xno \ && test "$am_cv_$1_dependencies_compiler_type" = gcc3]) ]) # AM_SET_DEPDIR # ------------- # Choose a directory name for dependency files. # This macro is AC_REQUIREd in _AM_DEPENDENCIES. AC_DEFUN([AM_SET_DEPDIR], [AC_REQUIRE([AM_SET_LEADING_DOT])dnl AC_SUBST([DEPDIR], ["${am__leading_dot}deps"])dnl ]) # AM_DEP_TRACK # ------------ AC_DEFUN([AM_DEP_TRACK], [AC_ARG_ENABLE([dependency-tracking], [dnl AS_HELP_STRING( [--enable-dependency-tracking], [do not reject slow dependency extractors]) AS_HELP_STRING( [--disable-dependency-tracking], [speeds up one-time build])]) if test "x$enable_dependency_tracking" != xno; then am_depcomp="$ac_aux_dir/depcomp" AMDEPBACKSLASH='\' am__nodep='_no' fi AM_CONDITIONAL([AMDEP], [test "x$enable_dependency_tracking" != xno]) AC_SUBST([AMDEPBACKSLASH])dnl _AM_SUBST_NOTMAKE([AMDEPBACKSLASH])dnl AC_SUBST([am__nodep])dnl _AM_SUBST_NOTMAKE([am__nodep])dnl ]) # Generate code to set up dependency tracking. -*- Autoconf -*- # Copyright (C) 1999-2021 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # _AM_OUTPUT_DEPENDENCY_COMMANDS # ------------------------------ AC_DEFUN([_AM_OUTPUT_DEPENDENCY_COMMANDS], [{ # Older Autoconf quotes --file arguments for eval, but not when files # are listed without --file. Let's play safe and only enable the eval # if we detect the quoting. # TODO: see whether this extra hack can be removed once we start # requiring Autoconf 2.70 or later. AS_CASE([$CONFIG_FILES], [*\'*], [eval set x "$CONFIG_FILES"], [*], [set x $CONFIG_FILES]) shift # Used to flag and report bootstrapping failures. am_rc=0 for am_mf do # Strip MF so we end up with the name of the file. am_mf=`AS_ECHO(["$am_mf"]) | sed -e 's/:.*$//'` # Check whether this is an Automake generated Makefile which includes # dependency-tracking related rules and includes. # Grep'ing the whole file directly is not great: AIX grep has a line # limit of 2048, but all sed's we know have understand at least 4000. sed -n 's,^am--depfiles:.*,X,p' "$am_mf" | grep X >/dev/null 2>&1 \ || continue am_dirpart=`AS_DIRNAME(["$am_mf"])` am_filepart=`AS_BASENAME(["$am_mf"])` AM_RUN_LOG([cd "$am_dirpart" \ && sed -e '/# am--include-marker/d' "$am_filepart" \ | $MAKE -f - am--depfiles]) || am_rc=$? done if test $am_rc -ne 0; then AC_MSG_FAILURE([Something went wrong bootstrapping makefile fragments for automatic dependency tracking. If GNU make was not used, consider re-running the configure script with MAKE="gmake" (or whatever is necessary). You can also try re-running configure with the '--disable-dependency-tracking' option to at least be able to build the package (albeit without support for automatic dependency tracking).]) fi AS_UNSET([am_dirpart]) AS_UNSET([am_filepart]) AS_UNSET([am_mf]) AS_UNSET([am_rc]) rm -f conftest-deps.mk } ])# _AM_OUTPUT_DEPENDENCY_COMMANDS # AM_OUTPUT_DEPENDENCY_COMMANDS # ----------------------------- # This macro should only be invoked once -- use via AC_REQUIRE. # # This code is only required when automatic dependency tracking is enabled. # This creates each '.Po' and '.Plo' makefile fragment that we'll need in # order to bootstrap the dependency handling code. AC_DEFUN([AM_OUTPUT_DEPENDENCY_COMMANDS], [AC_CONFIG_COMMANDS([depfiles], [test x"$AMDEP_TRUE" != x"" || _AM_OUTPUT_DEPENDENCY_COMMANDS], [AMDEP_TRUE="$AMDEP_TRUE" MAKE="${MAKE-make}"])]) # Do all the work for Automake. -*- Autoconf -*- # Copyright (C) 1996-2021 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This macro actually does too much. Some checks are only needed if # your package does certain things. But this isn't really a big deal. dnl Redefine AC_PROG_CC to automatically invoke _AM_PROG_CC_C_O. m4_define([AC_PROG_CC], m4_defn([AC_PROG_CC]) [_AM_PROG_CC_C_O ]) # AM_INIT_AUTOMAKE(PACKAGE, VERSION, [NO-DEFINE]) # AM_INIT_AUTOMAKE([OPTIONS]) # ----------------------------------------------- # The call with PACKAGE and VERSION arguments is the old style # call (pre autoconf-2.50), which is being phased out. PACKAGE # and VERSION should now be passed to AC_INIT and removed from # the call to AM_INIT_AUTOMAKE. # We support both call styles for the transition. After # the next Automake release, Autoconf can make the AC_INIT # arguments mandatory, and then we can depend on a new Autoconf # release and drop the old call support. AC_DEFUN([AM_INIT_AUTOMAKE], [AC_PREREQ([2.65])dnl m4_ifdef([_$0_ALREADY_INIT], [m4_fatal([$0 expanded multiple times ]m4_defn([_$0_ALREADY_INIT]))], [m4_define([_$0_ALREADY_INIT], m4_expansion_stack)])dnl dnl Autoconf wants to disallow AM_ names. We explicitly allow dnl the ones we care about. m4_pattern_allow([^AM_[A-Z]+FLAGS$])dnl AC_REQUIRE([AM_SET_CURRENT_AUTOMAKE_VERSION])dnl AC_REQUIRE([AC_PROG_INSTALL])dnl if test "`cd $srcdir && pwd`" != "`pwd`"; then # Use -I$(srcdir) only when $(srcdir) != ., so that make's output # is not polluted with repeated "-I." AC_SUBST([am__isrc], [' -I$(srcdir)'])_AM_SUBST_NOTMAKE([am__isrc])dnl # test to see if srcdir already configured if test -f $srcdir/config.status; then AC_MSG_ERROR([source directory already configured; run "make distclean" there first]) fi fi # test whether we have cygpath if test -z "$CYGPATH_W"; then if (cygpath --version) >/dev/null 2>/dev/null; then CYGPATH_W='cygpath -w' else CYGPATH_W=echo fi fi AC_SUBST([CYGPATH_W]) # Define the identity of the package. dnl Distinguish between old-style and new-style calls. m4_ifval([$2], [AC_DIAGNOSE([obsolete], [$0: two- and three-arguments forms are deprecated.]) m4_ifval([$3], [_AM_SET_OPTION([no-define])])dnl AC_SUBST([PACKAGE], [$1])dnl AC_SUBST([VERSION], [$2])], [_AM_SET_OPTIONS([$1])dnl dnl Diagnose old-style AC_INIT with new-style AM_AUTOMAKE_INIT. m4_if( m4_ifset([AC_PACKAGE_NAME], [ok]):m4_ifset([AC_PACKAGE_VERSION], [ok]), [ok:ok],, [m4_fatal([AC_INIT should be called with package and version arguments])])dnl AC_SUBST([PACKAGE], ['AC_PACKAGE_TARNAME'])dnl AC_SUBST([VERSION], ['AC_PACKAGE_VERSION'])])dnl _AM_IF_OPTION([no-define],, [AC_DEFINE_UNQUOTED([PACKAGE], ["$PACKAGE"], [Name of package]) AC_DEFINE_UNQUOTED([VERSION], ["$VERSION"], [Version number of package])])dnl # Some tools Automake needs. AC_REQUIRE([AM_SANITY_CHECK])dnl AC_REQUIRE([AC_ARG_PROGRAM])dnl AM_MISSING_PROG([ACLOCAL], [aclocal-${am__api_version}]) AM_MISSING_PROG([AUTOCONF], [autoconf]) AM_MISSING_PROG([AUTOMAKE], [automake-${am__api_version}]) AM_MISSING_PROG([AUTOHEADER], [autoheader]) AM_MISSING_PROG([MAKEINFO], [makeinfo]) AC_REQUIRE([AM_PROG_INSTALL_SH])dnl AC_REQUIRE([AM_PROG_INSTALL_STRIP])dnl AC_REQUIRE([AC_PROG_MKDIR_P])dnl # For better backward compatibility. To be removed once Automake 1.9.x # dies out for good. For more background, see: # # AC_SUBST([mkdir_p], ['$(MKDIR_P)']) # 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 _AM_IF_OPTION([tar-ustar], [_AM_PROG_TAR([ustar])], [_AM_IF_OPTION([tar-pax], [_AM_PROG_TAR([pax])], [_AM_PROG_TAR([v7])])]) _AM_IF_OPTION([no-dependencies],, [AC_PROVIDE_IFELSE([AC_PROG_CC], [_AM_DEPENDENCIES([CC])], [m4_define([AC_PROG_CC], m4_defn([AC_PROG_CC])[_AM_DEPENDENCIES([CC])])])dnl AC_PROVIDE_IFELSE([AC_PROG_CXX], [_AM_DEPENDENCIES([CXX])], [m4_define([AC_PROG_CXX], m4_defn([AC_PROG_CXX])[_AM_DEPENDENCIES([CXX])])])dnl AC_PROVIDE_IFELSE([AC_PROG_OBJC], [_AM_DEPENDENCIES([OBJC])], [m4_define([AC_PROG_OBJC], m4_defn([AC_PROG_OBJC])[_AM_DEPENDENCIES([OBJC])])])dnl AC_PROVIDE_IFELSE([AC_PROG_OBJCXX], [_AM_DEPENDENCIES([OBJCXX])], [m4_define([AC_PROG_OBJCXX], m4_defn([AC_PROG_OBJCXX])[_AM_DEPENDENCIES([OBJCXX])])])dnl ]) # Variables for tags utilities; see am/tags.am if test -z "$CTAGS"; then CTAGS=ctags fi AC_SUBST([CTAGS]) if test -z "$ETAGS"; then ETAGS=etags fi AC_SUBST([ETAGS]) if test -z "$CSCOPE"; then CSCOPE=cscope fi AC_SUBST([CSCOPE]) AC_REQUIRE([AM_SILENT_RULES])dnl dnl The testsuite driver may need to know about EXEEXT, so add the dnl 'am__EXEEXT' conditional if _AM_COMPILER_EXEEXT was seen. This dnl macro is hooked onto _AC_COMPILER_EXEEXT early, see below. AC_CONFIG_COMMANDS_PRE(dnl [m4_provide_if([_AM_COMPILER_EXEEXT], [AM_CONDITIONAL([am__EXEEXT], [test -n "$EXEEXT"])])])dnl # POSIX will say in a future version that running "rm -f" with no argument # is OK; and we want to be able to make that assumption in our Makefile # recipes. So use an aggressive probe to check that the usage we want is # actually supported "in the wild" to an acceptable degree. # See automake bug#10828. # To make any issue more visible, cause the running configure to be aborted # by default if the 'rm' program in use doesn't match our expectations; the # user can still override this though. if rm -f && rm -fr && rm -rf; then : OK; else cat >&2 <<'END' Oops! Your 'rm' program seems unable to run without file operands specified on the command line, even when the '-f' option is present. This is contrary to the behaviour of most rm programs out there, and not conforming with the upcoming POSIX standard: Please tell bug-automake@gnu.org about your system, including the value of your $PATH and any error possibly output before this message. This can help us improve future automake versions. END if test x"$ACCEPT_INFERIOR_RM_PROGRAM" = x"yes"; then echo 'Configuration will proceed anyway, since you have set the' >&2 echo 'ACCEPT_INFERIOR_RM_PROGRAM variable to "yes"' >&2 echo >&2 else cat >&2 <<'END' Aborting the configuration process, to ensure you take notice of the issue. You can download and install GNU coreutils to get an 'rm' implementation that behaves properly: . If you want to complete the configuration process using your problematic 'rm' anyway, export the environment variable ACCEPT_INFERIOR_RM_PROGRAM to "yes", and re-run configure. END AC_MSG_ERROR([Your 'rm' program is bad, sorry.]) 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 dnl mangled by Autoconf and run in a shell conditional statement. m4_define([_AC_COMPILER_EXEEXT], m4_defn([_AC_COMPILER_EXEEXT])[m4_provide([_AM_COMPILER_EXEEXT])]) # When config.status generates a header, we must update the stamp-h file. # This file resides in the same directory as the config header # that is generated. The stamp files are numbered to have different names. # Autoconf calls _AC_AM_CONFIG_HEADER_HOOK (when defined) in the # loop where config.status creates the headers, so we can generate # our stamp files there. AC_DEFUN([_AC_AM_CONFIG_HEADER_HOOK], [# Compute $1's index in $config_headers. _am_arg=$1 _am_stamp_count=1 for _am_header in $config_headers :; do case $_am_header in $_am_arg | $_am_arg:* ) break ;; * ) _am_stamp_count=`expr $_am_stamp_count + 1` ;; esac done echo "timestamp for $_am_arg" >`AS_DIRNAME(["$_am_arg"])`/stamp-h[]$_am_stamp_count]) # Copyright (C) 2001-2021 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # AM_PROG_INSTALL_SH # ------------------ # Define $install_sh. AC_DEFUN([AM_PROG_INSTALL_SH], [AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl if test x"${install_sh+set}" != xset; then case $am_aux_dir in *\ * | *\ *) install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;; *) install_sh="\${SHELL} $am_aux_dir/install-sh" esac fi AC_SUBST([install_sh])]) # Copyright (C) 2003-2021 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # Check whether the underlying file-system supports filenames # with a leading dot. For instance MS-DOS doesn't. AC_DEFUN([AM_SET_LEADING_DOT], [rm -rf .tst 2>/dev/null mkdir .tst 2>/dev/null if test -d .tst; then am__leading_dot=. else am__leading_dot=_ fi rmdir .tst 2>/dev/null AC_SUBST([am__leading_dot])]) # Check to see how 'make' treats includes. -*- Autoconf -*- # Copyright (C) 2001-2021 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # AM_MAKE_INCLUDE() # ----------------- # Check whether make has an 'include' directive that can support all # the idioms we need for our automatic dependency tracking code. AC_DEFUN([AM_MAKE_INCLUDE], [AC_MSG_CHECKING([whether ${MAKE-make} supports the include directive]) cat > confinc.mk << 'END' am__doit: @echo this is the am__doit target >confinc.out .PHONY: am__doit END am__include="#" am__quote= # BSD make does it like this. echo '.include "confinc.mk" # ignored' > confmf.BSD # Other make implementations (GNU, Solaris 10, AIX) do it like this. echo 'include confinc.mk # ignored' > confmf.GNU _am_result=no for s in GNU BSD; do AM_RUN_LOG([${MAKE-make} -f confmf.$s && cat confinc.out]) AS_CASE([$?:`cat confinc.out 2>/dev/null`], ['0:this is the am__doit target'], [AS_CASE([$s], [BSD], [am__include='.include' am__quote='"'], [am__include='include' am__quote=''])]) if test "$am__include" != "#"; then _am_result="yes ($s style)" break fi done rm -f confinc.* confmf.* AC_MSG_RESULT([${_am_result}]) AC_SUBST([am__include])]) AC_SUBST([am__quote])]) # Fake the existence of programs that GNU maintainers use. -*- Autoconf -*- # Copyright (C) 1997-2021 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # AM_MISSING_PROG(NAME, PROGRAM) # ------------------------------ AC_DEFUN([AM_MISSING_PROG], [AC_REQUIRE([AM_MISSING_HAS_RUN]) $1=${$1-"${am_missing_run}$2"} AC_SUBST($1)]) # AM_MISSING_HAS_RUN # ------------------ # Define MISSING if not defined so far and test if it is modern enough. # If it is, set am_missing_run to use it, otherwise, to nothing. AC_DEFUN([AM_MISSING_HAS_RUN], [AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl AC_REQUIRE_AUX_FILE([missing])dnl if test x"${MISSING+set}" != xset; then MISSING="\${SHELL} '$am_aux_dir/missing'" fi # Use eval to expand $SHELL if eval "$MISSING --is-lightweight"; then am_missing_run="$MISSING " else am_missing_run= AC_MSG_WARN(['missing' script is too old or missing]) fi ]) # Helper functions for option handling. -*- Autoconf -*- # Copyright (C) 2001-2021 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # _AM_MANGLE_OPTION(NAME) # ----------------------- AC_DEFUN([_AM_MANGLE_OPTION], [[_AM_OPTION_]m4_bpatsubst($1, [[^a-zA-Z0-9_]], [_])]) # _AM_SET_OPTION(NAME) # -------------------- # Set option NAME. Presently that only means defining a flag for this option. AC_DEFUN([_AM_SET_OPTION], [m4_define(_AM_MANGLE_OPTION([$1]), [1])]) # _AM_SET_OPTIONS(OPTIONS) # ------------------------ # OPTIONS is a space-separated list of Automake options. AC_DEFUN([_AM_SET_OPTIONS], [m4_foreach_w([_AM_Option], [$1], [_AM_SET_OPTION(_AM_Option)])]) # _AM_IF_OPTION(OPTION, IF-SET, [IF-NOT-SET]) # ------------------------------------------- # Execute IF-SET if OPTION is set, IF-NOT-SET otherwise. AC_DEFUN([_AM_IF_OPTION], [m4_ifset(_AM_MANGLE_OPTION([$1]), [$2], [$3])]) # Copyright (C) 1999-2021 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # _AM_PROG_CC_C_O # --------------- # Like AC_PROG_CC_C_O, but changed for automake. We rewrite AC_PROG_CC # to automatically call this. AC_DEFUN([_AM_PROG_CC_C_O], [AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl AC_REQUIRE_AUX_FILE([compile])dnl AC_LANG_PUSH([C])dnl AC_CACHE_CHECK( [whether $CC understands -c and -o together], [am_cv_prog_cc_c_o], [AC_LANG_CONFTEST([AC_LANG_PROGRAM([])]) # Make sure it works both with $CC and with simple cc. # Following AC_PROG_CC_C_O, we do the test twice because some # compilers refuse to overwrite an existing .o file with -o, # though they will create one. am_cv_prog_cc_c_o=yes for am_i in 1 2; do if AM_RUN_LOG([$CC -c conftest.$ac_ext -o conftest2.$ac_objext]) \ && test -f conftest2.$ac_objext; then : OK else am_cv_prog_cc_c_o=no break fi done rm -f core conftest* unset am_i]) if test "$am_cv_prog_cc_c_o" != yes; then # Losing compiler, so override with the script. # FIXME: It is wrong to rewrite CC. # But if we don't then we get into trouble of one sort or another. # A longer-term fix would be to have automake use am__CC in this case, # and then we could set am__CC="\$(top_srcdir)/compile \$(CC)" CC="$am_aux_dir/compile $CC" fi AC_LANG_POP([C])]) # For backward compatibility. AC_DEFUN_ONCE([AM_PROG_CC_C_O], [AC_REQUIRE([AC_PROG_CC])]) # Copyright (C) 2001-2021 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # AM_RUN_LOG(COMMAND) # ------------------- # Run COMMAND, save the exit status in ac_status, and log it. # (This has been adapted from Autoconf's _AC_RUN_LOG macro.) AC_DEFUN([AM_RUN_LOG], [{ echo "$as_me:$LINENO: $1" >&AS_MESSAGE_LOG_FD ($1) >&AS_MESSAGE_LOG_FD 2>&AS_MESSAGE_LOG_FD ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&AS_MESSAGE_LOG_FD (exit $ac_status); }]) # Copyright (C) 2009-2021 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # AM_SILENT_RULES([DEFAULT]) # -------------------------- # Enable less verbose build rules; with the default set to DEFAULT # ("yes" being less verbose, "no" or empty being verbose). AC_DEFUN([AM_SILENT_RULES], [AC_ARG_ENABLE([silent-rules], [dnl AS_HELP_STRING( [--enable-silent-rules], [less verbose build output (undo: "make V=1")]) AS_HELP_STRING( [--disable-silent-rules], [verbose build output (undo: "make V=0")])dnl ]) case $enable_silent_rules in @%:@ ((( yes) AM_DEFAULT_VERBOSITY=0;; no) AM_DEFAULT_VERBOSITY=1;; *) AM_DEFAULT_VERBOSITY=m4_if([$1], [yes], [0], [1]);; esac dnl dnl A few 'make' implementations (e.g., NonStop OS and NextStep) dnl do not support nested variable expansions. dnl See automake bug#9928 and bug#10237. am_make=${MAKE-make} AC_CACHE_CHECK([whether $am_make supports nested variables], [am_cv_make_support_nested_variables], [if AS_ECHO([['TRUE=$(BAR$(V)) BAR0=false BAR1=true V=1 am__doit: @$(TRUE) .PHONY: am__doit']]) | $am_make -f - >/dev/null 2>&1; then am_cv_make_support_nested_variables=yes else am_cv_make_support_nested_variables=no fi]) if test $am_cv_make_support_nested_variables = yes; then dnl Using '$V' instead of '$(V)' breaks IRIX make. AM_V='$(V)' AM_DEFAULT_V='$(AM_DEFAULT_VERBOSITY)' else AM_V=$AM_DEFAULT_VERBOSITY AM_DEFAULT_V=$AM_DEFAULT_VERBOSITY fi AC_SUBST([AM_V])dnl AM_SUBST_NOTMAKE([AM_V])dnl AC_SUBST([AM_DEFAULT_V])dnl AM_SUBST_NOTMAKE([AM_DEFAULT_V])dnl AC_SUBST([AM_DEFAULT_VERBOSITY])dnl AM_BACKSLASH='\' AC_SUBST([AM_BACKSLASH])dnl _AM_SUBST_NOTMAKE([AM_BACKSLASH])dnl ]) # Copyright (C) 2001-2021 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # AM_PROG_INSTALL_STRIP # --------------------- # One issue with vendor 'install' (even GNU) is that you can't # specify the program used to strip binaries. This is especially # annoying in cross-compiling environments, where the build's strip # is unlikely to handle the host's binaries. # Fortunately install-sh will honor a STRIPPROG variable, so we # always use install-sh in "make install-strip", and initialize # STRIPPROG with the value of the STRIP variable (set by the user). AC_DEFUN([AM_PROG_INSTALL_STRIP], [AC_REQUIRE([AM_PROG_INSTALL_SH])dnl # Installed binaries are usually stripped using 'strip' when the user # run "make install-strip". However 'strip' might not be the right # tool to use in cross-compilation environments, therefore Automake # will honor the 'STRIP' environment variable to overrule this program. dnl Don't test for $cross_compiling = yes, because it might be 'maybe'. if test "$cross_compiling" != no; then AC_CHECK_TOOL([STRIP], [strip], :) fi INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s" AC_SUBST([INSTALL_STRIP_PROGRAM])]) # Copyright (C) 2006-2021 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # _AM_SUBST_NOTMAKE(VARIABLE) # --------------------------- # Prevent Automake from outputting VARIABLE = @VARIABLE@ in Makefile.in. # This macro is traced by Automake. AC_DEFUN([_AM_SUBST_NOTMAKE]) # AM_SUBST_NOTMAKE(VARIABLE) # -------------------------- # Public sister of _AM_SUBST_NOTMAKE. AC_DEFUN([AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE($@)]) # Check how to create a tarball. -*- Autoconf -*- # Copyright (C) 2004-2021 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # _AM_PROG_TAR(FORMAT) # -------------------- # Check how to create a tarball in format FORMAT. # FORMAT should be one of 'v7', 'ustar', or 'pax'. # # Substitute a variable $(am__tar) that is a command # writing to stdout a FORMAT-tarball containing the directory # $tardir. # tardir=directory && $(am__tar) > result.tar # # Substitute a variable $(am__untar) that extract such # a tarball read from stdin. # $(am__untar) < result.tar # AC_DEFUN([_AM_PROG_TAR], [# Always define AMTAR for backward compatibility. Yes, it's still used # in the wild :-( We should find a proper way to deprecate it ... AC_SUBST([AMTAR], ['$${TAR-tar}']) # We'll loop over all known methods to create a tar archive until one works. _am_tools='gnutar m4_if([$1], [ustar], [plaintar]) pax cpio none' m4_if([$1], [v7], [am__tar='$${TAR-tar} chof - "$$tardir"' am__untar='$${TAR-tar} xf -'], [m4_case([$1], [ustar], [# The POSIX 1988 'ustar' format is defined with fixed-size fields. # There is notably a 21 bits limit for the UID and the GID. In fact, # the 'pax' utility can hang on bigger UID/GID (see automake bug#8343 # and bug#13588). am_max_uid=2097151 # 2^21 - 1 am_max_gid=$am_max_uid # The $UID and $GID variables are not portable, so we need to resort # to the POSIX-mandated id(1) utility. Errors in the 'id' calls # below are definitely unexpected, so allow the users to see them # (that is, avoid stderr redirection). am_uid=`id -u || echo unknown` am_gid=`id -g || echo unknown` AC_MSG_CHECKING([whether UID '$am_uid' is supported by ustar format]) if test $am_uid -le $am_max_uid; then AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) _am_tools=none fi AC_MSG_CHECKING([whether GID '$am_gid' is supported by ustar format]) if test $am_gid -le $am_max_gid; then AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) _am_tools=none fi], [pax], [], [m4_fatal([Unknown tar format])]) AC_MSG_CHECKING([how to create a $1 tar archive]) # Go ahead even if we have the value already cached. We do so because we # need to set the values for the 'am__tar' and 'am__untar' variables. _am_tools=${am_cv_prog_tar_$1-$_am_tools} for _am_tool in $_am_tools; do case $_am_tool in gnutar) for _am_tar in tar gnutar gtar; do AM_RUN_LOG([$_am_tar --version]) && break done am__tar="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$$tardir"' am__tar_="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$tardir"' am__untar="$_am_tar -xf -" ;; plaintar) # Must skip GNU tar: if it does not support --format= it doesn't create # ustar tarball either. (tar --version) >/dev/null 2>&1 && continue am__tar='tar chf - "$$tardir"' am__tar_='tar chf - "$tardir"' am__untar='tar xf -' ;; pax) am__tar='pax -L -x $1 -w "$$tardir"' am__tar_='pax -L -x $1 -w "$tardir"' am__untar='pax -r' ;; cpio) am__tar='find "$$tardir" -print | cpio -o -H $1 -L' am__tar_='find "$tardir" -print | cpio -o -H $1 -L' am__untar='cpio -i -H $1 -d' ;; none) am__tar=false am__tar_=false am__untar=false ;; esac # If the value was cached, stop now. We just wanted to have am__tar # and am__untar set. test -n "${am_cv_prog_tar_$1}" && break # tar/untar a dummy directory, and stop if the command works. rm -rf conftest.dir mkdir conftest.dir echo GrepMe > conftest.dir/file AM_RUN_LOG([tardir=conftest.dir && eval $am__tar_ >conftest.tar]) rm -rf conftest.dir if test -s conftest.tar; then AM_RUN_LOG([$am__untar /dev/null 2>&1 && break fi done rm -rf conftest.dir AC_CACHE_VAL([am_cv_prog_tar_$1], [am_cv_prog_tar_$1=$_am_tool]) AC_MSG_RESULT([$am_cv_prog_tar_$1])]) AC_SUBST([am__tar]) AC_SUBST([am__untar]) ]) # _AM_PROG_TAR ncdc-1.23.1/Makefile.in0000644000175000017500000013204214314535761011506 00000000000000# Makefile.in generated by automake 1.16.5 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2021 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. @SET_MAKE@ VPATH = @srcdir@ 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 \ ?) ;; \ *) echo "am__make_running_with_option: internal error: invalid" \ "target option '$${target_option-}' specified" >&2; \ exit 1;; \ esac; \ has_opt=no; \ sane_makeflags=$$MAKEFLAGS; \ if $(am__is_gnu_make); then \ sane_makeflags=$$MFLAGS; \ else \ case $$MAKEFLAGS in \ *\\[\ \ ]*) \ bs=\\; \ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ esac; \ fi; \ skip_next=no; \ strip_trailopt () \ { \ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ }; \ for flg in $$sane_makeflags; do \ test $$skip_next = yes && { skip_next=no; continue; }; \ case $$flg in \ *=*|--*) continue;; \ -*I) strip_trailopt 'I'; skip_next=yes;; \ -*I?*) strip_trailopt 'I';; \ -*O) strip_trailopt 'O'; skip_next=yes;; \ -*O?*) strip_trailopt 'O';; \ -*l) strip_trailopt 'l'; skip_next=yes;; \ -*l?*) strip_trailopt 'l';; \ -[dEDm]) skip_next=yes;; \ -[JT]) skip_next=yes;; \ esac; \ case $$flg in \ *$$target_option*) has_opt=yes; break;; \ esac; \ done; \ test $$has_opt = yes am__make_dryrun = (target_option=n; $(am__make_running_with_option)) am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) pkgdatadir = $(datadir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ pkglibexecdir = $(libexecdir)/@PACKAGE@ am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd install_sh_DATA = $(install_sh) -c -m 644 install_sh_PROGRAM = $(install_sh) -c install_sh_SCRIPT = $(install_sh) -c INSTALL_HEADER = $(INSTALL_DATA) transform = $(program_transform_name) NORMAL_INSTALL = : PRE_INSTALL = : POST_INSTALL = : NORMAL_UNINSTALL = : PRE_UNINSTALL = : POST_UNINSTALL = : noinst_PROGRAMS = $(am__EXEEXT_1) $(am__EXEEXT_2) @USE_POD2MAN_TRUE@am__append_1 = gendoc @HAVE_MH_FALSE@am__append_2 = makeheaders bin_PROGRAMS = ncdc$(EXEEXT) subdir = . ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/deps/lean.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) $(noinst_HEADERS) $(am__DIST_COMMON) am__CONFIG_DISTCLEAN_FILES = config.status config.cache config.log \ configure.lineno config.status.lineno mkinstalldirs = $(install_sh) -d CONFIG_HEADER = config.h CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am__installdirs = "$(DESTDIR)$(bindir)" "$(DESTDIR)$(man1dir)" @USE_POD2MAN_TRUE@am__EXEEXT_1 = gendoc$(EXEEXT) @HAVE_MH_FALSE@am__EXEEXT_2 = makeheaders$(EXEEXT) PROGRAMS = $(bin_PROGRAMS) $(noinst_PROGRAMS) LIBRARIES = $(noinst_LIBRARIES) AR = ar ARFLAGS = cru AM_V_AR = $(am__v_AR_@AM_V@) am__v_AR_ = $(am__v_AR_@AM_DEFAULT_V@) am__v_AR_0 = @echo " AR " $@; am__v_AR_1 = libdeps_a_AR = $(AR) $(ARFLAGS) libdeps_a_LIBADD = am__dirstamp = $(am__leading_dot)dirstamp am_libdeps_a_OBJECTS = deps/ylib/yuri.$(OBJEXT) deps/yxml.$(OBJEXT) libdeps_a_OBJECTS = $(am_libdeps_a_OBJECTS) am__gendoc_SOURCES_DIST = doc/gendoc.c @USE_POD2MAN_TRUE@am_gendoc_OBJECTS = doc/gendoc.$(OBJEXT) gendoc_OBJECTS = $(am_gendoc_OBJECTS) gendoc_LDADD = $(LDADD) am_makeheaders_OBJECTS = deps/makeheaders.$(OBJEXT) makeheaders_OBJECTS = $(am_makeheaders_OBJECTS) makeheaders_LDADD = $(LDADD) am_ncdc_OBJECTS = src/bloom.$(OBJEXT) src/cc.$(OBJEXT) \ src/commands.$(OBJEXT) src/db.$(OBJEXT) src/dl.$(OBJEXT) \ src/dlfile.$(OBJEXT) src/fl_load.$(OBJEXT) \ src/fl_local.$(OBJEXT) src/fl_save.$(OBJEXT) \ src/fl_util.$(OBJEXT) src/geoip.$(OBJEXT) src/hub.$(OBJEXT) \ src/listen.$(OBJEXT) src/main.$(OBJEXT) src/net.$(OBJEXT) \ src/proto.$(OBJEXT) src/search.$(OBJEXT) src/strutil.$(OBJEXT) \ src/tth.$(OBJEXT) src/ui.$(OBJEXT) src/ui_colors.$(OBJEXT) \ src/ui_listing.$(OBJEXT) src/ui_logwindow.$(OBJEXT) \ src/ui_textinput.$(OBJEXT) src/uit_conn.$(OBJEXT) \ src/uit_dl.$(OBJEXT) src/uit_fl.$(OBJEXT) \ src/uit_hub.$(OBJEXT) src/uit_main.$(OBJEXT) \ src/uit_msg.$(OBJEXT) src/uit_search.$(OBJEXT) \ src/uit_userlist.$(OBJEXT) src/util.$(OBJEXT) \ src/vars.$(OBJEXT) ncdc_OBJECTS = $(am_ncdc_OBJECTS) am__DEPENDENCIES_1 = ncdc_DEPENDENCIES = libdeps.a $(am__DEPENDENCIES_1) \ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ $(am__DEPENDENCIES_1) $(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 am__v_P_1 = : AM_V_GEN = $(am__v_GEN_@AM_V@) am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) am__v_GEN_0 = @echo " GEN " $@; am__v_GEN_1 = AM_V_at = $(am__v_at_@AM_V@) am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) am__v_at_0 = @ am__v_at_1 = DEFAULT_INCLUDES = -I.@am__isrc@ depcomp = $(SHELL) $(top_srcdir)/depcomp am__maybe_remake_depfiles = depfiles am__depfiles_remade = deps/$(DEPDIR)/makeheaders.Po \ deps/$(DEPDIR)/yxml.Po deps/ylib/$(DEPDIR)/yuri.Po \ doc/$(DEPDIR)/gendoc.Po src/$(DEPDIR)/bloom.Po \ src/$(DEPDIR)/cc.Po src/$(DEPDIR)/commands.Po \ src/$(DEPDIR)/db.Po src/$(DEPDIR)/dl.Po \ src/$(DEPDIR)/dlfile.Po src/$(DEPDIR)/fl_load.Po \ src/$(DEPDIR)/fl_local.Po src/$(DEPDIR)/fl_save.Po \ src/$(DEPDIR)/fl_util.Po src/$(DEPDIR)/geoip.Po \ src/$(DEPDIR)/hub.Po src/$(DEPDIR)/listen.Po \ src/$(DEPDIR)/main.Po src/$(DEPDIR)/net.Po \ src/$(DEPDIR)/proto.Po src/$(DEPDIR)/search.Po \ src/$(DEPDIR)/strutil.Po src/$(DEPDIR)/tth.Po \ src/$(DEPDIR)/ui.Po src/$(DEPDIR)/ui_colors.Po \ src/$(DEPDIR)/ui_listing.Po src/$(DEPDIR)/ui_logwindow.Po \ src/$(DEPDIR)/ui_textinput.Po src/$(DEPDIR)/uit_conn.Po \ src/$(DEPDIR)/uit_dl.Po src/$(DEPDIR)/uit_fl.Po \ src/$(DEPDIR)/uit_hub.Po src/$(DEPDIR)/uit_main.Po \ src/$(DEPDIR)/uit_msg.Po src/$(DEPDIR)/uit_search.Po \ src/$(DEPDIR)/uit_userlist.Po src/$(DEPDIR)/util.Po \ src/$(DEPDIR)/vars.Po am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) AM_V_CC = $(am__v_CC_@AM_V@) am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) am__v_CC_0 = @echo " CC " $@; am__v_CC_1 = CCLD = $(CC) LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ 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 = $(libdeps_a_SOURCES) $(gendoc_SOURCES) \ $(makeheaders_SOURCES) $(ncdc_SOURCES) DIST_SOURCES = $(libdeps_a_SOURCES) $(am__gendoc_SOURCES_DIST) \ $(makeheaders_SOURCES) $(ncdc_SOURCES) am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ *) (install-info --version) >/dev/null 2>&1;; \ esac am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; am__vpath_adj = case $$p in \ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ *) f=$$p;; \ esac; am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; am__install_max = 40 am__nobase_strip_setup = \ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` am__nobase_strip = \ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" am__nobase_list = $(am__nobase_strip_setup); \ for p in $$list; do echo "$$p $$p"; done | \ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ if (++n[$$2] == $(am__install_max)) \ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ END { for (dir in files) print dir, files[dir] }' am__base_list = \ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' am__uninstall_files_from_dir = { \ test -z "$$files" \ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ $(am__cd) "$$dir" && rm -f $$files; }; \ } man1dir = $(mandir)/man1 NROFF = nroff MANS = $(man_MANS) HEADERS = $(noinst_HEADERS) am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) \ config.h.in # Read a list of newline-separated strings from the standard input, # and print each of them once, without duplicates. Input order is # *not* preserved. am__uniquify_input = $(AWK) '\ BEGIN { nonempty = 0; } \ { items[$$0] = 1; nonempty = 1; } \ END { if (nonempty) { for (i in items) print i; }; } \ ' # Make sure the list of sources is unique. This is necessary because, # e.g., the same source file might be shared among _SOURCES variables # for different programs/libraries. am__define_uniq_tagged_files = \ list='$(am__tagged_files)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | $(am__uniquify_input)` AM_RECURSIVE_TARGETS = cscope am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/config.h.in COPYING \ ChangeLog README compile depcomp install-sh missing DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) distdir = $(PACKAGE)-$(VERSION) top_distdir = $(distdir) am__remove_distdir = \ if test -d "$(distdir)"; then \ find "$(distdir)" -type d ! -perm -200 -exec chmod u+w {} ';' \ && rm -rf "$(distdir)" \ || { sleep 5 && rm -rf "$(distdir)"; }; \ else :; fi am__post_remove_distdir = $(am__remove_distdir) DIST_ARCHIVES = $(distdir).tar.gz GZIP_ENV = --best DIST_TARGETS = dist-gzip # Exists only to be overridden by the user if desired. AM_DISTCHECK_DVI_TARGET = dvi distuninstallcheck_listfiles = find . -type f -print am__distuninstallcheck_listfiles = $(distuninstallcheck_listfiles) \ | sed 's|^\./|$(prefix)/|' | grep -v '$(infodir)/dir$$' distcleancheck_listfiles = find . -type f -print ACLOCAL = @ACLOCAL@ AMTAR = @AMTAR@ AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ AWK = @AWK@ BZ2_LIBS = @BZ2_LIBS@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ CFLAGS = @CFLAGS@ CPPFLAGS = @CPPFLAGS@ CSCOPE = @CSCOPE@ CTAGS = @CTAGS@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ ECHO_C = @ECHO_C@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ ETAGS = @ETAGS@ EXEEXT = @EXEEXT@ GEOIP_CFLAGS = @GEOIP_CFLAGS@ GEOIP_LIBS = @GEOIP_LIBS@ GIT = @GIT@ GLIB_CFLAGS = @GLIB_CFLAGS@ GLIB_LIBS = @GLIB_LIBS@ GNUTLS_CFLAGS = @GNUTLS_CFLAGS@ GNUTLS_LIBS = @GNUTLS_LIBS@ INSTALL = @INSTALL@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ LDFLAGS = @LDFLAGS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LTLIBOBJS = @LTLIBOBJS@ MAKEINFO = @MAKEINFO@ MKDIR_P = @MKDIR_P@ NCURSES_CFLAGS = @NCURSES_CFLAGS@ NCURSES_LIBS = @NCURSES_LIBS@ OBJEXT = @OBJEXT@ PACKAGE = @PACKAGE@ PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PATH_SEPARATOR = @PATH_SEPARATOR@ PKG_CONFIG = @PKG_CONFIG@ PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ RANLIB = @RANLIB@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ SQLITE_CFLAGS = @SQLITE_CFLAGS@ SQLITE_LIBS = @SQLITE_LIBS@ STRIP = @STRIP@ VERSION = @VERSION@ Z_LIBS = @Z_LIBS@ abs_builddir = @abs_builddir@ abs_srcdir = @abs_srcdir@ abs_top_builddir = @abs_top_builddir@ abs_top_srcdir = @abs_top_srcdir@ ac_ct_CC = @ac_ct_CC@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__tar = @am__tar@ am__untar = @am__untar@ bindir = @bindir@ build_alias = @build_alias@ builddir = @builddir@ datadir = @datadir@ datarootdir = @datarootdir@ docdir = @docdir@ dvidir = @dvidir@ exec_prefix = @exec_prefix@ have_mh = @have_mh@ have_pod2man = @have_pod2man@ host_alias = @host_alias@ htmldir = @htmldir@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ libdir = @libdir@ libexecdir = @libexecdir@ localedir = @localedir@ localstatedir = @localstatedir@ mandir = @mandir@ mkdir_p = @mkdir_p@ oldincludedir = @oldincludedir@ pdfdir = @pdfdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ runstatedir = @runstatedir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ srcdir = @srcdir@ sysconfdir = @sysconfdir@ target_alias = @target_alias@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ EXTRA_DIST = ChangeLog doc/ncdc.1 doc/ncdc.pod.in deps/ylib/yuri.h \ deps/yxml.h AM_CFLAGS = ${NCURSES_CFLAGS} $(GLIB_CFLAGS) $(GNUTLS_CFLAGS) $(SQLITE_CFLAGS) AM_CPPFLAGS = -I$(builddir)/src -I$(srcdir)/deps -I$(srcdir)/deps/ylib @INSTALL_MANPAGE_TRUE@man_MANS = doc/ncdc.1 @USE_POD2MAN_TRUE@gendoc_SOURCES = doc/gendoc.c @USE_POD2MAN_TRUE@CLEANFILES = doc/ncdc.1 doc/ncdc.pod @HAVE_MH_FALSE@mkhdr = ./makeheaders$(EXEEXT) @HAVE_MH_TRUE@mkhdr = makeheaders @HAVE_MH_FALSE@mkhdr_dep = makeheaders$(EXEEXT) @HAVE_MH_TRUE@mkhdr_dep = makeheaders_SOURCES = deps/makeheaders.c noinst_LIBRARIES = libdeps.a libdeps_a_SOURCES = deps/ylib/yuri.c deps/yxml.c ncdc_SOURCES = \ src/bloom.c\ src/cc.c\ src/commands.c\ src/db.c\ src/dl.c\ src/dlfile.c\ src/fl_load.c\ src/fl_local.c\ src/fl_save.c\ src/fl_util.c\ src/geoip.c\ src/hub.c\ src/listen.c\ src/main.c\ src/net.c\ src/proto.c\ src/search.c\ src/strutil.c\ src/tth.c\ src/ui.c\ src/ui_colors.c\ src/ui_listing.c\ src/ui_logwindow.c\ src/ui_textinput.c\ src/uit_conn.c\ src/uit_dl.c\ src/uit_fl.c\ src/uit_hub.c\ src/uit_main.c\ src/uit_msg.c\ src/uit_search.c\ src/uit_userlist.c\ src/util.c\ src/vars.c auto_headers = $(ncdc_SOURCES:.c=.h) noinst_HEADERS = src/doc.h src/ncdc.h ncdc_LDADD = libdeps.a -lm $(NCURSES_LIBS) $(Z_LIBS) $(BZ2_LIBS) $(GLIB_LIBS) $(GNUTLS_LIBS) $(GCRYPT_LIBS) $(SQLITE_LIBS) $(GEOIP_LIBS) MOSTLYCLEANFILES = $(auto_headers) src/version.h mkhdr.done all: config.h $(MAKE) $(AM_MAKEFLAGS) all-am .SUFFIXES: .SUFFIXES: .c .o .obj am--refresh: Makefile @: $(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) @for dep in $?; do \ case '$(am__configure_deps)' in \ *$$dep*) \ echo ' cd $(srcdir) && $(AUTOMAKE) --foreign'; \ $(am__cd) $(srcdir) && $(AUTOMAKE) --foreign \ && exit 0; \ exit 1;; \ esac; \ done; \ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign Makefile'; \ $(am__cd) $(top_srcdir) && \ $(AUTOMAKE) --foreign Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ echo ' $(SHELL) ./config.status'; \ $(SHELL) ./config.status;; \ *) \ echo ' cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__maybe_remake_depfiles)'; \ cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__maybe_remake_depfiles);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) $(SHELL) ./config.status --recheck $(top_srcdir)/configure: $(am__configure_deps) $(am__cd) $(srcdir) && $(AUTOCONF) $(ACLOCAL_M4): $(am__aclocal_m4_deps) $(am__cd) $(srcdir) && $(ACLOCAL) $(ACLOCAL_AMFLAGS) $(am__aclocal_m4_deps): config.h: stamp-h1 @test -f $@ || rm -f stamp-h1 @test -f $@ || $(MAKE) $(AM_MAKEFLAGS) stamp-h1 stamp-h1: $(srcdir)/config.h.in $(top_builddir)/config.status @rm -f stamp-h1 cd $(top_builddir) && $(SHELL) ./config.status config.h $(srcdir)/config.h.in: $(am__configure_deps) ($(am__cd) $(top_srcdir) && $(AUTOHEADER)) rm -f stamp-h1 touch $@ distclean-hdr: -rm -f config.h stamp-h1 install-binPROGRAMS: $(bin_PROGRAMS) @$(NORMAL_INSTALL) @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \ $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \ fi; \ for p in $$list; do echo "$$p $$p"; done | \ sed 's/$(EXEEXT)$$//' | \ while read p p1; do if test -f $$p \ ; then echo "$$p"; echo "$$p"; else :; fi; \ done | \ sed -e 'p;s,.*/,,;n;h' \ -e 's|.*|.|' \ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ sed 'N;N;N;s,\n, ,g' | \ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ if ($$2 == $$4) files[d] = files[d] " " $$1; \ else { print "f", $$3 "/" $$4, $$1; } } \ END { for (d in files) print "f", d, files[d] }' | \ while read type dir files; do \ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ test -z "$$files" || { \ echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ } \ ; done uninstall-binPROGRAMS: @$(NORMAL_UNINSTALL) @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ files=`for p in $$list; do echo "$$p"; done | \ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ -e 's/$$/$(EXEEXT)/' \ `; \ test -n "$$list" || exit 0; \ echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ cd "$(DESTDIR)$(bindir)" && rm -f $$files clean-binPROGRAMS: -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS) clean-noinstPROGRAMS: -test -z "$(noinst_PROGRAMS)" || rm -f $(noinst_PROGRAMS) clean-noinstLIBRARIES: -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES) deps/ylib/$(am__dirstamp): @$(MKDIR_P) deps/ylib @: > deps/ylib/$(am__dirstamp) deps/ylib/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) deps/ylib/$(DEPDIR) @: > deps/ylib/$(DEPDIR)/$(am__dirstamp) deps/ylib/yuri.$(OBJEXT): deps/ylib/$(am__dirstamp) \ deps/ylib/$(DEPDIR)/$(am__dirstamp) deps/$(am__dirstamp): @$(MKDIR_P) deps @: > deps/$(am__dirstamp) deps/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) deps/$(DEPDIR) @: > deps/$(DEPDIR)/$(am__dirstamp) deps/yxml.$(OBJEXT): deps/$(am__dirstamp) \ deps/$(DEPDIR)/$(am__dirstamp) libdeps.a: $(libdeps_a_OBJECTS) $(libdeps_a_DEPENDENCIES) $(EXTRA_libdeps_a_DEPENDENCIES) $(AM_V_at)-rm -f libdeps.a $(AM_V_AR)$(libdeps_a_AR) libdeps.a $(libdeps_a_OBJECTS) $(libdeps_a_LIBADD) $(AM_V_at)$(RANLIB) libdeps.a doc/$(am__dirstamp): @$(MKDIR_P) doc @: > doc/$(am__dirstamp) doc/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) doc/$(DEPDIR) @: > doc/$(DEPDIR)/$(am__dirstamp) doc/gendoc.$(OBJEXT): doc/$(am__dirstamp) \ doc/$(DEPDIR)/$(am__dirstamp) gendoc$(EXEEXT): $(gendoc_OBJECTS) $(gendoc_DEPENDENCIES) $(EXTRA_gendoc_DEPENDENCIES) @rm -f gendoc$(EXEEXT) $(AM_V_CCLD)$(LINK) $(gendoc_OBJECTS) $(gendoc_LDADD) $(LIBS) deps/makeheaders.$(OBJEXT): deps/$(am__dirstamp) \ deps/$(DEPDIR)/$(am__dirstamp) makeheaders$(EXEEXT): $(makeheaders_OBJECTS) $(makeheaders_DEPENDENCIES) $(EXTRA_makeheaders_DEPENDENCIES) @rm -f makeheaders$(EXEEXT) $(AM_V_CCLD)$(LINK) $(makeheaders_OBJECTS) $(makeheaders_LDADD) $(LIBS) src/$(am__dirstamp): @$(MKDIR_P) src @: > src/$(am__dirstamp) src/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) src/$(DEPDIR) @: > src/$(DEPDIR)/$(am__dirstamp) src/bloom.$(OBJEXT): src/$(am__dirstamp) src/$(DEPDIR)/$(am__dirstamp) src/cc.$(OBJEXT): src/$(am__dirstamp) src/$(DEPDIR)/$(am__dirstamp) src/commands.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/db.$(OBJEXT): src/$(am__dirstamp) src/$(DEPDIR)/$(am__dirstamp) src/dl.$(OBJEXT): src/$(am__dirstamp) src/$(DEPDIR)/$(am__dirstamp) src/dlfile.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/fl_load.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/fl_local.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/fl_save.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/fl_util.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/geoip.$(OBJEXT): src/$(am__dirstamp) src/$(DEPDIR)/$(am__dirstamp) src/hub.$(OBJEXT): src/$(am__dirstamp) src/$(DEPDIR)/$(am__dirstamp) src/listen.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/main.$(OBJEXT): src/$(am__dirstamp) src/$(DEPDIR)/$(am__dirstamp) src/net.$(OBJEXT): src/$(am__dirstamp) src/$(DEPDIR)/$(am__dirstamp) src/proto.$(OBJEXT): src/$(am__dirstamp) src/$(DEPDIR)/$(am__dirstamp) src/search.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/strutil.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/tth.$(OBJEXT): src/$(am__dirstamp) src/$(DEPDIR)/$(am__dirstamp) src/ui.$(OBJEXT): src/$(am__dirstamp) src/$(DEPDIR)/$(am__dirstamp) src/ui_colors.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/ui_listing.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/ui_logwindow.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/ui_textinput.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/uit_conn.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/uit_dl.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/uit_fl.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/uit_hub.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/uit_main.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/uit_msg.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/uit_search.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/uit_userlist.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/util.$(OBJEXT): src/$(am__dirstamp) src/$(DEPDIR)/$(am__dirstamp) src/vars.$(OBJEXT): src/$(am__dirstamp) src/$(DEPDIR)/$(am__dirstamp) ncdc$(EXEEXT): $(ncdc_OBJECTS) $(ncdc_DEPENDENCIES) $(EXTRA_ncdc_DEPENDENCIES) @rm -f ncdc$(EXEEXT) $(AM_V_CCLD)$(LINK) $(ncdc_OBJECTS) $(ncdc_LDADD) $(LIBS) mostlyclean-compile: -rm -f *.$(OBJEXT) -rm -f deps/*.$(OBJEXT) -rm -f deps/ylib/*.$(OBJEXT) -rm -f doc/*.$(OBJEXT) -rm -f src/*.$(OBJEXT) distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@deps/$(DEPDIR)/makeheaders.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@deps/$(DEPDIR)/yxml.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@deps/ylib/$(DEPDIR)/yuri.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@doc/$(DEPDIR)/gendoc.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bloom.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/cc.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/commands.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/db.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/dl.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/dlfile.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/fl_load.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/fl_local.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/fl_save.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/fl_util.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/geoip.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/hub.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/listen.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/net.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/proto.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/search.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/strutil.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/tth.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/ui.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/ui_colors.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/ui_listing.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/ui_logwindow.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/ui_textinput.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/uit_conn.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/uit_dl.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/uit_fl.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/uit_hub.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/uit_main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/uit_msg.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/uit_search.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/uit_userlist.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/util.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/vars.Po@am__quote@ # am--include-marker $(am__depfiles_remade): @$(MKDIR_P) $(@D) @echo '# dummy' >$@-t && $(am__mv) $@-t $@ am--depfiles: $(am__depfiles_remade) .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ @am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ @am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< .c.obj: @am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ @am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ @am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` install-man1: $(man_MANS) @$(NORMAL_INSTALL) @list1=''; \ list2='$(man_MANS)'; \ test -n "$(man1dir)" \ && test -n "`echo $$list1$$list2`" \ || exit 0; \ echo " $(MKDIR_P) '$(DESTDIR)$(man1dir)'"; \ $(MKDIR_P) "$(DESTDIR)$(man1dir)" || exit 1; \ { for i in $$list1; do echo "$$i"; done; \ if test -n "$$list2"; then \ for i in $$list2; do echo "$$i"; done \ | sed -n '/\.1[a-z]*$$/p'; \ fi; \ } | while read p; do \ if test -f $$p; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; echo "$$p"; \ done | \ sed -e 'n;s,.*/,,;p;h;s,.*\.,,;s,^[^1][0-9a-z]*$$,1,;x' \ -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,' | \ sed 'N;N;s,\n, ,g' | { \ list=; while read file base inst; do \ if test "$$base" = "$$inst"; then list="$$list $$file"; else \ echo " $(INSTALL_DATA) '$$file' '$(DESTDIR)$(man1dir)/$$inst'"; \ $(INSTALL_DATA) "$$file" "$(DESTDIR)$(man1dir)/$$inst" || exit $$?; \ fi; \ done; \ for i in $$list; do echo "$$i"; done | $(am__base_list) | \ while read files; do \ test -z "$$files" || { \ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(man1dir)'"; \ $(INSTALL_DATA) $$files "$(DESTDIR)$(man1dir)" || exit $$?; }; \ done; } uninstall-man1: @$(NORMAL_UNINSTALL) @list=''; test -n "$(man1dir)" || exit 0; \ files=`{ for i in $$list; do echo "$$i"; done; \ l2='$(man_MANS)'; for i in $$l2; do echo "$$i"; done | \ sed -n '/\.1[a-z]*$$/p'; \ } | sed -e 's,.*/,,;h;s,.*\.,,;s,^[^1][0-9a-z]*$$,1,;x' \ -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,'`; \ dir='$(DESTDIR)$(man1dir)'; $(am__uninstall_files_from_dir) ID: $(am__tagged_files) $(am__define_uniq_tagged_files); mkid -fID $$unique tags: tags-am TAGS: tags tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) set x; \ here=`pwd`; \ $(am__define_uniq_tagged_files); \ shift; \ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ test -n "$$unique" || unique=$$empty_fix; \ if test $$# -gt 0; then \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ "$$@" $$unique; \ else \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ $$unique; \ fi; \ fi ctags: ctags-am CTAGS: ctags ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) $(am__define_uniq_tagged_files); \ test -z "$(CTAGS_ARGS)$$unique" \ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ $$unique GTAGS: here=`$(am__cd) $(top_builddir) && pwd` \ && $(am__cd) $(top_srcdir) \ && gtags -i $(GTAGS_ARGS) "$$here" cscope: cscope.files test ! -s cscope.files \ || $(CSCOPE) -b -q $(AM_CSCOPEFLAGS) $(CSCOPEFLAGS) -i cscope.files $(CSCOPE_ARGS) clean-cscope: -rm -f cscope.files cscope.files: clean-cscope cscopelist cscopelist: cscopelist-am cscopelist-am: $(am__tagged_files) list='$(am__tagged_files)'; \ case "$(srcdir)" in \ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ *) sdir=$(subdir)/$(srcdir) ;; \ esac; \ for i in $$list; do \ if test -f "$$i"; then \ echo "$(subdir)/$$i"; \ else \ echo "$$sdir/$$i"; \ fi; \ done >> $(top_builddir)/cscope.files distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags -rm -f cscope.out cscope.in.out cscope.po.out cscope.files distdir: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) distdir-am distdir-am: $(DISTFILES) $(am__remove_distdir) test -d "$(distdir)" || mkdir "$(distdir)" @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ if test -d $$d/$$file; then \ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ if test -d "$(distdir)/$$file"; then \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ else \ test -f "$(distdir)/$$file" \ || cp -p $$d/$$file "$(distdir)/$$file" \ || exit 1; \ fi; \ done -test -n "$(am__skip_mode_fix)" \ || find "$(distdir)" -type d ! -perm -755 \ -exec chmod u+rwx,go+rx {} \; -o \ ! -type d ! -perm -444 -links 1 -exec chmod a+r {} \; -o \ ! -type d ! -perm -400 -exec chmod a+r {} \; -o \ ! -type d ! -perm -444 -exec $(install_sh) -c -m a+r {} {} \; \ || chmod -R a+r "$(distdir)" dist-gzip: distdir tardir=$(distdir) && $(am__tar) | eval GZIP= gzip $(GZIP_ENV) -c >$(distdir).tar.gz $(am__post_remove_distdir) dist-bzip2: distdir tardir=$(distdir) && $(am__tar) | BZIP2=$${BZIP2--9} bzip2 -c >$(distdir).tar.bz2 $(am__post_remove_distdir) dist-lzip: distdir tardir=$(distdir) && $(am__tar) | lzip -c $${LZIP_OPT--9} >$(distdir).tar.lz $(am__post_remove_distdir) dist-xz: distdir tardir=$(distdir) && $(am__tar) | XZ_OPT=$${XZ_OPT--e} xz -c >$(distdir).tar.xz $(am__post_remove_distdir) dist-zstd: distdir tardir=$(distdir) && $(am__tar) | zstd -c $${ZSTD_CLEVEL-$${ZSTD_OPT--19}} >$(distdir).tar.zst $(am__post_remove_distdir) dist-tarZ: distdir @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 shar distribution archives is" \ "deprecated." >&2 @echo WARNING: "It will be removed altogether in Automake 2.0" >&2 shar $(distdir) | eval GZIP= gzip $(GZIP_ENV) -c >$(distdir).shar.gz $(am__post_remove_distdir) dist-zip: distdir -rm -f $(distdir).zip zip -rq $(distdir).zip $(distdir) $(am__post_remove_distdir) dist dist-all: $(MAKE) $(AM_MAKEFLAGS) $(DIST_TARGETS) am__post_remove_distdir='@:' $(am__post_remove_distdir) # This target untars the dist file and tries a VPATH configuration. Then # it guarantees that the distribution is self-contained by making another # tarfile. distcheck: dist case '$(DIST_ARCHIVES)' in \ *.tar.gz*) \ eval GZIP= gzip $(GZIP_ENV) -dc $(distdir).tar.gz | $(am__untar) ;;\ *.tar.bz2*) \ bzip2 -dc $(distdir).tar.bz2 | $(am__untar) ;;\ *.tar.lz*) \ lzip -dc $(distdir).tar.lz | $(am__untar) ;;\ *.tar.xz*) \ xz -dc $(distdir).tar.xz | $(am__untar) ;;\ *.tar.Z*) \ uncompress -c $(distdir).tar.Z | $(am__untar) ;;\ *.shar.gz*) \ eval GZIP= gzip $(GZIP_ENV) -dc $(distdir).shar.gz | unshar ;;\ *.zip*) \ unzip $(distdir).zip ;;\ *.tar.zst*) \ zstd -dc $(distdir).tar.zst | $(am__untar) ;;\ esac chmod -R a-w $(distdir) chmod u+w $(distdir) 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/sub \ && ../../configure \ $(AM_DISTCHECK_CONFIGURE_FLAGS) \ $(DISTCHECK_CONFIGURE_FLAGS) \ --srcdir=../.. --prefix="$$dc_install_base" \ && $(MAKE) $(AM_MAKEFLAGS) \ && $(MAKE) $(AM_MAKEFLAGS) $(AM_DISTCHECK_DVI_TARGET) \ && $(MAKE) $(AM_MAKEFLAGS) check \ && $(MAKE) $(AM_MAKEFLAGS) install \ && $(MAKE) $(AM_MAKEFLAGS) installcheck \ && $(MAKE) $(AM_MAKEFLAGS) uninstall \ && $(MAKE) $(AM_MAKEFLAGS) distuninstallcheck_dir="$$dc_install_base" \ distuninstallcheck \ && chmod -R a-w "$$dc_install_base" \ && ({ \ (cd ../.. && umask 077 && mkdir "$$dc_destdir") \ && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" install \ && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" uninstall \ && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" \ distuninstallcheck_dir="$$dc_destdir" distuninstallcheck; \ } || { rm -rf "$$dc_destdir"; exit 1; }) \ && rm -rf "$$dc_destdir" \ && $(MAKE) $(AM_MAKEFLAGS) dist \ && rm -rf $(DIST_ARCHIVES) \ && $(MAKE) $(AM_MAKEFLAGS) distcleancheck \ && cd "$$am__cwd" \ || exit 1 $(am__post_remove_distdir) @(echo "$(distdir) archives ready for distribution: "; \ list='$(DIST_ARCHIVES)'; for i in $$list; do echo $$i; done) | \ sed -e 1h -e 1s/./=/g -e 1p -e 1x -e '$$p' -e '$$x' distuninstallcheck: @test -n '$(distuninstallcheck_dir)' || { \ echo 'ERROR: trying to run $@ with an empty' \ '$$(distuninstallcheck_dir)' >&2; \ exit 1; \ }; \ $(am__cd) '$(distuninstallcheck_dir)' || { \ echo 'ERROR: cannot chdir into $(distuninstallcheck_dir)' >&2; \ exit 1; \ }; \ test `$(am__distuninstallcheck_listfiles) | wc -l` -eq 0 \ || { echo "ERROR: files left after uninstall:" ; \ if test -n "$(DESTDIR)"; then \ echo " (check DESTDIR support)"; \ fi ; \ $(distuninstallcheck_listfiles) ; \ exit 1; } >&2 distcleancheck: distclean @if test '$(srcdir)' = . ; then \ echo "ERROR: distcleancheck can only run from a VPATH build" ; \ exit 1 ; \ fi @test `$(distcleancheck_listfiles) | wc -l` -eq 0 \ || { echo "ERROR: files left in build directory after distclean:" ; \ $(distcleancheck_listfiles) ; \ exit 1; } >&2 check-am: all-am check: check-am all-am: Makefile $(PROGRAMS) $(LIBRARIES) $(MANS) $(HEADERS) config.h installdirs: for dir in "$(DESTDIR)$(bindir)" "$(DESTDIR)$(man1dir)"; do \ test -z "$$dir" || $(MKDIR_P) "$$dir"; \ done install: install-am install-exec: install-exec-am install-data: install-data-am uninstall: uninstall-am install-am: all-am @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am installcheck: installcheck-am install-strip: if test -z '$(STRIP)'; then \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ install; \ else \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ fi mostlyclean-generic: -test -z "$(MOSTLYCLEANFILES)" || rm -f $(MOSTLYCLEANFILES) clean-generic: -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) distclean-generic: -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) -rm -f deps/$(DEPDIR)/$(am__dirstamp) -rm -f deps/$(am__dirstamp) -rm -f deps/ylib/$(DEPDIR)/$(am__dirstamp) -rm -f deps/ylib/$(am__dirstamp) -rm -f doc/$(DEPDIR)/$(am__dirstamp) -rm -f doc/$(am__dirstamp) -rm -f src/$(DEPDIR)/$(am__dirstamp) -rm -f src/$(am__dirstamp) maintainer-clean-generic: @echo "This command is intended for maintainers to use" @echo "it deletes files that may require special tools to rebuild." clean: clean-am clean-am: clean-binPROGRAMS clean-generic clean-noinstLIBRARIES \ clean-noinstPROGRAMS mostlyclean-am distclean: distclean-am -rm -f $(am__CONFIG_DISTCLEAN_FILES) -rm -f deps/$(DEPDIR)/makeheaders.Po -rm -f deps/$(DEPDIR)/yxml.Po -rm -f deps/ylib/$(DEPDIR)/yuri.Po -rm -f doc/$(DEPDIR)/gendoc.Po -rm -f src/$(DEPDIR)/bloom.Po -rm -f src/$(DEPDIR)/cc.Po -rm -f src/$(DEPDIR)/commands.Po -rm -f src/$(DEPDIR)/db.Po -rm -f src/$(DEPDIR)/dl.Po -rm -f src/$(DEPDIR)/dlfile.Po -rm -f src/$(DEPDIR)/fl_load.Po -rm -f src/$(DEPDIR)/fl_local.Po -rm -f src/$(DEPDIR)/fl_save.Po -rm -f src/$(DEPDIR)/fl_util.Po -rm -f src/$(DEPDIR)/geoip.Po -rm -f src/$(DEPDIR)/hub.Po -rm -f src/$(DEPDIR)/listen.Po -rm -f src/$(DEPDIR)/main.Po -rm -f src/$(DEPDIR)/net.Po -rm -f src/$(DEPDIR)/proto.Po -rm -f src/$(DEPDIR)/search.Po -rm -f src/$(DEPDIR)/strutil.Po -rm -f src/$(DEPDIR)/tth.Po -rm -f src/$(DEPDIR)/ui.Po -rm -f src/$(DEPDIR)/ui_colors.Po -rm -f src/$(DEPDIR)/ui_listing.Po -rm -f src/$(DEPDIR)/ui_logwindow.Po -rm -f src/$(DEPDIR)/ui_textinput.Po -rm -f src/$(DEPDIR)/uit_conn.Po -rm -f src/$(DEPDIR)/uit_dl.Po -rm -f src/$(DEPDIR)/uit_fl.Po -rm -f src/$(DEPDIR)/uit_hub.Po -rm -f src/$(DEPDIR)/uit_main.Po -rm -f src/$(DEPDIR)/uit_msg.Po -rm -f src/$(DEPDIR)/uit_search.Po -rm -f src/$(DEPDIR)/uit_userlist.Po -rm -f src/$(DEPDIR)/util.Po -rm -f src/$(DEPDIR)/vars.Po -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-hdr distclean-tags dvi: dvi-am dvi-am: html: html-am html-am: info: info-am info-am: install-data-am: install-man install-dvi: install-dvi-am install-dvi-am: install-exec-am: install-binPROGRAMS install-html: install-html-am install-html-am: install-info: install-info-am install-info-am: install-man: install-man1 install-pdf: install-pdf-am install-pdf-am: install-ps: install-ps-am install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-am -rm -f $(am__CONFIG_DISTCLEAN_FILES) -rm -rf $(top_srcdir)/autom4te.cache -rm -f deps/$(DEPDIR)/makeheaders.Po -rm -f deps/$(DEPDIR)/yxml.Po -rm -f deps/ylib/$(DEPDIR)/yuri.Po -rm -f doc/$(DEPDIR)/gendoc.Po -rm -f src/$(DEPDIR)/bloom.Po -rm -f src/$(DEPDIR)/cc.Po -rm -f src/$(DEPDIR)/commands.Po -rm -f src/$(DEPDIR)/db.Po -rm -f src/$(DEPDIR)/dl.Po -rm -f src/$(DEPDIR)/dlfile.Po -rm -f src/$(DEPDIR)/fl_load.Po -rm -f src/$(DEPDIR)/fl_local.Po -rm -f src/$(DEPDIR)/fl_save.Po -rm -f src/$(DEPDIR)/fl_util.Po -rm -f src/$(DEPDIR)/geoip.Po -rm -f src/$(DEPDIR)/hub.Po -rm -f src/$(DEPDIR)/listen.Po -rm -f src/$(DEPDIR)/main.Po -rm -f src/$(DEPDIR)/net.Po -rm -f src/$(DEPDIR)/proto.Po -rm -f src/$(DEPDIR)/search.Po -rm -f src/$(DEPDIR)/strutil.Po -rm -f src/$(DEPDIR)/tth.Po -rm -f src/$(DEPDIR)/ui.Po -rm -f src/$(DEPDIR)/ui_colors.Po -rm -f src/$(DEPDIR)/ui_listing.Po -rm -f src/$(DEPDIR)/ui_logwindow.Po -rm -f src/$(DEPDIR)/ui_textinput.Po -rm -f src/$(DEPDIR)/uit_conn.Po -rm -f src/$(DEPDIR)/uit_dl.Po -rm -f src/$(DEPDIR)/uit_fl.Po -rm -f src/$(DEPDIR)/uit_hub.Po -rm -f src/$(DEPDIR)/uit_main.Po -rm -f src/$(DEPDIR)/uit_msg.Po -rm -f src/$(DEPDIR)/uit_search.Po -rm -f src/$(DEPDIR)/uit_userlist.Po -rm -f src/$(DEPDIR)/util.Po -rm -f src/$(DEPDIR)/vars.Po -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic mostlyclean: mostlyclean-am mostlyclean-am: mostlyclean-compile mostlyclean-generic pdf: pdf-am pdf-am: ps: ps-am ps-am: uninstall-am: uninstall-binPROGRAMS uninstall-man uninstall-man: uninstall-man1 .MAKE: all install-am install-strip .PHONY: CTAGS GTAGS TAGS all all-am am--depfiles am--refresh check \ check-am clean clean-binPROGRAMS clean-cscope clean-generic \ clean-noinstLIBRARIES clean-noinstPROGRAMS cscope \ cscopelist-am ctags ctags-am dist dist-all dist-bzip2 \ dist-gzip dist-lzip dist-shar dist-tarZ dist-xz dist-zip \ dist-zstd distcheck distclean distclean-compile \ distclean-generic distclean-hdr distclean-tags distcleancheck \ distdir distuninstallcheck dvi dvi-am html html-am info \ info-am install install-am install-binPROGRAMS install-data \ install-data-am install-dvi install-dvi-am install-exec \ install-exec-am install-html install-html-am install-info \ install-info-am install-man install-man1 install-pdf \ install-pdf-am install-ps install-ps-am install-strip \ installcheck installcheck-am installdirs maintainer-clean \ maintainer-clean-generic mostlyclean mostlyclean-compile \ mostlyclean-generic pdf pdf-am ps ps-am tags tags-am uninstall \ uninstall-am uninstall-binPROGRAMS uninstall-man \ uninstall-man1 .PRECIOUS: Makefile @USE_POD2MAN_TRUE@doc/ncdc.pod: $(srcdir)/doc/ncdc.pod.in gendoc$(EXEEXT) @USE_POD2MAN_TRUE@ $(AM_V_GEN)./gendoc$(EXEEXT) <"$(srcdir)/doc/ncdc.pod.in" >doc/ncdc.pod @USE_POD2MAN_TRUE@doc/ncdc.1: doc/ncdc.pod @USE_POD2MAN_TRUE@ $(AM_V_GEN)pod2man --center "ncdc manual" --release "@PACKAGE@-@VERSION@" doc/ncdc.pod >doc/ncdc.1 # Create a separate version.h and make sure only main.c depends on it. This # avoids the need to recompile everything on each commit. @USE_GIT_VERSION_TRUE@src/version.h: $(srcdir)/.git/logs/HEAD @USE_GIT_VERSION_TRUE@ $(AM_V_GEN)echo '"'"`git describe --abbrev=4 --dirty=-d | sed s/^v//`"'"' >src/version.h @USE_GIT_VERSION_FALSE@src/version.h: Makefile @USE_GIT_VERSION_FALSE@ $(AM_V_GEN)echo '"'"@VERSION@"'"' >src/version.h src/main.$(OBJEXT): src/version.h $(auto_headers): mkhdr.done mkhdr.done: $(mkhdr_dep) $(ncdc_SOURCES) $(AM_V_GEN)$(mkhdr) `echo $(ncdc_SOURCES) | sed 's#\([^ ]*\)\.c#$(srcdir)/\1.c:$(builddir)/\1.h#g'` && touch mkhdr.done # Regenerate the header dependencies below, should be run every time # ncdc_SOURCES is modified. update-headerdeps: cd $(srcdir) &&\ perl -le 'print "$$_.\$$(OBJEXT): $$_.h" for grep s/\.c//, @ARGV' -- $(ncdc_SOURCES) |\ perl -e 'open(I, "Makefile.am~") or die $$!; while() { print O $$_; last if /^# HEADER_DEPS/ }; print O $$_ while(<>);' mv $(srcdir)/Makefile.am~ $(srcdir)/Makefile.am # !! Do not write anything below this line !! # HEADER_DEPS src/bloom.$(OBJEXT): src/bloom.h src/cc.$(OBJEXT): src/cc.h src/commands.$(OBJEXT): src/commands.h src/db.$(OBJEXT): src/db.h src/dl.$(OBJEXT): src/dl.h src/dlfile.$(OBJEXT): src/dlfile.h src/fl_load.$(OBJEXT): src/fl_load.h src/fl_local.$(OBJEXT): src/fl_local.h src/fl_save.$(OBJEXT): src/fl_save.h src/fl_util.$(OBJEXT): src/fl_util.h src/geoip.$(OBJEXT): src/geoip.h src/hub.$(OBJEXT): src/hub.h src/listen.$(OBJEXT): src/listen.h src/main.$(OBJEXT): src/main.h src/net.$(OBJEXT): src/net.h src/proto.$(OBJEXT): src/proto.h src/search.$(OBJEXT): src/search.h src/strutil.$(OBJEXT): src/strutil.h src/tth.$(OBJEXT): src/tth.h src/ui.$(OBJEXT): src/ui.h src/ui_colors.$(OBJEXT): src/ui_colors.h src/ui_listing.$(OBJEXT): src/ui_listing.h src/ui_logwindow.$(OBJEXT): src/ui_logwindow.h src/ui_textinput.$(OBJEXT): src/ui_textinput.h src/uit_conn.$(OBJEXT): src/uit_conn.h src/uit_dl.$(OBJEXT): src/uit_dl.h src/uit_fl.$(OBJEXT): src/uit_fl.h src/uit_hub.$(OBJEXT): src/uit_hub.h src/uit_main.$(OBJEXT): src/uit_main.h src/uit_msg.$(OBJEXT): src/uit_msg.h src/uit_search.$(OBJEXT): src/uit_search.h src/uit_userlist.$(OBJEXT): src/uit_userlist.h src/util.$(OBJEXT): src/util.h src/vars.$(OBJEXT): src/vars.h # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: ncdc-1.23.1/doc/0000755000175000017500000000000014314550104010250 500000000000000ncdc-1.23.1/doc/ncdc.10000644000175000017500000015263314314550104011173 00000000000000.\" Automatically generated by Pod::Man 4.14 (Pod::Simple 3.42) .\" .\" Standard preamble: .\" ======================================================================== .de Sp \" Vertical space (when we can't use .PP) .if t .sp .5v .if n .sp .. .de Vb \" Begin verbatim text .ft CW .nf .ne \\$1 .. .de Ve \" End verbatim text .ft R .fi .. .\" Set up some character translations and predefined strings. \*(-- will .\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left .\" double quote, and \*(R" will give a right double quote. \*(C+ will .\" give a nicer C++. Capital omega is used to do unbreakable dashes and .\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff, .\" nothing in troff, for use with C<>. .tr \(*W- .ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' .ie n \{\ . ds -- \(*W- . ds PI pi . if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch . if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch . ds L" "" . ds R" "" . ds C` "" . ds C' "" 'br\} .el\{\ . ds -- \|\(em\| . ds PI \(*p . ds L" `` . ds R" '' . ds C` . ds C' 'br\} .\" .\" Escape single quotes in literal strings from groff's Unicode transform. .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" .\" If the F register is >0, we'll generate index entries on stderr for .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index .\" entries marked with X<> in POD. Of course, you'll have to process the .\" output yourself in some meaningful fashion. .\" .\" Avoid warning from groff about undefined register 'F'. .de IX .. .nr rF 0 .if \n(.g .if rF .nr rF 1 .if (\n(rF:(\n(.g==0)) \{\ . if \nF \{\ . de IX . tm Index:\\$1\t\\n%\t"\\$2" .. . if !\nF==2 \{\ . nr % 0 . nr F 2 . \} . \} .\} .rr rF .\" .\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). .\" Fear. Run. Save yourself. No user-serviceable parts. . \" fudge factors for nroff and troff .if n \{\ . ds #H 0 . ds #V .8m . ds #F .3m . ds #[ \f1 . ds #] \fP .\} .if t \{\ . ds #H ((1u-(\\\\n(.fu%2u))*.13m) . ds #V .6m . ds #F 0 . ds #[ \& . ds #] \& .\} . \" simple accents for nroff and troff .if n \{\ . ds ' \& . ds ` \& . ds ^ \& . ds , \& . ds ~ ~ . ds / .\} .if t \{\ . ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u" . ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u' . ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u' . ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u' . ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u' . ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u' .\} . \" troff and (daisy-wheel) nroff accents .ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V' .ds 8 \h'\*(#H'\(*b\h'-\*(#H' .ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#] .ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H' .ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u' .ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#] .ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#] .ds ae a\h'-(\w'a'u*4/10)'e .ds Ae A\h'-(\w'A'u*4/10)'E . \" corrections for vroff .if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u' .if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u' . \" for low resolution devices (crt and lpr) .if \n(.H>23 .if \n(.V>19 \ \{\ . ds : e . ds 8 ss . ds o a . ds d- d\h'-1'\(ga . ds D- D\h'-1'\(hy . ds th \o'bp' . ds Th \o'LP' . ds ae ae . ds Ae AE .\} .rm #[ #] #H #V #F C .\" ======================================================================== .\" .IX Title "NCDC 1" .TH NCDC 1 "2022-09-27" "ncdc-1.23.1" "ncdc manual" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l .nh .SH "NAME" ncdc \- Ncurses Direct Connect Client .SH "SYNOPSIS" .IX Header "SYNOPSIS" ncdc [options] .SH "DESCRIPTION" .IX Header "DESCRIPTION" Ncdc is a modern and lightweight direct connect client with a friendly ncurses interface. .SH "GETTING STARTED" .IX Header "GETTING STARTED" This is a basic introduction for those who are new to ncdc. See the chapters below for a more detailed description of the available functionality. .PP What you see when starting up ncdc is an input line where you can input commands and a log window where the results are displayed, much like a regular terminal. Commands within ncdc start with a slash (e.g. \f(CW\*(C`/help\*(C'\fR) and have tab completion to help you. .PP The first thing you will want to do after starting ncdc for the first time is to setup some basic information and settings: .PP .Vb 4 \& /set nick MyNick \& /set description ncdc is awesome! \& /set connection 10 \& /share "My Awesome Files" /path/to/files .Ve .PP And if you have a direct connection to the internet or if your router allows port forwarding, you may also want to enable active mode: .PP .Vb 2 \& /set active_port 34194 \& /set active true .Ve .PP See the help text for each of the commands and settings for more information. Of course, all of the above settings are saved to the database and will be used again on the next run. .PP To connect to a hub, use /open: .PP .Vb 1 \& /open ncdc adc://dc.blicky.net:2780/ .Ve .PP Here \fIncdc\fR is the personal name you give to the hub, and the second argument the \s-1URL.\s0 This \s-1URL\s0 will be saved in the database, so the next time you want to connect to this hub, you can simply do \f(CW\*(C`/open ncdc\*(C'\fR. See the help text for \&\f(CW\*(C`/open\*(C'\fR and \f(CW\*(C`/connect\*(C'\fR for more information. If you want to automatically connect to a hub when ncdc starts up, use the \f(CW\*(C`autoconnect\*(C'\fR setting. .PP Ncdc uses a tabbed interface: every hub opens in a new tab, and there are several other kinds of tabs available as well. The type of tab is indicated in the tab list on the bottom of the screen with a character prefix. Hubs, for example, are prefixed with a \f(CW\*(C`#\*(C'\fR. If a tab needs your attention, a colored exclamation mark is displayed before the tab name, different colors are used for different types of activity. .PP Everything else should be fairly self-explanatory: To search for files, use the \&\f(CW\*(C`/search\*(C'\fR command. To browse through the user list of a hub, use \f(CW\*(C`/userlist\*(C'\fR or hit Alt+u. To browse someone's file list, use \f(CW\*(C`/browse\*(C'\fR or hit the 'b' key in the user list. And to monitor your upload and download connections, use \&\f(CW\*(C`/connections\*(C'\fR or hit Alt+n. .SH "OPTIONS" .IX Header "OPTIONS" .IP "\fB\-c, \-\-session\-dir=\fR \fIdir\fR" 4 .IX Item "-c, --session-dir= dir" Use a different session directory. Defaults to the contents of the environment variable `$NCDC_DIR' or if this is unset to `$HOME/.ncdc'. .IP "\fB\-h, \-\-help\fR" 4 .IX Item "-h, --help" Display summary of options. .IP "\fB\-n, \-\-no\-autoconnect\fR" 4 .IX Item "-n, --no-autoconnect" Don't automatically connect to hubs with the \f(CW\*(C`autoconnect\*(C'\fR option set. .IP "\fB\-\-no\-bracketed\-paste\fR" 4 .IX Item "--no-bracketed-paste" Disable bracketed pasting. .IP "\fB\-v, \-\-version\fR" 4 .IX Item "-v, --version" Display ncdc version. .SH "GETTING CONNECTED" .IX Header "GETTING CONNECTED" As with most file sharing clients, ncdc supports two modes of being connected: \&\fIactive\fR and \fIpassive\fR. In passive mode (the default), you can connect to the outside world but nobody can connect (directly) to you. When passive, you will only be able to transfer files with people who are in active mode. In active mode, however, you will have some port open to the rest of the network to which other clients can connect. When active, you will be able to transfer files with everyone and you may get more and faster search results. Configuring active mode is therefore recommended. .PP In many setups, all you need to do to switch to active mode is to set a \s-1TCP/UDP\s0 port and enable the \f(CW\*(C`active\*(C'\fR setting: .PP .Vb 2 \& /set active_port 34194 \& /set active true .Ve .PP When you connect to a hub, the status bar will tell you whether you are active or passive on that particular hub, and what \s-1IP\s0 address is being used to allow others to connect to you. For most hubs, your \s-1IP\s0 address will be detected automatically, but in the event that this fails, you can also set it yourself: .PP .Vb 1 \& /set active_ip 13.33.33.7 .Ve .PP If you are behind a \s-1NAT\s0 or firewall, you have to ensure that the port you configured is somehow allowed and/or forwarded. The \f(CW\*(C`active_port\*(C'\fR setting is used for incoming \s-1TCP\s0 connections and \s-1UDP\s0 messages. You can configure a different \s-1UDP\s0 port with the \f(CW\*(C`active_udp_port\*(C'\fR setting. Contrary to many toher Direct Connect clients, ncdc only uses a single port for incoming \s-1TCP\s0 and \s-1TLS\s0 connections; There is no separate port for \s-1TLS.\s0 .PP The \f(CW\*(C`/listen\*(C'\fR command can tell you which ports it expects to be forwarded, and for which hubs these ports will be used. It only lists hubs on which you are currently active, so the output will change when you open or close a hub connection. .PP If you have multiple network interfaces, you can force ncdc to use only a single interface by setting the \f(CW\*(C`local_address\*(C'\fR setting to the address of that interface. This affects both outgoing connections (they will be forced to go through the configured interface) and incoming connections (the ports will be bound to the configured interface). .PP All of the previously mentioned settings can be set globally (with \f(CW\*(C`/set\*(C'\fR) and on a per-hub basis (with \f(CW\*(C`/hset\*(C'\fR). This allows you to be active on an internet hub and a LAN-only hub at the same time. It also allows you to be active in one hub while passive in another, or to use different ports for each hub. .SH "INTERACTIVE COMMANDS" .IX Header "INTERACTIVE COMMANDS" The following is the list of commands that can be used within ncdc. The /help command can also be used get a list of available commands and to access this documentation. .IP "\fB/accept\fR" 4 .IX Item "/accept" Use this command to accept the \s-1TLS\s0 certificate of a hub. This command is used only in the case the keyprint of the \s-1TLS\s0 certificate of a hub does not match the keyprint stored in the database. .IP "\fB/browse\fR [[\-f] ]" 4 .IX Item "/browse [[-f] ]" Without arguments, this opens a new tab where you can browse your own file list. Note that changes to your list are not immediately visible in the browser. You need to re-open the tab to get the latest version of your list. .Sp With arguments, the file list of the specified user will be downloaded (if it has not been downloaded already) and the browse tab will open once it's complete. The `\-f' flag can be used to force the file list to be (re\-)downloaded. .IP "\fB/clear\fR" 4 .IX Item "/clear" Clears the log displayed on the screen. Does not affect the log files in any way. Ctrl+l is a shortcut for this command. .IP "\fB/close\fR" 4 .IX Item "/close" Close the current tab. When closing a hub tab, you will be disconnected from the hub and all related userlist and \s-1PM\s0 tabs will also be closed. Alt+c is a shortcut for this command. .IP "\fB/connect\fR [
]" 4 .IX Item "/connect [
]" Initiate a connection with a hub. If no address is specified, will connect to the hub you last used on the current tab. The address should be in the form of `protocol://host:port/' or `host:port'. The `:port' part is in both cases optional and defaults to :411. The following protocols are recognized: dchub, nmdc, nmdcs, adc, adcs. When connecting to an nmdcs or adcs hub and the \s-1SHA256\s0 keyprint is known, you can attach this to the url as `?kp=SHA256/' .Sp Note that this command can only be used on hub tabs. If you want to open a new connection to a hub, you need to use /open first. For example: .Sp .Vb 2 \& /open testhub \& /connect dchub://dc.some\-test\-hub.com/ .Ve .Sp See the /open command for more information. .IP "\fB/connections\fR" 4 .IX Item "/connections" Open the connections tab. .IP "\fB/delhub\fR " 4 .IX Item "/delhub " Remove a hub from the configuration .IP "\fB/disconnect\fR" 4 .IX Item "/disconnect" Disconnect from a hub. .IP "\fB/gc\fR" 4 .IX Item "/gc" Cleans up unused data and reorganizes existing data to allow more efficient storage and usage. Currently, this commands removes unused hash data, does a \s-1VACUUM\s0 on db.sqlite3, removes unused files in inc/ and old files in fl/. .Sp This command may take some time to complete, and will fully block ncdc while it is running. It is recommended to run this command every once in a while. Every month is a good interval. Note that when ncdc says that it has completed this command, it's lying to you. Ncdc will still run a few large queries on the background, which may take up to a minute to complete. .IP "\fB/grant\fR [\-list|]" 4 .IX Item "/grant [-list|]" Grant someone a slot. This allows the user to download from you even if you have no free slots. The slot will remain granted until the /ungrant command is used, even if ncdc has been restarted in the mean time. .Sp To get a list of users whom you have granted a slot, use `/grant' without arguments or with `\-list'. Be warned that using `/grant' without arguments on a \s-1PM\s0 tab will grant the slot to the user you are talking with. Make sure to use `\-list' in that case. .Sp Note that a granted slot is specific to a single hub. If the same user is also on other hubs, he/she will not be granted a slot on those hubs. .IP "\fB/help\fR [|set |keys [
]]" 4 .IX Item "/help [|set |keys [
]]" To get a list of available commands, use /help without arguments. To get information on a particular command, use /help . To get information on a configuration setting, use /help set . To get help on key bindings, use /help keys. .IP "\fB/hset\fR [ []]" 4 .IX Item "/hset [ []]" Get or set per-hub configuration variables. Works equivalent to the `/set' command, but can only be used on hub tabs. Use `/hunset' to reset a variable back to its global value. .IP "\fB/hunset\fR []" 4 .IX Item "/hunset []" This command can be used to reset a per-hub configuration variable back to its global value. .IP "\fB/kick\fR " 4 .IX Item "/kick " Kick a user from the hub. This command only works on \s-1NMDC\s0 hubs, and you need to be an \s-1OP\s0 to be able to use it. .IP "\fB/listen\fR" 4 .IX Item "/listen" List currently opened ports. .IP "\fB/me\fR " 4 .IX Item "/me " This allows you to talk in third person. Most clients will display your message as something like: .Sp .Vb 1 \& ** Nick is doing something .Ve .Sp Note that this command only works correctly on \s-1ADC\s0 hubs. The \s-1NMDC\s0 protocol does not have this feature, and your message will be sent as-is, including the /me. .IP "\fB/msg\fR []" 4 .IX Item "/msg []" Send a private message to a user on the currently opened hub. If no message is given, the tab will be opened but no message will be sent. .IP "\fB/nick\fR []" 4 .IX Item "/nick []" Alias for `/hset nick' on hub tabs, and `/set nick' otherwise. .IP "\fB/open\fR [\-n] [] [
]" 4 .IX Item "/open [-n] [] [
]" Without arguments, list all hubs known by the current configuration. Otherwise, this opens a new tab to use for a hub. The name is a (short) personal name you use to identify the hub, and will be used for storing hub-specific configuration. .Sp If you have specified an address or have previously connected to a hub from a tab with the same name, /open will automatically connect to the hub. Use the `\-n' flag to disable this behaviour. .Sp See /connect for more information on connecting to a hub. .IP "\fB/password\fR " 4 .IX Item "/password " This command can be used to send a password to the hub without saving it to the database. If you wish to login automatically without having to type /password every time, use '/hset password '. Be warned, however, that your password will be saved unencrypted in that case. .IP "\fB/pm\fR []" 4 .IX Item "/pm []" Alias for /msg .IP "\fB/queue\fR" 4 .IX Item "/queue" Open the download queue. .IP "\fB/quit\fR" 4 .IX Item "/quit" Quit ncdc. .IP "\fB/reconnect\fR" 4 .IX Item "/reconnect" Reconnect to the hub. When your nick or the hub encoding have been changed, the new settings will be used after the reconnect. .Sp This command can also be used on the main tab, in which case all connected hubs will be reconnected. .IP "\fB/refresh\fR []" 4 .IX Item "/refresh []" Initiates share refresh. If no argument is given, the complete list will be refreshed. Otherwise only the specified directory will be refreshed. The path argument can be either an absolute filesystem path or a virtual path within your share. .IP "\fB/say\fR " 4 .IX Item "/say " Sends a chat message to the current hub or user. You normally don't have to use the /say command explicitly, any command not staring with '/' will automatically imply `/say '. For example, typing `hello.' in the command line is equivalent to `/say hello.'. Using the /say command explicitly may be useful to send message starting with '/' to the chat, for example `/say /help is what you are looking for'. .IP "\fB/search\fR [options] " 4 .IX Item "/search [options] " Performs a file search, opening a new tab with the results. .Sp Available options: .Sp .Vb 6 \& \-hub Search the current hub only. (default) \& \-all Search all connected hubs, except those with \`chat_only\*(Aq set. \& \-le Size of the file must be less than . \& \-ge Size of the file must be larger than . \& \-t File must be of type . (see below) \& \-tth TTH root of this file must match . .Ve .Sp File sizes ( above) accept the following suffixes: G (GiB), M (MiB) and K (KiB). .Sp The following file types can be used with the \-t option: .Sp .Vb 8 \& 1 any Any file or directory. (default) \& 2 audio Audio files. \& 3 archive (Compressed) archives. \& 4 doc Text documents. \& 5 exe Windows executables. \& 6 img Image files. \& 7 video Video files. \& 8 dir Directories. .Ve .Sp Note that file type matching is done using file extensions, and is not very reliable. .IP "\fB/set\fR [ []]" 4 .IX Item "/set [ []]" Get or set global configuration variables. Use without arguments to get a list of all global settings and their current value. Glob-style pattern matching on the settings is also possible. Use, for example, `/set color*' to list all color-related settings. .Sp See the `/unset' command to change a setting back to its default, and the `/hset' command to manage configuration on a per-hub basis. Changes to the settings are automatically saved to the database, and will not be lost after restarting ncdc. .Sp To get information on a particular setting, use `/help set '. .IP "\fB/share\fR [ ]" 4 .IX Item "/share [ ]" Use /share without arguments to get a list of shared directories. When called with a name and a path, the path will be added to your share. Note that shell escaping may be used in the name. For example, to add a directory with the name `Fun Stuff', you could do the following: .Sp .Vb 1 \& /share "Fun Stuff" /path/to/fun/stuff .Ve .Sp Or: .Sp .Vb 1 \& /share Fun\e Stuff /path/to/fun/stuff .Ve .Sp The full path to the directory will not be visible to others, only the name you give it will be public. An initial `/refresh' is done automatically on the added directory. .IP "\fB/ungrant\fR []" 4 .IX Item "/ungrant []" Revoke a granted slot. .IP "\fB/unset\fR []" 4 .IX Item "/unset []" This command can be used to reset a global configuration variable back to its default value. .IP "\fB/unshare\fR []" 4 .IX Item "/unshare []" To remove a single directory from your share, use `/unshare ', to remove all directories from your share, use `/unshare /'. .Sp Note that the hash data associated with the removed files will remain in the database. This allows you to re-add the files to your share without needing to re-hash them. The downside is that the database file may grow fairly large with unneeded information. See the `/gc' command to clean that up. .IP "\fB/userlist\fR" 4 .IX Item "/userlist" Opens the user list of the currently selected hub. Can also be accessed using Alt+u. .IP "\fB/version\fR" 4 .IX Item "/version" Display version information. .IP "\fB/whois\fR " 4 .IX Item "/whois " This will open the user list and select the given user. .SH "SETTINGS" .IX Header "SETTINGS" The following is a list of configuration settings. These settings can be changed and queried using the \f(CW\*(C`/set\*(C'\fR command for global settings and \f(CW\*(C`/hset\*(C'\fR for hub-local settings. All configuration data is stored in the db.sqlite3 file in the session directory. .IP "\fBactive\fR " 4 .IX Item "active " Enables or disables active mode. You may have to configure your router and/or firewall for this to work, see the `active_ip' and `active_port' settings for more information. .IP "\fBactive_ip\fR " 4 .IX Item "active_ip " Your public \s-1IP\s0 address for use in active mode. If this is not set or set to '0.0.0.0' for IPv4 or '::' for IPv6, then ncdc will try to automatically get your \s-1IP\s0 address from the hub. If you do set this manually, it is important that other clients can reach you using this \s-1IP\s0 address. If you connect to a hub on the internet, this should be your internet (\s-1WAN\s0) \s-1IP.\s0 Likewise, if you connect to a hub on your \s-1LAN,\s0 this should be your \s-1LAN IP.\s0 .Sp Both an IPv4 and an IPv6 address are set by providing two \s-1IP\s0 addresses separated with a comma. When unset, '0.0.0.0,::' is assumed. Only the \s-1IP\s0 version used to connect to the hub is used. That is, if you connect to an IPv6 hub, then the configured IPv6 address is used and the IPv4 address is ignored. .Sp When set to the special value `local', ncdc will automatically get your \s-1IP\s0 address from the local network interface that is used to connect to the hub. This option should only be used if there is no \s-1NAT\s0 between you and the hub, because this will give the wrong \s-1IP\s0 if you are behind a \s-1NAT.\s0 .IP "\fBactive_port\fR " 4 .IX Item "active_port " The listen port for incoming connections in active mode. Set to `0' to automatically assign a random port. This setting is by default also used for the \s-1UDP\s0 port, see the `active_tls_port' settings to change that. If you are behind a router or firewall, make sure that you have configured it to forward and allow these ports. .IP "\fBactive_udp_port\fR " 4 .IX Item "active_udp_port " The listen port for incoming \s-1UDP\s0 connections in active mode. Defaults to the `active_port' setting, or to a random number if `active_port' is not set. .IP "\fBadc_blom\fR " 4 .IX Item "adc_blom " Whether to support the \s-1BLOM\s0 extension on \s-1ADC\s0 hubs. This may decrease the bandwidth usage on the hub connection, in exchange for a bit of computational overhead. Some hubs require this setting to be enabled. This setting requires a reconnect with the hub to be active. .IP "\fBautoconnect\fR " 4 .IX Item "autoconnect " Set to true to automatically connect to the current hub when ncdc starts up. .IP "\fBautorefresh\fR " 4 .IX Item "autorefresh " The time between automatic file refreshes. Recognized suffices are 's' for seconds, 'm' for minutes, 'h' for hours and 'd' for days. Set to 0 to disable automatically refreshing the file list. This setting also determines whether ncdc will perform a refresh on startup. See the `/refresh' command to manually refresh your file list. .IP "\fBbacklog\fR " 4 .IX Item "backlog " When opening a hub or \s-1PM\s0 tab, ncdc can load a certain amount of lines from the log file into the log window. Setting this to a positive value enables this feature and configures the number of lines to load. Note that, while this setting can be set on a per-hub basis, \s-1PM\s0 windows will use the global value (global.backlog). .IP "\fBchat_only\fR " 4 .IX Item "chat_only " Set to true to indicate that this hub is only used for chatting. That is, you won't or can't download from it. This setting affects the /search command when it is given the \-all option. .IP "\fBcolor_*\fR " 4 .IX Item "color_* " The settings starting with the `color_' prefix allow you to change the interface colors. The following is a list of available color settings: .Sp .Vb 10 \& list_default \- default item in a list \& list_header \- header of a list \& list_select \- selected item in a list \& log_default \- default log color \& log_time \- the time prefix in log messages \& log_nick \- default nick color \& log_highlight \- nick color of a highlighted line \& log_ownnick \- color of your own nick \& log_join \- color of join messages \& log_quit \- color of quit messages \& separator \- the list separator/footer bar \& tab_active \- the active tab in the tab list \& tabprio_low \- low priority tab notification color \& tabprio_med \- medium priority tab notification color \& tabprio_high \- high priority tab notification color \& title \- the title bar .Ve .Sp The actual color value can be set with a comma-separated list of color names and/or attributes. The first color in the list is the foreground color, the second color is used for the background. When the fore\- or background color is not specified, the default colors of your terminal will be used. The following color names can be used: black, blue, cyan, default, green, magenta, red, white and yellow. The following attributes can be used: bold, blink, reverse and underline. The actual color values displayed by your terminal may vary. Adding the `bold' attribute usually makes the foreground color appear brighter as well. .IP "\fBconnection\fR " 4 .IX Item "connection " Set your upload speed. This is just an indication for other users in the hub so that they know what speed they can expect when downloading from you. The actual format you can use here may vary, but it is recommended to set it to either a plain number for Mbit/s (e.g. `50' for 50 mbit) or a number with a `KiB/s' indicator (e.g. `2300 KiB/s'). On \s-1ADC\s0 hubs you must use one of the previously mentioned formats, otherwise no upload speed will be broadcasted. This setting is broadcasted as-is on \s-1NMDC\s0 hubs, to allow for using old-style connection values (e.g. `\s-1DSL\s0' or `Cable') on hubs that require this. .Sp This setting is ignored if `upload_rate' has been set. If it is, that value is broadcasted instead. .IP "\fBdescription\fR " 4 .IX Item "description " A short public description that will be displayed in the user list of a hub. .IP "\fBdisconnect_offline\fR " 4 .IX Item "disconnect_offline " Automatically disconnect any upload or download transfers when a user leaves the hub, or when you leave the hub. Setting this to `true' ensures that you are only connected with people who are online on the same hubs as you are. .IP "\fBdownload_dir\fR " 4 .IX Item "download_dir " The directory where finished downloads are moved to. Finished downloads are by default stored in /dl/. It is possible to set this to a location that is on a different filesystem than the incoming directory, but doing so is not recommended: ncdc will block when moving the completed files to their final destination. .IP "\fBdownload_exclude\fR " 4 .IX Item "download_exclude " When recursively adding a directory to the download queue \- by pressing `d' on a directory in the file list browser \- any item in the selected directory with a name that matches this regular expression will not be added to the download queue. .Sp This regex is not checked when adding individual files from either the file list browser or the search results. .IP "\fBdownload_rate\fR " 4 .IX Item "download_rate " Maximum combined transfer rate of all downloads. The total download speed will be limited to this value. The suffixes `G', 'M', and 'K' can be used for GiB/s, MiB/s and KiB/s, respectively. Note that, similar to upload_rate, \s-1TCP\s0 overhead are not counted towards this limit, so the actual bandwidth usage might be a little higher. .IP "\fBdownload_segment\fR " 4 .IX Item "download_segment " Minimum segment size to use when requesting file data from another user. Set to 0 to disable segmented downloading. .IP "\fBdownload_shared\fR " 4 .IX Item "download_shared " Whether to download files which are already present in your share. When this is set to `false', adding already shared files results in a \s-1UI\s0 message instead of adding the file to the download queue. .IP "\fBdownload_slots\fR " 4 .IX Item "download_slots " Maximum number of simultaneous downloads. .IP "\fBemail\fR " 4 .IX Item "email " Your email address. This will be displayed in the user list of the hub, so only set this if you want it to be public. .IP "\fBencoding\fR " 4 .IX Item "encoding " The character set/encoding to use for hub and \s-1PM\s0 messages. This setting is only used on \s-1NMDC\s0 hubs, \s-1ADC\s0 always uses \s-1UTF\-8.\s0 Some common values are: .Sp .Vb 6 \& CP1250 (Central Europe) \& CP1251 (Cyrillic) \& CP1252 (Western Europe) \& ISO\-8859\-7 (Greek) \& KOI8\-R (Cyrillic) \& UTF\-8 (International) .Ve .IP "\fBfilelist_maxage\fR " 4 .IX Item "filelist_maxage " The maximum age of a downloaded file list. If a file list was downloaded longer ago than the configured interval, it will be removed from the cache (the fl/ directory) and subsequent requests to open the file list will result in the list being downloaded from the user again. Recognized suffices are 's' for seconds, 'm' for minutes, 'h' for hours and 'd' for days. Set to 0 to disable the cache altogether. .IP "\fBflush_file_cache\fR [,...]" 4 .IX Item "flush_file_cache [,...]" Tell the \s-1OS\s0 to flush the file (disk) cache for file contents read while hashing and/or uploading or written to while downloading. On one hand, this will avoid trashing your disk cache with large files and thus improve the overall responsiveness of your system. On the other hand, ncdc may purge any shared files from the cache, even if they are still used by other applications. In general, it is a good idea to enable this if you also use your system for other things besides ncdc, you share large files (>100MB) and people are not constantly downloading the same file from you. .IP "\fBgeoip_cc\fR |disabled" 4 .IX Item "geoip_cc |disabled" Path to the GeoIP2 Country database file (GeoLite2\-Country.mmdb), or 'disabled' to disable GeoIP lookups. The database can be downloaded from https://dev.maxmind.com/geoip/geoip2/geolite2/. .IP "\fBhash_rate\fR " 4 .IX Item "hash_rate " Maximum file hashing speed. See the `download_rate' setting for allowed formats for this setting. .IP "\fBhubname\fR " 4 .IX Item "hubname " The name of the currently opened hub tab. This is a user-assigned name, and is only used within ncdc itself. This is the same name as given to the `/open' command. .IP "\fBincoming_dir\fR " 4 .IX Item "incoming_dir " The directory where incomplete downloads are stored. This setting can only be changed when the download queue is empty. Also see the download_dir setting. .IP "\fBlocal_address\fR " 4 .IX Item "local_address " Specifies the address of the local network interface to use for connecting to the outside and for accepting incoming connections in active mode. Both an IPv4 and an IPv6 address are set by providing two \s-1IP\s0 addresses separated with a comma. When unset, '0.0.0.0,::' is assumed. .Sp If no IPv4 address is specified, '0.0.0.0' is added automatically. Similarly, if no IPv6 address is specified, '::' is added automatically. The address that is actually used depends on the \s-1IP\s0 version actually used. That is, if you're on an IPv6 hub, then ncdc will listen on the specified IPv6 address. Note that, even if the hub you're on is on IPv6, ncdc may still try to connect to another client over IPv4, at which point the socket will be bound to the configured IPv4 address. .IP "\fBlog_debug\fR " 4 .IX Item "log_debug " Log debug messages to stderr.log in the session directory. It is highly recommended to enable this setting if you wish to debug or hack ncdc. Be warned, however, that this may generate a lot of data if you're connected to a large hub. .IP "\fBlog_downloads\fR " 4 .IX Item "log_downloads " Log downloaded files to transfers.log. .IP "\fBlog_hubchat\fR " 4 .IX Item "log_hubchat " Log the main hub chat. Note that changing this requires any affected hub tabs to be closed and reopened before the change is effective. .IP "\fBlog_uploads\fR " 4 .IX Item "log_uploads " Log file uploads to transfers.log. .IP "\fBmax_ul_per_user\fR " 4 .IX Item "max_ul_per_user " The maximum number of simultaneous upload connections to one user. .IP "\fBminislots\fR " 4 .IX Item "minislots " Set the number of available minislots. A `minislot' is a special slot that is used when all regular upload slots are in use and someone is requesting your filelist or a small file. In this case, the other client automatically applies for a minislot, and can still download from you as long as not all minislots are in use. What constitutes a `small' file can be changed with the `minislot_size' setting. Also see the `slots' configuration setting and the `/grant' command. .IP "\fBminislot_size\fR " 4 .IX Item "minislot_size " The maximum size of a file that may be downloaded using a `minislot', in KiB. See the `minislots' setting for more information. .IP "\fBnick\fR " 4 .IX Item "nick " Your nick. Nick changes are only visible on newly connected hubs, use the `/reconnect' command to use your new nick immediately. Note that it is highly discouraged to change your nick on \s-1NMDC\s0 hubs. This is because clients downloading from you have no way of knowing that you changed your nick, and therefore can't immediately continue to download from you. .IP "\fBnotify_bell\fR " 4 .IX Item "notify_bell " When enabled, ncdc will send a bell to your terminal when a tab indicates a notification. The notification types are: .Sp .Vb 3 \& high \- Messages directed to you (PM or highlight in hub chat), \& medium \- Regular hub chat, \& low \- User joins/quits, new search results, etc. .Ve .Sp How a \*(L"bell\*(R" (or \*(L"beep\*(R" or \*(L"alert\*(R", whatever you prefer to call it) manifests itself depends on your terminal. In some setups, this generates an audible system bell. In other setups it can makes your terminal window flash or do other annoying things to get your attention. And in some setups it is ignored completely. .IP "\fBpassword\fR " 4 .IX Item "password " Sets your password for the current hub and enables auto-login on connect. If you just want to login to a hub without saving your password, use the `/password' command instead. Passwords are saved unencrypted in the config file. .IP "\fBreconnect_timeout\fR " 4 .IX Item "reconnect_timeout " The time to wait before automatically reconnecting to a hub. Set to 0 to disable automatic reconnect. .IP "\fBsendfile\fR " 4 .IX Item "sendfile " Whether or not to use the \fBsendfile()\fR system call to upload files, if supported. Using \fBsendfile()\fR allows less resource usage while uploading, but may not work well on all systems. .IP "\fBshare_emptydirs\fR " 4 .IX Item "share_emptydirs " Share empty directories. When disabled (the default), empty directories in your share will not be visible to others. This also affects empty directories containing only empty directories, etc. A file list refresh is required for this setting to be effective. .IP "\fBshare_exclude\fR " 4 .IX Item "share_exclude " Any file or directory with a name that matches this regular expression will not be shared. A file list refresh is required for this setting to be effective. .IP "\fBshare_hidden\fR " 4 .IX Item "share_hidden " Whether to share hidden files and directories. A `hidden' file or directory is one of which the file name starts with a dot. (e.g. `.bashrc'). A file list refresh is required for this setting to be effective. .IP "\fBshare_symlinks\fR " 4 .IX Item "share_symlinks " Whether to follow symlinks in shared directories. When disabled (default), ncdc will never share any files outside of the directory you specified. When enabled, any symlinks in your shared directories will be followed, even when they point to a directory outside your share. .IP "\fBshow_free_slots\fR " 4 .IX Item "show_free_slots " When set to true, [n sl] will be prepended to your description, where n is the number of currently available upload slots. .IP "\fBshow_joinquit\fR " 4 .IX Item "show_joinquit " Whether to display join/quit messages in the hub chat. .IP "\fBslots\fR " 4 .IX Item "slots " The number of upload slots. This determines for the most part how many people can download from you simultaneously. It is possible that this limit is exceeded in certain circumstances, see the `minislots' setting and the `/grant' command. .IP "\fBsudp_policy\fR " 4 .IX Item "sudp_policy " Set the policy for sending or receiving encrypted \s-1UDP\s0 search results. When set to `disabled', all \s-1UDP\s0 search results will be sent and received in plain text. Set this to `allow' to let ncdc reply with encrypted search results if the other client requested it. `prefer' will also cause ncdc itself to request encryption. .Sp Note that, regardless of this setting, encrypted \s-1UDP\s0 search results are only used on \s-1ADCS\s0 hubs. They will never be sent on \s-1NMDC\s0 or non-TLS \s-1ADC\s0 hubs. Also note that, even if you set this to `prefer', encryption is still only used when the client on the other side of the connection also supports it. .IP "\fBtls_policy\fR " 4 .IX Item "tls_policy " Set the policy for secure client-to-client connections. Setting this to `disabled' disables \s-1TLS\s0 support for client connections, but still allows you to connect to TLS-enabled hubs. `allow' will allow the use of \s-1TLS\s0 if the other client requests this, but ncdc itself will not request \s-1TLS\s0 when connecting to others, `prefer' tells ncdc to request \s-1TLS\s0 when connecting to others. Setting this to 'force' will disallow non-TLS connections and also requires that the hub connection itself is \s-1TLS.\s0 .Sp The use of \s-1TLS\s0 for client connections usually results in less optimal performance when uploading and downloading, but is quite effective at avoiding protocol-specific traffic shaping that some ISPs may do. .IP "\fBtls_priority\fR " 4 .IX Item "tls_priority " Set the GnuTLS priority string used for all TLS-enabled connections. See the \*(L"Priority strings\*(R" section in the GnuTLS manual for details on what this does and how it works. Currently it is not possible to set a different priority string for different types of connections (e.g. hub or incoming/outgoing client connections). .IP "\fBui_time_format\fR " 4 .IX Item "ui_time_format " The format of the time displayed in the lower-left of the screen. Set `\-' to not display a time at all. The string is passed to the Glib \fBg_date_time_format()\fR function, which accepts roughly the same formats as \fBstrftime()\fR. Check out the \fBstrftime\fR\|(3) man page or the Glib documentation for more information. Note that this setting does not influence the date/time format used in other places, such as the chat window or log files. .IP "\fBupload_rate\fR " 4 .IX Item "upload_rate " Maximum combined transfer rate of all uploads. See the `download_rate' setting for more information on rate limiting. Note that this setting also overrides any `connection' setting. .SH "KEY BINDINGS" .IX Header "KEY BINDINGS" On any tab without the text input line, you can press `?' to get the key bindings for that tab. The list of key bindings is available through the \&\f(CW\*(C`/help keys\*(C'\fR command, and is reproduced below. .IP "\fBGlobal key bindings\fR" 4 .IX Item "Global key bindings" .Vb 11 \& Alt+j Open previous tab. \& Alt+k Open next tab. \& Alt+h Move current tab left. \& Alt+l Move current tab right. \& Alt+a Move tab with recent activity. \& Alt+ Open tab with number . \& Alt+c Close current tab. \& Alt+n Open the connections tab. \& Alt+q Open the download queue tab. \& Alt+o Open own file list. \& Alt+r Refresh file list. \& \& Keys for tabs with a log window: \& Ctrl+l Clear current log window. \& PgUp Scroll the log backward. \& PgDown Scroll the log forward. \& \& Keys for tabs with a text input line: \& Left/Right Move cursor one character left or right. \& End/Home Move cursor to the end / start of the line. \& Up/Down Scroll through the command history. \& Tab Auto\-complete current command, nick or argument. \& Alt+b Move cursor one word backward. \& Alt+f Move cursor one word forward. \& Backspace Delete character before cursor. \& Delete Delete character under cursor. \& Ctrl+w Delete to previous space. \& Alt+d Delete to next space. \& Ctrl+k Delete everything after cursor. \& Ctrl+u Delete entire line. .Ve .IP "\fBFile browser\fR" 4 .IX Item "File browser" .Vb 10 \& Up/Down Select one item up/down. \& k/j Select one item up/down. \& PgUp/PgDown Select one page of items up/down. \& End/Home Select last/first item in the list. \& / Start incremental regex search (press Return to stop editing). \& ,/. Search next / previous. \& Right/l Open selected directory. \& Left/h Open parent directory. \& t Toggle sorting directories before files. \& s Order by file size. \& n Order by file name. \& d Add selected file/directory to the download queue. \& m Match selected item with the download queue. \& M Match entire file list with the download queue. \& a Search for alternative download sources. .Ve .IP "\fBConnection list\fR" 4 .IX Item "Connection list" .Vb 10 \& Up/Down Select one item up/down. \& k/j Select one item up/down. \& PgUp/PgDown Select one page of items up/down. \& End/Home Select last/first item in the list. \& d Disconnect selected connection. \& i/Return Toggle information box. \& f Find user in user list. \& m Send a PM to the selected user. \& q Find file in download queue. \& b/B Browse the selected user\*(Aqs list, B to force a redownload. .Ve .IP "\fBDownload queue\fR" 4 .IX Item "Download queue" .Vb 10 \& Up/Down Select one item up/down. \& k/j Select one item up/down. \& PgUp/PgDown Select one page of items up/down. \& End/Home Select last/first item in the list. \& K/J Select one user up/down. \& f Find user in user list. \& c Find connection in the connection list. \& a Search for alternative download sources. \& d Remove selected file from the queue. \& +/\- Increase/decrease priority. \& i/Return Toggle user list. \& r Remove selected user for this file. \& R Remove selected user from all files in the download queue. \& x Clear error state for the selected user for this file. \& X Clear error state for the selected user for all files. \& \& Note: when an item in the queue has \`ERR\*(Aq indicated in the \& priority column, you have two choices: You can remove the \& item from the queue using \`d\*(Aq, or attempt to continue the \& download by increasing its priority using \`+\*(Aq. .Ve .IP "\fBSearch results tab\fR" 4 .IX Item "Search results tab" .Vb 10 \& Up/Down Select one item up/down. \& k/j Select one item up/down. \& PgUp/PgDown Select one page of items up/down. \& End/Home Select last/first item in the list. \& f Find user in user list. \& b/B Browse the selected user\*(Aqs list, B to force a redownload. \& d Add selected file to the download queue. \& h Toggle hub column visibility. \& u Order by username. \& s Order by file size. \& l Order by free slots. \& n Order by file name. \& m Match selected item with the download queue. \& M Match all search results with the download queue. \& q Match selected users\*(Aq list with the download queue. \& Q Match all matched users\*(Aq lists with the download queue. \& a Search for alternative download sources. .Ve .IP "\fBUser list tab\fR" 4 .IX Item "User list tab" .Vb 10 \& Up/Down Select one item up/down. \& k/j Select one item up/down. \& PgUp/PgDown Select one page of items up/down. \& End/Home Select last/first item in the list. \& / Start incremental regex search (press Return to stop editing). \& ,/. Search next / previous. \& o Toggle sorting OPs before others. \& s/S Order by share size. \& u/U Order by username. \& t/T Toggle visibility / order by tag column. \& e/E Toggle visibility / order by email column. \& c/C Toggle visibility / order by connection column. \& p/P Toggle visibility / order by IP column. \& i/Return Toggle information box. \& m Send a PM to the selected user. \& g Grant a slot to the selected user. \& b/B Browse the selected users\*(Aq list, B to force a redownload. \& q Match selected users\*(Aq list with the download queue. .Ve .SH "WEIRD UI FLAGS" .IX Header "WEIRD UI FLAGS" Some listings have a flags display, and their meaning may not be immediately obvious. .SS "File status" .IX Subsection "File status" You may see one-letter flags to the left of file names in search results and file list tabs. Their meaning is as follows: .IP "H" 4 .IX Item "H" The file had been added to the file list, but has not been hashed yet and thus is not visible to others. This flag can appear only when browsing your own list. .IP "S" 4 .IX Item "S" The file is already in your share. If \f(CW\*(C`download_shared\*(C'\fR is set to \f(CW\*(C`false\*(C'\fR, trying to download it will result only in an informational message. This flag never appears when browsing your own file list. .IP "Q" 4 .IX Item "Q" The file is currently in your download queue. Trying to download it will result only in an informational message. .SS "Connection flags" .IX Subsection "Connection flags" On the connection list window, there are two flags: .IP "S" 4 .IX Item "S" Connection state, can be either \fBC\fRonnecting, \fBH\fRandshake, \fBI\fRdle, \&\fBD\fRownloading, \fBU\fRploading or \fB\-\fR for disconnected. .IP "t" 4 .IX Item "t" This means that the connection is encrypted with \s-1TLS.\s0 .SS "User flags" .IX Subsection "User flags" The user list has some flags too: .IP "o" 4 .IX Item "o" Set if the user is an operator in the hub. .IP "p" 4 .IX Item "p" When the user is passive. If this flag is not set, the user is active. .IP "t" 4 .IX Item "t" Set if connections with this user will be encrypted with \s-1TLS.\s0 .SH "ENVIRONMENT" .IX Header "ENVIRONMENT" \&\f(CW$NCDC_DIR\fR is used to determine the session dir, it is only honoured if \fI\-c\fR is not set on the command line. .SH "FILES" .IX Header "FILES" \&\f(CW$NCDC_DIR\fR corresponds to the session dir set via \fI\-c\fR, environment variable \&\f(CW$NCDC_DIR\fR or \f(CW$HOME\fR/.ncdc. .ie n .IP "$NCDC_DIR/cert/" 4 .el .IP "\f(CW$NCDC_DIR\fR/cert/" 4 .IX Item "$NCDC_DIR/cert/" Directory where the client certificates are stored. Must contain a private key file (client.key) and public certificate (client.crt). These will be generated automatically when ncdc starts up the first time. .ie n .IP "$NCDC_DIR/db.sqlite3" 4 .el .IP "\f(CW$NCDC_DIR\fR/db.sqlite3" 4 .IX Item "$NCDC_DIR/db.sqlite3" The database. This stores all configuration variables, hash data of shared files, download queue information and other state information. Manually editing this file with the `sqlite3' commandline tool is possible but discouraged. Any changes made to the database while ncdc is running will not be read, and may even get overwritten by ncdc. .ie n .IP "$NCDC_DIR/dl/" 4 .el .IP "\f(CW$NCDC_DIR\fR/dl/" 4 .IX Item "$NCDC_DIR/dl/" Directory where completed downloads are moved to by default. Can be changed with the \f(CW\*(C`download_dir\*(C'\fR configuration option. .ie n .IP "$NCDC_DIR/files.xml.bz2" 4 .el .IP "\f(CW$NCDC_DIR\fR/files.xml.bz2" 4 .IX Item "$NCDC_DIR/files.xml.bz2" Filelist containing a listing of all shared files. .ie n .IP "$NCDC_DIR/fl/" 4 .el .IP "\f(CW$NCDC_DIR\fR/fl/" 4 .IX Item "$NCDC_DIR/fl/" Directory where downloaded file lists from other users are stored. The names of the files are hex-encoded user IDs that are used internally by ncdc. Old file lists are deleted automatically after a configurable interval. See the \&\f(CW\*(C`filelist_maxage\*(C'\fR configuration option. .ie n .IP "$NCDC_DIR/history" 4 .el .IP "\f(CW$NCDC_DIR\fR/history" 4 .IX Item "$NCDC_DIR/history" Command history. .ie n .IP "$NCDC_DIR/inc/" 4 .el .IP "\f(CW$NCDC_DIR\fR/inc/" 4 .IX Item "$NCDC_DIR/inc/" Default location for incomplete downloads. Can be changed with the \&\f(CW\*(C`incoming_dir\*(C'\fR setting. The file names in this directory are the base32\-encoded \s-1TTH\s0 root of the completed file. .ie n .IP "$NCDC_DIR/logs/" 4 .el .IP "\f(CW$NCDC_DIR\fR/logs/" 4 .IX Item "$NCDC_DIR/logs/" Directory where all the log files are stored. File names starting with `#' are hub logs and `~' are user (\s-1PM\s0) logs. Special log files are transfers.log and main.log. .Sp ncdc does not have built-in functionality to rotate or compress log files automatically. When rotating log files manually (e.g. via a cron job), make sure to send the \s-1SIGUSR1\s0 signal afterwards to force ncdc to flush the old logs and create or open the new log files. .ie n .IP "$NCDC_DIR/stderr.log" 4 .el .IP "\f(CW$NCDC_DIR\fR/stderr.log" 4 .IX Item "$NCDC_DIR/stderr.log" Error/debug log. This file is cleared every time ncdc starts up. .ie n .IP "$NCDC_DIR/version" 4 .el .IP "\f(CW$NCDC_DIR\fR/version" 4 .IX Item "$NCDC_DIR/version" Version of the data directory. This file locked while an ncdc instance is running, making sure that no two ncdc instances work with the same session directory at the same time. .SS "Format of transfers.log" .IX Subsection "Format of transfers.log" Uploads and downloads are logged in the transfers.log file. Transfers are separated by a newline (\f(CW0x0a\fR). Each log line has the following fields, separated by a space: .IP "1." 4 Date/time when the transfer ended, formatted as \f(CW\*(C`[YYYY\-MM\-DD HH:MM:SS ZONE]\*(C'\fR, .IP "2." 4 Hub name, including the \f(CW\*(C`#\*(C'\fR prefix, .IP "3." 4 Base32\-encoded \s-1CID\s0 of the other user for \s-1ADC\s0 transfers, or a '\-' for \s-1NMDC,\s0 .IP "4." 4 User name (escaped), .IP "5." 4 IPv4 or IPv6 address, .IP "6." 4 Direction, \f(CW\*(C`u\*(C'\fR for upload or \f(CW\*(C`d\*(C'\fR for download, .IP "7." 4 Whether the transfer completed successfully (\f(CW\*(C`c\*(C'\fR) or has been interrupted/disconnected before all requested file data has been transferred (\f(CW\*(C`i\*(C'\fR), .IP "8." 4 Base32\-encoded \s-1TTH\s0 of the transferred file, or '\-' for \f(CW\*(C`files.xml.bz2\*(C'\fR, .IP "9." 4 Total transfer time, in seconds, .IP "10." 4 File size, in bytes, .IP "11." 4 File offset, in bytes, .IP "12." 4 Transfer size, in bytes, .IP "13." 4 File path (escaped). Absolute virtual path for uploads, destination path for downloads. .PP All fields are encoded in \s-1UTF\-8.\s0 Fields that may contain a space or newline are escaped as follows: A space is escaped as \f(CW\*(C`\es\*(C'\fR, a newline as \f(CW\*(C`\en\*(C'\fR and a backslash as \f(CW\*(C`\e\e\*(C'\fR. The timestamp is not escaped. .PP Many clients download files is separate (smallish) chunks. Ncdc makes no attempt to combine multiple chunk requests in a single log entry, so you may see the same uploaded file several times with a different file offset. .SH "LICENSE" .IX Header "LICENSE" Copyright (C) 2011\-2019 Yoran Heling .PP ncdc is distributed under the \s-1MIT\s0 license, please read the \s-1COPYING\s0 file for more information. .SH "BUGS" .IX Header "BUGS" Please report bugs or feature requests to the bug tracker or the mailing list. Both can be found on the ncdc homepage at . There is also an \s-1ADC\s0 hub available at \f(CW\*(C`adc://dc.blicky.net:2780/\*(C'\fR for general support and discussions. .SH "AUTHOR" .IX Header "AUTHOR" ncdc is written by Yoran Heling .PP Web: ncdc-1.23.1/doc/gendoc.c0000644000175000017500000000567313446364113011616 00000000000000/* ncdc - NCurses Direct Connect client Copyright (c) 2011-2019 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include #include #include "config.h" #define DOC_CMD #define DOC_SET #define DOC_KEY #include "../src/doc.h" static void gen_cmd() { const doc_cmd_t *c = doc_cmds; printf("=over\n\n"); for(; *c->name; c++) { printf("=item B", c->name); if(c->args) printf(" %s", c->args); fputs("\n\n", stdout); fputs(c->desc ? c->desc : c->sum, stdout); fputs("\n\n", stdout); } printf("=back\n\n"); } static void gen_set() { const doc_set_t *s = doc_sets; printf("=over\n\n"); for(; s->name; s++) { printf("=item B<%s> %s\n\n", s->name, s->type); fputs(s->desc, stdout); fputs("\n\n", stdout); } printf("=back\n\n"); } static void gen_key() { const doc_key_t *k = doc_keys; printf("=over\n\n"); for(; k->sect; k++) { printf("=item B<%s>\n\n ", k->title); // TODO: It would be nicer to have this in POD rather than as verbatim paragraphs const char *m = k->desc; for(; *m; m++) { if(*m == '\n') fputs("\n ", stdout); else fputc(*m, stdout); } fputs("\n\n", stdout); } printf("=back\n\n"); } int main(int argc, char **argv) { if(argc != 1) { fprintf(stderr, "This command does not accept any commandline arguments."); return 1; } char line[4096]; while(fgets(line, sizeof(line), stdin) != NULL) { char *t = line; char *m; while((m = strchr(t, '@')) != NULL) { fwrite(t, 1, m-t, stdout); t = m; if(strncmp(m, "@commands@", 10) == 0) { gen_cmd(); t += 10; } else if(strncmp(m, "@settings@", 10) == 0) { gen_set(); t += 10; } else if(strncmp(m, "@keys@", 6) == 0) { gen_key(); t += 6; } else { fputc('@', stdout); t++; } } fputs(t, stdout); } return 0; } ncdc-1.23.1/doc/ncdc.pod.in0000644000175000017500000002710013446364231012221 00000000000000 =head1 NAME ncdc - Ncurses Direct Connect Client =head1 SYNOPSIS ncdc [options] =head1 DESCRIPTION Ncdc is a modern and lightweight direct connect client with a friendly ncurses interface. =head1 GETTING STARTED This is a basic introduction for those who are new to ncdc. See the chapters below for a more detailed description of the available functionality. What you see when starting up ncdc is an input line where you can input commands and a log window where the results are displayed, much like a regular terminal. Commands within ncdc start with a slash (e.g. C) and have tab completion to help you. The first thing you will want to do after starting ncdc for the first time is to setup some basic information and settings: /set nick MyNick /set description ncdc is awesome! /set connection 10 /share "My Awesome Files" /path/to/files And if you have a direct connection to the internet or if your router allows port forwarding, you may also want to enable active mode: /set active_port 34194 /set active true See the help text for each of the commands and settings for more information. Of course, all of the above settings are saved to the database and will be used again on the next run. To connect to a hub, use /open: /open ncdc adc://dc.blicky.net:2780/ Here I is the personal name you give to the hub, and the second argument the URL. This URL will be saved in the database, so the next time you want to connect to this hub, you can simply do C. See the help text for C and C for more information. If you want to automatically connect to a hub when ncdc starts up, use the C setting. Ncdc uses a tabbed interface: every hub opens in a new tab, and there are several other kinds of tabs available as well. The type of tab is indicated in the tab list on the bottom of the screen with a character prefix. Hubs, for example, are prefixed with a C<#>. If a tab needs your attention, a colored exclamation mark is displayed before the tab name, different colors are used for different types of activity. Everything else should be fairly self-explanatory: To search for files, use the C command. To browse through the user list of a hub, use C or hit Alt+u. To browse someone's file list, use C or hit the 'b' key in the user list. And to monitor your upload and download connections, use C or hit Alt+n. =head1 OPTIONS =over =item B<-c, --session-dir=> I Use a different session directory. Defaults to the contents of the environment variable `$NCDC_DIR' or if this is unset to `$HOME/.ncdc'. =item B<-h, --help> Display summary of options. =item B<-n, --no-autoconnect> Don't automatically connect to hubs with the C option set. =item B<--no-bracketed-paste> Disable bracketed pasting. =item B<-v, --version> Display ncdc version. =back =head1 GETTING CONNECTED As with most file sharing clients, ncdc supports two modes of being connected: I and I. In passive mode (the default), you can connect to the outside world but nobody can connect (directly) to you. When passive, you will only be able to transfer files with people who are in active mode. In active mode, however, you will have some port open to the rest of the network to which other clients can connect. When active, you will be able to transfer files with everyone and you may get more and faster search results. Configuring active mode is therefore recommended. In many setups, all you need to do to switch to active mode is to set a TCP/UDP port and enable the C setting: /set active_port 34194 /set active true When you connect to a hub, the status bar will tell you whether you are active or passive on that particular hub, and what IP address is being used to allow others to connect to you. For most hubs, your IP address will be detected automatically, but in the event that this fails, you can also set it yourself: /set active_ip 13.33.33.7 If you are behind a NAT or firewall, you have to ensure that the port you configured is somehow allowed and/or forwarded. The C setting is used for incoming TCP connections and UDP messages. You can configure a different UDP port with the C setting. Contrary to many toher Direct Connect clients, ncdc only uses a single port for incoming TCP and TLS connections; There is no separate port for TLS. The C command can tell you which ports it expects to be forwarded, and for which hubs these ports will be used. It only lists hubs on which you are currently active, so the output will change when you open or close a hub connection. If you have multiple network interfaces, you can force ncdc to use only a single interface by setting the C setting to the address of that interface. This affects both outgoing connections (they will be forced to go through the configured interface) and incoming connections (the ports will be bound to the configured interface). All of the previously mentioned settings can be set globally (with C) and on a per-hub basis (with C). This allows you to be active on an internet hub and a LAN-only hub at the same time. It also allows you to be active in one hub while passive in another, or to use different ports for each hub. =head1 INTERACTIVE COMMANDS The following is the list of commands that can be used within ncdc. The /help command can also be used get a list of available commands and to access this documentation. @commands@ =head1 SETTINGS The following is a list of configuration settings. These settings can be changed and queried using the C command for global settings and C for hub-local settings. All configuration data is stored in the db.sqlite3 file in the session directory. @settings@ =head1 KEY BINDINGS On any tab without the text input line, you can press `?' to get the key bindings for that tab. The list of key bindings is available through the C command, and is reproduced below. @keys@ =head1 WEIRD UI FLAGS Some listings have a flags display, and their meaning may not be immediately obvious. =head2 File status You may see one-letter flags to the left of file names in search results and file list tabs. Their meaning is as follows: =over =item H The file had been added to the file list, but has not been hashed yet and thus is not visible to others. This flag can appear only when browsing your own list. =item S The file is already in your share. If C is set to C, trying to download it will result only in an informational message. This flag never appears when browsing your own file list. =item Q The file is currently in your download queue. Trying to download it will result only in an informational message. =back =head2 Connection flags On the connection list window, there are two flags: =over =item S Connection state, can be either Bonnecting, Bandshake, Bdle, Bownloading, Bploading or B<-> for disconnected. =item t This means that the connection is encrypted with TLS. =back =head2 User flags The user list has some flags too: =over =item o Set if the user is an operator in the hub. =item p When the user is passive. If this flag is not set, the user is active. =item t Set if connections with this user will be encrypted with TLS. =back =head1 ENVIRONMENT $NCDC_DIR is used to determine the session dir, it is only honoured if I<-c> is not set on the command line. =head1 FILES $NCDC_DIR corresponds to the session dir set via I<-c>, environment variable $NCDC_DIR or $HOME/.ncdc. =over =item $NCDC_DIR/cert/ Directory where the client certificates are stored. Must contain a private key file (client.key) and public certificate (client.crt). These will be generated automatically when ncdc starts up the first time. =item $NCDC_DIR/db.sqlite3 The database. This stores all configuration variables, hash data of shared files, download queue information and other state information. Manually editing this file with the `sqlite3' commandline tool is possible but discouraged. Any changes made to the database while ncdc is running will not be read, and may even get overwritten by ncdc. =item $NCDC_DIR/dl/ Directory where completed downloads are moved to by default. Can be changed with the C configuration option. =item $NCDC_DIR/files.xml.bz2 Filelist containing a listing of all shared files. =item $NCDC_DIR/fl/ Directory where downloaded file lists from other users are stored. The names of the files are hex-encoded user IDs that are used internally by ncdc. Old file lists are deleted automatically after a configurable interval. See the C configuration option. =item $NCDC_DIR/history Command history. =item $NCDC_DIR/inc/ Default location for incomplete downloads. Can be changed with the C setting. The file names in this directory are the base32-encoded TTH root of the completed file. =item $NCDC_DIR/logs/ Directory where all the log files are stored. File names starting with `#' are hub logs and `~' are user (PM) logs. Special log files are transfers.log and main.log. ncdc does not have built-in functionality to rotate or compress log files automatically. When rotating log files manually (e.g. via a cron job), make sure to send the SIGUSR1 signal afterwards to force ncdc to flush the old logs and create or open the new log files. =item $NCDC_DIR/stderr.log Error/debug log. This file is cleared every time ncdc starts up. =item $NCDC_DIR/version Version of the data directory. This file locked while an ncdc instance is running, making sure that no two ncdc instances work with the same session directory at the same time. =back =head2 Format of transfers.log Uploads and downloads are logged in the transfers.log file. Transfers are separated by a newline (C<0x0a>). Each log line has the following fields, separated by a space: =over =item 1. Date/time when the transfer ended, formatted as C<[YYYY-MM-DD HH:MM:SS ZONE]>, =item 2. Hub name, including the C<#> prefix, =item 3. Base32-encoded CID of the other user for ADC transfers, or a '-' for NMDC, =item 4. User name (escaped), =item 5. IPv4 or IPv6 address, =item 6. Direction, C for upload or C for download, =item 7. Whether the transfer completed successfully (C) or has been interrupted/disconnected before all requested file data has been transferred (C), =item 8. Base32-encoded TTH of the transferred file, or '-' for C, =item 9. Total transfer time, in seconds, =item 10. File size, in bytes, =item 11. File offset, in bytes, =item 12. Transfer size, in bytes, =item 13. File path (escaped). Absolute virtual path for uploads, destination path for downloads. =back All fields are encoded in UTF-8. Fields that may contain a space or newline are escaped as follows: A space is escaped as C<\s>, a newline as C<\n> and a backslash as C<\\>. The timestamp is not escaped. Many clients download files is separate (smallish) chunks. Ncdc makes no attempt to combine multiple chunk requests in a single log entry, so you may see the same uploaded file several times with a different file offset. =head1 LICENSE Copyright (C) 2011-2019 Yoran Heling ncdc is distributed under the MIT license, please read the COPYING file for more information. =head1 BUGS Please report bugs or feature requests to the bug tracker or the mailing list. Both can be found on the ncdc homepage at L. There is also an ADC hub available at C for general support and discussions. =head1 AUTHOR ncdc is written by Yoran Heling Web: L ncdc-1.23.1/src/0000755000175000017500000000000014314550104010272 500000000000000ncdc-1.23.1/src/uit_main.c0000644000175000017500000000526614245144605012204 00000000000000/* ncdc - NCurses Direct Connect client Copyright (c) 2011-2022 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "ncdc.h" #include "uit_main.h" ui_tab_type_t uit_main[1]; // There is only a single main tab, and it is always open. ui_tab_t *uit_main_tab; ui_tab_t *uit_main_create() { g_return_val_if_fail(!uit_main_tab, NULL); ui_tab_t *tab = uit_main_tab = g_new0(ui_tab_t, 1); tab->name = "main"; tab->log = ui_logwindow_create("main", 0); tab->type = uit_main; ui_mf(tab, 0, "Welcome to ncdc %s!", main_version); ui_m(tab, 0, "Check out the manual page for a general introduction to ncdc.\n" "Make sure you always run the latest version available from https://dev.yorhel.nl/ncdc\n"); ui_mf(tab, 0, "Using working directory: %s", db_dir); return tab; } static void t_close(ui_tab_t *tab) { ui_m(NULL, UIM_NOLOG, "Type /quit to exit ncdc."); } static void t_draw(ui_tab_t *t) { ui_logwindow_draw(t->log, 1, 0, winrows-4, wincols); mvaddstr(winrows-3, 0, "main>"); ui_textinput_draw(ui_global_textinput, winrows-3, 6, wincols-6, NULL); } static char *t_title(ui_tab_t *t) { return g_strdup_printf("Welcome to ncdc %s!", main_version); } static void t_key(ui_tab_t *t, guint64 key) { char *str = NULL; if(!ui_logwindow_key(t->log, key, winrows) && ui_textinput_key(ui_global_textinput, key, &str) && str) { cmd_handle(str); g_free(str); } } // Select the main tab and run `/help keys '. void uit_main_keys(const char *s) { ui_tab_cur = g_list_find(ui_tabs, uit_main_tab); char *c = g_strdup_printf("/help keys %s", s); cmd_handle(c); g_free(c); } ui_tab_type_t uit_main[1] = { { t_draw, t_title, t_key, t_close } }; ncdc-1.23.1/src/ui_logwindow.c0000644000175000017500000002763014245144566013112 00000000000000/* ncdc - NCurses Direct Connect client Copyright (c) 2011-2022 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "ncdc.h" #include "ui_logwindow.h" /* Log format of some special messages: * Chat message: " msg" * Chat /me: "** nick msg" * User joined "--> nick has joined." (may be internationalized, * User quit "--< nick has quit." don't depend on the actual message) * Anything else is a system / help message. */ #if INTERFACE #define LOGWIN_BUF 1023 // must be 2^x-1 struct ui_logwindow_t { int lastlog; int lastvis; logfile_t *logfile; char *buf[LOGWIN_BUF+1]; gboolean updated; int (*checkchat)(void *, char *, char *); void *handle; }; #endif void ui_logwindow_addline(ui_logwindow_t *lw, const char *msg, gboolean raw, gboolean nolog) { if(lw->lastlog == lw->lastvis) lw->lastvis = lw->lastlog + 1; lw->lastlog++; lw->updated = TRUE; /* Replace \t with four spaces, because the log drawing code can't handle tabs. */ GString *msgbuf = NULL; const char *msgl = msg; if(strchr(msg, '\t')) { msgbuf = g_string_sized_new(strlen(msg)+20); for(; *msgl; msgl++) { if(*msgl == '\t') g_string_append(msgbuf, " "); else g_string_append_c(msgbuf, *msgl); } msgl = msgbuf->str; } char *ts = localtime_fmt("%H:%M:%S "); lw->buf[lw->lastlog & LOGWIN_BUF] = raw ? g_strdup(msgl) : g_strconcat(ts, msgl, NULL); g_free(ts); if(msgbuf) g_string_free(msgbuf, TRUE); if(!nolog && lw->logfile) logfile_add(lw->logfile, msg); int next = (lw->lastlog + 1) & LOGWIN_BUF; if(lw->buf[next]) { g_free(lw->buf[next]); lw->buf[next] = NULL; } } static void ui_logwindow_load(ui_logwindow_t *lw, const char *fn, int num) { char **l = file_tail(fn, num); if(!l) { g_warning("Unable to tail log file '%s': %s", fn, g_strerror(errno)); return; } int i, len = g_strv_length(l); char *m; for(i=0; i char *msg = strchr(l[i], ']'); char *time = strchr(l[i], ' '); char *tmp = time ? strchr(time+1, ' ') : NULL; if(l[i][0] != '[' || !msg || !time || !tmp || tmp < time || msg[1] != ' ') continue; time++; *msg = 0; msg += 2; // if this is the first line, display a notice if(!i) { m = g_strdup_printf("-- Backlog starts on %s.", l[i]+1); ui_logwindow_addline(lw, m, FALSE, TRUE); g_free(m); } // display the line *tmp = 0; m = g_strdup_printf("%s %s", time, msg); ui_logwindow_addline(lw, m, TRUE, TRUE); g_free(m); *tmp = ' '; // if this is the last line, display another notice if(i == len-1) { m = g_strdup_printf("-- Backlog ends on %s", l[i]+1); ui_logwindow_addline(lw, m, FALSE, TRUE); g_free(m); ui_logwindow_addline(lw, "", FALSE, TRUE); } } g_strfreev(l); } ui_logwindow_t *ui_logwindow_create(const char *file, int load) { ui_logwindow_t *lw = g_new0(ui_logwindow_t, 1); if(file) { lw->logfile = logfile_create(file); if(load) ui_logwindow_load(lw, lw->logfile->path, load); } return lw; } void ui_logwindow_free(ui_logwindow_t *lw) { logfile_free(lw->logfile); ui_logwindow_clear(lw); g_free(lw); } void ui_logwindow_add(ui_logwindow_t *lw, const char *msg) { if(!msg[0]) { ui_logwindow_addline(lw, "", FALSE, FALSE); return; } // For chat messages and /me's, prefix every line with "" or "** nick" char *prefix = NULL; char *tmp; if( (*msg == '<' && (tmp = strchr(msg, '>')) != NULL && *(++tmp) == ' ') || // (*msg == '*' && msg[1] == '*' && msg[2] == ' ' && (tmp = strchr(msg+3, ' ')) != NULL)) // ** nick prefix = g_strndup(msg, tmp-msg+1); // Split \r?\n? stuff into separate log lines gboolean first = TRUE; while(1) { int len = strcspn(msg, "\r\n"); tmp = !prefix || first ? g_strndup(msg, len) : g_strdup_printf("%s%.*s", prefix, len, msg); ui_logwindow_addline(lw, tmp, FALSE, FALSE); g_free(tmp); msg += len; if(!*msg) break; msg += *msg == '\r' && msg[1] == '\n' ? 2 : 1; first = FALSE; } g_free(prefix); } void ui_logwindow_clear(ui_logwindow_t *lw) { int i; for(i=0; i<=LOGWIN_BUF; i++) { g_free(lw->buf[i]); lw->buf[i] = NULL; } lw->lastlog = lw->lastvis = 0; } void ui_logwindow_scroll(ui_logwindow_t *lw, int i) { lw->lastvis += i; // lastvis may never be larger than the last entry present lw->lastvis = MIN(lw->lastvis, lw->lastlog); // lastvis may never be smaller than the last entry still in the log lw->lastvis = MAX(lw->lastlog - LOGWIN_BUF+1, lw->lastvis); // lastvis may never be smaller than one lw->lastvis = MAX(1, lw->lastvis); } // Calculate the wrapping points in a line. Storing the mask in *rows, the row // where the indent is reset in *ind_row, and returning the number of rows. static int ui_logwindow_calc_wrap(char *str, int cols, int indent, int *rows, int *ind_row) { rows[0] = rows[1] = 0; *ind_row = 0; int cur = 1, curcols = 0, i = 0; // Appends an entity that will not be wrapped (i.e. a single character or a // word that isn't too long). Does a 'break' if there are too many lines. #define append(w, b, ind) \ int t_w = w;\ if(curcols+t_w > cols) {\ if(++cur >= 200)\ break;\ if(ind && !*ind_row) {\ *ind_row = cur-1;\ indent = 0;\ }\ curcols = indent;\ }\ if(!(cur > 1 && j == i && curcols == indent))\ curcols += t_w;\ i += b;\ rows[cur] = i; while(str[i] && cur < 200) { // Determine the width of the current word int j = i; int width = 0; for(; str[j] && str[j] != ' '; j = g_utf8_next_char(str+j)-str) width += gunichar_width(g_utf8_get_char(str+j)); // Special-case the space if(j == i) { append(1,1, FALSE); // If the word still fits on the current line or is smaller than cols*3/4 // and cols-indent, then consider it as a single entity } else if(curcols+width <= cols || width < MIN(cols*3/4, cols-indent)) { append(width, j-i, FALSE); // Otherwise, wrap on character-boundary and ignore indent } else { char *tmp = str+i; for(; *tmp && *tmp != ' '; tmp = g_utf8_next_char(tmp)) { append(gunichar_width(g_utf8_get_char(tmp)), g_utf8_next_char(tmp)-tmp, TRUE); } } } #undef append if(!*ind_row) *ind_row = cur; return cur-1; } // Determines the colors each part of a log line should have. Returns the // highest index to the attr array. static int ui_logwindow_calc_color(ui_logwindow_t *lw, char *str, int *sep, int *attr) { sep[0] = 0; int mask = 0; // add a mask #define addm(from, to, a)\ int t_f = from;\ if(sep[mask] != t_f) {\ sep[mask+1] = t_f;\ attr[mask] = UIC(log_default);\ mask++;\ }\ sep[mask] = t_f;\ sep[mask+1] = to;\ attr[mask] = a;\ mask++; // time char *msg = strchr(str, ' '); if(msg && msg-str != 8) // Make sure it's not "Day changed to ..", which doesn't have the time prefix msg = NULL; if(msg) { addm(0, msg-str, UIC(log_time)); msg++; } // chat messages ( and ** nick) char *tmp; if(msg && ( (msg[0] == '<' && (tmp = strchr(msg, '>')) != NULL && tmp[1] == ' ') || // (msg[0] == '*' && msg[1] == '*' && msg[2] == ' ' && (tmp = strchr(msg+3, ' ')) != NULL))) { // ** nick int nickstart = (msg-str) + (msg[0] == '<' ? 1 : 3); int nickend = tmp-str; // check for a highlight or whether it is our own nick char old = tmp[0]; tmp[0] = 0; int r = lw->checkchat ? lw->checkchat(lw->handle, str+nickstart, str+nickend+1) : 0; tmp[0] = old; // and use the correct color addm(nickstart, nickend, r == 2 ? UIC(log_ownnick) : r == 1 ? UIC(log_highlight) : UIC(log_nick)); } // join/quits (--> and --<) if(msg && msg[0] == '-' && msg[1] == '-' && (msg[2] == '>' || msg[2] == '<')) { addm(msg-str, strlen(str), msg[2] == '>' ? UIC(log_join) : UIC(log_quit)); } #undef addm // make sure the last mask is correct and return if(sep[mask+1] != strlen(str)) { sep[mask+1] = strlen(str); attr[mask] = UIC(log_default); } return mask; } // Draws a line between x and x+cols on row y (continuing on y-1 .. y-(rows+1) for // multiple rows). Returns the actual number of rows written to. static int ui_logwindow_drawline(ui_logwindow_t *lw, int y, int x, int nrows, int cols, char *str) { g_return_val_if_fail(nrows > 0, 1); // Determine the indentation for multi-line rows. This is: // - Always after the time part (hh:mm:ss ) // - For chat messages: after the nick ( ) // - For /me's: after the (** ) int indent = 0; char *tmp = strchr(str, ' '); if(tmp) indent = tmp-str+1; if(tmp && tmp[1] == '<' && (tmp = strchr(tmp, '>')) != NULL) indent = tmp-str+2; else if(tmp && tmp[1] == '*' && tmp[2] == '*') indent += 3; // Convert indent from bytes to columns if(indent && indent <= strlen(str)) { int i = indent; char old = str[i]; str[i] = 0; indent = str_columns(str); str[i] = old; } // Determine the wrapping boundaries. // Defines a mask over the string: <#0,#1), <#1,#2), .. static int rows[201]; int ind_row; int rmask = ui_logwindow_calc_wrap(str, cols, indent, rows, &ind_row); // Determine the colors to give each part static int colors_sep[10]; // Mask, similar to the rows array static int colors[10]; // Color attribute for each mask int cmask = ui_logwindow_calc_color(lw, str, colors_sep, colors); // print the rows int r = 0, c = 0, lr = 0; if(rmask-r < nrows) move(y - rmask + r, r == 0 || r >= ind_row ? x : x+indent); while(r <= rmask && c <= cmask) { int rend = rows[r+1]; int cend = colors_sep[c+1]; int rstart = rows[r]; int cstart = colors_sep[c]; int start = MAX(rstart, cstart); int end = MIN(cend, rend); // Ignore spaces at the start of a new line while(r > 0 && lr != r && start < end && str[start] == ' ') start++; if(start < end) lr = r; if(start != end && rmask-r < nrows) { attron(colors[c]); addnstr(str+start, end-start); attroff(colors[c]); } if(rend <= cend) { r++; if(rmask-r < nrows) move(y - rmask + r, r == 0 || r >= ind_row ? x : x+indent); } if(rend >= cend) c++; } return rmask+1; } void ui_logwindow_draw(ui_logwindow_t *lw, int y, int x, int rows, int cols) { int top = rows + y - 1; int cur = lw->lastvis; lw->updated = FALSE; while(top >= y) { char *str = lw->buf[cur & LOGWIN_BUF]; if(!str) break; top -= ui_logwindow_drawline(lw, top, x, top-y+1, cols, str); cur = (cur-1) & LOGWIN_BUF; } } gboolean ui_logwindow_key(ui_logwindow_t *lw, guint64 key, int rows) { switch(key) { case INPT_KEY(KEY_NPAGE): ui_logwindow_scroll(lw, rows/2); return TRUE; case INPT_KEY(KEY_PPAGE): ui_logwindow_scroll(lw, -rows/2); return TRUE; } return FALSE; } ncdc-1.23.1/src/search.c0000644000175000017500000003674014245144534011646 00000000000000/* ncdc - NCurses Direct Connect client Copyright (c) 2011-2022 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "ncdc.h" #include "search.h" #if INTERFACE // Callback function, to be called when a search result has been received on an // active search_q. typedef void (*search_cb)(search_r_t *r, void *dat); struct search_q_t { char type; // NMDC search type (if 9, ignore all fields except tth) gboolean ge; // TRUE -> match >= size; FALSE -> match <= size guint64 size; // 0 = disabled. char **query; // list of patterns to include char tth[24]; // only used when type = 9 char key[16]; // SUDP key that we sent along with the SCH search_cb cb; void *cb_dat; }; // Represents a search result, coming from either NMDC $SR or ADC RES. struct search_r_t { guint64 uid; char *file; // full path + filename. Slashes as path saparator, no trailing slash guint64 size; // file size, G_MAXUINT64 = directory int slots; // free slots char tth[24]; // TTH root (for regular files) } struct search_type_t { char *name; char *exts[25]; }; #endif // A set of search_q pointers, listing the searches we're currently interested in. static GHashTable *search_list = NULL; // NMDC search types and the relevant ADC SEGA extensions. search_type_t search_types[] = { {}, { "any" }, // 1 { "audio", { "ape", "flac", "m4a", "mid", "mp3", "mpc", "ogg", "ra", "wav", "wma" } }, { "archive", { "7z", "ace", "arj", "bz2", "gz", "lha", "lzh", "rar", "tar", "tz", "z", "zip" } }, { "doc", { "doc", "docx", "htm", "html", "nfo", "odf", "odp", "ods", "odt", "pdf", "ppt", "pptx", "rtf", "txt", "xls", "xlsx", "xml", "xps" } }, { "exe", { "app", "bat", "cmd", "com", "dll", "exe", "jar", "msi", "ps1", "vbs", "wsf" } }, { "img", { "bmp", "cdr", "eps", "gif", "ico", "img", "jpeg", "jpg", "png", "ps", "psd", "sfw", "tga", "tif", "webp" } }, { "video", { "3gp", "asf", "asx", "avi", "divx", "flv", "mkv", "mov", "mp4", "mpeg", "mpg", "ogm", "pxp", "qt", "rm", "rmvb", "swf", "vob", "webm", "wmv" } }, { "dir" }, // 8 {} // 9 }; void search_q_free(search_q_t *q) { if(!q) return; if(q->query) g_strfreev(q->query); g_slice_free(search_q_t, q); } // Convenience function to create a search_q for a TTH search. search_q_t * search_q_new_tth(const char *tth) { search_q_t *q = g_slice_new0(search_q_t); memcpy(q->tth, tth, 24); q->type = 9; return q; } // Can be used as a GDestroyNotify callback void search_r_free(gpointer data) { search_r_t *r = data; if(!r) return; g_free(r->file); g_slice_free(search_r_t, r); } search_r_t *search_r_copy(search_r_t *r) { search_r_t *res = g_slice_dup(search_r_t, r); res->file = g_strdup(r->file); return res; } // Generate the required /search command for a query. char *search_command(search_q_t *q, gboolean onhub) { GString *str = g_string_new("/search"); g_string_append(str, onhub ? " -hub" : " -all"); if(q->type == 9) { char tth[40] = {}; base32_encode(q->tth, tth); g_string_append(str, " -tth "); g_string_append(str, tth); } if(q->type != 9) { g_string_append(str, " -t "); g_string_append(str, search_types[(int)q->type].name); } if(q->type != 9 && q->size) // TODO: convert back to K/M/G suffix when possible? g_string_append_printf(str, " -%s %"G_GUINT64_FORMAT, q->ge ? "ge" : "le", q->size); char **query = q->type == 9 ? NULL : q->query; char **tmp = query; for(; tmp&&*tmp; tmp++) if(**tmp == '-') break; if(tmp&&*tmp) g_string_append(str, " --"); for(tmp=query; tmp&&*tmp; tmp++) { g_string_append_c(str, ' '); if(strcspn(*tmp, " \\'\"") != strlen(*tmp)) { char *s = g_shell_quote(*tmp); g_string_append(str, s); g_free(s); } else g_string_append(str, *tmp); } return g_string_free(str, FALSE); } // Performs the search query on the given hub, or on all hubs if hub=NULL. // Returns FALSE on error and sets *err. *err may also be set when TRUE is // returned and there's a non-fatal warning. // Ownership of the search_q struct is passed to search.c. If this function // returns an error, it will be freed, otherwise it is added to the active // search list and must be freed/removed with search_remove() when there's no // interest in results anymore. // q->cb() will be called for each result that arrives, until search_remove(). gboolean search_add(hub_t *hub, search_q_t *q, GError **err) { if((!q->query || !*q->query) && q->type != 9) { g_set_error(err, 1, 0, "No search query given."); search_q_free(q); return FALSE; } if(var_get_int(0, VAR_sudp_policy) == VAR_SUDPP_PREFER) g_warn_if_fail(gnutls_rnd(GNUTLS_RND_NONCE, q->key, 16) == 0); // Search a single hub if(hub) { if(!hub->nick_valid) { g_set_error(err, 1, 0, "Not connected"); search_q_free(q); return FALSE; } if(var_get_bool(hub->id, VAR_chat_only)) g_set_error(err, 1, 0, "Searching on a hub with the `chat_only' setting enabled."); hub_search(hub, q); } // Search all hubs (excluding those with chat_only set) else { gboolean one = FALSE; GHashTableIter i; hub_t *h = NULL; g_hash_table_iter_init(&i, hubs); while(g_hash_table_iter_next(&i, NULL, (gpointer *)&h)) { if(h->nick_valid && !var_get_bool(h->id, VAR_chat_only)) { hub_search(h, q); one = TRUE; } } if(!one) { g_set_error(err, 1, 0, "Not connected to any non-chat hubs."); search_q_free(q); return FALSE; } } // Add to the active searches list if(!search_list) search_list = g_hash_table_new(g_direct_hash, g_direct_equal); g_hash_table_insert(search_list, q, q); return TRUE; } // Remove a query from the active searches. void search_remove(search_q_t *q) { if(search_list && g_hash_table_remove(search_list, q)) search_q_free(q); } // Match a search result with a query. static gboolean match(search_q_t *q, search_r_t *r) { // TTH match is fast and easy if(q->type == 9) return r->size == G_MAXUINT64 ? FALSE : memcmp(q->tth, r->tth, 24) == 0 ? TRUE : FALSE; // Match file/dir type if(q->type == 8 && r->size != G_MAXUINT64) return FALSE; if((q->size || (q->type >= 2 && q->type <= 7)) && r->size == G_MAXUINT64) return FALSE; // Match size if(q->size && !(q->ge ? r->size >= q->size : r->size <= q->size)) return FALSE; // Match query char **str = q->query; for(; str&&*str; str++) if(G_LIKELY(!str_casestr(r->file, *str))) return FALSE; // Match extension char **ext = search_types[(int)q->type].exts; if(ext && *ext) { char *l = strrchr(r->file, '.'); if(G_UNLIKELY(!l || !l[1])) return FALSE; l++; for(; *ext; ext++) if(G_UNLIKELY(g_ascii_strcasecmp(l, *ext) == 0)) break; if(!*ext) return FALSE; } // Okay, we have a match return TRUE; } // Match the search result against any active searches and runs the q->cb // callbacks. static void dispatch(search_r_t *r) { if(!search_list) return; GHashTableIter i; g_hash_table_iter_init(&i, search_list); search_q_t *q; while(g_hash_table_iter_next(&i, (gpointer *)&q, NULL)) if(q->cb && match(q, r)) q->cb(r, q->cb_dat); } // Modifies msg in-place for temporary stuff. static search_r_t *parse_nmdc(hub_t *hub, char *msg) { search_r_t r = {}; char *tmp, *tmp2; gboolean hastth = FALSE; // forward search to get the username and offset to the filename if(strncmp(msg, "$SR ", 4) != 0) return NULL; msg += 4; char *user = msg; msg = strchr(msg, ' '); if(!msg) return NULL; *(msg++) = 0; r.file = msg; // msg is now searched backwards, because we can't reliably determine the end // of the filename otherwise. // (hub_ip:hub_port). tmp = strrchr(msg, ' '); if(!tmp) return NULL; *(tmp++) = 0; if(*(tmp++) != '(') return NULL; char *hubaddr = tmp; tmp = strchr(tmp, ')'); if(!tmp) return NULL; *tmp = 0; // <0x05>TTH:stuff tmp = strrchr(msg, 5); if(!tmp) return NULL; *(tmp++) = 0; if(strncmp(tmp, "TTH:", 4) == 0) { if(!istth(tmp+4)) return NULL; base32_decode(tmp+4, r.tth); hastth = TRUE; } // free_slots/total_slots. We only care about the free slots. tmp = strrchr(msg, ' '); if(!tmp) return NULL; *(tmp++) = 0; r.slots = g_ascii_strtoull(tmp, &tmp2, 10); if(tmp == tmp2 || !tmp2 || *tmp2 != '/') return NULL; // At this point, msg contains either "filename<0x05>size" in the case of a // file or "path" in the case of a directory. tmp = strrchr(msg, 5); if(tmp) { // files must have a TTH if(!hastth) return NULL; *(tmp++) = 0; r.size = g_ascii_strtoull(tmp, &tmp2, 10); if(tmp == tmp2 || !tmp2 || *tmp2) return NULL; } else r.size = G_MAXUINT64; // \ -> /, and remove trailing slashes for(tmp = r.file; *tmp; tmp++) if(*tmp == '\\') *tmp = '/'; while(--tmp > r.file && *tmp == '/') *tmp = 0; // For active search results: figure out the hub // TODO: Use the hub list associated with the incoming port of listen.c? if(!hub) { tmp = strchr(hubaddr, ':') ? g_strdup(hubaddr) : g_strdup_printf("%s:411", hubaddr); int colon = strchr(tmp, ':') - tmp; GHashTableIter i; hub_t *h = NULL; g_hash_table_iter_init(&i, hubs); while(g_hash_table_iter_next(&i, NULL, (gpointer *)&h)) { if(!h->nick_valid || h->adc) continue; // Excact hub:ip match, stop searching if(strcmp(tmp, net_remoteaddr(h->net)) == 0) { hub = h; break; } // Otherwise, try a fuzzy search (ignoring the port) tmp[colon] = 0; if(strncmp(tmp, net_remoteaddr(h->net), colon) == 0) hub = h; tmp[colon] = ':'; } g_free(tmp); if(!hub) return NULL; } // Figure out r.uid hub_user_t *u = g_hash_table_lookup(hub->users, user); if(!u) return NULL; r.uid = u->uid; // If we're here, then we can safely copy and return the result. search_r_t *res = g_slice_dup(search_r_t, &r); res->file = nmdc_unescape_and_decode(hub, r.file); return res; } static search_r_t *parse_adc(hub_t *hub, adc_cmd_t *cmd) { search_r_t r = {}; char *tmp, *tmp2; // If this came from UDP, fetch the users' CID if(!hub && (cmd->type != 'U' || cmd->argc < 1 || !iscid(cmd->argv[0]))) return NULL; char *cid = hub ? NULL : cmd->argv[0]; char **argv = hub ? cmd->argv : cmd->argv+1; // file r.file = adc_getparam(argv, "FN", NULL); if(!r.file) return NULL; gboolean isfile = TRUE; while(strlen(r.file) > 1 && r.file[strlen(r.file)-1] == '/') { r.file[strlen(r.file)-1] = 0; isfile = FALSE; } // tth & size tmp = isfile ? adc_getparam(argv, "TR", NULL) : NULL; if(tmp) { if(!istth(tmp)) return NULL; base32_decode(tmp, r.tth); tmp = adc_getparam(argv, "SI", NULL); if(!tmp) return NULL; r.size = g_ascii_strtoull(tmp, &tmp2, 10); if(tmp == tmp2 || !tmp2 || *tmp2) return NULL; } else r.size = G_MAXUINT64; // slots tmp = adc_getparam(argv, "SL", NULL); if(tmp) { r.slots = g_ascii_strtoull(tmp, &tmp2, 10); if(tmp == tmp2 || !tmp2 || *tmp2) return NULL; } // uid - passive if(hub) { hub_user_t *u = g_hash_table_lookup(hub->sessions, GINT_TO_POINTER(cmd->source)); if(!u) return NULL; r.uid = u->uid; // uid - active. Active responses must have the hubid in the token, from // which we can generate the uid. } else { tmp = adc_getparam(argv, "TO", NULL); if(!tmp || strlen(tmp) != 13 || !isbase32(tmp)) return NULL; guint64 hubid; base32_decode(tmp, (char *)&hubid); r.uid = hub_user_adc_id(hubid, cid); } // If we're here, then we can safely copy and return the result. return search_r_copy(&r); } gboolean search_handle_adc(hub_t *hub, adc_cmd_t *cmd) { search_r_t *r = parse_adc(hub, cmd); if(!r) return FALSE; dispatch(r); search_r_free(r); return TRUE; } // May modify *msg in-place. gboolean search_handle_nmdc(hub_t *hub, char *msg) { search_r_t *r = parse_nmdc(hub, msg); if(!r) return FALSE; dispatch(r); search_r_free(r); return TRUE; } // length(out) >= inlen. static char *try_decrypt(const char *key, const char *in, int inlen, char *out) { if(inlen < 32 || inlen & 15) return NULL; // Decrypt memcpy(out, in, inlen); crypt_aes128cbc(FALSE, key, 16, out, inlen); // Validate padding and replace with 0-bytes. int padlen = out[inlen-1]; if(padlen < 1 || padlen > 16) return NULL; int r; for(r=0; rkey, pack, len, buf); if(new && (strncmp(new, "$SR ", 4) == 0 || strncmp(new, "URES ", 5) == 0)) { g_free(pack); pack = buf; msg = new; sudp = TRUE; adc = msg[0] == 'U'; } } if(!sudp) { g_free(pack); g_free(buf); return FALSE; } } else { g_free(pack); return FALSE; } // handle message char *next; while((next = strchr(msg, adc ? '\n' : '|')) != NULL) { *(next++) = 0; g_debug("%s:%s< %s", sudp ? "SUDP" : "UDP", addr, msg); if(adc) { adc_cmd_t cmd; if(!adc_parse(msg, &cmd, NULL, NULL)) { g_free(pack); return FALSE; } gboolean r = search_handle_adc(NULL, &cmd); g_strfreev(cmd.argv); if(!r) { g_free(pack); return FALSE; } } else if(!search_handle_nmdc(NULL, msg)) { g_free(pack); return FALSE; } msg = next; } g_free(pack); return TRUE; } ncdc-1.23.1/src/dlfile.c0000644000175000017500000005240314245144471011632 00000000000000/* ncdc - NCurses Direct Connect client Copyright (c) 2011-2022 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "ncdc.h" #include "dlfile.h" /* Terminology * * chunk: Smallest "addressable" byte range within a file, see DLFILE_CHUNKSIZE * block: Smallest "verifiable" byte range within a file (i.e. what a TTH * leaf represents, see DL_MINBLOCKSIZE). Always a multiple of the * chunk size. * thread: A range of chunks that haven't been downloaded yet. * segment: A range of chunks that is requested for downloading in a single * CGET/$ADCGET. Not necessarily aligned to or a multiple of the block * size. Segments are allocated at the start of a thread. */ #if INTERFACE /* Size of a chunk within the downloaded file. This determines the granularity * of the file data that is remembered across restarts, the size of the chunk * bitmap and the minimum download request. * Must be a power of two and less than or equal to DL_MINBLOCKSIZE */ #define DLFILE_CHUNKSIZE (128*1024) /* For file lists (dl->islist), only len and chunk are used. The other fields * aren't used because no length of TTH info is known before downloading. */ struct dlfile_thread_t { dl_t *dl; tth_ctx_t hash_tth; guint32 allocated; /* Number of remaining chunks allocated to this thread (including current) */ guint32 avail; /* Number of undownloaded chunks in and after this thread (including current & allocated) */ guint32 chunk; /* Current chunk number */ guint32 len; /* Number of bytes downloaded into this chunk */ gboolean busy; /* Whether this thread is being used */ /* Fields for deferred error reporting */ guint64 uid; char *err_msg, *uerr_msg; char err, uerr; }; #endif static guint32 dlfile_chunks(guint64 size) { return (size+DLFILE_CHUNKSIZE-1)/DLFILE_CHUNKSIZE; } static gboolean dlfile_hasfreeblock(dlfile_thread_t *t) { guint32 chunksinblock = t->dl->hash_block / DLFILE_CHUNKSIZE; return t->avail - t->allocated > chunksinblock || (t->chunk + t->avail == dlfile_chunks(t->dl->size) && t->chunk + t->allocated <= (((dlfile_chunks(t->dl->size)-1)/chunksinblock)*chunksinblock)); } /* Highly verbose debugging function. Prints out a list of threads for a particular dl item. */ static void dlfile_threaddump(dl_t *dl, int n) { #if 0 GSList *l; for(l=dl->threads; l; l=l->next) { dlfile_thread_t *ti = l->data; g_debug("THREAD DUMP#%p.%d: busy = %d, chunk = %u, allocated = %u, avail = %u", dl, n, ti->busy, ti->chunk, ti->allocated, ti->avail); } #endif } static void dlfile_fatal_load_error(dl_t *dl, const char *op, const char *err) { g_error("Unable to %s incoming file `%s', (%s; incoming file for `%s').\n" "Delete the incoming file or otherwise repair it before restarting ncdc.", op, dl->inc, err ? err : g_strerror(errno), dl->dest); } /* Must be called while the lock is held when dl->active_threads may be >0 */ static gboolean dlfile_save_bitmap(dl_t *dl, int fd) { guint8 *buf = dl->bitmap; off_t off = dl->size; size_t left = bita_size(dlfile_chunks(dl->size)); while(left > 0) { int r = pwrite(fd, buf, left, off); if(r < 0) return FALSE; left -= r; off += r; buf += r; } return TRUE; } static gboolean dlfile_save_bitmap_timeout(gpointer dat) { dl_t *dl = dat; g_mutex_lock(&dl->lock); dl->bitmap_src = 0; if(dl->incfd > 0 && !dlfile_save_bitmap(dl, dl->incfd)) { g_warning("Error writing bitmap for `%s': %s.", dl->dest, g_strerror(errno)); dl_queue_seterr(dl, DLE_IO_INC, g_strerror(errno)); } if(dl->incfd > 0 && !dl->active_threads) { close(dl->incfd); dl->incfd = 0; } g_mutex_unlock(&dl->lock); return FALSE; } /* Must be called while dl->lock is held. */ static void dlfile_save_bitmap_defer(dl_t *dl) { if(!dl->bitmap_src) dl->bitmap_src = g_timeout_add_seconds(5, dlfile_save_bitmap_timeout, dl); } static void dlfile_load_canconvert(dl_t *dl) { static gboolean canconvert = FALSE; if(canconvert) return; printf( "I found a partially downloaded file without a bitmap. This probably\n" "means that you are upgrading from ncdc 1.17 or earlier, which did not\n" "yet support segmented downloading.\n\n" "To convert your partially downloaded files to the new format and to\n" "continue with starting up ncdc, press enter. To abort, hit Ctrl+C.\n\n" "Note: After this conversion, you should NOT downgrade ncdc. If you\n" " wish to do that, backup or delete your inc/ directory first.\n\n" "Note#2: If you get this message when you haven't upgraded ncdc, then\n" " your partially downloaded file is likely corrupt, and continuing\n" " this conversion will not help. In that case, the best you can do is\n" " delete the corrupted file and restart ncdc.\n\n" "The file that triggered this warning is:\n" " %s\n" "Which is the incoming file for:\n" " %s\n" "(But there are possibly more affected files)\n", dl->inc, dl->dest); getchar(); canconvert = TRUE; } static void dlfile_load_nonbitmap(dl_t *dl, int fd, guint8 *bitmap) { struct stat st; if(fstat(fd, &st) < 0) dlfile_fatal_load_error(dl, "stat", NULL); if((guint64)st.st_size >= dl->size) dlfile_fatal_load_error(dl, "load", "File too large"); dlfile_load_canconvert(dl); guint64 left = st.st_size; guint32 chunk = 0; while(left > DLFILE_CHUNKSIZE) { bita_set(bitmap, chunk); chunk++; left -= DLFILE_CHUNKSIZE; } } static gboolean dlfile_load_bitmap(dl_t *dl, int fd) { gboolean needsave = FALSE; guint32 chunks = dlfile_chunks(dl->size); guint8 *bitmap = bita_new(chunks); guint8 *dest = bitmap; off_t off = dl->size; size_t left = bita_size(chunks); while(left > 0) { int r = pread(fd, dest, left, off); if(r < 0) dlfile_fatal_load_error(dl, "read bitmap from", NULL); if(!r) { dlfile_load_nonbitmap(dl, fd, bitmap); needsave = TRUE; break; } left -= r; off += r; dest += r; } bita_free(dl->bitmap); dl->bitmap = bitmap; return needsave; } static dlfile_thread_t *dlfile_load_block(dl_t *dl, int fd, guint32 chunk, guint32 chunksinblock, guint32 *reset) { dlfile_thread_t *t = g_slice_new0(dlfile_thread_t); t->dl = dl; t->chunk = chunk; t->avail = chunksinblock; tth_init(&t->hash_tth); char *bufp = malloc(DLFILE_CHUNKSIZE); *reset = chunksinblock; while(bita_get(dl->bitmap, t->chunk)) { char *buf = bufp; off_t off = (guint64)t->chunk * DLFILE_CHUNKSIZE; size_t left = DLFILE_CHUNKSIZE; while(left > 0) { int r = pread(fd, buf, left, off); if(r <= 0) dlfile_fatal_load_error(dl, "read from", NULL); off -= r; left -= r; buf += r; } tth_update(&t->hash_tth, bufp, DLFILE_CHUNKSIZE); t->chunk++; t->avail--; dl->have += DLFILE_CHUNKSIZE; (*reset)--; } free(bufp); dl->threads = g_slist_prepend(dl->threads, t); return t; } /* Go over the bitmap and create a thread for each range of undownloaded * chunks. Threads are created in a TTHL-block-aligned fashion to ensure that * the downloading progress can continue from the threads while keeping the * integrity checks. */ static gboolean dlfile_load_threads(dl_t *dl, int fd) { guint32 chunknum = dlfile_chunks(dl->size); guint32 chunksperblock = dl->hash_block / DLFILE_CHUNKSIZE; gboolean needsave = FALSE; dlfile_thread_t *t = NULL; guint32 i,j; for(i=0; isize) - i); for(j=i; jbitmap, j)) break; gboolean hasfullblock = j == i+chunksinblock; if(t && !bita_get(dl->bitmap, i)) { t->avail += chunksinblock; reset = chunksinblock; } else if(hasfullblock) { t = NULL; dl->have += dl->hash_block; } else t = dlfile_load_block(dl, fd, i, chunksinblock, &reset); for(j=i+(chunksinblock-reset); jbitmap, j)) { bita_reset(dl->bitmap, j); needsave = TRUE; } } return needsave; } void dlfile_load(dl_t *dl) { /* If moving to the destination failed in a previous run, assume that the * incoming file is complete and all that's left to do is resume the * finalization (which the user has to initiate by clearing the error). This * needs to be handled as a special case because the incoming file will not * contain the bitmap anymore at this point, and the loading process below * will fail. */ if(dl->prio == DLP_ERR && dl->error == DLE_IO_DEST) { g_message("Download for `%s' in IO_DEST error state, assuming `%s' contains the finished download.", dl->dest, dl->inc); dl->have = dl->size; return; } dl->have = 0; int fd = open(dl->inc, O_RDWR); if(fd < 0) { if(errno != ENOENT) dlfile_fatal_load_error(dl, "open", NULL); return; } /* If the above didn't fail, then we should already have TTHL data. * Otherwise, close and delete whatever we have. */ if(!dl->hastthl) { g_warning("No TTHL data for `%s', deleting partially downloaded data.", dl->dest); close(fd); unlink(dl->inc); return; } gboolean needsave = dlfile_load_bitmap(dl, fd); if(dlfile_load_threads(dl, fd)) needsave = TRUE; if(needsave && !dlfile_save_bitmap(dl, fd)) dlfile_fatal_load_error(dl, "save bitmap to", NULL); dlfile_threaddump(dl, 0); close(fd); } /* Called from dl.c when a dl item is being deleted, either from * dlfile_finished() or when the item is removed from the UI. */ void dlfile_rm(dl_t *dl) { g_return_if_fail(!dl->active_threads); if(dl->bitmap_src) g_source_remove(dl->bitmap_src); if(dl->incfd > 0) g_warn_if_fail(close(dl->incfd) == 0); if(dl->inc) unlink(dl->inc); GSList *l; for(l=dl->threads; l; l=l->next) g_slice_free(dlfile_thread_t, l->data); g_slist_free(dl->threads); g_free(dl->bitmap); } /* Create the inc file and initialize the necessary structs to prepare for * handling downloaded data. */ static gboolean dlfile_open(dl_t *dl) { if(dl->incfd <= 0) dl->incfd = open(dl->inc, O_WRONLY|O_CREAT, 0666); if(dl->incfd < 0) { g_warning("Error opening %s: %s", dl->inc, g_strerror(errno)); dl_queue_seterr(dl, DLE_IO_INC, g_strerror(errno)); return FALSE; } /* Everything else has already been initialized if we have a thread or bitmap */ if(dl->threads || dl->bitmap) return TRUE; if(!dl->islist) { dl->bitmap = bita_new(dlfile_chunks(dl->size)); if(!dlfile_save_bitmap(dl, dl->incfd)) { g_warning("Error writing bitmap for `%s': %s.", dl->dest, g_strerror(errno)); dl_queue_seterr(dl, DLE_IO_INC, g_strerror(errno)); free(dl->bitmap); dl->bitmap = NULL; return FALSE; } } dlfile_thread_t *t = g_slice_new0(dlfile_thread_t); t->dl = dl; t->chunk = 0; t->allocated = 0; if(!dl->islist) t->avail = dlfile_chunks(dl->size); tth_init(&t->hash_tth); dl->threads = g_slist_prepend(dl->threads, t); return TRUE; } /* XXX: This function may block in the main thread for a while. Perhaps do it in a threadpool? */ void dlfile_finished(dl_t *dl) { if(dl->incfd <= 0 && !dlfile_open(dl)) return; /* Regular files: Remove bitmap from the file * File lists: Ensure that the file size is correct after we've downloaded a * longer file list before that got interrupted. */ if(ftruncate(dl->incfd, dl->size) < 0) { g_warning("Error truncating the incoming file for `%s': %s.", dl->dest, g_strerror(errno)); dl_queue_seterr(dl, DLE_IO_INC, g_strerror(errno)); return; } int r = close(dl->incfd); dl->incfd = 0; if(r < 0) { g_warning("Error closing the incoming file for `%s': %s.", dl->dest, g_strerror(errno)); dl_queue_seterr(dl, DLE_IO_INC, g_strerror(errno)); return; } char *fdest = g_filename_from_utf8(dl->dest, -1, NULL, NULL, NULL); if(!fdest) fdest = g_strdup(dl->dest); /* Create destination directory, if it does not exist yet. */ char *parent = g_path_get_dirname(fdest); r = g_mkdir_with_parents(parent, 0777); g_free(parent); if(r < 0) { g_warning("Error creating directory for `%s': %s.", dl->dest, g_strerror(errno)); dl_queue_seterr(dl, DLE_IO_DEST, g_strerror(errno)); g_free(fdest); return; } /* Prevent overwiting other files by appending a prefix to the destination if * it already exists. It is assumed that fn + any dupe-prevention-extension * does not exceed NAME_MAX. (Not that checking against NAME_MAX is really * reliable - some filesystems have an even more strict limit) */ int num = 1; char *dest = g_strdup(fdest); while(!dl->islist && g_file_test(dest, G_FILE_TEST_EXISTS)) { g_free(dest); dest = g_strdup_printf("%s.%d", fdest, num++); } g_free(fdest); GError *err = NULL; file_move(dl->inc, dest, dl->islist, &err); g_free(dest); if(err) { g_warning("Error moving file to destination `%s': %s.", dl->dest, err->message); dl_queue_seterr(dl, DLE_IO_DEST, err->message); g_error_free(err); return; } dl_finished(dl); } /* The 'speed' argument should be a pessimistic estimate of the peers' speed, * in bytes/s. I think this is best obtained from a 30 second average. * Returns the thread pointer. */ dlfile_thread_t *dlfile_getchunk(dl_t *dl, guint64 uid, guint64 speed) { dlfile_thread_t *t = NULL; if(!dlfile_open(dl)) return NULL; /* File lists should always be downloaded in a single GET request because * their contents may be modified between subsequent requests. */ if(dl->islist) { t = dl->threads->data; t->chunk = 0; t->len = 0; t->uid = uid; t->busy = TRUE; dl->hassize = FALSE; dl->have = 0; dl->allbusy = TRUE; dl->active_threads++; return t; } /* Walk through the threads and look for: * t = Thread with largest avail and with allocated = 0 * tsec = Thread with an unallocated block and largest avail-allocated */ dlfile_thread_t *tsec = NULL; GSList *l; g_mutex_lock(&dl->lock); dlfile_threaddump(dl, 1); for(l=dl->threads; l; l=l->next) { dlfile_thread_t *ti = l->data; if(ti->avail && (!tsec || ti->avail-ti->allocated > tsec->avail-tsec->allocated) && dlfile_hasfreeblock(ti)) tsec = ti; if(!ti->busy && (!t || ti->avail > t->avail)) t = ti; } if(!t) { guint32 chunksinblock = dl->hash_block/DLFILE_CHUNKSIZE; guint32 chunk = ((tsec->chunk + tsec->allocated + (tsec->avail - tsec->allocated)/2) / chunksinblock) * chunksinblock; if(chunk < tsec->chunk + tsec->allocated) /* Only possible for the last block in the file */ chunk += chunksinblock; t = g_slice_new0(dlfile_thread_t); t->dl = dl; t->chunk = chunk; t->avail = tsec->avail - (chunk - tsec->chunk); g_return_val_if_fail(t->avail > 0, NULL); tth_init(&t->hash_tth); tsec->avail -= t->avail; dl->threads = g_slist_prepend(dl->threads, t); } /* Number of chunks to request as one segment. The size of a segment is * chosen to approximate a download time of ~5 min. */ guint32 minsegment = var_get_int64(0, VAR_download_segment); if(minsegment) { guint32 chunks = MIN(G_MAXUINT32, 1 + ((speed * 300) / DLFILE_CHUNKSIZE)); chunks = MAX(chunks, (minsegment+DLFILE_CHUNKSIZE-1) / DLFILE_CHUNKSIZE); t->allocated = MIN(t->avail, chunks); } else t->allocated = t->avail; t->busy = TRUE; t->uid = uid; dl->active_threads++; /* Go through the list again to update dl->allbusy */ for(l=dl->threads; l; l=l->next) { dlfile_thread_t *ti = l->data; if(ti->avail && (!ti->busy || dlfile_hasfreeblock(ti))) break; } dl->allbusy = !l; dlfile_threaddump(dl, 2); g_mutex_unlock(&dl->lock); g_debug("Allocating: allbusy = %d, chunk = %u, allocated = %u, avail = %u, chunksinblock = %u, chunksinfile = %u", dl->allbusy, t->chunk, t->allocated, t->avail, (guint32)dl->hash_block/DLFILE_CHUNKSIZE, dlfile_chunks(dl->size)); return t; } static gboolean dlfile_recv_check(dlfile_thread_t *t, char *leaf) { guint32 num = (t->chunk-1)/(t->dl->hash_block / DLFILE_CHUNKSIZE); if(t->dl->size < t->dl->hash_block ? memcmp(leaf, t->dl->hash, 24) == 0 : db_dl_checkhash(t->dl->hash, num, leaf)) return TRUE; g_mutex_lock(&t->dl->lock); /* Hash failure, remove the failed block from the bitmap and dl->have, and * reset this thread so that the block can be re-downloaded. */ guint32 startchunk = num * (t->dl->hash_block / DLFILE_CHUNKSIZE); // Or: chunksinblock = MIN(t->dl->hash_block / DLFILE_CHUNKSIZE, dlfile_chunks(t->dl->size) - startchunk); guint32 chunksinblock = t->chunk - startchunk; t->chunk = startchunk; t->avail += chunksinblock; t->allocated += chunksinblock; t->dl->have -= MIN(t->dl->hash_block, t->dl->size - (guint64)startchunk * DLFILE_CHUNKSIZE); guint32 i; for(i=startchunk; idl->bitmap, i); dlfile_save_bitmap_defer(t->dl); g_mutex_unlock(&t->dl->lock); t->uerr = DLE_HASH; t->uerr_msg = g_strdup_printf("Hash for block %u (chunk %u-%u) does not match.", num, startchunk, startchunk+chunksinblock); return FALSE; } static gboolean dlfile_recv_write(dlfile_thread_t *t, const char *buf, int len) { off_t off = ((guint64)t->chunk * DLFILE_CHUNKSIZE) + t->len; off_t offi = off; size_t rem = len; const char *bufi = buf; while(rem > 0) { ssize_t r = pwrite(t->dl->incfd, bufi, rem, offi); if(r <= 0) { t->err = DLE_IO_INC; t->err_msg = g_strdup(g_strerror(errno)); return FALSE; } offi += r; rem -= r; bufi += r; } fadv_oneshot(t->dl->incfd, off, len, VAR_FFC_DOWNLOAD); return TRUE; } /* Called when new data has been received from a downloading thread. The data * is written to the file, the TTH calculation is updated and checked with the * DB, and the bitmap is updated. * This function may be called from another OS thread. * Returns TRUE to indicate success, FALSE on failure. */ gboolean dlfile_recv(void *vt, const char *buf, int len) { dlfile_thread_t *t = vt; if(!dlfile_recv_write(t, buf, len)) return FALSE; while(len > 0) { guint32 inchunk = MIN((guint32)len, DLFILE_CHUNKSIZE - t->len); t->len += inchunk; gboolean islast = ((guint64)t->chunk * DLFILE_CHUNKSIZE) + t->len == t->dl->size; if(!t->dl->islist) tth_update(&t->hash_tth, buf, inchunk); buf += inchunk; len -= inchunk; g_mutex_lock(&t->dl->lock); t->dl->have += inchunk; if(!islast && t->len < DLFILE_CHUNKSIZE) { g_mutex_unlock(&t->dl->lock); continue; } if(!t->dl->islist) { bita_set(t->dl->bitmap, t->chunk); dlfile_save_bitmap_defer(t->dl); } t->chunk++; t->allocated--; t->avail--; t->len = 0; g_mutex_unlock(&t->dl->lock); if(!t->dl->islist && (islast || t->chunk % (t->dl->hash_block / DLFILE_CHUNKSIZE) == 0)) { char leaf[24]; tth_final(&t->hash_tth, leaf); tth_init(&t->hash_tth); if(!dlfile_recv_check(t, leaf)) return FALSE; } } return TRUE; } void dlfile_recv_done(dlfile_thread_t *t) { dl_t *dl = t->dl; dl->active_threads--; t->busy = FALSE; gboolean freet = FALSE; if(dl->islist ? dl->hassize && dl->have == dl->size : !t->avail) { g_return_if_fail(!(t->err || t->uerr)); /* A failed thread can't be complete */ dl->threads = g_slist_remove(dl->threads, t); freet = TRUE; } else { t->allocated = 0; dl->allbusy = FALSE; } dlfile_threaddump(dl, 3); /* File has been removed from the queue but the dl struct is still in memory * because this thread hadn't finished yet. Free it now. Note that the actual * call to dl_queue_rm() is deferred, because we can't access *t after * calling it. */ gboolean doclose = !dl->bitmap_src && !dl->active_threads; gboolean dorm = FALSE; if(!dl->active_threads && !g_hash_table_lookup(dl_queue, dl->hash)) { dorm = TRUE; doclose = FALSE; } else if(t->err) dl_queue_seterr(t->dl, t->err, t->err_msg); else if(t->uerr) dl_queue_setuerr(t->uid, t->dl->hash, t->uerr, t->uerr_msg); else if(!dl->threads) { dlfile_finished(dl); doclose = FALSE; } if(doclose) { close(dl->incfd); dl->incfd = 0; } g_free(t->err_msg); g_free(t->uerr_msg); t->err = t->uerr = 0; t->err_msg = t->uerr_msg = NULL; if(freet) g_slice_free(dlfile_thread_t, t); if(dorm) dl_queue_rm(dl); } ncdc-1.23.1/src/ui.c0000644000175000017500000003751414245144556011022 00000000000000/* ncdc - NCurses Direct Connect client Copyright (c) 2011-2022 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "ncdc.h" #include "ui.h" #if INTERFACE // These are assumed to occupy two bits #define UIP_EMPTY 0 // no change #define UIP_LOW 1 // system messages #define UIP_MED 2 // chat messages, or error messages in the main tab #define UIP_HIGH 3 // direct messages to you (PM or name mentioned) struct ui_tab_type_t { void (*draw)(ui_tab_t *); char *(*title)(ui_tab_t *); void (*key)(ui_tab_t *, guint64); void (*close)(ui_tab_t *); }; // This is a "base class", in OOP terms. Each tab inherits from this struct and // provides an implementation for the ui_tab_type_t functions. struct ui_tab_t { // Tab type, can be any of the uit_* pointers defined in each uit_*.c file. ui_tab_type_t *type; // Tab priority, UIP_*. int prio; // Tab name, managed by the uit_* code. char *name; // The tab that opened this tab, may be NULL or dangling. ui_tab_t *parent; // If the tab has a logwindow, then this is a pointer to it. Used by the // ui_m() family to send messages to the tab. ui_logwindow_t *log; // If the tab is associated with a hub, then this is a pointer to it. // Currently used for uit_hub, uit_userlist and uit_msg. // TODO: Find a better abstraction and remove this pointer. hub_t *hub; }; #endif GList *ui_tabs = NULL; GList *ui_tab_cur = NULL; // screen dimensions int wincols; int winrows; gboolean ui_beep = FALSE; // set to true anywhere to send a beep // Generic message displaying thing. #if INTERFACE // These flags can be OR'ed together with UIP_ flags. No UIP_ flag or UIP_EMPTY // implies UIP_LOW. There is no need to set any priority when tab == NULL, // since it will be displayed right away anyway. // Message should also be notified in status bar (implied automatically if the // requested tab has no log window). This also uses UIP_EMPTY if no other UIP_ // flag is set. #define UIM_NOTIFY 4 // Ownership of the message string is passed to the message handling function. // (Which fill g_free() it after use) #define UIM_PASS 8 // This is a chat message, i.e. check to see if your name is part of the // message, and if so, give it UIP_HIGH. #define UIM_CHAT 16 // Indicates that ui_m_mainthread() is called directly - without using an idle // function. #define UIM_DIRECT 32 // Do not log to the tab. Implies UIM_NOTIFY #define UIM_NOLOG (64 | UIM_NOTIFY) #endif static char *ui_m_text = NULL; static guint ui_m_timer; static gboolean ui_m_updated = FALSE; typedef struct ui_m_t { char *msg; ui_tab_t *tab; int flags; } ui_m_t; static gboolean ui_m_timeout(gpointer data) { if(ui_m_text) { g_free(ui_m_text); ui_m_text = NULL; g_source_remove(ui_m_timer); ui_m_updated = TRUE; } return FALSE; } static gboolean ui_m_mainthread(gpointer dat) { ui_m_t *msg = dat; ui_tab_t *tab = msg->tab; int prio = msg->flags & 3; // lower two bits if(!tab) tab = ui_tab_cur->data; // It can happen that the tab is closed while we were waiting for this idle // function to be called, so check whether it's still in the list. else if(!(msg->flags & UIM_DIRECT) && !g_list_find(ui_tabs, tab)) goto ui_m_cleanup; gboolean notify = (msg->flags & UIM_NOTIFY) || !tab->log; if(notify && ui_m_text) { g_free(ui_m_text); ui_m_text = NULL; g_source_remove(ui_m_timer); ui_m_updated = TRUE; } if(notify && msg->msg) { ui_m_text = g_strdup(msg->msg); ui_m_timer = g_timeout_add(3000, ui_m_timeout, NULL); ui_m_updated = TRUE; } if(tab->log && msg->msg && !(msg->flags & (UIM_NOLOG & ~UIM_NOTIFY))) { if((msg->flags & UIM_CHAT) && tab->type == uit_hub && uit_hub_checkhighlight(tab, msg->msg)) prio = UIP_HIGH; ui_logwindow_add(tab->log, msg->msg); ui_tab_incprio(tab, MAX(prio, notify ? UIP_EMPTY : UIP_LOW)); } ui_m_cleanup: g_free(msg->msg); g_free(msg); return FALSE; } // a notication message, either displayed in the log of the current tab or, if // the tab has no log window, in the "status bar". Calling this function with // NULL will reset the status bar message. Unlike everything else, this // function can be called from any thread. void ui_m(ui_tab_t *tab, int flags, const char *msg) { ui_m_t *dat = g_new0(ui_m_t, 1); dat->msg = (flags & UIM_PASS) ? (char *)msg : g_strdup(msg); dat->tab = tab; dat->flags = flags; // call directly if we're running from the main thread. use an idle function // otherwise. if((dat->flags & UIM_DIRECT) || g_main_context_is_owner(NULL)) { dat->flags |= UIM_DIRECT; ui_m_mainthread(dat); } else g_idle_add_full(G_PRIORITY_HIGH_IDLE, ui_m_mainthread, dat, NULL); } // UIM_PASS shouldn't be used here (makes no sense). void ui_mf(ui_tab_t *tab, int flags, const char *fmt, ...) { va_list va; va_start(va, fmt); ui_m(tab, flags | UIM_PASS, g_strdup_vprintf(fmt, va)); va_end(va); } // Global stuff ui_textinput_t *ui_global_textinput; void ui_tab_open(ui_tab_t *tab, gboolean sel, ui_tab_t *parent) { ui_tabs = g_list_append(ui_tabs, tab); tab->parent = parent; if(sel) ui_tab_cur = g_list_last(ui_tabs); } // To be called from ui_tab_type_t->close() // TODO: Do this the other way around. Have one global ui_tab_close() function // that performs this task and calls tab->close() to free things up. void ui_tab_remove(ui_tab_t *tab) { // Look for any tabs that have this one as parent, and let those inherit this tab's parent GList *n = ui_tabs; for(; n; n=n->next) { ui_tab_t *t = n->data; if(t->parent == tab) t->parent = tab->parent; } // If this tab was selected, select its parent or a neighbour GList *cur = g_list_find(ui_tabs, tab); if(cur == ui_tab_cur) { GList *par = tab->parent ? g_list_find(ui_tabs, tab->parent) : NULL; ui_tab_cur = par && par != cur ? par : cur->prev ? cur->prev : cur->next; } // And remove the tab ui_tabs = g_list_delete_link(ui_tabs, cur); } // Increases the priority of the given tab, if the current priority is lower. void ui_tab_incprio(ui_tab_t *tab, int prio) { if(prio <= tab->prio) return; int set = var_get_int(0, VAR_notify_bell); set = set == VAR_NOTB_LOW ? UIP_LOW : set == VAR_NOTB_MED ? UIP_MED : set == VAR_NOTB_HIGH ? UIP_HIGH : UIP_EMPTY; if(set != UIP_EMPTY && prio >= set) ui_beep = TRUE; tab->prio = prio; } // Based on gui_window_set_bracketed_paste_mode() in weechat's gui/curses/gui-curses-window.c void ui_set_bracketed_paste(int enable) { char *env = getenv("TMUX"); int tmux = env && *env; env = getenv("TERM"); int screen = env && (strncmp(env, "screen", 6) == 0) && !tmux; fprintf(stderr, "%s\033[?2004%c%s", screen ? "\033P" : "", enable ? 'h' : 'l', screen ? "\033\\" : ""); fflush(stderr); } void ui_init(gboolean bracketed_paste) { // init curses initscr(); raw(); noecho(); curs_set(0); keypad(stdscr, 1); nodelay(stdscr, 1); // ensure curses is init'd before event-keys defined before events happen if(bracketed_paste) { define_key("\x1b[200~", KEY_BRACKETED_PASTE_START); define_key("\x1b[201~", KEY_BRACKETED_PASTE_END); ui_set_bracketed_paste(1); } // global textinput field ui_global_textinput = ui_textinput_create(TRUE, cmd_suggest); // first tab = main tab ui_tab_open(uit_main_create(), TRUE, NULL); ui_colors_init(); ui_draw(); } static void ui_draw_status() { if(fl_is_refreshing()) mvaddstr(winrows-1, 0, "[Refreshing share]"); else if(fl_hash_queue && g_hash_table_size(fl_hash_queue)) mvprintw(winrows-1, 0, "[Hashing: %d / %s / %.2f MiB/s]", g_hash_table_size(fl_hash_queue), str_formatsize(fl_hash_queue_size), ((float)ratecalc_rate(&fl_hash_rate))/(1024.0f*1024.0f)); mvprintw(winrows-1, wincols-37, "[U/D:%6d/%6d KiB/s]", ratecalc_rate(&net_out)/1024, ratecalc_rate(&net_in)/1024); mvprintw(winrows-1, wincols-11, "[S:%3d/%3d]", cc_slots_in_use(NULL), var_get_int(0, VAR_slots)); ui_m_updated = FALSE; if(ui_m_text) { mvaddstr(winrows-1, 0, ui_m_text); mvaddstr(winrows-1, str_columns(ui_m_text), " "); } } #define tabcol(t, n) (2+ceil(log10((n)+1))+str_columns(((ui_tab_t *)(t)->data)->name)) #define prio2a(p) ((p) == UIP_LOW ? UIC(tabprio_low) : (p) == UIP_MED ? UIC(tabprio_med) : UIC(tabprio_high)) /* All tabs are in one of the following states: * - Selected (tab == ui_tab_cur->data) = sel "n:name" in A_BOLD * - No change (!sel && tab->prio == UIP_EMPTY) "n:name" normal * - Change, low priority (!sel && tab->prio == UIP_LOW) "n!name", with ! in UIC(tabprio_low) * - Change, medium priority (!sel && tab->prio == UIP_MED) "n!name", with ! in ^_MED * - Change, high priority (!sel && tab->prio == UIP_HIGH) "n!name", with ! in ^_HIGH * * The truncated indicators are in the following states: * - No changes ">>" or "<<" * - Change "!>" or " cur) top = cur; do { w = maxw; i = top; for(n=g_list_nth(ui_tabs, top); n; n=n->next) { w -= tabcol(n, ++i); if(w < 0 || n == ui_tab_cur) break; } } while(top != cur && w < 0 && ++top); // display some more tabs when there is still room left while(top > 0 && w > tabcol(g_list_nth(ui_tabs, top-1), top-1)) { top--; w -= tabcol(g_list_nth(ui_tabs, top), top); } // check highest priority of hidden tabs before top // (This also sets n and i to the start of the visible list) char maxprio = 0; for(n=ui_tabs,i=0; inext) { ui_tab_t *t = n->data; if(t->prio > maxprio) maxprio = t->prio; } // print left truncate indicator if(top > 0) { mvaddch(winrows-2, xoffset, '<'); if(!maxprio) addch('<'); else { attron(prio2a(maxprio)); addch('!'); attroff(prio2a(maxprio)); } } else mvaddch(winrows-2, xoffset+1, '['); // print the tab list w = maxw; for(; n; n=n->next) { w -= tabcol(n, ++i); if(w < 0) break; ui_tab_t *t = n->data; addch(' '); if(n == ui_tab_cur) attron(UIC(tab_active)); printw("%d", i); if(n == ui_tab_cur || !t->prio) addch(':'); else { attron(prio2a(t->prio)); addch('!'); attroff(prio2a(t->prio)); } addstr(t->name); if(n == ui_tab_cur) attroff(UIC(tab_active)); } // check priority of hidden tabs after the last visible one GList *last = n; maxprio = 0; for(; n&&maxprionext) { ui_tab_t *t = n->data; if(t->prio > maxprio) maxprio = t->prio; } // print right truncate indicator if(!last) addstr(" ]"); else { hline(' ', w + tabcol(last, i)); if(!maxprio) mvaddch(winrows-2, wincols-3, '>'); else { attron(prio2a(maxprio)); mvaddch(winrows-2, wincols-3, '!'); attroff(prio2a(maxprio)); } addch('>'); } } #undef tabcol #undef prio2a void ui_draw() { ui_tab_t *curtab = ui_tab_cur->data; curtab->prio = UIP_EMPTY; getmaxyx(stdscr, winrows, wincols); curs_set(0); // may be overridden later on by a textinput widget erase(); // first line - title char *title = curtab->type->title(curtab); attron(UIC(title)); mvhline(0, 0, ' ', wincols); mvaddnstr(0, 0, title, str_offset_from_columns(title, wincols)); attroff(UIC(title)); g_free(title); // second-last line - time and tab list mvhline(winrows-2, 0, ACS_HLINE, wincols); // time int xoffset = 0; char *tfmt = var_get(0, VAR_ui_time_format); if(strcmp(tfmt, "-") != 0) { char *ts = localtime_fmt(tfmt); mvaddstr(winrows-2, 1, ts); xoffset = 2 + str_columns(ts); g_free(ts); } // tabs ui_draw_tablist(xoffset); // last line - status info or notification ui_draw_status(); // tab contents curtab->type->draw(curtab); refresh(); if(ui_beep) { beep(); ui_beep = FALSE; } } gboolean ui_checkupdate() { ui_tab_t *cur = ui_tab_cur->data; return ui_m_updated || ui_beep || (cur->log && cur->log->updated); } // Called when the day has changed. Argument is new date. void ui_daychange(const char *day) { char *msg = g_strdup_printf("Day changed to %s", day); GList *n = ui_tabs; for(; n; n=n->next) { ui_tab_t *t = n->data; if(t->log) ui_logwindow_addline(t->log, msg, TRUE, TRUE); } g_free(msg); } // Get the extra flag used to annotate files which are present in the download // queue (Q) or already shared (S). Currently used in file list and search tabs. char ui_file_flag(const char *tth) { return fl_local_from_tth(tth) ? 'S' : g_hash_table_lookup(dl_queue, tth) ? 'Q' : ' '; } void ui_input(guint64 key) { ui_tab_t *curtab = ui_tab_cur->data; switch(key) { case INPT_CTRL('c'): // ctrl+c ui_m(NULL, UIM_NOLOG, "Type /quit to exit ncdc."); break; case INPT_ALT('j'): // alt+j (previous tab) ui_tab_cur = ui_tab_cur->prev ? ui_tab_cur->prev : g_list_last(ui_tabs); break; case INPT_ALT('k'): // alt+k (next tab) ui_tab_cur = ui_tab_cur->next ? ui_tab_cur->next : ui_tabs; break; case INPT_ALT('h'): ; // alt+h (swap tab with left) GList *prev = ui_tab_cur->prev; if(prev) { ui_tabs = g_list_delete_link(ui_tabs, ui_tab_cur); ui_tabs = g_list_insert_before(ui_tabs, prev, curtab); ui_tab_cur = prev->prev; } break; case INPT_ALT('l'): ; // alt+l (swap tab with right) GList *next = ui_tab_cur->next; if(next) { ui_tabs = g_list_delete_link(ui_tabs, ui_tab_cur); ui_tabs = g_list_insert_before(ui_tabs, next->next, curtab); ui_tab_cur = next->next; } break; case INPT_ALT('a'): { // alt+a (switch to next active tab) GList *n = ui_tabs; GList *c = NULL; int max = UIP_EMPTY; for(; n; n=n->next) { if(((ui_tab_t *)n->data)->prio > max) { max = ((ui_tab_t *)n->data)->prio; c = n; } } if(c) ui_tab_cur = c; break; } case INPT_ALT('c'): // alt+c (alias for /close) cmd_handle("/close"); break; case INPT_CTRL('l'): // ctrl+l (alias for /clear) cmd_handle("/clear"); break; case INPT_ALT('r'): // alt+r (alias for /refresh) cmd_handle("/refresh"); break; case INPT_ALT('o'): // alt+o (alias for /browse) cmd_handle("/browse"); break; case INPT_ALT('n'): // alt+n (alias for /connections) cmd_handle("/connections"); break; case INPT_ALT('q'): // alt+q (alias for /queue) cmd_handle("/queue"); break; default: // alt+num (switch tab) if(key >= INPT_ALT('0') && key <= INPT_ALT('9')) { GList *n = g_list_nth(ui_tabs, INPT_CODE(key) == '0' ? 9 : INPT_CODE(key)-'1'); if(n) ui_tab_cur = n; // let tab handle it } else curtab->type->key(curtab, key); } } ncdc-1.23.1/src/tth.c0000644000175000017500000010737014245144552011176 00000000000000/* ncdc - NCurses Direct Connect client Copyright (c) 2011-2022 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "ncdc.h" #include "tth.h" /* An implementation of Tiger Hash Function based on the article by * Ross Anderson and Eli Biham "Tiger: A Fast New Hash Function". * * This implementation is based on librhash/tiger.c, part of the RHash * utility, with modifications for better integration into ncdc. * RHash homepage: http://rhash.anz.ru/ * RHash on sourceforge: http://sourceforge.net/projects/rhash/ * * The following copyright statement was included in the original file: * * Implementation written by Alexei Kravchenko. * * Copyleft: * I hereby release this code into the public domain. This applies worldwide. * I grant any entity the right to use this work for ANY PURPOSE, * without any conditions, unless such conditions are required by law. */ #if INTERFACE #define tiger_block_size 64 struct tiger_ctx_t { guint64 hash[3]; /* algorithm 192-bit state */ char message[tiger_block_size]; /* 512-bit buffer for leftovers */ guint64 length; /* processed message length */ }; #endif #if defined(_LP64) || defined(__LP64__) || defined(__x86_64) || \ defined(__x86_64__) || defined(_M_AMD64) || defined(_M_X64) # define CPU_X64 #endif #define IS_ALIGNED_64(p) (0 == (7 & ((const char*)(p) - (const char*)0))) // S-boxes, defined below static guint64 tiger_sboxes[4][256]; #define t1 tiger_sboxes[0] #define t2 tiger_sboxes[1] #define t3 tiger_sboxes[2] #define t4 tiger_sboxes[3] void tiger_init(tiger_ctx_t *ctx) { ctx->length = 0; /* initialize algorithm state */ ctx->hash[0] = G_GUINT64_CONSTANT(0x0123456789ABCDEF); ctx->hash[1] = G_GUINT64_CONSTANT(0xFEDCBA9876543210); ctx->hash[2] = G_GUINT64_CONSTANT(0xF096A5B4C3B2E187); } #ifdef CPU_X64 /* for x86-64 */ #define round(a,b,c,x,mul) \ c ^= x; \ a -= t1[(guint8)(c)] ^ \ t2[(guint8)((c) >> (2*8))] ^ \ t3[(guint8)((c) >> (4*8))] ^ \ t4[(guint8)((c) >> (6*8))] ; \ b += t4[(guint8)((c) >> (1*8))] ^ \ t3[(guint8)((c) >> (3*8))] ^ \ t2[(guint8)((c) >> (5*8))] ^ \ t1[(guint8)((c) >> (7*8))]; \ b *= mul; #else /* for IA32 */ #define round(a,b,c,x,mul) \ c ^= x; \ a -= t1[(guint8)(c)] ^ \ t2[(guint8)(((guint32)(c)) >> (2*8))] ^ \ t3[(guint8)((c) >> (4*8))] ^ \ t4[(guint8)(((guint32)((c) >> (4*8))) >> (2*8))] ; \ b += t4[(guint8)(((guint32)(c)) >> (1*8))] ^ \ t3[(guint8)(((guint32)(c)) >> (3*8))] ^ \ t2[(guint8)(((guint32)((c) >> (4*8))) >> (1*8))] ^ \ t1[(guint8)(((guint32)((c) >> (4*8))) >> (3*8))]; \ b *= mul; #endif /* CPU_X64 */ #define pass(a,b,c,mul) \ round(a,b,c,x0,mul) \ round(b,c,a,x1,mul) \ round(c,a,b,x2,mul) \ round(a,b,c,x3,mul) \ round(b,c,a,x4,mul) \ round(c,a,b,x5,mul) \ round(a,b,c,x6,mul) \ round(b,c,a,x7,mul) #define key_schedule { \ x0 -= x7 ^ G_GUINT64_CONSTANT(0xA5A5A5A5A5A5A5A5); \ x1 ^= x0; \ x2 += x1; \ x3 -= x2 ^ ((~x1)<<19); \ x4 ^= x3; \ x5 += x4; \ x6 -= x5 ^ ((~x4)>>23); \ x7 ^= x6; \ x0 += x7; \ x1 -= x0 ^ ((~x7)<<19); \ x2 ^= x1; \ x3 += x2; \ x4 -= x3 ^ ((~x2)>>23); \ x5 ^= x4; \ x6 += x5; \ x7 -= x6 ^ G_GUINT64_CONSTANT(0x0123456789ABCDEF); \ } static void tiger_process_block(guint64 state[3], guint64 *block) { /* Optimized for GCC IA32. The order of declarations is important for compiler. */ guint64 a, b, c; guint64 x0, x1, x2, x3, x4, x5, x6, x7; #ifndef CPU_X64 guint64 tmp; char i; #endif x0=GUINT64_FROM_LE(block[0]); x1=GUINT64_FROM_LE(block[1]); x2=GUINT64_FROM_LE(block[2]); x3=GUINT64_FROM_LE(block[3]); x4=GUINT64_FROM_LE(block[4]); x5=GUINT64_FROM_LE(block[5]); x6=GUINT64_FROM_LE(block[6]); x7=GUINT64_FROM_LE(block[7]); a = state[0]; b = state[1]; c = state[2]; /* passes and key shedules */ #ifndef CPU_X64 for(i=0; i<3; i++) { if(i != 0) key_schedule; pass(a, b, c, (i==0 ? 5 : i==1 ? 7 : 9)); tmp=a; a=c; c=b; b=tmp; } #else pass(a, b, c, 5); key_schedule; pass(c, a, b, 7); key_schedule; pass(b, c, a, 9); #endif /* feedforward operation */ state[0] = a ^ state[0]; state[1] = b - state[1]; state[2] = c + state[2]; } void tiger_update(tiger_ctx_t *ctx, const char *msg, size_t size) { size_t index = (size_t)ctx->length & 63; size_t left; ctx->length += size; /* Try to fill partial block */ if(index) { left = tiger_block_size - index; if(size < left) { memcpy(ctx->message + index, msg, size); return; } else { memcpy(ctx->message + index, msg, left); tiger_process_block(ctx->hash, (guint64 *)ctx->message); msg += left; size -= left; } } while(size >= tiger_block_size) { if(IS_ALIGNED_64(msg)) { /* the most common case is processing of an already aligned message without copying it */ tiger_process_block(ctx->hash, (guint64 *)msg); } else { memcpy(ctx->message, msg, tiger_block_size); tiger_process_block(ctx->hash, (guint64 *)ctx->message); } msg += tiger_block_size; size -= tiger_block_size; } if(size) { /* save leftovers */ memcpy(ctx->message, msg, size); } } void tiger_final(tiger_ctx_t *ctx, char result[24]) { unsigned index = (unsigned)ctx->length & 63; guint64 *msg64 = (guint64 *)ctx->message; /* pad message and run for last block */ /* append the byte 0x01 to the message */ ctx->message[index++] = 0x01; /* if no room left in the message to store 64-bit message length */ if(index > 56) { /* then fill the rest with zeros and process it */ while(index < 64) ctx->message[index++] = 0; tiger_process_block(ctx->hash, msg64); index = 0; } while(index < 56) ctx->message[index++] = 0; msg64[7] = GUINT64_FROM_LE(ctx->length << 3); tiger_process_block(ctx->hash, msg64); /* save result hash */ guint64 *res = (guint64 *)result; res[0] = GINT64_TO_LE(ctx->hash[0]); res[1] = GINT64_TO_LE(ctx->hash[1]); res[2] = GINT64_TO_LE(ctx->hash[2]); } /* This TTH implementation was written from scratch, and is designed to behave * similar to MerkleTree.h in DC++. The actual code has some inspiration from * both the DC++ and RHash implementations. (See the note on the tiger * implementation above for information about RHash) */ #if INTERFACE struct tth_ctx_t { tiger_ctx_t tiger; int leafnum; // There can be 2^29 leafs. Fits in an integer. int gotfirst; // Stack used to calculate the hash. // Max. size = 2^29 * 1024 = 512 GiB // When the stack starts with a leaf node, the position in the stack // determines the data size the hash represents: // size = tth_base_block << pos // (pos being the index from 0) char stack[29][24]; }; // Calculate the number of blocks when the filesize and blocksize are known. // = max(1, ceil(fs/bs)) #define tth_num_blocks(fs, bs) MAX(((fs)+(bs)-1)/(bs), 1) #endif #define tth_base_block 1024 #define tth_new_leaf(ctx) do {\ tiger_init(&((ctx)->tiger));\ tiger_update(&((ctx)->tiger), "\0", 1);\ } while(0) #define tth_combine(left, right, res) do {\ tiger_ctx_t x;\ tiger_init(&x);\ tiger_update(&x, "\1", 1);\ tiger_update(&x, left, 24);\ tiger_update(&x, right, 24);\ tiger_final(&x, res);\ } while(0) void tth_init(tth_ctx_t *ctx) { tth_new_leaf(ctx); ctx->leafnum = ctx->gotfirst = 0; } void tth_update_leaf(tth_ctx_t *ctx, const char *leaf) { int pos = 0; char tmp[24]; int it; memcpy(tmp, leaf, 24); // This trick uses the leaf number to determine when it needs to combine // with a previous hash (idea borrowed from RHash) for(it=1; it & ctx->leafnum; it <<= 1) { tth_combine(ctx->stack[pos], tmp, tmp); pos++; } memcpy(ctx->stack[pos], tmp, 24); ctx->leafnum++; ctx->gotfirst = 1; } void tth_update(tth_ctx_t *ctx, const char *msg, size_t len) { char leaf[24]; int left; if(len > 0) ctx->gotfirst = 1; while(len > 0) { left = MIN(tth_base_block - (ctx->tiger.length-1), len); tiger_update(&ctx->tiger, msg, left); len -= left; msg += left; g_assert(ctx->tiger.length-1 <= tth_base_block); // we've got a new base leaf if(ctx->tiger.length-1 == tth_base_block) { tiger_final(&ctx->tiger, leaf); tth_update_leaf(ctx, leaf); tth_new_leaf(ctx); } } g_assert(len == 0); } // combine everything on the stack to produce the last hash (based on RHash code) static void tth_stack_final(tth_ctx_t *ctx, char *result) { guint64 it = 1; int pos = 0; char *last; for(it=1; itleafnum && (it&ctx->leafnum)==0; it<<=1) pos++; last = ctx->stack[pos]; for(it<<=1; it <= ctx->leafnum; it<<=1) { pos++; if(it & ctx->leafnum) { tth_combine(ctx->stack[pos], last, result); last = result; } } if(last != result) memcpy(result, last, 24); } void tth_final(tth_ctx_t *ctx, char *result) { // finish up last leaf if(!ctx->gotfirst || ctx->tiger.length > 1) { tiger_final(&ctx->tiger, result); tth_update_leaf(ctx, result); } // calculate final hash tth_stack_final(ctx, result); } // Calculate the root from a list of leaf/intermetiate hashes. All hashes must // be at the same level. void tth_root(char *blocks, int num, char *result) { tth_ctx_t t; tth_init(&t); int i; for(i=0; i]", "Download and browse someone's file list.", "Without arguments, this opens a new tab where you can browse your own file list." " Note that changes to your list are not immediately visible in the browser." " You need to re-open the tab to get the latest version of your list.\n\n" "With arguments, the file list of the specified user will be downloaded (if" " it has not been downloaded already) and the browse tab will open once it's" " complete. The `-f' flag can be used to force the file list to be (re-)downloaded." }, { "clear", NULL, "Clear the display.", "Clears the log displayed on the screen. Does not affect the log files in any way." " Ctrl+l is a shortcut for this command." }, { "close", NULL, "Close the current tab.", "Close the current tab. When closing a hub tab, you will be disconnected from" " the hub and all related userlist and PM tabs will also be closed. Alt+c is" " a shortcut for this command." }, { "connect", "[
]", "Connect to a hub.", "Initiate a connection with a hub. If no address is specified, will connect" " to the hub you last used on the current tab. The address should be in the" " form of `protocol://host:port/' or `host:port'. The `:port' part is in both" " cases optional and defaults to :411. The following protocols are" " recognized: dchub, nmdc, nmdcs, adc, adcs. When connecting to an nmdcs or" " adcs hub and the SHA256 keyprint is known, you can attach this to the url as" " `?kp=SHA256/'\n\n" "Note that this command can only be used on hub tabs. If you want to open a new" " connection to a hub, you need to use /open first. For example:\n\n" " /open testhub\n" " /connect dchub://dc.some-test-hub.com/\n\n" "See the /open command for more information." }, { "connections", NULL, "Open the connections tab.", NULL }, { "delhub", "", "Remove a hub from the configuration", NULL }, { "disconnect", NULL, "Disconnect from a hub.", NULL }, { "gc", NULL, "Perform some garbage collection.", "Cleans up unused data and reorganizes existing data to allow more efficient" " storage and usage. Currently, this commands removes unused hash data, does" " a VACUUM on db.sqlite3, removes unused files in inc/ and old files in" " fl/.\n\n" "This command may take some time to complete, and will fully block ncdc while" " it is running. It is recommended to run this command every once in a while." " Every month is a good interval. Note that when ncdc says that it has" " completed this command, it's lying to you. Ncdc will still run a few large" " queries on the background, which may take up to a minute to complete." }, { "grant", "[-list|]", "Grant someone a slot.", "Grant someone a slot. This allows the user to download from you even if you" " have no free slots. The slot will remain granted until the /ungrant" " command is used, even if ncdc has been restarted in the mean time.\n\n" "To get a list of users whom you have granted a slot, use `/grant' without" " arguments or with `-list'. Be warned that using `/grant' without arguments on" " a PM tab will grant the slot to the user you are talking with. Make sure to" " use `-list' in that case.\n\n" "Note that a granted slot is specific to a single hub. If the same user is" " also on other hubs, he/she will not be granted a slot on those hubs." }, { "help", "[|set |keys [
]]", "Request information on commands.", "To get a list of available commands, use /help without arguments.\n" "To get information on a particular command, use /help .\n" "To get information on a configuration setting, use /help set .\n" "To get help on key bindings, use /help keys.\n" }, { "hset", "[ []]", "Get or set per-hub configuration variables.", "Get or set per-hub configuration variables. Works equivalent to the `/set'" " command, but can only be used on hub tabs. Use `/hunset' to reset a" " variable back to its global value." }, { "hunset", "[]", "Unset a per-hub configuration variable.", "This command can be used to reset a per-hub configuration variable back to" " its global value." }, { "kick", "", "Kick a user from the hub.", "Kick a user from the hub. This command only works on NMDC hubs, and you need" " to be an OP to be able to use it." }, { "listen", NULL, "List currently opened ports.", NULL, }, { "me", "", "Chat in third person.", "This allows you to talk in third person. Most clients will display your message as something like:\n\n" " ** Nick is doing something\n\n" "Note that this command only works correctly on ADC hubs. The NMDC protocol" " does not have this feature, and your message will be sent as-is, including the /me." }, { "msg", " []", "Send a private message.", "Send a private message to a user on the currently opened hub. If no" " message is given, the tab will be opened but no message will be sent." }, { "nick", "[]", "Alias for `/hset nick' on hub tabs, and `/set nick' otherwise.", NULL }, { "open", "[-n] [] [
]", "Open a new hub tab and connect to the hub.", "Without arguments, list all hubs known by the current configuration." " Otherwise, this opens a new tab to use for a hub. The name is a (short)" " personal name you use to identify the hub, and will be used for storing" " hub-specific configuration.\n\n" "If you have specified an address or have previously connected to a hub" " from a tab with the same name, /open will automatically connect to" " the hub. Use the `-n' flag to disable this behaviour.\n\n" "See /connect for more information on connecting to a hub." }, { "password", "", "Send your password to the hub.", "This command can be used to send a password to the hub without saving it to" " the database. If you wish to login automatically without having to type" " /password every time, use '/hset password '. Be warned, however," " that your password will be saved unencrypted in that case." }, { "pm", " []", "Alias for /msg", NULL }, { "queue", NULL, "Open the download queue.", NULL }, { "quit", NULL, "Quit ncdc.", "Quit ncdc." }, { "reconnect", NULL, "Shortcut for /disconnect and /connect", "Reconnect to the hub. When your nick or the hub encoding have been changed," " the new settings will be used after the reconnect.\n\n" "This command can also be used on the main tab, in which case all connected" " hubs will be reconnected." }, { "refresh", "[]", "Refresh file list.", "Initiates share refresh. If no argument is given, the complete list will be" " refreshed. Otherwise only the specified directory will be refreshed. The" " path argument can be either an absolute filesystem path or a virtual path" " within your share." }, { "say", "", "Send a chat message.", "Sends a chat message to the current hub or user. You normally don't have to" " use the /say command explicitly, any command not staring with '/' will" " automatically imply `/say '. For example, typing `hello.' in the" " command line is equivalent to `/say hello.'. Using the /say command" " explicitly may be useful to send message starting with '/' to the chat, for" " example `/say /help is what you are looking for'." }, { "search", "[options] ", "Search for files.", "Performs a file search, opening a new tab with the results.\n\n" "Available options:\n\n" " -hub Search the current hub only. (default)\n" " -all Search all connected hubs, except those with `chat_only' set.\n" " -le Size of the file must be less than .\n" " -ge Size of the file must be larger than .\n" " -t File must be of type . (see below)\n" " -tth TTH root of this file must match .\n\n" "File sizes ( above) accept the following suffixes: G (GiB), M (MiB) and K (KiB).\n\n" "The following file types can be used with the -t option:\n\n" " 1 any Any file or directory. (default)\n" " 2 audio Audio files.\n" " 3 archive (Compressed) archives.\n" " 4 doc Text documents.\n" " 5 exe Windows executables.\n" " 6 img Image files.\n" " 7 video Video files.\n" " 8 dir Directories.\n\n" "Note that file type matching is done using file extensions, and is not very reliable." }, { "set", "[ []]", "Get or set global configuration variables.", "Get or set global configuration variables. Use without arguments to get a" " list of all global settings and their current value. Glob-style pattern" " matching on the settings is also possible. Use, for example, `/set color*'" " to list all color-related settings.\n\n" "See the `/unset' command to change a setting back to its default, and the" " `/hset' command to manage configuration on a per-hub basis. Changes to the" " settings are automatically saved to the database, and will not be lost after" " restarting ncdc.\n\n" "To get information on a particular setting, use `/help set '." }, { "share", "[ ]", "Add a directory to your share.", "Use /share without arguments to get a list of shared directories.\n" "When called with a name and a path, the path will be added to your share." " Note that shell escaping may be used in the name. For example, to add a" " directory with the name `Fun Stuff', you could do the following:\n\n" " /share \"Fun Stuff\" /path/to/fun/stuff\n\n" "Or:\n\n" " /share Fun\\ Stuff /path/to/fun/stuff\n\n" "The full path to the directory will not be visible to others, only the name" " you give it will be public. An initial `/refresh' is done automatically on" " the added directory." }, { "ungrant", "[]", "Revoke a granted slot.", NULL }, { "unset", "[]", "Unset a global configuration variable.", "This command can be used to reset a global configuration variable back to its default value." }, { "unshare", "[]", "Remove a directory from your share.", "To remove a single directory from your share, use `/unshare ', to" " remove all directories from your share, use `/unshare /'.\n\n" "Note that the hash data associated with the removed files will remain in the" " database. This allows you to re-add the files to your share without needing" " to re-hash them. The downside is that the database file may grow fairly large" " with unneeded information. See the `/gc' command to clean that up." }, { "userlist", NULL, "Open the user list.", "Opens the user list of the currently selected hub. Can also be accessed using Alt+u." }, { "version", NULL, "Display version information.", NULL }, { "whois", "", "Locate a user in the user list.", "This will open the user list and select the given user." }, { "" } }; #endif // DOC_CMD #ifdef DOC_SET typedef struct doc_set_t { char const *name; int hub; char const *type, *desc; } doc_set_t; static const doc_set_t doc_sets[] = { { "active", 1, "", "Enables or disables active mode. You may have to configure your router" " and/or firewall for this to work, see the `active_ip' and `active_port'" " settings for more information." }, { "active_ip", 1, "", "Your public IP address for use in active mode. If this is not set or set to" " '0.0.0.0' for IPv4 or '::' for IPv6, then ncdc will try to automatically" " get your IP address from the hub. If you do set this manually, it is" " important that other clients can reach you using this IP address. If you" " connect to a hub on the internet, this should be your internet (WAN) IP." " Likewise, if you connect to a hub on your LAN, this should be your LAN" " IP.\n\n" "Both an IPv4 and an IPv6 address are set by providing two IP addresses" " separated with a comma. When unset, '0.0.0.0,::' is assumed. Only the IP" " version used to connect to the hub is used. That is, if you connect to an" " IPv6 hub, then the configured IPv6 address is used and the IPv4 address is" " ignored.\n\n" "When set to the special value `local', ncdc will automatically get your IP" " address from the local network interface that is used to connect to the" " hub. This option should only be used if there is no NAT between you and" " the hub, because this will give the wrong IP if you are behind a NAT." }, { "active_port", 1, "", "The listen port for incoming connections in active mode. Set to `0' to" " automatically assign a random port. This setting is by default also used" " for the UDP port, see the `active_tls_port' settings to change that. If you" " are behind a router or firewall, make sure that you have configured it to" " forward and allow these ports." }, { "active_udp_port", 1, "", "The listen port for incoming UDP connections in active mode. Defaults to the" " `active_port' setting, or to a random number if `active_port' is not set." }, { "adc_blom", 1, "", "Whether to support the BLOM extension on ADC hubs. This may decrease the" " bandwidth usage on the hub connection, in exchange for a bit of" " computational overhead. Some hubs require this setting to be enabled. This" " setting requires a reconnect with the hub to be active." }, { "autoconnect", 1, "", "Set to true to automatically connect to the current hub when ncdc starts up." }, { "autorefresh", 0, "", "The time between automatic file refreshes. Recognized suffices are 's' for" " seconds, 'm' for minutes, 'h' for hours and 'd' for days. Set to 0 to" " disable automatically refreshing the file list. This setting also" " determines whether ncdc will perform a refresh on startup. See the" " `/refresh' command to manually refresh your file list." }, { "backlog", 1, "", "When opening a hub or PM tab, ncdc can load a certain amount of lines from" " the log file into the log window. Setting this to a positive value enables" " this feature and configures the number of lines to load. Note that, while" " this setting can be set on a per-hub basis, PM windows will use the global" " value (global.backlog)." }, { "chat_only", 1, "", "Set to true to indicate that this hub is only used for chatting. That is," " you won't or can't download from it. This setting affects the /search" " command when it is given the -all option." }, // Note: the setting list isn't alphabetic here, but in a more intuitive order { "color_*", 0, "", "The settings starting with the `color_' prefix allow you to change the" " interface colors. The following is a list of available color settings:\n\n" " list_default - default item in a list\n" " list_header - header of a list\n" " list_select - selected item in a list\n" " log_default - default log color\n" " log_time - the time prefix in log messages\n" " log_nick - default nick color\n" " log_highlight - nick color of a highlighted line\n" " log_ownnick - color of your own nick\n" " log_join - color of join messages\n" " log_quit - color of quit messages\n" " separator - the list separator/footer bar\n" " tab_active - the active tab in the tab list\n" " tabprio_low - low priority tab notification color\n" " tabprio_med - medium priority tab notification color\n" " tabprio_high - high priority tab notification color\n" " title - the title bar\n" "\n" "The actual color value can be set with a comma-separated list of color names" " and/or attributes. The first color in the list is the foreground color, the" " second color is used for the background. When the fore- or background color" " is not specified, the default colors of your terminal will be used.\n" "The following color names can be used: black, blue, cyan, default, green," " magenta, red, white and yellow.\n" "The following attributes can be used: bold, blink, reverse and underline.\n" "The actual color values displayed by your terminal may vary. Adding the" " `bold' attribute usually makes the foreground color appear brighter as well." }, { "connection", 1, "", "Set your upload speed. This is just an indication for other users in the hub" " so that they know what speed they can expect when downloading from you. The" " actual format you can use here may vary, but it is recommended to set it to" " either a plain number for Mbit/s (e.g. `50' for 50 mbit) or a number with a" " `KiB/s' indicator (e.g. `2300 KiB/s'). On ADC hubs you must use one of the" " previously mentioned formats, otherwise no upload speed will be" " broadcasted. This setting is broadcasted as-is on NMDC hubs, to allow for" " using old-style connection values (e.g. `DSL' or `Cable') on hubs that" " require this.\n\n" "This setting is ignored if `upload_rate' has been set. If it is, that value" " is broadcasted instead." }, { "description", 1, "", "A short public description that will be displayed in the user list of a hub." }, { "disconnect_offline", 1, "", "Automatically disconnect any upload or download transfers when a user leaves" " the hub, or when you leave the hub. Setting this to `true' ensures that you" " are only connected with people who are online on the same hubs as you are." }, { "download_dir", 0, "", "The directory where finished downloads are moved to. Finished downloads are" " by default stored in /dl/. It is possible to set this to" " a location that is on a different filesystem than the incoming directory," " but doing so is not recommended: ncdc will block when moving the completed" " files to their final destination." }, { "download_exclude", 0, "", "When recursively adding a directory to the download queue - by pressing `d'" " on a directory in the file list browser - any item in the selected" " directory with a name that matches this regular expression will not be" " added to the download queue.\n\n" "This regex is not checked when adding individual files from either the file" " list browser or the search results." }, { "download_rate", 0, "", "Maximum combined transfer rate of all downloads. The total download speed" " will be limited to this value. The suffixes `G', 'M', and 'K' can be used" " for GiB/s, MiB/s and KiB/s, respectively. Note that, similar to upload_rate," " TCP overhead are not counted towards this limit, so the actual bandwidth" " usage might be a little higher." }, { "download_segment", 0, "", "Minimum segment size to use when requesting file data from another user." " Set to 0 to disable segmented downloading." }, { "download_shared", 0, "", "Whether to download files which are already present in your share. When this" " is set to `false', adding already shared files results in a UI message" " instead of adding the file to the download queue." }, { "download_slots", 0, "", "Maximum number of simultaneous downloads." }, { "email", 1, "", "Your email address. This will be displayed in the user list of the hub, so" " only set this if you want it to be public." }, { "encoding", 1, "", "The character set/encoding to use for hub and PM messages. This setting is" " only used on NMDC hubs, ADC always uses UTF-8. Some common values are:\n\n" " CP1250 (Central Europe)\n" " CP1251 (Cyrillic)\n" " CP1252 (Western Europe)\n" " ISO-8859-7 (Greek)\n" " KOI8-R (Cyrillic)\n" " UTF-8 (International)" }, { "filelist_maxage", 0, "", "The maximum age of a downloaded file list. If a file list was downloaded" " longer ago than the configured interval, it will be removed from the cache" " (the fl/ directory) and subsequent requests to open the file list will" " result in the list being downloaded from the user again. Recognized" " suffices are 's' for seconds, 'm' for minutes, 'h' for hours and 'd' for" " days. Set to 0 to disable the cache altogether." }, { "flush_file_cache", 0, "[,...]", "Tell the OS to flush the file (disk) cache for file contents read while" " hashing and/or uploading or written to while downloading. On one hand, this" " will avoid trashing your disk cache with large files and thus improve the" " overall responsiveness of your system. On the other hand, ncdc may purge any" " shared files from the cache, even if they are still used by other" " applications. In general, it is a good idea to enable this if you also use" " your system for other things besides ncdc, you share large files (>100MB)" " and people are not constantly downloading the same file from you." }, { "geoip_cc", 0, "|disabled", "Path to the GeoIP2 Country database file (GeoLite2-Country.mmdb), or" " 'disabled' to disable GeoIP lookups. The database can be downloaded " " from https://dev.maxmind.com/geoip/geoip2/geolite2/." }, { "hash_rate", 0, "", "Maximum file hashing speed. See the `download_rate' setting for allowed" " formats for this setting." }, { "hubname", 1, "", "The name of the currently opened hub tab. This is a user-assigned name, and" " is only used within ncdc itself. This is the same name as given to the" " `/open' command." }, { "incoming_dir", 0, "", "The directory where incomplete downloads are stored. This setting can only" " be changed when the download queue is empty. Also see the download_dir" " setting." }, { "local_address", 1, "", "Specifies the address of the local network interface to use for connecting" " to the outside and for accepting incoming connections in active mode." " Both an IPv4 and an IPv6 address are set by providing two IP addresses" " separated with a comma. When unset, '0.0.0.0,::' is assumed.\n\n" "If no IPv4 address is specified, '0.0.0.0' is added automatically." " Similarly, if no IPv6 address is specified, '::' is added automatically. The" " address that is actually used depends on the IP version actually used. That" " is, if you're on an IPv6 hub, then ncdc will listen on the specified IPv6" " address. Note that, even if the hub you're on is on IPv6, ncdc may still try" " to connect to another client over IPv4, at which point the socket will be" " bound to the configured IPv4 address." }, { "log_debug", 0, "", "Log debug messages to stderr.log in the session directory. It is highly" " recommended to enable this setting if you wish to debug or hack ncdc. Be" " warned, however, that this may generate a lot of data if you're connected" " to a large hub." }, { "log_downloads", 0, "", "Log downloaded files to transfers.log." }, { "log_hubchat", 1, "", "Log the main hub chat. Note that changing this requires any affected hub" " tabs to be closed and reopened before the change is effective." }, { "log_uploads", 0, "", "Log file uploads to transfers.log." }, { "max_ul_per_user", 0, "", "The maximum number of simultaneous upload connections to one user." }, { "minislots", 0, "", "Set the number of available minislots. A `minislot' is a special slot that" " is used when all regular upload slots are in use and someone is requesting" " your filelist or a small file. In this case, the other client automatically" " applies for a minislot, and can still download from you as long as not all" " minislots are in use. What constitutes a `small' file can be changed with" " the `minislot_size' setting. Also see the `slots' configuration setting and" " the `/grant' command." }, { "minislot_size", 0, "", "The maximum size of a file that may be downloaded using a `minislot', in" " KiB. See the `minislots' setting for more information." }, { "nick", 1, "", "Your nick. Nick changes are only visible on newly connected hubs, use the " " `/reconnect' command to use your new nick immediately. Note that it is" " highly discouraged to change your nick on NMDC hubs. This is because" " clients downloading from you have no way of knowing that you changed your" " nick, and therefore can't immediately continue to download from you." }, { "notify_bell", 0, "", "When enabled, ncdc will send a bell to your terminal when a tab indicates a" " notification. The notification types are:\n\n" " high - Messages directed to you (PM or highlight in hub chat),\n" " medium - Regular hub chat,\n" " low - User joins/quits, new search results, etc.\n" "\nHow a \"bell\" (or \"beep\" or \"alert\", whatever you prefer to call it)" " manifests itself depends on your terminal. In some setups, this generates an" " audible system bell. In other setups it can makes your terminal window flash" " or do other annoying things to get your attention. And in some setups it is" " ignored completely." }, { "password", 1, "", "Sets your password for the current hub and enables auto-login on connect. If" " you just want to login to a hub without saving your password, use the" " `/password' command instead. Passwords are saved unencrypted in the config" " file." }, { "reconnect_timeout", 1, "", "The time to wait before automatically reconnecting to a hub. Set to 0 to" " disable automatic reconnect." }, { "sendfile", 0, "", "Whether or not to use the sendfile() system call to upload files, if" " supported. Using sendfile() allows less resource usage while uploading, but" " may not work well on all systems." }, { "share_emptydirs", 0, "", "Share empty directories. When disabled (the default), empty directories in" " your share will not be visible to others. This also affects empty" " directories containing only empty directories, etc. A file list refresh is" " required for this setting to be effective." }, { "share_exclude", 0, "", "Any file or directory with a name that matches this regular expression will" " not be shared. A file list refresh is required for this setting to be" " effective." }, { "share_hidden", 0, "", "Whether to share hidden files and directories. A `hidden' file or directory" " is one of which the file name starts with a dot. (e.g. `.bashrc'). A file" " list refresh is required for this setting to be effective." }, { "share_symlinks", 0, "", "Whether to follow symlinks in shared directories. When disabled (default)," " ncdc will never share any files outside of the directory you specified. When" " enabled, any symlinks in your shared directories will be followed, even" " when they point to a directory outside your share." }, { "show_free_slots", 1, "", "When set to true, [n sl] will be prepended to your description, where n is" " the number of currently available upload slots." }, { "show_joinquit", 1, "", "Whether to display join/quit messages in the hub chat." }, { "slots", 0, "", "The number of upload slots. This determines for the most part how many" " people can download from you simultaneously. It is possible that this limit" " is exceeded in certain circumstances, see the `minislots' setting and the" " `/grant' command." }, { "sudp_policy", 1, "", "Set the policy for sending or receiving encrypted UDP search results. When" " set to `disabled', all UDP search results will be sent and received in" " plain text. Set this to `allow' to let ncdc reply with encrypted search" " results if the other client requested it. `prefer' will also cause ncdc" " itself to request encryption.\n\n" "Note that, regardless of this setting, encrypted UDP search results are only" " used on ADCS hubs. They will never be sent on NMDC or non-TLS ADC hubs. Also" " note that, even if you set this to `prefer', encryption is still only used" " when the client on the other side of the connection also supports it." }, { "tls_policy", 1, "", "Set the policy for secure client-to-client connections. Setting this to" " `disabled' disables TLS support for client connections, but still allows" " you to connect to TLS-enabled hubs. `allow' will allow the use of TLS if" " the other client requests this, but ncdc itself will not request TLS when" " connecting to others, `prefer' tells ncdc to request TLS when connecting to" " others. Setting this to 'force' will disallow non-TLS connections and also" " requires that the hub connection itself is TLS.\n\n" "The use of TLS for client connections usually results in less optimal" " performance when uploading and downloading, but is quite effective at" " avoiding protocol-specific traffic shaping that some ISPs may do." }, { "tls_priority", 0, "", "Set the GnuTLS priority string used for all TLS-enabled connections. See the" " \"Priority strings\" section in the GnuTLS manual for details on what this" " does and how it works. Currently it is not possible to set a different" " priority string for different types of connections (e.g. hub or" " incoming/outgoing client connections)." }, { "ui_time_format", 0, "", "The format of the time displayed in the lower-left of the screen. Set `-' to" " not display a time at all. The string is passed to the Glib" " g_date_time_format() function, which accepts roughly the same formats as" " strftime(). Check out the strftime(3) man page or the Glib documentation" " for more information. Note that this setting does not influence the" " date/time format used in other places, such as the chat window or log" " files." }, { "upload_rate", 0, "", "Maximum combined transfer rate of all uploads. See the `download_rate'" " setting for more information on rate limiting. Note that this setting also" " overrides any `connection' setting." }, { NULL } }; #endif // DOC_SET #ifdef DOC_KEY // There is some redundancy here with the actual keys used in the switch() // statements of each window/widget. But the methods for synchronizing the keys // aren't going to worth it I'm afraid. // Don't want to redirect people to the global keybindings for these four lines. #define LISTING_KEYS \ "Up/Down Select one item up/down.\n"\ "k/j Select one item up/down.\n"\ "PgUp/PgDown Select one page of items up/down.\n"\ "End/Home Select last/first item in the list.\n" #define SEARCH_KEYS \ "/ Start incremental regex search (press Return to stop editing).\n"\ ",/. Search next / previous.\n" typedef struct doc_key_t { char const *sect, *title, *desc; } doc_key_t; static const doc_key_t doc_keys[] = { { "global", "Global key bindings", "Alt+j Open previous tab.\n" "Alt+k Open next tab.\n" "Alt+h Move current tab left.\n" "Alt+l Move current tab right.\n" "Alt+a Move tab with recent activity.\n" "Alt+ Open tab with number .\n" "Alt+c Close current tab.\n" "Alt+n Open the connections tab.\n" "Alt+q Open the download queue tab.\n" "Alt+o Open own file list.\n" "Alt+r Refresh file list.\n" "\n" "Keys for tabs with a log window:\n" "Ctrl+l Clear current log window.\n" "PgUp Scroll the log backward.\n" "PgDown Scroll the log forward.\n" "\n" "Keys for tabs with a text input line:\n" "Left/Right Move cursor one character left or right.\n" "End/Home Move cursor to the end / start of the line.\n" "Up/Down Scroll through the command history.\n" "Tab Auto-complete current command, nick or argument.\n" "Alt+b Move cursor one word backward.\n" "Alt+f Move cursor one word forward.\n" "Backspace Delete character before cursor.\n" "Delete Delete character under cursor.\n" "Ctrl+w Delete to previous space.\n" "Alt+d Delete to next space.\n" "Ctrl+k Delete everything after cursor.\n" "Ctrl+u Delete entire line." }, { "browse", "File browser", LISTING_KEYS SEARCH_KEYS "Right/l Open selected directory.\n" "Left/h Open parent directory.\n" "t Toggle sorting directories before files.\n" "s Order by file size.\n" "n Order by file name.\n" "d Add selected file/directory to the download queue.\n" "m Match selected item with the download queue.\n" "M Match entire file list with the download queue.\n" "a Search for alternative download sources." }, { "connections", "Connection list", LISTING_KEYS "d Disconnect selected connection.\n" "i/Return Toggle information box.\n" "f Find user in user list.\n" "m Send a PM to the selected user.\n" "q Find file in download queue.\n" "b/B Browse the selected user's list, B to force a redownload." }, { "queue", "Download queue", LISTING_KEYS "K/J Select one user up/down.\n" "f Find user in user list.\n" "c Find connection in the connection list.\n" "a Search for alternative download sources.\n" "d Remove selected file from the queue.\n" "+/- Increase/decrease priority.\n" "i/Return Toggle user list.\n" "r Remove selected user for this file.\n" "R Remove selected user from all files in the download queue.\n" "x Clear error state for the selected user for this file.\n" "X Clear error state for the selected user for all files.\n" "\n" "Note: when an item in the queue has `ERR' indicated in the\n" "priority column, you have two choices: You can remove the\n" "item from the queue using `d', or attempt to continue the\n" "download by increasing its priority using `+'." }, { "search", "Search results tab", LISTING_KEYS "f Find user in user list.\n" "b/B Browse the selected user's list, B to force a redownload.\n" "d Add selected file to the download queue.\n" "h Toggle hub column visibility.\n" "u Order by username.\n" "s Order by file size.\n" "l Order by free slots.\n" "n Order by file name.\n" "m Match selected item with the download queue.\n" "M Match all search results with the download queue.\n" "q Match selected users' list with the download queue.\n" "Q Match all matched users' lists with the download queue.\n" "a Search for alternative download sources." }, { "userlist", "User list tab", LISTING_KEYS SEARCH_KEYS "o Toggle sorting OPs before others.\n" "s/S Order by share size.\n" "u/U Order by username.\n" "t/T Toggle visibility / order by tag column.\n" "e/E Toggle visibility / order by email column.\n" "c/C Toggle visibility / order by connection column.\n" "p/P Toggle visibility / order by IP column.\n" "i/Return Toggle information box.\n" "m Send a PM to the selected user.\n" "g Grant a slot to the selected user.\n" "b/B Browse the selected users' list, B to force a redownload.\n" "q Match selected users' list with the download queue." }, { NULL } }; #undef LISTING_KEYS #undef SEARCH_KEYS #endif // DOC_KEY ncdc-1.23.1/src/uit_search.c0000644000175000017500000003243014245144611012513 00000000000000/* ncdc - NCurses Direct Connect client Copyright (c) 2011-2022 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "ncdc.h" #include "uit_search.h" ui_tab_type_t uit_search[1]; typedef struct tab_t { ui_tab_t tab; ui_listing_t *list; search_q_t *q; char *hubname; time_t age; int order; gboolean reverse : 1; gboolean hide_hub : 1; } tab_t; // Columns to sort on #define SORT_USER 0 #define SORT_SIZE 1 #define SORT_SLOTS 2 #define SORT_FILE 3 // Note: The ordering of the results partly depends on whether the user is // online or not (i.e. whether we know its name and hub). However, we do not // get notified when a user or hub changes state and can therefore not keep the // ordering of the list correct. This isn't a huge problem, though. // Compares users, uses a hub comparison as fallback static int cmp_user(guint64 ua, guint64 ub) { hub_user_t *a = g_hash_table_lookup(hub_uids, &ua); hub_user_t *b = g_hash_table_lookup(hub_uids, &ub); int o = !a && !b ? (ua > ub ? 1 : ua < ub ? -1 : 0) : a && !b ? 1 : !a && b ? -1 : g_utf8_collate(a->name, b->name); if(!o && a && b) return g_utf8_collate(a->hub->tab->name, b->hub->tab->name); return o; } static int cmp_file(const char *fa, const char *fb) { const char *a = strrchr(fa, '/'); const char *b = strrchr(fb, '/'); return g_utf8_collate(a?a+1:fa, b?b+1:fb); } static gint sort_func(gconstpointer da, gconstpointer db, gpointer dat) { const search_r_t *a = da; const search_r_t *b = db; tab_t *t = dat; int p = t->order; /* Sort columns and their alternatives: * USER: user/hub -> file name -> file size * SIZE: size -> TTH -> file name * SLOTS: slots -> user/hub -> file name * FILE: file name -> size -> TTH */ #define CMP_USER cmp_user(a->uid, b->uid) #define CMP_SIZE (a->size == b->size ? 0 : (a->size == G_MAXUINT64 ? 0 : a->size) > (b->size == G_MAXUINT64 ? 0 : b->size) ? 1 : -1) #define CMP_SLOTS (a->slots > b->slots ? 1 : a->slots < b->slots ? -1 : 0) #define CMP_FILE cmp_file(a->file, b->file) #define CMP_TTH memcmp(a->tth, b->tth, 24) // Try 1 int o = p == SORT_USER ? CMP_USER : p == SORT_SIZE ? CMP_SIZE : p == SORT_SLOTS ? CMP_SLOTS : CMP_FILE; // Try 2 if(!o) o = p == SORT_USER ? CMP_FILE : p == SORT_SIZE ? CMP_TTH : p == SORT_SLOTS ? CMP_USER : CMP_SIZE; // Try 3 if(!o) o = p == SORT_USER ? CMP_SIZE : p == SORT_SIZE ? CMP_FILE : p == SORT_SLOTS ? CMP_FILE : CMP_TTH; #undef CMP_USER #undef CMP_SIZE #undef CMP_SLOTS #undef CMP_FILE #undef CMP_TTH return t->reverse ? -o : o; } // Callback from search.c when we have a new result. static void result(search_r_t *r, void *dat) { tab_t *t = dat; g_sequence_insert_sorted(t->list->list, search_r_copy(r), sort_func, t); ui_listing_inserted(t->list); ui_tab_incprio((ui_tab_t *)t, UIP_LOW); } static const char *search_r_get_file(GSequenceIter *iter) { search_r_t *r = g_sequence_get(iter); return r->file; } // Performs a search and opens a new tab for the results. // May return NULL on error, behaves similarly to search_add() w.r.t *err. // Ownership of q is passed to the tab, and will be freed on error or close. ui_tab_t *uit_search_create(hub_t *hub, search_q_t *q, GError **err) { tab_t *t = g_new0(tab_t, 1); t->tab.type = uit_search; t->q = q; t->hubname = hub ? g_strdup(hub->tab->name) : NULL; t->hide_hub = hub ? TRUE : FALSE; t->order = SORT_FILE; time(&t->age); // Do the search q->cb_dat = t; q->cb = result; if(!search_add(hub, q, err)) { g_free(t); return NULL; } // figure out a suitable tab name if(q->type == 9) { t->tab.name = g_new0(char, 41); t->tab.name[0] = '?'; base32_encode(q->tth, t->tab.name+1); } else { char *s = g_strjoinv(" ", q->query); t->tab.name = g_strdup_printf("?%s", s); g_free(s); } if(strlen(t->tab.name) > 15) t->tab.name[15] = 0; while(t->tab.name[strlen(t->tab.name)-1] == ' ') t->tab.name[strlen(t->tab.name)-1] = 0; t->list = ui_listing_create(g_sequence_new(search_r_free), NULL, t, search_r_get_file); return (ui_tab_t *)t; } static void t_close(ui_tab_t *tab) { tab_t *t = (tab_t *)tab; search_remove(t->q); g_sequence_free(t->list->list); ui_listing_free(t->list); ui_tab_remove(tab); g_free(t->hubname); g_free(t->tab.name); g_free(t); } static char *t_title(ui_tab_t *tab) { tab_t *t = (tab_t *)tab; char *sq = search_command(t->q, !!t->hubname); char *r = t->hubname ? g_strdup_printf("Results on %s for: %s", t->hubname, sq) : g_strdup_printf("Results for: %s", sq); g_free(sq); return r; } static void draw_row(ui_listing_t *list, GSequenceIter *iter, int row, void *dat) { search_r_t *r = g_sequence_get(iter); tab_t *t = dat; attron(iter == list->sel ? UIC(list_select) : UIC(list_default)); mvhline(row, 0, ' ', wincols); if(iter == list->sel) mvaddstr(row, 0, ">"); hub_user_t *u = g_hash_table_lookup(hub_uids, &r->uid); if(u) { mvaddnstr(row, 2, u->name, str_offset_from_columns(u->name, 19)); if(!t->hide_hub) mvaddnstr(row, 22, u->hub->tab->name, str_offset_from_columns(u->hub->tab->name, 13)); } else mvprintw(row, 2, "ID:%016"G_GINT64_MODIFIER"x%s", r->uid, !t->hide_hub ? " (offline)" : ""); int i = t->hide_hub ? 22 : 36; if(r->size == G_MAXUINT64) mvaddstr(row, i, " DIR"); else mvaddstr(row, i, str_formatsize(r->size)); mvprintw(row, i+12, "%3d/", r->slots); if(u) mvprintw(row, i+16, "%3d", u->slots); else mvaddstr(row, i+16, " -"); mvaddch(row, i+20, ui_file_flag(r->tth)); char *fn = strrchr(r->file, '/'); if(fn) fn++; else fn = r->file; mvaddnstr(row, i+22, fn, str_offset_from_columns(fn, wincols-i-22)); attroff(iter == list->sel ? UIC(list_select) : UIC(list_default)); } static void t_draw(ui_tab_t *tab) { tab_t *t = (tab_t *)tab; attron(UIC(list_header)); mvhline(1, 0, ' ', wincols); mvaddstr(1, 2, "User"); if(!t->hide_hub) mvaddstr(1, 22, "Hub"); int i = t->hide_hub ? 22 : 36; mvaddstr(1, i, "Size"); mvaddstr(1, i+12, "Slots"); mvaddstr(1, i+20, "F"); // short for "Flags" mvaddstr(1, i+22, "File"); attroff(UIC(list_header)); int bottom = winrows-4; ui_cursor_t cursor; int pos = ui_listing_draw(t->list, 2, bottom-1, &cursor, draw_row); search_r_t *sel = g_sequence_iter_is_end(t->list->sel) ? NULL : g_sequence_get(t->list->sel); // footer attron(UIC(separator)); mvhline(bottom, 0, ' ', wincols); if(!sel) mvaddstr(bottom, 0, "Nothing selected."); else if(sel->size == G_MAXUINT64) mvaddstr(bottom, 0, "Directory."); else { char tth[40] = {}; base32_encode(sel->tth, tth); mvprintw(bottom, 0, "%s (%s bytes)", tth, str_fullsize(sel->size)); } const char *age = str_formatinterval(time(NULL)-t->age); mvprintw(bottom, wincols-25-strlen(age), "%5d results in %s - %3d%%", g_sequence_get_length(t->list->list), age, pos); attroff(UIC(separator)); if(sel) mvaddnstr(bottom+1, 3, sel->file, str_offset_from_columns(sel->file, wincols-3)); move(cursor.y, cursor.x); } static void t_key(ui_tab_t *tab, guint64 key) { tab_t *t = (tab_t *)tab; if(ui_listing_key(t->list, key, (winrows-4)/2)) return; search_r_t *sel = g_sequence_iter_is_end(t->list->sel) ? NULL : g_sequence_get(t->list->sel); gboolean sort = FALSE; switch(key) { case INPT_CHAR('?'): uit_main_keys("search"); break; case INPT_CHAR('f'): // f - find user if(!sel) ui_m(NULL, 0, "Nothing selected."); else { hub_user_t *u = g_hash_table_lookup(hub_uids, &sel->uid); if(!u) ui_m(NULL, 0, "User is not online."); else uit_userlist_open(u->hub, u->uid, NULL, FALSE); } break; case INPT_CHAR('b'): // b - /browse userlist case INPT_CHAR('B'): // B - /browse -f userlist if(!sel) ui_m(NULL, 0, "Nothing selected."); else { hub_user_t *u = g_hash_table_lookup(hub_uids, &sel->uid); if(!u) ui_m(NULL, 0, "User is not online."); else uit_fl_queue(u->uid, key == INPT_CHAR('B'), sel->file, tab, TRUE, FALSE); } break; case INPT_CHAR('d'): // d - download file if(!sel) ui_m(NULL, 0, "Nothing selected."); else if(sel->size == G_MAXUINT64) ui_m(NULL, 0, "Can't download directories from the search. Use 'b' to browse the file list instead."); else dl_queue_add_res(sel); break; case INPT_CHAR('m'): // m - match selected item with queue if(!sel) ui_m(NULL, 0, "Nothing selected."); else if(sel->size == G_MAXUINT64) ui_m(NULL, 0, "Can't download directories from the search. Use 'b' to browse the file list instead."); else { int r = dl_queue_matchfile(sel->uid, sel->tth); ui_m(NULL, 0, r < 0 ? "File not in the queue." : r == 0 ? "User already in the queue." : "Added user to queue for the selected file."); } break; case INPT_CHAR('M'):;// M - match all results with queue int n = 0, a = 0; GSequenceIter *i = g_sequence_get_begin_iter(t->list->list); for(; !g_sequence_iter_is_end(i); i=g_sequence_iter_next(i)) { search_r_t *r = g_sequence_get(i); int v = dl_queue_matchfile(r->uid, r->tth); if(v >= 0) n++; if(v == 1) a++; } ui_mf(NULL, 0, "Matched %d files, %d new.", n, a); break; case INPT_CHAR('q'): // q - download filelist and match queue for selected user if(!sel) ui_m(NULL, 0, "Nothing selected."); else uit_fl_queue(sel->uid, FALSE, NULL, NULL, FALSE, TRUE); break; case INPT_CHAR('Q'):{// Q - download filelist and match queue for all results GSequenceIter *i = g_sequence_get_begin_iter(t->list->list); // Use a hash table to avoid checking the same filelist more than once GHashTable *uids = g_hash_table_new(g_int64_hash, g_int64_equal); for(; !g_sequence_iter_is_end(i); i=g_sequence_iter_next(i)) { search_r_t *r = g_sequence_get(i); // In the case that this wasn't a TTH search, check whether this search // result matches the queue before checking the file list. if(t->q->type == 9 || dl_queue_matchfile(r->uid, r->tth) >= 0) g_hash_table_insert(uids, &r->uid, (void *)1); } GHashTableIter iter; g_hash_table_iter_init(&iter, uids); guint64 *uid; while(g_hash_table_iter_next(&iter, (gpointer *)&uid, NULL)) uit_fl_queue(*uid, FALSE, NULL, NULL, FALSE, TRUE); ui_mf(NULL, 0, "Matching %d file lists...", g_hash_table_size(uids)); g_hash_table_unref(uids); } break; case INPT_CHAR('a'): // a - search for alternative sources if(!sel) ui_m(NULL, 0, "Nothing selected."); else if(sel->size == G_MAXUINT64) ui_m(NULL, 0, "Can't look for alternative sources for directories."); else uit_search_open_tth(sel->tth, tab); break; case INPT_CHAR('h'): // h - show/hide hub column t->hide_hub = !t->hide_hub; break; case INPT_CHAR('u'): // u - sort on username t->reverse = t->order == SORT_USER ? !t->reverse : FALSE; t->order = SORT_USER; sort = TRUE; break; case INPT_CHAR('s'): // s - sort on size t->reverse = t->order == SORT_SIZE ? !t->reverse : FALSE; t->order = SORT_SIZE; sort = TRUE; break; case INPT_CHAR('l'): // l - sort on slots t->reverse = t->order == SORT_SLOTS ? !t->reverse : FALSE; t->order = SORT_SLOTS; sort = TRUE; break; case INPT_CHAR('n'): // n - sort on filename t->reverse = t->order == SORT_FILE ? !t->reverse : FALSE; t->order = SORT_FILE; sort = TRUE; break; } if(sort) { g_sequence_sort(t->list->list, sort_func, t); ui_listing_sorted(t->list); ui_mf(NULL, 0, "Ordering by %s (%s)", t->order == SORT_USER ? "user name" : t->order == SORT_SIZE ? "file size" : t->order == SORT_SLOTS ? "free slots" : "filename", t->reverse ? "descending" : "ascending"); } } // Open a new tab for a TTH search on all hubs, or write a message to ui_m() on // error. void uit_search_open_tth(const char *tth, ui_tab_t *parent) { GError *err = NULL; ui_tab_t *rtab = uit_search_create(NULL, search_q_new_tth(tth), &err); if(err) { ui_mf(NULL, 0, "%s%s", rtab ? "Warning: " : "", err->message); g_error_free(err); } if(rtab) ui_tab_open(rtab, TRUE, parent); } ui_tab_type_t uit_search[1] = { { t_draw, t_title, t_key, t_close } }; ncdc-1.23.1/src/vars.c0000644000175000017500000010303014245144622011335 00000000000000/* ncdc - NCurses Direct Connect client Copyright (c) 2011-2022 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "ncdc.h" #include "vars.h" // Internal (low-level) utility functions #define bool_raw(v) (!v ? FALSE : strcmp(v, "true") == 0 ? TRUE : FALSE) #define int_raw(v) (!v ? 0 : g_ascii_strtoll(v, NULL, 0)) static gboolean bool_parse(const char *val, GError **err) { if(strcmp(val, "1") == 0 || strcmp(val, "t") == 0 || strcmp(val, "y") == 0 || strcmp(val, "true") == 0 || strcmp(val, "yes") == 0 || strcmp(val, "on") == 0) return TRUE; if(strcmp(val, "0") == 0 || strcmp(val, "f") == 0 || strcmp(val, "n") == 0 || strcmp(val, "false") == 0 || strcmp(val, "no") == 0 || strcmp(val, "off") == 0) return FALSE; g_set_error_literal(err, 1, 0, "Unrecognized boolean value."); return FALSE; } static char *f_id(const char *val) { return g_strdup(val); } #define f_bool f_id #define f_int f_id static char *f_interval(const char *val) { return g_strdup(str_formatinterval(int_raw(val))); } static char *f_speed(const char *val) { return g_strdup_printf("%s/s", str_formatsize(int_raw(val))); } static char *p_id(const char *val, GError **err) { return g_strdup(val); } static char *p_bool(const char *val, GError **err) { GError *e = NULL; gboolean b = bool_parse(val, &e); if(e) { g_propagate_error(err, e); return NULL; } return g_strdup(b ? "true" : "false"); } static char *p_int(const char *val, GError **err) { long v = strtol(val, NULL, 10); if((!v && errno == EINVAL) || v < INT_MIN || v > INT_MAX || v < 0) { g_set_error_literal(err, 1, 0, "Invalid number."); return NULL; } return g_strdup_printf("%d", (int)v); } static char *p_int_ge1(const char *val, GError **err) { char *r = p_int(val, err); if(r && int_raw(r) < 1) { g_set_error_literal(err, 1, 0, "Invalid value."); g_free(r); return NULL; } return r; } static char *p_int_range(const char *val, int min, int max, const char *msg, GError **err) { char *r = p_int(val, err); if(r && (int_raw(r) < min || int_raw(r) > max)) { g_set_error_literal(err, 1, 0, msg); g_free(r); return NULL; } return r; } static char *p_interval(const char *val, GError **err) { int n = str_parseinterval(val); if(n < 0) { g_set_error_literal(err, 1, 0, "Invalid interval."); return NULL; } return g_strdup_printf("%d", n); } static char *p_ip(const char *val, GError **err) { struct in_addr i4 = ip4_any; struct in6_addr i6 = ip6_any; char *sep = strchr(val, ','); if(sep) *sep = 0; if(ip4_isvalid(val)) i4 = ip4_pack(val); else if(ip6_isvalid(val)) i6 = ip6_pack(val); else { g_set_error_literal(err, 1, 0, "Invalid IP."); return NULL; } if(sep) { *(sep++) = ','; while(*sep == ' ') sep++; if(ip4_isany(i4) && ip4_isvalid(sep)) i4 = ip4_pack(sep); else if(ip6_isany(i6) && ip6_isvalid(sep)) i6 = ip6_pack(sep); else { g_set_error_literal(err, 1, 0, "Invalid IP."); return NULL; } } return g_strdup_printf("%s,%s", ip4_unpack(i4), ip6_unpack(i6)); } static char *p_active_ip(const char *val, GError **err) { if(strcmp(val, "local") == 0) return g_strdup(val); return p_ip(val, err); } static char *p_regex(const char *val, GError **err) { GRegex *r = g_regex_new(val, 0, 0, err); if(!r) return NULL; else { g_regex_unref(r); return g_strdup(val); } } static char *p_speed(const char *val, GError **err) { char *v = strlen(val) > 3 && strcmp(val+strlen(val)-2, "/s") == 0 ? g_strndup(val, strlen(val)-2) : g_strdup(val); guint64 size = str_parsesize(v); g_free(v); if(size == G_MAXUINT64) { g_set_error_literal(err, 1, 0, "Invalid speed."); return NULL; } return g_strdup_printf("%"G_GUINT64_FORMAT, size); } // Only suggests "true" or "false" regardless of the input. There are only two // states anyway, and one would want to switch between those two without any // hassle. static void su_bool(const char *old, const char *val, char **sug) { gboolean f = !(val[0] == 0 || val[0] == '1' || val[0] == 't' || val[0] == 'y' || val[0] == 'o'); sug[f ? 1 : 0] = g_strdup("true"); sug[f ? 0 : 1] = g_strdup("false"); } static void su_old(const char *old, const char *val, char **sug) { if(old && strncmp(old, val, strlen(val)) == 0) sug[0] = g_strdup(old); } static void su_path(const char *old, const char *val, char **sug) { path_suggest(val, sug); } static gboolean s_hubinfo(guint64 hub, const char *key, const char *val, GError **err) { db_vars_set(hub, key, val); hub_global_nfochange(); return TRUE; } static gboolean s_active_conf(guint64 hub, const char *key, const char *val, GError **err) { db_vars_set(hub, key, val); listen_refresh(); hub_global_nfochange(); return TRUE; } typedef struct flag_option_t { int num; char *name; } flag_option_t; static int flags_raw(flag_option_t *ops, gboolean multi, const char *val, GError **err) { char **args = g_strsplit(val, ",", 0); int r = 0, n = 0; char **arg = args; for(; arg && *arg; arg++) { g_strstrip(*arg); if(!**arg) continue; flag_option_t *o = ops; for(; o->num; o++) { if(strcmp(o->name, *arg) == 0) { n++; r |= o->num; break; } } if(!o->num) { g_set_error(err, 1, 0, "Unknown flag: %s", *arg); g_strfreev(args); return 0; } } g_strfreev(args); if((!multi && n > 1) || n < 1) { g_set_error_literal(err, 1, 0, n > 1 ? "Too many flags." : "Not enough flags given."); return 0; } return r; } static char *flags_fmt(flag_option_t *o, int val) { GString *s = g_string_new(""); for(; o->num; o++) { if(val & o->num) { if(s->str[0]) g_string_append_c(s, ','); g_string_append(s, o->name); } } return g_string_free(s, FALSE); } static void flags_sug(flag_option_t *o, const char *val, char **sug) { char *v = g_strdup(val); char *attr = strrchr(v, ','); if(attr) *(attr++) = 0; else attr = v; g_strstrip(attr); int i = 0, len = strlen(attr); for(; o->num && i<20; o++) if(strncmp(attr, o->name, len) == 0) sug[i++] = g_strdup(o->name); if(i && attr != v) strv_prefix(sug, v, ",", NULL); g_free(v); } // active_*port static char *p_active_port(const char *val, GError **err) { return p_int_range(val, 0, 65535, "Port number must be between 1 and 65535.", err); } static char *g_active_udp(guint64 hub, const char *key) { char *r = db_vars_get(hub, key); return r ? r : db_vars_get(hub, "active_port"); } // autorefresh static char *f_autorefresh(const char *val) { int n = int_raw(val); if(!n) return g_strconcat(str_formatinterval(n), " (disabled)", NULL); return f_interval(val); } static char *p_autorefresh(const char *val, GError **err) { char *raw = p_interval(val, err); if(raw && raw[0] != '0' && int_raw(raw) < 600) { g_set_error_literal(err, 1, 0, "Interval between automatic refreshes should be at least 10 minutes."); g_free(raw); return NULL; } return raw; } // backlog static char *f_backlog(const char *var) { return g_strdup(strcmp(var, "0") == 0 ? "0 (disabled)" : var); } static char *p_backlog(const char *val, GError **err) { return p_int_range(val, 0, LOGWIN_BUF-1, "Maximum value is "G_STRINGIFY(LOGWIN_BUF-1)".", err); } // download_segment static char *f_download_segment(const char *var) { return g_strdup(strcmp(var, "0") == 0 ? "0 (disable segmented downloading)" : str_formatsize(int_raw(var))); } static char *p_download_segment(const char *val, GError **err) { guint64 size = str_parsesize(val); if(size == G_MAXUINT64) { g_set_error_literal(err, 1, 0, "Invalid speed."); return NULL; } if(size && size < DLFILE_CHUNKSIZE) size = DLFILE_CHUNKSIZE; return g_strdup_printf("%"G_GUINT64_FORMAT, size); } // nick static char *p_nick(const char *val, GError **err) { if(strlen(val) > 32) { g_set_error_literal(err, 1, 0, "Too long nick name."); return NULL; } if(strlen(val) < 1) { g_set_error_literal(err, 1, 0, "Too short nick name."); return NULL; } int i; for(i=strlen(val)-1; i>=0; i--) if(val[i] == '$' || val[i] == '|' || val[i] == ' ' || val[i] == '<' || val[i] == '>') break; if(i >= 0) { g_set_error_literal(err, 1, 0, "Invalid character in nick name."); return NULL; } ui_m(NULL, 0, "Your new nick will be used for new hub connections."); return g_strdup(val); } static gboolean s_nick(guint64 hub, const char *key, const char *val, GError **err) { // TODO: nick change without reconnect on ADC? if(!val && !hub) { g_set_error_literal(err, 1, 0, "May not be unset."); return FALSE; } db_vars_set(hub, key, val); return TRUE; } static char *i_nick() { // make sure a nick is set if(!db_vars_get(0, "nick")) { char *nick = g_strdup_printf("ncdc_%d", g_random_int_range(1, 9999)); db_vars_set(0, "nick", nick); g_free(nick); } return "ncdc"; } // cid / pid static char *i_cid_pid() { if(db_vars_get(0, "cid") && db_vars_get(0, "pid")) return NULL; // Generate a random PID char pid[24]; g_warn_if_fail(gnutls_rnd(GNUTLS_RND_RANDOM, pid, 24) == 0); // now hash the PID so we have our CID char cid[24]; tiger_ctx_t t; tiger_init(&t); tiger_update(&t, pid, 24); tiger_final(&t, cid); // encode and save char enc[40] = {}; base32_encode(pid, enc); db_vars_set(0, "pid", enc); base32_encode(cid, enc); db_vars_set(0, "cid", enc); return NULL; } // color_* static char *p_color(const char *val, GError **err) { short fg, bg; int x; if(!ui_color_str_parse(val, &fg, &bg, &x, err)) return NULL; return g_strdup(ui_color_str_gen(fg, bg, x)); } static void su_color(const char *old, const char *v, char **sug) { // TODO: use flags_sug()? char *val = g_strdup(v); char *attr = strrchr(val, ','); if(attr) *(attr++) = 0; else attr = val; g_strstrip(attr); ui_attr_t *a = ui_attr_names; int i = 0, len = strlen(attr); for(; a->name[0] && i<20; a++) if(strncmp(attr, a->name, len) == 0) sug[i++] = g_strdup(a->name); if(i && attr != val) strv_prefix(sug, val, ",", NULL); g_free(val); } static gboolean s_color(guint64 hub, const char *key, const char *val, GError **err) { db_vars_set(hub, key, val); ui_colors_update(); return TRUE; } // download_dir & incoming_dir static char *i_dl_inc_dir(gboolean dl) { return g_build_filename(db_dir, dl ? "dl" : "inc", NULL); } static gboolean s_dl_inc_dir(guint64 hub, const char *key, const char *val, GError **err) { gboolean dl = strcmp(key, "download_dir") == 0 ? TRUE : FALSE; // Don't allow changes to incoming_dir when the download queue isn't empty if(!dl && g_hash_table_size(dl_queue) > 0) { g_set_error_literal(err, 1, 0, "Can't change the incoming directory unless the download queue is empty."); return FALSE; } char *tmp = val ? g_strdup(val) : i_dl_inc_dir(dl); char nval[strlen(tmp)+1]; strcpy(nval, tmp); g_free(tmp); // make sure it exists if(g_mkdir_with_parents(nval, 0777)) { g_set_error(err, 1, 0, "Error creating the directory: %s", g_strerror(errno)); return FALSE; } // test whether they are on the same filesystem struct stat a, b; char *bd = var_get(0, dl ? VAR_incoming_dir : VAR_download_dir); if(stat(bd, &b) == 0) { if(stat(nval, &a) < 0) { g_set_error(err, 1, 0, "Error stat'ing %s: %s", nval, g_strerror(errno)); return FALSE; } if(a.st_dev != b.st_dev) ui_m(NULL, 0, "WARNING: The download directory is not on the same filesystem as the incoming" " directory. This may cause the program to hang when downloading large files."); } db_vars_set(hub, key, val); return TRUE; } // download_slots static gboolean s_download_slots(guint64 hub, const char *key, const char *val, GError **err) { int old = var_get_int(hub, VAR_download_slots); db_vars_set(hub, key, val); if(int_raw(val) > old) dl_queue_start(); return TRUE; } // encoding static char *p_encoding(const char *val, GError **err) { if(!str_convert_check(val, err)) { if(err && !*err) g_set_error_literal(err, 1, 0, "Invalid encoding."); return NULL; } return g_strdup(val); } static void su_encoding(const char *old, const char *val, char **sug) { static flag_option_t encoding_flags[] = { {1,"CP1250"}, {1,"CP1251"}, {1,"CP1252"}, {1,"ISO-2022-JP"}, {1,"ISO-8859-2"}, {1,"ISO-8859-7"}, {1,"ISO-8859-8"}, {1,"ISO-8859-9"}, {1,"KOI8-R"}, {1,"LATIN1"}, {1,"SJIS"}, {1,"UTF-8"}, {1,"WINDOWS-1250"}, {1,"WINDOWS-1251"}, {1,"WINDOWS-1252"}, {0} }; flags_sug(encoding_flags, val, sug); } // email / description / connection static char *p_connection(const char *val, GError **err) { if(!str_connection_to_speed(val)) ui_mf(NULL, 0, "Couldn't convert `%s' to bytes/second, won't broadcast upload speed on ADC. See `/help set connection' for more information.", val); return g_strdup(val); } // flush_file_cache // Special interface to allow quick and threaded access to the current value #if INTERFACE #define var_ffc_get() g_atomic_int_get(&var_ffc) #define var_ffc_set(v) g_atomic_int_set(&var_ffc, v) #endif int var_ffc = 0; #if INTERFACE #define VAR_FFC_NONE 1 #define VAR_FFC_DOWNLOAD 2 #define VAR_FFC_UPLOAD 4 #define VAR_FFC_HASH 8 #endif static flag_option_t var_ffc_ops[] = { { VAR_FFC_NONE, "none" }, { VAR_FFC_DOWNLOAD, "download" }, { VAR_FFC_UPLOAD, "upload" }, { VAR_FFC_HASH, "hash" }, { 0 } }; static char *f_ffc(const char *raw) { #if HAVE_POSIX_FADVISE return flags_fmt(var_ffc_ops, int_raw(raw)); #else return g_strdup("none (not supported)"); #endif } static char *p_ffc(const char *val, GError **err) { int n = flags_raw(var_ffc_ops, TRUE, val, err); if(n & VAR_FFC_NONE) n = VAR_FFC_NONE; return n ? g_strdup_printf("%d", n) : NULL; } static void su_ffc(const char *old, const char *val, char **sug) { flags_sug(var_ffc_ops, val, sug); } static char *g_ffc(guint64 hub, const char *key) { #ifndef HAVE_POSIX_FADVISE return G_STRINGIFY(VAR_FFC_NONE); #else char *r = db_vars_get(hub, key); if(!r) return NULL; static char num[4]; // true/false check is for compatibility with old versions g_snprintf(num, 4, "%d", strcmp(r, "true") == 0 ? VAR_FFC_UPLOAD | VAR_FFC_HASH : strcmp(r, "false") == 0 ? VAR_FFC_NONE : flags_raw(var_ffc_ops, TRUE, r, NULL) ); return num; #endif } static gboolean s_ffc(guint64 hub, const char *key, const char *val, GError **err) { #ifndef HAVE_POSIX_FADVISE g_set_error(err, 1, 0, "This option can't be modified: %s.", "posix_fadvise() not supported"); return FALSE; #else char *r = flags_fmt(var_ffc_ops, int_raw(val)); db_vars_set(hub, key, r[0] ? r : NULL); g_free(r); var_ffc_set(int_raw(val)); return TRUE; #endif } static char *i_ffc() { char *r = g_ffc(0, "flush_file_cache"); var_ffc_set(int_raw(r)); return G_STRINGIFY(VAR_FFC_NONE); } // geoip static gboolean s_geoip_cc(guint64 hub, const char *key, const char *val, GError **err) { #ifdef USE_GEOIP db_vars_set(hub, key, val); geoip_reinit(); return TRUE; #else g_set_error(err, 1, 0, "This option can't be modified: %s.", "Ncdc has not been compiled with GeoIP support"); return FALSE; #endif } // hubname static char *p_hubname(const char *val, GError **err) { if(val[0] == '#') val++; char *g = g_strdup_printf("#%s", val); if(!str_is_valid_hubname(g+1)) { g_set_error_literal(err, 1, 0, "Illegal characters or too long."); g_free(g); return NULL; } else if(db_vars_hubid(g)) { g_set_error_literal(err, 1, 0, "Name already used."); g_free(g); return NULL; } return g; } static gboolean s_hubname(guint64 hub, const char *key, const char *val, GError **err) { if(!val) { g_set_error_literal(err, 1, 0, "May not be unset."); return FALSE; } db_vars_set(hub, key, val); GList *n; for(n=ui_tabs; n; n=n->next) { ui_tab_t *t = n->data; if(t->type == uit_hub && t->hub->id == hub) { g_free(t->name); t->name = g_strdup(val); } } return TRUE; } // log_debug gboolean var_log_debug = TRUE; static gboolean s_log_debug(guint64 hub, const char *key, const char *val, GError **err) { db_vars_set(hub, key, val); var_log_debug = bool_raw(val); return TRUE; } static char *i_log_debug() { char *r = db_vars_get(0, "log_debug"); var_log_debug = bool_raw(r); return "false"; } // minislot_size static char *p_minislot_size(const char *val, GError **err) { char *r = p_int(val, err); int n = r ? int_raw(r) : 0; g_free(r); if(r && n < 64) { g_set_error_literal(err, 1, 0, "Minislot size must be at least 64 KiB."); return NULL; } return r ? g_strdup_printf("%d", MIN(G_MAXINT, n*1024)) : NULL; } static char *f_minislot_size(const char *val) { return g_strdup_printf("%d KiB", (int)int_raw(val)/1024); } // password static char *f_password(const char *val) { char *r = g_malloc(strlen(val)+1); memset(r, '*', strlen(val)); r[strlen(val)-1] = 0; return r; } static gboolean s_password(guint64 hub, const char *key, const char *val, GError **err) { db_vars_set(hub, key, val); // send password to hub hub_t *h = hub_global_byid(hub); if(h && net_is_connected(h->net) && !h->nick_valid) hub_password(h, NULL); return TRUE; } // sendfile static char *f_sendfile(const char *val) { #ifdef HAVE_SENDFILE return f_id(val); #else return g_strdup("false (not supported)"); #endif } static char *p_sendfile(const char *val, GError **err) { char *r = p_bool(val, err); #ifndef HAVE_SENDFILE if(r && bool_raw(val)) { g_set_error(err, 1, 0, "This option can't be modified: %s.", "sendfile() not supported"); g_free(r); r = NULL; } #endif return r; } // tls_policy #if INTERFACE #define VAR_TLSP_DISABLE 1 #define VAR_TLSP_ALLOW 2 #define VAR_TLSP_PREFER 4 #define VAR_TLSP_FORCE 8 #endif static flag_option_t var_tls_policy_ops[] = { { VAR_TLSP_DISABLE, "disabled" }, { VAR_TLSP_ALLOW, "allow" }, { VAR_TLSP_PREFER, "prefer" }, { VAR_TLSP_FORCE, "force" }, { 0 } }; static char *f_tls_policy(const char *val) { return flags_fmt(var_tls_policy_ops, int_raw(val)); } static char *p_tls_policy(const char *val, GError **err) { int n = flags_raw(var_tls_policy_ops, FALSE, val, err); return n ? g_strdup_printf("%d", n) : NULL; } static void su_tls_policy(const char *old, const char *val, char **sug) { flags_sug(var_tls_policy_ops, val, sug); } static char *g_tls_policy(guint64 hub, const char *key) { char *r = db_vars_get(hub, key); if(!r) return NULL; static char num[2] = {}; // Compatibility with old versions if(r && r[0] >= '0' && r[0] <= '2' && !r[1]) num[0] = var_tls_policy_ops[r[0]-'0'].num; else num[0] = flags_raw(var_tls_policy_ops, FALSE, r, NULL); num[0] += '0'; return num; } static gboolean s_tls_policy(guint64 hub, const char *key, const char *val, GError **err) { char *r = flags_fmt(var_tls_policy_ops, int_raw(val)); db_vars_set(hub, key, r[0] ? r : NULL); g_free(r); listen_refresh(); hub_global_nfochange(); return TRUE; } // tls_priority static char *p_tls_priority(const char *val, GError **err) { gnutls_priority_t prio; const char *pos; if(gnutls_priority_init(&prio, val, &pos) != GNUTLS_E_SUCCESS) { g_set_error(err, 1, 0, "Error parsing priority string at '%s'", pos); return NULL; } gnutls_priority_deinit(prio); return g_strdup(val); } // notify_bell #if INTERFACE #define VAR_NOTB_DISABLE 1 #define VAR_NOTB_LOW 2 #define VAR_NOTB_MED 4 #define VAR_NOTB_HIGH 8 #endif static flag_option_t var_notify_bell_ops[] = { { VAR_NOTB_DISABLE, "disabled" }, { VAR_NOTB_LOW, "low" }, { VAR_NOTB_MED, "medium" }, { VAR_NOTB_HIGH, "high" }, { 0 } }; static char *f_notify_bell(const char *val) { return flags_fmt(var_notify_bell_ops, int_raw(val)); } static char *p_notify_bell(const char *val, GError **err) { int n = flags_raw(var_notify_bell_ops, FALSE, val, err); return n ? g_strdup_printf("%d", n) : NULL; } static void su_notify_bell(const char *old, const char *val, char **sug) { flags_sug(var_notify_bell_ops, val, sug); } static char *g_notify_bell(guint64 hub, const char *key) { char *r = db_vars_get(hub, key); if(!r) return NULL; static char num[2] = {}; num[0] = '0' + flags_raw(var_notify_bell_ops, FALSE, r, NULL); return num; } static gboolean s_notify_bell(guint64 hub, const char *key, const char *val, GError **err) { char *r = flags_fmt(var_notify_bell_ops, int_raw(val)); db_vars_set(hub, key, r[0] ? r : NULL); g_free(r); return TRUE; } // sudp_policy #if INTERFACE #define VAR_SUDPP_DISABLE 1 #define VAR_SUDPP_ALLOW 2 #define VAR_SUDPP_PREFER 4 #endif static flag_option_t var_sudp_policy_ops[] = { { VAR_SUDPP_DISABLE, "disabled" }, { VAR_SUDPP_ALLOW, "allow" }, { VAR_SUDPP_PREFER, "prefer" }, { 0 } }; static char *f_sudp_policy(const char *val) { return flags_fmt(var_sudp_policy_ops, int_raw(val)); } static char *p_sudp_policy(const char *val, GError **err) { int n = flags_raw(var_sudp_policy_ops, FALSE, val, err); return n ? g_strdup_printf("%d", n) : NULL; } static void su_sudp_policy(const char *old, const char *val, char **sug) { flags_sug(var_sudp_policy_ops, val, sug); } static char *g_sudp_policy(guint64 hub, const char *key) { static char num[2] = {}; char *r = db_vars_get(hub, key); if(!r) return NULL; num[0] = '0' + flags_raw(var_sudp_policy_ops, FALSE, r, NULL); return num; } static gboolean s_sudp_policy(guint64 hub, const char *key, const char *val, GError **err) { char *r = flags_fmt(var_sudp_policy_ops, int_raw(val)); db_vars_set(hub, key, r[0] ? r : NULL); g_free(r); hub_global_nfochange(); return TRUE; } // Exported data #if INTERFACE struct var_t { // Name does not necessarily have to correspond to the name in the 'vars' // table. Though in that case special getraw() and setraw() functions have to // be used. const char *name; gboolean global : 1; gboolean hub : 1; // Formats the raw value for human viewing. Returned string will be // g_free()'d. May be NULL if !hub && !global. char *(*format)(const char *val); // Validates and parses a human input string and returns the "raw" string. // Returned string will be g_free()'d. May also return an error if the // setting can't be set yet (e.g. if some other setting has to be set // first.). Will write any warnings or notes to ui_m(NULL, ..). // May be NULL if !hub && !global. char *(*parse)(const char *val, GError **err); // Suggestion function. *old is the old (raw) value. *val the current string // on the input line. May be NULL if no suggestions are available. void (*sug)(const char *old, const char *val, char **sug); // Get the raw value. The returned string will not be freed and may be // modified later. When this is NULL, db_vars_get() is used. char *(*getraw)(guint64 hub, const char *name); // Set the raw value and make sure it's active. val = NULL to unset it. In // general, this function should not fail if parse() didn't return an error, // but it may still refuse to set the value set *err to indicate failure. // (e.g. when trying to unset a var that must always exist). gboolean (*setraw)(guint64 hub, const char *name, const char *val, GError **err); // Default raw value, to be used when getraw() returns NULL. char *def; }; // name g h format parse suggest getraw setraw default/init #define VARS\ V(active, 1,1, f_bool, p_bool, su_bool, NULL, s_active_conf, "false")\ V(active_ip, 1,1, f_id, p_active_ip, su_old, NULL, s_active_conf, NULL)\ V(active_port, 1,1, f_int, p_active_port, NULL, NULL, s_active_conf, NULL)\ V(active_udp_port, 1,1, f_int, p_active_port, NULL, g_active_udp, s_active_conf, NULL)\ V(adc_blom, 1,1, f_bool, p_bool, su_bool, NULL, NULL, "false")\ V(autoconnect, 0,1, f_bool, p_bool, su_bool, NULL, NULL, "false")\ V(autorefresh, 1,0, f_autorefresh, p_autorefresh, NULL, NULL, NULL, "3600")\ V(backlog, 1,1, f_backlog, p_backlog, NULL, NULL, NULL, "0")\ V(chat_only, 1,1, f_bool, p_bool, su_bool, NULL, NULL, "false")\ V(cid, 0,0, NULL, NULL, NULL, NULL, NULL, i_cid_pid())\ UI_COLORS \ V(connection, 1,1, f_id, p_connection, su_old, NULL, s_hubinfo, NULL)\ V(description, 1,1, f_id, p_id, su_old, NULL, s_hubinfo, NULL)\ V(disconnect_offline,1,1,f_bool, p_bool, su_bool, NULL, NULL, "false")\ V(download_dir, 1,0, f_id, p_id, su_path, NULL, s_dl_inc_dir, i_dl_inc_dir(TRUE))\ V(download_exclude, 1,0, f_id, p_regex, su_old, NULL, NULL, NULL)\ V(download_rate, 1,0, f_speed, p_speed, NULL, NULL, NULL, NULL)\ V(download_segment, 1,0, f_download_segment,p_download_segment,NULL, NULL, NULL, g_strdup_printf("%"G_GUINT64_FORMAT, (guint64)DLFILE_CHUNKSIZE))\ V(download_shared, 1,0, f_bool, p_bool, su_bool, NULL, NULL, "true")\ V(download_slots, 1,0, f_int, p_int, NULL, NULL, s_download_slots,"3")\ V(email, 1,1, f_id, p_id, su_old, NULL, s_hubinfo, NULL)\ V(encoding, 1,1, f_id, p_encoding, su_encoding, NULL, NULL, "UTF-8")\ V(filelist_maxage, 1,0, f_interval, p_interval, su_old, NULL, NULL, "604800")\ V(fl_done, 0,0, NULL, NULL, NULL, NULL, NULL, "false")\ V(flush_file_cache, 1,0, f_ffc, p_ffc, su_ffc, g_ffc, s_ffc, i_ffc())\ V(geoip_cc, 1,0, f_id, p_id, su_path, NULL, s_geoip_cc, NULL)\ V(hash_rate, 1,0, f_speed, p_speed, NULL, NULL, NULL, NULL)\ V(hubaddr, 0,0, NULL, NULL, NULL, NULL, NULL, NULL)\ V(hubkp, 0,0, NULL, NULL, NULL, NULL, NULL, NULL)\ V(hubname, 0,1, f_id, p_hubname, su_old, NULL, s_hubname, NULL)\ V(incoming_dir, 1,0, f_id, p_id, su_path, NULL, s_dl_inc_dir, i_dl_inc_dir(FALSE))\ V(local_address, 1,1, f_id, p_ip, su_old, NULL, s_active_conf, db_vars_get(0, "active_bind"))\ V(log_debug, 1,0, f_bool, p_bool, su_bool, NULL, s_log_debug, i_log_debug())\ V(log_downloads, 1,0, f_bool, p_bool, su_bool, NULL, NULL, "true")\ V(log_hubchat, 1,1, f_bool, p_bool, su_bool, NULL, NULL, "true")\ V(log_uploads, 1,0, f_bool, p_bool, su_bool, NULL, NULL, "true")\ V(max_ul_per_user, 1,1, f_int, p_int_ge1, NULL, NULL, NULL, "1")\ V(minislots, 1,0, f_int, p_int_ge1, NULL, NULL, NULL, "3")\ V(minislot_size, 1,0, f_minislot_size,p_minislot_size, NULL, NULL, NULL, "65536")\ V(nick, 1,1, f_id, p_nick, su_old, NULL, s_nick, i_nick())\ V(notify_bell, 1,0, f_notify_bell, p_notify_bell, su_notify_bell,g_notify_bell,s_notify_bell, G_STRINGIFY(VAR_NOTB_DISABLE))\ V(password, 0,1, f_password, p_id, NULL, NULL, s_password, NULL)\ V(pid, 0,0, NULL, NULL, NULL, NULL, NULL, i_cid_pid())\ V(reconnect_timeout,1,1, f_interval, p_interval, su_old, NULL, NULL, "30")\ V(sendfile, 1,0, f_sendfile, p_sendfile, su_bool, NULL, NULL, "true")\ V(share_emptydirs, 1,0, f_bool, p_bool, su_bool, NULL, NULL, "false")\ V(share_exclude, 1,0, f_id, p_regex, su_old, NULL, NULL, NULL)\ V(share_hidden, 1,0, f_bool, p_bool, su_bool, NULL, NULL, "false")\ V(share_symlinks, 1,0, f_bool, p_bool, su_bool, NULL, NULL, "false")\ V(show_free_slots, 1,1, f_bool, p_bool, su_bool, NULL, s_hubinfo, "false")\ V(show_joinquit, 1,1, f_bool, p_bool, su_bool, NULL, NULL, "false")\ V(slots, 1,0, f_int, p_int_ge1, NULL, NULL, s_hubinfo, "10")\ V(sudp_policy, 1,0, f_sudp_policy, p_sudp_policy, su_sudp_policy,g_sudp_policy,s_sudp_policy, G_STRINGIFY(VAR_SUDPP_PREFER))\ V(tls_policy, 1,1, f_tls_policy, p_tls_policy, su_tls_policy, g_tls_policy, s_tls_policy, G_STRINGIFY(VAR_TLSP_PREFER))\ V(tls_priority, 1,0, f_id, p_tls_priority, su_old, NULL, NULL, "NORMAL:-ARCFOUR-128")\ V(ui_time_format, 1,0, f_id, p_id, su_old, NULL, NULL, "[%H:%M:%S]")\ V(upload_rate, 1,0, f_speed, p_speed, NULL, NULL, NULL, NULL) enum var_type { #define V(n, gl, h, f, p, su, g, s, d) VAR_##n, #define C(n, d) VAR_color_##n, VARS #undef V #undef C VAR_END }; #endif var_t vars[] = { #define V(n, gl, h, f, p, su, g, s, d) { G_STRINGIFY(n), gl, h, f, p, su, g, s, NULL }, #define C(n, d) { "color_"G_STRINGIFY(n), 1, 0, f_id, p_color, su_color, NULL, s_color, d }, VARS #undef V #undef C { NULL } }; // Exported functions // Get a var id by name. Returns -1 if not found. // TODO: case insensitive? Allow '-' in addition to '_'? // TODO: binary search? int vars_byname(const char *n) { int i; for(i=0; iislist && !b->islist ? -1 : !a->islist && b->islist ? 1 : strcmp(a->dest, b->dest); } // Note that we sort on username, uid. But we do not get a notification when a // user changes offline/online state, thus don't have the ability to keep the // list sorted reliably. This isn't a huge problem, though, the list is // removed/recreated every time another dl item is selected. This sorting is // just better than having the users in completely random order all the time. static gint dud_sort_func(gconstpointer da, gconstpointer db, gpointer dat) { const dl_user_dl_t *a = da; const dl_user_dl_t *b = db; hub_user_t *ua = g_hash_table_lookup(hub_uids, &a->u->uid); hub_user_t *ub = g_hash_table_lookup(hub_uids, &b->u->uid); return !ua && !ub ? (a->u->uid > b->u->uid ? 1 : a->u->uid < b->u->uid ? -1 : 0) : ua && !ub ? 1 : !ua && ub ? -1 : g_utf8_collate(ua->name, ub->name); } static void setusers(dl_t *dl) { if(dltab->cur == dl) return; // free if(!dl) { if(dltab->cur && dltab->users) { g_sequence_free(dltab->users->list); ui_listing_free(dltab->users); } dltab->users = NULL; dltab->cur = NULL; return; } // create setusers(NULL); GSequence *l = g_sequence_new(NULL); int i; for(i=0; iu->len; i++) g_sequence_insert_sorted(l, g_sequence_get(g_ptr_array_index(dl->u, i)), dud_sort_func, NULL); dltab->users = ui_listing_create(l, NULL, NULL, NULL); dltab->cur = dl; } ui_tab_t *uit_dl_create() { g_return_val_if_fail(!dltab, NULL); dltab = g_new0(tab_t, 1); dltab->tab.name = "queue"; dltab->tab.type = uit_dl; // create and pupulate the list GSequence *l = g_sequence_new(NULL); GHashTableIter iter; g_hash_table_iter_init(&iter, dl_queue); dl_t *dl; while(g_hash_table_iter_next(&iter, NULL, (gpointer *)&dl)) dl->iter = g_sequence_insert_sorted(l, dl, sort_func, NULL); dltab->list = ui_listing_create(l, NULL, NULL, NULL); return (ui_tab_t *)dltab; } static void t_close(ui_tab_t *tab) { g_return_if_fail(tab == (ui_tab_t *)dltab); ui_tab_remove(tab); setusers(NULL); g_sequence_free(dltab->list->list); ui_listing_free(dltab->list); g_free(dltab); dltab = NULL; } static char *t_title() { return g_strdup("Download queue"); } static void draw_row(ui_listing_t *list, GSequenceIter *iter, int row, void *dat) { dl_t *dl = g_sequence_get(iter); attron(iter == list->sel ? UIC(list_select) : UIC(list_default)); mvhline(row, 0, ' ', wincols); if(iter == list->sel) mvaddstr(row, 0, ">"); int online = 0; int i; for(i=0; iu->len; i++) if(g_hash_table_lookup(hub_uids, &(((dl_user_dl_t *)g_sequence_get(g_ptr_array_index(dl->u, i)))->u->uid))) online++; mvprintw(row, 2, "%2d/%2d", online, dl->u->len); mvaddstr(row, 9, str_formatsize(dl->size)); if(dl->size) mvprintw(row, 20, "%3d%%", (int) ((dl->have*100)/dl->size)); else mvaddstr(row, 20, " -"); if(dl->prio == DLP_ERR) mvaddstr(row, 26, " ERR"); else if(dl->prio == DLP_OFF) mvaddstr(row, 26, " OFF"); else mvprintw(row, 26, "%3d", dl->prio); if(dl->islist) mvaddstr(row, 32, "files.xml.bz2"); else { char *def = var_get(0, VAR_download_dir); int len = strlen(def); char *dest = strncmp(def, dl->dest, len) == 0 ? dl->dest+len+(dl->dest[len-1] == '/' ? 0 : 1) : dl->dest; mvaddnstr(row, 32, dest, str_offset_from_columns(dest, wincols-32)); } attroff(iter == list->sel ? UIC(list_select) : UIC(list_default)); } static void dud_draw_row(ui_listing_t *list, GSequenceIter *iter, int row, void *dat) { dl_user_dl_t *dud = g_sequence_get(iter); attron(iter == list->sel ? UIC(list_select) : UIC(list_default)); mvhline(row, 0, ' ', wincols); if(iter == list->sel) mvaddstr(row, 0, ">"); hub_user_t *u = g_hash_table_lookup(hub_uids, &dud->u->uid); if(u) { mvaddnstr(row, 2, u->name, str_offset_from_columns(u->name, 19)); mvaddnstr(row, 22, u->hub->tab->name, str_offset_from_columns(u->hub->tab->name, 13)); } else mvprintw(row, 2, "ID:%016"G_GINT64_MODIFIER"x (offline)", dud->u->uid); if(dud->error) mvprintw(row, 36, "Error: %s", dl_strerror(dud->error, dud->error_msg)); else if(dud->u->active == dud) mvaddstr(row, 36, "Downloading."); else if(dud->u->state == DLU_ACT) mvaddstr(row, 36, "Downloading another file."); else if(dud->u->state == DLU_EXP) mvaddstr(row, 36, "Connecting."); else mvaddstr(row, 36, "Idle."); attroff(iter == list->sel ? UIC(list_select) : UIC(list_default)); } static void t_draw(ui_tab_t *tab) { g_return_if_fail(tab == (ui_tab_t *)dltab); attron(UIC(list_header)); mvhline(1, 0, ' ', wincols); mvaddstr(1, 2, "Users"); mvaddstr(1, 9, "Size"); mvaddstr(1, 20, "Done"); mvaddstr(1, 26, "Prio"); mvaddstr(1, 32, "File"); attroff(UIC(list_header)); int bottom = dltab->details ? winrows-14 : winrows-4; ui_cursor_t cursor; int pos = ui_listing_draw(dltab->list, 2, bottom-1, &cursor, draw_row); dl_t *sel = g_sequence_iter_is_end(dltab->list->sel) ? NULL : g_sequence_get(dltab->list->sel); // footer / separator attron(UIC(separator)); mvhline(bottom, 0, ' ', wincols); if(sel) { char hash[40] = {}; base32_encode(sel->hash, hash); mvaddstr(bottom, 0, hash); } else mvaddstr(bottom, 0, "Nothing selected."); mvprintw(bottom, wincols-19, "%5d file%c - %3d%%", g_hash_table_size(dl_queue), g_hash_table_size(dl_queue) == 1 ? ' ' : 's', pos); attroff(UIC(separator)); // error info if(sel && sel->prio == DLP_ERR) mvprintw(++bottom, 0, "Error: %s", dl_strerror(sel->error, sel->error_msg)); // user list if(sel && dltab->details) { setusers(sel); attron(A_BOLD); mvaddstr(bottom+1, 2, "User"); mvaddstr(bottom+1, 22, "Hub"); mvaddstr(bottom+1, 36, "Status"); attroff(A_BOLD); if(!dltab->users || !g_sequence_get_length(dltab->users->list)) mvaddstr(bottom+3, 0, " No users for this download."); else ui_listing_draw(dltab->users, bottom+2, winrows-3, &cursor, dud_draw_row); } move(cursor.y, cursor.x); } static void t_key(ui_tab_t *tab, guint64 key) { g_return_if_fail(tab == (ui_tab_t *)dltab); if(ui_listing_key(dltab->list, key, (winrows-(dltab->details?14:4))/2)) return; dl_t *sel = g_sequence_iter_is_end(dltab->list->sel) ? NULL : g_sequence_get(dltab->list->sel); dl_user_dl_t *usel = NULL; if(!dltab->details) usel = NULL; else { setusers(sel); usel = !dltab->users || g_sequence_iter_is_end(dltab->users->sel) ? NULL : g_sequence_get(dltab->users->sel); } switch(key) { case INPT_CHAR('?'): uit_main_keys("queue"); break; case INPT_CHAR('J'): // J - user down if(dltab->details && dltab->users) { dltab->users->sel = g_sequence_iter_next(dltab->users->sel); if(g_sequence_iter_is_end(dltab->users->sel)) dltab->users->sel = g_sequence_iter_prev(dltab->users->sel); } break; case INPT_CHAR('K'): // K - user up if(dltab->details && dltab->users) dltab->users->sel = g_sequence_iter_prev(dltab->users->sel); break; case INPT_CHAR('f'): // f - find user if(!usel) ui_m(NULL, 0, "No user selected."); else { hub_user_t *u = g_hash_table_lookup(hub_uids, &usel->u->uid); if(!u) ui_m(NULL, 0, "User is not online."); else uit_userlist_open(u->hub, u->uid, NULL, FALSE); } break; case INPT_CHAR('d'): // d - remove item if(!sel) ui_m(NULL, 0, "Nothing selected."); else { ui_mf(NULL, 0, "Removed `%s' from queue.", sel->dest); dl_queue_rm(sel); } break; case INPT_CHAR('c'): // c - find connection /* XXX: If the file is downloaded from multiple users, this'll only select one (obviously) */ if(!sel) ui_m(NULL, 0, "Nothing selected."); else if(!sel->active_threads) ui_m(NULL, 0, "Download not in progress."); else { cc_t *cc = NULL; int i; for(i=0; iu->len; i++) { dl_user_dl_t *dud = g_sequence_get(g_ptr_array_index(sel->u, i)); if(dud->u->active == dud) { cc = dud->u->cc; break; } } if(!cc) ui_m(NULL, 0, "Download not in progress."); else uit_conn_open(cc, tab); } break; case INPT_CHAR('a'): // a - search for alternative sources if(!sel) ui_m(NULL, 0, "Nothing selected."); else if(sel->islist) ui_m(NULL, 0, "Can't search for alternative sources for file lists."); else uit_search_open_tth(sel->hash, tab); break; case INPT_CHAR('R'): // R - remove user from all queued files case INPT_CHAR('r'): // r - remove user from file if(!usel) ui_m(NULL, 0, "No user selected."); else { dl_queue_rmuser(usel->u->uid, key == INPT_CHAR('R') ? NULL : sel->hash); ui_m(NULL, 0, key == INPT_CHAR('R') ? "Removed user from the download queue." : "Removed user for this file."); } break; case INPT_CHAR('x'): // x - clear user-specific error state case INPT_CHAR('X'): // X - clear user-specific error state for all files if(!usel) ui_m(NULL, 0, "No user selected."); else if(key == INPT_CHAR('x') && !usel->error) ui_m(NULL, 0, "Selected user is not in an error state."); else dl_queue_setuerr(usel->u->uid, key == INPT_CHAR('X') ? NULL : sel->hash, 0, 0); break; case INPT_CHAR('+'): // + case INPT_CHAR('='): // = - increase priority if(!sel) ui_m(NULL, 0, "Nothing selected."); else if(sel->prio >= 2) ui_m(NULL, 0, "Already set to highest priority."); else dl_queue_setprio(sel, sel->prio == DLP_ERR ? 0 : sel->prio == DLP_OFF ? -2 : sel->prio+1); break; case INPT_CHAR('-'): // - - decrease priority if(!sel) ui_m(NULL, 0, "Nothing selected."); else if(sel->prio <= DLP_OFF) ui_m(NULL, 0, "Item already disabled."); else dl_queue_setprio(sel, sel->prio == -2 ? DLP_OFF : sel->prio-1); break; case INPT_CTRL('j'): // newline case INPT_CHAR('i'): // i (toggle user list) dltab->details = !dltab->details; break; } } #if INTERFACE #define UITDL_ADD 0 #define UITDL_DEL 1 #endif void uit_dl_listchange(dl_t *dl, int change) { if(!dltab) return; switch(change) { case UITDL_ADD: dl->iter = g_sequence_insert_sorted(dltab->list->list, dl, sort_func, NULL); ui_listing_inserted(dltab->list); break; case UITDL_DEL: if(dl == dltab->cur) setusers(NULL); ui_listing_remove(dltab->list, dl->iter); g_sequence_remove(dl->iter); break; } } void uit_dl_dud_listchange(dl_user_dl_t *dud, int change) { if(!dltab || dud->dl != dltab->cur || !dltab->users) return; switch(change) { case UITDL_ADD: // Note that _insert_sorted() may not actually insert the item in the // correct position, since the list is not guaranteed to be correctly // sorted in the first place. g_sequence_insert_sorted(dltab->users->list, dud, dud_sort_func, NULL); ui_listing_inserted(dltab->users); break; case UITDL_DEL: ; GSequenceIter *i; for(i=g_sequence_get_begin_iter(dltab->users->list); !g_sequence_iter_is_end(i); i=g_sequence_iter_next(i)) if(g_sequence_get(i) == dud) break; if(!g_sequence_iter_is_end(i)) { ui_listing_remove(dltab->users, i); g_sequence_remove(i); } break; } } // Opens the dl tab (if it's not open yet), selects the specified dl item (if // that's not NULL) and the specified user (if that's not 0). void uit_dl_open(dl_t *dl, guint64 uid, ui_tab_t *parent) { if(dltab) ui_tab_cur = g_list_find(ui_tabs, dltab); else ui_tab_open(uit_dl_create(), TRUE, parent); // dl->iter should be valid now if(dl) dltab->list->sel = dl->iter; // select the right user if(uid) { dltab->details = TRUE; setusers(dl); GSequenceIter *i = g_sequence_get_begin_iter(dltab->users->list); for(; !g_sequence_iter_is_end(i); i=g_sequence_iter_next(i)) if(((dl_user_dl_t *)g_sequence_get(i))->u->uid == uid) { dltab->users->sel = i; break; } } } ui_tab_type_t uit_dl[1] = { { t_draw, t_title, t_key, t_close } }; ncdc-1.23.1/src/cc.c0000644000175000017500000014660414245144453010767 00000000000000/* ncdc - NCurses Direct Connect client Copyright (c) 2011-2022 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "ncdc.h" #include "cc.h" // List of expected incoming or outgoing connections. This is list managed by // the functions below, in addition to cc_init_global() and cc_remove_hub(), typedef struct cc_expect_t { hub_t *hub; char *nick; // NMDC, hub encoding. Also set on ADC, but only for debugging purposes guint64 uid; guint16 port; char *token; // ADC char *kp; // ADC - slice-alloc'ed with 32 bytes time_t added; int timeout_src; gboolean adc : 1; gboolean dl : 1; // if we were the one starting the connection (i.e. we want to download) } cc_expect_t; static GQueue *cc_expected; static void cc_expect_rm(GList *n, cc_t *success) { cc_expect_t *e = n->data; if(e->dl && !success) dl_user_cc(e->uid, FALSE); else if(e->dl) success->dl = TRUE; g_source_remove(e->timeout_src); if(e->kp) g_slice_free1(32, e->kp); g_free(e->token); g_free(e->nick); g_slice_free(cc_expect_t, e); g_queue_delete_link(cc_expected, n); } static gboolean cc_expect_timeout(gpointer data) { GList *n = data; cc_expect_t *e = n->data; g_message("Expected connection from %s on %s, but received none.", e->nick, e->hub->tab->name); cc_expect_rm(n, NULL); return FALSE; } void cc_expect_add(hub_t *hub, hub_user_t *u, guint16 port, char *t, gboolean dl) { cc_expect_t *e = g_slice_new0(cc_expect_t); e->adc = hub->adc; e->hub = hub; e->dl = dl; e->uid = u->uid; e->port = port; if(e->adc) e->nick = g_strdup(u->name); else e->nick = g_strdup(u->name_hub); if(u->kp) { e->kp = g_slice_alloc(32); memcpy(e->kp, u->kp, 32); } if(t) e->token = g_strdup(t); time(&(e->added)); g_queue_push_tail(cc_expected, e); e->timeout_src = g_timeout_add_seconds_full(G_PRIORITY_LOW, 60, cc_expect_timeout, cc_expected->tail, NULL); } // Checks the expects list for the current connection, sets cc->dl, cc->uid, // cc->hub and cc->kp_user and removes it from the expects list. cc->token must // be known, and either cc->cid must be set or a uid must be given. static gboolean cc_expect_adc_rm(cc_t *cc, guint64 uid) { // We're calculating the uid for each (expect->hub->id, cc->cid) pair in the // list and compare it with expect->uid to see if we've got the right user. // This isn't the most efficient solution... GList *n; for(n=cc_expected->head; n; n=n->next) { cc_expect_t *e = n->data; if(e->adc && e->port == cc->port && strcmp(cc->token, e->token) == 0 && e->uid == (cc->cid ? hub_user_adc_id(e->hub->id, cc->cid) : uid)) { cc->uid = e->uid; cc->hub = e->hub; cc->kp_user = e->kp; e->kp = NULL; cc_expect_rm(n, cc); return TRUE; } } return FALSE; } // Same as above, but for NMDC. Sets cc->dl, cc->uid and cc->hub. cc->nick_raw // must be known, and for passive connections cc->hub must also be known. static gboolean cc_expect_nmdc_rm(cc_t *cc) { GList *n; for(n=cc_expected->head; n; n=n->next) { cc_expect_t *e = n->data; if(cc->hub && cc->hub != e->hub) continue; if(!e->adc && e->port == cc->port && strcmp(e->nick, cc->nick_raw) == 0) { cc->hub = e->hub; cc->uid = e->uid; cc_expect_rm(n, cc); return TRUE; } } return FALSE; } // Throttling of GET file offset, for buggy clients that keep requesting the // same file+offset. Throttled to 1 request per hour, with an allowed burst of 10. #define THROTTLE_INTV 3600 #define THROTTLE_BURST 10 typedef struct throttle_get_t { char tth[24]; guint64 uid; guint64 offset; // G_MAXUINT64 is for 'GET tthl', which also needs throttling apparently... time_t throttle; } throttle_get_t; static GHashTable *throttle_list; // initialized in cc_init_global() static guint throttle_hash(gconstpointer key) { const throttle_get_t *t = key; guint *tth = (guint *)t->tth; return *tth + (gint)t->offset + (gint)t->uid; } static gboolean throttle_equal(gconstpointer a, gconstpointer b) { const throttle_get_t *x = a; const throttle_get_t *y = b; return x->uid == y->uid && memcmp(x->tth, y->tth, 24) == 0 && x->offset == y->offset; } static void throttle_free(gpointer dat) { g_slice_free(throttle_get_t, dat); } static gboolean throttle_check(cc_t *cc, char *tth, guint64 offset) { // construct a key throttle_get_t key; memcpy(key.tth, tth, 24); key.uid = cc->uid; key.offset = offset; time(&key.throttle); // lookup throttle_get_t *val = g_hash_table_lookup(throttle_list, &key); // value present and above threshold, throttle! if(val && val->throttle-key.throttle > THROTTLE_BURST*THROTTLE_INTV) return TRUE; // value present and below threshold, update throttle value if(val) { val->throttle = MAX(key.throttle, val->throttle+THROTTLE_INTV); return FALSE; } // value not present, add it val = g_slice_dup(throttle_get_t, &key); g_hash_table_insert(throttle_list, val, val); return FALSE; } static gboolean throttle_purge_func(gpointer key, gpointer val, gpointer dat) { throttle_get_t *v = val; time_t *t = dat; return v->throttle < *t ? TRUE : FALSE; } // Purge old throttle items from the throttle_list. Called from a timer that is // initialized in cc_init_global(). static gboolean throttle_purge(gpointer dat) { time_t t = time(NULL); int r = g_hash_table_foreach_remove(throttle_list, throttle_purge_func, &t); g_debug("throttle_purge: Purged %d items, %d items left.", r, g_hash_table_size(throttle_list)); return TRUE; } // Main C-C objects #if INTERFACE // States #define CCS_CONN 0 #define CCS_HANDSHAKE 1 #define CCS_IDLE 2 #define CCS_TRANSFER 3 // check cc->dl whether it's up or down #define CCS_DISCONN 4 // waiting to get removed on a timeout struct cc_t { net_t *net; hub_t *hub; char *nick_raw; // (NMDC) char *nick; char *hub_name; // Copy of hub->tab->name when hub is reset to NULL gboolean adc : 1; gboolean tls : 1; gboolean active : 1; gboolean isop : 1; gboolean slot_mini : 1; gboolean slot_granted : 1; gboolean dl : 1; gboolean zlig : 1; // Only used for partial file lists, and only used on ADC because I don't know how NMDC clients handle that guint16 port; guint16 state; int dir; // (NMDC) our direction. -1 = Upload, otherwise: Download $dir char *cid; // (ADC) base32-encoded CID int timeout_src; char remoteaddr[64]; // xxx.xxx.xxx.xxx:ppppp or [ipv6addr]:ppppp char *token; // (ADC) char *last_file; dlfile_thread_t *dlthread; guint64 uid; guint64 last_size; guint64 last_offset; guint64 last_length; gboolean last_tthl; time_t last_start; char last_hash[24]; char *kp_real; // (ADC) slice-alloc'ed with 32 bytes. This is the actually calculated keyprint. char *kp_user; // (ADC) This is the keyprint from the users' INF GError *err; GSequenceIter *iter; }; #endif /* State machine: Event allowed states next states Generic init: cc_create - conn incoming connection conn handshake hub-initiated connect conn conn connected after ^ conn handshake NMDC: $MaxedOut transfer_d disconn $Error transfer_d idle_d [1] $ADCSND transfer_d transfer_d $ADCGET idle_u transfer_u $Direction handshake idle_[ud] [1] $Supports handshake handshake $Lock handshake handshake $MyNick handshake handshake ADC: SUP handshake handshake INF handshake idle_[ud] [1] GET idle_u transfer_u SND transfer_d transfer_d STA x53 (slots full) transfer_d disconn STA x5[12] (file error) transfer_d idle_d or disconn STA other any no change or disconn Generic other: transfer complete transfer_[ud] idle_[ud] [1] any protocol error any disconn network error[2] any disconn user disconnect any disconn dl.c wants download idle_d transfer_d [1] possibly immediately followed by transfer_d if cc->dl and we have something to download. [2] includes the idle timeout. Note that the ADC protocol distinguishes between "protocol" and "identify", I combined that into a single "handshake" state since NMDC lacks something similar. Also note that the "transfer" state does not mean that a file is actually being sent over the network: when downloading, it also refers to the period of initiating the download (i.e. a GET has been sent and we're waiting for a SND). The _d and _u suffixes relate to the value of cc->dl, and is relevant for the idle and transfer states. Exchanging TTHL data is handled differently with uploading and downloading: With uploading it is done in a single call to net_write(), and as such the transfer_u state will not be used. Downloading, on the other hand, uses net_readbytes(), and the cc instance will stay in the transfer_d state until the TTHL data has been fully received. */ static void adc_handle(net_t *net, char *msg, int _len); static void nmdc_handle(net_t *net, char *msg, int _len); // opened connections - uit_conn is responsible for the ordering GSequence *cc_list; void cc_global_init() { cc_expected = g_queue_new(); cc_list = g_sequence_new(NULL); throttle_list = g_hash_table_new_full(throttle_hash, throttle_equal, NULL, throttle_free); g_timeout_add_seconds_full(G_PRIORITY_LOW, 600, throttle_purge, NULL, NULL); } // Calls cc_disconnect() on every open cc connection. This makes sure that any // current transfers are aborted and logged to the transfer log. void cc_global_close() { GSequenceIter *i = g_sequence_get_begin_iter(cc_list); for(; !g_sequence_iter_is_end(i); i=g_sequence_iter_next(i)) { cc_t *c = g_sequence_get(i); if(c->state != CCS_DISCONN) cc_disconnect(c, TRUE); } } // Should be called periodically. Walks through the list of opened connections // and checks that we're not connected to someone who's not online on any hubs. // Closes the connection otherwise. // I suspect that this is more efficient than checking with the cc list every // time someone joins/quits a hub. void cc_global_onlinecheck() { GSequenceIter *i = g_sequence_get_begin_iter(cc_list); for(; !g_sequence_iter_is_end(i); i=g_sequence_iter_next(i)) { cc_t *c = g_sequence_get(i); if((c->state == CCS_IDLE || c->state == CCS_TRANSFER) // idle or transfer mode && !g_hash_table_lookup(hub_uids, &c->uid) // user offline && var_get_bool(c->hub?c->hub->id:0, VAR_disconnect_offline)) // 'disconnect_offline' enabled cc_disconnect(c, FALSE); } } // When a hub tab is closed (not just disconnected), make sure all hub fields // are reset to NULL - since we won't be able to dereference it anymore. Note // that we do keep the connections opened, and things can resume as normal // without the hub field, since it is only used in the initial phase (with the // $MyNick's being exchanged.) // Note that the connection will remain hubless even when the same hub is later // opened again. I don't think this is a huge problem, however. void cc_remove_hub(hub_t *hub) { // Remove from cc objects GSequenceIter *i = g_sequence_get_begin_iter(cc_list); for(; !g_sequence_iter_is_end(i); i=g_sequence_iter_next(i)) { cc_t *c = g_sequence_get(i); if(c->hub == hub) { c->hub_name = g_strdup(hub->tab->name); c->hub = NULL; } } // Remove from expects list GList *p, *n; for(n=cc_expected->head; n;) { p = n->next; cc_expect_t *e = n->data; if(e->hub == hub) cc_expect_rm(n, NULL); n = p; } } // Can be cached if performance is an issue. Note that even file transfers that // do not require a slot are still counted as taking a slot. For this reason, // the return value can be larger than the configured number of slots. This // also means that an upload that requires a slot will not be granted if there // are many transfers active that don't require a slot. int cc_slots_in_use(int *mini) { int num = 0; int m = 0; GSequenceIter *i = g_sequence_get_begin_iter(cc_list); for(; !g_sequence_iter_is_end(i); i=g_sequence_iter_next(i)) { cc_t *c = g_sequence_get(i); if(!c->dl && c->state == CCS_TRANSFER) num++; if(!c->dl && c->state == CCS_TRANSFER && c->slot_mini) m++; } if(mini) *mini = m; return num; } // To be called when an upload or download has finished. Will get info from the // cc struct and write it to the transfer log. static void xfer_log_add(cc_t *cc) { g_return_if_fail(cc->state == CCS_TRANSFER && cc->last_file); // we don't log tthl transfers or transfers that hadn't been started yet if(cc->last_tthl || !cc->last_length) return; if(!var_get_bool(0, cc->dl ? VAR_log_downloads : VAR_log_uploads)) return; static logfile_t *log = NULL; if(!log) log = logfile_create("transfers"); char tth[40] = {}; if(strcmp(cc->last_file, "files.xml.bz2") == 0) strcpy(tth, "-"); else base32_encode(cc->last_hash, tth); guint64 transfer_size = cc->last_length - net_left(cc->net); char *nick = adc_escape(cc->nick, FALSE); char *file = adc_escape(cc->last_file, FALSE); yuri_t uri; g_return_if_fail(yuri_parse_copy(cc->remoteaddr, &uri) == 0); char *msg = g_strdup_printf("%s %s %s %s %c %c %s %d %"G_GUINT64_FORMAT" %"G_GUINT64_FORMAT" %"G_GUINT64_FORMAT" %s", cc->hub ? cc->hub->tab->name : cc->hub_name, cc->adc ? cc->cid : "-", nick, uri.host, cc->dl ? 'd' : 'u', transfer_size == cc->last_length ? 'c' : 'i', tth, (int)(time(NULL)-cc->last_start), cc->last_size, cc->last_offset, transfer_size, file); logfile_add(log, msg); free(uri.buf); g_free(msg); g_free(nick); g_free(file); } // Returns true if the connection doesn't exceed maximum per-user connection limits. static gboolean cc_should_allow_connection(cc_t *cc) { GSequenceIter *i = g_sequence_get_begin_iter(cc_list); int current_conns = 0; const int max_conns = cc->dl ? 1 : var_get_int(cc->hub->id, VAR_max_ul_per_user); for(; !g_sequence_iter_is_end(i); i=g_sequence_iter_next(i)) { cc_t *c = g_sequence_get(i); if(cc != c && c->state != CCS_DISCONN && !!c->adc == !!cc->adc && !!c->dl == !!cc->dl && (c->cid && cc->cid ? strcmp(c->cid, cc->cid) == 0 : c->uid == cc->uid)) ++current_conns; if(current_conns >= max_conns) return false; } return true; } static gboolean request_slot(cc_t *cc, gboolean need_full) { int minislots; int slots = cc_slots_in_use(&minislots); cc->slot_mini = FALSE; // if this connection is granted a slot, then just allow it if(cc->slot_granted) return TRUE; // if we have a free slot, use that if(slots < var_get_int(0, VAR_slots)) return TRUE; // if we can use a minislot, do so if(!need_full && minislots < var_get_int(0, VAR_minislots)) { cc->slot_mini = TRUE; return TRUE; } // if we can use a minislot yet we don't have one, still allow an OP if(!need_full && cc->isop) return TRUE; // none of the above? then we're out of slots return FALSE; } // Called from dl.c void cc_download(cc_t *cc, dl_t *dl) { g_return_if_fail(cc->dl && cc->state == CCS_IDLE); memcpy(cc->last_hash, dl->hash, 24); // get virtual path char fn[45] = {}; if(dl->islist) strcpy(fn, "files.xml.bz2"); // TODO: fallback for clients that don't support BZIP? (as if they exist...) else { strcpy(fn, "TTH/"); base32_encode(dl->hash, fn+4); } // if we have not received TTHL data yet, request it if(!dl->islist && !dl->hastthl) { if(cc->adc) net_writef(cc->net, "CGET tthl %s 0 -1\n", fn); else net_writef(cc->net, "$ADCGET tthl %s 0 -1|", fn); cc->last_offset = 0; // otherwise, send GET request } else { /* TODO: A more long-term rate calculation algorithm might be more suitable here */ cc->dlthread = dlfile_getchunk(dl, cc->uid, ratecalc_rate(net_rate_in(cc->net))); if(!cc->dlthread) { g_set_error_literal(&cc->err, 1, 0, "Download interrupted."); cc_disconnect(cc, FALSE); return; } cc->last_offset = ((guint64)cc->dlthread->chunk * DLFILE_CHUNKSIZE) + cc->dlthread->len; gint64 len = dl->islist ? -1 : MIN(((gint64)cc->dlthread->allocated * DLFILE_CHUNKSIZE) - cc->dlthread->len, (gint64)(dl->size - cc->last_offset)); if(cc->adc) net_writef(cc->net, "CGET file %s %"G_GUINT64_FORMAT" %"G_GINT64_FORMAT"\n", fn, cc->last_offset, len); else net_writef(cc->net, "$ADCGET file %s %"G_GUINT64_FORMAT" %"G_GINT64_FORMAT"|", fn, cc->last_offset, len); } g_free(cc->last_file); cc->last_file = g_strdup(dl->islist ? "files.xml.bz2" : dl->dest); cc->last_size = dl->size; cc->last_length = 0; // to be filled in handle_adcsnd() cc->state = CCS_TRANSFER; } static void handle_recvdone(net_t *n, void *dat) { // If the connection is still active, log the transfer and check for more // stuff to download if(n && net_is_connected(n)) { cc_t *cc = net_handle(n); net_readmsg(cc->net, cc->adc ? '\n' : '|', cc->adc ? adc_handle : nmdc_handle); xfer_log_add(cc); cc->state = CCS_IDLE; dl_user_cc(cc->uid, cc); } // Notify dl dlfile_recv_done(dat); } static void handle_recvtth(net_t *n, char *buf, int read) { cc_t *cc = net_handle(n); g_return_if_fail(read == cc->last_length); dl_settthl(cc->uid, cc->last_hash, buf, cc->last_length); if(net_is_connected(n)) { cc->last_tthl = FALSE; cc->state = CCS_IDLE; dl_user_cc(cc->uid, cc); net_readmsg(cc->net, cc->adc ? '\n' : '|', cc->adc ? adc_handle : nmdc_handle); } } static void handle_adcsnd(cc_t *cc, gboolean tthl, guint64 start, gint64 bytes) { dl_t *dl = g_hash_table_lookup(dl_queue, cc->last_hash); if(!dl || (!tthl && !cc->dlthread)) { g_set_error_literal(&cc->err, 1, 0, "Download interrupted."); cc_disconnect(cc, FALSE); return; } cc->last_length = bytes; cc->last_tthl = tthl; if(!tthl) { if(dl->islist) { cc->last_size = dl->size = bytes; dl->hassize = TRUE; } net_recvfile(cc->net, bytes, dlfile_recv, handle_recvdone, cc->dlthread); cc->dlthread = NULL; } else { g_return_if_fail(start == 0 && bytes > 0 && (bytes%24) == 0 && bytes < 48*1024); net_readbytes(cc->net, bytes, handle_recvtth); } time(&cc->last_start); } static void handle_sendcomplete(net_t *net) { cc_t *cc = net_handle(net); xfer_log_add(cc); cc->state = CCS_IDLE; } static void send_file(cc_t *cc, const char *path, guint64 start, guint64 len, gboolean flush, GError **err) { int fd = 0; if((fd = open(path, O_RDONLY)) < 0 || lseek(fd, start, SEEK_SET) == (off_t)-1) { // Don't give a detailed error message, the remote shouldn't know too much about us. g_set_error_literal(err, 1, 50, "Error opening file"); g_message("Error opening/seeking '%s' for sending: %s", path, g_strerror(errno)); return; } net_sendfile(cc->net, fd, len, flush, handle_sendcomplete); } // err->code: // 40: Generic protocol error // 50: Generic internal error // 51: File not available // 53: No slots // Handles both ADC GET and the NMDC $ADCGET. static void handle_adcget(cc_t *cc, char *type, char *id, guint64 start, gint64 bytes, gboolean zlib, gboolean re1, GError **err) { // tthl if(strcmp(type, "tthl") == 0) { if(strncmp(id, "TTH/", 4) != 0 || !istth(id+4) || start != 0 || bytes != -1) { g_set_error_literal(err, 1, 40, "Invalid arguments"); return; } char root[24]; base32_decode(id+4, root); int len = 0; char *dat = db_fl_gettthl(root, &len); if(!dat) g_set_error_literal(err, 1, 51, "File Not Available"); else if(!cc->slot_granted && throttle_check(cc, root, G_MAXUINT64)) { g_message("CC:%s: TTHL request throttled: %s", net_remoteaddr(cc->net), id); g_set_error_literal(err, 1, 50, "Action throttled"); } else { // no need to adc_escape(id) here, since it cannot contain any special characters net_writef(cc->net, cc->adc ? "CSND tthl %s 0 %d\n" : "$ADCSND tthl %s 0 %d|", id, len); net_write(cc->net, dat, len); g_free(dat); } return; } // list if(strcmp(type, "list") == 0) { if(id[0] != '/' || id[strlen(id)-1] != '/' || start != 0 || bytes != -1) { g_set_error_literal(err, 1, 40, "Invalid arguments"); return; } fl_list_t *f = fl_local_list ? fl_list_from_path(fl_local_list, id) : NULL; if(!f || f->isfile) { g_set_error_literal(err, 1, 51, "File Not Available"); return; } // Use a targetsize of 16k for non-recursive lists and 256k for recursive // ones. This should give useful results in most cases. The only exception // here is Jucy, which does not handle "Incomplete" entries in a recursive // list, but... yeah, that's Jucy's problem. :-) GString *buf = g_string_new(""); GError *e = NULL; int len = fl_save(f, var_get(0, VAR_cid), re1 ? 256*1024 : 16*1024, zlib, buf, NULL, &e); if(!len) { g_set_error(err, 1, 50, "Creating partial XML list: %s", e->message); g_error_free(e); g_string_free(buf, TRUE); return; } char *eid = adc_escape(id, !cc->adc); net_writef(cc->net, cc->adc ? "CSND list %s 0 %d%s\n" : "$ADCSND list %s 0 %d%s|", eid, len, zlib ? " ZL1" : ""); net_write(cc->net, buf->str, buf->len); g_free(eid); g_string_free(buf, TRUE); return; } // file if(strcmp(type, "file") != 0) { g_set_error_literal(err, 40, 0, "Unsupported ADCGET type"); return; } // get path (for file uploads) // TODO: files.xml? (Required by ADC, but I doubt it's used) char *path = NULL; char *vpath = NULL; fl_list_t *f = NULL; gboolean needslot = TRUE; // files.xml.bz2 if(strcmp(id, "files.xml.bz2") == 0) { path = g_strdup(fl_local_list_file); vpath = g_strdup("files.xml.bz2"); needslot = FALSE; // / (path in the nameless root) } else if(id[0] == '/' && fl_local_list) { f = fl_list_from_path(fl_local_list, id); // TTH/ } else if(strncmp(id, "TTH/", 4) == 0 && istth(id+4)) { char root[24]; base32_decode(id+4, root); GSList *l = fl_local_from_tth(root); f = l ? l->data : NULL; } if(f) { char *enc_path = fl_local_path(f); path = g_filename_from_utf8(enc_path, -1, NULL, NULL, NULL); g_free(enc_path); vpath = fl_list_path(f); } // validate struct stat st = {}; if(!path || stat(path, &st) < 0 || !S_ISREG(st.st_mode) || start > st.st_size) { if(st.st_size && start > st.st_size) g_set_error_literal(err, 1, 52, "File Part Not Available"); else g_set_error_literal(err, 1, 51, "File Not Available"); g_free(path); g_free(vpath); return; } if(bytes < 0 || bytes > st.st_size-start) bytes = st.st_size-start; if(needslot && st.st_size < var_get_int(0, VAR_minislot_size)) needslot = FALSE; if(f && !cc->slot_granted && throttle_check(cc, f->tth, start)) { g_message("CC:%s: File upload throttled: %s offset %"G_GUINT64_FORMAT, net_remoteaddr(cc->net), vpath, start); g_set_error_literal(err, 1, 50, "Action throttled"); g_free(path); g_free(vpath); return; } // send if(request_slot(cc, needslot)) { g_free(cc->last_file); cc->last_file = vpath; cc->last_length = bytes; cc->last_offset = start; cc->last_size = st.st_size; if(f) memcpy(cc->last_hash, f->tth, 24); char *tmp = adc_escape(id, !cc->adc); // Note: For >=2GB chunks, we tell the other client that we're sending them // more than 2GB, but in actuality we stop transfering stuff at 2GB. Other // DC clients (DC++, notabily) don't like it when you reply with a // different byte count than they requested. :-( net_writef(cc->net, cc->adc ? "CSND file %s %"G_GUINT64_FORMAT" %"G_GUINT64_FORMAT"\n" : "$ADCSND file %s %"G_GUINT64_FORMAT" %"G_GUINT64_FORMAT"|", tmp, start, bytes); cc->state = CCS_TRANSFER; time(&cc->last_start); send_file(cc, path, start, cc->last_length, strcmp(vpath, "files.xml.bz2") == 0 ? FALSE : TRUE, err); g_free(tmp); } else { g_set_error_literal(err, 1, 53, "No Slots Available"); g_free(vpath); } g_free(path); } // To be called when we know with which user and on which hub this connection // is. May be called multiple times for the same connection. static void handle_id(cc_t *cc, hub_user_t *u) { if(!cc->nick) cc->nick = g_strdup(u->name); cc->isop = u->isop; cc->uid = u->uid; uit_conn_listchange(cc->iter, UITCONN_MOD); // Set u->ip4 or u->ip6 if we didn't get this from the hub yet (NMDC, this // information is only used for display purposes). // Note that in the case of ADC, this function is called before the // connection has actually been established, so the remote address isn't // known yet. This doesn't matter, however, as the hub already sends IP // information with ADC (if it didn't, we won't be able to connect in the // first place). if(net_is_connected(cc->net) && (ip4_isany(u->ip4) && ip6_isany(u->ip6))) { yuri_t uri; if(yuri_parse_copy(net_remoteaddr(cc->net), &uri) == 0) { if(uri.hosttype == YURI_IPV4) u->ip4 = ip4_pack(uri.host); else u->ip6 = ip6_pack(uri.host); free(uri.buf); } } // Check the number of connections with the same user for the same purpose // (up/down). For NMDC, the purpose of this connection is determined when we // receive a $Direction, so it's only checked here for ADC. if(cc->adc && !cc_should_allow_connection(cc)) { g_set_error_literal(&(cc->err), 1, 0, "too many open connections with this user"); cc_disconnect(cc, FALSE); return; } // Because tls_policy is a hub-local setting, we can only verify TLS settings // for incoming connections once we know from which hub this connection came. // The check is perhaps a bit late, but better disconnect late than never. if(cc->tls && var_get_int(cc->hub->id, VAR_tls_policy) == VAR_TLSP_DISABLE) { g_set_error_literal(&(cc->err), 1, 0, "TLS connection"); cc_disconnect(cc, FALSE); return; } if(!cc->tls && var_get_int(cc->hub->id, VAR_tls_policy) == VAR_TLSP_FORCE) { g_set_error_literal(&(cc->err), 1, 0, "non-TLS connection"); cc_disconnect(cc, FALSE); return; } cc->slot_granted = db_users_get(u->hub->id, u->name) & DB_USERFLAG_GRANT ? TRUE : FALSE; } static void adc_handle(net_t *net, char *msg, int _len) { cc_t *cc = net_handle(net); if(!*msg) return; g_clear_error(&cc->err); g_return_if_fail(cc->state != CCS_CONN && cc->state != CCS_DISCONN); net_readmsg(net, '\n', adc_handle); adc_cmd_t cmd; GError *err = NULL; adc_parse(msg, &cmd, NULL, &err); if(err) { g_message("CC:%s: ADC parse error: %s. --> %s", net_remoteaddr(cc->net), err->message, msg); g_error_free(err); return; } if(cmd.type != 'C') { g_message("CC:%s: Not a client command: %s", net_remoteaddr(cc->net), msg); g_strfreev(cmd.argv); return; } switch(cmd.cmd) { case ADCC_SUP: if(cc->state != CCS_HANDSHAKE) { g_set_error_literal(&cc->err, 1, 0, "Protocol error."); g_message("CC:%s: Received message in wrong state: %s", net_remoteaddr(cc->net), msg); cc_disconnect(cc, TRUE); } else { int i; for(i=0; izlig = TRUE; if(cc->active) net_writestr(cc->net, "CSUP ADBASE ADTIGR ADBZIP ADZLIG\n"); GString *r = adc_generate('C', ADCC_INF, 0, 0); adc_append(r, "ID", var_get(0, VAR_cid)); if(!cc->active) adc_append(r, "TO", cc->token); g_string_append_c(r, '\n'); net_writestr(cc->net, r->str); g_string_free(r, TRUE); } break; case ADCC_INF: if(cc->state != CCS_HANDSHAKE) { g_set_error_literal(&cc->err, 1, 0, "Protocol error."); g_message("CC:%s: Received message in wrong state: %s", net_remoteaddr(cc->net), msg); cc_disconnect(cc, TRUE); } else { cc->state = CCS_IDLE;; char *id = adc_getparam(cmd.argv, "ID", NULL); char *token = adc_getparam(cmd.argv, "TO", NULL); if(!id || (cc->active && !token)) { g_set_error_literal(&cc->err, 1, 0, "Protocol error."); g_message("CC:%s: No token or CID present: %s", net_remoteaddr(cc->net), msg); cc_disconnect(cc, TRUE); break; } else if(!iscid(id) || (!cc->active && (!cc->hub || cc->uid != hub_user_adc_id(cc->hub->id, id)))) { g_set_error_literal(&cc->err, 1, 0, "Protocol error."); g_message("CC:%s: Incorrect CID: %s", net_remoteaddr(cc->net), msg); cc_disconnect(cc, TRUE); break; } else { cc->cid = g_strdup(id); if(cc->active) { cc->token = g_strdup(token); cc_expect_adc_rm(cc, 0); } hub_user_t *u = cc->uid ? g_hash_table_lookup(hub_uids, &cc->uid) : NULL; if(!u) { g_set_error_literal(&cc->err, 1, 0, "Protocol error."); g_message("CC:%s: Unexpected ADC connection: %s", net_remoteaddr(cc->net), msg); cc_disconnect(cc, TRUE); break; } handle_id(cc, u); } // Perform keyprint validation // TODO: Throw an error if kp_user is set but we've not received a kp_real? if(cc->kp_real && cc->kp_user && memcmp(cc->kp_real, cc->kp_user, 32) != 0) { g_set_error_literal(&cc->err, 1, 0, "Protocol error."); char user[53] = {}, real[53] = {}; base32_encode_dat(cc->kp_user, user, 32); base32_encode_dat(cc->kp_real, real, 32); g_message("CC:%s: Client keyprint does not match TLS keyprint: %s != %s", net_remoteaddr(cc->net), user, real); cc_disconnect(cc, TRUE); } else if(cc->kp_real && cc->kp_user) g_debug("CC:%s: Client authenticated using KEYP.", net_remoteaddr(cc->net)); if(cc->dl && cc->state == CCS_IDLE) dl_user_cc(cc->uid, cc); } break; case ADCC_GET: if(cmd.argc < 4) { g_message("CC:%s: Invalid command: %s", net_remoteaddr(cc->net), msg); } else if(cc->dl || cc->state != CCS_IDLE) { g_set_error_literal(&cc->err, 1, 0, "Protocol error."); g_message("CC:%s: Received message in wrong state: %s", net_remoteaddr(cc->net), msg); cc_disconnect(cc, TRUE); } else { guint64 start = g_ascii_strtoull(cmd.argv[2], NULL, 0); gint64 len = g_ascii_strtoll(cmd.argv[3], NULL, 0); GError *err = NULL; handle_adcget(cc, cmd.argv[0], cmd.argv[1], start, len, cc->zlig&&adc_getparam(cmd.argv, "ZL", NULL)?TRUE:FALSE, adc_getparam(cmd.argv, "RE", NULL)?TRUE:FALSE, &err); if(err) { GString *r = adc_generate('C', ADCC_STA, 0, 0); g_string_append_printf(r, " 1%02d", err->code); adc_append(r, NULL, err->message); g_string_append_c(r, '\n'); net_writestr(cc->net, r->str); g_string_free(r, TRUE); g_propagate_error(&cc->err, err); } } break; case ADCC_SND: if(cmd.argc < 4) { g_message("CC:%s: Invalid command: %s", net_remoteaddr(cc->net), msg); } else if(!cc->dl || cc->state != CCS_TRANSFER) { g_set_error_literal(&cc->err, 1, 0, "Protocol error."); g_message("CC:%s: Received message in wrong state: %s", net_remoteaddr(cc->net), msg); cc_disconnect(cc, TRUE); } else if(adc_getparam(cmd.argv, "ZL", NULL)) { // Even though we indicate support for ZLIG, we don't actually support // *receiving* zlib compressed transfers. So this is an error. // TODO: This is in violation with the ADC spec, to probably want to fix // this at some point. g_set_error_literal(&cc->err, 1, 0, "Protocol error."); g_message("CC:%s: Received zlib-compressed data when we didn't request it: %s", net_remoteaddr(cc->net), msg); cc_disconnect(cc, TRUE); } else handle_adcsnd(cc, strcmp(cmd.argv[0], "tthl") == 0, g_ascii_strtoull(cmd.argv[2], NULL, 0), g_ascii_strtoll(cmd.argv[3], NULL, 0)); break; case ADCC_GFI: if(cmd.argc < 2 || strcmp(cmd.argv[0], "file") != 0) { g_message("CC:%s: Invalid command: %s", net_remoteaddr(cc->net), msg); } else if(cc->dl || cc->state != CCS_IDLE) { g_set_error_literal(&cc->err, 1, 0, "Protocol error."); g_message("CC:%s: Received message in wrong state: %s", net_remoteaddr(cc->net), msg); cc_disconnect(cc, TRUE); } else { // Get file fl_list_t *f = NULL; if(cmd.argv[1][0] == '/' && fl_local_list) { f = fl_list_from_path(fl_local_list, cmd.argv[1]); } else if(strncmp(cmd.argv[1], "TTH/", 4) == 0 && istth(cmd.argv[1]+4)) { char root[24]; base32_decode(cmd.argv[1]+4, root); GSList *l = fl_local_from_tth(root); f = l ? l->data : NULL; } // Generate response GString *r; if(!f) { r = adc_generate('C', ADCC_STA, 0, 0); g_string_append_printf(r, " 151 File Not Available"); } else { r = adc_generate('C', ADCC_RES, 0, 0); g_string_append_printf(r, " SL%d SI%"G_GUINT64_FORMAT, var_get_int(0, VAR_slots) - cc_slots_in_use(NULL), f->size); char *path = fl_list_path(f); adc_append(r, "FN", path); g_free(path); if(f->isfile) { char tth[40] = {}; base32_encode(f->tth, tth); g_string_append_printf(r, " TR%s", tth); } else g_string_append_c(r, '/'); } g_string_append_c(r, '\n'); net_writestr(cc->net, r->str); g_string_free(r, TRUE); } break; case ADCC_STA: if(cmd.argc < 2 || strlen(cmd.argv[0]) != 3) { g_message("CC:%s: Invalid command: %s", net_remoteaddr(cc->net), msg); // Don't disconnect here for compatibility with old DC++ cores that // incorrectly send "0" instead of "000" as first argument. // Slots full } else if(cmd.argv[0][1] == '5' && cmd.argv[0][2] == '3') { if(!cc->dl || cc->state != CCS_TRANSFER) { g_set_error_literal(&cc->err, 1, 0, "Protocol error."); g_message("CC:%s: Received message in wrong state: %s", net_remoteaddr(cc->net), msg); cc_disconnect(cc, TRUE); } else { // Make a "slots full" message fatal; dl.c assumes this behaviour. g_set_error_literal(&cc->err, 1, 0, "No Slots Available"); cc_disconnect(cc, TRUE); } // File (Part) Not Available: notify dl.c } else if(cmd.argv[0][1] == '5' && (cmd.argv[0][2] == '1' || cmd.argv[0][2] == '2')) { if(!cc->dl || cc->state != CCS_TRANSFER) { g_set_error_literal(&cc->err, 1, 0, "Protocol error."); g_message("CC:%s: Received message in wrong state: %s", net_remoteaddr(cc->net), msg); cc_disconnect(cc, TRUE); } else { dl_queue_setuerr(cc->uid, cc->last_hash, DLE_NOFILE, NULL); cc->state = CCS_IDLE; dl_user_cc(cc->uid, cc); if(cc->dlthread) { dlfile_recv_done(cc->dlthread); cc->dlthread = NULL; } } // Other message } else if(cmd.argv[0][0] == '1' || cmd.argv[0][0] == '2') { g_set_error(&cc->err, 1, 0, "(%s) %s", cmd.argv[0], cmd.argv[1]); if(cmd.argv[0][0] == '2') cc_disconnect(cc, FALSE); } else if(!adc_getparam(cmd.argv, "RF", NULL)) g_message("CC:%s: Status: (%s) %s", net_remoteaddr(cc->net), cmd.argv[0], cmd.argv[1]); break; default: g_message("CC:%s: Unknown command: %s", net_remoteaddr(cc->net), msg); } g_strfreev(cmd.argv); } static void nmdc_mynick(cc_t *cc, const char *nick) { if(cc->nick_raw) { g_message("CC:%s: Received $MyNick twice.", net_remoteaddr(cc->net)); cc_disconnect(cc, TRUE); return; } cc->nick_raw = g_strdup(nick); // check the expects list cc_expect_nmdc_rm(cc); // didn't see this one coming? disconnect! if(!cc->hub) { g_message("CC:%s: Unexpected NMDC connection from %s.", net_remoteaddr(cc->net), nick); cc_disconnect(cc, FALSE); return; } hub_user_t *u = g_hash_table_lookup(cc->hub->users, nick); if(!u) { g_set_error_literal(&(cc->err), 1, 0, "User not online."); cc_disconnect(cc, FALSE); return; } handle_id(cc, u); if(cc->active) { net_writef(cc->net, "$MyNick %s|", cc->hub->nick_hub); net_writef(cc->net, "$Lock EXTENDEDPROTOCOL/wut? Pk=%s-%s|", PACKAGE_NAME, main_version); } } static void nmdc_direction(cc_t *cc, gboolean down, int num) { gboolean old_dl = cc->dl; // if they want to download and we don't, then it's simple. if(down && !cc->dl) ; // if we want to download and they don't, then it's just as simple. else if(cc->dl && !down) ; // if neither of us wants to download... then what the heck are we doing? else if(!down && !cc->dl) { g_message("CC:%s: None of us wants to download.", net_remoteaddr(cc->net)); g_set_error_literal(&cc->err, 1, 0, "Protocol error."); cc_disconnect(cc, FALSE); return; // if we both want to download and the numbers are equal... then fuck it! } else if(cc->dir == num) { g_message("CC:%s: $Direction numbers are equal.", net_remoteaddr(cc->net)); g_set_error_literal(&cc->err, 1, 0, "Protocol error."); cc_disconnect(cc, FALSE); return; // if we both want to download and the numbers aren't equal, then check the numbers } else cc->dl = cc->dir > num; // Now that this connection has a purpose, check the connection number limit. if(!cc_should_allow_connection(cc)) { g_set_error_literal(&cc->err, 1, 0, "Too many open connections with this user"); cc_disconnect(cc, FALSE); return; } cc->state = CCS_IDLE; // If we wanted to download, but didn't get the chance to do so, notify the dl manager. if(old_dl && !cc->dl) dl_user_cc(cc->uid, NULL); // If we reached the IDLE-dl state, notify the dl manager of this if(cc->dl) dl_user_cc(cc->uid, cc); } static void nmdc_handle(net_t *net, char *cmd, int _len) { cc_t *cc = net_handle(net); if(!*cmd) return; g_clear_error(&cc->err); g_return_if_fail(cc->state != CCS_CONN && cc->state != CCS_DISCONN); net_readmsg(net, '|', nmdc_handle); GMatchInfo *nfo; // create regexes (declared statically, allocated/compiled on first call) #define CMDREGEX(name, regex) \ static GRegex * name = NULL;\ if(!name) name = g_regex_new("\\$" regex, G_REGEX_OPTIMIZE|G_REGEX_ANCHORED|G_REGEX_DOTALL|G_REGEX_RAW, 0, NULL) CMDREGEX(mynick, "MyNick ([^ $]+)"); CMDREGEX(lock, "Lock ([^ $]+) Pk=[^ $]+"); CMDREGEX(supports, "Supports (.+)"); CMDREGEX(direction, "Direction (Download|Upload) ([0-9]+)"); CMDREGEX(adcget, "ADCGET ([^ ]+) (.+) ([0-9]+) (-?[0-9]+)"); CMDREGEX(adcsnd, "ADCSND (file|tthl) .+ ([0-9]+) (-?[0-9]+)"); CMDREGEX(error, "Error (.+)"); CMDREGEX(maxedout, "MaxedOut"); // $MyNick if(g_regex_match(mynick, cmd, 0, &nfo)) { // 1 = nick if(cc->state != CCS_HANDSHAKE) { g_set_error_literal(&cc->err, 1, 0, "Protocol error."); g_message("CC:%s: Received message in wrong state: %s", net_remoteaddr(cc->net), cmd); cc_disconnect(cc, TRUE); } else { char *nick = g_match_info_fetch(nfo, 1); nmdc_mynick(cc, nick); g_free(nick); } } g_match_info_free(nfo); // $Lock if(g_regex_match(lock, cmd, 0, &nfo)) { // 1 = lock char *lock = g_match_info_fetch(nfo, 1); if(cc->state != CCS_HANDSHAKE) { g_set_error_literal(&cc->err, 1, 0, "Protocol error."); g_message("CC:%s: Received message in wrong state: %s", net_remoteaddr(cc->net), cmd); cc_disconnect(cc, TRUE); // we don't implement the classic NMDC get, so we can't talk with non-EXTENDEDPROTOCOL clients } else if(strncmp(lock, "EXTENDEDPROTOCOL", 16) != 0) { g_set_error_literal(&cc->err, 1, 0, "Protocol error."); g_message("CC:%s: Does not advertise EXTENDEDPROTOCOL.", net_remoteaddr(cc->net)); cc_disconnect(cc, TRUE); } else { net_writestr(cc->net, "$Supports MiniSlots XmlBZList ADCGet TTHL TTHF|"); char *key = nmdc_lock2key(lock); cc->dir = cc->dl ? g_random_int_range(0, 65535) : -1; net_writef(cc->net, "$Direction %s %d|", cc->dl ? "Download" : "Upload", cc->dl ? cc->dir : 0); net_writef(cc->net, "$Key %s|", key); g_free(key); g_free(lock); } } g_match_info_free(nfo); // $Supports if(g_regex_match(supports, cmd, 0, &nfo)) { // 1 = list char *list = g_match_info_fetch(nfo, 1); if(cc->state != CCS_HANDSHAKE) { g_set_error_literal(&cc->err, 1, 0, "Protocol error."); g_message("CC:%s: Received message in wrong state: %s", net_remoteaddr(cc->net), cmd); cc_disconnect(cc, TRUE); // Client must support ADCGet to download from us, since we haven't implemented the old NMDC $Get. } else if(!strstr(list, "ADCGet")) { g_set_error_literal(&cc->err, 1, 0, "Protocol error."); g_message("CC:%s: Does not support ADCGet.", net_remoteaddr(cc->net)); cc_disconnect(cc, TRUE); } g_free(list); } g_match_info_free(nfo); // $Direction if(g_regex_match(direction, cmd, 0, &nfo)) { // 1 = dir, 2 = num if(cc->state != CCS_HANDSHAKE) { g_set_error_literal(&cc->err, 1, 0, "Protocol error."); g_message("CC:%s: Received message in wrong state: %s", net_remoteaddr(cc->net), cmd); cc_disconnect(cc, TRUE); } else { char *dir = g_match_info_fetch(nfo, 1); char *num = g_match_info_fetch(nfo, 2); nmdc_direction(cc, strcmp(dir, "Download") == 0, strtol(num, NULL, 10)); g_free(dir); g_free(num); } } g_match_info_free(nfo); // $ADCGET if(g_regex_match(adcget, cmd, 0, &nfo)) { // 1 = type, 2 = identifier, 3 = start_pos, 4 = bytes char *type = g_match_info_fetch(nfo, 1); char *id = g_match_info_fetch(nfo, 2); char *start = g_match_info_fetch(nfo, 3); char *bytes = g_match_info_fetch(nfo, 4); guint64 st = g_ascii_strtoull(start, NULL, 10); gint64 by = g_ascii_strtoll(bytes, NULL, 10); char *un_id = adc_unescape(id, TRUE); if(cc->dl || cc->state != CCS_IDLE) { g_set_error_literal(&cc->err, 1, 0, "Protocol error."); g_message("CC:%s: Received message in wrong state: %s", net_remoteaddr(cc->net), cmd); cc_disconnect(cc, TRUE); } else if(un_id && g_utf8_validate(un_id, -1, NULL)) { GError *err = NULL; handle_adcget(cc, type, un_id, st, by, FALSE, FALSE, &err); if(err) { if(err->code != 53) net_writef(cc->net, "$Error %s|", err->message); else net_writestr(cc->net, "$MaxedOut|"); g_propagate_error(&cc->err, err); } } g_free(un_id); g_free(type); g_free(id); g_free(start); g_free(bytes); } g_match_info_free(nfo); // $ADCSND if(g_regex_match(adcsnd, cmd, 0, &nfo)) { // 1 = file/tthl, 2 = start_pos, 3 = bytes if(!cc->dl || cc->state != CCS_TRANSFER) { g_set_error_literal(&cc->err, 1, 0, "Protocol error."); g_message("CC:%s: Received message in wrong state: %s", net_remoteaddr(cc->net), cmd); cc_disconnect(cc, TRUE); } else { char *type = g_match_info_fetch(nfo, 1); char *start = g_match_info_fetch(nfo, 2); char *bytes = g_match_info_fetch(nfo, 3); handle_adcsnd(cc, strcmp(type, "tthl") == 0, g_ascii_strtoull(start, NULL, 10), g_ascii_strtoll(bytes, NULL, 10)); g_free(type); g_free(start); g_free(bytes); } } g_match_info_free(nfo); // $Error if(g_regex_match(error, cmd, 0, &nfo)) { // 1 = message if(!cc->dl || cc->state != CCS_TRANSFER) { g_set_error_literal(&cc->err, 1, 0, "Protocol error."); g_message("CC:%s: Received message in wrong state: %s", net_remoteaddr(cc->net), cmd); cc_disconnect(cc, TRUE); } else { char *msg = g_match_info_fetch(nfo, 1); g_set_error_literal(&cc->err, 1, 0, msg); // Handle "File Not Available" and ".. no more exists" if(str_casestr(msg, "file not available") || str_casestr(msg, "no more exists")) dl_queue_setuerr(cc->uid, cc->last_hash, DLE_NOFILE, NULL); g_free(msg); cc->state = CCS_IDLE; dl_user_cc(cc->uid, cc); if(cc->dlthread) { dlfile_recv_done(cc->dlthread); cc->dlthread = NULL; } } } g_match_info_free(nfo); // $MaxedOut if(g_regex_match(maxedout, cmd, 0, &nfo)) { if(!cc->dl || cc->state != CCS_TRANSFER) { g_set_error_literal(&cc->err, 1, 0, "Protocol error."); g_message("CC:%s: Received message in wrong state: %s", net_remoteaddr(cc->net), cmd); } else g_set_error_literal(&cc->err, 1, 0, "No Slots Available"); cc_disconnect(cc, FALSE); } g_match_info_free(nfo); } static void handle_error(net_t *n, int action, const char *err) { cc_t *cc = net_handle(n); if(!cc->err) // ignore network errors if there already was a protocol error g_set_error_literal(&cc->err, 1, 0, err); // If we already were shutting down, that means this cc entry is already in // disconnected state. In that case, just force the net handle in // disconnected state as well. if(net_is_disconnecting(n)) net_disconnect(n); else cc_disconnect(net_handle(n), !net_is_asy(n) || action != NETERR_TIMEOUT); } // Hub may be unknown when this is an incoming connection cc_t *cc_create(hub_t *hub) { cc_t *cc = g_new0(cc_t, 1); cc->net = net_new(cc, handle_error); cc->hub = hub; cc->iter = g_sequence_append(cc_list, cc); cc->state = CCS_CONN; uit_conn_listchange(cc->iter, UITCONN_ADD); return cc; } // Simply stores the keyprint of the certificate in cc->kp_real, it will be // checked when receiving CINF. static void handle_handshake(net_t *n, const char *kpr, int proto) { cc_t *c = net_handle(n); if(kpr) { if(!c->kp_real) c->kp_real = g_slice_alloc(32); memcpy(c->kp_real, kpr, 32); } else if(c->kp_real) { g_slice_free1(32, c->kp_real); c->kp_real = NULL; } } static void handle_connect(net_t *n, const char *addr) { if(addr) return; cc_t *cc = net_handle(n); strncpy(cc->remoteaddr, net_remoteaddr(cc->net), sizeof(cc->remoteaddr)); if(!cc->hub) { cc_disconnect(cc, FALSE); return; } if(cc->tls) net_settls(cc->net, FALSE, FALSE, handle_handshake); if(!net_is_connected(cc->net)) return; if(cc->adc) { net_writestr(n, "CSUP ADBASE ADTIGR ADBZIP ADZLIG\n"); // Note that while http://www.adcportal.com/wiki/REF says we should send // the hostname used to connect to the hub, the actual IP is easier to get // in our case. I personally don't see how having a hostname is better than // having an actual IP, but an attacked user who gets incoming connections // from both ncdc and other clients now knows both the DNS *and* the IP of // the hub. :-) net_writef(n, "CSTA 000 referrer RFadc%s://%s\n", cc->hub->tls ? "s" : "", net_remoteaddr(cc->hub->net)); } else { net_writef(n, "$MyNick %s|", cc->hub->nick_hub); net_writef(n, "$Lock EXTENDEDPROTOCOL/wut? Pk=%s-%s,Ref=%s|", PACKAGE_NAME, main_version, net_remoteaddr(cc->hub->net)); } cc->state = CCS_HANDSHAKE; net_readmsg(cc->net, cc->adc ? '\n' : '|', cc->adc ? adc_handle : nmdc_handle); } void cc_nmdc_connect(cc_t *cc, const char *host, unsigned short port, const char *laddr, gboolean tls) { g_return_if_fail(cc->state == CCS_CONN); g_snprintf(cc->remoteaddr, sizeof(cc->remoteaddr), ip6_isvalid(host) ? "[%s]:%d" : "%s:%d", host, (int)port); cc->tls = tls; net_connect(cc->net, host, port, laddr, handle_connect); g_clear_error(&cc->err); } void cc_adc_connect(cc_t *cc, hub_user_t *u, const char *laddr, unsigned short port, gboolean tls, char *token) { g_return_if_fail(cc->state == CCS_CONN); g_return_if_fail(cc->hub); g_return_if_fail(u && u->active && !(ip4_isany(u->ip4) && ip6_isany(u->ip6))); cc->tls = tls; cc->adc = TRUE; cc->token = g_strdup(token); /* TODO: If the user has both ip4 and ip6, we should prefer the AF used to * connect to the hub, rather than ip4. */ const char *host = !ip4_isany(u->ip4) ? ip4_unpack(u->ip4) : ip6_unpack(u->ip6); g_snprintf(cc->remoteaddr, sizeof(cc->remoteaddr), ip4_isany(u->ip4) ? "[%s]:%d" : "%s:%d", host, (int)port); // check whether this was as a reply to a RCM from us cc_expect_adc_rm(cc, u->uid); if(!cc->kp_user && u->kp) { cc->kp_user = g_slice_alloc(32); memcpy(cc->kp_user, u->kp, 32); } // check / update user info handle_id(cc, u); // handle_id() can do a cc_disconnect() when it discovers a duplicate // connection. This will reset cc->token, and we should stop this connection // attempt. if(!cc->token) return; // connect net_connect(cc->net, host, port, laddr, handle_connect); g_clear_error(&cc->err); } static void handle_detectprotocol(net_t *net, char *dat, int len) { g_return_if_fail(len > 0); cc_t *cc = net_handle(net); // Enable TLS if(!cc->tls && *dat >= 0x14 && *dat <= 0x17) { cc->tls = TRUE; net_settls(cc->net, TRUE, FALSE, handle_handshake); if(net_is_connected(cc->net)) net_peekbytes(cc->net, 1, handle_detectprotocol); // Queue another detectprotocol to detect NMDC/ADC return; } if(dat[0] == 'C') cc->adc = TRUE; net_readmsg(cc->net, cc->adc ? '\n' : '|', cc->adc ? adc_handle : nmdc_handle); } void cc_incoming(cc_t *cc, guint16 port, int sock, const char *addr, gboolean v6) { net_connected(cc->net, sock, addr, v6); if(!net_is_connected(cc->net)) return; cc->port = port; cc->active = TRUE; cc->state = CCS_HANDSHAKE; net_peekbytes(cc->net, 1, handle_detectprotocol); strncpy(cc->remoteaddr, net_remoteaddr(cc->net), sizeof(cc->remoteaddr)); } static gboolean handle_timeout(gpointer dat) { cc_free(dat); return FALSE; } void cc_disconnect(cc_t *cc, gboolean force) { g_return_if_fail(cc->state != CCS_DISCONN); if(cc->dl && cc->uid) dl_user_cc(cc->uid, NULL); if(cc->dlthread) { dlfile_recv_done(cc->dlthread); cc->dlthread = NULL; } if(cc->state == CCS_TRANSFER) xfer_log_add(cc); if(force || !net_is_asy(cc->net)) net_disconnect(cc->net); else net_shutdown(cc->net, NULL); cc->timeout_src = g_timeout_add_seconds(60, handle_timeout, cc); g_free(cc->token); cc->token = NULL; cc->state = CCS_DISCONN; } void cc_free(cc_t *cc) { if(!cc->timeout_src) cc_disconnect(cc, TRUE); if(cc->timeout_src) g_source_remove(cc->timeout_src); uit_conn_listchange(cc->iter, UITCONN_DEL); g_sequence_remove(cc->iter); net_disconnect(cc->net); net_unref(cc->net); if(cc->err) g_error_free(cc->err); if(cc->kp_real) g_slice_free1(32, cc->kp_real); if(cc->kp_user) g_slice_free1(32, cc->kp_user); g_free(cc->nick_raw); g_free(cc->nick); g_free(cc->hub_name); g_free(cc->last_file); g_free(cc->cid); g_free(cc); } ncdc-1.23.1/src/uit_fl.c0000644000175000017500000003332414245144577011665 00000000000000/* ncdc - NCurses Direct Connect client Copyright (c) 2011-2022 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "ncdc.h" #include "uit_fl.h" ui_tab_type_t uit_fl[1]; typedef struct tab_t { ui_tab_t tab; ui_listing_t *list; guint64 uid; fl_list_t *fl; char *uname; char *sel; GError *err; time_t age; int order; gboolean reverse : 1; gboolean dirfirst : 1; gboolean loading : 1; gboolean needmatch : 1; } tab_t; // Columns to sort on #define SORT_NAME 0 #define SORT_SIZE 1 static gint sort_func(gconstpointer a, gconstpointer b, gpointer dat) { const fl_list_t *la = a; const fl_list_t *lb = b; tab_t *t = dat; // dirs before files if(t->dirfirst && !!la->isfile != !!lb->isfile) return la->isfile ? 1 : -1; int r = t->order == SORT_NAME ? fl_list_cmp(la, lb) : (la->size > lb->size ? 1 : la->size < lb->size ? -1 : 0); if(!r) r = t->order == SORT_NAME ? (la->size > lb->size ? 1 : la->size < lb->size ? -1 : 0) : fl_list_cmp(la, lb); return t->reverse ? -r : r; } static const char *get_name(GSequenceIter *iter) { fl_list_t *fl = g_sequence_get(iter); return fl->name; } static void setdir(tab_t *t, fl_list_t *fl, fl_list_t *sel) { // Free previously opened dir if(t->list) { g_sequence_free(t->list->list); ui_listing_free(t->list); } // Open this one and select *sel, if set t->fl = fl; GSequence *seq = g_sequence_new(NULL); GSequenceIter *seli = NULL; int i; for(i=0; isub->len; i++) { GSequenceIter *iter = g_sequence_insert_sorted(seq, g_ptr_array_index(fl->sub, i), sort_func, t); if(sel == g_ptr_array_index(fl->sub, i)) seli = iter; } t->list = ui_listing_create(seq, NULL, t, get_name); if(seli) t->list->sel = seli; } static void matchqueue(tab_t *t, fl_list_t *root) { if(!t->fl) { t->needmatch = TRUE; return; } if(!root) { root = t->fl; while(root->parent) root = root->parent; } int a = 0; int n = dl_queue_match_fl(t->uid, root, &a); ui_mf(NULL, 0, "Matched %d files, %d new.", n, a); t->needmatch = FALSE; } static void dosel(tab_t *t, fl_list_t *fl, const char *sel) { fl_list_t *root = fl; while(root->parent) root = root->parent; fl_list_t *n = fl_list_from_path(root, sel); if(!n) ui_mf((ui_tab_t *)t, 0, "Can't select `%s': item not found.", sel); // open the parent directory and select item setdir(t, n?n->parent:fl, n); } // Callback function for use in uit_fl_queue() - not associated with any tab. // Will just match the list against the queue and free it. static void loadmatch(fl_list_t *fl, GError *err, void *dat) { guint64 uid = *(guint64 *)dat; g_free(dat); hub_user_t *u = g_hash_table_lookup(hub_uids, &uid); char *user = u ? g_strdup_printf("%s on %s", u->name, u->hub->tab->name) : g_strdup_printf("%016"G_GINT64_MODIFIER"x (user offline)", uid); if(err) { ui_mf(uit_main_tab, 0, "Error opening file list of %s for matching: %s", user, err->message); g_error_free(err); } else { int a = 0; int n = dl_queue_match_fl(uid, fl, &a); ui_mf(NULL, 0, "Matched queue for %s: %d files, %d new.", user, n, a); fl_list_free(fl); } g_free(user); } // Open/match or queue a file list. (Not really a uit_* function, but where else would it belong?) void uit_fl_queue(guint64 uid, gboolean force, const char *sel, ui_tab_t *parent, gboolean open, gboolean match) { if(!open && !match) return; hub_user_t *u = g_hash_table_lookup(hub_uids, &uid); // check for u == we if(u && (u->hub->adc ? u->hub->sid == u->sid : u->hub->nick_valid && strcmp(u->hub->nick_hub, u->name_hub) == 0)) { u = NULL; uid = 0; } g_warn_if_fail(uid || !match); // check for existing tab GList *n; tab_t *t; for(n=ui_tabs; n; n=n->next) { t = n->data; if(t->tab.type == uit_fl && t->uid == uid) break; } if(n) { if(open) ui_tab_cur = n; if(sel) { if(!t->loading && t->fl) dosel(n->data, t->fl, sel); else if(t->loading) { g_free(t->sel); t->sel = g_strdup(sel); } } if(match) matchqueue(t, NULL); return; } // open own list if(!uid) { if(open) ui_tab_open(uit_fl_create(0, sel), TRUE, parent); return; } // check for cached file list, otherwise queue it char *tmp = g_strdup_printf("%016"G_GINT64_MODIFIER"x.xml.bz2", uid); char *fn = g_build_filename(db_dir, "fl", tmp, NULL); g_free(tmp); gboolean e = !force; if(!force) { struct stat st; int age = var_get_int(0, VAR_filelist_maxage); e = stat(fn, &st) < 0 || st.st_mtime < time(NULL)-MAX(age, 30) ? FALSE : TRUE; } if(e) { if(open) { ui_tab_t *tab = uit_fl_create(uid, sel); ui_tab_open(tab, TRUE, parent); if(match) matchqueue((tab_t *)tab, NULL); } else if(match) fl_load_async(fn, loadmatch, g_memdup(&uid, 8)); } else { g_return_if_fail(u); // the caller should have checked this dl_queue_addlist(u, sel, parent, open, match); ui_mf(NULL, 0, "File list of %s added to the download queue.", u->name); } g_free(fn); } static void loaddone(fl_list_t *fl, GError *err, void *dat) { // If the tab has been closed, then we can ignore the result if(!g_list_find(ui_tabs, dat)) { if(fl) fl_list_free(fl); if(err) g_error_free(err); return; } // Otherwise, update state tab_t *t = dat; t->err = err; t->loading = FALSE; ui_tab_incprio((ui_tab_t *)t, err ? UIP_HIGH : UIP_MED); if(t->sel) { if(fl) dosel(t, fl, t->sel); g_free(t->sel); t->sel = NULL; } else if(fl) setdir(t, fl, NULL); } ui_tab_t *uit_fl_create(guint64 uid, const char *sel) { // get user hub_user_t *u = uid ? g_hash_table_lookup(hub_uids, &uid) : NULL; // create tab tab_t *t = g_new0(tab_t, 1); t->tab.type = uit_fl; t->tab.name = !uid ? g_strdup("/own") : u ? g_strdup_printf("/%s", u->name) : g_strdup_printf("/%016"G_GINT64_MODIFIER"x", uid); t->uname = u ? g_strdup(u->name) : NULL; t->uid = uid; t->dirfirst = TRUE; t->order = SORT_NAME; time(&t->age); // get file list if(!uid) { fl_list_t *fl = fl_local_list ? fl_list_copy(fl_local_list) : NULL; ui_tab_incprio((ui_tab_t *)t, UIP_MED); if(fl && fl->sub && sel) dosel(t, fl, sel); else if(fl && fl->sub) setdir(t, fl, NULL); } else { char *tmp = g_strdup_printf("%016"G_GINT64_MODIFIER"x.xml.bz2", uid); char *fn = g_build_filename(db_dir, "fl", tmp, NULL); struct stat st; if(stat(fn, &st) >= 0) t->age = st.st_mtime; fl_load_async(fn, loaddone, t); g_free(tmp); g_free(fn); ui_tab_incprio((ui_tab_t *)t, UIP_LOW); t->loading = TRUE; if(sel) t->sel = g_strdup(sel); } return (ui_tab_t *)t; } static void t_close(ui_tab_t *tab) { tab_t *t = (tab_t *)tab; ui_tab_remove(tab); if(t->list) { g_sequence_free(t->list->list); ui_listing_free(t->list); } fl_list_t *p = t->fl; while(p && p->parent) p = p->parent; if(p) fl_list_free(p); if(t->err) g_error_free(t->err); g_free(t->tab.name); g_free(t->sel); g_free(t->uname); g_free(t); } static char *t_title(ui_tab_t *tab) { tab_t *t = (tab_t *)tab; char *tt = !t->uid ? g_strdup_printf("Browsing own file list.") : t->uname ? g_strdup_printf("Browsing file list of %s (%016"G_GINT64_MODIFIER"x)", t->uname, t->uid) : g_strdup_printf("Browsing file list of %016"G_GINT64_MODIFIER"x (user offline)", t->uid); char *tn = g_strdup_printf("%s [%s]", tt, str_formatinterval(60*((time(NULL)-t->age)/60))); g_free(tt); return tn; } static void draw_row(ui_listing_t *list, GSequenceIter *iter, int row, void *dat) { fl_list_t *fl = g_sequence_get(iter); tab_t *t = dat; attron(iter == list->sel ? UIC(list_select) : UIC(list_default)); mvhline(row, 0, ' ', wincols); if(iter == list->sel) mvaddstr(row, 0, ">"); if(t->uid) // add shared/queued flags only while browsing others' lists. mvaddch(row, 2, ui_file_flag(fl->tth)); else mvaddch(row, 2, fl->isfile && !fl->hastth ? 'H' :' '); mvaddstr(row, 4, str_formatsize(fl->size)); if(!fl->isfile) mvaddch(row, 17, '/'); ui_listing_draw_match(list, iter, row, 18, str_offset_from_columns(fl->name, wincols-19)); attroff(iter == list->sel ? UIC(list_select) : UIC(list_default)); } static void t_draw(ui_tab_t *tab) { tab_t *t = (tab_t *)tab; // first line mvhline(1, 0, ACS_HLINE, wincols); mvaddch(1, 3, ' '); char *path = t->list ? fl_list_path(t->fl) : g_strdup("/"); int c = str_columns(path) - wincols + 8; mvaddstr(1, 4, path+str_offset_from_columns(path, MAX(0, c))); g_free(path); addch(' '); // rows int pos = -1; ui_cursor_t cursor = { 0, 2 }; if(t->loading) mvaddstr(3, 2, "Loading filelist..."); else if(t->err) mvprintw(3, 2, "Error loading filelist: %s", t->err->message); else if(t->fl && t->fl->sub && t->fl->sub->len) pos = ui_listing_draw(t->list, 2, winrows-4, &cursor, draw_row); else mvaddstr(3, 2, "Directory empty."); // footer fl_list_t *sel = pos >= 0 && !g_sequence_iter_is_end(t->list->sel) ? g_sequence_get(t->list->sel) : NULL; attron(UIC(separator)); mvhline(winrows-3, 0, ' ', wincols); if(pos >= 0) mvprintw(winrows-3, wincols-34, "%6d items %s %3d%%", t->fl->sub->len, str_formatsize(t->fl->size), pos); if(sel && sel->isfile) { if(!sel->hastth) mvaddstr(winrows-3, 0, "Not hashed yet, this file is not visible to others."); else { char hash[40] = {}; base32_encode(sel->tth, hash); mvaddstr(winrows-3, 0, hash); mvprintw(winrows-3, 40, "(%s bytes)", str_fullsize(sel->size)); } } if(sel && !sel->isfile) { int num = sel->sub ? sel->sub->len : 0; if(!num) mvaddstr(winrows-3, 0, " Selected directory is empty."); else mvprintw(winrows-3, 0, " %d items, %s bytes", num, str_fullsize(sel->size)); } attroff(UIC(separator)); move(cursor.y, cursor.x); } static void t_key(ui_tab_t *tab, guint64 key) { tab_t *t = (tab_t *)tab; if(t->list && ui_listing_key(t->list, key, winrows/2)) return; fl_list_t *sel = !t->list || g_sequence_iter_is_end(t->list->sel) ? NULL : g_sequence_get(t->list->sel); gboolean sort = FALSE; switch(key) { case INPT_CHAR('?'): uit_main_keys("browse"); break; case INPT_CTRL('j'): // newline case INPT_KEY(KEY_RIGHT): // right case INPT_CHAR('l'): // l open selected directory if(sel && !sel->isfile && sel->sub) setdir(t, sel, NULL); break; case INPT_CTRL('h'): // backspace case INPT_KEY(KEY_LEFT): // left case INPT_CHAR('h'): // h open parent directory if(t->fl && t->fl->parent) setdir(t, t->fl->parent, t->fl); break; // Sorting #define SETSORT(c) \ t->reverse = t->order == c ? !t->reverse : FALSE;\ t->order = c;\ sort = TRUE; case INPT_CHAR('s'): // s - sort on file size SETSORT(SORT_SIZE); break; case INPT_CHAR('n'): // n - sort on file name SETSORT(SORT_NAME); break; case INPT_CHAR('t'): // t - toggle sorting dirs before files t->dirfirst = !t->dirfirst; sort = TRUE; break; #undef SETSORT case INPT_CHAR('d'): // d (download) if(!sel) ui_m(NULL, 0, "Nothing selected."); else if(!t->uid) ui_m(NULL, 0, "Can't download from yourself."); else if(!sel->isfile && fl_list_isempty(sel)) ui_m(NULL, 0, "Directory empty."); else { g_return_if_fail(!sel->isfile || sel->hastth); char *excl = var_get(0, VAR_download_exclude); GRegex *r = excl ? g_regex_new(excl, 0, 0, NULL) : NULL; dl_queue_add_fl(t->uid, sel, NULL, r); if(r) g_regex_unref(r); } break; case INPT_CHAR('m'): // m - match queue with selected file/dir case INPT_CHAR('M'): // M - match queue with entire file list if(!t->fl) ui_m(NULL, 0, "File list empty."); else if(!t->uid) ui_m(NULL, 0, "Can't download from yourself."); else if(key == INPT_CHAR('m') && !sel) ui_m(NULL, 0, "Nothing selected."); else matchqueue(t, key == INPT_CHAR('m') ? sel : NULL); break; case INPT_CHAR('a'): // a - search for alternative sources if(!sel) ui_m(NULL, 0, "Nothing selected."); else if(!sel->isfile) ui_m(NULL, 0, "Can't look for alternative sources for directories."); else if(!sel->hastth) ui_m(NULL, 0, "No TTH hash known."); else uit_search_open_tth(sel->tth, tab); break; } if(sort && t->fl) { g_sequence_sort(t->list->list, sort_func, t); ui_listing_sorted(t->list); ui_mf(NULL, 0, "Ordering by %s (%s%s)", t->order == SORT_NAME ? "file name" : "file size", t->reverse ? "descending" : "ascending", t->dirfirst ? ", dirs first" : ""); } } ui_tab_type_t uit_fl[1] = { { t_draw, t_title, t_key, t_close } }; ncdc-1.23.1/src/fl_load.c0000644000175000017500000002417614245144476012006 00000000000000/* ncdc - NCurses Direct Connect client Copyright (c) 2011-2022 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "ncdc.h" #include "fl_load.h" #include #define STACKSIZE (8*1024) #define READBUFSIZE (32*1024) // Only used for attributes that we care about, and those tend to be short, // file names being the longest possible values. I am unaware of a filesystem // that allows filenames longer than 256 bytes, so this should be a safe value. #define MAXATTRVAL 1024 #define S_START 0 // waiting for #define S_FLOPEN 1 // In a #define S_DIROPEN 2 // In a #define S_INDIR 3 // In a .. or .. #define S_FILEOPEN 4 // In a #define S_INFILE 5 // In a .. typedef struct ctx_t { gboolean local; int state; char filetth[24]; gboolean filehastth; guint64 filesize; char *name; fl_list_t *root; fl_list_t *cur; int unknown_level; int consume; char *attrp; char attr[MAXATTRVAL]; yxml_t x; char stack[STACKSIZE]; char buf[READBUFSIZE]; } ctx_t; #define isvalidfilename(x) (\ !(((x)[0] == '.' && (!(x)[1] || ((x)[1] == '.' && !(x)[2])))) && !strchr((x), '/')) static void fl_load_token(ctx_t *x, yxml_ret_t r, GError **err) { // Detect the end of the attributes for an open XML element. if(r != YXML_ATTRSTART && r != YXML_ATTRVAL && r != YXML_ATTREND) { if(x->state == S_DIROPEN) { if(!x->name) { g_set_error_literal(err, 1, 0, "Missing Name attribute in Directory element"); return; } fl_list_t *new = fl_list_create(x->name, FALSE); new->isfile = FALSE; new->sub = g_ptr_array_new_with_free_func(fl_list_free); fl_list_add(x->cur, new, -1); x->cur = new; g_free(x->name); x->name = NULL; x->state = S_INDIR; } else if(x->state == S_FILEOPEN) { if(!x->name || !x->filehastth || x->filesize == G_MAXUINT64) { g_set_error(err, 1, 0, "Missing %s attribute in File element", !x->name ? "Name" : !x->filehastth ? "TTH" : "Size"); return; } // Create the file entry fl_list_t *new = fl_list_create(x->name, x->local); new->isfile = TRUE; new->size = x->filesize; new->hastth = TRUE; memcpy(new->tth, x->filetth, 24); fl_list_add(x->cur, new, -1); x->filehastth = FALSE; x->filesize = G_MAXUINT64; g_free(x->name); x->name = NULL; x->state = S_INFILE; } else if(x->state == S_FLOPEN) x->state = S_INDIR; } switch(r) { case YXML_ELEMSTART: if(x->unknown_level) x->unknown_level++; else if(x->state == S_START) { if(g_ascii_strcasecmp(x->x.elem, "FileListing") == 0) x->state = S_FLOPEN; else g_set_error_literal(err, 1, 0, "XML root element is not "); } else { if(g_ascii_strcasecmp(x->x.elem, "File") == 0) x->state = S_FILEOPEN; else if(g_ascii_strcasecmp(x->x.elem, "Directory") == 0) { if(x->state == S_INFILE) g_set_error_literal(err, 1, 0, "Invalid inside a "); else x->state = S_DIROPEN; } else x->unknown_level++; } break; case YXML_ELEMEND: if(x->unknown_level) x->unknown_level--; else if(x->state == S_INFILE) x->state = S_INDIR; else { fl_list_sort(x->cur); x->cur = x->cur->parent; } break; case YXML_ATTRSTART: x->consume = !x->unknown_level && ( (x->state == S_DIROPEN && g_ascii_strcasecmp(x->x.attr, "Name") == 0) || (x->state == S_FILEOPEN && ( g_ascii_strcasecmp(x->x.attr, "Name") == 0 || g_ascii_strcasecmp(x->x.attr, "Size") == 0 || g_ascii_strcasecmp(x->x.attr, "TTH") == 0 )) ); x->attrp = x->attr; break; case YXML_ATTRVAL: if(!x->consume) break; if(x->attrp-x->attr > sizeof(x->attr)-5) { g_set_error_literal(err, 1, 0, "Too long XML attribute"); return; } char *v = x->x.data; while(*v) *(x->attrp++) = *(v++); break; case YXML_ATTREND: if(!x->consume) break; *x->attrp = 0; // Name, for either file or directory if((*x->x.attr|32) == 'n' && !x->name) { x->name = g_utf8_validate(x->attr, -1, NULL) ? g_strdup(x->attr) : str_convert("UTF-8", "UTF-8", x->attr); if(!isvalidfilename(x->name)) g_set_error_literal(err, 1, 0, "Invalid file name"); } // TTH, for files if((*x->x.attr|32) == 't' && !x->filehastth) { if(!istth(x->attr)) g_set_error_literal(err, 1, 0, "Invalid TTH"); else { base32_decode(x->attr, x->filetth); x->filehastth = TRUE; } } // Size, for files if((*x->x.attr|32) == 's' && x->filesize == G_MAXUINT64) { char *end = NULL; x->filesize = g_ascii_strtoull(x->attr, &end, 10); if(!end || *end) g_set_error_literal(err, 1, 0, "Invalid file size"); } break; default: break; } } static int fl_load_readbz(bz_stream *bzs, int fd, char *bzbuf, GError **err) { int buflen; bzs->next_in = bzbuf; if(bzs->avail_in == 0) { buflen = read(fd, bzs->next_in + bzs->avail_in, READBUFSIZE - bzs->avail_in); if(buflen == 0) return -1; if(buflen < 0) { g_set_error(err, 1, 0, "Read error: %s", g_strerror(errno)); return -1; } bzs->avail_in += buflen; } int bzerr = BZ2_bzDecompress(bzs); if(bzerr == BZ_STREAM_END) { BZ2_bzDecompressEnd(bzs); BZ2_bzDecompressInit(bzs, 0, 0); } else if(bzerr != BZ_OK) { g_set_error(err, 1, 0, "bzip2 decompression error (%d): %s", bzerr, g_strerror(errno)); return -1; } memmove(bzbuf, bzs->next_in, bzs->avail_in); bzs->next_in = bzbuf; return READBUFSIZE-bzs->avail_out; } static fl_list_t *fl_load_parse(int fd, bz_stream *bzs, gboolean local, GError **err) { ctx_t *x = g_new(ctx_t, 1); x->state = S_START; x->root = fl_list_create("", FALSE); x->root->sub = g_ptr_array_new_with_free_func(fl_list_free); x->cur = x->root; x->filesize = G_MAXUINT64; x->local = local; x->unknown_level = 0; x->filehastth = FALSE; x->name = NULL; yxml_init(&x->x, x->stack, STACKSIZE); int buflen = 0; char *bzbuf = NULL; while(1) { // Fill buffer if(bzs) { if(!bzbuf) bzbuf = g_malloc(READBUFSIZE); bzs->next_out = x->buf; bzs->avail_out = READBUFSIZE; buflen = fl_load_readbz(bzs, fd, bzbuf, err); if(buflen < 0) break; } else { buflen = read(fd, x->buf, READBUFSIZE); if(buflen == 0) break; if(buflen < 0) { g_set_error(err, 1, 0, "Read error: %s", g_strerror(errno)); break; } } // And parse char *pbuf = x->buf; while(!*err && buflen > 0) { yxml_ret_t r = yxml_parse(&x->x, *pbuf); pbuf++; buflen--; if(r == YXML_OK) continue; if(r < 0) { g_set_error_literal(err, 1, 0, "XML parsing error"); break; } fl_load_token(x, r, err); } if(*err) { g_prefix_error(err, "Line %"G_GUINT32_FORMAT":%"G_GUINT64_FORMAT": ", x->x.line, x->x.byte); break; } } if(!*err && yxml_eof(&x->x) < 0) g_set_error_literal(err, 1, 0, "XML document did not end correctly"); fl_list_t *root = x->root; g_free(bzbuf); g_free(x->name); g_free(x); return root; } fl_list_t *fl_load(const char *file, GError **err, gboolean local) { g_return_val_if_fail(err == NULL || *err == NULL, NULL); fl_list_t *root = NULL; int fd; bz_stream *bzs = NULL; GError *ierr = NULL; // open file fd = open(file, O_RDONLY); if(fd < 0) { g_set_error_literal(&ierr, 1, 0, g_strerror(errno)); goto end; } // Create BZ2 stream object if this is a bzip2 file if(strlen(file) > 4 && strcmp(file+(strlen(file)-4), ".bz2") == 0) { bzs = g_new0(bz_stream, 1); BZ2_bzDecompressInit(bzs, 0, 0); } root = fl_load_parse(fd, bzs, local, &ierr); end: if(bzs) { BZ2_bzDecompressEnd(bzs); g_free(bzs); } if(fd >= 0) close(fd); if(ierr) { g_propagate_error(err, ierr); if(root) fl_list_free(root); root = NULL; } return root; } // Async version of fl_load(). Performs the load in a background thread. Only // used for non-local filelists. typedef struct async_t { char *file; void (*cb)(fl_list_t *, GError *, void *); void *dat; GError *err; fl_list_t *fl; } async_t; static gboolean async_d(gpointer dat) { async_t *arg = dat; arg->cb(arg->fl, arg->err, arg->dat); g_free(arg->file); g_slice_free(async_t, arg); return FALSE; } static void async_f(gpointer dat, gpointer udat) { async_t *arg = dat; arg->fl = fl_load(arg->file, &arg->err, FALSE); g_idle_add(async_d, arg); } // Ownership of both the file list and the error is passed to the callback // function. void fl_load_async(const char *file, void (*cb)(fl_list_t *, GError *, void *), void *dat) { static GThreadPool *pool = NULL; if(!pool) pool = g_thread_pool_new(async_f, NULL, 2, FALSE, NULL); async_t *arg = g_slice_new0(async_t); arg->file = g_strdup(file); arg->dat = dat; arg->cb = cb; g_thread_pool_push(pool, arg, NULL); } ncdc-1.23.1/src/hub.c0000644000175000017500000020237114245144513011147 00000000000000/* ncdc - NCurses Direct Connect client Copyright (c) 2011-2022 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "ncdc.h" #include "hub.h" #if INTERFACE struct hub_user_t { gboolean hasinfo : 1; gboolean isop : 1; gboolean isjoined : 1; // managed by uit_hub_userchange() gboolean active : 1; gboolean hasudp4 : 1; gboolean hasudp6 : 1; gboolean hastls : 1; // NMDC: 0x10 flag in $MyINFO; ADC: SU has ADCS or ADC0 gboolean hasadc0 : 1; // (ADC) Whether the SU flag was ADC0 (otherwise it was ADCS) unsigned char h_norm; unsigned char h_reg; unsigned char h_op; unsigned char slots; unsigned short udp4; unsigned short udp6; unsigned int as; // auto-open slot if upload is below n bytes/s int sid; // for ADC struct in_addr ip4; struct in6_addr ip6; hub_t *hub; char *name; // UTF-8 char *name_hub; // hub-encoded (NMDC) char *desc; char *conn; // NMDC: string pointer, ADC: GUINT_TO_POINTER() of the US param char *mail; char *client; guint64 uid; guint64 sharesize; char *kp; // ADC with KEYP, 32 bytes slice-alloc'ed GSequenceIter *iter; // used by ui_userlist_* } struct hub_t { gboolean adc : 16; // TRUE = ADC, FALSE = NMDC protocol. gboolean tls : 16; int state; // (ADC) ADC_S_* ui_tab_t *tab; net_t *net; // Hub info / config guint64 id; // "hubid" number char *hubname; // UTF-8, or NULL when unknown char *hubname_hub; // (NMDC) in hub encoding // Our user info char *nick_hub; // (NMDC) in hub encoding char *nick; // UTF-8 int sid; // (ADC) session ID char *ip; // Our IP, as received from the hub gboolean nick_valid : 1; // TRUE is the above nick has also been validated (and we're properly logged in) gboolean isreg : 1; // whether we used a password to login gboolean isop : 1; // whether we're an OP or not // User list information int sharecount; guint64 sharesize; GHashTable *users; // key = username (in hub encoding for NMDC) GHashTable *sessions; // (ADC) key = sid // (NMDC) what we and the hub support gboolean supports_nogetinfo; // Timers guint nfo_timer; // hub_send_nfo() timer guint reconnect_timer; // reconnect timer // ADC login info char *gpa_salt; int gpa_salt_len; // TLS certificate verification char *kp; // NULL if it matches config, 32 bytes slice-alloced otherwise // last info we sent to the hub char *nfo_desc, *nfo_conn, *nfo_mail, *nfo_ip; unsigned char nfo_slots, nfo_free_slots, nfo_h_norm, nfo_h_reg, nfo_h_op; guint64 nfo_share; guint16 nfo_udp_port; gboolean nfo_sup_tls, nfo_sup_sudp; // userlist fetching detection gboolean received_first; // true if one precondition for joincomplete is satisfied. gboolean joincomplete; // if we have the userlist guint joincomplete_timer; // fallback timer which ensures joincomplete is set at some point }; #define hub_init_global() do {\ hub_uids = g_hash_table_new(g_int64_hash, g_int64_equal);\ hubs = g_hash_table_new(g_int64_hash, g_int64_equal);\ } while(0) #endif // Global hash table of all users, with UID being the index and hub_user struct // as value. GHashTable *hub_uids = NULL; // Global hash table of all opened (but not necessarily connected) hubs, with // hubid being the index and hub_t as value. GHashTable *hubs = NULL; // hub_user_t related functions /* Generate user ID for ADC */ guint64 hub_user_adc_id(guint64 hubid, const char *cid) { tiger_ctx_t t; char tmp[MAX(MAXCIDLEN, 24)]; guint64 uid; tiger_init(&t); tiger_update(&t, (char *)&hubid, 8); base32_decode(cid, tmp); tiger_update(&t, tmp, (strlen(cid)*5)/8); tiger_final(&t, tmp); memcpy(&uid, tmp, 8); return uid; } guint64 hub_user_nmdc_id(guint64 hubid, const char *name) { tiger_ctx_t t; guint64 uid; char tmp[24]; tiger_init(&t); tiger_update(&t, (char *)&hubid, 8); tiger_update(&t, name, strlen(name)); tiger_final(&t, tmp); memcpy(&uid, tmp, 8); return uid; } // cid is required for ADC. expected to be base32-encoded. static hub_user_t *user_add(hub_t *hub, const char *name, const char *cid) { hub_user_t *u = g_hash_table_lookup(hub->users, name); if(u) return u; u = g_slice_new0(hub_user_t); u->hub = hub; if(hub->adc) { u->name = g_strdup(name); u->uid = hub_user_adc_id(hub->id, cid); } else { u->name_hub = g_strdup(name); u->name = charset_convert(hub, TRUE, name); u->uid = hub_user_nmdc_id(hub->id, name); } // insert in hub->users g_hash_table_insert(hub->users, hub->adc ? u->name : u->name_hub, u); // insert in hub_uids if(g_hash_table_lookup(hub_uids, &(u->uid))) g_critical("Duplicate user or hash collision for %s @ %s", u->name, hub->tab->name); else g_hash_table_insert(hub_uids, &(u->uid), u); // notify the UI uit_hub_userchange(hub->tab, UIHUB_UC_JOIN, u); // notify the dl manager if(hub->nick_valid) dl_user_join(u->uid); return u; } static void user_free(gpointer dat) { hub_user_t *u = dat; // remove from hub_uids g_hash_table_remove(hub_uids, &(u->uid)); // remove from hub->sessions if(u->hub->adc && u->sid) g_hash_table_remove(u->hub->sessions, GINT_TO_POINTER(u->sid)); // free if(u->kp) g_slice_free1(32, u->kp); g_free(u->name_hub); g_free(u->name); g_free(u->desc); if(!u->hub->adc) g_free(u->conn); g_free(u->mail); g_free(u->client); g_slice_free(hub_user_t, u); } // Get a user by a UTF-8 string. May fail for NMDC if the UTF-8 -> hub encoding // is not really one-to-one. hub_user_t *hub_user_get(hub_t *hub, const char *name) { if(hub->adc) return g_hash_table_lookup(hub->users, name); char *name_hub = charset_convert(hub, FALSE, name); hub_user_t *u = g_hash_table_lookup(hub->users, name_hub); g_free(name_hub); return u; } // Auto-complete suggestions for hub_user_get() void hub_user_suggest(hub_t *hub, char *str, char **sug) { GHashTableIter iter; hub_user_t *u; int i=0, len = strlen(str); g_hash_table_iter_init(&iter, hub->users); while(i<20 && g_hash_table_iter_next(&iter, NULL, (gpointer *)&u)) if(g_ascii_strncasecmp(u->name, str, len) == 0) sug[i++] = g_strdup(u->name); qsort(sug, i, sizeof(char *), cmpstringp); } #if INTERFACE #define hub_user_conn(u) (!(u)->conn ? NULL :\ (u)->hub->adc ? g_strdup_printf("%d KiB/s", GPOINTER_TO_UINT((u)->conn)/1024) : g_strdup((u)->conn)) #define hub_user_ip(u, def) (!ip4_isany((u)->ip4) ? ip4_unpack((u)->ip4) : !ip6_isany((u)->ip6) ? ip6_unpack((u)->ip6) : def) #endif char *hub_user_tag(hub_user_t *u) { if(!u->client || !u->slots) return NULL; GString *t = g_string_new(""); g_string_printf(t, "<%s,M:%c,H:%d/%d/%d,S:%d", u->client, u->active ? 'A' : 'P', u->h_norm, u->h_reg, u->h_op, u->slots); if(u->as) g_string_append_printf(t, ",O:%d", u->as/1024); g_string_append_c(t, '>'); return g_string_free(t, FALSE); } #define cleanspace(str) do {\ while(*(str) == ' ')\ (str)++;\ while((str)[0] && (str)[strlen(str)-1] == ' ')\ (str)[strlen(str)-1] = 0;\ } while(0) static void user_nmdc_nfo(hub_t *hub, hub_user_t *u, char *str) { // these all point into *str. *str is modified to contain zeroes in the correct positions char *next, *tmp; char *desc = NULL; char *client = NULL; char *conn = NULL; char *mail = NULL; gboolean active = FALSE; unsigned char h_norm = 0; unsigned char h_reg = 0; unsigned char h_op = 0; unsigned char slots = 0; unsigned char flags = 0; unsigned int as = 0; guint64 share = 0; if(!(next = strchr(str, '$')) || strlen(next) < 3 || next[2] != '$') return; if(next[1] == 'A') active = TRUE; *next = 0; next += 3; // tag if(str[0] && str[strlen(str)-1] == '>' && (tmp = strrchr(str, '<'))) { *tmp = 0; tmp++; tmp[strlen(tmp)-1] = 0; // tmp now points to the contents of the tag char *t; #define L(s) do {\ if(!client)\ client = tmp;\ else if(strcmp(tmp, "M:A") == 0)\ active = TRUE;\ else\ (void) (sscanf(tmp, "H:%hhu/%hhu/%hhu", &h_norm, &h_reg, &h_op)\ || sscanf(tmp, "S:%hhu", &slots)\ || sscanf(tmp, "O:%u", &as));\ } while(0) while((t = strchr(tmp, ','))) { *t = 0; L(tmp); tmp = t+1; } L(tmp); #undef L } // description desc = str; cleanspace(desc); // connection and flag str = next; if(!(next = strchr(str, '$'))) return; if(str != next) { flags = *(next-1); *(next-1) = 0; } *next = 0; next++; conn = str; cleanspace(conn); // email str = next; if(!(next = strchr(str, '$'))) return; *next = 0; next++; mail = str; cleanspace(mail); // share str = next; if(!(next = strchr(str, '$'))) return; *next = 0; share = g_ascii_strtoull(str, NULL, 10); // If we still haven't 'return'ed yet, that means we have a correct $MyINFO. Now we can update the struct. g_free(u->desc); g_free(u->client); g_free(u->conn); g_free(u->mail); u->sharesize = share; u->desc = desc[0] ? nmdc_unescape_and_decode(hub, desc) : NULL; u->client = client && client[0] ? g_strdup(client) : NULL; u->conn = conn[0] ? nmdc_unescape_and_decode(hub, conn) : NULL; u->mail = mail[0] ? nmdc_unescape_and_decode(hub, mail) : NULL; u->h_norm = h_norm; u->h_reg = h_reg; u->h_op = h_op; u->slots = slots; u->as = as*1024; u->hasinfo = TRUE; u->active = active; u->hastls = (flags & 0x10) ? TRUE : FALSE; uit_hub_userchange(hub->tab, UIHUB_UC_NFO, u); } #undef cleanspace #define P(a,b) (((a)<<8) + (b)) static void user_adc_nfo(hub_t *hub, hub_user_t *u, adc_cmd_t *cmd) { u->hasinfo = TRUE; // sid if(!u->sid) { g_hash_table_insert(hub->sessions, GINT_TO_POINTER(cmd->source), u); u->sid = cmd->source; } // This is faster than calling adc_getparam() each time char **n; for(n=cmd->argv; n&&*n; n++) { if(strlen(*n) < 2) continue; char *p = *n+2; switch(P(**n, (*n)[1])) { case P('N','I'): // nick g_hash_table_steal(hub->users, u->name); g_free(u->name); u->name = g_strdup(p); g_hash_table_insert(hub->users, u->name, u); break; case P('D','E'): // description g_free(u->desc); u->desc = p[0] ? g_strdup(p) : NULL; break; case P('V','E'): // client name (+ version) g_free(u->client); char *ap = adc_getparam(cmd->argv, "AP", NULL); u->client = !p[0] ? NULL : !ap || strncmp(p, ap, strlen(ap)) == 0 ? g_strdup(p) : g_strdup_printf("%s %s", ap, p); break; case P('E','M'): // mail g_free(u->mail); u->mail = p[0] ? g_strdup(p) : NULL; break; case P('S','S'): // share size u->sharesize = g_ascii_strtoull(p, NULL, 10); break; case P('H','N'): // h_norm u->h_norm = strtol(p, NULL, 10); break; case P('H','R'): // h_reg u->h_reg = strtol(p, NULL, 10); break; case P('H','O'): // h_op u->h_op = strtol(p, NULL, 10); break; case P('S','L'): // slots u->slots = strtol(p, NULL, 10); break; case P('A','S'): // as u->slots = strtol(p, NULL, 10); break; case P('I','4'): // IPv4 address u->ip4 = ip4_pack(p); break; case P('I','6'): // IPv6 address u->ip6 = ip6_pack(p); break; case P('U','4'): // UDP4 port u->udp4 = strtol(p, NULL, 10); break; case P('U','6'): // UDP6 port u->udp6 = strtol(p, NULL, 10); break; case P('S','U'): // supports u->active = !!strstr(p, "TCP4") || !!strstr(p, "TCP6"); u->hasudp4 = !!strstr(p, "UDP4"); u->hasudp6 = !!strstr(p, "UDP6"); u->hasadc0 = !!strstr(p, "ADC0"); u->hastls = u->hasadc0 || strstr(p, "ADCS"); break; case P('C','T'): // client type (only used to figure out u->isop) u->isop = (strtol(p, NULL, 10) & (4 | 8 | 16 | 32)) > 0; break; case P('U','S'): // upload speed u->conn = GUINT_TO_POINTER((int)g_ascii_strtoull(p, NULL, 0)); break; case P('K','P'): // keyprint if(u->kp) { g_slice_free1(32, u->kp); u->kp = NULL; } if(strncmp(p, "SHA256/", 7) == 0 && strlen(p+7) == 52 && isbase32(p+7)) { u->kp = g_slice_alloc(32); base32_decode(p+7, u->kp); } else g_message("Invalid KP field in INF for %s on %s (%s)", u->name, net_remoteaddr(hub->net), p); break; } } uit_hub_userchange(hub->tab, UIHUB_UC_NFO, u); } #undef P // hub stuff hub_t *hub_global_byid(guint64 id) { return g_hash_table_lookup(hubs, &id); } // Should be called when something changes that may affect our INF or $MyINFO. void hub_global_nfochange() { GHashTableIter i; g_hash_table_iter_init(&i, hubs); hub_t *h; while(g_hash_table_iter_next(&i, NULL, (gpointer *)&h)) { if(h->nick_valid) hub_send_nfo(h); } } // Get the current active IP used for this hub. Returns NULL if we're not active. char *hub_ip(hub_t *hub) { if(!net_is_connected(hub->net)) return NULL; char *ip = var_get(hub->id, VAR_active_ip); if(ip && strcmp(ip, "local") == 0) { ip = (char *)net_localaddr(hub->net); } else if(!net_is_ipv6(hub->net)) { struct in_addr ip4 = var_parse_ip4(ip); ip = ip4_isany(ip4) ? NULL : (char *)ip4_unpack(ip4); } else { struct in6_addr ip6 = var_parse_ip6(ip); ip = ip6_isany(ip6) ? NULL : (char *)ip6_unpack(ip6); } return ip ? ip : hub->ip; } void hub_password(hub_t *hub, char *pass) { g_return_if_fail(hub->adc ? hub->state == ADC_S_VERIFY : !hub->nick_valid); if(!pass) pass = var_get(hub->id, VAR_password); if(!pass) { ui_m(hub->tab, UIP_HIGH, "\nPassword required. Type '/password ' to log in without saving your password." "\nOr use '/hset password ' to log in and save your password in the config file (unencrypted!).\n"); } else if(hub->adc) { char enc[40] = {}; char res[24]; tiger_ctx_t t; tiger_init(&t); tiger_update(&t, pass, strlen(pass)); tiger_update(&t, hub->gpa_salt, hub->gpa_salt_len); tiger_final(&t, res); base32_encode(res, enc); net_writef(hub->net, "HPAS %s\n", enc); hub->isreg = TRUE; } else { net_writef(hub->net, "$MyPass %s|", pass); // Password is sent raw, not encoded. Don't think encoding really matters here. hub->isreg = TRUE; } } void hub_kick(hub_t *hub, hub_user_t *u) { g_return_if_fail(!hub->adc && hub->nick_valid && u); net_writef(hub->net, "$Kick %s|", u->name_hub); } // Initiate a C-C connection with a user void hub_opencc(hub_t *hub, hub_user_t *u) { char token[14] = {}; if(hub->adc) { char nonce[8]; g_warn_if_fail(gnutls_rnd(GNUTLS_RND_NONCE, nonce, 8) == 0); base32_encode_dat(nonce, token, 8); } int tlspolicy = var_get_int(hub->id, VAR_tls_policy); int port = listen_hub_tcp(hub->id); gboolean usetls = tlspolicy == VAR_TLSP_FORCE || (tlspolicy == VAR_TLSP_PREFER && u->hastls); char *adcproto = !usetls ? "ADC/1.0" : u->hasadc0 ? "ADCS/0.10" : "ADCS/1.0"; // we're active, send CTM if(port) { if(hub->adc) { GString *c = adc_generate('D', ADCC_CTM, hub->sid, u->sid); g_string_append_printf(c, " %s %d %s\n", adcproto, port, token); net_writef(hub->net, c->str); g_string_free(c, TRUE); } else net_writef(hub->net, net_is_ipv6(hub->net) ? "$ConnectToMe %s [%s]:%d%s" : "$ConnectToMe %s %s:%d%s|", u->name_hub, hub_ip(hub), port, usetls ? "S" : ""); // we're passive, send RCM } else { if(hub->adc) { GString *c = adc_generate('D', ADCC_RCM, hub->sid, u->sid); g_string_append_printf(c, " %s %s\n", adcproto, token); net_writestr(hub->net, c->str); g_string_free(c, TRUE); } else // Can't specify TLS preference in $RevConnectToMe :( net_writef(hub->net, "$RevConnectToMe %s %s|", hub->nick_hub, u->name_hub); } cc_expect_add(hub, u, port, hub->adc ? token : NULL, TRUE); } // Send a search request void hub_search(hub_t *hub, search_q_t *q) { // ADC if(hub->adc) { // TODO: use FSCH to only get results from active users when we are passive? GString *cmd = adc_generate('B', ADCC_SCH, hub->sid, 0); if(listen_hub_active(hub->id)) { char token[14] = {}; base32_encode_dat((char *)&hub->id, token, 8); g_string_append(cmd, " TO"); g_string_append(cmd, token); } if(q->type == 9) { char tth[40] = {}; base32_encode(q->tth, tth); g_string_append_printf(cmd, " TR%s", tth); } else { if(q->size) g_string_append_printf(cmd, " %s%"G_GUINT64_FORMAT, q->ge ? "GE" : "LE", q->size); if(q->type == 8) g_string_append(cmd, " TY2"); else if(q->type != 1) { char **e = search_types[(int)q->type].exts; for(; *e; e++) g_string_append_printf(cmd, " EX%s", *e); g_string_append(cmd, " TY1"); } char **s = q->query; for(; *s; s++) g_string_append_printf(cmd, " AN%s", *s); } if(hub->tls && var_get_int(0, VAR_sudp_policy) == VAR_SUDPP_PREFER) { char key[27] = {}; base32_encode_dat(q->key, key, 16); g_string_append_printf(cmd, " KY%s", key); } g_string_append_c(cmd, '\n'); net_writestr(hub->net, cmd->str); g_string_free(cmd, TRUE); // NMDC } else { guint16 udpport = listen_hub_udp(hub->id); char *dest = udpport ? g_strdup_printf(net_is_ipv6(hub->net) ? "[%s]:%d" : "%s:%d", hub_ip(hub), udpport) : g_strdup_printf("Hub:%s", hub->nick_hub); if(q->type == 9) { char tth[40] = {}; base32_encode(q->tth, tth); net_writef(hub->net, "$Search %s F?T?0?9?TTH:%s|", dest, tth); } else { char *str = g_strjoinv(" ", q->query); char *enc = nmdc_encode_and_escape(hub, str); g_free(str); for(str=enc; *str; str++) if(*str == ' ') *str = '$'; net_writef(hub->net, "$Search %s %c?%c?%"G_GUINT64_FORMAT"?%d?%s|", dest, q->size ? 'T' : 'F', q->ge ? 'F' : 'T', q->size, q->type, enc); g_free(enc); } g_free(dest); } } #define streq(a) ((!a && !hub->nfo_##a) || (a && hub->nfo_##a && strcmp(a, hub->nfo_##a) == 0)) #define eq(a) (a == hub->nfo_##a) #define beq(a) (!!a == !!hub->nfo_##a) static unsigned char num_free_slots(unsigned char hub_slots) { int rv = hub_slots - cc_slots_in_use(NULL); return rv > 0 ? rv : 0; } static GString* format_desc(hub_t *hub, unsigned char free_slots) { GString *desc; const char *static_desc = var_get(hub->id, VAR_description); if(var_get_bool(hub->id, VAR_show_free_slots)) { desc = g_string_sized_new(128); if(static_desc) g_string_printf(desc, "[%d sl] %s", free_slots, static_desc); else g_string_printf(desc, "[%d sl]", free_slots); } else desc = g_string_new(static_desc); return desc; } void hub_send_nfo(hub_t *hub) { if(!net_is_connected(hub->net)) return; // get info, to be compared with hub->nfo_ char *desc, *conn = NULL, *mail, *ip; unsigned char slots, free_slots, h_norm, h_reg, h_op; guint64 share; guint16 udp_port; gboolean sup_tls, sup_sudp; GString *fmt_desc; mail = var_get(hub->id, VAR_email); slots = var_get_int(0, VAR_slots); free_slots = num_free_slots(slots); fmt_desc = format_desc(hub, free_slots); desc = fmt_desc->str; char buf[50] = {}; if(var_get_int(0, VAR_upload_rate)) { g_snprintf(buf, sizeof(buf), "%d KiB/s", var_get_int(0, VAR_upload_rate)/1024); conn = buf; } else conn = var_get(hub->id, VAR_connection); h_norm = h_reg = h_op = 0; GHashTableIter iter; hub_t *oh = NULL; g_hash_table_iter_init(&iter, hubs); while(g_hash_table_iter_next(&iter, NULL, (gpointer *)&oh)) { if(!oh->nick_valid) continue; if(oh->isop) h_op++; else if(oh->isreg) h_reg++; else h_norm++; } ip = listen_hub_active(hub->id) ? hub_ip(hub) : NULL; udp_port = listen_hub_udp(hub->id); share = fl_local_list_size; sup_tls = var_get_int(hub->id, VAR_tls_policy) > VAR_TLSP_DISABLE ? TRUE : FALSE; sup_sudp = hub->tls && var_get_int(0, VAR_sudp_policy) != VAR_SUDPP_DISABLE ? TRUE : FALSE; // check whether we need to make any further effort if(hub->nick_valid && streq(desc) && streq(conn) && streq(mail) && eq(slots) && eq(free_slots) && streq(ip) && eq(h_norm) && eq(h_reg) && eq(h_op) && eq(share) && eq(udp_port) && beq(sup_tls) && beq(sup_sudp)) { g_string_free(fmt_desc, TRUE); return; } char *nfo; // ADC if(hub->adc) { // TODO: DS and SF? GString *cmd = adc_generate('B', ADCC_INF, hub->sid, 0); // send non-changing stuff in the IDENTIFY state gboolean f = hub->state == ADC_S_IDENTIFY; if(f) { g_string_append_printf(cmd, " ID%s PD%s VEncdc\\s%s", var_get(0, VAR_cid), var_get(0, VAR_pid), main_version); adc_append(cmd, "NI", hub->nick); // Always add our KP field, even if we're not active. Other clients may // validate our certificate even when we are the one connecting. g_string_append_printf(cmd, " KPSHA256/%s", db_certificate_kp); } if(f || !streq(ip)) g_string_append_printf(cmd, net_is_ipv6(hub->net) ? " I6%s" : " I4%s", ip ? ip : !net_is_ipv6(hub->net) ? ip4_unpack(ip4_any) : ip6_unpack(ip6_any)); if(f || !streq(ip) || !beq(sup_tls) || !beq(sup_sudp)) { g_string_append(cmd, " SU"); int comma = 0; if(ip) g_string_append_printf(cmd, "%s%s", comma++ ? "," : "", net_is_ipv6(hub->net) ? "TCP6,UDP6" : "TCP4,UDP4"); if(sup_tls) g_string_append_printf(cmd, "%s%s", comma++ ? "," : "", "ADC0,ADCS"); if(sup_sudp) g_string_append_printf(cmd, "%s%s", comma++ ? "," : "", "SUD1,SUDP"); } if((f || !eq(udp_port))) { if(udp_port) g_string_append_printf(cmd, net_is_ipv6(hub->net) ? " U6%d" : " U4%d", udp_port); else g_string_append(cmd, net_is_ipv6(hub->net) ? " U6" : " U4"); } // Separating the SS and SF fields isn't very important. It is relatively // safe to assume that if SS changes (which we look at), then SF will most // likely have changed as well. And vice versa. if(f || !eq(share)) g_string_append_printf(cmd, " SS%"G_GUINT64_FORMAT" SF%d", share, fl_local_list_length); if(f || !eq(slots)) g_string_append_printf(cmd, " SL%d", slots); if(f || !eq(free_slots)) g_string_append_printf(cmd, " FS%d", free_slots); if(f || !eq(h_norm)) g_string_append_printf(cmd, " HN%d", h_norm); if(f || !eq(h_reg)) g_string_append_printf(cmd, " HR%d", h_reg); if(f || !eq(h_op)) g_string_append_printf(cmd, " HO%d", h_op); if(f || !streq(desc)) adc_append(cmd, "DE", desc?desc:""); if(f || !streq(mail)) adc_append(cmd, "EM", mail?mail:""); if((f || !streq(conn)) && str_connection_to_speed(conn)) g_string_append_printf(cmd, " US%"G_GUINT64_FORMAT, str_connection_to_speed(conn)); g_string_append_c(cmd, '\n'); nfo = g_string_free(cmd, FALSE); // NMDC } else { char *ndesc = nmdc_encode_and_escape(hub, desc?desc:""); char *nconn = nmdc_encode_and_escape(hub, conn?conn:"0.005"); char *nmail = nmdc_encode_and_escape(hub, mail?mail:""); nfo = g_strdup_printf("$MyINFO $ALL %s %s$ $%s%c$%s$%"G_GUINT64_FORMAT"$|", hub->nick_hub, ndesc, main_version, ip ? 'A' : 'P', h_norm, h_reg, h_op, slots, nconn, 1 | (sup_tls ? 0x10 : 0), nmail, share); g_free(ndesc); g_free(nconn); g_free(nmail); } // send net_writestr(hub->net, nfo); g_free(nfo); // update g_free(hub->nfo_desc); hub->nfo_desc = g_string_free(fmt_desc, FALSE); g_free(hub->nfo_conn); hub->nfo_conn = g_strdup(conn); g_free(hub->nfo_mail); hub->nfo_mail = g_strdup(mail); g_free(hub->nfo_ip); hub->nfo_ip = g_strdup(ip); hub->nfo_slots = slots; hub->nfo_free_slots = free_slots; hub->nfo_h_norm = h_norm; hub->nfo_h_reg = h_reg; hub->nfo_h_op = h_op; hub->nfo_share = share; hub->nfo_udp_port = udp_port; hub->nfo_sup_tls = sup_tls; hub->nfo_sup_sudp = sup_sudp; } #undef eq #undef streq void hub_say(hub_t *hub, const char *str, gboolean me) { if(!hub->nick_valid) return; if(hub->adc) { GString *c = adc_generate('B', ADCC_MSG, hub->sid, 0); adc_append(c, NULL, str); if(me) g_string_append(c, " ME1"); g_string_append_c(c, '\n'); net_writestr(hub->net, c->str); g_string_free(c, TRUE); } else { char *msg = nmdc_encode_and_escape(hub, str); net_writef(hub->net, me ? "<%s> /me %s|" : "<%s> %s|", hub->nick_hub, msg); g_free(msg); } } void hub_msg(hub_t *hub, hub_user_t *user, const char *str, gboolean me) { if(hub->adc) { GString *c = adc_generate('E', ADCC_MSG, hub->sid, user->sid); adc_append(c, NULL, str); char enc[5] = {}; ADC_EFCC(hub->sid, enc); g_string_append_printf(c, " PM%s", enc); if(me) g_string_append(c, " ME1"); g_string_append_c(c, '\n'); net_writestr(hub->net, c->str); g_string_free(c, TRUE); } else { char *msg = nmdc_encode_and_escape(hub, str); net_writef(hub->net, me ? "$To: %s From: %s $<%s> /me %s|" : "$To: %s From: %s $<%s> %s|", user->name_hub, hub->nick_hub, hub->nick_hub, msg); g_free(msg); // emulate protocol echo msg = g_strdup_printf(me ? "<%s> /me %s" : "<%s> %s", hub->nick, str); uit_msg_msg(user, msg); g_free(msg); } } // Call this when the hub tells us our IP. static void setownip(hub_t *hub, hub_user_t *u) { char *oldconf = hub_ip(hub); char *oldval = hub->ip; char *new = net_is_ipv6(hub->net) ? (ip6_isany(u->ip6) ? NULL : (char *)ip6_unpack(u->ip6)) : (ip4_isany(u->ip4) ? NULL : (char *)ip4_unpack(u->ip4)); if((!new && oldval) || (new && !oldval) || (new && oldval && strcmp(new, oldval) != 0)) { g_free(hub->ip); hub->ip = g_strdup(new); } // If we're supposed to be active, but weren't because of a missing IP. if(!oldconf && new && var_get_bool(hub->id, VAR_active)) { listen_refresh(); hub_send_nfo(hub); } } static void adc_sch_reply_send(hub_t *hub, net_udp_t *udp, GString *r, const char *key) { if(!udp) { net_writestr(hub->net, r->str); return; } else if(!key || var_get_int(0, VAR_sudp_policy) == VAR_SUDPP_DISABLE) { net_udp_send(udp, r->str); return; } // net_udp_* can't log this since it will be encrypted, so log it here. g_debug("SUDP:%s> %.*s", udp->addr, (int)r->len-1, r->str); // prepend 16 random bytes to message char nonce[16]; g_warn_if_fail(gnutls_rnd(GNUTLS_RND_NONCE, nonce, 16) == 0); g_string_prepend_len(r, nonce, 16); // use PKCS#5 padding to align the message length to the cypher block size (16) int pad = 16 - (r->len & 15); int i; for(i=0; istr, r->len); net_udp_send_raw(udp, r->str, r->len); } static void adc_sch_reply(hub_t *hub, adc_cmd_t *cmd, hub_user_t *u, fl_list_t **res, int len) { char *ky = adc_getparam(cmd->argv, "KY", NULL); // SUDP key char *to = adc_getparam(cmd->argv, "TO", NULL); // token char sudpkey[16]; if(ky && isbase32(ky) && strlen(ky) == 26) base32_decode(ky, sudpkey); int slots = var_get_int(0, VAR_slots); int slots_free = slots - cc_slots_in_use(NULL); if(slots_free < 0) slots_free = 0; char tth[40] = {}; char *cid = NULL; net_udp_t udpbuf, *udp = NULL; if((u->hasudp4 && u->udp4) || (u->hasudp6 && u->udp6)) { cid = var_get(0, VAR_cid); /* TODO: If the user has both UDP4 and UDP6, we should prefer the AF used * to connect to the hub, rather than IPv4. */ udp = &udpbuf; net_udp_init(udp, u->hasudp4 && u->udp4 ? ip4_unpack(u->ip4) : ip6_unpack(u->ip6), u->hasudp4 && u->udp4 ? u->udp4 : u->udp6, var_get(hub->id, VAR_local_address) ); } int i; for(i=0; isid, cmd->source); if(udp) g_string_append_printf(r, " %s", cid); if(to) adc_append(r, "TO", to); g_string_append_printf(r, " SL%d SI%"G_GUINT64_FORMAT, slots_free, res[i]->size); char *path = fl_list_path(res[i]); adc_append(r, "FN", path); g_free(path); if(res[i]->isfile) { base32_encode(res[i]->tth, tth); g_string_append_printf(r, " TR%s", tth); } else g_string_append_c(r, '/'); // make sure a directory path ends with a slash g_string_append_c(r, '\n'); adc_sch_reply_send(hub, udp, r, ky ? sudpkey : NULL); g_string_free(r, TRUE); } if(udp) net_udp_destroy(udp); } static void adc_sch(hub_t *hub, adc_cmd_t *cmd) { char *an = adc_getparam(cmd->argv, "AN", NULL); // and char *no = adc_getparam(cmd->argv, "NO", NULL); // not char *ex = adc_getparam(cmd->argv, "EX", NULL); // ext char *le = adc_getparam(cmd->argv, "LE", NULL); // less-than char *ge = adc_getparam(cmd->argv, "GE", NULL); // greater-than char *eq = adc_getparam(cmd->argv, "EQ", NULL); // equal char *ty = adc_getparam(cmd->argv, "TY", NULL); // type (1=file, 2=dir) char *tr = adc_getparam(cmd->argv, "TR", NULL); // TTH root char *td = adc_getparam(cmd->argv, "TD", NULL); // tree depth // no strong enough filters specified? ignore if(!an && !no && !ex && !le && !ge && !eq && !tr) return; // le, ge and eq are mutually exclusive (actually, they aren't when they're all equal, but it's still silly) if((eq?1:0) + (le?1:0) + (ge?1:0) > 1) return; // Actually matching the tree depth is rather resouce-intensive, since we // don't store it in fl_list_t. Instead, just assume fl_hash_keep_level // for everything. This may be wrong, but if it is, it's most likely to be a // pessimistic estimate, which happens in the case TTH data is converted from // a DC++ client to ncdc. if(td && strtoll(td, NULL, 10) > fl_hash_keep_level) return; if(tr && !istth(tr)) return; hub_user_t *u = g_hash_table_lookup(hub->sessions, GINT_TO_POINTER(cmd->source)); if(!u) return; // create search struct fl_search_t s = {}; s.sizem = eq ? 0 : le ? -1 : ge ? 1 : -2; s.size = s.sizem == -2 ? 0 : g_ascii_strtoull(eq ? eq : le ? le : ge, NULL, 10); s.filedir = !ty ? 3 : ty[0] == '1' ? 1 : 2; char **tmp = adc_getparams(cmd->argv, "AN"); s.and = fl_search_create_and(tmp); g_free(tmp); tmp = adc_getparams(cmd->argv, "NO"); s.not = fl_search_create_not(tmp); g_free(tmp); s.ext = adc_getparams(cmd->argv, "EX"); int i = 0; int max = (u->hasudp4 && u->udp4) || (u->hasudp6 && u->udp6) ? 10 : 5; fl_list_t *res[max]; // TTH lookup if(tr) { char root[24]; base32_decode(tr, root); GSList *l = fl_local_from_tth(root); // it still has to match the other requirements... for(; inext) { fl_list_t *c = l->data; if(fl_search_match_full(c, &s)) res[i++] = c; } // Advanced lookup (Noo! This is slooow!) } else i = fl_search_rec(fl_local_list, &s, res, max); if(i) adc_sch_reply(hub, cmd, u, res, i); fl_search_free_and(s.and); if(s.not) g_regex_unref(s.not); g_free(s.ext); } // Many ways to say the same thing #define is_adcs_proto(p) (strcmp(p, "ADCS/1.0") == 0 || strcmp(p, "ADCS/0.10") == 0 || strcmp(p, "ADC0/0.10") == 0) #define is_adc_proto(p) (strcmp(p, "ADC/1.0") == 0 || strcmp(p, "ADC/0.10") == 0) #define is_valid_proto(pol, p) ((pol) == VAR_TLSP_DISABLE ? is_adc_proto(p) : (pol) == VAR_TLSP_FORCE ? is_adcs_proto(p) : is_adc_proto(p) || is_adcs_proto(p)) static void adc_handle(net_t *net, char *msg, int _len) { hub_t *hub = net_handle(net); net_readmsg(net, '\n', adc_handle); adc_cmd_t cmd; GError *err = NULL; if(!msg[0]) return; int feats[2] = {}; if(listen_hub_active(hub->id)) feats[0] = ADC_DFCC("TCP4"); adc_parse(msg, &cmd, feats, &err); if(err) { g_message("ADC parse error from %s: %s. --> %s", net_remoteaddr(hub->net), err->message, msg); g_error_free(err); return; } switch(cmd.cmd) { case ADCC_SID: if(hub->state != ADC_S_PROTOCOL || cmd.type != 'I' || cmd.argc != 1 || strlen(cmd.argv[0]) != 4) g_message("Invalid message from %s: %s", net_remoteaddr(hub->net), msg); else { hub->sid = ADC_DFCC(cmd.argv[0]); hub->state = ADC_S_IDENTIFY; hub->nick = g_strdup(var_get(hub->id, VAR_nick)); uit_hub_setnick(hub->tab); hub_send_nfo(hub); } break; case ADCC_SUP: // TODO: do something with it. // For C-C connections, this enables the IDENTIFY state, but for hubs it's the SID command that does this. break; case ADCC_INF: // inf from hub if(cmd.type == 'I') { // Get hub name. Some hubs (PyAdc) send multiple 'NI's, ignore the first // one in that case. Other hubs don't send 'NI', but only a 'DE'. char **left = NULL; char *hname = adc_getparam(cmd.argv, "NI", &left); if(left) hname = adc_getparam(left, "NI", NULL); if(!hname) hname = adc_getparam(cmd.argv, "DE", NULL); if(hname) { g_free(hub->hubname); hub->hubname = g_strdup(hname); } // inf from user } else if(cmd.type == 'B') { hub_user_t *u = g_hash_table_lookup(hub->sessions, GINT_TO_POINTER(cmd.source)); if(!u) { char *nick = adc_getparam(cmd.argv, "NI", NULL); char *cid = adc_getparam(cmd.argv, "ID", NULL); if(nick && cid && iscid(cid)) u = user_add(hub, nick, cid); } if(!u) g_message("INF for user who is not on the hub (%s): %s", net_remoteaddr(hub->net), msg); else { if(!u->hasinfo) hub->sharecount++; else hub->sharesize -= u->sharesize; user_adc_nfo(hub, u, &cmd); hub->sharesize += u->sharesize; // if we received our own INF, that means the user list is complete and // we are properly logged in. if(u->sid == hub->sid) { hub->state = ADC_S_NORMAL; hub->isop = u->isop; if(!hub->nick_valid) dl_user_join(0); hub->nick_valid = TRUE; hub->joincomplete = TRUE; // This means we also have an IP, probably setownip(hub, u); } } } break; case ADCC_QUI: if(cmd.type != 'I' || cmd.argc < 1 || strlen(cmd.argv[0]) != 4) g_message("Invalid message from %s: %s", net_remoteaddr(hub->net), msg); else { int sid = ADC_DFCC(cmd.argv[0]); hub_user_t *u = g_hash_table_lookup(hub->sessions, GINT_TO_POINTER(sid)); if(sid == hub->sid) { char *rd = adc_getparam(cmd.argv, "RD", NULL); char *ms = adc_getparam(cmd.argv, "MS", NULL); char *tl = adc_getparam(cmd.argv, "TL", NULL); if(rd) { ui_mf(hub->tab, UIP_HIGH, "\nThe hub is requesting you to move to %s.\nType `/connect %s' to do so.\n", rd, rd); if(ms) ui_mf(hub->tab, 0, "Message: %s", ms); } else if(ms) ui_m(hub->tab, UIP_MED, ms); hub_disconnect(hub, rd || (tl && strcmp(tl, "-1") == 0) ? FALSE : TRUE); } else if(u) { // TODO: handle DI, and perhaps do something with MS uit_hub_userchange(hub->tab, UIHUB_UC_QUIT, u); hub->sharecount--; hub->sharesize -= u->sharesize; g_hash_table_remove(hub->users, u->name); } else g_message("QUI for user who is not on the hub (%s): %s", net_remoteaddr(hub->net), msg); } break; case ADCC_STA: if(cmd.argc < 2 || strlen(cmd.argv[0]) != 3) g_message("Invalid message from %s: %s", net_remoteaddr(hub->net), msg); else { int code = (cmd.argv[0][1]-'0')*10 + (cmd.argv[0][2]-'0'); int severity = cmd.argv[0][0]-'0'; /* Direct STA messages are usually from CTM/RCM errors. Passing those to * the main chat is spammy and unintuitive. */ if(cmd.type != 'D') ui_mf(hub->tab, UIP_LOW, "(%s-%02d) %s", !severity ? "status" : severity == 1 ? "warning" : "error", code, cmd.argv[1]); else { hub_user_t *u = g_hash_table_lookup(hub->sessions, GINT_TO_POINTER(cmd.source)); g_message("Direct Status message from %s on %s: %s", u ? u->name : "unknown user", net_remoteaddr(hub->net), msg); } } break; case ADCC_CTM: if(cmd.argc < 3 || cmd.type != 'D' || cmd.dest != hub->sid) g_message("Invalid message from %s: %s", net_remoteaddr(hub->net), msg); else if(!is_valid_proto(var_get_int(hub->id, VAR_tls_policy), cmd.argv[0])) { GString *r = adc_generate('D', ADCC_STA, hub->sid, cmd.source); g_string_append(r, " 141 Unknown\\sprotocol"); adc_append(r, "PR", cmd.argv[0]); adc_append(r, "TO", cmd.argv[2]); g_string_append_c(r, '\n'); net_writestr(hub->net, r->str); g_string_free(r, TRUE); } else { hub_user_t *u = g_hash_table_lookup(hub->sessions, GINT_TO_POINTER(cmd.source)); int port = strtol(cmd.argv[1], NULL, 0); if(!u) g_message("CTM from user who is not on the hub (%s): %s", net_remoteaddr(hub->net), msg); else if(port < 1 || port > 65535) g_message("Invalid message from %s: %s", net_remoteaddr(hub->net), msg); else if(!u->active || (ip4_isany(u->ip4) && ip6_isany(u->ip6))) { g_message("CTM from user who is not active (%s): %s", net_remoteaddr(hub->net), msg); GString *r = adc_generate('D', ADCC_STA, hub->sid, cmd.source); g_string_append(r, " 140 No\\sIP\\sto\\sconnect\\sto.\n"); net_writestr(hub->net, r->str); g_string_free(r, TRUE); } else cc_adc_connect(cc_create(hub), u, var_get(hub->id, VAR_local_address), port, is_adcs_proto(cmd.argv[0]), cmd.argv[2]); } break; case ADCC_RCM: if(cmd.argc < 2 || cmd.type != 'D' || cmd.dest != hub->sid) g_message("Invalid message from %s: %s", net_remoteaddr(hub->net), msg); else if(!is_valid_proto(var_get_int(hub->id, VAR_tls_policy), cmd.argv[0])) { GString *r = adc_generate('D', ADCC_STA, hub->sid, cmd.source); g_string_append(r, " 141 Unknown\\protocol"); adc_append(r, "PR", cmd.argv[0]); adc_append(r, "TO", cmd.argv[1]); g_string_append_c(r, '\n'); net_writestr(hub->net, r->str); g_string_free(r, TRUE); } else if(!listen_hub_active(hub->id)) { GString *r = adc_generate('D', ADCC_STA, hub->sid, cmd.source); g_string_append(r, " 142 Not\\sactive"); adc_append(r, "PR", cmd.argv[0]); adc_append(r, "TO", cmd.argv[1]); g_string_append_c(r, '\n'); net_writestr(hub->net, r->str); g_string_free(r, TRUE); } else { hub_user_t *u = g_hash_table_lookup(hub->sessions, GINT_TO_POINTER(cmd.source)); if(!u) g_message("RCM from user who is not on the hub (%s): %s", net_remoteaddr(hub->net), msg); else { int port = listen_hub_tcp(hub->id); GString *r = adc_generate('D', ADCC_CTM, hub->sid, cmd.source); adc_append(r, NULL, cmd.argv[0]); g_string_append_printf(r, " %d", port); adc_append(r, NULL, cmd.argv[1]); g_string_append_c(r, '\n'); net_writestr(hub->net, r->str); g_string_free(r, TRUE); cc_expect_add(hub, u, port, cmd.argv[1], FALSE); } } break; case ADCC_MSG:; if(cmd.argc < 1 || (cmd.type != 'B' && cmd.type != 'E' && cmd.type != 'D' && cmd.type != 'I' && cmd.type != 'F')) g_message("Invalid message from %s: %s", net_remoteaddr(hub->net), msg); else { char *pm = adc_getparam(cmd.argv+1, "PM", NULL); gboolean me = adc_getparam(cmd.argv+1, "ME", NULL) != NULL; hub_user_t *u = cmd.type != 'I' ? g_hash_table_lookup(hub->sessions, GINT_TO_POINTER(cmd.source)) : NULL; hub_user_t *d = (cmd.type == 'E' || cmd.type == 'D') && cmd.source == hub->sid ? g_hash_table_lookup(hub->sessions, GINT_TO_POINTER(cmd.dest)) : NULL; if(cmd.type != 'I' && !u && !d) g_message("Message from someone not on this hub. (%s: %s)", net_remoteaddr(hub->net), msg); else { char *m = g_strdup_printf(me ? "** %s %s" : "<%s> %s", cmd.type == 'I' ? "hub" : u->name, cmd.argv[0]); if(cmd.type == 'E' || cmd.type == 'D' || cmd.type == 'F') { // PM hub_user_t *pmu = pm && strlen(pm) == 4 && ADC_DFCC(pm) ? g_hash_table_lookup(hub->sessions, GINT_TO_POINTER(ADC_DFCC(pm))) : NULL; if(!pmu) g_message("Invalid PM param in MSG from %s: %s", net_remoteaddr(hub->net), msg); else uit_msg_msg(cmd.source == hub->sid ? d : pmu, m); } else // hub chat ui_m(hub->tab, UIM_CHAT|UIP_MED, m); g_free(m); } } break; case ADCC_SCH: if(cmd.type != 'B' && cmd.type != 'D' && cmd.type != 'E' && cmd.type != 'F') g_message("Invalid message from %s: %s", net_remoteaddr(hub->net), msg); else if(cmd.source != hub->sid) adc_sch(hub, &cmd); break; case ADCC_GPA: if(cmd.type != 'I' || cmd.argc < 1 || (hub->state != ADC_S_IDENTIFY && hub->state != ADC_S_VERIFY)) g_message("Invalid message from %s: %s", net_remoteaddr(hub->net), msg); else { g_free(hub->gpa_salt); hub->state = ADC_S_VERIFY; hub->gpa_salt_len = (strlen(cmd.argv[0])*5)/8; hub->gpa_salt = g_new(char, hub->gpa_salt_len); base32_decode(cmd.argv[0], hub->gpa_salt); hub_password(hub, NULL); } break; case ADCC_RES: if(cmd.type != 'D' || cmd.argc < 3) g_message("Invalid message from %s: %s", net_remoteaddr(hub->net), msg); else if(!search_handle_adc(hub, &cmd)) g_message("Invalid message from %s: %s", net_remoteaddr(hub->net), msg); break; case ADCC_GET: if(cmd.type != 'I' || cmd.argc < 4 || strcmp(cmd.argv[0], "blom") != 0 || strcmp(cmd.argv[1], "/") != 0 || strcmp(cmd.argv[2], "0") != 0) g_message("Invalid message from %s: %s", net_remoteaddr(hub->net), msg); else { char *bk = adc_getparam(cmd.argv+4, "BK", NULL); char *bh = adc_getparam(cmd.argv+4, "BH", NULL); long m = strtol(cmd.argv[3], NULL, 10); long k = bk ? strtol(bk, NULL, 10) : 0; long h = bh ? strtol(bh, NULL, 10) : 0; bloom_t b; if(bloom_init(&b, m, k, h) < 0) g_message("Invalid bloom filter parameters from %s: %s", net_remoteaddr(hub->net), msg); else { fl_local_bloom(&b); GString *r = adc_generate('H', ADCC_SND, 0, 0); g_string_append_printf(r, " blom / 0 %d\n", b.m); net_writestr(hub->net, r->str); g_string_free(r, TRUE); net_write(hub->net, (char *)b.d, b.m); bloom_free(&b); } } break; default: g_message("Unknown command from %s: %s", net_remoteaddr(hub->net), msg); } g_strfreev(cmd.argv); } #undef is_adcs_proto #undef is_adc_proto #undef is_valid_proto // If port = 0, 'from' is interpreted as a nick. Otherwise, from should be an IP address. static void nmdc_search(hub_t *hub, char *from, unsigned short port, int size_m, guint64 size, int type, char *query) { int max = port ? 10 : 5; fl_list_t *res[10]; fl_search_t s = {}; s.filedir = type == 1 ? 3 : type == 8 ? 2 : 1; s.ext = search_types[type].exts; s.size = size; s.sizem = size_m; int i = 0; // TTH lookup (YAY! this is fast!) if(type == 9) { if(strncmp(query, "TTH:", 4) != 0 || !istth(query+4)) { g_message("Invalid TTH $Search for %s", from); return; } char root[24]; base32_decode(query+4, root); GSList *l = fl_local_from_tth(root); // it still has to match the other requirements... for(; inext) { fl_list_t *c = l->data; if(fl_search_match_full(c, &s)) res[i++] = c; } // Advanced lookup (Noo! This is slooow!) } else { char *tmp = query; for(; *tmp; tmp++) if(*tmp == '$') *tmp = ' '; tmp = nmdc_unescape_and_decode(hub, query); char **args = g_strsplit(tmp, " ", 0); g_free(tmp); s.and = fl_search_create_and(args); g_strfreev(args); i = fl_search_rec(fl_local_list, &s, res, max); fl_search_free_and(s.and); } // reply if(!i) return; const char *hubaddr = net_remoteaddr(hub->net); int slots = var_get_int(0, VAR_slots); int slots_free = slots - cc_slots_in_use(NULL); if(slots_free < 0) slots_free = 0; char tth[44] = "TTH:"; tth[43] = 0; net_udp_t udp; if(port) net_udp_init(&udp, from, port, var_get(hub->id, VAR_local_address)); while(--i>=0) { char *fl = fl_list_path(res[i]); // Windows style path delimiters... why!? char *tmp = fl; char *size = NULL; for(; *tmp; tmp++) if(*tmp == '/') *tmp = '\\'; tmp = nmdc_encode_and_escape(hub, fl); if(res[i]->isfile) { base32_encode(res[i]->tth, tth+4); size = g_strdup_printf("\05%"G_GUINT64_FORMAT, res[i]->size); } char *msg = g_strdup_printf("$SR %s %s%s %d/%d\05%s (%s)", hub->nick_hub, tmp, size ? size : "", slots_free, slots, res[i]->isfile ? tth : hub->hubname_hub, hubaddr); if(!port) net_writef(hub->net, "%s\05%s|", msg, from); else net_udp_sendf(&udp, "%s|", msg); g_free(fl); g_free(msg); g_free(size); g_free(tmp); } if(port) net_udp_destroy(&udp); } static void nmdc_handle(net_t *net, char *cmd, int _len) { hub_t *hub = net_handle(net); // Immediately queue next read. It will be cancelled when net_disconnect() is // called anyway. net_readmsg(net, '|', nmdc_handle); if(!cmd[0]) return; GMatchInfo *nfo; // create regexes (declared statically, allocated/compiled on first call) #define CMDREGEX(name, regex) \ static GRegex * name = NULL;\ if(!name) name = g_regex_new("\\$" regex, G_REGEX_OPTIMIZE|G_REGEX_ANCHORED|G_REGEX_DOTALL|G_REGEX_RAW, 0, NULL) CMDREGEX(lock, "Lock ([^ $]+) Pk=[^ $]+"); CMDREGEX(supports, "Supports (.+)"); CMDREGEX(hello, "Hello ([^ $]+)"); CMDREGEX(quit, "Quit ([^ $]+)"); CMDREGEX(nicklist, "NickList (.+)"); CMDREGEX(oplist, "OpList (.+)"); CMDREGEX(userip, "UserIP (.+)"); CMDREGEX(myinfo, "MyINFO \\$ALL ([^ $]+) (.+)"); CMDREGEX(hubname, "HubName (.+)"); CMDREGEX(to, "To: ([^ $]+) From: ([^ $]+) \\$(.+)"); CMDREGEX(forcemove, "ForceMove (.+)"); CMDREGEX(connecttome, "ConnectToMe ([^ $]+) ([a-fA-F0-9:\\[\\]\\.]+)(S|)"); CMDREGEX(revconnecttome, "RevConnectToMe ([^ $]+) ([^ $]+)"); CMDREGEX(search, "Search (Hub:(?:[^ $]+)|(?:[a-fA-F0-9:\\[\\]\\.]+)) ([TF])\\?([TF])\\?([0-9]+)\\?([1-9])\\?(.+)"); // $Lock if(g_regex_match(lock, cmd, 0, &nfo)) { // 1 = lock char *lock = g_match_info_fetch(nfo, 1); if(strncmp(lock, "EXTENDEDPROTOCOL", 16) == 0) net_writestr(hub->net, "$Supports NoGetINFO NoHello UserIP2 TLS|"); char *key = nmdc_lock2key(lock); net_writef(hub->net, "$Key %s|", key); hub->nick = g_strdup(var_get(hub->id, VAR_nick)); hub->nick_hub = charset_convert(hub, FALSE, hub->nick); uit_hub_setnick(hub->tab); net_writef(hub->net, "$ValidateNick %s|", hub->nick_hub); g_free(key); g_free(lock); } g_match_info_free(nfo); // $Supports if(g_regex_match(supports, cmd, 0, &nfo)) { // 1 = list char *list = g_match_info_fetch(nfo, 1); if(strstr(list, "NoGetINFO")) hub->supports_nogetinfo = TRUE; // we also support NoHello, but no need to check for that g_free(list); } g_match_info_free(nfo); // $Hello if(g_regex_match(hello, cmd, 0, &nfo)) { // 1 = nick char *nick = g_match_info_fetch(nfo, 1); if(strcmp(nick, hub->nick_hub) == 0) { // some hubs send our $Hello twice (like verlihub) // just ignore the second one if(!hub->nick_valid) { hub->nick_valid = TRUE; ui_m(hub->tab, 0, "Nick validated."); net_writestr(hub->net, "$Version 1,0091|"); hub_send_nfo(hub); net_writestr(hub->net, "$GetNickList|"); // Most hubs send the user list after our nick has been validated (in // contrast to ADC), but it doesn't hurt to call this function at this // point anyway. dl_user_join(0); } } else { hub_user_t *u = user_add(hub, nick, NULL); if(!u->hasinfo && !hub->supports_nogetinfo) net_writef(hub->net, "$GetINFO %s|", nick); } g_free(nick); } g_match_info_free(nfo); // $Quit if(g_regex_match(quit, cmd, 0, &nfo)) { // 1 = nick char *nick = g_match_info_fetch(nfo, 1); hub_user_t *u = g_hash_table_lookup(hub->users, nick); if(u) { uit_hub_userchange(hub->tab, UIHUB_UC_QUIT, u); if(u->hasinfo) { hub->sharecount--; hub->sharesize -= u->sharesize; } g_hash_table_remove(hub->users, nick); } g_free(nick); } g_match_info_free(nfo); // $NickList if(g_regex_match(nicklist, cmd, 0, &nfo)) { // 1 = list of users // not really efficient, but does the trick char *str = g_match_info_fetch(nfo, 1); char **list = g_strsplit(str, "$$", 0); g_free(str); char **cur; for(cur=list; *cur&&**cur; cur++) { hub_user_t *u = user_add(hub, *cur, NULL); if(!u->hasinfo && !hub->supports_nogetinfo) net_writef(hub->net, "$GetINFO %s %s|", *cur, hub->nick_hub); } hub->received_first = TRUE; g_strfreev(list); } g_match_info_free(nfo); // $OpList if(g_regex_match(oplist, cmd, 0, &nfo)) { // 1 = list of ops // not really efficient, but does the trick char *str = g_match_info_fetch(nfo, 1); char **list = g_strsplit(str, "$$", 0); g_free(str); char **cur; // Actually, we should be going through the entire user list and set // isop=FALSE when the user is not listed here. I consider this to be too // inefficient and not all that important at this point. hub->isop = FALSE; for(cur=list; *cur&&**cur; cur++) { hub_user_t *u = user_add(hub, *cur, NULL); if(!u->isop) { u->isop = TRUE; uit_hub_userchange(hub->tab, UIHUB_UC_NFO, u); } else u->isop = TRUE; if(strcmp(hub->nick_hub, *cur) == 0) hub->isop = TRUE; } hub->received_first = TRUE; g_strfreev(list); } g_match_info_free(nfo); // $UserIP if(g_regex_match(userip, cmd, 0, &nfo)) { // 1 = list of users/ips char *str = g_match_info_fetch(nfo, 1); char **list = g_strsplit(str, "$$", 0); g_free(str); char **cur; for(cur=list; *cur&&**cur; cur++) { char *sep = strchr(*cur, ' '); if(!sep) continue; *sep = 0; hub_user_t *u = user_add(hub, *cur, NULL); if(ip4_isvalid(sep+1)) { struct in_addr new = ip4_pack(sep+1); if(ip4_cmp(new, u->ip4) != 0) { u->ip4 = new; uit_hub_userchange(hub->tab, UIHUB_UC_NFO, u); } } else { struct in6_addr new = ip6_pack(sep+1); if(ip6_cmp(new, u->ip6) != 0) { u->ip6 = new; uit_hub_userchange(hub->tab, UIHUB_UC_NFO, u); } } // Our own IP, configure active mode if(strcmp(*cur, hub->nick_hub) == 0) setownip(hub, u); } g_strfreev(list); } g_match_info_free(nfo); // $MyINFO if(g_regex_match(myinfo, cmd, 0, &nfo)) { // 1 = nick, 2 = info string char *nick = g_match_info_fetch(nfo, 1); char *str = g_match_info_fetch(nfo, 2); hub_user_t *u = user_add(hub, nick, NULL); if(!u->hasinfo) hub->sharecount++; else hub->sharesize -= u->sharesize; user_nmdc_nfo(hub, u, str); if(!u->hasinfo) hub->sharecount--; else hub->sharesize += u->sharesize; if(hub->received_first && !hub->joincomplete && hub->sharecount == g_hash_table_size(hub->users)) hub->joincomplete = TRUE; g_free(str); g_free(nick); } g_match_info_free(nfo); // $HubName if(g_regex_match(hubname, cmd, 0, &nfo)) { // 1 = name g_free(hub->hubname_hub); g_free(hub->hubname); hub->hubname_hub = g_match_info_fetch(nfo, 1); hub->hubname = nmdc_unescape_and_decode(hub, hub->hubname_hub); } g_match_info_free(nfo); // $To if(g_regex_match(to, cmd, 0, &nfo)) { // 1 = to, 2 = from, 3 = msg char *to = g_match_info_fetch(nfo, 1); char *from = g_match_info_fetch(nfo, 2); char *msg = g_match_info_fetch(nfo, 3); char *msge = nmdc_unescape_and_decode(hub, msg); hub_user_t *u = g_hash_table_lookup(hub->users, from); if(!u) { g_message("[hub: %s] Got a $To from `%s', who is not on this hub!", hub->tab->name, from); char *user = charset_convert(hub, TRUE, from); ui_m(hub->tab, UIM_PASS|UIM_CHAT|UIP_MED, g_strdup_printf(" PM from unknown user: <%s> %s", user, msge)); free(user); } else uit_msg_msg(u, msge); g_free(msge); g_free(from); g_free(to); g_free(msg); } g_match_info_free(nfo); // $ForceMove if(g_regex_match(forcemove, cmd, 0, &nfo)) { // 1 = addr char *addr = g_match_info_fetch(nfo, 1); char *eaddr = nmdc_unescape_and_decode(hub, addr); ui_mf(hub->tab, UIP_HIGH, "\nThe hub is requesting you to move to %s.\nType `/connect %s' to do so.\n", eaddr, eaddr); hub_disconnect(hub, FALSE); g_free(eaddr); g_free(addr); } g_match_info_free(nfo); // $ConnectToMe if(g_regex_match(connecttome, cmd, 0, &nfo)) { // 1 = me, 2 = addr, 3 = TLS char *me = g_match_info_fetch(nfo, 1); char *addr = g_match_info_fetch(nfo, 2); char *tls = g_match_info_fetch(nfo, 3); if(strcmp(me, hub->nick_hub) != 0) g_message("Received a $ConnectToMe for someone else (to %s from %s)", me, addr); else { yuri_t uri; if(yuri_parse(addr, &uri) != 0 || *uri.scheme || uri.port == 0 || uri.hosttype == YURI_DOMAIN || *uri.path || *uri.query || *uri.fragment) g_message("Invalid host:port in $ConnectToMe (%s)", addr); else if(*tls && var_get_int(hub->id, VAR_tls_policy) == VAR_TLSP_DISABLE) g_message("$ConnectToMe from (%s) requires TLS, but TLS has been disabled in our tls_policy", addr); else if(!*tls && var_get_int(hub->id, VAR_tls_policy) == VAR_TLSP_FORCE) g_message("$ConnectToMe from (%s) without TLS, but TLS is required according to our tls_policy", addr); else cc_nmdc_connect(cc_create(hub), uri.host, uri.port, var_get(hub->id, VAR_local_address), *tls ? TRUE : FALSE); } g_free(me); g_free(addr); g_free(tls); } g_match_info_free(nfo); // $RevConnectToMe if(g_regex_match(revconnecttome, cmd, 0, &nfo)) { // 1 = other, 2 = me char *other = g_match_info_fetch(nfo, 1); char *me = g_match_info_fetch(nfo, 2); hub_user_t *u = g_hash_table_lookup(hub->users, other); if(strcmp(me, hub->nick_hub) != 0) g_message("Received a $RevConnectToMe for someone else (to %s from %s)", me, other); else if(!u) g_message("Received a $RevConnectToMe from someone not on the hub."); else if(!u->hastls && var_get_int(hub->id, VAR_tls_policy) == VAR_TLSP_FORCE) g_message("Received a $RevConnectToMe from client that does not support TLS."); else if(listen_hub_active(hub->id)) { // Unlike with ADC, the client sending the $RCTM can not indicate it // wants to use TLS or not, so the decision is with us. Let's require // tls_policy to be PREFER or FORCE here. int usetls = u->hastls && (var_get_int(hub->id, VAR_tls_policy) & (VAR_TLSP_PREFER|VAR_TLSP_FORCE)); int port = listen_hub_tcp(hub->id); net_writef(hub->net, net_is_ipv6(hub->net) ? "$ConnectToMe %s [%s]:%d%s|" : "$ConnectToMe %s %s:%d%s|", other, hub_ip(hub), port, usetls ? "S" : ""); cc_expect_add(hub, u, port, NULL, FALSE); } else g_message("Received a $RevConnectToMe, but we're not active."); g_free(me); g_free(other); } g_match_info_free(nfo); // $Search if(g_regex_match(search, cmd, 0, &nfo)) { // 1=from, 2=sizerestrict, 3=ismax, 4=size, 5=type, 6=query char *from = g_match_info_fetch(nfo, 1); char *sizerestrict = g_match_info_fetch(nfo, 2); char *ismax = g_match_info_fetch(nfo, 3); char *size = g_match_info_fetch(nfo, 4); char *type = g_match_info_fetch(nfo, 5); char *query = g_match_info_fetch(nfo, 6); unsigned short port = 0; char *nfrom = NULL; if(strncmp(from, "Hub:", 4) == 0) { if(strcmp(from+4, hub->nick_hub) != 0) /* Not our nick */ nfrom = from+4; } else { yuri_t uri; if(yuri_parse(from, &uri) != 0 || *uri.scheme || uri.port == 0 || uri.hosttype == YURI_DOMAIN || *uri.path || *uri.query || *uri.fragment) g_message("Invalid host:port in $Search (%s)", from); else if(!listen_hub_active(hub->id) || strcmp(uri.host, hub_ip(hub)) != 0 || uri.port != listen_hub_udp(hub->id)) { /* This search is not for our IP:port */ nfrom = uri.host; port = uri.port; } } if(nfrom) nmdc_search(hub, nfrom, port, sizerestrict[0] == 'F' ? -2 : ismax[0] == 'T' ? -1 : 1, g_ascii_strtoull(size, NULL, 10), type[0]-'0', query); g_free(from); g_free(sizerestrict); g_free(ismax); g_free(size); g_free(type); g_free(query); } g_match_info_free(nfo); // $GetPass if(strncmp(cmd, "$GetPass", 8) == 0) hub_password(hub, NULL); // $BadPass if(strncmp(cmd, "$BadPass", 8) == 0) { if(var_get(hub->id, VAR_password)) ui_m(hub->tab, 0, "Wrong password. Use '/hset password ' to edit your password or '/hunset password' to reset it."); else ui_m(hub->tab, 0, "Wrong password. Type /reconnect to try again."); hub_disconnect(hub, FALSE); } // $ValidateDenide if(strncmp(cmd, "$ValidateDenide", 15) == 0) { ui_m(hub->tab, 0, "Username invalid or already taken."); hub_disconnect(hub, TRUE); } // $HubIsFull if(strncmp(cmd, "$HubIsFull", 10) == 0) { ui_m(hub->tab, 0, "Hub is full."); hub_disconnect(hub, TRUE); } // $SR if(strncmp(cmd, "$SR", 3) == 0) { if(!search_handle_nmdc(hub, cmd)) g_message("Received invalid $SR from %s", net_remoteaddr(hub->net)); } // global hub message if(cmd[0] != '$') { char *msg = nmdc_unescape_and_decode(hub, cmd); if(msg[0] == '<' || (msg[0] == '*' && msg[1] == '*')) ui_m(hub->tab, UIM_PASS|UIM_CHAT|UIP_MED, msg); else { ui_m(hub->tab, UIM_PASS|UIM_CHAT|UIP_MED, g_strconcat(" ", msg, NULL)); g_free(msg); } } } static gboolean check_nfo(gpointer data) { hub_t *hub = data; if(hub->nick_valid) hub_send_nfo(hub); return TRUE; } static gboolean reconnect_timer(gpointer dat) { hub_connect(dat); ((hub_t *)dat)->reconnect_timer = 0; return FALSE; } static gboolean joincomplete_timer(gpointer dat) { hub_t *hub = dat; hub->joincomplete = TRUE; hub->joincomplete_timer = 0; return FALSE; } static void handle_error(net_t *n, int action, const char *err) { hub_t *hub = net_handle(n); ui_mf(hub->tab, 0, "%s: %s", action == NETERR_CONN ? "Could not connect to hub" : action == NETERR_RECV ? "Read error" : "Write error", err); hub_disconnect(hub, TRUE); } hub_t *hub_create(ui_tab_t *tab) { hub_t *hub = g_new0(hub_t, 1); // Get or create the hub id hub->id = db_vars_hubid(tab->name); if(!hub->id) { g_warn_if_fail(gnutls_rnd(GNUTLS_RND_RANDOM, &hub->id, 8) == 0); var_set(hub->id, VAR_hubname, tab->name, NULL); } hub->net = net_new(hub, handle_error); hub->tab = tab; hub->users = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, user_free); hub->sessions = g_hash_table_new(g_direct_hash, g_direct_equal); hub->nfo_timer = g_timeout_add_seconds(60, check_nfo, hub); g_hash_table_insert(hubs, &hub->id, hub); return hub; } static void handle_fully_connected(hub_t *hub) { net_set_keepalive(hub->net, hub->adc ? "\n" : "|"); /* If we have a pre-configured active IP, make sure to enable active mode * immediately. */ if(hub_ip(hub)) listen_refresh(); if(hub->adc) net_writef(hub->net, "HSUP ADBASE ADTIGR%s\n", var_get_bool(hub->id, VAR_adc_blom) ? " ADBLO0 ADBLOM" : ""); // In the case that the joincomplete detection fails, consider the join to be // complete anyway after a 2-minute timeout. hub->joincomplete_timer = g_timeout_add_seconds(120, joincomplete_timer, hub); // Start handling incoming messages net_readmsg(hub->net, hub->adc ? '\n' : '|', hub->adc ? adc_handle : nmdc_handle); } static void handle_handshake(net_t *n, const char *kpr, int proto) { g_return_if_fail(kpr != NULL); hub_t *hub = net_handle(n); g_return_if_fail(!hub->kp); if(proto == ALPN_NMDC) { hub->adc = FALSE; ui_mf(hub->tab, 0, "ALPN: negotiated NMDC."); } else if(proto == ALPN_ADC) { hub->adc = TRUE; ui_mf(hub->tab, 0, "ALPN: negotiated ADC."); } char kpf[53] = {}; base32_encode_dat(kpr, kpf, 32); // Get configured keyprint char *old = var_get(hub->id, VAR_hubkp); // No keyprint? Then assume first-use trust and save it to the config file. if(!old) { ui_mf(hub->tab, 0, "No previous TLS keyprint known. Storing `%s' for future validation.", kpf); var_set(hub->id, VAR_hubkp, kpf, NULL); handle_fully_connected(hub); return; } // Keyprint matches? no problems! if(strcmp(old, kpf) == 0) { handle_fully_connected(hub); return; } // Keyprint doesn't match... now we have a problem! ui_mf(hub->tab, UIP_HIGH, "\nWARNING: The TLS certificate of this hub has changed!\n" "Old keyprint: %s\n" "New keyprint: %s\n" "This can mean two things:\n" "- The hub you are connecting to is NOT the same as the one you intended to connect to.\n" "- The hub owner has changed the TLS certificate.\n" "If you accept the new keyprint and wish continue connecting, type `/accept'.\n", old, kpf); hub_disconnect(hub, FALSE); hub->kp = g_slice_alloc(32); memcpy(hub->kp, kpr, 32); } static void handle_connect(net_t *n, const char *addr) { hub_t *hub = net_handle(n); if(addr) { ui_mf(hub->tab, 0, "Trying %s...", addr); return; } ui_mf(hub->tab, 0, "Connected to %s.", net_remoteaddr(n)); if(hub->tls) net_settls(hub->net, FALSE, TRUE, handle_handshake); if(!net_is_connected(hub->net)) return; // TLS may negotiate a different protocol, and handle_handshake // will eventually call handle_fully_connected as well. if(hub->tls) return; handle_fully_connected(hub); } void hub_connect(hub_t *hub) { char *oaddr = var_get(hub->id, VAR_hubaddr); yuri_t addr; yuri_parse_copy(oaddr, &addr); if(!addr.port) addr.port = 411; hub->adc = strncmp(addr.scheme, "adc", 3) == 0; hub->tls = strcmp(addr.scheme, "adcs") == 0 || strcmp(addr.scheme, "nmdcs") == 0; if(!hub->tls && var_get_int(hub->id, VAR_tls_policy) == VAR_TLSP_FORCE) { ui_mf(hub->tab, 0, "Refusing to connect to %s; tls_policy is set to 'force' but this is not a TLS address.", oaddr); free(addr.buf); return; } if(hub->reconnect_timer) { g_source_remove(hub->reconnect_timer); hub->reconnect_timer = 0; } if(hub->joincomplete_timer) { g_source_remove(hub->joincomplete_timer); hub->joincomplete_timer = 0; } ui_mf(hub->tab, 0, "Connecting to %s...", oaddr); net_connect(hub->net, addr.host, addr.port, var_get(hub->id, VAR_local_address), handle_connect); free(addr.buf); } void hub_disconnect(hub_t *hub, gboolean recon) { if(hub->reconnect_timer) { g_source_remove(hub->reconnect_timer); hub->reconnect_timer = 0; } if(hub->joincomplete_timer) { g_source_remove(hub->joincomplete_timer); hub->joincomplete_timer = 0; } net_disconnect(hub->net); if(hub->kp) { g_slice_free1(32, hub->kp); hub->kp = NULL; } uit_hub_disconnect(hub->tab); g_hash_table_remove_all(hub->sessions); g_hash_table_remove_all(hub->users); g_free(hub->nick); hub->nick = NULL; g_free(hub->nick_hub); hub->nick_hub = NULL; g_free(hub->hubname); hub->hubname = NULL; g_free(hub->hubname_hub); hub->hubname_hub = NULL; g_free(hub->ip); hub->ip = NULL; hub->nick_valid = hub->isreg = hub->isop = hub->received_first = hub->joincomplete = hub->sharecount = hub->sharesize = hub->supports_nogetinfo = hub->state = hub->nfo_h_norm = hub->nfo_h_reg = hub->nfo_h_op = 0; if(!recon) ui_m(hub->tab, 0, "Disconnected."); else { int timeout = var_get_int(hub->id, VAR_reconnect_timeout); if(timeout) { ui_mf(hub->tab, 0, "Connection lost. Waiting %s before reconnecting.", str_formatinterval(timeout)); hub->reconnect_timer = g_timeout_add_seconds(timeout, reconnect_timer, hub); } else ui_m(hub->tab, 0, "Connection lost."); } } void hub_free(hub_t *hub) { // Make sure to disconnect before calling cc_remove_hub(). dl_queue_expect(), // called from cc_remove_hub() will look in the global userlist for // alternative hubs. Users of this hub must not be present in the list, // otherwise things will go wrong. hub_disconnect(hub, FALSE); cc_remove_hub(hub); g_hash_table_remove(hubs, &hub->id); listen_refresh(); net_unref(hub->net); g_free(hub->nfo_desc); g_free(hub->nfo_conn); g_free(hub->nfo_mail); g_free(hub->nfo_ip); g_free(hub->gpa_salt); g_hash_table_unref(hub->users); g_hash_table_unref(hub->sessions); g_source_remove(hub->nfo_timer); g_free(hub); } ncdc-1.23.1/src/strutil.c0000644000175000017500000002777314245144542012114 00000000000000/* ncdc - NCurses Direct Connect client Copyright (c) 2011-2022 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "ncdc.h" #include "strutil.h" /* A best-effort character conversion function. * * If, for whatever reason, a character could not be converted, a question mark * will be inserted instead. Unlike g_convert_with_fallback(), this function * does not fail on invalid byte sequences in the input string, either. Those * will simply be replaced with question marks as well. * * The character sets in 'to' and 'from' are assumed to form a valid conversion * according to your iconv implementation. * * Modifying this function to not require glib, but instead use the iconv and * memory allocation functions provided by your system, should be trivial. * * This function does not correctly handle character sets that may use zeroes * in the middle of a string (e.g. UTF-16). * * This function may not represent best practice with respect to character set * conversion, nor has it been thoroughly tested. */ char *str_convert(const char *to, const char *from, const char *str) { GIConv cd = g_iconv_open(to, from); if(cd == (GIConv)-1) { g_critical("No conversion from '%s' to '%s': %s", from, to, g_strerror(errno)); return g_strdup(""); } gsize inlen = strlen(str); gsize outlen = inlen+96; gsize outsize = inlen+100; char *inbuf = (char *)str; char *dest = g_malloc(outsize); char *outbuf = dest; while(inlen > 0) { gsize r = g_iconv(cd, &inbuf, &inlen, &outbuf, &outlen); if(r != (gsize)-1) continue; if(errno == E2BIG) { gsize used = outsize - outlen - 4; outlen += outsize; outsize += outsize; dest = g_realloc(dest, outsize); outbuf = dest + used; } else if(errno == EILSEQ || errno == EINVAL) { // skip this byte from the input inbuf++; inlen--; // Only output question mark if we happen to have enough space, otherwise // it's too much of a hassle... (In most (all?) cases we do have enough // space, otherwise we'd have gotten E2BIG anyway) if(outlen >= 1) { *outbuf = '?'; outbuf++; outlen--; } } else g_warn_if_reached(); } memset(outbuf, 0, 4); g_iconv_close(cd); return dest; } // Test that conversion is possible from UTF-8 to fmt and backwards. Not a // very comprehensive test, but ensures str_convert() can do its job. // The reason for this test is to make sure the conversion *exists*, // whether it makes sense or not can't easily be determined. Note that my // code currently can't handle zeroes in encoded strings, which is why this // is also tested (though, again, not comprehensive. But at least it does // not allow UTF-16) // Returns FALSE if the encoding can't be used, optionally setting err when it // has something useful to say. gboolean str_convert_check(const char *fmt, GError **err) { GError *l_err = NULL; gsize read, written, written2; char *enc = g_convert("abc", -1, "UTF-8", fmt, &read, &written, &l_err); if(l_err) { g_propagate_error(err, l_err); return FALSE; } else if(!enc || read != 3 || strlen(enc) != written) { g_free(enc); return FALSE; } else { char *dec = g_convert(enc, written, fmt, "UTF-8", &read, &written2, &l_err); g_free(enc); if(l_err) { g_propagate_error(err, l_err); return FALSE; } else if(!dec || read != written || written2 != 3 || strcmp(dec, "abc") != 0) { g_free(dec); return FALSE; } else { g_free(dec); return TRUE; } } } // Number of columns required to represent the UTF-8 string. int str_columns(const char *str) { int w = 0; while(*str) { w += gunichar_width(g_utf8_get_char(str)); str = g_utf8_next_char(str); } return w; } // calculate width in columns of str but only up to byte_len int substr_columns(const char *str, int byte_len) { const char *ostr = str; int w = 0; while(*str) { int c = gunichar_width(g_utf8_get_char(str)); str = g_utf8_next_char(str); if(str - ostr > byte_len) break; w += c; } return w; } // returns the byte offset to the last character in str (UTF-8) that does not // fit within col columns. int str_offset_from_columns(const char *str, int col) { const char *ostr = str; int w = 0; while(*str && w < col) { w += gunichar_width(g_utf8_get_char(str)); if(w <= col) str = g_utf8_next_char(str); } return str-ostr; } // Stolen from ncdu (with small modifications) // Result is stored in an internal buffer. char *str_formatsize(guint64 size) { static char dat[11]; /* "xxx.xx MiB" */ double r = size; char c = ' '; if(r < 1000.0f) { } else if(r < 1023e3f) { c = 'K'; r/=1024.0f; } else if(r < 1023e6f) { c = 'M'; r/=1048576.0f; } else if(r < 1023e9f) { c = 'G'; r/=1073741824.0f; } else if(r < 1023e12f){ c = 'T'; r/=1099511627776.0f; } else { c = 'P'; r/=1125899906842624.0f; } g_snprintf(dat, 11, "%6.2f %c%cB", r, c, c == ' ' ? ' ' : 'i'); return dat; } char *str_fullsize(guint64 size) { static char tmp[50]; static char res[50]; int i, j; /* the K&R method */ i = 0; do { tmp[i++] = size % 10 + '0'; } while((size /= 10) > 0); tmp[i] = '\0'; /* reverse and add thousand seperators */ j = 0; while(i--) { res[j++] = tmp[i]; if(i != 0 && i%3 == 0) res[j++] = '.'; } res[j] = '\0'; return res; } // UTF-8 aware case-insensitive string comparison. // This should be somewhat equivalent to // strcmp(g_utf8_casefold(a), g_utf8_casefold(b)), // but hopefully faster by avoiding the memory allocations. // Note that g_utf8_collate() is not suitable for case-insensitive filename // comparison. For example, g_utf8_collate('a', 'A') != 0. int str_casecmp(const char *a, const char *b) { int d; while(*a && *b) { d = g_unichar_tolower(g_utf8_get_char(a)) - g_unichar_tolower(g_utf8_get_char(b)); if(d) return d; a = g_utf8_next_char(a); b = g_utf8_next_char(b); } return *a ? 1 : *b ? -1 : 0; } // UTF-8 aware case-insensitive substring match. // This should be somewhat equivalent to // strstr(g_utf8_casefold(haystack), g_utf8_casefold(needle)) // If the same needle is used to match against many haystacks, it will be far // more efficient to use regular expressions instead. Those tend to be around 4 // times faster. char *str_casestr(const char *haystack, const char *needle) { gsize hlen = g_utf8_strlen(haystack, -1); gsize nlen = g_utf8_strlen(needle, -1); int d, l; const char *a, *b; while(hlen-- >= nlen) { a = haystack; b = needle; l = nlen; d = 0; while(l-- > 0 && *b) { d = g_unichar_tolower(g_utf8_get_char(a)) - g_unichar_tolower(g_utf8_get_char(b)); if(d) break; a = g_utf8_next_char(a); b = g_utf8_next_char(b); } if(!d) return (char *)haystack; haystack = g_utf8_next_char(haystack); } return NULL; } // Parses a size string. ('[GMK](iB)?'). Returns G_MAXUINT64 on error. guint64 str_parsesize(const char *str) { char *e = NULL; guint64 num = strtoull(str, &e, 10); if(e == str) return G_MAXUINT64; if(!*e) return num; if(*e == 'G' || *e == 'g') num *= 1024*1024*1024; else if(*e == 'M' || *e == 'm') num *= 1024*1024; else if(*e == 'K' || *e == 'k') num *= 1024; else return G_MAXUINT64; if(!e[1] || g_ascii_strcasecmp(e+1, "b") == 0 || g_ascii_strcasecmp(e+1, "ib") == 0) return num; else return G_MAXUINT64; } char *str_formatinterval(int sec) { static char buf[100]; int l=0; if(sec >= 24*3600) { l += g_snprintf(buf+l, 99-l, "%dd ", sec/(24*3600)); sec %= 24*3600; } if(sec >= 3600) { l += g_snprintf(buf+l, 99-l, "%dh ", sec/3600); sec %= 3600; } if(sec >= 60) { l += g_snprintf(buf+l, 99-l, "%dm ", sec/60); sec %= 60; } if(sec || !l) l += g_snprintf(buf+l, 99-l, "%ds", sec); if(buf[l-1] == ' ') buf[l-1] = 0; return buf; } // Parses an interval string, returns -1 on error. int str_parseinterval(const char *str) { int sec = 0; while(*str) { if(*str == ' ') str++; else if(*str >= '0' && *str <= '9') { char *e; int num = strtoull(str, &e, 0); if(!e || e == str) return -1; if(!*e || *e == ' ' || *e == 's' || *e == 'S') sec += num; else if(*e == 'm' || *e == 'M') sec += num*60; else if(*e == 'h' || *e == 'H') sec += num*3600; else if(*e == 'd' || *e == 'D') sec += num*3600*24; else return -1; str = *e ? e+1 : e; } else return -1; } return sec; } // Prefixes all strings in the array-of-strings with a string, obtained by // concatenating all arguments together. Last argument must be NULL. void strv_prefix(char **arr, const char *str, ...) { // create the prefix va_list va; va_start(va, str); char *prefix = g_strdup(str); const char *c; while((c = va_arg(va, const char *))) { char *o = prefix; prefix = g_strconcat(prefix, c, NULL); g_free(o); } va_end(va); // add the prefix to every string char **a; for(a=arr; *a; a++) { char *o = *a; *a = g_strconcat(prefix, *a, NULL); g_free(o); } g_free(prefix); } // Split a two-argument string into the two arguments. The first argument // should be shell-escaped, the second shouldn't. The string should be // writable. *first should be free()'d, *second refers to a location in str. void str_arg2_split(char *str, char **first, char **second) { GError *err = NULL; while(*str == ' ') str++; char *sep = str; gboolean bs = FALSE; *first = *second = NULL; do { if(err) g_error_free(err); err = NULL; sep = strchr(sep+1, ' '); if(sep && *(sep-1) == '\\') bs = TRUE; else { if(sep) *sep = 0; *first = g_shell_unquote(str, &err); if(sep) *sep = ' '; bs = FALSE; } } while(sep && (err || bs)); if(sep && sep != str) { *second = sep+1; while(**second == ' ') (*second)++; } } // Validates a hub name gboolean str_is_valid_hubname(const char *name) { const char *tmp; int len = 0; if(*name == '-' || *name == '_') return FALSE; for(tmp=name; *tmp; tmp = g_utf8_next_char(tmp)) if(++len && !g_unichar_isalnum(g_utf8_get_char(tmp)) && *tmp != '_' && *tmp != '-' && *tmp != '.') break; return !*tmp && len && len <= 25; } // Converts the "connection" setting into a speed in bytes/s, returns 0 on error. guint64 str_connection_to_speed(const char *conn) { if(!conn) return 0; char *end; double val = strtod(conn, &end); // couldn't convert if(end == conn) return 0; // raw number, assume mbit/s if(!*end) return (val*1000.0*1000.0)/8.0; // KiB/s, assume KiB/s (heh) if(strcasecmp(end, "KiB/s") == 0 || strcasecmp(end, " KiB/s") == 0) return val*1024.0; // otherwise, no idea what to do with it return 0; } // String pointer comparison, for use with qsort() on string arrays. int cmpstringp(const void *p1, const void *p2) { return strcmp(* (char * const *) p1, * (char * const *) p2); } ncdc-1.23.1/src/fl_local.c0000644000175000017500000010670714245144501012147 00000000000000/* ncdc - NCurses Direct Connect client Copyright (c) 2011-2022 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "ncdc.h" #include "fl_local.h" char *fl_local_list_file; fl_list_t *fl_local_list = NULL; GQueue *fl_refresh_queue = NULL; time_t fl_refresh_last = 0; // time when the last full file list refresh has been queued static gboolean fl_needflush = FALSE; // Index of the files in fl_local_list. Key = TTH root, value = GSList of files. static GHashTable *fl_hash_index; guint64 fl_local_list_size; // total share size, minus duplicate files int fl_local_list_length; // total number of unique files in the share static GThreadPool *fl_scan_pool; static GThreadPool *fl_hash_pool; GHashTable *fl_hash_queue = NULL; // set of files-to-hash guint64 fl_hash_queue_size = 0; static fl_list_t *fl_hash_cur = NULL; // most recent file initiated for hashing ratecalc_t fl_hash_rate; static guint64 fl_hash_reset = 0; // increased when fl_hash_cur is removed from the queue (to stop current hash operation) static GMutex fl_hash_resetlock; static GCond fl_hash_resetcond; #define TTH_BUFSIZE (512*1024) // Utility functions // Get full path to an item in our list. Result should be free'd. This function // isn't particularly fast. char *fl_local_path(fl_list_t *fl) { if(!fl->parent->parent) return g_strdup(db_share_path(fl->name)); char *tmp, *path = g_strdup(fl->name); fl_list_t *cur = fl->parent; while(cur->parent && cur->parent->parent) { tmp = path; path = g_build_filename(cur->name, path, NULL); g_free(tmp); cur = cur->parent; } tmp = g_build_filename(db_share_path(cur->name), path, NULL); g_free(path); return tmp; } // Similar to fl_list_from_path, except this function starts from the root of // the local filelist, and also accepts filesystem paths. In the latter case, // the path must be absolute and "real" (i.e., what realpath() would return), // since that is the path that is stored in the config file. This function // makes no attempt to convert the given path into a real path. fl_list_t *fl_local_from_path(const char *path) { fl_list_t *n = fl_list_from_path(fl_local_list, path); if(!n && path[0] == '/') { db_share_item_t *l = db_share_list(); for(; l->name; l++) if(strncmp(path, l->path, strlen(l->path)) == 0) break; if(l->name) { char *npath = g_strconcat(l->name, path+strlen(l->path), NULL); n = fl_list_from_path(fl_local_list, npath); g_free(npath); } } return n; } // Auto-complete for fl_local_from_path() void fl_local_suggest(char *path, char **sug) { fl_list_suggest(fl_local_list, path, sug); if(!sug[0]) path_suggest(path, sug); } // get files with the (raw) TTH. Result does not have to be freed. GSList *fl_local_from_tth(const char *root) { return g_hash_table_lookup(fl_hash_index, root); } // Fill a bloom filter with all local hashes void fl_local_bloom(bloom_t *b) { GHashTableIter iter; const char *tth; g_hash_table_iter_init(&iter, fl_hash_index); while(g_hash_table_iter_next(&iter, (gpointer *)&tth, NULL)) bloom_add(b, tth); } // should be run from a timer. periodically flushes all unsaved data to disk. gboolean fl_flush(gpointer dat) { if(fl_needflush) { // save our file list GError *err = NULL; if(!fl_save(fl_local_list, var_get(0, VAR_cid), 0, FALSE, NULL, fl_local_list_file, &err)) { // this is a pretty fatal error... oh well, better luck next time ui_mf(uit_main_tab, UIP_MED, "Error saving file list: %s", err->message); g_error_free(err); } } fl_needflush = FALSE; return TRUE; } // are we currently refreshing the share? gboolean fl_is_refreshing(void) { return fl_refresh_queue && fl_refresh_queue->head; } // Hash index interface. These operate on fl_hash_index and make sure // fl_local_list_size and _length stay correct. // Add to the hash index static void fl_hashindex_insert(fl_list_t *fl) { GSList *cur = g_hash_table_lookup(fl_hash_index, fl->tth); if(cur) { g_return_if_fail(cur == g_slist_insert(cur, fl, 1)); // insert item without modifying the pointer } else { cur = g_slist_prepend(cur, fl); g_hash_table_insert(fl_hash_index, fl->tth, cur); fl_local_list_size += fl->size; } fl_local_list_length = g_hash_table_size(fl_hash_index); } // ...and remove a file. This is done when a file is actually removed from the // share, or when its TTH information has been invalidated. static void fl_hashindex_del(fl_list_t *fl) { if(!fl->hastth) return; GSList *cur = g_hash_table_lookup(fl_hash_index, fl->tth); fl->hastth = FALSE; cur = g_slist_remove(cur, fl); if(!cur) { g_hash_table_remove(fl_hash_index, fl->tth); fl_local_list_size -= fl->size; // there's another file with the same TTH. } else g_hash_table_replace(fl_hash_index, ((fl_list_t *)cur->data)->tth, cur); fl_local_list_length = g_hash_table_size(fl_hash_index); } // Scanning directories // Note: The `file' structure points to a (sub-)item in fl_local_list, and will // be accessed from both the scan thread and the main thread. It is therefore // important that no changes are made to the local file list while the scan // thread is active. typedef struct fl_scan_t { fl_list_t **file, **res; char **path; GRegex *excl_regex; gboolean emptydirs; gboolean inc_hidden; gboolean symlink; gboolean (*donefun)(gpointer); } fl_scan_t; // Removes duplicate files (that is, files with the same name in a // case-insensitive context) from a dirtectory. static void fl_scan_rmdupes(fl_list_t *fl, const char *vpath) { int i = 1; while(isub->len) { fl_list_t *a = g_ptr_array_index(fl->sub, i-1); fl_list_t *b = g_ptr_array_index(fl->sub, i); if(fl_list_cmp_strict(a, b) == 0) { char *tmp = g_build_filename(vpath, b->name, NULL); ui_mf(uit_main_tab, UIP_MED, "Not sharing \"%s\": Other file with same name (but different case) already shared.", tmp); fl_list_remove(b); g_free(tmp); } else i++; } } // Queue an id to be removed from the hash files. Files are removed in a batch // of DELETE FROM queries in a single transaction. This is significantly faster // than using a separate transaction for each DELETE. static void fl_scan_invalidate(gint64 id, gboolean force_flush) { static gint64 rmids[50]; static int i = 0; if(id) rmids[i++] = id; if(i >= 50 || (force_flush && i > 0)) { db_fl_rmfiles(rmids, i); i = 0; } } // Fetches TTH information either from the database or from *oldpar, and // invalidates this data if the file has changed. static void fl_scan_check(fl_list_t *oldpar, fl_list_t *new, const char *real) { time_t oldlastmod; guint64 oldsize; char oldhash[24]; gint64 oldid = 0; fl_list_t *old = oldpar && oldpar->sub ? fl_list_file_strict(oldpar, new) : NULL; // Get from the previous in-memory structure if(old && fl_list_getlocal(old).id) { oldid = fl_list_getlocal(old).id; oldlastmod = fl_list_getlocal(old).lastmod; oldsize = old->size; memcpy(oldhash, old->tth, 24); // Otherwise, do a database lookup on the file path } else oldid = db_fl_getfile(real, &oldlastmod, &oldsize, oldhash); // Check for file change if(oldid && (oldlastmod < fl_list_getlocal(new).lastmod || oldsize != new->size)) { g_debug("fl: Dropping hash information for `%s': file has changed.", real); fl_scan_invalidate(oldid, FALSE); // Otherwise, update *new } else if(oldid) { new->hastth = TRUE; memcpy(new->tth, oldhash, 24); fl_list_getlocal(new).lastmod = oldlastmod; fl_list_getlocal(new).id = oldid; } } // *name is in filesystem encoding. For *path and *vpath see fl_scan_dir(). static fl_list_t *fl_scan_item(fl_list_t *old, const char *path, const char *vpath, const char *name, fl_scan_t *opts) { char *uname = NULL; // name-to-UTF8 char *vcpath = NULL; // vpath + uname char *ename = NULL; // uname-to-filesystem char *cpath = NULL; // path + ename char *real = NULL; // path_expand(cpath)-to-UTF8 fl_list_t *node = NULL; // Try to get a UTF-8 filename uname = g_filename_to_utf8(name, -1, NULL, NULL, NULL); if(!uname) uname = g_filename_display_name(name); // Check for share_exclude as soon as we have the confname if(opts->excl_regex && g_regex_match(opts->excl_regex, uname, 0, NULL)) goto done; // Get the virtual path (for reporting purposes) vcpath = g_build_filename(vpath, uname, NULL); // Check that the UTF-8 filename can be converted back to something we can // access on the filesystem. If it can't be converted back, we won't share // the file at all. Keeping track of a raw-to-UTF-8 filename lookup table // isn't worth the effort. ename = g_filename_from_utf8(uname, -1, NULL, NULL, NULL); if(!ename) { ui_mf(uit_main_tab, UIP_MED, "Error reading directory entry in \"%s\": Invalid encoding.", vcpath); goto done; } // Get cpath and try to stat() the file cpath = g_build_filename(path, ename, NULL); struct stat dat; int r = opts->symlink ? stat(cpath, &dat) : lstat(cpath, &dat); if(r < 0 || S_ISLNK(dat.st_mode) || !(S_ISREG(dat.st_mode) || S_ISDIR(dat.st_mode))) { if(r < 0) ui_mf(uit_main_tab, UIP_MED, "Error stat'ing \"%s\": %s", vcpath, g_strerror(errno)); else if(!S_ISLNK(dat.st_mode)) ui_mf(uit_main_tab, UIP_MED, "Not sharing \"%s\": Neither file nor directory.", vcpath); goto done; } // Get the path_expand() (for lookup in the database) // TODO: this path is only used if fl_scan_check() can't find the item in the // old fl_list structure. It may be more efficient to only try to execute // this code when this is the case. char *tmp = path_expand(cpath); if(!tmp) { ui_mf(uit_main_tab, UIP_MED, "Error getting file path for \"%s\": %s", vcpath, g_strerror(errno)); goto done; } real = g_filename_to_utf8(tmp, -1, NULL, NULL, NULL); g_free(tmp); if(!real) { ui_mf(uit_main_tab, UIP_MED, "Error getting file path for \"%s\": %s", vcpath, "Encoding error."); goto done; } // create the node node = fl_list_create(uname, S_ISREG(dat.st_mode) ? TRUE : FALSE); if(S_ISREG(dat.st_mode)) { node->isfile = TRUE; node->size = dat.st_size; fl_list_getlocal(node).lastmod = dat.st_mtime; } // Fetch id, tth, and hashtth fields. if(node->isfile) fl_scan_check(old, node, real); done: g_free(uname); g_free(vcpath); g_free(cpath); g_free(ename); g_free(real); return node; } // recursive // Doesn't handle paths longer than PATH_MAX, but I don't think it matters all that much. // *path is the filesystem path in filename encoding, vpath is the virtual path in UTF-8. static void fl_scan_dir(fl_list_t *parent, fl_list_t *old, const char *path, const char *vpath, fl_scan_t *opts) { GError *err = NULL; GDir *dir = g_dir_open(path, 0, &err); if(!dir) { ui_mf(uit_main_tab, UIP_MED, "Error reading directory \"%s\": %s", vpath, g_strerror(errno)); g_error_free(err); return; } const char *name; while((name = g_dir_read_name(dir))) { if(strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue; if(!opts->inc_hidden && name[0] == '.') continue; // check with *excl, stat and create fl_list_t *item = fl_scan_item(old, path, vpath, name, opts); // and add it if(item) fl_list_add(parent, item, -1); } g_dir_close(dir); // Sort fl_list_sort(parent); fl_scan_rmdupes(parent, vpath); // check for directories (outside of the above loop, to avoid having too many // directories opened at the same time. Costs some extra CPU cycles, though...) int i; for(i=0; isub->len; i++) { fl_list_t *cur = g_ptr_array_index(parent->sub, i); if(!cur->isfile) { char *enc = g_filename_from_utf8(cur->name, -1, NULL, NULL, NULL); char *cpath = g_build_filename(path, enc, NULL); char *virtpath = g_build_filename(vpath, cur->name, NULL); cur->sub = g_ptr_array_new_with_free_func(fl_list_free); fl_scan_dir(cur, old && old->sub ? fl_list_file_strict(old, cur) : NULL, cpath, virtpath, opts); g_free(virtpath); g_free(cpath); g_free(enc); } // Removes this item if it is an empty directory and if !opts->emptydirs. if(!opts->emptydirs && !cur->isfile && !cur->sub->len) { fl_list_remove(cur); i--; // Make sure that the index doesn't change with the next iteration } } } // Must be called in a separate thread. static void fl_scan_thread(gpointer data, gpointer udata) { fl_scan_t *args = data; int i, len = g_strv_length(args->path); for(i=0; ipath[i], -1, NULL, NULL, NULL); cur->sub = g_ptr_array_new_with_free_func(fl_list_free); fl_scan_dir(cur, args->file[i], tmp, args->path[i], args); g_free(tmp); args->res[i] = cur; } fl_scan_invalidate(0, TRUE); g_idle_add_full(G_PRIORITY_HIGH_IDLE, args->donefun, args, NULL); } // Hashing files // This struct is passed from the main thread to the hasher and back with modifications. typedef struct fl_hash_t { fl_list_t *file; // only accessed from main thread char *path; // owned by main thread, read from hash thread guint64 filesize; // set by main thread char root[24]; // set by hash thread GError *err; // set by hash thread time_t lastmod; // set by hash thread gint64 id; // set by hash thread gdouble time; // set by hash thread guint64 lastreset; // last value of fl_hash_reset when starting this thread } fl_hash_t; // Maximum number of levels, including root (level 0). The ADC docs specify // that this should be at least 7, and everyone else uses 10. Since keeping 10 // levels of hash data does eat up more space than I'd be willing to spend on // it, let's use 8. #if INTERFACE #define fl_hash_keep_level 8 #endif /* Space requirements per file for different levels (in bytes, min - max) and * the block size of a 1GiB file. * * 10 6144 - 12288 2 MiB * 9 3072 - 6144 4 MiB * 8 1536 - 3072 8 MiB * 7 768 - 1536 16 MiB * 6 384 - 768 32 MiB */ // there's no need for better granularity than this #define fl_hash_max_granularity G_GUINT64_CONSTANT(64 * 1024) // adding/removing items from the files-to-be-hashed queue // _append() assumes that fl->hastth is false. #define fl_hash_queue_append(fl) do {\ g_warn_if_fail(!fl->hastth);\ gboolean start = !g_hash_table_size(fl_hash_queue);\ if(start || !g_hash_table_lookup(fl_hash_queue, fl))\ fl_hash_queue_size += fl->size;\ g_hash_table_insert(fl_hash_queue, fl, (void *)1);\ if(start)\ fl_hash_process();\ } while(0) #define fl_hash_queue_del(fl) do {\ if((fl)->isfile && g_hash_table_lookup(fl_hash_queue, fl)) {\ fl_hash_queue_size -= fl->size;\ g_hash_table_remove(fl_hash_queue, fl);\ if((fl) == fl_hash_cur) {\ g_mutex_lock(&fl_hash_resetlock);\ fl_hash_reset++;\ g_cond_signal(&fl_hash_resetcond);\ g_mutex_unlock(&fl_hash_resetlock);\ }\ }\ } while(0) // Recursively deletes a fl_list structure from the hash queue static void fl_hash_queue_delrec(fl_list_t *f) { if(f->isfile) fl_hash_queue_del(f); else { int i; for(i=0; isub->len; i++) fl_hash_queue_delrec(g_ptr_array_index(f->sub, i)); } } // Checks whether this hashing operation has been cancelled and waits until the // hash ratecalc object has enough burst to allow us to continue hashing again. // Returns the allowed burst, or 0 on cancellation. static int fl_hash_burst(guint64 lastreset) { int b = 0; g_mutex_lock(&fl_hash_resetlock); while(fl_hash_reset == lastreset && (b = ratecalc_burst(&fl_hash_rate)) <= 0) g_cond_wait_until(&fl_hash_resetcond, &fl_hash_resetlock, g_get_monotonic_time() + 100*G_TIME_SPAN_MILLISECOND); g_mutex_unlock(&fl_hash_resetlock); return b; } static gboolean fl_hash_done(gpointer dat); static void fl_hash_thread(gpointer data, gpointer udata) { fl_hash_t *args = data; // static, since only one hash thread is allowed and this saves stack space tth_ctx_t tth; char *buf = g_malloc(TTH_BUFSIZE); char *blocks = NULL; int f = -1; char *real = NULL; time(&args->lastmod); GTimer *tm = g_timer_new(); char *tmp = path_expand(args->path); if(!tmp) { g_set_error(&args->err, 1, 0, "Error getting file path: %s", g_strerror(errno)); goto finish; } real = g_filename_to_utf8(tmp, -1, NULL, NULL, NULL); g_free(tmp); g_return_if_fail(real); // really shouldn't happen, we fetched this from a UTF-8 string after all. f = open(args->path, O_RDONLY); if(f < 0) { g_set_error(&args->err, 1, 0, "Error reading file: %s", g_strerror(errno)); goto finish; } // Initialize some stuff guint64 blocksize = tth_blocksize(args->filesize, 1<<(fl_hash_keep_level-1)); blocksize = MAX(blocksize, fl_hash_max_granularity); int blocks_num = tth_num_blocks(args->filesize, blocksize); blocks = g_malloc(24*blocks_num); tth_init(&tth); fadv_t adv; fadv_init(&adv, f, 0, VAR_FFC_HASH); int r, nr; guint64 rd = 0; int block_cur = 0; guint64 block_len = 0; if((nr = fl_hash_burst(args->lastreset)) <= 0) goto finish; while((r = read(f, buf, MIN(nr, TTH_BUFSIZE))) > 0) { rd += r; fadv_purge(&adv, r); // file has been modified. time to back out if(rd > args->filesize) { g_set_error_literal(&args->err, 1, 0, "File has been modified."); goto finish; } ratecalc_add(&fl_hash_rate, r); // and hash char *b = buf; while(r > 0) { int w = MIN(r, blocksize-block_len); tth_update(&tth, b, w); block_len += w; b += w; r -= w; if(block_len >= blocksize) { tth_final(&tth, blocks+(block_cur*24)); tth_init(&tth); block_cur++; block_len = 0; } } if((nr = fl_hash_burst(args->lastreset)) <= 0) goto finish; } if(r < 0) { g_set_error(&args->err, 1, 0, "Error reading file: %s", g_strerror(errno)); goto finish; } if(rd != args->filesize) { g_set_error_literal(&args->err, 1, 0, "File has been modified."); goto finish; } // Calculate last block if(!args->filesize || block_len) { tth_final(&tth, blocks+(block_cur*24)); block_cur++; } g_return_if_fail(block_cur == blocks_num); // Calculate root hash tth_root(blocks, blocks_num, args->root); // Add to database args->id = db_fl_addhash(real, args->filesize, args->lastmod, args->root, blocks, 24*blocks_num); if(!args->id) g_set_error_literal(&args->err, 1, 0, "Error saving hash data to the database."); finish: if(f > 0) { fadv_close(&adv); close(f); } g_free(buf); g_free(real); g_free(blocks); args->time = g_timer_elapsed(tm, NULL); g_timer_destroy(tm); g_idle_add_full(G_PRIORITY_HIGH_IDLE, fl_hash_done, args, NULL); } static void fl_hash_process() { if(!g_hash_table_size(fl_hash_queue)) { ratecalc_unregister(&fl_hash_rate); ratecalc_reset(&fl_hash_rate); var_set_bool(0, VAR_fl_done, TRUE); return; } var_set_bool(0, VAR_fl_done, FALSE); ratecalc_register(&fl_hash_rate, RCC_HASH); // get one item from fl_hash_queue GHashTableIter iter; fl_list_t *file; g_hash_table_iter_init(&iter, fl_hash_queue); g_hash_table_iter_next(&iter, (gpointer *)&file, NULL); fl_hash_cur = file; // pass stuff to the hash thread fl_hash_t *args = g_new0(fl_hash_t, 1); args->file = file; char *tmp = fl_local_path(file); args->path = g_filename_from_utf8(tmp, -1, NULL, NULL, NULL); g_free(tmp); args->filesize = file->size; args->lastreset = fl_hash_reset; g_message("Start hashing %s", args->path); g_thread_pool_push(fl_hash_pool, args, NULL); } static gboolean fl_hash_done(gpointer dat) { fl_hash_t *args = dat; fl_list_t *fl = args->file; // remove file from queue, ignore this hash if the file was already removed // by some other process. if(!g_hash_table_remove(fl_hash_queue, fl)) goto fl_hash_done_f; fl_hash_queue_size -= fl->size; if(args->err) { ui_mf(uit_main_tab, UIP_MED, "Error hashing \"%s\": %s", args->path, args->err->message); goto fl_hash_done_f; } g_message("Completed hashing %s in %.2fs", args->path, args->time); // update file and hash info memcpy(fl->tth, args->root, 24); fl->hastth = 1; fl_list_getlocal(fl).lastmod = args->lastmod; fl_list_getlocal(fl).id = args->id; fl_hashindex_insert(fl); fl_needflush = TRUE; fl_hash_done_f: if(args->err) g_error_free(args->err); g_free(args->path); g_free(args); // Hash next file in the queue fl_hash_process(); return FALSE; } // Refresh filelist & (un)share directories // get or create a root directory static fl_list_t *fl_refresh_getroot(const char *name) { // no root? create! if(!fl_local_list) { fl_local_list = fl_list_create("", FALSE); fl_local_list->sub = g_ptr_array_new_with_free_func(fl_list_free); } fl_list_t *cur = fl_list_file(fl_local_list, name); // dir not present yet? create a stub if(!cur) { cur = fl_list_create(name, FALSE); cur->sub = g_ptr_array_new_with_free_func(fl_list_free); fl_list_add(fl_local_list, cur, -1); fl_list_sort(fl_local_list); } return cur; } // Recursively adds the files to either the hash index or the hash queue. static void fl_refresh_addhash(fl_list_t *cur) { if(cur->isfile) { if(cur->hastth) fl_hashindex_insert(cur); else fl_hash_queue_append(cur); } else { int i; for(i=0; isub->len; i++) fl_refresh_addhash(g_ptr_array_index(cur->sub, i)); } } // Recursively removes the files from the hash index. Unlike _addhash(), this // doesn't touch the hash queue. The files should have been removed from the // hash queue before doing the refresh. static void fl_refresh_delhash(fl_list_t *cur) { if(cur->isfile && cur->hastth) fl_hashindex_del(cur); else if(!cur->isfile) { int i; for(i=0; isub->len; i++) fl_refresh_delhash(g_ptr_array_index(cur->sub, i)); } } static void fl_refresh_compare(fl_list_t *old, fl_list_t *new) { int oldi = 0; int newi = 0; while(oldi < old->sub->len || newi < new->sub->len) { fl_list_t *oldl = oldi >= old->sub->len ? NULL : g_ptr_array_index(old->sub, oldi); fl_list_t *newl = newi >= new->sub->len ? NULL : g_ptr_array_index(new->sub, newi); // Don't use fl_list_cmp() here, since that one doesn't return 0 int cmp = !oldl ? 1 : !newl ? -1 : fl_list_cmp_strict(oldl, newl); gboolean check = FALSE, remove = FALSE, insert = FALSE; // special case #1: old == new, but one is a directory and the other is a file // special case #2: old == new, but the file names have different case // In both situations we just delete our information and overwrite it with new. if(cmp == 0 && (!!oldl->isfile != !!newl->isfile || strcmp(oldl->name, newl->name) != 0)) remove = insert = TRUE; else if(cmp == 0) // old == new, just check check = TRUE; else if(cmp > 0) // old > new: insert new insert = TRUE; else // old < new: remove remove = TRUE; // check if(check) { // File, update information if(oldl->isfile) { // Remove old file from the hash index if it was in there if(oldl->hastth) fl_hashindex_del(oldl); // Update old with info from new oldl->hastth = newl->hastth; oldl->size = newl->size; // TODO: Size of parent dir isn't updated memcpy(oldl->tth, newl->tth, 24); fl_list_getlocal(oldl).id = fl_list_getlocal(newl).id; fl_list_getlocal(oldl).lastmod = fl_list_getlocal(newl).lastmod; // Add updated file to either the hash queue or index fl_refresh_addhash(oldl); // Directory, recurse into it } else fl_refresh_compare(oldl, newl); oldi++; newi++; } // remove if(remove) { fl_refresh_delhash(oldl); fl_list_remove(oldl); // don't modify oldi, after deletion it will automatically point to the next item in the list } // insert if(insert) { fl_list_t *tmp = fl_list_copy(newl); fl_list_add(old, tmp, oldi); fl_refresh_addhash(tmp); oldi++; // after fl_list_add(), oldi points to the new item. But we don't have to check that one again, so increase. newi++; } } } /* A visual explanation of the above algorithm: * old new * a a same (new == old; new++, old++) * b b same * d c insert c (!old || new < old; new++, old stays) * d d same * e f delete e (!new || new > old; new stays, old++) * f f same * * More advanced example: * a c delete a * b c delete b * e c insert c * e d insert d * e e same * f - delete f */ static gboolean fl_refresh_scanned(gpointer dat); static void fl_refresh_process() { if(!fl_refresh_queue->head) return; // construct the list of to-be-scanned directories fl_list_t *dir = fl_refresh_queue->head->data; fl_scan_t *args = g_slice_new0(fl_scan_t); args->donefun = fl_refresh_scanned; args->emptydirs = var_get_bool(0, VAR_share_emptydirs); args->inc_hidden = var_get_bool(0, VAR_share_hidden); args->symlink = var_get_bool(0, VAR_share_symlinks); char *excl = var_get(0, VAR_share_exclude); if(excl) args->excl_regex = g_regex_new(excl, G_REGEX_OPTIMIZE, 0, NULL); // Don't allow files in the scanned directory to be hashed while refreshing. // Since the refresh thread will create a completely new fl_list structure, // any changes to the old one will be lost. fl_hash_queue_delrec(dir); // one dir, the simple case if(dir != fl_local_list) { args->file = g_new0(fl_list_t *, 2); args->res = g_new0(fl_list_t *, 2); args->path = g_new0(char *, 2); args->file[0] = dir; args->path[0] = fl_local_path(dir); // refresh the entire share, which consists of multiple dirs. } else { time(&fl_refresh_last); db_share_item_t *l; int i, len = 0; for(l=db_share_list(); l->name; l++) len++; args->file = g_new0(fl_list_t *, len+1); args->res = g_new0(fl_list_t *, len+1); args->path = g_new0(char *, len+1); for(i=0,l=db_share_list(); l->name; i++,l++) { args->file[i] = fl_refresh_getroot(l->name); args->path[i] = g_strdup(l->path); } } // scan the requested directories in the background g_thread_pool_push(fl_scan_pool, args, NULL); } static gboolean fl_refresh_scanned(gpointer dat) { fl_scan_t *args = dat; int i, len = g_strv_length(args->path); for(i=0; ifile[i], args->res[i]); fl_list_free(args->res[i]); } // If the hash queue is empty after calling fl_refresh_compare() then it // means the file list is completely hashed. if(!g_hash_table_size(fl_hash_queue)) var_set_bool(0, VAR_fl_done, TRUE); fl_needflush = TRUE; g_strfreev(args->path); if(args->excl_regex) g_regex_unref(args->excl_regex); g_free(args->file); g_free(args->res); g_slice_free(fl_scan_t, args); g_queue_pop_head(fl_refresh_queue); if(fl_refresh_queue->head) fl_refresh_process(); else // force a flush when all queued refreshes have been processed fl_flush(NULL); return FALSE; } void fl_refresh(fl_list_t *dir) { if(!dir) dir = fl_local_list; GList *n; for(n=fl_refresh_queue->head; n; n=n->next) { fl_list_t *c = n->data; // if current dir is part of listed dir then it's already queued if(dir == c || fl_list_is_child(c, dir)) return; // if listed dir is part of current dir, then we can remove that item (provided it's not being refreshed currently) if(n->prev && (dir == fl_local_list || fl_list_is_child(c, dir))) { n = n->prev; g_queue_delete_link(fl_refresh_queue, n->next); } } // add to queue and process g_queue_push_tail(fl_refresh_queue, dir); if(fl_refresh_queue->head == fl_refresh_queue->tail) fl_refresh_process(); } // Adds a directory to the file list and initiates a refresh on it (Assumes the // directory has already been added to the config file). void fl_share(const char *dir) { fl_refresh(fl_refresh_getroot(dir)); } // Only affects the filelist and hash data, does not modify the config file. // This function is far more efficient than removing the dir from the config // and doing a /refresh. (Which may also result in some errors being displayed // when a currently-being-hashed file is removed due to the directory not being // present in the config file anymore). void fl_unshare(const char *dir) { if(dir) { fl_list_t *fl = fl_list_file(fl_local_list, dir); g_return_if_fail(fl); fl_hash_queue_delrec(fl); fl_refresh_delhash(fl); fl_list_remove(fl); } else if(fl_local_list) { fl_hash_queue_delrec(fl_local_list); fl_refresh_delhash(fl_local_list); fl_list_free(fl_local_list); fl_local_list = fl_list_create("", FALSE); fl_local_list->sub = g_ptr_array_new_with_free_func(fl_list_free); } // force a refresh, people may be in a hurry with removing stuff fl_needflush = TRUE; fl_flush(NULL); } // Initialize local filelist // Walks through the file list and inserts everything into the fl_hashindex. static void fl_init_list(fl_list_t *fl) { int i; for(i=0; isub->len; i++) { fl_list_t *c = g_ptr_array_index(fl->sub, i); if(c->isfile && c->hastth) fl_hashindex_insert(c); else if(!c->isfile) fl_init_list(c); } } static gboolean fl_init_autorefresh(gpointer dat) { int r = var_get_int(0, VAR_autorefresh); time_t t = time(NULL); if(r && fl_refresh_last+r < t && !fl_is_refreshing() && fl_hash_queue_size == 0) fl_refresh(NULL); return TRUE; } void fl_init() { GError *err = NULL; gboolean dorefresh = FALSE; // init stuff fl_local_list = NULL; fl_local_list_file = g_build_filename(db_dir, "files.xml.bz2", NULL); fl_refresh_queue = g_queue_new(); fl_scan_pool = g_thread_pool_new(fl_scan_thread, NULL, 1, FALSE, NULL); fl_hash_pool = g_thread_pool_new(fl_hash_thread, NULL, 1, FALSE, NULL); fl_hash_queue = g_hash_table_new(g_direct_hash, g_direct_equal); // Even though the keys are the tth roots, we can just use g_int_hash. The // first four bytes provide enough unique data anyway. fl_hash_index = g_hash_table_new(g_int_hash, tiger_hash_equal); ratecalc_init(&fl_hash_rate); // flush unsaved data to disk every 60 seconds g_timeout_add_seconds_full(G_PRIORITY_LOW, 60, fl_flush, NULL, NULL); // Check every 60 seconds whether we need to refresh. This automatically // adapts itself to changes to the autorefresh config variable. Unlike using // the configured interval as a timeout, in which case we need to manually // adjust the timer on every change. g_timeout_add_seconds_full(G_PRIORITY_LOW, 60, fl_init_autorefresh, NULL, NULL); // The following code may take a while on large shares, so indicate to the UI // that we're busy. ui_m(NULL, UIM_NOLOG|UIM_DIRECT, "Loading file list..."); ui_draw(); // check whether something is shared gboolean sharing = db_share_list()->name ? TRUE : FALSE; // load our files.xml.bz2 fl_local_list = sharing ? fl_load(fl_local_list_file, &err, TRUE) : NULL; if(sharing && !fl_local_list) { ui_mf(uit_main_tab, UIP_MED, "Error loading local filelist: %s. Re-building list.", err->message); g_error_free(err); dorefresh = TRUE; } else if(!sharing) // Force a refresh when we're not sharing anything. This makes sure that we // at least have a files.xml.bz2 dorefresh = TRUE; ui_m(NULL, UIM_NOLOG, NULL); // Always make sure we at least have an fl_local_list if(!fl_local_list) { fl_local_list = fl_list_create("", FALSE); fl_local_list->sub = g_ptr_array_new_with_free_func(fl_list_free); } // If ncdc was previously closed while hashing, make sure to force a refresh // this time to continue the hash progress. if(sharing && !var_get_bool(0, VAR_fl_done)) { dorefresh = TRUE; ui_m(uit_main_tab, UIM_NOTIFY, "File list incomplete, refreshing..."); } // Initialize the fl_hash_index if(fl_local_list) fl_init_list(fl_local_list); // reset loading indicator if(!fl_local_list || !dorefresh) ui_m(NULL, UIM_NOLOG|UIM_DIRECT, NULL); if(dorefresh || var_get_int(0, VAR_autorefresh)) fl_refresh(NULL); } // Garbage-collect. This will remove unused entries from the hashfiles table. // The algorithm works as follows: // - Create array of `active' ids. // (`active' = there is an fl_list entry in memory with that id) // - Sort the array. // - Walk through all ids from the hashfiles table in ascending order // - If an id is present in the hashfiles table but not in the sorted array // created earlier, add the id to a delete-these-ids array. // - Delete all rows indicated with the delete-these-ids array // This algorithm is intended to have low memory requirements, while still // being quite fast and efficient. static GArray *fl_gc_active = NULL; static int fl_gc_last = 0; static GArray *fl_gc_remove = NULL; static gint fl_gc_idcmp(gconstpointer a, gconstpointer b) { const gint64 *na = a; const gint64 *nb = b; return *na > *nb ? 1 : *na == *nb ? 0 : -1; } // Called from db_fl_getids() with a new id. It is assumed that this id is // larger than or equal to the given id in any previous calls. static void fl_gc_id(gint64 id) { while(fl_gc_last < fl_gc_active->len && g_array_index(fl_gc_active, gint64, fl_gc_last) < id) fl_gc_last++; if(fl_gc_last >= fl_gc_active->len || g_array_index(fl_gc_active, gint64, fl_gc_last) > id) g_array_append_val(fl_gc_remove, id); } // Returns TRUE when it has done garbage collection, FALSE if it's not possible // to create a list of `active' ids because no full file refresh has been // performed yet. gboolean fl_gc() { if(!fl_refresh_last || fl_refresh_queue->head) return FALSE; // Init data fl_gc_active = g_array_sized_new(FALSE, FALSE, 8, g_hash_table_size(fl_hash_index)); fl_gc_remove = g_array_new(FALSE, FALSE, 8); fl_gc_last = 0; // Fill fl_active array. It is possible that two identical ids are added to // the array, but this isn't a problem. GSList *l; GHashTableIter iter; g_hash_table_iter_init(&iter, fl_hash_index); while(g_hash_table_iter_next(&iter, NULL, (gpointer *)&l)) for(; l; l=l->next) g_array_append_val(fl_gc_active, fl_list_getlocal((fl_list_t *)l->data).id); g_array_sort(fl_gc_active, fl_gc_idcmp); // walk through hashfiles table and fill fl_gc_remove db_fl_getids(fl_gc_id); // Delete rows and clean up g_debug("fl-gc: Removing %d entries from hashrows.", fl_gc_remove->len); db_fl_rmfiles((gint64 *)fl_gc_remove->data, fl_gc_remove->len); g_array_unref(fl_gc_active); g_array_unref(fl_gc_remove); return TRUE; } ncdc-1.23.1/src/fl_save.c0000644000175000017500000002723614245144504012015 00000000000000/* ncdc - NCurses Direct Connect client Copyright (c) 2011-2022 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "ncdc.h" #include "fl_save.h" /* The "targetsize algorithm" (I thought of that name myself, googling won't help you. Probably): * * If enabled, the targetsize algorithm determines which directories should be * included in the serialization or not. The goal of this algorithm is to: * - Assign a roughly equal number of serialization bytes to each directory in * the same level. * - Try to keep the total serialized data below or around the target size. * * It works as follows: * serialize_full(dir, targetsize): * files_left = files_in_dir(dir) * dirs_left = dirs_in_dir(dir) * for each item in dir: * if item is file: * serialize_file(file) * files_left--; * if item is dir: * newtargetsize = (((targetsize - size_serialized) - (files_left*AVGFILELEN)) / dirs_left) - size_of_this_dir_entry * if newtargetsize > items_in_dir(item)*AVGENTRYLEN: * serialize_full(item, newtargetsize) * else * serialize_incompete(item) * dirs_left--; * * Some notes: * - The performance impact of this algorithm is quite minimal. :-) * - This algorithm is based on heuristics, it's not optimal for anything. * - All entries of the top level requested directory are included. * - This algorithm is stupid: It may exceed the target size quite a bit in * some situations, but may also include way too little information in * others. This kinda sucks. * - On conservative guesses, this algorithm will favour directories at the end * of the list. As directory entries are ordered alphabetically in our data * structures, the notion of "end of the list" here is usually equivalent to * the "end of the list" as the user on the other side will see it. This * kinda sucks as well. */ // This isn't a strict maximum, may be exceeded by a single or entry. #define BUFSIZE (64*1024 - 1024) // Minimum output buffer size to give to zlib's deflate() function. #define ZLIBBUFSIZE (16*1024) // Some estimate stats for determining what to include in a file list. #define AVGFILELEN 105 // Average size of a entry #define AVGENTRYLEN 80 // Average size of any entry in a directory. (Excluding recursive dirs) // Output configurations #define FO_FU 0 // Write to file (uncompressed) #define FO_FB 1 // Write to file (bzip2) #define FO_MU 2 // Write to memory (uncompressed) #define FO_MZ 3 // Write to memory (zlib) typedef struct ctx_t { int conf; // FO_* int size; GString *buf; // Write buffer (final in F0_MU, temporary otherwise) GString *dest; // F0_MZ - Destination buffer z_stream *zlib; // F0_MZ BZFILE *fh_bz; // F0_FB FILE *fh_f; // F0_F* const char *file; // F0_F* - Filename (ownership is of the caller) char *tmpfile; // F0_F* - Temp filename (ownership is ours) GError *err; } ctx_t; // Flushes the write buffer to the underlying bzip2/zlib/file object (if any). static int doflush(ctx_t *x, gboolean force) { switch(x->conf) { case FO_FB: { int bzerr; BZ2_bzWrite(&bzerr, x->fh_bz, x->buf->str, x->buf->len); if(bzerr != BZ_OK) { g_set_error(&x->err, 1, 0, "Write error: %s", g_strerror(errno)); return -1; } x->buf->len = 0; break; } case FO_FU: { int r = fwrite(x->buf->str, 1, x->buf->len, x->fh_f); if(r != x->buf->len) { g_set_error(&x->err, 1, 0, "Write error: %s", g_strerror(errno)); return -1; } x->buf->len = 0; break; } case FO_MZ: { int r; x->zlib->next_in = (Bytef *)x->buf->str; x->zlib->avail_in = x->buf->len; do { if(x->zlib->avail_out < ZLIBBUFSIZE) { g_string_set_size(x->dest, x->dest->len+ZLIBBUFSIZE); x->dest->len -= ZLIBBUFSIZE; x->zlib->avail_out += ZLIBBUFSIZE; x->zlib->next_out = (Bytef *)(x->dest->str + x->zlib->total_out); } r = deflate(x->zlib, force ? Z_FINISH : Z_NO_FLUSH); x->dest->len = x->zlib->total_out; } while(x->buf->len > 0 && r == Z_OK); if(force ? (r != Z_STREAM_END) : (r != Z_OK && r != Z_BUF_ERROR)) { g_set_error(&x->err, 1, 0, "Zlib compression error (%d)", r); return -1; } g_string_erase(x->buf, 0, ((char *)x->zlib->next_in)-x->buf->str); break; } case FO_MU: // Nothing to do here, x->buf is already our destiniation. break; } return 0; } // Append a single character #define ac(c) do {\ g_string_append_c(x->buf, c);\ x->size++;\ } while(0) // Append a string #define as(s) do {\ g_string_append(x->buf, s);\ x->size += strlen(s);\ } while(0) // Append an unsigned 64-bit integer #define a64(i) do {\ int _oldlen = x->buf->len;\ g_string_append_printf(x->buf, "%"G_GUINT64_FORMAT, i);\ x->size += x->buf->len - _oldlen;\ } while(0) // XML-escape and write a string literal static void al(ctx_t *x, const char *str) { while(*str) { switch(*str) { case '&': as("&"); break; case '>': as(">"); break; case '<': as("<"); break; case '"': as("""); break; default: ac(*str); } str++; } } // Recursively write the child nodes of an fl_list item. static int af(ctx_t *x, fl_list_t *fl, int targetsize) { // First, do a pass through the list to find out how many dir and file entries we have. int i; int sizemax = x->size + targetsize; int numdirs = 0, numfiles = 0; if(targetsize) { for(i=0; isub->len; i++) { fl_list_t *cur = g_ptr_array_index(fl->sub, i); if(cur->isfile && cur->hastth) numfiles++; else if(!cur->isfile) numdirs++; } } // Now walk through the list again and serialize stuff. for(i=0; isub->len; i++) { fl_list_t *cur = g_ptr_array_index(fl->sub, i); // Always serialize files. if(cur->isfile && cur->hastth) { char tth[40] = {}; base32_encode(cur->tth, tth); as("name); as("\" Size=\""); a64(cur->size); as("\" TTH=\""); as(tth); // No need to escape this, it's base32 as("\"/>\n"); numfiles--; } // For directories: // new_targetsize = ((size_left - (files_left*AVGFILELEN)) / dirs_left) - size_of_this_dir_entry // (That's a moving average) if(!cur->isfile) { gboolean isempty = fl_list_isempty(cur); int size = targetsize ? (((sizemax - x->size) - (numfiles*AVGFILELEN)) / numdirs) - (30 + strlen(cur->name)) : 0; gboolean include = isempty ? FALSE : !targetsize ? TRUE : size > (int)(cur->sub->len*AVGENTRYLEN); as("name); ac('"'); if(!include && !isempty) as(" Incomplete=\"1\""); if(include) { as(">\n"); if(af(x, cur, size)) return 0; as("\n"); } else as("/>\n"); numdirs--; } if(x->buf->len >= BUFSIZE && doflush(x, FALSE)) return -1; } return 0; } // Write the top-level XML static int at(ctx_t *x, fl_list_t *fl, const char *cid, int targetsize) { as("\n"); as("\n"); // all elements if(fl && fl->sub) if(af(x, fl, targetsize ? MAX(targetsize-300, 1) : 0)) return -1; as("\n"); return 0; } static int ctx_open(ctx_t *x, int conf, const char *file, GString *buf) { memset(x, 0, sizeof(ctx_t)); x->file = file; x->conf = conf; // Set buf/dest if(x->conf == FO_MU) x->buf = buf; else x->buf = g_string_new(""); if(x->conf == FO_MZ) x->dest = buf; // zlib compressor if(x->conf == FO_MZ) { x->zlib = g_slice_new0(z_stream); x->zlib->zalloc = Z_NULL; x->zlib->zfree = Z_NULL; x->zlib->opaque = NULL; int r = deflateInit(x->zlib, Z_DEFAULT_COMPRESSION); if(r != Z_OK) { g_set_error(&x->err, 1, 0, "Unable to initialize zlib compression (%d: %s)", r, x->zlib->msg); return -1; } } // open file if(x->conf == FO_FB || x->conf == FO_FU) { x->tmpfile = g_strdup_printf("%s.tmp-%d", file, rand()); x->fh_f = fopen(x->tmpfile, "w"); if(!x->fh_f) { g_set_error_literal(&x->err, 1, 0, g_strerror(errno)); return -1; } } // bzip2 compressor if(x->conf == FO_FB) { int bzerr; x->fh_bz = BZ2_bzWriteOpen(&bzerr, x->fh_f, 7, 0, 0); if(bzerr != BZ_OK) { g_set_error(&x->err, 1, 0, "Unable to create bzip2 file (%d): %s", bzerr, g_strerror(errno)); return -1; } } return 0; } // Flushes the buffer, closes/renames the file and frees some memory. Does not // free x->file (not our property) and x->err (still used). static void ctx_close(ctx_t *x) { if(!x->err) doflush(x, TRUE); if(x->conf != FO_MU && x->buf) g_string_free(x->buf, TRUE); if(x->conf == FO_MZ && x->zlib) { deflateEnd(x->zlib); g_slice_free(z_stream, x->zlib); } if(x->conf == FO_FB && x->fh_bz) { int bzerr; BZ2_bzWriteClose(&bzerr, x->fh_bz, 0, NULL, NULL); if(bzerr != BZ_OK && !x->err) g_set_error(&x->err, 1, 0, "Error closing bzip2 stream (%d): %s", bzerr, g_strerror(errno)); } if(x->conf == FO_FB || x->conf == FO_FU) { if(x->fh_f && fclose(x->fh_f) && !x->err) g_set_error(&x->err, 1, 0, "Error closing file: %s", g_strerror(errno)); if(!x->err && rename(x->tmpfile, x->file) < 0) g_set_error(&x->err, 1, 0, "Error moving file: %s", g_strerror(errno)); if(x->tmpfile && x->err) unlink(x->tmpfile); g_free(x->tmpfile); } } // Serialize a file list to a string. Config is chosen from the arguments: // FU: buf == NULL, file doesn't end with .bz2 // FB: buf == NULL, file ends with .bz2 // MU: buf != NULL, !zlib // MZ: buf == NULL, zlib // Returns the uncompressed size of the list or 0 on error. int fl_save(fl_list_t *fl, const char *cid, int targetsize, gboolean zlib, GString *buf, const char *file, GError **err) { g_return_val_if_fail(err == NULL || *err == NULL, FALSE); ctx_t x; int conf = buf && zlib ? FO_MZ : buf ? FO_MU : strlen(file) > 4 && strcmp(file+(strlen(file)-4), ".bz2") == 0 ? FO_FB : FO_FU; if(ctx_open(&x, conf, file, buf) == 0) at(&x, fl, cid, targetsize); ctx_close(&x); if(x.err) g_propagate_error(err, x.err); return x.err ? 0 : x.size; } ncdc-1.23.1/src/uit_msg.c0000644000175000017500000001354314245144607012045 00000000000000/* ncdc - NCurses Direct Connect client Copyright (c) 2011-2022 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "ncdc.h" #include "uit_msg.h" ui_tab_type_t uit_msg[1]; typedef struct tab_t { ui_tab_t tab; // "group" is the uid of the user/bot we're supposed to be sending messages // to. In normal PMs, this is the same as the user we're conversing with, but // in group chats this is usually the uid of a bot, and each individual // message may come from different users. guint64 group; } tab_t; // group -> tab_t lookup table. static GHashTable *msg_tabs = NULL; #define inittable() if(!msg_tabs) msg_tabs = g_hash_table_new(g_int64_hash, g_int64_equal); ui_tab_t *uit_msg_create(hub_t *hub, hub_user_t *group) { inittable(); g_return_val_if_fail(!g_hash_table_lookup(msg_tabs, &group->uid), NULL); tab_t *t = g_new0(tab_t, 1); t->tab.type = uit_msg; t->tab.hub = hub; t->group = group->uid; t->tab.name = g_strdup_printf("~%s", group->name); t->tab.log = ui_logwindow_create(t->tab.name, var_get_int(0, VAR_backlog)); t->tab.log->handle = hub->tab; t->tab.log->checkchat = uit_hub_log_checkchat; ui_mf((ui_tab_t *)t, 0, "Chatting with %s on %s.", group->name, hub->tab->name); g_hash_table_insert(msg_tabs, &t->group, t); return (ui_tab_t *)t; } static void t_close(ui_tab_t *tab) { tab_t *t = (tab_t *)tab; g_hash_table_remove(msg_tabs, &t->group); ui_tab_remove(tab); ui_logwindow_free(tab->log); g_free(tab->name); g_free(tab); } static void t_draw(ui_tab_t *tab) { ui_logwindow_draw(tab->log, 1, 0, winrows-4, wincols); mvaddstr(winrows-3, 0, tab->name); addstr("> "); int pos = str_columns(tab->name)+2; ui_textinput_draw(ui_global_textinput, winrows-3, pos, wincols-pos, NULL); } static char *t_title(ui_tab_t *tab) { tab_t *t = (tab_t *)tab; return g_strdup_printf("Chatting with %s on %s%s.", tab->name+1, tab->hub->tab->name, g_hash_table_lookup(hub_uids, &t->group) ? "" : " (offline)"); } static void t_key(ui_tab_t *tab, guint64 key) { char *str = NULL; if(!ui_logwindow_key(tab->log, key, winrows) && ui_textinput_key(ui_global_textinput, key, &str) && str) { cmd_handle(str); g_free(str); } } // Called from the hub tab when user info changes. A QUIT is always done before // the user is removed from the hub_uids table. // TODO: This only detects changes for the "group" user. Doesn't matter for // normal PMs (where group == user), but for actual group chats we probably // want to detect userchanges on everyone who has participated in the chat. void uit_msg_userchange(hub_user_t *u, int change) { inittable(); tab_t *t = g_hash_table_lookup(msg_tabs, &u->uid); if(!t) return; switch(change) { case UIHUB_UC_JOIN: ui_mf((ui_tab_t *)t, 0, "--> %s has joined.", u->name); break; case UIHUB_UC_QUIT: ui_mf((ui_tab_t *)t, 0, "--< %s has quit.", u->name); break; case UIHUB_UC_NFO: // Detect nick changes. // Note: the name of the log file remains the same even after a nick // change. This probably isn't a major problem, though. Nick changes are // not very common and are only detected on ADC hubs. if(strcmp(u->name, t->tab.name+1) != 0) { ui_mf((ui_tab_t *)t, 0, "%s is now known as %s.", t->tab.name+1, u->name); g_free(t->tab.name); t->tab.name = g_strdup_printf("~%s", u->name); } break; } } // Opens a msg tab for specified group. Returns FALSE if no such group exists. gboolean uit_msg_open(guint64 group, ui_tab_t *parent) { inittable(); ui_tab_t *t = g_hash_table_lookup(msg_tabs, &group); if(t) { ui_tab_cur = g_list_find(ui_tabs, t); return TRUE; } hub_user_t *u = g_hash_table_lookup(hub_uids, &group); if(!u) return FALSE; t = uit_msg_create(u->hub, u); ui_tab_open(t, TRUE, parent); return TRUE; } // Called from hub.c when a message has arrived. *msg already has the or ** nick prefixed. void uit_msg_msg(hub_user_t *group, const char *msg) { inittable(); ui_tab_t *t = g_hash_table_lookup(msg_tabs, &group->uid); if(!t) { t = uit_msg_create(group->hub, group); ui_tab_open(t, FALSE, group->hub->tab); } ui_m(t, UIP_HIGH, msg); } // Called when a hub has been disconnected. I.e. all users on that hub are now // offline. void uit_msg_disconnect(hub_t *hub) { inittable(); GHashTableIter i; g_hash_table_iter_init(&i, msg_tabs); tab_t *t = NULL; while(g_hash_table_iter_next(&i, NULL, (gpointer *)&t)) { // The user could have quit while we kept this tab open, so check that the // user was still online when the disconnect happened. hub_user_t *u = g_hash_table_lookup(hub_uids, &t->group); if(u && u->hub == hub) ui_mf((ui_tab_t *)t, 0, "--< %s has quit.", t->tab.name+1); } } guint64 uit_msg_uid(ui_tab_t *tab) { return ((tab_t *)tab)->group; } ui_tab_type_t uit_msg[1] = { { t_draw, t_title, t_key, t_close } }; ncdc-1.23.1/src/bloom.c0000644000175000017500000000421314245144446011501 00000000000000/* ncdc - NCurses Direct Connect client Copyright (c) 2011-2022 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* This bloom filter implementation assumes a hash size of 192 bits */ #include "ncdc.h" #include "bloom.h" #if INTERFACE typedef struct { int m; /* Size of the bloom filter in _bytes_ */ int k; /* Number of sub-hashes */ int h; /* Number of bits for each sub-hash */ unsigned char *d; } bloom_t; #endif /* Returns -1 if m,k,h are not valid, 0 on success */ int bloom_init(bloom_t *b, int m, int k, int h) { /* Restrictions, as defined by the ADC BLOM spec */ if(m <= 0 || k <= 0 || h <= 0 || (m & 7) != 0 || k*h > 192 || h > 64 || ((guint64)1)<<(h-3) <= ((guint64)m)>>3) return -1; b->m = m; b->k = k; b->h = h; b->d = g_malloc0(m); return 0; } void bloom_add(bloom_t *b, const char *hash) { int i, j, pos = 0; guint64 tmp; for(i=0; ik; i++) { tmp = 0; for(j=0; jh; j++) { tmp |= ((guint64)((((const unsigned char *)hash)[pos>>3] >> (pos&7)) & 1)) << j; pos++; } j = tmp % (b->m<<3); b->d[j>>3] |= 1<<(j&7); } } void bloom_free(bloom_t *b) { g_free(b->d); } ncdc-1.23.1/src/listen.c0000644000175000017500000002714414245144516011675 00000000000000/* ncdc - NCurses Direct Connect client Copyright (c) 2011-2022 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "ncdc.h" #include "listen.h" #if INTERFACE #define LBT_UDP 1 #define LBT_TCP 2 #define LBT_IP4 4 #define LBT_IP6 8 #define LBT_UDP4 (LBT_UDP|LBT_IP4) #define LBT_UDP6 (LBT_UDP|LBT_IP6) #define LBT_TCP4 (LBT_TCP|LBT_IP4) #define LBT_TCP6 (LBT_TCP|LBT_IP6) #define LBT_STR(x) ((x) == LBT_TCP4 ? "TCP4" : (x) == LBT_TCP6 ? "TCP6" : (x) == LBT_UDP4 ? "UDP4" : "UDP6") // port + ip4 are "cached" for convenience. struct listen_bind_t { guint16 type; // LBT_* guint16 port; struct in_addr ip4; struct in6_addr ip6; int src; // glib event source int sock; GSList *hubs; // hubs that use this bind }; struct listen_hub_bind_t { guint64 hubid; // A hub is always active in either IPv4 or IPv6, both is currently not supported. listen_bind_t *tcp, *udp; }; #endif GList *listen_binds = NULL; // List of currently used binds GHashTable *listen_hub_binds = NULL; // Map of &hubid to listen_hub_bind // The port to use when active_port hasn't been set. Initialized to a random // value on startup. Note that the same port is still used for all hubs that // have the port set to "random". This obviously isn't very "random", but // avoids a change to the port every time that listen_refresh() is called. // Also note that this isn't necessarily a "random *free* port" such as what // the OS would give if you actually specify port=0, so if a port happens to be // chosen that is already in use, you'll get an error. (This isn't very nice... // Especially if the port is being used for e.g. an outgoing connection, which // isn't very uncommon for high ports). static guint16 random_tcp_port, random_udp_port; // Public interface to fetch current listen configuration gboolean listen_hub_active(guint64 hub) { listen_hub_bind_t *b = g_hash_table_lookup(listen_hub_binds, &hub); return b && b->tcp; } // These all returns 0 if passive or disabled guint16 listen_hub_tcp(guint64 hub) { listen_hub_bind_t *b = g_hash_table_lookup(listen_hub_binds, &hub); return b && b->tcp ? b->tcp->port : 0; } guint16 listen_hub_udp(guint64 hub) { listen_hub_bind_t *b = g_hash_table_lookup(listen_hub_binds, &hub); return b && b->udp ? b->udp->port : 0; } const char *listen_bind_ipport(listen_bind_t *b) { static char buf[100]; g_snprintf(buf, 100, b->type & LBT_IP4 ? "%s:%d" : "[%s]:%d", b->type & LBT_IP4 ? ip4_unpack(b->ip4) : ip6_unpack(b->ip6), (int)b->port); return buf; } void listen_global_init() { listen_hub_binds = g_hash_table_new_full(g_int64_hash, g_int64_equal, NULL, g_free); random_tcp_port = g_random_int_range(1025, 65535); random_udp_port = g_random_int_range(1025, 65535); } // Closes all listen sockets and clears *listen_binds and *listen_hub_binds. static void listen_stop() { g_debug("listen: Stopping."); g_hash_table_remove_all(listen_hub_binds); GList *n, *b = listen_binds; while(b) { n = b->next; listen_bind_t *lb = b->data; if(lb->src) g_source_remove(lb->src); if(lb->sock) close(lb->sock); g_slist_free(lb->hubs); g_free(lb); g_list_free_1(b); b = n; } listen_binds = NULL; } static gboolean listen_tcp_handle(gpointer dat) { listen_bind_t *b = dat; int c; char addr_str[100]; if(b->type & LBT_IP4) { struct sockaddr_in a = {}; socklen_t len = sizeof(a); c = accept(b->sock, (struct sockaddr *)&a, &len); g_snprintf(addr_str, 100, "%s:%d", ip4_unpack(a.sin_addr), ntohs(a.sin_port)); } else { struct sockaddr_in6 a = {}; socklen_t len = sizeof(a); c = accept(b->sock, (struct sockaddr *)&a, &len); g_snprintf(addr_str, 100, "[%s]:%d", ip6_unpack(a.sin6_addr), ntohs(a.sin6_port)); } // handle error if(c < 0) { if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) return TRUE; ui_mf(uit_main_tab, 0, "TCP accept error on %s: %s. Switching to passive mode.", listen_bind_ipport(b), g_strerror(errno)); listen_stop(); hub_global_nfochange(); return FALSE; } // Create connection fcntl(c, F_SETFL, fcntl(c, F_GETFL, 0)|O_NONBLOCK); cc_incoming(cc_create(NULL), b->port, c, addr_str, b->type & LBT_IP4 ? FALSE : TRUE); return TRUE; } static gboolean listen_udp_handle(gpointer dat) { static char buf[5000]; // can be static, this function is only called in the main thread. listen_bind_t *b = dat; int r; char addr_str[100]; if(b->type & LBT_IP4) { struct sockaddr_in a = {}; socklen_t len = sizeof(a); r = recvfrom(b->sock, buf, sizeof(buf)-1, 0, (struct sockaddr *)&a, &len); g_snprintf(addr_str, 100, "%s:%d", ip4_unpack(a.sin_addr), ntohs(a.sin_port)); } else { struct sockaddr_in6 a = {}; socklen_t len = sizeof(a); r = recvfrom(b->sock, buf, sizeof(buf)-1, 0, (struct sockaddr *)&a, &len); g_snprintf(addr_str, 100, "[%s]:%d", ip6_unpack(a.sin6_addr), ntohs(a.sin6_port)); } // handle error if(r < 0) { if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) return TRUE; ui_mf(uit_main_tab, 0, "UDP read error on %s: %s. Switching to passive mode.", listen_bind_ipport(b), g_strerror(errno)); listen_stop(); hub_global_nfochange(); return FALSE; } // Since all incoming messages must be search results, just pass the messages to search.c buf[r] = 0; if(!search_handle_udp(addr_str, buf, r)) { // The message may habe been encrypted with SUDP, so this error reporting // may not be too useful. g_message("UDP:%s: Invalid message: %s", addr_str, buf); } return TRUE; } #define bind_hub_add(lb, h) do {\ if((h)->tcp != lb && (h)->udp != lb)\ (lb)->hubs = g_slist_prepend((lb)->hubs, h);\ if((lb)->type & LBT_TCP)\ (h)->tcp = lb;\ else\ (h)->udp = lb;\ } while(0) static void bind_add(listen_hub_bind_t *b, int type, char *ip, guint16 port) { if(!port) port = type & LBT_UDP ? random_udp_port : random_tcp_port; listen_bind_t lb = {}; lb.type = type; lb.port = port; if(type & LBT_IP4) lb.ip4 = var_parse_ip4(ip); else lb.ip6 = var_parse_ip6(ip); g_debug("Listen: Adding %s %s", LBT_STR(type), listen_bind_ipport(&lb)); // First: look if we can re-use an existing bind and look for any unresolvable conflicts. GList *c; for(c=listen_binds; c; c=c->next) { listen_bind_t *i = c->data; // Same? Just re-use. if(type == i->type && i->port == port && (type & LBT_IP4 ? ip4_cmp(i->ip4, lb.ip4) == 0 || ip4_isany(i->ip4) : ip6_cmp(i->ip6, lb.ip6) == 0 || ip6_isany(i->ip6))) { g_debug("Listen: Re-using!"); bind_hub_add(i, b); return; } } // Create and add bind item listen_bind_t *nlb = g_new0(listen_bind_t, 1); *nlb = lb; bind_hub_add(nlb, b); // Look for existing binds that should be merged. GList *n; if(nlb->type & LBT_IP4 ? ip4_isany(nlb->ip4) : ip6_isany(nlb->ip6)) { for(c=listen_binds; c; c=n) { n = c->next; listen_bind_t *i = c->data; if(i->port != nlb->port || i->type != nlb->type) continue; g_debug("Listen: Merging!"); // Move over all hubs to *nlb GSList *in; for(in=i->hubs; in; in=in->next) bind_hub_add(nlb, (listen_hub_bind_t *)in->data); g_slist_free(i->hubs); // And remove this bind g_free(i); listen_binds = g_list_delete_link(listen_binds, c); } } listen_binds = g_list_prepend(listen_binds, nlb); } static void bind_create(listen_bind_t *b) { g_debug("Listen: binding %s %s", LBT_STR(b->type), listen_bind_ipport(b)); int err = 0; // Create socket int sock = socket(b->type & LBT_IP4 ? AF_INET : AF_INET6, b->type & LBT_UDP ? SOCK_DGRAM : SOCK_STREAM, 0); g_return_if_fail(sock > 0); // Make sure that, if this is an IPv6 '::' socket, it does not bind to IPv4. // If it would, then this or a subsequent bind() may fail because we may also // create a separate socket for the same port on IPv4. int one = 1; #ifdef IPV6_V6ONLY if(b->type & LBT_IP6) setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&one, sizeof(one)); #endif setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&one, sizeof(one)); fcntl(sock, F_SETFL, fcntl(sock, F_GETFL, 0)|O_NONBLOCK); // Bind int r = b->type & LBT_IP4 ? bind(sock, ip4_sockaddr(b->ip4, b->port), sizeof(struct sockaddr_in)) : bind(sock, ip6_sockaddr(b->ip6, b->port), sizeof(struct sockaddr_in6)); if(r < 0) err = errno; // listen if(!err && b->type & LBT_TCP && listen(sock, 5) < 0) err = errno; // Bind or listen failed? Abandon ship! (This may be a bit extreme, but at // least it avoids any other problems that may arise from a partially // activated configuration). if(err) { ui_mf(uit_main_tab, UIP_MED, "Error binding to %s %s, %s. Switching to passive mode.", LBT_STR(b->type), listen_bind_ipport(b), g_strerror(err)); close(sock); listen_stop(); return; } b->sock = sock; // Start accepting incoming connections or handling incoming messages GSource *src = fdsrc_new(sock, G_IO_IN); g_source_set_callback((GSource *)src, b->type & LBT_UDP ? listen_udp_handle : listen_tcp_handle, b, NULL); b->src = g_source_attach((GSource *)src, NULL); g_source_unref((GSource *)src); } // Should be called every time a hub is opened/closed or an active_ config // variable has changed. void listen_refresh() { listen_stop(); g_debug("listen: Refreshing"); // Walk through the list of hubs and get their config GHashTableIter i; hub_t *h = NULL; g_hash_table_iter_init(&i, hubs); while(g_hash_table_iter_next(&i, NULL, (gpointer *)&h)) { // We only look at hubs on which we are active if(!var_get_bool(h->id, VAR_active) || !hub_ip(h)) continue; // Add to listen_hub_binds listen_hub_bind_t *b = g_new0(listen_hub_bind_t, 1); b->hubid = h->id; g_hash_table_insert(listen_hub_binds, &b->hubid, b); // And add the required binds for this hub (Due to the conflict resolution in binds_add(), this is O(n^2)) // Note: bind_add() can call listen_stop() on error, detect this on whether listen_hub_binds is empty or not. bind_add(b, net_is_ipv6(h->net) ? LBT_TCP6 : LBT_TCP4, var_get(b->hubid, VAR_local_address), var_get_int(b->hubid, VAR_active_port)); if(!g_hash_table_size(listen_hub_binds)) break; bind_add(b, net_is_ipv6(h->net) ? LBT_UDP6 : LBT_UDP4, var_get(b->hubid, VAR_local_address), var_get_int(b->hubid, VAR_active_udp_port)); if(!g_hash_table_size(listen_hub_binds)) break; } // Now walk through *listen_binds and actually create the listen sockets GList *l; for(l=listen_binds; l; listen_binds ? (l=l->next) : (l=NULL)) bind_create((listen_bind_t *)l->data); } ncdc-1.23.1/src/net.c0000644000175000017500000012113414245144527011161 00000000000000/* ncdc - NCurses Direct Connect client Copyright (c) 2011-2022 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "ncdc.h" #include "net.h" // global network stats ratecalc_t net_in, net_out; #define NET_RECV_SIZE ( 8*1024) #define NET_MAX_RBUF (1024*1024) #define NET_TRANS_BUF ( 32*1024) #if INTERFACE // actions that can fail #define NETERR_CONN 0 #define NETERR_RECV 1 #define NETERR_SEND 2 #define NETERR_TIMEOUT 3 typedef struct net_t net_t; // ALPN protocols #define ALPN_DEFAULT 0 // default, no ALPN #define ALPN_NMDC 1 #define ALPN_ADC 2 #endif // Network states #define NETST_IDL 0 // idle, disconnected #define NETST_DNS 1 // resolving DNS #define NETST_CON 2 // connecting #define NETST_ASY 3 // connected, handling async messages #define NETST_SYN 4 // connected, handling a synchronous send/receive #define NETST_DIS 5 // disconnecting (cleanly) typedef struct dnscon_t dnscon_t; typedef struct synfer_t synfer_t; struct net_t { int state; ratecalc_t rate_in; ratecalc_t rate_out; dnscon_t *dnscon; // state DNS,CON. Setting ->net to NULL 'cancels' DNS resolving. int sock; // state CON,ASY,SYN,DIS int socksrc; // state CON,ASY,DIS. Glib event source on 'sock'. char addr[56]; // state ASY,SYN,DIS, ip:port char laddr[40]; // state ASY,SYN,DIS, ip only gnutls_session_t tls; // state ASY,SYN,DIS (only if tls is enabled) void (*cb_handshake)(net_t *, const char *, int); // state ASY, called after complete handshake. void (*cb_shutdown)(net_t *); // state DIS, called after complete disconnect. gboolean v6 : 4; // state ASY,SYN, whether we're on IPv6 gboolean tls_handshake : 4; // state ASY, whether we're handshaking. gboolean shutdown_closed : 4; // state DIS, whether shutdown() has been called on the socket. gboolean writing : 4; // state ASY. Whether 'socksrc' is write poll event. gboolean wantwrite : 4; // state ASY. Whether we want a write on sock. GString *tlsrbuf; // state ASY. Temporary buffer for data read before switching to TLS. (To be fed to GnuTLS) GString *rbuf; // state ASY. Read buffer. GString *wbuf; // state ASY. Write buffer. // Called when an error has occured. Second argument is NETERR_*, third a // string representing the error. void (*cb_err)(net_t *, int, const char *); // Read buffer handling. Callback will be called only once. (State ASY) void (*rd_cb)(net_t *, char *, int len); gboolean rd_msg : 1; // TRUE: message, rd_dat=EOM; FALSE=bytes, rd_dat=count gboolean rd_consume : 1; int rd_dat; // Synchronous file transfers (SYN state) When set in the ASY state, it means // that buffers should be flushed before switching to the SYN state. synfer_t *syn; // some pointer for use by the user void *handle; // reference counter int ref; // Timeout handling int timeout_src; time_t timeout_last; const char *timeout_msg; }; static int net_sock_bind(int af, int sock, char *laddr) { if(!laddr) return 0; if(af == AF_INET) { struct in_addr a = var_parse_ip4(laddr); return bind(sock, ip4_sockaddr(a, 0), sizeof(struct sockaddr_in)); } if(af == AF_INET6) { struct in6_addr a = var_parse_ip6(laddr); return bind(sock, ip6_sockaddr(a, 0), sizeof(struct sockaddr_in6)); } g_return_val_if_reached(0); } // Low-level recv/send wrappers static ssize_t tls_pull(gnutls_transport_ptr_t dat, void *buf, size_t len) { net_t *n = dat; // Special buffer to allow passing read data back to the GnuTLS stream. if(n->tlsrbuf) { memcpy(buf, n->tlsrbuf->str, MIN(len, n->tlsrbuf->len)); if(len >= n->tlsrbuf->len) { g_string_free(n->tlsrbuf, TRUE); n->tlsrbuf = NULL; } else g_string_erase(n->tlsrbuf, 0, len); return len; } // Otherwise, get the data directly from the network. int r = recv(n->sock, buf, len, 0); if(r < 0) gnutls_transport_set_errno(n->tls, errno == EWOULDBLOCK ? EAGAIN : errno); else { ratecalc_add(&net_in, r); ratecalc_add(&n->rate_in, r); } return r; } // Behaves similarly to a normal recv(), but writes a readable error message to // *err. If the error is temporary (e.g. EAGAIN), returns -1 but with *err=NULL. // Does not return 0, disconnect is considered a fatal error. static int low_recv(net_t *n, char *buf, int len, const char **err) { int r = n->tls ? gnutls_record_recv(n->tls, buf, len) : recv(n->sock, buf, len, 0); if(r < 0 && (n->tls ? !gnutls_error_is_fatal(r) : errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)) { *err = NULL; return -1; } if(n->state != NETST_DIS) time(&n->timeout_last); if(r <= 0) { *err = !r || !n->tls ? g_strerror(!r ? ECONNRESET : errno) : gnutls_strerror(r); return -1; } if(!n->tls) { ratecalc_add(&net_in, r); ratecalc_add(&n->rate_in, r); } return r; } static ssize_t tls_push(gnutls_transport_ptr_t dat, const void *buf, size_t len) { net_t *n = dat; int r = send(n->sock, buf, len, 0); if(r < 0) gnutls_transport_set_errno(n->tls, errno == EWOULDBLOCK ? EAGAIN : errno); else { ratecalc_add(&net_out, r); ratecalc_add(&n->rate_out, r); } return r; } // Same as low_recv(), but for send(). static int low_send(net_t *n, const char *buf, int len, const char **err) { int r = n->tls ? gnutls_record_send(n->tls, buf, len) : send(n->sock, buf, len, 0); // Note: r == 0 is seen as a temporary error if(!r || (r < 0 && (n->tls ? !gnutls_error_is_fatal(r) : errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN))) { *err = NULL; return -1; } if(n->state != NETST_DIS) time(&n->timeout_last); if(r < 0) { *err = n->tls ? gnutls_strerror(r) : g_strerror(errno); return -1; } if(!n->tls) { ratecalc_add(&net_out, r); ratecalc_add(&n->rate_out, r); } return r; } // Synchronous file transfers static void asy_setuppoll(net_t *n); struct synfer_t { GMutex lock; // protects n->left, any data used within the low_* functions and, in the case of a disconnect, net->sock and net->tls. net_t *net; guint64 left; // The transfer thread itself does not need the lock to read this value, only to write. (It is the only writer) int fd; // for uploads int cancel; // set to 1 to cancel transfer int can[2]; // close() this pipe (can[1]) to cancel the transfer gboolean upl : 1; // whether this is an upload or download gboolean flush : 1; // for uploads char *err; void *ctx; // for downloads void (*cb_downdone)(net_t *, void *); gboolean (*cb_downdata)(void *, const char *, int); void (*cb_upldone)(net_t *); }; static GThreadPool *syn_pool = NULL; static void syn_new(net_t *n, gboolean upl, guint64 len) { n->syn = g_slice_new0(synfer_t); n->syn->left = len; n->syn->net = n; n->syn->upl = upl; g_mutex_init(&n->syn->lock); if(pipe(n->syn->can) < 0) { g_critical("pipe() failed: %s", g_strerror(errno)); g_return_if_reached(); } net_ref(n); } static void syn_free(synfer_t *s) { net_unref(s->net); close(s->can[0]); if(s->fd) close(s->fd); if(s->cb_downdone) s->cb_downdone(NULL, s->ctx); g_free(s->err); g_slice_free(synfer_t, s); } static void syn_cancel(net_t *n) { n->syn->cancel = 1; close(n->syn->can[1]); n->syn = NULL; } // Called as an idle function from syn_thread static gboolean syn_done(gpointer dat) { synfer_t *s = dat; net_t *n = s->net; // Cancelled if(s->cancel) { syn_free(s); return FALSE; } // Error if(s->err) { g_debug("%s: Syn: %s", net_remoteaddr(n), s->err); syn_cancel(n); n->cb_err(n, s->upl ? NETERR_SEND : NETERR_RECV, s->err); syn_free(s); return FALSE; } syn_cancel(n); n->state = NETST_ASY; n->wantwrite = FALSE; asy_setuppoll(n); if(s->cb_upldone) s->cb_upldone(n); if(s->cb_downdone) { s->cb_downdone(n, s->ctx); s->cb_downdone = NULL; } syn_free(s); return FALSE; } // Does two things: Waits some time to ensure that we are allowed to burst with // the rate limiting thing, and then waits for the socket to become // readable/writable. Returns the number of bytes that may be read/written on // success, 0 if the operation has been cancelled. static int syn_wait(synfer_t *s, int sock, gboolean write) { // Lock to get the socket fd g_mutex_lock(&s->lock); GPollFD fds[2] = {}; fds[0].fd = s->can[0]; fds[0].events = G_IO_IN; fds[1].fd = sock; fds[1].events = write ? G_IO_OUT : G_IO_IN; g_mutex_unlock(&s->lock); // Poll for burst int b = 0; int r = 0; while(r <= 0 && (b = ratecalc_burst(write ? &s->net->rate_out : &s->net->rate_in)) <= 0) { // Wake up 4 times per second. If the resource is CPU or HDD I/O // constrained, then this means that at most 1/4th of the possible usage // time is "thrown away". I don't expect this to be much of an issue, // however. r = g_poll(fds, 1, 250); // only poll for the cancel fd here. g_return_val_if_fail(r >= 0 || errno == EINTR, 0); } if(r) return 0; // Now poll for read/writability of the socket. do r = g_poll(fds, 2, -1); while(r < 0 && errno == EINTR); if(fds[0].revents) return 0; return b; } #ifdef HAVE_SENDFILE static void syn_upload_sendfile(synfer_t *s, int sock, fadv_t *adv) { off_t off = lseek(s->fd, 0, SEEK_CUR); if(off == (off_t)-1) { s->err = g_strdup(g_strerror(errno)); return; } while(s->left > 0 && !s->err && !s->cancel) { off_t oldoff = off; int b = syn_wait(s, sock, TRUE); if(b <= 0) return; // No need for a lock here, we're not using the TLS session and socket fd's // are thread-safe. To some extent at least. #ifdef HAVE_LINUX_SENDFILE // XXX: On 32bit Linux with musl, sendfile() may fail with EOVERFLOW when // an offset argument is given and is larger than UINT32_MAX, so we're // passing NULL instead to use the fd's internal file offset. ssize_t r = sendfile(sock, s->fd, NULL, MIN(b, s->left)); #elif HAVE_BSD_SENDFILE off_t len = 0; gint64 r = sendfile(s->fd, sock, off, (size_t)MIN(b, s->left), NULL, &len, 0); // a partial write results in an EAGAIN error on BSD, even though this isn't // really an error condition at all. if(r != -1 || (r == -1 && errno == EAGAIN)) r = len; #endif if(r >= 0) { if(s->flush) fadv_purge(adv, r); off = oldoff + r; // This bypasses the low_send() function, so manually add it to the // ratecalc thing and update timeout_last. ratecalc_add(&net_out, r); ratecalc_add(&s->net->rate_out, r); g_mutex_lock(&s->lock); time(&s->net->timeout_last); s->left -= r; g_mutex_unlock(&s->lock); continue; } else if(errno == EAGAIN || errno == EINTR) { continue; } else if(errno == ENOTSUP || errno == ENOSYS || errno == EINVAL || errno == EOVERFLOW) { // Don't set s->err here, let the fallback handle the rest g_message("sendfile() failed with `%s', using fallback.", g_strerror(errno)); // The fallback code continues from the fd position, so make sure to // update it in case (FreeBSD's) sendfile() didn't do so. if(lseek(s->fd, off, SEEK_SET) == (off_t)-1) { g_message("Can't switch to fallback, seek failed: %d (%s)", errno, g_strerror(errno)); s->err = g_strdup(g_strerror(errno)); } return; } else { if(errno != EPIPE && errno != ECONNRESET) g_message("sendfile() returned an unknown error: %d (%s)", errno, g_strerror(errno)); s->err = g_strdup(g_strerror(errno)); return; } } } #endif static void syn_upload_buf(synfer_t *s, int sock, fadv_t *adv) { char *buf = g_malloc(NET_TRANS_BUF); while(s->left > 0 && !s->err && !s->cancel) { int rd = read(s->fd, buf, MIN(NET_TRANS_BUF, s->left)); if(rd <= 0) { s->err = g_strdup(g_strerror(errno)); goto done; } if(s->flush) fadv_purge(adv, rd); char *p = buf; while(rd > 0) { int b = syn_wait(s, sock, TRUE); if(b <= 0) goto done; g_mutex_lock(&s->lock); const char *err = NULL; int wr = s->cancel || !s->net->sock ? 0 : low_send(s->net, p, MIN(rd, b), &err); // successful write if(wr > 0) { p += wr; s->left -= wr; rd -= wr; } g_mutex_unlock(&s->lock); if(!wr) // cancelled goto done; if(wr < 0 && !err) // would block continue; if(wr < 0) { // actual error s->err = g_strdup(err); goto done; } } } done: g_free(buf); } static void syn_download(synfer_t *s, int sock) { char *buf = g_malloc(NET_TRANS_BUF); while(s->left > 0 && !s->err && !s->cancel) { int b = syn_wait(s, sock, FALSE); if(b <= 0) break; g_mutex_lock(&s->lock); const char *err = NULL; int r = s->cancel || !s->net->sock ? 0 : low_recv(s->net, buf, MIN(NET_TRANS_BUF, s->left), &err); if(r > 0) s->left -= r; g_mutex_unlock(&s->lock); if(!r) break; if(r < 0 && !err) continue; if(r < 0) { s->err = g_strdup(err); break; } if(!s->cb_downdata(s->ctx, buf, r)) { s->err = g_strdup("Operation cancelled"); break; } } g_free(buf); } static void syn_thread(gpointer dat, gpointer udat) { synfer_t *s = dat; // Make a copy of sock to make sure it doesn't disappear on us. // (Still need to obtain the lock to make use of it). g_mutex_lock(&s->lock); int sock = s->net->sock; gboolean tls = !!s->net->tls; g_mutex_unlock(&s->lock); if(sock && !s->cancel && s->upl) { fadv_t adv; if(s->flush) fadv_init(&adv, s->fd, lseek(s->fd, 0, SEEK_CUR), VAR_FFC_UPLOAD); #ifdef HAVE_SENDFILE if(!tls && var_get_bool(0, VAR_sendfile)) syn_upload_sendfile(s, sock, &adv); #endif if(s->left > 0 && !s->err && !s->cancel) syn_upload_buf(s, sock, &adv); if(s->flush) fadv_close(&adv); } if(sock && !s->cancel && !s->upl) syn_download(s, sock); g_idle_add(syn_done, s); } static void syn_start(net_t *n) { n->state = NETST_SYN; // We're coming from the ASY state, so make sure to clean this up. if(n->socksrc) { g_source_remove(n->socksrc); n->socksrc = 0; } g_thread_pool_push(syn_pool, n->syn, NULL); } guint64 net_left(net_t *n) { if(!n->syn) return 0; g_mutex_lock(&n->syn->lock); guint64 r = n->syn->left; g_mutex_unlock(&n->syn->lock); return r; } // Asynchronous TLS handshaking & message handling & disconnecting static gboolean handle_timer(gpointer dat); // Checks rbuf against any queued read events and handles those. (Can be called // as a glib idle function) static gboolean asy_handlerbuf(gpointer dat) { net_t *n = dat; // The callbacks itself may in turn call other net_* functions, and thus // immediately queue another read action. Hence the while loop. Note that no // net_* function that remains in the ASY state is allowed to modify rbuf, // otherwise we need to make a copy of rbuf before passing it to the // callback. net_ref(n); while(n->state == NETST_ASY && n->rbuf->len && n->rd_cb && !n->syn) { gboolean msg = n->rd_msg; gboolean consume = n->rd_consume; int dat = n->rd_dat; void(*cb)(net_t *, char *, int) = n->rd_cb; char *end = msg ? memchr(n->rbuf->str, dat, n->rbuf->len) : n->rbuf->len >= dat ? n->rbuf->str + dat : NULL; if(!end) break; n->rd_cb = NULL; if(msg) { *end = 0; if(consume) g_debug("%s< %s%c", net_remoteaddr(n), n->rbuf->str, dat != '\n' ? dat : ' '); } cb(n, n->rbuf->str, end - n->rbuf->str); if(n->state == NETST_ASY || n->state == NETST_SYN || n->state == NETST_DIS) { if(consume) g_string_erase(n->rbuf, 0, end - n->rbuf->str + (msg ? 1 : 0)); else if(msg) *end = dat; } } // Handle recvfile if(n->syn && n->state == NETST_ASY && !n->syn->upl) { synfer_t *s = n->syn; if(n->rbuf->len) { int w = MIN(n->rbuf->len, s->left); s->left -= w; s->cb_downdata(s->ctx, n->rbuf->str, w); g_string_erase(n->rbuf, 0, w); } if(s->left) syn_start(n); else { s->cb_downdone(n, s->ctx); s->cb_downdone = NULL; syn_cancel(n); syn_free(s); } } net_unref(n); return FALSE; } // Tries a read. Returns FALSE if there was an error other than "please try // again later". static gboolean asy_read(net_t *n) { // Make sure we have enough buffer space if(n->rbuf->allocated_len < NET_MAX_RBUF && n->rbuf->allocated_len - n->rbuf->len < NET_RECV_SIZE) { gsize oldlen = n->rbuf->len; g_string_set_size(n->rbuf, MIN(NET_MAX_RBUF, n->rbuf->len+NET_RECV_SIZE)); n->rbuf->len = oldlen; } int len = n->rbuf->allocated_len - n->rbuf->len - 1; if(len <= 10) { // Some arbitrary low number. g_debug("%s: Read buffer full", net_remoteaddr(n)); n->cb_err(n, NETERR_RECV, "Read buffer full"); return FALSE; } const char *err = NULL; int r = low_recv(n, n->rbuf->str + n->rbuf->len, len, &err); if(r < 0 && !err) return TRUE; // Handle error and disconnect if(r < 0) { g_debug("%s: %s", net_remoteaddr(n), err); n->cb_err(n, NETERR_RECV, err); return FALSE; } // Otherwise, update buffer info g_return_val_if_fail(n->rbuf->len + r < n->rbuf->allocated_len, FALSE); n->rbuf->len += r; n->rbuf->str[n->rbuf->len] = 0; net_ref(n); asy_handlerbuf(n); gboolean ret = n->state == NETST_ASY; net_unref(n); return ret; } static gboolean dis_shutdown(net_t *n) { // Shutdown TLS if(n->tls) { int r = gnutls_bye(n->tls, GNUTLS_SHUT_RDWR); if(r == 0) { gnutls_deinit(n->tls); n->tls = NULL; } else if(r < 0 && !gnutls_error_is_fatal(r)) { if(gnutls_record_get_direction(n->tls)) n->wantwrite = TRUE; return TRUE; } else { char *e = g_strdup_printf("Shutdown error: %s", gnutls_strerror(r)); g_debug("%s: %s", net_remoteaddr(n), e); n->cb_err(n, NETERR_RECV, e); g_free(e); return FALSE; } } // Shutdown socket if(!n->tls && !n->shutdown_closed) { shutdown(n->sock, SHUT_WR); n->shutdown_closed = TRUE; } // Wait for ACK (discard anything we read) if(!n->tls) { char buf[10]; int r = recv(n->sock, buf, sizeof(buf), 0); if(r < 0 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)) return TRUE; if(r < 0) { g_debug("%s: %s", net_remoteaddr(n), g_strerror(errno)); n->cb_err(n, NETERR_RECV, g_strerror(errno)); return FALSE; } if(r == 0) { if(n->cb_shutdown) n->cb_shutdown(n); net_disconnect(n); // still do a force disconnect to clean up stuff and go to IDLE state } } return FALSE; } static gboolean asy_write(net_t *n) { if(!n->wbuf->len) return TRUE; const char *err = NULL; int r = low_send(n, n->wbuf->str, n->wbuf->len, &err); if(r < 0 && !err) { n->wantwrite = TRUE; return TRUE; } // Handle error if(r < 0) { char *e = g_strdup_printf("Write error: %s", err); g_debug("%s: %s", net_remoteaddr(n), e); n->cb_err(n, NETERR_SEND, e); g_free(e); return FALSE; } g_string_erase(n->wbuf, 0, r); if(!n->wbuf->len) { if(n->syn && n->syn->upl) { syn_start(n); return FALSE; } if(n->state == NETST_DIS) return dis_shutdown(n); } n->wantwrite = !!n->wbuf->len; return TRUE; } static gboolean asy_handshake(net_t *n) { if(!n->tls_handshake) return TRUE; int r = gnutls_handshake(n->tls); if(!r) { // Successful handshake unsigned int len; char kpr[32] = {}; char kpf[53] = {}; const gnutls_datum_t *certs = gnutls_certificate_get_peers(n->tls, &len); if(certs && len >= 1) { certificate_sha256(*certs, kpr); base32_encode_dat(kpr, kpf, 32); } g_debug("%s: TLS Handshake successful, KP=SHA256/%s", net_remoteaddr(n), kpf); n->tls_handshake = FALSE; gboolean ret = TRUE; int alpn_selected = ALPN_DEFAULT; #if GNUTLS_VERSION_NUMBER >= 0x030200 gnutls_datum_t neg; if(gnutls_alpn_get_selected_protocol(n->tls, &neg) == GNUTLS_E_SUCCESS) { g_debug("%s: ALPN negotiated: %.*s", net_remoteaddr(n), (int)neg.size, neg.data); if (neg.size == 3 && !memcmp(neg.data, "adc", neg.size)) { alpn_selected = ALPN_ADC; } else if (neg.size == 4 && !memcmp(neg.data, "nmdc", neg.size)) { alpn_selected = ALPN_NMDC; } } #endif if(n->cb_handshake) { net_ref(n); n->cb_handshake(n, *kpf ? kpr : NULL, alpn_selected); n->cb_handshake = NULL; ret = n->state == NETST_ASY; net_unref(n); } if(ret && n->syn) { syn_start(n); return FALSE; } return ret; } else if(gnutls_error_is_fatal(r)) { // Error char *e = g_strdup_printf("TLS error: %s", gnutls_strerror(r)); g_debug("%s: %s", net_remoteaddr(n), e); n->cb_err(n, NETERR_RECV, e); g_free(e); return FALSE; } if(gnutls_record_get_direction(n->tls)) n->wantwrite = TRUE; return TRUE; } static gboolean asy_pollresult(gpointer dat) { net_t *n = dat; n->socksrc = 0; n->wantwrite = FALSE; // Shutdown if(n->state == NETST_DIS) { if(dis_shutdown(n)) asy_setuppoll(n); return FALSE; } // Handshake if(n->tls_handshake && !asy_handshake(n)) return FALSE; // Fill rbuf if(!n->tls_handshake && !asy_read(n)) return FALSE; // Flush wbuf if(!n->tls_handshake && !asy_write(n)) return FALSE; asy_setuppoll(n); return FALSE; } static void asy_setuppoll(net_t *n) { // If we already have the right poll source active, ignore this. if(n->socksrc && (!n->wantwrite || n->writing)) return; if(n->socksrc) g_source_remove(n->socksrc); n->writing = n->wantwrite; GSource *src = fdsrc_new(n->sock, G_IO_IN | (n->writing ? G_IO_OUT : 0)); g_source_set_callback(src, asy_pollresult, n, NULL); n->socksrc = g_source_attach(src, NULL); g_source_unref(src); } static void asy_setupread(net_t *n, gboolean msg, gboolean consume, int dat, void(*cb)(net_t *, char *, int)) { g_return_if_fail(n->state == NETST_ASY); n->rd_msg = msg; n->rd_consume = consume; n->rd_dat = dat; n->rd_cb = cb; g_idle_add(asy_handlerbuf, n); } // Will run the specified callback once a full message has been received. A // "message" meaning any bytes before reading the EOM character. The EOM // character is not passed to the callback. // Only a single net_(read|peek) may be active at a single time. void net_readmsg(net_t *n, unsigned char eom, void(*cb)(net_t *, char *, int)) { asy_setupread(n, TRUE, TRUE, eom, cb); } void net_readbytes(net_t *n, int bytes, void(*cb)(net_t *, char *, int)) { asy_setupread(n, FALSE, TRUE, bytes, cb); } // Will run the specified callback once at least the specified number of bytes // are in the buffer. The data will remain in the buffer after the callback has // run. void net_peekbytes(net_t *n, int bytes, void(*cb)(net_t *, char *, int)) { asy_setupread(n, FALSE, FALSE, bytes, cb); } // Similar to net_readbytes(), but will call the data() callback for every read // from the network, this callback may be run from another thread. When done, // the done() callback will be run in the main thread. void net_recvfile(net_t *n, guint64 len, gboolean(*data)(void *, const char *, int), void(*done)(net_t *, void *), void *ctx) { g_return_if_fail(n->state == NETST_ASY); syn_new(n, FALSE, len); n->syn->cb_downdata = data; n->syn->cb_downdone = done; n->syn->ctx = ctx; n->rd_cb = NULL; g_idle_add(asy_handlerbuf, n); } #define flush if(n->tls_handshake || asy_write(n)) asy_setuppoll(n) // This is often used to write a raw byte strings, so is not logged for debugging. void net_write(net_t *n, const char *buf, int len) { if(n->state != NETST_ASY || n->syn) g_warning("%s: Write in incorrect state.", net_remoteaddr(n)); else { g_string_append_len(n->wbuf, buf, len); flush; } } // Logs the write for debugging. Does not log a trailing newline if there is one. static void asy_debugwrite(net_t *n, int oldlen) { if(n->wbuf->len && n->wbuf->len > oldlen) { int len = n->wbuf->len-oldlen; if(n->wbuf->str[n->wbuf->len-1] == '\n') len--; g_debug("%s> %.*s", net_remoteaddr(n), len, n->wbuf->str+oldlen); } } void net_writestr(net_t *n, const char *msg) { if(n->state != NETST_ASY || n->syn) g_warning("%s: Writestr in incorrect state: %s", net_remoteaddr(n), msg); else { int old = n->wbuf->len; g_string_append(n->wbuf, msg); asy_debugwrite(n, old); flush; } } void net_writef(net_t *n, const char *fmt, ...) { if(n->state != NETST_ASY || n->syn) g_warning("%s: Writef in incorrect state: %s", net_remoteaddr(n), fmt); else { int old = n->wbuf->len; va_list va; va_start(va, fmt); g_string_append_vprintf(n->wbuf, fmt, va); va_end(va); asy_debugwrite(n, old); flush; } } #undef flush // Switches to the SYN state when the write buffer has been flushed. fd will be // close()'d when done. cb() will be called in the main thread. void net_sendfile(net_t *n, int fd, guint64 len, gboolean flush, void (*cb)(net_t *)) { g_return_if_fail(n->state == NETST_ASY && !n->syn); syn_new(n, TRUE, len); n->syn->flush = flush; n->syn->cb_upldone = cb; n->syn->fd = fd; if(!n->wbuf->len) syn_start(n); } // Clean and orderly shutdown. Callback is called when done (unless there was // some error). Only supported in the ASY state. void net_shutdown(net_t *n, void(*cb)(net_t *)) { g_return_if_fail(n->state == NETST_ASY); g_debug("%s: Shutting down", net_remoteaddr(n)); n->state = NETST_DIS; n->cb_shutdown = cb; time(&n->timeout_last); if(!n->wbuf->len) dis_shutdown(n); } #if GNUTLS_VERSION_NUMBER >= 0x030200 static const gnutls_datum_t alpn_protos[2] = { { (unsigned char *)"adc", 3 }, { (unsigned char *)"nmdc", 4 } }; #endif // Enables TLS-mode and initates the handshake. May not be called when there's // something in the write buffer. If the read buffer is not empty, its contents // are assumed to be valid TLS packets and will be forwarded to gnutls. // The callback function, if set, will be called when the handshake has // completed. If a certificate of the peer has been received, its keyprint will // be sent as first argument. NULL otherwise. // Once TLS is enabled, it's not possible to switch back to a raw connection // again. void net_settls(net_t *n, gboolean serv, gboolean negotiate, void (*cb)(net_t *, const char *, int)) { g_return_if_fail(n->state == NETST_ASY); g_return_if_fail(!n->wbuf->len); g_return_if_fail(!n->tls); if(n->rbuf->len) { n->tlsrbuf = n->rbuf; n->rbuf = g_string_sized_new(1024); } gnutls_init(&n->tls, serv ? GNUTLS_SERVER : GNUTLS_CLIENT); gnutls_credentials_set(n->tls, GNUTLS_CRD_CERTIFICATE, db_certificate); const char *pos; gnutls_priority_set_direct(n->tls, var_get(0, VAR_tls_priority), &pos); gnutls_transport_set_ptr(n->tls, n); gnutls_transport_set_push_function(n->tls, tls_push); gnutls_transport_set_pull_function(n->tls, tls_pull); #if GNUTLS_VERSION_NUMBER >= 0x030200 if(negotiate) { gnutls_alpn_set_protocols(n->tls, alpn_protos, 2, 0); } #endif n->cb_handshake = cb; n->tls_handshake = TRUE; asy_handshake(n); } void net_connected(net_t *n, int sock, const char *addr, gboolean v6) { g_return_if_fail(n->state == NETST_IDL || n->state == NETST_CON); g_debug("%s: Connected.", addr); n->state = NETST_ASY; n->sock = sock; if(addr != n->addr) strncpy(n->addr, addr, sizeof(n->addr)); n->v6 = v6; n->wbuf = g_string_sized_new(1024); n->rbuf = g_string_sized_new(1024); if(v6) { struct sockaddr_in6 a; socklen_t l = sizeof(a); getsockname(sock, (void *)&a, &l); strcpy(n->laddr, ip6_unpack(a.sin6_addr)); } else { struct sockaddr_in a; socklen_t l = sizeof(a); getsockname(sock, (void *)&a, &l); strcpy(n->laddr, ip4_unpack(a.sin_addr)); } ratecalc_reset(&n->rate_in); ratecalc_reset(&n->rate_out); ratecalc_register(&n->rate_in, RCC_DOWN); ratecalc_register(&n->rate_out, RCC_UP); time(&n->timeout_last); if(!n->timeout_src) n->timeout_src = g_timeout_add_seconds(5, handle_timer, n); asy_setuppoll(n); // Always make sure we're polling for read, to catch an async disconnect. } // DNS resolution and connecting struct dnscon_t { net_t *net; char *addr; char *laddr; unsigned short port; struct addrinfo *nfo; struct addrinfo *next; char *err; void(*cb)(net_t *, const char *); }; static GThreadPool *dns_pool = NULL; static void dnscon_free(dnscon_t *r) { g_free(r->err); g_free(r->addr); g_free(r->laddr); if(r->nfo) freeaddrinfo(r->nfo); g_slice_free(dnscon_t, r); } static void dnscon_tryconn(net_t *n); static void dnsconn_handleconn(net_t *n, int err) { // Successful. if(err == 0) { net_connected(n, n->sock, n->addr, n->dnscon->next->ai_family == AF_INET6); if(n->dnscon->cb) n->dnscon->cb(n, NULL); dnscon_free(n->dnscon); n->dnscon = NULL; return; } close(n->sock); n->sock = 0; // Error, but we've got more addresses to try! if(n->dnscon->next->ai_next) { n->dnscon->next = n->dnscon->next->ai_next; dnscon_tryconn(n); return; } // Error on the last try, time to give up. g_debug("%s: Connect error: %s", net_remoteaddr(n), g_strerror(err)); n->cb_err(n, NETERR_CONN, g_strerror(err)); } static gboolean dnscon_conresult(gpointer dat) { net_t *n = dat; n->socksrc = 0; int err = 0; socklen_t len = sizeof(err); getsockopt(n->sock, SOL_SOCKET, SO_ERROR, &err, &len); dnsconn_handleconn(n, err); return FALSE; } static void dnscon_tryconn(net_t *n) { struct addrinfo *c = n->dnscon->next; time(&n->timeout_last); // Set n->addr if(c->ai_family == AF_INET) g_snprintf(n->addr, sizeof(n->addr), "%s:%d", inet_ntoa(((struct sockaddr_in *)c->ai_addr)->sin_addr), (int)ntohs(((struct sockaddr_in *)c->ai_addr)->sin_port)); else { n->addr[0] = '['; inet_ntop(AF_INET6, &((struct sockaddr_in6 *)c->ai_addr)->sin6_addr, n->addr+1, sizeof(n->addr)-1); snprintf(n->addr+strlen(n->addr), sizeof(n->addr)-strlen(n->addr), "]:%d", (int)ntohs(((struct sockaddr_in6 *)c->ai_addr)->sin6_port)); } if(n->dnscon->cb) n->dnscon->cb(n, n->addr); // Create new socket and connect. n->sock = socket(c->ai_family, SOCK_STREAM, 0); fcntl(n->sock, F_SETFL, fcntl(n->sock, F_GETFL, 0)|O_NONBLOCK); if(net_sock_bind(c->ai_family, n->sock, n->dnscon->laddr) < 0) { char *e = g_strdup_printf("Can't bind to local address: %s", g_strerror(errno)); g_debug("%s: %s", net_remoteaddr(n), e); n->cb_err(n, NETERR_CONN, e); g_free(e); return; } int r = connect(n->sock, n->dnscon->next->ai_addr, n->dnscon->next->ai_addrlen); // The common case, I guess if(r && errno == EINPROGRESS) { GSource *src = fdsrc_new(n->sock, G_IO_OUT); g_source_set_callback(src, dnscon_conresult, n, NULL); n->socksrc = g_source_attach(src, NULL); g_source_unref(src); return; } dnsconn_handleconn(n, r == 0 ? 0 : errno); } // Called as an idle function from the dnscon_thread. static gboolean dnscon_gotdns(gpointer dat) { dnscon_t *r = dat; net_t *n = r->net; // It's possible that a net_disconnect() has happened in the mean time. Free // and ignore the results in that case. if(!n) { dnscon_free(r); return FALSE; } // Handle error if(r->err) { g_debug("%s: DNS resolve: %s", net_remoteaddr(n), r->err); n->cb_err(n, NETERR_CONN, r->err); return FALSE; } // Is it possible for getaddrinfo() to return an empty result set without an error? g_return_val_if_fail(r->nfo, FALSE); // Try connecting to each of the addresses. n->state = NETST_CON; r->next = r->nfo; dnscon_tryconn(n); return FALSE; } // Async DNS resolution in a background thread static void dnscon_thread(gpointer dat, gpointer udat) { dnscon_t *r = dat; struct addrinfo hint = {}; hint.ai_family = AF_UNSPEC; hint.ai_socktype = SOCK_STREAM; hint.ai_protocol = 0; hint.ai_flags = 0; char port[20]; g_snprintf(port, sizeof(port), "%d", (int)r->port); int n = getaddrinfo(r->addr, port, &hint, &r->nfo); if(n) r->err = g_strdup(n == EAI_SYSTEM ? g_strerror(errno) : gai_strerror(n)); g_idle_add(dnscon_gotdns, r); } // Connection management time_t net_last_activity(net_t *n) { if(n->syn) g_mutex_lock(&n->syn->lock); time_t last = n->timeout_last; if(n->syn) g_mutex_unlock(&n->syn->lock); return last; } // Set to non-NULL to enable keepalive in the ASY state. Will automatically // send *msg over the socket after a certain period of inactivity. *msg is // assumed to be some statically allocated string. void net_set_keepalive(net_t *n, const char *msg) { n->timeout_msg = msg; } const char *net_remoteaddr(net_t *n) { return n->addr; } const char *net_localaddr(net_t *n) { return n->laddr; } ratecalc_t *net_rate_in(net_t *n) { return &n->rate_in; } ratecalc_t *net_rate_out(net_t *n) { return &n->rate_out; } void *net_handle(net_t *n) { return n->handle; } gboolean net_is_asy(net_t *n) { return n->state == NETST_ASY; } gboolean net_is_connected(net_t *n) { return n->state == NETST_ASY || n->state == NETST_SYN; } gboolean net_is_connecting(net_t *n) { return n->state == NETST_DNS || n->state == NETST_CON; } gboolean net_is_disconnecting(net_t *n) { return n->state == NETST_DIS; } gboolean net_is_idle(net_t *n) { return n->state == NETST_IDL; } gboolean net_is_ipv6(net_t *n) { return n->v6; } static gboolean handle_timer(gpointer dat) { net_t *n = dat; time_t intv = time(NULL)-net_last_activity(n); // time() isn't that reliable. if(intv < 0) { time(&n->timeout_last); return TRUE; } // 30 second timeout on connecting, disconnecting, synchronous transfers, and // non-keepalive ASY connections. if(intv > 30 && (n->state == NETST_DNS || n->state == NETST_CON || n->state == NETST_DIS || n->state == NETST_SYN || (n->state == NETST_ASY && !n->timeout_msg))) { if(n->state == NETST_DNS || n->state == NETST_CON) n->cb_err(n, NETERR_TIMEOUT, g_strerror(ETIMEDOUT)); else { g_debug("%s: Timeout.", net_remoteaddr(n)); n->cb_err(n, NETERR_TIMEOUT, "Idle timeout"); } n->timeout_src = 0; return FALSE; } // For keepalive ASY connections, send the timeout_msg after 2 minutes if(intv > 120 && n->state == NETST_ASY && n->timeout_msg) net_writestr(n, n->timeout_msg); return TRUE; } net_t *net_new(void *handle, void(*err)(net_t *, int, const char *)) { net_t *n = g_new0(net_t, 1); n->ref = 1; n->handle = handle; n->cb_err = err; ratecalc_init(&n->rate_in); ratecalc_init(&n->rate_out); time(&n->timeout_last); return n; } // 'host' can be either a hostname or IP address. The callback is called with // an address each time a connection attempt is made. It is called with NULL // when the connection was successful (at which point net_remoteaddr() should // work). void net_connect(net_t *n, const char *host, unsigned short port, const char *laddr, void(*cb)(net_t *, const char *)) { g_return_if_fail(n->state == NETST_IDL); dnscon_t *r = g_slice_new0(dnscon_t); r->addr = g_strdup(host); r->laddr = g_strdup(laddr); r->port = port; r->net = n; r->cb = cb; if(!n->timeout_src) n->timeout_src = g_timeout_add_seconds(5, handle_timer, n); time(&n->timeout_last); n->dnscon = r; n->state = NETST_DNS; g_thread_pool_push(dns_pool, r, NULL); } // Force-disconnect. Can be called from any state. void net_disconnect(net_t *n) { synfer_t *s = NULL; switch(n->state) { case NETST_DNS: n->dnscon->net = NULL; n->dnscon = NULL; break; case NETST_CON: dnscon_free(n->dnscon); n->dnscon = NULL; break; case NETST_ASY: case NETST_DIS: n->rd_cb = NULL; s = n->syn; if(s) { syn_cancel(n); syn_free(s); } break; case NETST_SYN: s = n->syn; if(s) syn_cancel(n); break; } // If we're in the SYN state, then the socket and tls session are in control // of the file transfer thread. Hence the need for the conditional locks. if(s) g_mutex_lock(&s->lock); if(n->tls) { gnutls_deinit(n->tls); n->tls = NULL; } if(n->sock) { close(n->sock); n->sock = 0; } time(&n->timeout_last); if(s) g_mutex_unlock(&s->lock); if(n->rbuf) { g_string_free(n->rbuf, TRUE); g_string_free(n->wbuf, TRUE); n->rbuf = n->wbuf = NULL; } if(n->tlsrbuf) { g_string_free(n->tlsrbuf, TRUE); n->tlsrbuf = NULL; } if(n->socksrc) { g_source_remove(n->socksrc); n->socksrc = 0; } if(n->timeout_src) { g_source_remove(n->timeout_src); n->timeout_src = 0; } ratecalc_unregister(&n->rate_in); ratecalc_unregister(&n->rate_out); if(n->state == NETST_ASY || n->state == NETST_SYN || n->state == NETST_DIS) g_debug("%s: Disconnected.", net_remoteaddr(n)); n->addr[0] = 0; n->wantwrite = n->writing = n->tls_handshake = n->shutdown_closed = FALSE; n->state = NETST_IDL; } void net_ref(net_t *n) { g_atomic_int_inc(&(n->ref)); } void net_unref(net_t *n) { if(!g_atomic_int_dec_and_test(&n->ref)) return; g_return_if_fail(n->state == NETST_IDL); g_free(n); } // Simple API for sending UDP packets #if INTERFACE struct net_udp_t { char addr[62]; int sock; }; #endif // Creates a new UDP socket for sending messages to the given destination. // host is assumed to be a valid IPv4 or IPv6 address. void net_udp_init(net_udp_t *udp, const char *host, unsigned short port, char *laddr) { int af = ip4_isvalid(host) ? AF_INET : AF_INET6; snprintf(udp->addr, sizeof(udp->addr), af == AF_INET ? "%s:%d" : "[%s]:%d", host, (int)port); udp->sock = socket(af, SOCK_DGRAM, 0); fcntl(udp->sock, F_SETFL, fcntl(udp->sock, F_GETFL, 0)|O_NONBLOCK); if(net_sock_bind(af, udp->sock, laddr) < 0) { g_message("Can't bind UDP socket for '%s' to local address '%s': %s", udp->addr, laddr, g_strerror(errno)); close(udp->sock); return; } int n; if(af == AF_INET) { struct in_addr a = ip4_pack(host); n = connect(udp->sock, ip4_sockaddr(a, port), sizeof(struct sockaddr_in)); } else { struct in6_addr a = ip6_pack(host); n = connect(udp->sock, ip6_sockaddr(a, port), sizeof(struct sockaddr_in6)); } if(n < 0) { g_message("Can't associate UDP socket with '%s': %s", udp->addr, g_strerror(errno)); close(udp->sock); return; } } void net_udp_destroy(net_udp_t *udp) { if(udp->sock >= 0) close(udp->sock); } // Send a message to a UDP socket, logs but otherwise ignores errors. Note that // the socket is created as non-blocking and this function does not attempt to // retry the send() on EWOULDBLOCK or EAGAIN. It is assumed that the kernel // buffers are large enough that we can burst-queue several messages, and that, // if the kernel buffers are full, we might be better off dropping some // messages than queueing them until infinity. void net_udp_send_raw(net_udp_t *udp, const char *msg, int len) { int r = send(udp->sock, msg, len, 0); if(r != len) g_message("Error sending UDP message to '%s': %s", udp->addr, g_strerror(errno)); else ratecalc_add(&net_out, len); } void net_udp_send(net_udp_t *udp, const char *msg) { int len = strlen(msg); if(len) { g_debug("UDP:%s> %.*s", udp->addr, msg[len-1] == '\n' ? len-1 : len, msg); net_udp_send_raw(udp, msg, len); } } void net_udp_sendf(net_udp_t *udp, const char *fmt, ...) { va_list va; va_start(va, fmt); char *str = g_strdup_vprintf(fmt, va); va_end(va); net_udp_send(udp, str); g_free(str); } // initialize some global structures void net_init_global() { ratecalc_init(&net_in); ratecalc_init(&net_out); // Don't group these with RCC_UP or RCC_DOWN, otherwise bandwidth will be counted twice. ratecalc_register(&net_in, RCC_NONE); ratecalc_register(&net_out, RCC_NONE); dns_pool = g_thread_pool_new(dnscon_thread, NULL, -1, FALSE, NULL); syn_pool = g_thread_pool_new(syn_thread, NULL, -1, FALSE, NULL); } ncdc-1.23.1/src/geoip.c0000644000175000017500000000520714245144510011470 00000000000000/* ncdc - NCurses Direct Connect client Copyright (c) 2011-2022 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "ncdc.h" #include "geoip.h" gboolean geoip_available = FALSE; #ifdef USE_GEOIP #include static MMDB_s *db; void geoip_reinit() { if(db) { MMDB_close(db); free(db); db = NULL; geoip_available = FALSE; } const char *fn = var_get(0, VAR_geoip_cc); if(!fn || strcmp(fn, "disabled") == 0) return; db = malloc(sizeof(MMDB_s)); int status = MMDB_open(fn, 0, db); if(status != MMDB_SUCCESS) { ui_mf(NULL, 0, "Can't open '%s' (%s), no country codes will be displayed.", fn, MMDB_strerror(status)); free(db); db = NULL; } else { geoip_available = TRUE; } } const char *geoip_country(const struct sockaddr *const ip) { if(!db) return NULL; int status; MMDB_lookup_result_s result = MMDB_lookup_sockaddr(db, ip, &status); if(status != MMDB_SUCCESS) { ui_mf(NULL, 0, "Error looking up IP address: %s", MMDB_strerror(status)); return NULL; } if(!result.found_entry) return NULL; MMDB_entry_data_s data; status = MMDB_get_value(&result.entry, &data, "country", "iso_code", (void*)NULL); if(status != MMDB_SUCCESS) { ui_mf(NULL, 0, "Error looking up IP address: %s", MMDB_strerror(status)); return NULL; } if(!data.has_data || data.type != MMDB_DATA_TYPE_UTF8_STRING) return NULL; static char buf[8]; memset(buf, 0, sizeof(buf)); memcpy(buf, data.utf8_string, data.data_size); return buf; } #else /* No GEOIP */ void geoip_reinit() {} const char *geoip_country(const struct sockaddr *const ip) { return NULL; } #endif ncdc-1.23.1/src/fl_util.c0000644000175000017500000003207414245144506012032 00000000000000/* ncdc - NCurses Direct Connect client Copyright (c) 2011-2022 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "ncdc.h" #include "fl_util.h" #if INTERFACE // file list struct fl_list_t { fl_list_t *parent; // root = NULL GPtrArray *sub; guint64 size; // including sub-items char tth[24]; gboolean isfile : 1; gboolean hastth : 1; // only if isfile==TRUE gboolean islocal : 1; // only if isfile==TRUE char name[1]; }; // Extra attributes for local files. This struct is stored in the same memory // region as the fl_list struct itself, but is placed somewhere after the name // field. Use the respecive macros to get access to this data. struct fl_list_local_t { time_t lastmod; gint64 id; }; #endif // Utility functions #if INTERFACE // Calculates the minimum required size for a non-local fl_list allocation. #define fl_list_minsize(n) (G_STRUCT_OFFSET(fl_list_t, name) + strlen(n) + 1) // Calculates the offset to the fl_list_local struct, given a name. Padding // bytes are added to ensure that the struct is aligned at 8-byte boundary. #define fl_list_local_offset(n) ((fl_list_minsize(n) + 7) & ~7) // Calculates the size required for a local fl_list allocation. #define fl_list_localsize(n) (fl_list_local_offset(n) + sizeof(fl_list_local_t)) // Calculates the actual size of a fl_list. #define fl_list_size(n, l) (l ? fl_list_localsize(n) : fl_list_minsize(n)) // Get the fl_list_local part of a fl_list struct. #define fl_list_getlocal(f) G_STRUCT_MEMBER(fl_list_local_t, f, fl_list_local_offset((f)->name)) #endif // only frees the given item and its childs. leaves the parent(s) untouched void fl_list_free(gpointer dat) { fl_list_t *fl = dat; if(!fl) return; if(fl->sub) g_ptr_array_unref(fl->sub); g_slice_free1(fl_list_size(fl->name, fl->islocal), fl); } // Create a new fl_list structure with the given name. Can't be renamed later // on due to the way the data is stored in memory. fl_list_t *fl_list_create(const char *name, gboolean local) { fl_list_t *fl = g_slice_alloc0(fl_list_size(name, local)); strcpy(fl->name, name); fl->islocal = local; return fl; } // Used for sorting and determining whether two files in the same directory are // equivalent (that is, have the same name). File names are case-insensitive, // as required by the ADC protocol. gint fl_list_cmp_strict(gconstpointer a, gconstpointer b) { return str_casecmp(((fl_list_t *)a)->name, ((fl_list_t *)b)->name); } // A more lenient comparison function, equivalent to fl_list_cmp_strict() in // all cases except when that one would return 0. This function will return // zero only if the file list names are byte-equivalent. gint fl_list_cmp(gconstpointer a, gconstpointer b) { const fl_list_t *la = a; const fl_list_t *lb = b; gint r = str_casecmp(la->name, lb->name); if(!r) r = strcmp(la->name, lb->name); return r; } // A sort function suitable for g_ptr_array_sort() static gint fl_list_cmp_sort(gconstpointer a, gconstpointer b) { return fl_list_cmp(*((fl_list_t **)a), *((fl_list_t **)b)); } // Adds `cur' to the directory `parent'. Make sure to call fl_list_sort() // afterwards. void fl_list_add(fl_list_t *parent, fl_list_t *cur, int before) { cur->parent = parent; if(before >= 0) ptr_array_insert_before(parent->sub, before, cur); else g_ptr_array_add(parent->sub, cur); // update parents size while(parent) { parent->size += cur->size; parent = parent->parent; } } // Sort the contents of a directory. Should be called after doing a (batch of) // fl_list_add(). void fl_list_sort(fl_list_t *fl) { g_return_if_fail(!fl->isfile && fl->sub); g_ptr_array_sort(fl->sub, fl_list_cmp_sort); } // Removes an item from the file list, making sure to update the parents. // This function assumes that the list has been properly sorted. void fl_list_remove(fl_list_t *fl) { // update parents size fl_list_t *par = fl->parent; while(par) { par->size -= fl->size; par = par->parent; } // remove from parent int i = -1; if(fl->parent) { i = ptr_array_search(fl->parent->sub, fl, fl_list_cmp); if(i >= 0) g_ptr_array_remove_index(fl->parent->sub, i); } // And free if it wasn't removed yet. (The remove would have implicitely freed it already) if(i < 0) fl_list_free(fl); } fl_list_t *fl_list_copy(const fl_list_t *fl) { int size = fl_list_size(fl->name, fl->islocal); fl_list_t *cur = g_slice_alloc(size); memcpy(cur, fl, size); cur->parent = NULL; if(fl->sub) { cur->sub = g_ptr_array_sized_new(fl->sub->len); g_ptr_array_set_free_func(cur->sub, fl_list_free); int i; for(i=0; isub->len; i++) { fl_list_t *tmp = fl_list_copy(g_ptr_array_index(fl->sub, i)); tmp->parent = cur; g_ptr_array_add(cur->sub, tmp); } } return cur; } // Determines whether a directory is "empty", i.e. it has no subdirectories or // hashed files. gboolean fl_list_isempty(fl_list_t *fl) { g_return_val_if_fail(!fl->isfile, FALSE); int i; for(i=0; isub->len; i++) { fl_list_t *f = g_ptr_array_index(fl->sub, i); if(!f->isfile || f->hastth) return FALSE; } return TRUE; } // Get a file by name in a directory. This search is case-insensitive. fl_list_t *fl_list_file(const fl_list_t *dir, const char *name) { fl_list_t *cmp = fl_list_create(name, FALSE); int i = ptr_array_search(dir->sub, cmp, fl_list_cmp); fl_list_free(cmp); return i < 0 ? NULL : g_ptr_array_index(dir->sub, i); } // Get a file name in a directory with the same name as *fl. This search is case-sensitive. fl_list_t *fl_list_file_strict(const fl_list_t *dir, const fl_list_t *fl) { int i = ptr_array_search(dir->sub, fl, fl_list_cmp_strict); return i < 0 ? NULL : g_ptr_array_index(dir->sub, i); } gboolean fl_list_is_child(const fl_list_t *parent, const fl_list_t *child) { for(child=child->parent; child; child=child->parent) if(child == parent) return TRUE; return FALSE; } // Get the virtual path to a file char *fl_list_path(fl_list_t *fl) { if(!fl->parent) return g_strdup("/"); char *tmp, *path = g_strdup(fl->name); fl_list_t *cur = fl->parent; while(cur->parent) { tmp = path; path = g_build_filename(cur->name, path, NULL); g_free(tmp); cur = cur->parent; } tmp = path; path = g_build_filename("/", path, NULL); g_free(tmp); return path; } // Resolves a path string (Either absolute or relative to root). Does not // support stuff like ./ and ../, and '/' is assumed to refer to the given // root. (So '/dir' and 'dir' are simply equivalent) // Case-insensitive, and '/' is the only recognised path separator fl_list_t *fl_list_from_path(fl_list_t *root, const char *path) { while(path[0] == '/') path++; if(!path[0]) return root; g_return_val_if_fail(root->sub, NULL); int slash = strcspn(path, "/"); char *name = g_strndup(path, slash); fl_list_t *n = fl_list_file(root, name); g_free(name); if(!n) return NULL; if(slash == strlen(path)) return n; if(n->isfile) return NULL; return fl_list_from_path(n, path+slash+1); } // Auto-complete for fl_list_from_path() void fl_list_suggest(fl_list_t *root, char *opath, char **sug) { fl_list_t *parent = root; char *path = g_strdup(opath); char *name; char *sep = strrchr(path, '/'); if(sep) { *sep = 0; name = sep+1; parent = fl_list_from_path(parent, path); } else { name = path; path = ""; } if(parent) { int n = 0, len = strlen(name); // Note: performance can be improved by using a binary search instead int i; for(i=0; isub->len && n<20; i++) { fl_list_t *f = g_ptr_array_index(parent->sub, i); if(strncmp(f->name, name, len) == 0) sug[n++] = f->isfile ? g_strconcat(path, "/", f->name, NULL) : g_strconcat(path, "/", f->name, "/", NULL); } } if(sep) g_free(path); else g_free(name); } // searching #if INTERFACE struct fl_search_t { signed char sizem; // -2 any, -1 <=, 0 ==, 1 >= char filedir; // 1 = file, 2 = dir, 3 = any guint64 size; char **ext; // extension list GRegex **and; // keywords that must all be present {/\Qstring\E/i, .., NULL} GRegex *not; // keywords that may not be present /\Qstring1\E|\Qstring2\E|../i }; #define fl_search_match(fl, s) (\ ((((s)->filedir & 2) && !(fl)->isfile) || (((s)->filedir & 1) && (fl)->isfile && (fl)->hastth))\ && ((s)->sizem == -2 || (!(s)->sizem && (fl)->size == (s)->size)\ || ((s)->sizem < 0 && (fl)->size <= (s)->size) || ((s)->sizem > 0 && (fl)->size > (s)->size))\ && fl_search_match_name(fl, s)) #endif // Create fl_search.and from a NULL-terminated string array. // Free with fl_search_free_and(). GRegex **fl_search_create_and(char **a) { if(!a || !*a) return NULL; int len = g_strv_length(a); GRegex **res = g_new(GRegex *, len+1); int i; for(i=0; *a; a++) { char *tmp = g_regex_escape_string(*a, -1); res[i++] = g_regex_new(tmp, G_REGEX_CASELESS|G_REGEX_OPTIMIZE, 0, NULL); g_free(tmp); } res[i] = NULL; return res; } // Create a fl_search.not regex from a NULL-terminated string array. // Free with g_regex_unref(). GRegex *fl_search_create_not(char **a) { if(!a || !*a) return NULL; GString *reg = g_string_new("(?:"); int first = 0; for(; *a; a++) { if(first++) g_string_append_c(reg, '|'); char *tmp = g_regex_escape_string(*a, -1); g_string_append(reg, tmp); g_free(tmp); } g_string_append_c(reg, ')'); GRegex *res = g_regex_new(reg->str, G_REGEX_CASELESS|G_REGEX_OPTIMIZE, 0, NULL); g_string_free(reg, TRUE); return res; } void fl_search_free_and(GRegex **l) { GRegex **i = l; for(; i&&*i; i++) g_regex_unref(*i); g_free(l); } static int fl_search_and_len(GRegex **l) { int i = 0; for(; l&&*l; l++) i++; return i; } // Only matches against fl->name itself, not the path to it (AND keywords // matched in the path are assumed to be removed already) gboolean fl_search_match_name(fl_list_t *fl, fl_search_t *s) { GRegex **tmpr; for(tmpr=s->and; tmpr&&*tmpr; tmpr++) if(G_LIKELY(!g_regex_match(*tmpr, fl->name, 0, NULL))) return FALSE; if(s->not && g_regex_match(s->not, fl->name, 0, NULL)) return FALSE; char **tmp; tmp = s->ext; if(!tmp || !*tmp) return TRUE; char *l = strrchr(fl->name, '.'); if(G_UNLIKELY(!l || !l[1])) return FALSE; l++; for(; *tmp; tmp++) if(G_UNLIKELY(g_ascii_strcasecmp(l, *tmp) == 0)) return TRUE; return FALSE; } // Recursive depth-first search through the list, used for replying to non-TTH // $Search and SCH requests. Not exactly fast, but what did you expect? :-( int fl_search_rec(fl_list_t *parent, fl_search_t *s, fl_list_t **res, int max) { if(!parent || !parent->sub) return 0; // weed out stuff from 'and' if it's already matched in parent (I'm assuming // that stuff matching the parent of parent has already been removed) GRegex **o = s->and; GRegex *nand[fl_search_and_len(o)+1]; int i = 0; for(; o&&*o; o++) if(G_LIKELY(!parent->parent || !g_regex_match(*o, parent->name, 0, NULL))) nand[i++] = *o; nand[i] = NULL; o = s->and; s->and = nand; // loop through the directory int n = 0; for(i=0; nsub->len; i++) { fl_list_t *f = g_ptr_array_index(parent->sub, i); if(fl_search_match(f, s)) res[n++] = f; if(!f->isfile && n < max) n += fl_search_rec(f, s, res+n, max-n); } s->and = o; return n; } // Similar to fl_search_match(), but also matches the name of the parents. gboolean fl_search_match_full(fl_list_t *fl, fl_search_t *s) { // weed out stuff from 'and' if it's already matched in any of its parents. GRegex **oand = s->and; int len = fl_search_and_len(s->and); GRegex *nand[len]; fl_list_t *p = fl->parent; int i; memcpy(nand, s->and, len*sizeof(GRegex *)); for(; p && p->parent; p=p->parent) for(i=0; iname, 0, NULL))) nand[i] = NULL; GRegex *and[len]; int j=0; for(i=0; iand = and; // and now match gboolean r = fl_search_match(fl, s); s->and = oand; return r; } ncdc-1.23.1/src/db.c0000644000175000017500000013721514245144460010763 00000000000000/* ncdc - NCurses Direct Connect client Copyright (c) 2011-2022 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "ncdc.h" #include "db.h" // Most of the db_* functions can be used from multiple threads. The database // is only accessed from within the database thread (db_thread_func()). All // access to the database from other threads is performed via message passing. // // Some properties of this implementation: // - Multiple UPDATE/DELETE/INSERT statements in a short interval are grouped // together in a single transaction. // - All queries are executed in the same order as they are queued. // TODO: Improve error handling. In the current implementation, if an error // occurs, the transaction is aborted and none of the queries scheduled for the // transaction is executed. The only way the user can know that his has // happened is by looking at stderr.log, it'd be better to provide a notify to // the UI. static GAsyncQueue *db_queue = NULL; static GThread *db_thread = NULL; static GHashTable *db_stmt_cache = NULL; // A "queue item" is a darray (see util.c) to represent a queued SQL query, // with the following structure: // int32 = flags // ptr = (char *)sql_query. This must be a static string in global memory. // arguments: // int32 = type // rest depending on type: // NULL: no further arguments // INT: int32 // INT64: int64 // TEXT: string // BLOB: dat // RES: ptr to a GAsyncQueue followed by an array of int32 DBQ_* items // until DBQ_END. (Only INT, INT64, TEXT and BLOB can be used) // if(type != END) // goto arguments // A "result item" is a darray to represent a result row, with the following // structure: // int32 = result code (SQLITE_ROW, SQLITE_DONE or anything else for error) // For SQLITE_DONE: // if DBQ_LASTID is requested: int64. Otherwise no other arguments. // For SQLITE_ROW: // for each array in the above RES thing, the data of the column. // Query flags #define DBF_NEXT 1 // Current query must be in the same transaction as next query in the queue. #define DBF_LAST 2 // Current query must be the last in a transaction (forces a flush) #define DBF_SINGLE 4 // Query must not be executed in a transaction (e.g. VACUUM) #define DBF_NOCACHE 8 // Don't cache this query in the prepared statement cache #define DBF_END 128 // Signal the database thread to close // Column types #define DBQ_END 0 #define DBQ_NULL 1 // No arguments #define DBQ_INT 2 // int #define DBQ_INT64 3 // gint64 #define DBQ_TEXT 4 // char * (NULL allowed) #define DBQ_BLOB 5 // int length, char *data (NULL allowed) #define DBQ_RES 6 #define DBQ_LASTID 7 // To indicate that the query wants the last inserted row id as result // How long to keep a transaction active before flushing. In microseconds. #define DB_FLUSH_TIMEOUT (5000000) // Give back a final response and unref the queue. static void db_queue_item_final(GAsyncQueue *res, int code, gint64 lastid) { if(!res) return; GByteArray *r = g_byte_array_new(); darray_init(r); darray_add_int32(r, code); if(code == SQLITE_DONE) darray_add_int64(r, lastid); g_async_queue_push(res, g_byte_array_free(r, FALSE)); g_async_queue_unref(res); } // Give back an error result and decrement the reference counter of the // response queue. Assumes the `flags' has already been read. static void db_queue_item_error(char *q) { char *b = darray_get_ptr(q); // query b++; // otherwise gcc will complain int t; while((t = darray_get_int32(q)) != DBQ_END && t != DBQ_RES) ; if(t == DBQ_RES) db_queue_item_final(darray_get_ptr(q), SQLITE_ERROR, 0); } // Similar to sqlite3_prepare_v2(), except this returns a cached statement // handler if the query had already been prepared before. Note that the lookup // in the db_stmt_cache is *NOT* done by the actual query string, but by its // pointer value. This is a lot more efficient, but assumes that SQL statements // are never dynamically generated: they must be somewhere in static memory. // Note: db_stmt_cache is assumed to be used only for the given *db pointer. // Important: DON'T run sqlite3_finalize() on queries returned by this // function! Use sqlite3_reset() instead. static int db_queue_process_prepare(sqlite3 *db, const char *query, sqlite3_stmt **s) { *s = g_hash_table_lookup(db_stmt_cache, query); if(*s) return SQLITE_OK; int r = sqlite3_prepare_v2(db, query, -1, s, NULL); if(r == SQLITE_OK) g_hash_table_insert(db_stmt_cache, (gpointer)query, *s); return r; } // Executes a single query. // If transaction = TRUE, the query is assumed to be executed in a transaction // (which has already been initiated) // The return path (if any) and lastid (0 if not requested) are stored in *res // and *lastid. The caller of this function is responsible for sending back the // final response. If this function returns anything other than SQLITE_DONE, // the query has failed. // It is assumed that the first `flags' part of the queue item has already been // fetched. static int db_queue_process_one(sqlite3 *db, char *q, gboolean nocache, gboolean transaction, GAsyncQueue **res, gint64 *lastid) { char *query = darray_get_ptr(q); *res = NULL; *lastid = 0; // Would be nice to have the parameters logged g_debug("db: Executing \"%s\"", query); // Get statement handler int r = SQLITE_ROW; sqlite3_stmt *s; if(nocache ? sqlite3_prepare_v2(db, query, -1, &s, NULL) : db_queue_process_prepare(db, query, &s)) { g_critical("SQLite3 error preparing `%s': %s", query, sqlite3_errmsg(db)); r = SQLITE_ERROR; } // Bind parameters int t, n; int i = 1; char *a; while((t = darray_get_int32(q)) != DBQ_END && t != DBQ_RES) { if(r == SQLITE_ERROR) continue; switch(t) { case DBQ_NULL: sqlite3_bind_null(s, i); break; case DBQ_INT: sqlite3_bind_int(s, i, darray_get_int32(q)); break; case DBQ_INT64: sqlite3_bind_int64(s, i, darray_get_int64(q)); break; case DBQ_TEXT: sqlite3_bind_text(s, i, darray_get_string(q), -1, SQLITE_STATIC); break; case DBQ_BLOB: a = darray_get_dat(q, &n); sqlite3_bind_blob(s, i, a, n, SQLITE_STATIC); break; } i++; } // Fetch information about what results we need to send back gboolean wantlastid = FALSE; char columns[20]; // 20 should be enough for everyone n = 0; if(t == DBQ_RES) { *res = darray_get_ptr(q); while((t = darray_get_int32(q)) != DBQ_END) { if(t == DBQ_LASTID) wantlastid = TRUE; else columns[n++] = t; } } // Execute query while(r == SQLITE_ROW) { // do the step() if(transaction) r = sqlite3_step(s); else while((r = sqlite3_step(s)) == SQLITE_BUSY) ; if(r != SQLITE_DONE && r != SQLITE_ROW) g_critical("SQLite3 error on step() of `%s': %s", query, sqlite3_errmsg(db)); // continue with the next step() if we're not going to do anything with the results if(r != SQLITE_ROW || !*res || !n) continue; // send back a response GByteArray *rc = g_byte_array_new(); darray_init(rc); darray_add_int32(rc, r); for(i=0; idata; // Otherwise, create the cache db_share_cache = g_array_new(TRUE, FALSE, sizeof(db_share_item_t)); GAsyncQueue *a = g_async_queue_new_full(g_free); db_queue_push(DBF_NOCACHE, "SELECT name, path FROM share ORDER BY name", DBQ_RES, a, DBQ_TEXT, DBQ_TEXT, DBQ_END ); char *r; db_share_item_t i; while((r = g_async_queue_pop(a)) && darray_get_int32(r) == SQLITE_ROW) { i.name = g_strdup(darray_get_string(r)); i.path = g_strdup(darray_get_string(r)); g_array_append_val(db_share_cache, i); g_free(r); } g_free(r); g_async_queue_unref(a); return (db_share_item_t *)db_share_cache->data; } // Returns the path associated with a shared directory. The returned string // should not be freed, and may be modified by any later call to a db_share // function. const char *db_share_path(const char *name) { // The list is always ordered, so a binary search is possible and will be // more efficient than this linear search. I don't think anyone has enough // shared directories for that to matter, though. db_share_item_t *l = db_share_list(); for(; l->name; l++) if(strcmp(name, l->name) == 0) return l->path; return NULL; } // Remove an item from the share. Use name = NULL to remove everything. void db_share_rm(const char *name) { // Remove all if(!name) { // Purge cache db_share_item_t *l = db_share_list(); for(; l->name; l++) { g_free(l->name); g_free(l->path); } g_array_set_size(db_share_cache, 0); // Remove from the db db_queue_push(0, "DELETE FROM share", DBQ_END); // Remove one } else { // Remove from the cache db_share_item_t *l = db_share_list(); int i; for(i=0; l->name; l++,i++) { if(strcmp(name, l->name) == 0) { g_free(l->name); g_free(l->path); g_array_remove_index(db_share_cache, i); break; } } // Remove from the db db_queue_push(0, "DELETE FROM share WHERE name = ?", DBQ_TEXT, name, DBQ_END); } } // Add an item to the share. void db_share_add(const char *name, const char *path) { // Add to the cache db_share_item_t new; new.name = g_strdup(name); new.path = g_strdup(path); db_share_item_t *l = db_share_list(); int i; for(i=0; l->name; l++,i++) if(strcmp(l->name, name) > 0) break; g_array_insert_val(db_share_cache, i, new); // Add to the db db_queue_push(0, "INSERT INTO share (name, path) VALUES (?, ?)", DBQ_TEXT, name, DBQ_TEXT, path, DBQ_END); } // Vars table // As with db_share*, the db_vars* functions are NOT thread-safe, and must be // accessed only from the main thread. // Try to avoid using the db_vars_(get|set) functions directly. Use the // higher-level vars.c abstraction instead. typedef struct db_var_item_t { char *name; char *val; guint64 hub; } db_var_item_t; static GHashTable *db_vars_cache = NULL; // Hash, equal and free functions for the hash table static guint db_vars_cachehash(gconstpointer a) { const db_var_item_t *i = a; return g_str_hash(i->name) + g_int64_hash(&i->hub); } static gboolean db_vars_cacheeq(gconstpointer a, gconstpointer b) { const db_var_item_t *x = a; const db_var_item_t *y = b; return strcmp(x->name, y->name) == 0 && x->hub == y->hub ? TRUE : FALSE; } static void db_vars_cachefree(gpointer a) { db_var_item_t *i = a; g_free(i->name); g_free(i->val); g_slice_free(db_var_item_t, i); } // Ensures db_vars_cache is initialized static void db_vars_cacheget() { if(db_vars_cache) return; db_vars_cache = g_hash_table_new_full(db_vars_cachehash, db_vars_cacheeq, NULL, db_vars_cachefree); GAsyncQueue *a = g_async_queue_new_full(g_free); db_queue_push(DBF_NOCACHE, "SELECT name, hub, value FROM vars", DBQ_RES, a, DBQ_TEXT, DBQ_INT64, DBQ_TEXT, DBQ_END ); char *r; while((r = g_async_queue_pop(a)) && darray_get_int32(r) == SQLITE_ROW) { db_var_item_t *i = g_slice_new(db_var_item_t); i->name = g_strdup(darray_get_string(r)); i->hub = darray_get_int64(r); i->val = g_strdup(darray_get_string(r)); g_hash_table_insert(db_vars_cache, i, i); g_free(r); } g_free(r); g_async_queue_unref(a); } // Get a value from the vars table. The return value should not be modified or freed. char *db_vars_get(guint64 hub, const char *name) { db_vars_cacheget(); db_var_item_t i, *r; i.name = (char *)name; i.hub = hub; r = g_hash_table_lookup(db_vars_cache, &i); return r ? r->val : NULL; } // Unset a value (remove it) void db_vars_rm(guint64 hub, const char *name) { if(!db_vars_get(hub, name)) return; // Update cache db_var_item_t i; i.name = (char *)name; i.hub = hub; g_hash_table_remove(db_vars_cache, &i); // Update database db_queue_push(0, "DELETE FROM vars WHERE name = ? AND hub = ?", DBQ_TEXT, name, DBQ_INT64, hub, DBQ_END); } // Unset all values for a certain hubid. void db_vars_rmhub(guint64 hub) { g_return_if_fail(hub); // Not strictly an error, but not what this function was designed to do. db_vars_cacheget(); // Update cache GHashTableIter i; db_var_item_t *n; g_hash_table_iter_init(&i, db_vars_cache); while(g_hash_table_iter_next(&i, NULL, (gpointer *)&n)) if(n->hub == hub) g_hash_table_iter_remove(&i); // Update database db_queue_push(0, "DELETE FROM vars WHERE hub = ?", DBQ_INT64, hub, DBQ_END); } // Set a value. If val = NULL, then _rm() is called instead. void db_vars_set(guint64 hub, const char *name, const char *val) { if(!val) { db_vars_rm(hub, name); return; } char *old = db_vars_get(hub, name); if(old && strcmp(val, old) == 0) return; // Update cache db_var_item_t *i = g_slice_new(db_var_item_t);; i->hub = hub; i->name = g_strdup(name); i->val = g_strdup(val); g_hash_table_replace(db_vars_cache, i, i); // Update database db_queue_push(0, "INSERT OR REPLACE INTO vars (name, hub, value) VALUES (?, ?, ?)", DBQ_TEXT, name, DBQ_INT64, hub, DBQ_TEXT, val, DBQ_END); } // Get the hub id given the `hubname' variable. (linear search) guint64 db_vars_hubid(const char *name) { db_vars_cacheget(); if(*name == '#') name++; GHashTableIter i; db_var_item_t *n; g_hash_table_iter_init(&i, db_vars_cache); while(g_hash_table_iter_next(&i, NULL, (gpointer *)&n)) if(strcmp(n->name, "hubname") == 0 && *n->val && strcmp(n->val+1, name) == 0) return n->hub; return 0; } // Get a sorted list of hub names. Should be freed with g_strfreev() char **db_vars_hubs() { db_vars_cacheget(); GPtrArray *p = g_ptr_array_new(); GHashTableIter i; db_var_item_t *n; g_hash_table_iter_init(&i, db_vars_cache); while(g_hash_table_iter_next(&i, NULL, (gpointer *)&n)) if(strcmp(n->name, "hubname") == 0) g_ptr_array_add(p, g_strdup(n->val)); g_ptr_array_sort(p, cmpstringp); g_ptr_array_add(p, NULL); return (char **)g_ptr_array_free(p, FALSE); } // Users table // (not thread-safe) #if INTERFACE #define DB_USERFLAG_GRANT 1 // User has been granted a slot struct db_user_t { guint64 hub; guint64 uid; // Set, but not actually used. Matching is on the nick. int flags; char nick[1]; }; #endif static GHashTable *db_users_cache = NULL; static guint db_user_hash(gconstpointer key) { const db_user_t *u = key; return g_str_hash(u->nick) + g_int64_hash(&u->hub); } static gboolean db_user_equal(gconstpointer pa, gconstpointer pb) { const db_user_t *a = pa; const db_user_t *b = pb; return a->hub == b->hub && strcmp(a->nick, b->nick) == 0 ? TRUE : FALSE; } // For use with qsort() static int db_users_cmp(const void *pa, const void *pb) { db_user_t *a = *((db_user_t **)pa); db_user_t *b = *((db_user_t **)pb); int r = g_utf8_collate(a->nick, b->nick); if(r == 0) r = a->hub > b->hub ? 1 : a->hub < b->hub ? -1 : 0; return r; } static db_user_t *db_users_alloc(guint64 hub, guint64 uid, int flags, const char *nick) { db_user_t *u = g_malloc(offsetof(db_user_t, nick) + strlen(nick) + 1); u->hub = hub; u->uid = uid; u->flags = flags; strcpy(u->nick, nick); return u; } static void db_users_cacheget() { if(db_users_cache) return; db_users_cache = g_hash_table_new_full(db_user_hash, db_user_equal, NULL, g_free); GAsyncQueue *a = g_async_queue_new_full(g_free); db_queue_push(DBF_NOCACHE, "SELECT hub, uid, flags, nick FROM users", DBQ_RES, a, DBQ_INT64, DBQ_INT64, DBQ_INT, DBQ_TEXT, DBQ_END ); char *r; while((r = g_async_queue_pop(a)) && darray_get_int32(r) == SQLITE_ROW) { guint64 hub = darray_get_int64(r); guint64 uid = darray_get_int64(r); int flags = darray_get_int32(r); db_user_t *u = db_users_alloc(hub, uid, flags, darray_get_string(r)); g_hash_table_insert(db_users_cache, u, u); g_free(r); } g_free(r); g_async_queue_unref(a); } // Returns the flags for a particular user, 0 if not in the DB int db_users_get(guint64 hub, const char *nick) { db_users_cacheget(); db_user_t *u = db_users_alloc(hub, 0, 0, nick); db_user_t *r = g_hash_table_lookup(db_users_cache, u); g_free(u); return r ? r->flags : 0; } void db_users_rm(guint64 hub, const char *nick) { if(!db_users_get(hub, nick)) return; db_user_t *u = db_users_alloc(hub, 0, 0, nick); g_hash_table_remove(db_users_cache, u); g_free(u); db_queue_push(0, "DELETE FROM users WHERE nick = ? AND hub = ?", DBQ_TEXT, nick, DBQ_INT64, hub, DBQ_END); } // Set a value. If val = NULL, then _rm() is called instead. void db_users_set(guint64 hub, guint64 uid, const char *nick, int flags) { if(!flags) { db_users_rm(hub, nick); return; } db_user_t *u = db_users_alloc(hub, uid, flags, nick); g_hash_table_replace(db_users_cache, u, u); db_queue_push(0, "INSERT OR REPLACE INTO users (hub, uid, nick, flags) VALUES (?, ?, ?, ?)", DBQ_INT64, hub, DBQ_INT64, uid, DBQ_TEXT, nick, DBQ_INT, flags, DBQ_END); } // Remove all user info of a particular void db_users_rmhub(guint64 hub) { db_users_cacheget(); GHashTableIter i; db_user_t *u; g_hash_table_iter_init(&i, db_users_cache); while(g_hash_table_iter_next(&i, NULL, (gpointer *)&u)) if(u->hub == hub) g_hash_table_iter_remove(&i); db_queue_push(0, "DELETE FROM users WHERE hub = ?", DBQ_INT64, hub, DBQ_END); } // Get an ordered list (username, hubid) of users. The array must be g_free()'d // after use, but the elements shouldn't. db_user_t **db_users_list() { db_users_cacheget(); db_user_t **list = g_new(db_user_t *, g_hash_table_size(db_users_cache)+1); db_user_t *u; GHashTableIter i; g_hash_table_iter_init(&i, db_users_cache); int n = 0; while(g_hash_table_iter_next(&i, NULL, (gpointer *)&u)) list[n++] = u; list[n] = NULL; qsort(list, n, sizeof(db_user_t *), db_users_cmp); return list; } // Initialize the database directory and other stuff const char *db_dir = NULL; gnutls_certificate_credentials_t db_certificate; // Base32-encoded keyprint of our own certificate char *db_certificate_kp = NULL; static const char *cert_gen(const char *cert_file, const char *key_file, gnutls_x509_crt_t cert, gnutls_x509_privkey_t key) { unsigned char dat[32*1024]; size_t len; FILE *f; printf("Generating certificates..."); fflush(stdout); // Make sure either both exists or none exists unlink(cert_file); unlink(key_file); // Private key int bits = 2432; #if GNUTLS_VERSION_MAJOR > 2 || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR >= 12) bits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_RSA, GNUTLS_SEC_PARAM_NORMAL); #endif gnutls_x509_privkey_generate(key, GNUTLS_PK_RSA, bits, 0); len = sizeof(dat); g_assert(gnutls_x509_privkey_export(key, GNUTLS_X509_FMT_PEM, dat, &len) == 0); if(!(f = fopen(key_file, "w")) || fwrite(dat, 1, len, f) != len || fclose(f)) return g_strerror(errno); // Certificate (self-signed) time_t t = time(NULL); gnutls_x509_crt_set_dn_by_oid(cert, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, "Unknown", strlen("Unknown")); gnutls_x509_crt_set_dn_by_oid(cert, GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, 0, "Unknown", strlen("Unknown")); gnutls_x509_crt_set_dn_by_oid(cert, GNUTLS_OID_X520_COMMON_NAME, 0, "Unknown", strlen("Unknown")); gnutls_x509_crt_set_dn_by_oid(cert, GNUTLS_OID_X520_LOCALITY_NAME, 0, "Unknown", strlen("Unknown")); gnutls_x509_crt_set_dn_by_oid(cert, GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, 0, "Unknown", strlen("Unknown")); gnutls_x509_crt_set_dn_by_oid(cert, GNUTLS_OID_X520_COUNTRY_NAME, 0, "UN", strlen("UN")); gnutls_x509_crt_set_key(cert, key); gnutls_x509_crt_set_serial(cert, &t, sizeof(t)); gnutls_x509_crt_set_activation_time(cert, t-(24*3600)); gnutls_x509_crt_set_expiration_time(cert, t+(3560*24*3600)); gnutls_x509_crt_sign(cert, cert, key); len = sizeof(dat); g_assert(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_PEM, dat, &len) == 0); if(!(f = fopen(cert_file, "w")) || fwrite(dat, 1, len, f) != len || fclose(f)) return g_strerror(errno); return NULL; } // Convenience function based on // http://www.gnu.org/software/gnutls/manual/html_node/Using-a-callback-to-select-the-certificate-to-use.html static gnutls_datum_t load_file(const char *file, const char **err) { FILE *f; gnutls_datum_t d = { NULL, 0 }; long len; void *ptr = NULL; if (!(f = fopen(file, "r")) || fseek(f, 0, SEEK_END) != 0 || (len = ftell(f)) < 0 || fseek(f, 0, SEEK_SET) != 0 || !(ptr = g_malloc((size_t)len)) || fread(ptr, 1, (size_t)len, f) < (size_t)len) *err = g_strerror(errno); if(f) fclose(f); if(*err && ptr) g_free(ptr); if(!*err) { d.data = ptr; d.size = (unsigned int)len; } return d; } static const char *cert_load(const char *cert_file, const char *key_file, gnutls_x509_crt_t cert, gnutls_x509_privkey_t key) { const char *err = NULL; // Load cert int n; gnutls_datum_t crtdat = load_file(cert_file, &err); if(err) return err; if((n = gnutls_x509_crt_import(cert, &crtdat, GNUTLS_X509_FMT_PEM)) < 0) return gnutls_strerror(n); g_free(crtdat.data); // Load key gnutls_datum_t keydat = load_file(key_file, &err); if(err) return err; if((n = gnutls_x509_privkey_import(key, &keydat, GNUTLS_X509_FMT_PEM)) < 0) return gnutls_strerror(n); g_free(keydat.data); return NULL; } static void cert_init() { char *cert_file = g_build_filename(db_dir, "cert", "client.crt", NULL); char *key_file = g_build_filename(db_dir, "cert", "client.key", NULL); gnutls_x509_crt_t cert; gnutls_x509_privkey_t key; gnutls_x509_crt_init(&cert); gnutls_x509_privkey_init(&key); const char *err = NULL; if(g_file_test(cert_file, G_FILE_TEST_EXISTS) && g_file_test(key_file, G_FILE_TEST_EXISTS)) err = cert_load(cert_file, key_file, cert, key); else err = cert_gen(cert_file, key_file, cert, key); if(err) { printf( "ERROR: Could not load the client certificate files.\n" " %s\n\n" "Please check that a valid client certificate is stored in the following two files:\n" " %s\n %s\n" "Or remove the files to automatically generate a new certificate.\n", err, cert_file, key_file); exit(1); } // Set credentials gnutls_certificate_allocate_credentials(&db_certificate); gnutls_certificate_set_x509_key(db_certificate, &cert, 1, key); // Generate keyprint size_t len = 8*1024; // should be enough unsigned char crtder[len]; char raw[32]; g_assert(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, (void *)crtder, &len) == 0); gnutls_datum_t dat; dat.data = crtder; dat.size = len; db_certificate_kp = g_malloc0(53); certificate_sha256(dat, raw); base32_encode_dat(raw, db_certificate_kp, 32); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); g_free(cert_file); g_free(key_file); } // Checks or creates the initial session directory, including subdirectories // and the version/lock file. Returns the database version. (major<<8 + minor) static int db_dir_init() { // Get location of the session directory. It may already have been set in main.c if(!db_dir && (db_dir = g_getenv("NCDC_DIR"))) db_dir = g_strdup(db_dir); if(!db_dir) db_dir = g_build_filename(g_get_home_dir(), ".ncdc", NULL); // try to create it (ignoring errors if it already exists) g_mkdir(db_dir, 0700); if(g_access(db_dir, F_OK | R_OK | X_OK | W_OK) < 0) g_error("Directory '%s' does not exist or is not writable.", db_dir); // Make sure it's an absolute path (yes, after mkdir'ing it, path_expand() // may return an error if it doesn't exist). Just stick with the relative // path if this fails, it's not critical anyway. char *real = path_expand(db_dir); if(real) { g_free((char *)db_dir); db_dir = real; } // make sure some subdirectories exist and are writable #define cdir(d) do {\ char *tmp = g_build_filename(db_dir, d, NULL);\ g_mkdir(tmp, 0777);\ if(g_access(db_dir, F_OK | R_OK | X_OK | W_OK) < 0)\ g_error("Directory '%s' does not exist or is not writable.", tmp);\ g_free(tmp);\ } while(0) cdir("logs"); cdir("inc"); cdir("fl"); cdir("dl"); cdir("cert"); #undef cdir // make sure that there is no other ncdc instance working with the same config directory char *ver_file = g_build_filename(db_dir, "version", NULL); int ver_fd = g_open(ver_file, O_RDWR|O_CREAT, 0600); struct flock lck; lck.l_type = F_WRLCK; lck.l_whence = SEEK_SET; lck.l_start = 0; lck.l_len = 0; if(ver_fd < 0 || fcntl(ver_fd, F_SETLK, &lck) == -1) g_error("Unable to open lock file. Is another instance of ncdc running with the same configuration directory?"); // check data directory version // version = major, minor // minor = forward & backward compatible, major only backward. char dir_ver[2] = {2, 0}; if(read(ver_fd, dir_ver, 2) < 2) if(write(ver_fd, dir_ver, 2) < 2) g_error("Could not write to '%s': %s", ver_file, g_strerror(errno)); g_free(ver_file); // Don't close the above file. Keep it open and let the OS close it (and free // the lock) when ncdc is closed, was killed or has crashed. return (((int)dir_ver[0])<<8) + (int)dir_ver[1]; } #define DB_USERS_TABLE \ "CREATE TABLE users ("\ " hub INTEGER NOT NULL,"\ " uid INTEGER NOT NULL,"\ " nick TEXT NOT NULL,"\ " flags INTEGER NOT NULL"\ ")" static void db_init_schema() { // Get user_version GAsyncQueue *a = g_async_queue_new_full(g_free); db_queue_push(DBF_SINGLE|DBF_NOCACHE, "PRAGMA user_version", DBQ_RES, a, DBQ_INT, DBQ_END); char *r = g_async_queue_pop(a); int ver; if(darray_get_int32(r) == SQLITE_ROW) ver = darray_get_int32(r); else g_error("Unable to get database version."); g_free(r); g_async_queue_unref(a); // New database? Initialize schema. if(ver == 0) { db_queue_lock(); db_queue_push_unlocked(DBF_NEXT|DBF_NOCACHE, "PRAGMA user_version = 2", DBQ_END); db_queue_push_unlocked(DBF_NEXT|DBF_NOCACHE, "CREATE TABLE hashdata (" " root TEXT NOT NULL PRIMARY KEY," " size INTEGER NOT NULL," " tthl BLOB NOT NULL" ")", DBQ_END); db_queue_push_unlocked(DBF_NEXT|DBF_NOCACHE, "CREATE TABLE hashfiles (" " id INTEGER PRIMARY KEY," " filename TEXT NOT NULL UNIQUE," " tth TEXT NOT NULL," " lastmod INTEGER NOT NULL" ")", DBQ_END); db_queue_push_unlocked(DBF_NEXT|DBF_NOCACHE, "CREATE TABLE dl (" " tth TEXT NOT NULL PRIMARY KEY," " size INTEGER NOT NULL," " dest TEXT NOT NULL," " priority INTEGER NOT NULL DEFAULT 0," " error INTEGER NOT NULL DEFAULT 0," " error_msg TEXT," " tthl BLOB" ")", DBQ_END); db_queue_push_unlocked(DBF_NEXT|DBF_NOCACHE, "CREATE TABLE dl_users (" " tth TEXT NOT NULL," " uid INTEGER NOT NULL," " error INTEGER NOT NULL DEFAULT 0," " error_msg TEXT," " PRIMARY KEY(tth, uid)" ")", DBQ_END); db_queue_push_unlocked(DBF_NEXT|DBF_NOCACHE, "CREATE TABLE share (" " name TEXT NOT NULL PRIMARY KEY," " path TEXT NOT NULL" ")", DBQ_END); db_queue_push_unlocked(DBF_NEXT|DBF_NOCACHE, DB_USERS_TABLE, DBQ_END); // Get a result from the last one, to make sure the above queries were successful. GAsyncQueue *a = g_async_queue_new_full(g_free); db_queue_push_unlocked(DBF_LAST|DBF_NOCACHE, "CREATE TABLE vars (" " name TEXT NOT NULL," " hub INTEGER NOT NULL DEFAULT 0," " value TEXT NOT NULL," " PRIMARY KEY(name, hub)" ")", DBQ_RES, a, DBQ_END); db_queue_unlock(); char *r = g_async_queue_pop(a); if(darray_get_int32(r) != SQLITE_DONE) g_error("Error creating database schema."); g_free(r); g_async_queue_unref(a); } // Version 1 didn't have the users table if(ver == 1) { db_queue_lock(); GAsyncQueue *a = g_async_queue_new_full(g_free); db_queue_push_unlocked(DBF_NEXT|DBF_NOCACHE, "PRAGMA user_version = 2", DBQ_END); db_queue_push_unlocked(DBF_LAST|DBF_NOCACHE, DB_USERS_TABLE, DBQ_RES, a, DBQ_END); db_queue_unlock(); char *r = g_async_queue_pop(a); if(darray_get_int32(r) != SQLITE_DONE) g_error("Error updating database schema."); g_free(r); g_async_queue_unref(a); } } void db_init() { int ver = db_dir_init(); if(ver>>8 < 2) g_error("Database version too old. Please delete the directory to start from scratch, or run the ncdc-db-upgrade utility available with ncdc 1.13 and earlier."); if(ver>>8 > 2) g_error("Incompatible database version. You may want to upgrade ncdc."); // load client certificate cert_init(); // start database thread db_queue = g_async_queue_new(); db_thread = g_thread_new("database thread", db_thread_func, g_build_filename(db_dir, "db.sqlite3", NULL)); db_init_schema(); } // Executes a VACUUM void db_vacuum() { db_queue_push(DBF_SINGLE|DBF_NOCACHE, "VACUUM", DBQ_END); } ncdc-1.23.1/src/commands.c0000644000175000017500000011463614245144455012205 00000000000000/* ncdc - NCurses Direct Connect client Copyright (c) 2011-2022 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "ncdc.h" #include "commands.h" #define DOC_CMD #define DOC_KEY #define DOC_SET #include "doc.h" typedef struct cmd_t { char name[16]; void (*f)(char *); void (*suggest)(char *, char **); doc_cmd_t *doc; } cmd_t; // tentative definition of the cmd list static cmd_t cmds[]; // get a command by name. performs a linear search. can be rewritten to use a // binary search, but I doubt the performance difference really matters. static cmd_t *getcmd(const char *name) { cmd_t *c; for(c=cmds; *c->name; c++) if(strcmp(c->name, name) == 0) break; return c->f ? c : NULL; } // Get documentation for a command. May be slow at first, but caches the doc // structure later on. static doc_cmd_t *getdoc(cmd_t *cmd) { static doc_cmd_t empty = { "", NULL, "No documentation available." }; if(cmd->doc) return cmd->doc; doc_cmd_t *i = (doc_cmd_t *)doc_cmds; for(; *i->name; i++) if(strcmp(i->name, cmd->name) == 0) break; cmd->doc = *i->name ? i : ∅ return cmd->doc; } static void c_quit(char *args) { ncdc_quit(); } // handle /say and /me static void sayme(char *args, gboolean me) { ui_tab_t *tab = ui_tab_cur->data; if(tab->type != uit_hub && tab->type != uit_msg) ui_m(NULL, 0, "This command can only be used on hub and message tabs."); else if(!tab->hub->nick_valid) ui_m(NULL, 0, "Not connected or logged in yet."); else if(!args[0]) ui_m(NULL, 0, "Message empty."); else if(tab->type == uit_hub) hub_say(tab->hub, args, me); else { guint64 uid = uit_msg_uid(tab); hub_user_t *u = g_hash_table_lookup(hub_uids, &uid); if(!u) ui_m(NULL, 0, "User is not online."); else hub_msg(tab->hub, u, args, me); } } static void c_say(char *args) { sayme(args, FALSE); } static void c_me(char *args) { sayme(args, TRUE); } static void c_msg(char *args) { char *sep = strchr(args, ' '); if(sep) { *sep = 0; while(*(++sep) == ' '); } ui_tab_t *tab = ui_tab_cur->data; if(tab->type != uit_hub && tab->type != uit_msg) ui_m(NULL, 0, "This command can only be used on hub and message tabs."); else if(!tab->hub->nick_valid) ui_m(NULL, 0, "Not connected or logged in yet."); else if(!args[0]) ui_m(NULL, 0, "No user specified. See `/help msg' for more information."); else { hub_user_t *u = hub_user_get(tab->hub, args); if(!u) ui_m(NULL, 0, "No user found with that name. Note that usernames are case-sensitive."); else { // get or open tab and make sure it's selected uit_msg_open(u->uid, tab); // if we need to send something, do so if(sep && *sep) hub_msg(tab->hub, u, sep, FALSE); } } } static void c_help(char *args) { char *sec = strchr(args, ' '); if(sec) *(sec++) = 0; // list available commands if(!args[0]) { ui_m(NULL, 0, "\nAvailable commands:"); cmd_t *c = cmds; for(; c->f; c++) ui_mf(NULL, 0, " /%s - %s", c->name, getdoc(c)->sum); ui_m(NULL, 0, "\nFor help on key bindings, use `/help keys'.\n"); // list information on a setting } else if((strcmp(args, "set") == 0 || strcmp(args, "hset") == 0) && sec) { sec = strncmp(sec, "color_", 6) == 0 ? "color_*" : sec; doc_set_t *s = (doc_set_t *)doc_sets; for(; s->name; s++) if(strcmp(s->name, sec) == 0) break; if(!s->name) ui_mf(NULL, 0, "\nUnknown setting '%s'.", sec); else ui_mf(NULL, 0, "\nSetting: %s.%s %s\n\n%s\n", s->hub ? "#hub" : "global", s->name, s->type, s->desc); // list available key sections } else if(strcmp(args, "keys") == 0 && !sec) { ui_m(NULL, 0, "\nAvailable sections:"); const doc_key_t *k = doc_keys; for(; k->sect; k++) ui_mf(NULL, 0, " %s - %s", k->sect, k->title); ui_m(NULL, 0, "\nUse `/help keys ' to get help on the key bindings for the selected section.\n"); // get information on a particular key section } else if(strcmp(args, "keys") == 0 && sec) { const doc_key_t *k = doc_keys; for(; k->sect; k++) if(strcmp(k->sect, sec) == 0) break; if(!k->sect) ui_mf(NULL, 0, "\nUnknown keys section '%s'.", sec); else ui_mf(NULL, 0, "\nKey bindings for: %s - %s.\n\n%s\n", k->sect, k->title, k->desc); // get information on a particular command } else if(!sec) { if(*args == '/') args++; cmd_t *c = getcmd(args); if(!c) ui_mf(NULL, 0, "\nUnknown command '%s'.", args); else { doc_cmd_t *d = getdoc(c); ui_mf(NULL, 0, "\nUsage: /%s %s\n %s\n", c->name, d->args ? d->args : "", d->sum); if(d->desc) ui_mf(NULL, 0, "%s\n", d->desc); } } else ui_mf(NULL, 0, "\nUnknown help section `%s'.", args); } static void c_help_sug(char *args, char **sug) { // help h?set .. if(strncmp(args, "set ", 4) == 0 || strncmp(args, "hset ", 5) == 0) { char *sec = args + (*args == 'h' ? 5 : 4); int i, n=0, len = strlen(sec); for(i=0; isect; k++) if(strncmp(k->sect, args+5, len) == 0 && strlen(k->sect) != len) sug[i++] = g_strdup(k->sect); strv_prefix(sug, "keys ", NULL); return; } // help command int i = 0, len = strlen(args); gboolean ckeys = FALSE; cmd_t *c; for(c=cmds; i<20 && c->f; c++) { // Somehow merge "keys" into the list if(!ckeys && strcmp(c->name, "keys") > 0) { if(strncmp("keys", args, len) == 0 && len != 4) sug[i++] = g_strdup("keys"); ckeys = TRUE; } if(i < 20 && strncmp(c->name, args, len) == 0 && strlen(c->name) != len) sug[i++] = g_strdup(c->name); } } static gboolean c_connect_set_hubaddr(char *addr) { // Validate and parse yuri_t uri; if(yuri_parse(addr, &uri) != 0) { ui_m(NULL, 0, "Invalid URL format."); return FALSE; } if(!*uri.scheme) uri.scheme = "dchub"; if(!uri.port) uri.port = 411; if(strcmp(uri.scheme, "dchub") != 0 && strcmp(uri.scheme, "nmdc") != 0 && strcmp(uri.scheme, "nmdcs") != 0 && strcmp(uri.scheme, "adc") != 0 && strcmp(uri.scheme, "adcs") != 0) { ui_m(NULL, 0, "Unrecognized scheme."); return FALSE; } // Get kp from the query string char *kp = NULL, *key, *value; while(!kp && yuri_query_parse(&uri.query, &key, &value)) { if(strcmp(key, "kp") != 0) continue; if(strncmp(value, "SHA256/", 7) != 0 || strlen(value+7) != 52 || !isbase32(value+7)) { ui_m(NULL, 0, "Invalid keyprint."); return FALSE; } kp = value+7; if(strcmp(uri.scheme, "nmdcs") != 0 && strcmp(uri.scheme, "adcs") != 0) { ui_m(NULL, 0, "Keyprint is only valid for adcs:// or nmdcs:// URLs."); return FALSE; } } ui_tab_t *tab = ui_tab_cur->data; char *old = g_strdup(var_get(tab->hub->id, VAR_hubaddr)); // Reconstruct (without the kp) and save char *new = g_strdup_printf( uri.hosttype == YURI_IPV6 ? "%s://[%s]:%d" : "%s://%s:%d/", uri.scheme, uri.host, (int)uri.port); var_set(tab->hub->id, VAR_hubaddr, new, NULL); // Save kp if specified, or throw it away if the URL changed if(kp) var_set(tab->hub->id, VAR_hubkp, kp, NULL); else if(old && strcmp(old, new) != 0) var_set(tab->hub->id, VAR_hubkp, NULL, NULL); g_free(old); g_free(new); return TRUE; } static void c_connect(char *args) { ui_tab_t *tab = ui_tab_cur->data; if(tab->type != uit_hub) ui_m(NULL, 0, "This command can only be used on hub tabs."); else if(!net_is_idle(tab->hub->net)) ui_m(NULL, 0, "Already connected (or connecting). You may want to /disconnect first."); else { if(args[0] && !c_connect_set_hubaddr(args)) ; else if(!var_get(tab->hub->id, VAR_hubaddr)) ui_m(NULL, 0, "No hub address configured. Use '/connect
' to do so."); else hub_connect(tab->hub); } } // only autocompletes "dchub://" or the hubaddr, when set static void c_connect_sug(char *args, char **sug) { ui_tab_t *t = ui_tab_cur->data; if(t->type != uit_hub) return; int i = 0, len = strlen(args); char *addr = var_get(t->hub->id, VAR_hubaddr); if(addr && strncmp(addr, args, len) == 0) sug[i++] = g_strdup(addr); else if(addr) { char *naddr = g_strconcat("dchub://", addr, "/", NULL); if(strncmp(naddr, args, len) == 0) sug[i++] = naddr; else g_free(naddr); } if(strncmp("dchub://", args, len) == 0) sug[i++] = g_strdup("dchub://"); } static void c_disconnect(char *args) { ui_tab_t *tab = ui_tab_cur->data; if(args[0]) ui_m(NULL, 0, "This command does not accept any arguments."); else if(tab->type == uit_hub) { if(net_is_idle(tab->hub->net) && !tab->hub->reconnect_timer) ui_m(NULL, 0, "Not connected."); else hub_disconnect(tab->hub, FALSE); } else if(tab->type == uit_main) { ui_m(NULL, 0, "Disconnecting all hubs."); GHashTableIter i; hub_t *h = NULL; g_hash_table_iter_init(&i, hubs); while(g_hash_table_iter_next(&i, NULL, (gpointer *)&h)) if(!net_is_idle(h->net) || h->reconnect_timer) hub_disconnect(h, FALSE); } else ui_m(NULL, 0, "This command can only be used on the main tab or on hub tabs."); } static void c_reconnect(char *args) { ui_tab_t *tab = ui_tab_cur->data; if(args[0]) ui_m(NULL, 0, "This command does not accept any arguments."); else if(tab->type == uit_hub) { if(!net_is_idle(tab->hub->net) || tab->hub->reconnect_timer) hub_disconnect(tab->hub, FALSE); c_connect(""); // also checks for the existence of "hubaddr" } else if(tab->type == uit_main) { // TODO: This code is ugly, it shouldn't depend on ui_tabs at all. ui_m(NULL, 0, "Reconnecting all hubs."); GList *n = ui_tabs; for(; n; n=n->next) { tab = n->data; if(tab->type != uit_hub) continue; if(!net_is_idle(tab->hub->net)|| tab->hub->reconnect_timer) hub_disconnect(tab->hub, FALSE); ui_tab_cur = n; c_connect(""); } ui_tab_cur = g_list_find(ui_tabs, uit_main_tab); } else ui_m(NULL, 0, "This command can only be used on the main tab or on hub tabs."); } static void c_accept(char *args) { ui_tab_t *tab = ui_tab_cur->data; if(args[0]) ui_m(NULL, 0, "This command does not accept any arguments."); else if(tab->type != uit_hub) ui_m(NULL, 0, "This command can only be used on hub tabs."); else if(!tab->hub->kp) ui_m(NULL, 0, "Nothing to accept."); else { char enc[53] = {}; base32_encode_dat(tab->hub->kp, enc, 32); var_set(tab->hub->id, VAR_hubkp, enc, NULL); g_slice_free1(32, tab->hub->kp); tab->hub->kp = NULL; hub_connect(tab->hub); } } static void c_open_list() { char **hubs = db_vars_hubs(); if(!*hubs) { ui_m(NULL, 0, "No hubs found in the configuration data."); } else { ui_m(NULL, 0, ""); char **hub; for(hub=hubs; *hub; hub++) ui_mf(NULL, 0, "%20s %s", *hub, var_get(db_vars_hubid(*hub), VAR_hubaddr)); ui_m(NULL, 0, ""); } g_strfreev(hubs); } static void c_open(char *args) { if(!*args) { c_open_list(); return; } gboolean conn = TRUE; if(strncmp(args, "-n ", 3) == 0) { conn = FALSE; args += 3; g_strstrip(args); } char *name = args, *addr = strchr(args, ' '); if(name[0] == '#') name++; if(addr) *(addr++) = 0; if(!name[0]) { ui_m(NULL, 0, "No hub name given."); return; } if(!str_is_valid_hubname(name)) ui_m(NULL, 0, "Sorry, hub name may only consist of alphanumeric characters, and must not exceed 25 characters."); else { // Look for existing tab GList *n; for(n=ui_tabs; n; n=n->next) { char *tmp = ((ui_tab_t *)n->data)->name; if(tmp[0] == '#' && strcmp(tmp+1, name) == 0) break; } // Open or select tab if(!n) { ui_tab_t *tab = uit_hub_create(name, addr ? FALSE : conn); ui_tab_open(tab, TRUE, NULL); listen_refresh(); } else if(n != ui_tab_cur) ui_tab_cur = n; else ui_m(NULL, 0, addr ? "Tab already selected, saving new address instead." : "Tab already selected."); // Save address and (re)connect when necessary if(addr && c_connect_set_hubaddr(addr) && conn) c_reconnect(""); } } // Suggests hub names, also used for /delhub static void c_open_sug(char *args, char **sug) { int len = strlen(args); int i = 0; char **hub, **hubs = db_vars_hubs(); for(hub=hubs; i<20 && *hub; hub++) if((strncmp(args, *hub, len) == 0 || strncmp(args, *hub+1, len) == 0) && strlen(*hub) != len) sug[i++] = g_strdup(*hub); g_strfreev(hubs); } static void c_close(char *args) { if(args[0]) { ui_m(NULL, 0, "This command does not accept any arguments."); return; } ui_tab_t *tab = ui_tab_cur->data; tab->type->close(tab); } static void c_clear(char *args) { ui_tab_t *tab = ui_tab_cur->data; if(args[0]) ui_m(NULL, 0, "This command does not accept any arguments."); else if(tab->log) ui_logwindow_clear(tab->log); } static void c_userlist(char *args) { ui_tab_t *tab = ui_tab_cur->data; if(args[0]) ui_m(NULL, 0, "This command does not accept any arguments."); else if(tab->type != uit_hub) ui_m(NULL, 0, "This command can only be used on hub tabs."); else uit_userlist_open(tab->hub, 0, NULL, FALSE); } static void listshares() { db_share_item_t *l = db_share_list(); if(!l->name) ui_m(NULL, 0, "Nothing shared."); else { ui_m(NULL, 0, ""); for(; l->name; l++) { fl_list_t *fl = fl_local_list ? fl_list_file(fl_local_list, l->name) : NULL; ui_mf(NULL, 0, " /%s -> %s (%s)", l->name, l->path, fl ? str_formatsize(fl->size) : "-"); } ui_m(NULL, 0, ""); } } static void c_share(char *args) { if(!args[0]) { listshares(); return; } char *first, *second; str_arg2_split(args, &first, &second); if(!first || !first[0] || !second || !second[0]) ui_m(NULL, 0, "Error parsing arguments. See \"/help share\" for details."); else if(db_share_path(first)) ui_m(NULL, 0, "You have already shared a directory with that name."); else { char *fpath = g_filename_from_utf8(second, -1, NULL, NULL, NULL); char *rpath = fpath ? path_expand(fpath) : NULL; char *path = rpath ? g_filename_to_utf8(rpath, -1, NULL, NULL, NULL) : NULL; char *tmp; for(tmp = first; *tmp; tmp++) if(*tmp == '/' || *tmp == '\\') break; if(*tmp) ui_m(NULL, 0, "Invalid character in share name."); else if(!path) ui_mf(NULL, 0, "Error obtaining absolute path: %s", g_strerror(errno)); else if(!g_file_test(rpath, G_FILE_TEST_IS_DIR)) ui_m(NULL, 0, "Not a directory."); else { // Check whether it (or a subdirectory) is already shared db_share_item_t *l = db_share_list(); int plen = strlen(path); for(; l->name; l++) { int llen = strlen(l->path); if(strncmp(l->path, path, MIN(llen, plen)) == 0 && (llen > plen ? !l->path[plen] || l->path[plen] == '/' : !path[llen] || path[llen] == '/')) break; } if(l->name) ui_mf(NULL, 0, "Directory already (partly) shared in /%s", l->name); else { db_share_add(first, path); fl_share(first); ui_mf(NULL, 0, "Added to share: /%s -> %s", first, path); } } g_free(fpath); g_free(rpath); g_free(path); } g_free(first); } static void c_share_sug(char *args, char **sug) { char *first, *second; str_arg2_split(args, &first, &second); g_free(first); if(!first || !second) return; // we want the escaped first part first = g_strndup(args, second-args); path_suggest(second, sug); strv_prefix(sug, first, NULL); g_free(first); } static void c_unshare(char *args) { if(!args[0]) { listshares(); return; // otherwise we may crash } else if(fl_is_refreshing()) { ui_m(NULL, 0, "Sorry, can't remove directories from the share while refreshing."); return; } while(args[0] == '/') args++; // Remove everything if(!args[0]) { db_share_rm(NULL); fl_unshare(NULL); ui_m(NULL, 0, "Removed all directories from share."); // Remove a single dir } else { const char *path = db_share_path(args); if(!path) ui_m(NULL, 0, "No shared directory with that name."); else { ui_mf(NULL, 0, "Directory /%s (%s) removed from share.", args, path); db_share_rm(args); fl_unshare(args); } } } static void c_unshare_sug(char *args, char **sug) { int len = strlen(args), i = 0; if(args[0] == '/') args++; db_share_item_t *l = db_share_list(); for(; l->name; l++) if(strncmp(args, l->name, len) == 0 && strlen(l->name) != len) sug[i++] = g_strdup(l->name); } static void c_refresh(char *args) { fl_list_t *n = fl_local_from_path(args); if(!n) ui_mf(NULL, 0, "Directory `%s' not found.", args); else if(n->isfile) ui_mf(NULL, 0, "Can't refresh a single file, please provide a directory."); else fl_refresh(n); } static void nick_sug(char *args, char **sug, gboolean append) { ui_tab_t *t = ui_tab_cur->data; if(!t->hub) return; // get starting point of the nick char *nick = args+strlen(args); while(nick > args && *(nick-1) != ' ' && *(nick-1) != ',' && *(nick-1) != ':') nick--; hub_user_suggest(t->hub, nick, sug); // optionally append ": " after the nick if(append && nick == args) { char **n; for(n=sug; *n; n++) { char *tmp = *n; *n = g_strdup_printf("%s: ", tmp); g_free(tmp); } } // prefix *nick = 0; if(*args) strv_prefix(sug, args, NULL); } // also used for c_me static void c_say_sug(char *args, char **sug) { nick_sug(args, sug, TRUE); } // also used for c_whois, c_grant, c_kick and c_browse static void c_msg_sug(char *args, char **sug) { nick_sug(args, sug, FALSE); } static void c_version(char *args) { if(args[0]) ui_m(NULL, 0, "This command does not accept any arguments."); else ui_mf(NULL, 0, "\n%s\n", ncdc_version()); } static void c_connections(char *args) { if(args[0]) ui_m(NULL, 0, "This command does not accept any arguments."); else uit_conn_open(NULL, NULL); } static void c_queue(char *args) { if(args[0]) ui_m(NULL, 0, "This command does not accept any arguments."); else uit_dl_open(NULL, 0, NULL); } static void c_gc(char *args) { if(args[0]) ui_m(NULL, 0, "This command does not accept any arguments."); else { ui_m(NULL, UIM_NOLOG, "Collecting garbage..."); ui_draw(); if(!fl_gc()) ui_m(NULL, 0, "Not checking for unused hash data: File list refresh in progress or not performed yet."); db_fl_purgedata(); dl_fl_clean(NULL); dl_inc_clean(); db_vacuum(); ui_m(NULL, UIM_NOLOG, NULL); ui_m(NULL, 0, "Garbage-collection done."); } } static void c_whois(char *args) { ui_tab_t *tab = ui_tab_cur->data; char *u = NULL; guint64 uid = 0; gboolean utf8 = TRUE; if(tab->type != uit_hub && tab->type != uit_msg) ui_m(NULL, 0, "This command can only be used on hub and message tabs."); else if(!args[0] && tab->type != uit_msg) ui_m(NULL, 0, "No user specified. See `/help whois' for more information."); else if(tab->type == uit_msg) { if(args[0]) u = args; else uid = uit_msg_uid(tab); tab = tab->hub->tab; } else u = args; if((u || uid) && !uit_userlist_open(tab->hub, uid, u, utf8)) ui_m(NULL, 0, "No user found with that name."); } static void listgrants() { db_user_t **list = db_users_list(); int n = 0; db_user_t **i = list; for(; *i; i++) { if(!(i[0]->flags & DB_USERFLAG_GRANT)) continue; if(!n++) ui_m(NULL, 0, "\nGranted slots to:"); hub_t *hub = hub_global_byid(i[0]->hub); ui_mf(NULL, 0, " %s on %s%s", i[0]->nick, db_vars_get(i[0]->hub, "hubname"), !hub || !hub_user_get(hub, i[0]->nick) ? " (user offline)" : ""); } if(n) ui_m(NULL, 0, ""); else ui_m(NULL, 0, "No slots granted to anyone."); g_free(list); } static void c_grant(char *args) { ui_tab_t *tab = ui_tab_cur->data; hub_user_t *u = NULL; if((!*args && tab->type != uit_msg) || strcmp(args, "-list") == 0) listgrants(); else if(tab->type != uit_hub && tab->type != uit_msg) ui_m(NULL, 0, "This command can only be used on hub and message tabs."); else if(args[0]) { u = hub_user_get(tab->hub, args); if(!u) ui_m(NULL, 0, "No user found with that name."); } else { guint64 uid = uit_msg_uid(tab); u = g_hash_table_lookup(hub_uids, &uid); if(!u) ui_m(NULL, 0, "User not online."); } if(u) { db_users_set(u->hub->id, u->uid, u->name, db_users_get(u->hub->id, u->name) | DB_USERFLAG_GRANT); ui_m(NULL, 0, "Slot granted."); } } // TODO: Allow ungranting users on hubs that aren't open static void c_ungrant(char *args) { ui_tab_t *tab = ui_tab_cur->data; if(!*args && tab->type != uit_msg) { listgrants(); return; } if(!tab->hub) { ui_m(NULL, 0, "This command can only be used on hub tabs."); return; } char *nick = !*args && tab->type == uit_msg ? tab->name+1 : args; if(db_users_get(tab->hub->id, nick) & DB_USERFLAG_GRANT) { db_users_rm(tab->hub->id, nick); ui_mf(NULL, 0, "Slot for `%s' revoked.", nick); } else ui_mf(NULL, 0, "No slot granted to `%s' on this hub.", nick); } static void c_ungrant_sug(char *args, char **sug) { ui_tab_t *tab = ui_tab_cur->data; int len = strlen(args); db_user_t **list = db_users_list(); db_user_t **i = list; int n = 0; for(; n<20 && *i; i++) { if(!tab->hub || i[0]->hub != tab->hub->id) continue; if(strncasecmp(i[0]->nick, args, len) == 0) sug[n++] = g_strdup(i[0]->nick); } g_free(list); } static void c_password(char *args) { ui_tab_t *tab = ui_tab_cur->data; if(tab->type != uit_hub) ui_m(NULL, 0, "This command can only be used on hub tabs."); else if(!net_is_connected(tab->hub->net)) ui_m(NULL, 0, "Not connected to a hub. Did you want to use '/hset password' instead?"); else if(tab->hub->nick_valid) ui_m(NULL, 0, "Already logged in. Did you want to use '/hset password' instead?"); else hub_password(tab->hub, args); } static void c_kick(char *args) { ui_tab_t *tab = ui_tab_cur->data; if(tab->type != uit_hub) ui_m(NULL, 0, "This command can only be used on hub tabs."); else if(!tab->hub->nick_valid) ui_m(NULL, 0, "Not connected or logged in yet."); else if(!args[0]) ui_m(NULL, 0, "No user specified."); else if(tab->hub->adc) ui_m(NULL, 0, "This command only works on NMDC hubs."); else { hub_user_t *u = hub_user_get(tab->hub, args); if(!u) ui_m(NULL, 0, "No user found with that name."); else hub_kick(tab->hub, u); } } static void c_nick(char *args) { ui_tab_t *tab = ui_tab_cur->data; guint64 hub = tab->type == uit_hub || tab->type == uit_msg ? tab->hub->id : 0; int v = vars_byname("nick"); g_return_if_fail(v >= 0); GError *err = NULL; char *r = vars[v].parse(args, &err); if(!r || !var_set(hub, v, r, &err)) { ui_mf(NULL, 0, "Error changing nick: %s", err->message); g_free(r); return; } g_free(r); ui_mf(NULL, 0, "Nick changed."); } static void c_browse(char *args) { ui_tab_t *tab = ui_tab_cur->data; hub_user_t *u = NULL; gboolean force = FALSE; if(!args[0] && !fl_local_list) { ui_m(NULL, 0, "Nothing shared."); return; } else if(args[0]) { if(tab->type != uit_hub && tab->type != uit_msg) { ui_m(NULL, 0, "This command can only be used on hub and message tabs."); return; } char *sep = strchr(args, ' '); if(sep) { *(sep++) = 0; g_strstrip(sep); if(strcmp(args, "-f") == 0) { args = sep; force = TRUE; } else if(strcmp(sep, "-f") == 0) force = TRUE; } u = hub_user_get(tab->hub, args); if(!u) { ui_m(NULL, 0, "No user found with that name."); return; } } uit_fl_queue(u ? u->uid : 0, force, NULL, !args[0] ? NULL : tab, TRUE, FALSE); } static void c_search(char *args) { // Split arguments char **argv; int argc; GError *err = NULL; if(!g_shell_parse_argv(args, &argc, &argv, &err)) { ui_mf(NULL, 0, "Error parsing arguments: %s", err->message); g_error_free(err); return; } // Create basic query gboolean allhubs = FALSE; gboolean stoparg = FALSE; int qlen = 0; search_q_t *q = g_slice_new0(search_q_t); q->query = g_new0(char *, argc+1); q->type = 1; // Loop through arguments. (Later arguments overwrite earlier ones) int i; for(i=0; iquery[qlen++] = g_strdup(argv[i]); // -- else if(strcmp(argv[i], "--") == 0) stoparg = TRUE; // -hub, -all else if(strcmp(argv[i], "-hub") == 0) allhubs = FALSE; else if(strcmp(argv[i], "-all") == 0) allhubs = TRUE; // -le, -ge else if(strcmp(argv[i], "-le") == 0 || strcmp(argv[i], "-ge") == 0) { q->ge = strcmp(argv[i], "-ge") == 0; if(++i >= argc) { ui_mf(NULL, 0, "Option `%s' expects an argument.", argv[i-1]); goto c_search_clean; } q->size = str_parsesize(argv[i]); if(q->size == G_MAXUINT64) { ui_mf(NULL, 0, "Invalid size argument for option `%s'.", argv[i-1]); goto c_search_clean; } // -t } else if(strcmp(argv[i], "-t") == 0) { if(++i >= argc) { ui_mf(NULL, 0, "Option `%s' expects an argument.", argv[i-1]); goto c_search_clean; } if('1' <= argv[i][0] && argv[i][0] <= '8' && !argv[i][1]) q->type = argv[i][0]-'0'; else if(strcmp(argv[i], "any") == 0) q->type = 1; else if(strcmp(argv[i], "audio") == 0) q->type = 2; else if(strcmp(argv[i], "archive") == 0) q->type = 3; else if(strcmp(argv[i], "doc") == 0) q->type = 4; else if(strcmp(argv[i], "exe") == 0) q->type = 5; else if(strcmp(argv[i], "img") == 0) q->type = 6; else if(strcmp(argv[i], "video") == 0) q->type = 7; else if(strcmp(argv[i], "dir") == 0) q->type = 8; else { ui_mf(NULL, 0, "Unknown argument for option `%s'.", argv[i-1]); goto c_search_clean; } // -tth } else if(strcmp(argv[i], "-tth") == 0) { if(++i >= argc) { ui_mf(NULL, 0, "Option `%s' expects an argument.", argv[i-1]); goto c_search_clean; } if(!istth(argv[i])) { ui_m(NULL, 0, "Invalid TTH root."); goto c_search_clean; } q->type = 9; base32_decode(argv[i], q->tth); // oops } else { ui_mf(NULL, 0, "Unknown option: %s", argv[i]); goto c_search_clean; } } // validate & send ui_tab_t *tab = ui_tab_cur->data; if(!allhubs && tab->type != uit_hub && tab->type != uit_msg) { ui_m(NULL, 0, "This command can only be used on hub tabs. Use the `-all' option to search on all connected hubs."); goto c_search_clean; } ui_tab_t *rtab = uit_search_create(allhubs ? NULL : tab->hub, q, &err); q = NULL; // make sure to not free it if(err) { ui_mf(NULL, 0, "%s%s", rtab ? "Warning: " : "", err->message); g_error_free(err); } if(rtab) ui_tab_open(rtab, TRUE, allhubs ? NULL : tab); c_search_clean: g_strfreev(argv); search_q_free(q); } #define print_var(hub, hubname, var) do {\ gboolean glob = hub && vars[var].global && !(vars[var].getraw ? vars[var].getraw(hub, vars[var].name) : db_vars_get(hub, vars[var].name));\ char *raw = var_get(hub, var);\ if(!raw)\ ui_mf(NULL, 0, "%s.%s is not set.", hubname, vars[var].name);\ else {\ char *fmt = vars[var].format(raw);\ ui_mf(NULL, 0, "%s.%s = %s%s", hubname, vars[var].name, fmt, glob?" (global)":"");\ g_free(fmt);\ }\ } while(0) #define check_var(hub, var, key, unset) do {\ if(var < 0 || (!vars[var].global && !vars[var].hub)) {\ ui_mf(NULL, 0, "No setting with the name '%s'.", key);\ return;\ }\ if(hub ? !vars[var].hub : !vars[var].global) {\ ui_mf(NULL, 0,\ hub ? "`%s' is a global setting, did you mean to use /%s instead?"\ : "'%s' is a hub setting, did you mean to use /%s instead?",\ vars[var].name, !hub ? (unset ? "hunset" : "hset") : unset ? "unset" : "set");\ return;\ }\ } while(0) #define hubandhubname(h) \ guint64 hub = 0;\ char *hubname = "global";\ if(h) {\ ui_tab_t *tab = ui_tab_cur->data;\ if(tab->type != uit_hub && tab->type != uit_msg) {\ ui_m(NULL, 0, "This command can only be used on hub tabs.");\ return;\ }\ hub = tab->hub->id;\ hubname = tab->name;\ } static gboolean listsettings(guint64 hub, const char *hubname, const char *key) { int i, n = 0; char *pat = !key || !*key ? NULL : key[strlen(key)-1] == '*' || key[strlen(key)-1] == '?' ? g_strdup(key) : g_strconcat(key, "*", NULL); GPatternSpec *p = pat ? g_pattern_spec_new(pat) : NULL; g_free(pat); for(i=0; imessage); g_error_free(err); return; } var_set(hub, var, raw, &err); g_free(raw); if(err) { ui_mf(NULL, 0, "Error setting `%s': %s", vars[var].name, err->message); g_error_free(err); } else print_var(hub, hubname, var); } } static void c_set(char *args) { sethset(FALSE, args); } static void c_hset(char *args) { sethset(TRUE, args); } // Implements /unset and /hunset static void unsethunset(gboolean h, const char *key) { hubandhubname(h); // Get var, optionally list, and check whether it can be used in this context int var = *key ? vars_byname(key) : -1; if(var < 0 && listsettings(hub, hubname, key)) return; check_var(hub, var, key, TRUE); GError *err = NULL; // Unset var_set(hub, var, NULL, &err); if(err) { ui_mf(NULL, 0, "Error resetting `%s': %s", vars[var].name, err->message); g_error_free(err); } else ui_mf(NULL, 0, "%s.%s reset.", hubname, vars[var].name); } static void c_unset(char *args) { unsethunset(FALSE, args); } static void c_hunset(char *args) { unsethunset(TRUE, args); } // Implementents suggestions for /h?(un)?set static void setunset_sug(gboolean set, gboolean h, const char *val, char **sug) { guint64 hub = 0; if(h) { ui_tab_t *tab = ui_tab_cur->data; if(tab->type != uit_hub && tab->type != uit_msg) return; hub = tab->hub->id; } char *sep = strchr(val, ' '); // Suggest var name if(!set || !sep) { int len = strlen(val); int i, n = 0; for(i=0; i= 0 && vars[var].sug) { vars[var].sug(var_get(hub, var), sep, sug); strv_prefix(sug, val, " ", NULL); } } static void c_set_sug(char *args, char **sug) { setunset_sug(TRUE, FALSE, args, sug); } static void c_hset_sug(char *args, char **sug) { setunset_sug(TRUE, TRUE, args, sug); } static void c_unset_sug(char *args, char **sug) { setunset_sug(FALSE, FALSE, args, sug); } static void c_hunset_sug(char *args, char **sug) { setunset_sug(FALSE, TRUE, args, sug); } static void c_listen(char *args) { if(args[0]) { ui_m(NULL, 0, "This command does not accept any arguments."); return; } // TODO: If we're currently passive because of an error, might want to give // an overview of the configuration rather than this unhelpful "not active" // message. if(!listen_binds) { ui_m(NULL, 0, "Not active on any hub - no listening sockets enabled."); return; } ui_m(NULL, 0, ""); ui_m(NULL, 0, "Currently opened ports:"); // TODO: sort the listen_binds and ->hubs lists GList *l; for(l=listen_binds; l; l=l->next) { listen_bind_t *b = l->data; GString *h = g_string_new(""); GSList *n; for(n=b->hubs; n; n=n->next) { hub_t *hub = hub_global_byid(((listen_hub_bind_t *)n->data)->hubid); if(hub) { if(h->len > 0) g_string_append(h, ", "); g_string_append(h, hub->tab->name); } } ui_mf(NULL, 0, " %s (%s): %s", listen_bind_ipport(b), LBT_STR(b->type), h->str); g_string_free(h, TRUE); } ui_m(NULL, 0, ""); } static void c_delhub(char *args) { if(args[0] == '#') args++; if(!args[0]) { ui_m(NULL, 0, "No hub name given."); return; } guint64 id = db_vars_hubid(args); if(!id) { ui_m(NULL, 0, "No hub found by that name."); return; } hub_t *hub = hub_global_byid(id); if(hub) { ui_m(NULL, 0, "Hub tab still open. Please close the hub tab before removing it from the configuration."); return; } db_vars_rmhub(id); db_users_rmhub(id); ui_mf(NULL, 0, "Hub #%s deleted from the configuration.", args); } // definition of the command list static cmd_t cmds[] = { { "accept", c_accept, NULL }, { "browse", c_browse, c_msg_sug }, { "clear", c_clear, NULL }, { "close", c_close, NULL }, { "connect", c_connect, c_connect_sug }, { "connections", c_connections, NULL }, { "delhub", c_delhub, c_open_sug, }, { "disconnect", c_disconnect, NULL }, { "gc", c_gc, NULL }, { "grant", c_grant, c_msg_sug }, { "help", c_help, c_help_sug }, { "hset", c_hset, c_hset_sug }, { "hunset", c_hunset, c_hunset_sug }, { "kick", c_kick, c_msg_sug }, { "listen", c_listen, NULL }, { "me", c_me, c_say_sug }, { "msg", c_msg, c_msg_sug }, { "nick", c_nick, NULL }, { "open", c_open, c_open_sug }, { "password", c_password, NULL }, { "pm", c_msg, c_msg_sug }, { "queue", c_queue, NULL }, { "quit", c_quit, NULL }, { "reconnect", c_reconnect, NULL }, { "refresh", c_refresh, fl_local_suggest }, { "say", c_say, c_say_sug }, { "search", c_search, NULL }, { "set", c_set, c_set_sug }, { "share", c_share, c_share_sug }, { "ungrant", c_ungrant, c_ungrant_sug }, { "unset", c_unset, c_unset_sug }, { "unshare", c_unshare, c_unshare_sug }, { "userlist", c_userlist, NULL }, { "version", c_version, NULL }, { "whois", c_whois, c_msg_sug }, { "" } }; void cmd_handle(char *ostr) { // special case: ignore empty commands if(strspn(ostr, " \t") == strlen(ostr)) return; char *str = g_strdup(ostr); if(!str || !str[0]) { g_free(str); return; } // extract the command from the string char *cmd = str, *args; while(cmd[0] == ' ') cmd++; // not a command, imply '/say ' if(cmd[0] != '/') { cmd = "say"; args = str; // it is a command, extract cmd and args } else { cmd++; if(strchr(cmd, '\n')) { g_free(str); ui_m(NULL, 0, "Commands cannot contain newlines"); return; } char *sep = strchr(cmd, ' '); if(sep) *sep = 0; args = sep ? sep+1 : str+strlen(str); } // Strip whitespace around the argument, unless this is the /say command if(strcmp(cmd, "say") != 0) g_strstrip(args); // execute command when found, generate an error otherwise cmd_t *c = getcmd(cmd); if(c) c->f(args); else ui_mf(NULL, 0, "Unknown command '%s'.", cmd); g_free(str); } void cmd_suggest(char *ostr, char **sug) { cmd_t *c; char *str = g_strdup(ostr); // complete command name if(str[0] == '/' && !strchr(str, ' ')) { int i = 0; int len = strlen(str)-1; for(c=cmds; i<20 && c->f; c++) if(strncmp(str+1, c->name, len) == 0) sug[i++] = g_strconcat("/", c->name, " ", NULL); } else { if(str[0] != '/') getcmd("say")->suggest(str, sug); else { char *sep = strchr(str, ' '); *sep = 0; c = getcmd(str+1); if(c && c->suggest) { c->suggest(sep+1, sug); strv_prefix(sug, str, " ", NULL); } } } g_free(str); } ncdc-1.23.1/src/uit_hub.c0000644000175000017500000001620414245144602012025 00000000000000/* ncdc - NCurses Direct Connect client Copyright (c) 2011-2022 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "ncdc.h" #include "uit_hub.h" ui_tab_type_t uit_hub[1]; typedef struct tab_t { ui_tab_t tab; GRegex *highlight; char *nick; ui_tab_t *userlist; } tab_t; #if INTERFACE // Change types for uit_hub_userchange(). Also used for // uit_userlist_userchange() and uit_msg_userchange(). #define UIHUB_UC_JOIN 0 #define UIHUB_UC_QUIT 1 #define UIHUB_UC_NFO 2 #endif // Also used for the MSG tab int uit_hub_log_checkchat(void *dat, char *nick, char *msg) { tab_t *t = dat; if(!t->nick) return 0; if(strcmp(nick, t->nick) == 0) return 2; if(!t->highlight) return 0; return g_regex_match(t->highlight, msg, 0, NULL) ? 1 : 0; } ui_tab_t *uit_hub_create(const char *name, gboolean conn) { tab_t *t = g_new0(tab_t, 1); t->tab.name = g_strdup_printf("#%s", name); t->tab.type = uit_hub; t->tab.hub = hub_create((ui_tab_t *)t); t->tab.log = ui_logwindow_create( var_get_bool(t->tab.hub->id, VAR_log_hubchat) ? t->tab.name : NULL, var_get_int(t->tab.hub->id, VAR_backlog)); t->tab.log->handle = t; t->tab.log->checkchat = uit_hub_log_checkchat; // already used this name before? open connection again if(conn && var_get(t->tab.hub->id, VAR_hubaddr)) hub_connect(t->tab.hub); return (ui_tab_t *)t; } static void t_close(ui_tab_t *tab) { tab_t *t = (tab_t *)tab; // close the userlist tab if(t->userlist) t->userlist->type->close(t->userlist); // close msg tabs GList *n; for(n=ui_tabs; n;) { ui_tab_t *mt = n->data; n = n->next; if(mt->type == uit_msg && mt->hub == tab->hub) mt->type->close(mt); } ui_tab_remove(tab); hub_free(tab->hub); ui_logwindow_free(tab->log); g_free(t->nick); if(t->highlight) g_regex_unref(t->highlight); g_free(tab->name); g_free(tab); } static void t_draw(ui_tab_t *tab) { ui_logwindow_draw(tab->log, 1, 0, winrows-5, wincols); attron(UIC(separator)); mvhline(winrows-4, 0, ' ', wincols); if(net_is_connecting(tab->hub->net)) mvaddstr(winrows-4, wincols-15, "Connecting..."); else if(!net_is_connected(tab->hub->net)) mvaddstr(winrows-4, wincols-16, "Not connected."); else if(!tab->hub->nick_valid) mvaddstr(winrows-4, wincols-15, "Logging in..."); else { char *addr = var_get(tab->hub->id, VAR_hubaddr); char *conn = !listen_hub_active(tab->hub->id) ? g_strdup("[passive]") : g_strdup_printf("[active: %s]", hub_ip(tab->hub)); char *proto = tab->hub->adc ? "[adc]" : "[nmdc]"; char *tmp = g_strdup_printf("%s @ %s%s %s %s", tab->hub->nick, addr, tab->hub->isop ? " (operator)" : tab->hub->isreg ? " (registered)" : "", conn, proto); g_free(conn); mvaddstr(winrows-4, 0, tmp); g_free(tmp); int count = g_hash_table_size(tab->hub->users); tmp = g_strdup_printf("%6d users %10s%c", count, str_formatsize(tab->hub->sharesize), tab->hub->sharecount == count ? ' ' : '+'); mvaddstr(winrows-4, wincols-26, tmp); g_free(tmp); } attroff(UIC(separator)); mvaddstr(winrows-3, 0, tab->name); addstr("> "); int pos = str_columns(tab->name)+2; ui_textinput_draw(ui_global_textinput, winrows-3, pos, wincols-pos, NULL); } static char *t_title(ui_tab_t *tab) { return g_strdup_printf("%s: %s", tab->name, net_is_connecting(tab->hub->net) ? "Connecting..." : !net_is_connected(tab->hub->net) ? "Not connected." : !tab->hub->nick_valid ? "Logging in..." : tab->hub->hubname ? tab->hub->hubname : "Connected."); } static void t_key(ui_tab_t *tab, guint64 key) { char *str = NULL; if(!ui_logwindow_key(tab->log, key, winrows) && ui_textinput_key(ui_global_textinput, key, &str) && str) { cmd_handle(str); g_free(str); } else if(key == INPT_ALT('u')) uit_userlist_open(tab->hub, 0, NULL, FALSE); } // Called from hub.c when user information changes. void uit_hub_userchange(ui_tab_t *tab, int change, hub_user_t *user) { tab_t *t = (tab_t *)tab; // notify the userlist, when it is open if(t->userlist) uit_userlist_userchange(t->userlist, change, user); // notify any msg tab uit_msg_userchange(user, change); // display the join/quit, when requested gboolean log = var_get_bool(tab->hub->id, VAR_show_joinquit); if(change == UIHUB_UC_NFO && !user->isjoined) { user->isjoined = TRUE; if(log && tab->hub->joincomplete && (!tab->hub->nick_valid || (tab->hub->adc ? (tab->hub->sid != user->sid) : (strcmp(tab->hub->nick_hub, user->name_hub) != 0)))) ui_mf(tab, 0, "--> %s has joined.", user->name); } else if(change == UIHUB_UC_QUIT && log) ui_mf(tab, 0, "--< %s has quit.", user->name); } // Called when the hub is disconnected. Notifies any msg tabs and the userlist // tab, if there's one open. void uit_hub_disconnect(ui_tab_t *tab) { tab_t *t = (tab_t *)tab; // userlist if(t->userlist) uit_userlist_disconnect(t->userlist); // msg tabs uit_msg_disconnect(tab->hub); } // Called by hub.c when hub->nick is set or changed (Not called when hub->nick // is reset to NULL). A local hub_nick field is kept in the hub tab struct to // still provide highlighting for it after disconnecting from the hub. void uit_hub_setnick(ui_tab_t *tab) { tab_t *t = (tab_t *)tab; if(!tab->hub->nick) return; g_free(t->nick); if(t->highlight) g_regex_unref(t->highlight); t->nick = g_strdup(tab->hub->nick); char *name = g_regex_escape_string(tab->hub->nick, -1); char *pattern = g_strdup_printf("\\b%s\\b", name); t->highlight = g_regex_new(pattern, G_REGEX_CASELESS|G_REGEX_OPTIMIZE, 0, NULL); g_free(name); g_free(pattern); } // Called by ui_m() to check whether a chat message requires user attention. gboolean uit_hub_checkhighlight(ui_tab_t *tab, const char *msg) { tab_t *t = (tab_t *)tab; return t->highlight && g_regex_match(t->highlight, msg, 0, NULL); } // Called by uit_userlist void uit_hub_set_userlist(ui_tab_t *tab, ui_tab_t *list) { ((tab_t *)tab)->userlist = list; } ui_tab_t *uit_hub_userlist(ui_tab_t *tab) { return ((tab_t *)tab)->userlist; } ui_tab_type_t uit_hub[1] = { { t_draw, t_title, t_key, t_close } }; ncdc-1.23.1/src/main.c0000644000175000017500000003463314245144521011320 00000000000000/* ncdc - NCurses Direct Connect client Copyright (c) 2011-2022 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "ncdc.h" #include "main.h" // global variables const char *main_version = #include "version.h" ; GMainLoop *main_loop; // input handling declarations #if INTERFACE // macros to operate on key values #define INPT_KEY(code) (((guint64)0<<32) + (guint64)(code)) #define INPT_CHAR(code) (((guint64)1<<32) + (guint64)(code)) #define INPT_CTRL(code) (((guint64)2<<32) + (guint64)(code)) #define INPT_ALT(code) (((guint64)3<<32) + (guint64)(code)) #define INPT_CODE(key) ((gunichar)((key)&G_GUINT64_CONSTANT(0xFFFFFFFF))) #define INPT_TYPE(key) ((char)((key)>>32)) #define KEY_ESCAPE (KEY_MAX+1) #define KEY_BRACKETED_PASTE_START (KEY_ESCAPE+1) #define KEY_BRACKETED_PASTE_END (KEY_BRACKETED_PASTE_START+1) #endif #define ctrl_to_ascii(x) ((x) == 127 ? '?' : g_ascii_tolower((x)+64)) static void handle_input() { /* Mapping from get_wch() to input_key_t: * KEY_CODE_YES -> KEY(code) * KEY_CODE_NO: * char == 127 -> KEY(KEY_BACKSPACE) * char <= 31 -> CTRL(char) * !'^[' -> CHAR(char) * ('^[', !) -> KEY(KEY_ESCAPE) * ('^[', !CHAR) -> ignore both characters (1) * ('^[', CHAR && '[') -> ignore both characters and the character after that (2) * ('^[', CHAR && !'[') -> ALT(second char) * * 1. this is something like ctrl+alt+X, which we won't use * 2. these codes indicate a 'Key' that somehow wasn't captured with * KEY_CODE_YES. We won't attempt to interpret these ourselves. * * There are still several unhandled issues: * - Ncurses does not catch all key codes, and there is no way of knowing how * many bytes a key code spans. Things like ^[[1;3C won't be handled correctly. :-( * - Ncurses can actually return key codes > KEY_MAX, but does not provide * any mechanism for figuring out which key it actually was. * - It may be useful to use define_key() for some special (and common) codes * - Modifier keys will always be a problem. Most alt+key things work, except * for those that may start a control code. alt+[ is a famous one, but * there are others (like alt+O on my system). This is system-dependent, * and again we have no way of knowing these things. (except perhaps by * reading termcap entries on our own?) */ guint64 key; char buf[9]; int r; wint_t code; int lastesc = 0, curignore = 0; while((r = get_wch(&code)) != ERR) { if(curignore) { curignore = 0; continue; } // we use SIGWINCH, so KEY_RESIZE can be ignored if(r == KEY_CODE_YES && code == KEY_RESIZE) continue; // backspace (outside of an escape sequence) is often sent as DEL control character, correct this if(!lastesc && r != KEY_CODE_YES && code == 127) { r = KEY_CODE_YES; code = KEY_BACKSPACE; } // backspace inside an escape sequence is also possible, convert the other way around if(lastesc && r == KEY_CODE_YES && code == KEY_BACKSPACE) { r = !KEY_CODE_YES; code = 127; } key = r == KEY_CODE_YES ? INPT_KEY(code) : code == 27 ? INPT_ALT(0) : code <= 31 ? INPT_CTRL(ctrl_to_ascii(code)) : INPT_CHAR(code); // convert wchar_t into gunichar if(INPT_TYPE(key) == 1) { if((r = wctomb(buf, code)) < 0) g_warning("Cannot encode character 0x%X", code); buf[r] = 0; key = INPT_CHAR(g_utf8_get_char_validated(buf, -1)); if(INPT_CODE(key) == (gunichar)-1 || INPT_CODE(key) == (gunichar)-2) { g_warning("Invalid UTF-8 sequence in keyboard input. Are you sure you are running a UTF-8 locale?"); continue; } } // check for escape sequence if(lastesc) { lastesc = 0; if(INPT_TYPE(key) != 1) continue; if(INPT_CODE(key) == '[') { curignore = 1; continue; } key |= (guint64)3<<32; // a not very nice way of saying "turn this key into a INPT_ALT" ui_input(key); continue; } if(INPT_TYPE(key) == 3) { lastesc = 1; continue; } ui_input(key); } if(lastesc) ui_input(INPT_KEY(KEY_ESCAPE)); ui_draw(); } static gboolean stdin_read(GIOChannel *src, GIOCondition cond, gpointer dat) { handle_input(); return TRUE; } static gboolean one_second_timer(gpointer dat) { // TODO: ratecalc_calc() requires fairly precise timing, perhaps do this in a separate thread? ratecalc_calc(); // Detect day change static char pday[11] = ""; // YYYY-MM-DD char *cday = localtime_fmt("%F"); if(!pday[0]) strcpy(pday, cday); if(strcmp(cday, pday) != 0) { ui_daychange(cday); strcpy(pday, cday); } g_free(cday); // Disconnect offline users cc_global_onlinecheck(); // And draw the UI ui_draw(); return TRUE; } static gboolean screen_resized = FALSE; static gboolean screen_update_check(gpointer dat) { if(screen_resized) { endwin(); doupdate(); ui_draw(); screen_resized = FALSE; } else if(ui_checkupdate()) ui_draw(); return TRUE; } void ncdc_quit() { g_main_loop_quit(main_loop); } char *ncdc_version() { static GString *ver = NULL; static char *msg = "%s %s (built %s %s)\n" "Sendfile support: " #ifdef HAVE_SENDFILE "yes (%s)\n" #else "no\n" #endif "Libraries:\n" " GLib %d.%d.%d (%d.%d.%d)\n" " GnuTLS %s (%s)\n" " SQLite %s (%s)" #ifdef NCURSES_VERSION "\n ncurses %s" #endif ; if(ver) return ver->str; ver = g_string_new(""); g_string_printf(ver, msg, PACKAGE_NAME, main_version, __DATE__, __TIME__, #ifdef HAVE_LINUX_SENDFILE "Linux", #elif HAVE_BSD_SENDFILE "BSD", #endif GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION, GLIB_MICRO_VERSION, glib_major_version, glib_minor_version, glib_micro_version, GNUTLS_VERSION, gnutls_check_version(NULL), SQLITE_VERSION, sqlite3_libversion() #ifdef NCURSES_VERSION , NCURSES_VERSION #endif ); return ver->str; } static FILE *stderrlog; // redirect all non-fatal errors to the log static void log_redirect(const gchar *dom, GLogLevelFlags level, const gchar *msg, gpointer dat) { if(!(level & (G_LOG_LEVEL_INFO|G_LOG_LEVEL_DEBUG)) || (stderrlog != stderr && var_log_debug)) { char *ts = localtime_fmt("[%F %H:%M:%S %Z]"); fprintf(stderrlog, "%s *%s* %s\n", ts, loglevel_to_str(level), msg); g_free(ts); fflush(stderrlog); } } // clean-up our ncurses window before throwing a fatal error static void log_fatal(const gchar *dom, GLogLevelFlags level, const gchar *msg, gpointer dat) { endwin(); // print to both log file and stdout if(stderrlog != stderr) { fprintf(stderrlog, "\n\n*%s* %s\n", loglevel_to_str(level), msg); fflush(stderrlog); } printf("\n\n*%s* %s\n", loglevel_to_str(level), msg); } static void open_autoconnect() { char **hubs = db_vars_hubs(); char **hub; // TODO: make sure the tabs are opened in the same order as they were in the last run? for(hub=hubs; *hub; hub++) if(var_get_bool(db_vars_hubid(*hub), VAR_autoconnect)) ui_tab_open(uit_hub_create(*hub+1, TRUE), FALSE, NULL); listen_refresh(); g_strfreev(hubs); } // Fired when the screen is resized. Normally I would check for KEY_RESIZE, // but that doesn't work very nicely together with select(). See // http://www.webservertalk.com/archive107-2005-1-896232.html // Also note that this is a signal handler, and all functions we call here must // be re-entrant. Obviously none of the ncurses functions are, so let's set a // variable and handle it in the screen_update_check_timer. static void catch_sigwinch(int sig) { screen_resized = TRUE; } static void catch_sigpipe(int sig) { // Ignore. } // A special GSource to handle SIGTERM, SIGHUP and SIGUSR1 synchronously in the // main thread. This is done because the functions to control the glib event // loop are not re-entrant, and therefore cannot be called from signal // handlers. static gboolean main_sig_log = FALSE; static gboolean main_sig_quit = FALSE; static gboolean main_noterm = FALSE; static void catch_sigterm(int sig) { main_sig_quit = TRUE; } static void catch_sighup(int sig) { main_sig_quit = TRUE; main_noterm = TRUE; } // Re-open the log files when receiving SIGUSR1. static void catch_sigusr1(int sig) { main_sig_log = TRUE; } static gboolean sighandle_prepare(GSource *source, gint *timeout) { *timeout = -1; return main_sig_quit || main_sig_log; } static gboolean sighandle_check(GSource *source) { return main_sig_quit || main_sig_log; } static gboolean sighandle_dispatch(GSource *source, GSourceFunc callback, gpointer user_data) { return callback(NULL); } static GSourceFuncs sighandle_funcs = { sighandle_prepare, sighandle_check, sighandle_dispatch, NULL }; static gboolean sighandle_sourcefunc(gpointer dat) { if(main_sig_quit) { g_debug("%s received, terminating main loop.", main_noterm ? "SIGHUP" : "SIGTERM"); ncdc_quit(); main_sig_quit = FALSE; } if(main_sig_log) { logfile_global_reopen(); geoip_reinit(); main_sig_log = FALSE; } return TRUE; } // Commandline options static gboolean print_version(const gchar *name, const gchar *val, gpointer dat, GError **err) { puts(ncdc_version()); exit(0); } static gboolean auto_open = TRUE; static gboolean bracketed_paste = TRUE; static GOptionEntry cli_options[] = { { "version", 'v', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, print_version, "Print version and compilation information.", NULL }, { "session-dir", 'c', 0, G_OPTION_ARG_FILENAME, &db_dir, "Use a different session directory. Default: `$NCDC_DIR' or `$HOME/.ncdc'.", "" }, { "no-autoconnect", 'n', G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &auto_open, "Don't automatically connect to hubs with the `autoconnect' option set.", NULL }, { "no-bracketed-paste", 0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &bracketed_paste, "Disable bracketed pasting.", NULL }, { NULL } }; int main(int argc, char **argv) { setlocale(LC_ALL, ""); // Early logging goes to stderr stderrlog = stderr; // parse commandline options GOptionContext *optx = g_option_context_new("- NCurses Direct Connect"); g_option_context_add_main_entries(optx, cli_options, NULL); GError *err = NULL; if(!g_option_context_parse(optx, &argc, &argv, &err)) { puts(err->message); exit(1); } g_option_context_free(optx); // check that the current locale is UTF-8. Things aren't going to work otherwise if(!g_get_charset(NULL)) { puts("WARNING: Your current locale is not set to UTF-8."); puts("Non-ASCII characters may not display correctly."); puts("Hit Ctrl+c to abort ncdc, or the return key to continue anyway."); getchar(); } // init stuff gnutls_global_init(); // Create main loop main_loop = g_main_loop_new(NULL, FALSE); // setup logging g_log_set_handler(NULL, G_LOG_FATAL_MASK | G_LOG_FLAG_FATAL | G_LOG_LEVEL_ERROR, log_fatal, NULL); g_log_set_default_handler(log_redirect, NULL); // Init database & variables db_init(); vars_init(); // open log file char *errlog = g_build_filename(db_dir, "stderr.log", NULL); if(!(stderrlog = fopen(errlog, "w"))) { fprintf(stderr, "ERROR: Couldn't open %s for writing: %s\n", errlog, strerror(errno)); exit(1); } g_free(errlog); // Init more stuff hub_init_global(); net_init_global(); listen_global_init(); cc_global_init(); dl_init_global(); ui_cmdhist_init("history"); ui_init(bracketed_paste); geoip_reinit(); // setup SIGWINCH struct sigaction act; sigemptyset(&act.sa_mask); act.sa_flags = SA_RESTART; act.sa_handler = catch_sigwinch; if(sigaction(SIGWINCH, &act, NULL) < 0) g_error("Can't setup SIGWINCH: %s", g_strerror(errno)); // setup SIGTERM act.sa_handler = catch_sigterm; if(sigaction(SIGTERM, &act, NULL) < 0) g_error("Can't setup SIGTERM: %s", g_strerror(errno)); // setup SIGHUP act.sa_handler = catch_sighup; if(sigaction(SIGHUP, &act, NULL) < 0) g_error("Can't setup SIGHUP: %s", g_strerror(errno)); // setup SIGUSR1 act.sa_handler = catch_sigusr1; if(sigaction(SIGUSR1, &act, NULL) < 0) g_error("Can't setup SIGUSR1: %s", g_strerror(errno)); // setup SIGPIPE act.sa_handler = catch_sigpipe; if(sigaction(SIGPIPE, &act, NULL) < 0) g_error("Can't setup SIGPIPE: %s", g_strerror(errno)); fl_init(); if(auto_open) open_autoconnect(); // add some watches and start the main loop GIOChannel *in = g_io_channel_unix_new(STDIN_FILENO); g_io_add_watch(in, G_IO_IN, stdin_read, NULL); GSource *sighandle = g_source_new(&sighandle_funcs, sizeof(GSource)); g_source_set_priority(sighandle, G_PRIORITY_HIGH); g_source_set_callback(sighandle, sighandle_sourcefunc, NULL, NULL); g_source_attach(sighandle, NULL); g_source_unref(sighandle); g_timeout_add_seconds_full(G_PRIORITY_HIGH, 1, one_second_timer, NULL, NULL); g_timeout_add(100, screen_update_check, NULL); int maxage = var_get_int(0, VAR_filelist_maxage); g_timeout_add_seconds_full(G_PRIORITY_LOW, CLAMP(maxage, 3600, 24*3600), dl_fl_clean, NULL, NULL); g_main_loop_run(main_loop); // cleanup if(!main_noterm) { erase(); refresh(); endwin(); if(bracketed_paste) ui_set_bracketed_paste(0); printf("Flushing unsaved data to disk..."); fflush(stdout); } ui_cmdhist_close(); cc_global_close(); fl_flush(NULL); dl_close_global(); db_close(); gnutls_global_deinit(); if(!main_noterm) printf(" Done!\n"); g_debug("Clean shutdown."); return 0; } ncdc-1.23.1/src/ui_textinput.c0000644000175000017500000003313114245144570013131 00000000000000/* ncdc - NCurses Direct Connect client Copyright (c) 2011-2022 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "ncdc.h" #include "ui_textinput.h" // We only have one command history, so the struct and its instance is local to // this file, and the functions work with this instead of accepting an instance // as argument. The ui_textinput functions also access the struct and static // functions, but these don't need to be public - since ui_textinput is defined // below. #define CMDHIST_BUF 511 // must be 2^x-1 #define CMDHIST_MAXCMD 2000 typedef struct ui_cmdhist_t { char *buf[CMDHIST_BUF+1]; // circular buffer char *fn; int last; gboolean ismod; } ui_cmdhist_t; // we only have one command history, so this can be a global static ui_cmdhist_t *cmdhist; static void ui_cmdhist_add(const char *str) { int cur = cmdhist->last & CMDHIST_BUF; // ignore empty lines, or lines that are the same as the previous one if(!str || !str[0] || (cmdhist->buf[cur] && 0 == strcmp(str, cmdhist->buf[cur]))) return; cmdhist->last++; cur = cmdhist->last & CMDHIST_BUF; if(cmdhist->buf[cur]) { g_free(cmdhist->buf[cur]); cmdhist->buf[cur] = NULL; } // truncate the string if it is longer than CMDHIST_MAXCMD bytes, making sure // to not truncate within a UTF-8 sequence int len = 0; while(len < CMDHIST_MAXCMD-10 && str[len]) len = g_utf8_next_char(str+len) - str; cmdhist->buf[cur] = g_strndup(str, len); cmdhist->ismod = TRUE; } void ui_cmdhist_init(const char *file) { static char buf[CMDHIST_MAXCMD+2]; // + \n and \0 cmdhist = g_new0(ui_cmdhist_t, 1); cmdhist->fn = g_build_filename(db_dir, file, NULL); FILE *f = fopen(cmdhist->fn, "r"); if(f) { while(fgets(buf, CMDHIST_MAXCMD+2, f)) { int len = strlen(buf); if(len > 0 && buf[len-1] == '\n') buf[len-1] = 0; if(g_utf8_validate(buf, -1, NULL)) ui_cmdhist_add(buf); } fclose(f); } } // searches the history either backward or forward for the string q. The line 'start' is also counted. static int ui_cmdhist_search(gboolean backward, const char *q, int start) { int i; for(i=start; cmdhist->buf[i&CMDHIST_BUF] && (backward ? (i>=MAX(1, cmdhist->last-CMDHIST_BUF)) : (i<=cmdhist->last)); backward ? i-- : i++) { if(g_str_has_prefix(cmdhist->buf[i & CMDHIST_BUF], q)) return i; } return -1; } static void ui_cmdhist_save() { if(!cmdhist->ismod) return; cmdhist->ismod = FALSE; FILE *f = fopen(cmdhist->fn, "w"); if(!f) { g_warning("Unable to open history file '%s' for writing: %s", cmdhist->fn, g_strerror(errno)); return; } int i; for(i=0; i<=CMDHIST_BUF; i++) { char *l = cmdhist->buf[(cmdhist->last+1+i)&CMDHIST_BUF]; if(l) { if(fputs(l, f) < 0 || fputc('\n', f) < 0) g_warning("Error writing to history file '%s': %s", cmdhist->fn, strerror(errno)); } } if(fclose(f) < 0) g_warning("Error writing to history file '%s': %s", cmdhist->fn, strerror(errno)); } void ui_cmdhist_close() { int i; ui_cmdhist_save(); for(i=0; i<=CMDHIST_BUF; i++) if(cmdhist->buf[i]) g_free(cmdhist->buf[i]); g_free(cmdhist->fn); g_free(cmdhist); } #if INTERFACE struct ui_textinput_t { int pos; // position of the cursor, in number of characters GString *str; gboolean usehist; int s_pos; char *s_q; gboolean s_top; void (*complete)(char *, char **); char *c_q, *c_last, **c_sug; int c_cur; gboolean bracketed_paste; }; #endif ui_textinput_t *ui_textinput_create(gboolean usehist, void (*complete)(char *, char **)) { ui_textinput_t *ti = g_new0(ui_textinput_t, 1); ti->str = g_string_new(""); ti->usehist = usehist; ti->s_pos = -1; ti->complete = complete; ti->bracketed_paste = FALSE; return ti; } static void ui_textinput_complete_reset(ui_textinput_t *ti) { if(ti->complete) { g_free(ti->c_q); g_free(ti->c_last); g_strfreev(ti->c_sug); ti->c_q = ti->c_last = NULL; ti->c_sug = NULL; } } static void ui_textinput_complete(ui_textinput_t *ti) { if(!ti->complete) return; if(!ti->c_q) { ti->c_q = ui_textinput_get(ti); char *sep = g_utf8_offset_to_pointer(ti->c_q, ti->pos); ti->c_last = g_strdup(sep); *(sep) = 0; ti->c_cur = -1; ti->c_sug = g_new0(char *, 25); ti->complete(ti->c_q, ti->c_sug); } if(!ti->c_sug[++ti->c_cur]) ti->c_cur = -1; char *first = ti->c_cur < 0 ? ti->c_q : ti->c_sug[ti->c_cur]; char *str = g_strconcat(first, ti->c_last, NULL); ui_textinput_set(ti, str); ti->pos = g_utf8_strlen(first, -1); g_free(str); if(!g_strv_length(ti->c_sug)) ui_beep = TRUE; // If there is only one suggestion: finalize this auto-completion and reset // state. This may be slightly counter-intuitive, but makes auto-completing // paths a lot less annoying. if(g_strv_length(ti->c_sug) <= 1) ui_textinput_complete_reset(ti); } void ui_textinput_free(ui_textinput_t *ti) { ui_textinput_complete_reset(ti); g_string_free(ti->str, TRUE); if(ti->s_q) g_free(ti->s_q); g_free(ti); } void ui_textinput_set(ui_textinput_t *ti, const char *str) { g_string_assign(ti->str, str); ti->pos = g_utf8_strlen(ti->str->str, -1); } char *ui_textinput_get(ui_textinput_t *ti) { return g_strdup(ti->str->str); } char *ui_textinput_reset(ui_textinput_t *ti) { char *str = ui_textinput_get(ti); ui_textinput_set(ti, ""); if(ti->usehist) { // as a special case, don't allow /password to be logged. /hset password is // okay, since it will be stored anyway. if(!strstr(str, "/password ")) ui_cmdhist_add(str); if(ti->s_q) g_free(ti->s_q); ti->s_q = NULL; ti->s_pos = -1; } return str; } // must be drawn last, to keep the cursor position correct // also not the most efficient function ever, but probably fast enough. void ui_textinput_draw(ui_textinput_t *ti, int y, int x, int col, ui_cursor_t *cur) { // | | // "Some random string etc etc" // f # l // f = function(#, strwidth(upto_#), wincols) // if(strwidth(upto_#) < wincols*0.85) // f = 0 // else // f = strwidth(upto_#) - wincols*0.85 int i; // calculate f (in number of columns) int width = 0; char *str = ti->str->str; for(i=0; i<=ti->pos && *str; i++) { width += gunichar_width(g_utf8_get_char(str)); str = g_utf8_next_char(str); } int f = width - (col*85)/100; if(f < 0) f = 0; // now print it on the screen, starting from column f in the string and // stopping when we're out of screen columns mvhline(y, x, ' ', col); move(y, x); int pos = 0; str = ti->str->str; i = 0; while(*str) { char *ostr = str; str = g_utf8_next_char(str); int l = gunichar_width(g_utf8_get_char(ostr)); f -= l; if(f <= -col) break; if(f < 0) { // Don't display control characters if((unsigned char)*ostr >= 32) addnstr(ostr, str-ostr); if(i < ti->pos) pos += l; } i++; } x += pos; move(y, x); curs_set(1); if(cur) { cur->x = x; cur->y = y; } } static void ui_textinput_search(ui_textinput_t *ti, gboolean backwards) { int start; if(ti->s_pos < 0) { if(!backwards) { ui_beep = TRUE; return; } ti->s_q = ui_textinput_get(ti); start = cmdhist->last; } else start = ti->s_pos+(backwards ? -1 : 1); int pos = ui_cmdhist_search(backwards, ti->s_q, start); if(pos >= 0) { ti->s_pos = pos; ti->s_top = FALSE; ui_textinput_set(ti, cmdhist->buf[pos & CMDHIST_BUF]); } else if(backwards) ui_beep = TRUE; else { ti->s_pos = -1; ui_textinput_set(ti, ti->s_q); g_free(ti->s_q); ti->s_q = NULL; } } #define iswordchar(x) (!(\ ((x) >= ' ' && (x) <= '/') ||\ ((x) >= ':' && (x) <= '@') ||\ ((x) >= '[' && (x) <= '`') ||\ ((x) >= '{' && (x) <= '~')\ )) gboolean ui_textinput_key(ui_textinput_t *ti, guint64 key, char **str) { int chars = g_utf8_strlen(ti->str->str, -1); gboolean completereset = TRUE; switch(key) { case INPT_KEY(KEY_LEFT): // left - cursor one character left if(ti->pos > 0) ti->pos--; break; case INPT_KEY(KEY_RIGHT):// right - cursor one character right if(ti->pos < chars) ti->pos++; break; case INPT_KEY(KEY_END): // end case INPT_CTRL('e'): // C-e - cursor to end ti->pos = chars; break; case INPT_KEY(KEY_HOME): // home case INPT_CTRL('a'): // C-a - cursor to begin ti->pos = 0; break; case INPT_ALT('b'): // Alt+b - cursor one word backward if(ti->pos > 0) { char *pos = g_utf8_offset_to_pointer(ti->str->str, ti->pos-1); while(pos > ti->str->str && !iswordchar(*pos)) pos--; while(pos > ti->str->str && iswordchar(*(pos-1))) pos--; ti->pos = g_utf8_strlen(ti->str->str, pos-ti->str->str); } break; case INPT_ALT('f'): // Alt+f - cursor one word forward if(ti->pos < chars) { char *pos = g_utf8_offset_to_pointer(ti->str->str, ti->pos); while(!iswordchar(*pos)) pos++; while(*pos && iswordchar(*pos)) pos++; ti->pos = g_utf8_strlen(ti->str->str, pos-ti->str->str); } break; case INPT_KEY(KEY_BACKSPACE): // backspace - delete character before cursor if(ti->pos > 0) { char *pos = g_utf8_offset_to_pointer(ti->str->str, ti->pos-1); g_string_erase(ti->str, pos-ti->str->str, g_utf8_next_char(pos)-pos); ti->pos--; } break; case INPT_KEY(KEY_DC): // del - delete character under cursor if(ti->pos < chars) { char *pos = g_utf8_offset_to_pointer(ti->str->str, ti->pos); g_string_erase(ti->str, pos-ti->str->str, g_utf8_next_char(pos)-pos); } break; case INPT_CTRL('w'): // C-w - delete to previous space case INPT_ALT(127): // Alt+backspace if(ti->pos > 0) { char *end = g_utf8_offset_to_pointer(ti->str->str, ti->pos-1); char *begin = end; while(begin > ti->str->str && !iswordchar(*begin)) begin--; while(begin > ti->str->str && iswordchar(*(begin-1))) begin--; ti->pos -= g_utf8_strlen(begin, g_utf8_next_char(end)-begin); g_string_erase(ti->str, begin-ti->str->str, g_utf8_next_char(end)-begin); } break; case INPT_ALT('d'): // Alt+d - delete to next space if(ti->pos < chars) { char *begin = g_utf8_offset_to_pointer(ti->str->str, ti->pos); char *end = begin; while(*end == ' ') end++; while(*end && *(end+1) && *(end+1) != ' ') end++; g_string_erase(ti->str, begin-ti->str->str, g_utf8_next_char(end)-begin); } break; case INPT_CTRL('k'): // C-k - delete everything after cursor if(ti->pos < chars) g_string_erase(ti->str, g_utf8_offset_to_pointer(ti->str->str, ti->pos)-ti->str->str, -1); break; case INPT_CTRL('u'): // C-u - delete entire line g_string_erase(ti->str, 0, -1); ti->pos = 0; break; case INPT_KEY(KEY_UP): // up - history search back case INPT_KEY(KEY_DOWN): // down - history search forward if(ti->usehist) ui_textinput_search(ti, key == INPT_KEY(KEY_UP)); else return FALSE; break; case INPT_CTRL('i'): // tab - autocomplete if(ti->bracketed_paste) { g_string_insert_unichar(ti->str, g_utf8_offset_to_pointer(ti->str->str, ti->pos)-ti->str->str, ' '); ti->pos++; return FALSE; } else { ui_textinput_complete(ti); completereset = FALSE; } break; case INPT_CTRL('j'): // newline - accept & clear if(ti->bracketed_paste) { g_string_insert_unichar(ti->str, g_utf8_offset_to_pointer(ti->str->str, ti->pos)-ti->str->str, '\n'); ti->pos++; return FALSE; } // if not responded to, input simply keeps buffering; avoids modality // reappearing after each (non-bracketed) newline avoids user confusion // UTF-8: <32 always 1 byte from trusted input { int num_lines = 1; char *c; for(c=ti->str->str; *c; c++) num_lines += *c == '\n'; if(num_lines > 1) { ui_mf(NULL, UIM_NOLOG, "Press Ctrl-y to accept %d-line paste", num_lines); break; } } *str = ui_textinput_reset(ti); break; case INPT_CTRL('y'): // C-y - accept bracketed paste *str = ui_textinput_reset(ti); break; case KEY_BRACKETED_PASTE_START: ti->bracketed_paste = TRUE; break; case KEY_BRACKETED_PASTE_END: ti->bracketed_paste = FALSE; break; default: if(INPT_TYPE(key) == 1) { // char g_string_insert_unichar(ti->str, g_utf8_offset_to_pointer(ti->str->str, ti->pos)-ti->str->str, INPT_CODE(key)); ti->pos++; } else return FALSE; } if(completereset) ui_textinput_complete_reset(ti); return TRUE; } ncdc-1.23.1/src/uit_userlist.c0000644000175000017500000003556614245144614013140 00000000000000/* ncdc - NCurses Direct Connect client Copyright (c) 2011-2022 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "ncdc.h" #include "uit_userlist.h" ui_tab_type_t uit_userlist[1]; typedef struct tab_t { ui_tab_t tab; ui_listing_t *list; int order; gboolean reverse : 1; gboolean details : 1; gboolean opfirst : 1; gboolean hide_desc : 1; gboolean hide_tag : 1; gboolean hide_mail : 1; gboolean hide_conn : 1; gboolean hide_ip : 1; int cw_user, cw_country, cw_share, cw_conn, cw_desc, cw_mail, cw_tag, cw_ip; } tab_t; // Columns to sort on #define SORT_USER 0 #define SORT_SHARE 1 #define SORT_CONN 2 #define SORT_DESC 3 #define SORT_MAIL 4 #define SORT_CLIENT 5 #define SORT_IP 6 static gint sort_func(gconstpointer da, gconstpointer db, gpointer dat) { const hub_user_t *a = da; const hub_user_t *b = db; tab_t *t = dat; int p = t->order; if(t->opfirst && !a->isop != !b->isop) return a->isop && !b->isop ? -1 : 1; // All orders have the username as secondary order. int o = p == SORT_USER ? 0 : p == SORT_SHARE ? a->sharesize > b->sharesize ? 1 : -1: p == SORT_CONN ? (t->tab.hub->adc ? a->conn - b->conn : strcmp(a->conn?a->conn:"", b->conn?b->conn:"")) : p == SORT_DESC ? g_utf8_collate(a->desc?a->desc:"", b->desc?b->desc:"") : p == SORT_MAIL ? g_utf8_collate(a->mail?a->mail:"", b->mail?b->mail:"") : p == SORT_CLIENT ? strcmp(a->client?a->client:"", b->client?b->client:"") : (ip4_cmp(a->ip4, b->ip4) != 0 ? ip4_cmp(a->ip4, b->ip4) : ip6_cmp(a->ip6, b->ip6)); // Username sort if(!o) o = g_utf8_collate(a->name, b->name); if(!o && a->name_hub && b->name_hub) o = strcmp(a->name_hub, b->name_hub); if(!o) o = a - b; return t->reverse ? -1*o : o; } static const char *get_name(GSequenceIter *iter) { hub_user_t *u = g_sequence_get(iter); return u->name; } ui_tab_t *uit_userlist_create(hub_t *hub) { tab_t *t = g_new0(tab_t, 1); t->tab.name = g_strdup_printf("@%s", hub->tab->name+1); t->tab.type = uit_userlist; t->tab.hub = hub; t->opfirst = TRUE; t->hide_conn = TRUE; t->hide_mail = TRUE; t->hide_ip = TRUE; GSequence *users = g_sequence_new(NULL); // populate the list // g_sequence_sort() uses insertion sort? in that case it is faster to insert // all items using g_sequence_insert_sorted() rather than inserting them in // no particular order and then sorting them in one go. (which is faster for // linked lists, since it uses a faster sorting algorithm) GHashTableIter iter; g_hash_table_iter_init(&iter, hub->users); hub_user_t *u; while(g_hash_table_iter_next(&iter, NULL, (gpointer *)&u)) u->iter = g_sequence_insert_sorted(users, u, sort_func, t); t->list = ui_listing_create(users, NULL, t, get_name); return (ui_tab_t *)t; } static void t_close(ui_tab_t *tab) { tab_t *t = (tab_t *)tab; uit_hub_set_userlist(t->tab.hub->tab, NULL); ui_tab_remove(tab); // To clean things up, we should also reset all hub_user->iter fields. But // this isn't all that necessary since they won't be used anymore until they // get reset in a subsequent ui_userlist_create(). g_sequence_free(t->list->list); ui_listing_free(t->list); g_free(t->tab.name); g_free(t); } static char *t_title(ui_tab_t *tab) { return g_strdup_printf("%s / User list", tab->hub->tab->name); } #define DRAW_COL(row, colvar, width, str) do {\ if(width > 1)\ mvaddnstr(row, colvar, str, str_offset_from_columns(str, width-1));\ colvar += width;\ } while(0) static void draw_row(ui_listing_t *list, GSequenceIter *iter, int row, void *dat) { hub_user_t *user = g_sequence_get(iter); tab_t *t = dat; char *tag = hub_user_tag(user); char *conn = hub_user_conn(user); attron(iter == list->sel ? UIC(list_select) : UIC(list_default)); mvhline(row, 0, ' ', wincols); if(iter == list->sel) mvaddch(row, 0, '>'); if(user->isop) mvaddch(row, 2, 'o'); if(!user->active) mvaddch(row, 3, 'p'); if(user->hastls) mvaddch(row, 4, 't'); int j = 6; const char *cc = !ip4_isany(user->ip4) ? geoip_country(ip4_sockaddr(user->ip4, 0)) : !ip6_isany(user->ip6) ? geoip_country(ip6_sockaddr(user->ip6, 0)) : NULL; DRAW_COL(row, j, t->cw_country, cc?cc:""); if(t->cw_user > 1) ui_listing_draw_match(list, iter, row, j, str_offset_from_columns(user->name, t->cw_user-1)); j += t->cw_user; DRAW_COL(row, j, t->cw_share, user->hasinfo ? str_formatsize(user->sharesize) : ""); DRAW_COL(row, j, t->cw_desc, user->desc?user->desc:""); DRAW_COL(row, j, t->cw_tag, tag?tag:""); DRAW_COL(row, j, t->cw_mail, user->mail?user->mail:""); DRAW_COL(row, j, t->cw_conn, conn?conn:""); DRAW_COL(row, j, t->cw_ip, hub_user_ip(user, "")); g_free(conn); g_free(tag); attroff(iter == list->sel ? UIC(list_select) : UIC(list_default)); } /* Distributing a width among several columns with given weights: * w_t = sum(i=c_v; w_i) * w_s = 1 + sum(i=c_h; w_i/w_t) * b_i = w_i*w_s * Where: * c_v = set of all visible columns * c_h = set of all hidden columns * w_i = weight of column $i * w_t = sum of the weights of all visible columns * w_s = scale factor * b_i = calculated width of column $i, with 0 < b_i <= 1 * * TODO: abstract this, so that the weights and such don't need repetition. */ static void calc_widths(tab_t *t) { // available width int w = wincols-6; // Country code column (fixed size) t->cw_country = geoip_available ? 3 : 0; w -= t->cw_country; // share has a fixed size t->cw_share = 12; w -= 12; // IP column as well t->cw_ip = t->hide_ip ? 0 : 39; w -= t->cw_ip; // User column has a minimum size (but may grow a bit later on, so will still be counted as a column) t->cw_user = 15; w -= 15; // Total weight (first one is for the user column) double wt = 0.02 + (t->hide_conn ? 0.0 : 0.16) + (t->hide_desc ? 0.0 : 0.32) + (t->hide_mail ? 0.0 : 0.18) + (t->hide_tag ? 0.0 : 0.32); // Scale factor double ws = 1.0 + ( + (t->hide_conn ? 0.16: 0.0) + (t->hide_desc ? 0.32: 0.0) + (t->hide_mail ? 0.18: 0.0) + (t->hide_tag ? 0.32: 0.0))/wt; // scale to available width ws *= w; // Get the column widths. Note the use of floor() here, this prevents that // the total width exceeds the available width. The remaining columns will be // given to the user column, which is always present anyway. t->cw_conn = t->hide_conn ? 0 : floor(0.16*ws); t->cw_desc = t->hide_desc ? 0 : floor(0.32*ws); t->cw_mail = t->hide_mail ? 0 : floor(0.18*ws); t->cw_tag = t->hide_tag ? 0 : floor(0.32*ws); t->cw_user += w - t->cw_conn - t->cw_desc - t->cw_mail - t->cw_tag; } static void t_draw(ui_tab_t *tab) { tab_t *t = (tab_t *)tab; calc_widths(t); // header attron(UIC(list_header)); mvhline(1, 0, ' ', wincols); mvaddstr(1, 2, "opt"); int i = 6; DRAW_COL(1, i, t->cw_country, "CC"); DRAW_COL(1, i, t->cw_user, "Username"); DRAW_COL(1, i, t->cw_share, "Share"); DRAW_COL(1, i, t->cw_desc, "Description"); DRAW_COL(1, i, t->cw_tag, "Tag"); DRAW_COL(1, i, t->cw_mail, "E-Mail"); DRAW_COL(1, i, t->cw_conn, "Connection"); DRAW_COL(1, i, t->cw_ip, "IP"); attroff(UIC(list_header)); // rows int bottom = t->details ? winrows-7 : winrows-3; ui_cursor_t cursor; int pos = ui_listing_draw(t->list, 2, bottom-1, &cursor, draw_row); // footer attron(UIC(separator)); mvhline(bottom, 0, ' ', wincols); int count = g_hash_table_size(t->tab.hub->users); mvaddstr(bottom, 0, "Totals:"); mvprintw(bottom, t->cw_user+6, "%s%c %d users", str_formatsize(t->tab.hub->sharesize), t->tab.hub->sharecount == count ? ' ' : '+', count); mvprintw(bottom, wincols-6, "%3d%%", pos); attroff(UIC(separator)); // detailed info box if(t->details && g_sequence_iter_is_end(t->list->sel)) mvaddstr(bottom+1, 2, "No user selected."); else if(t->details) { hub_user_t *u = g_sequence_get(t->list->sel); attron(A_BOLD); mvaddstr(bottom+1, 4, "Username:"); mvaddstr(bottom+1, 25+16, "Share:"); mvaddstr(bottom+2, 2, "Connection:"); mvaddstr(bottom+2, 25+19, "IP:"); mvaddstr(bottom+3, 6, "E-Mail:"); mvaddstr(bottom+3, 25+18, "Tag:"); mvaddstr(bottom+4, 1, "Description:"); attroff(A_BOLD); mvaddstr(bottom+1, 14, u->name); if(u->hasinfo) mvprintw(bottom+1, 25+23, "%s (%s bytes)", str_formatsize(u->sharesize), str_fullsize(u->sharesize)); else mvaddstr(bottom+1, 25+23, "-"); char *conn = hub_user_conn(u); mvaddstr(bottom+2, 14, conn?conn:"-"); g_free(conn); mvaddstr(bottom+2, 25+23, hub_user_ip(u, "-")); mvaddstr(bottom+3, 14, u->mail?u->mail:"-"); char *tag = hub_user_tag(u); mvaddstr(bottom+3, 25+23, tag?tag:"-"); g_free(tag); mvaddstr(bottom+4, 14, u->desc?u->desc:"-"); // TODO: CID? } move(cursor.y, cursor.x); } #undef DRAW_COL static void t_key(ui_tab_t *tab, guint64 key) { tab_t *t = (tab_t *)tab; if(ui_listing_key(t->list, key, winrows/2)) return; hub_user_t *sel = g_sequence_iter_is_end(t->list->sel) ? NULL : g_sequence_get(t->list->sel); gboolean sort = FALSE; switch(key) { case INPT_CHAR('?'): uit_main_keys("userlist"); break; // Sorting #define SETSORT(c) \ t->reverse = t->order == c ? !t->reverse : FALSE;\ t->order = c;\ sort = TRUE; case INPT_CHAR('s'): // s/S - sort on share size case INPT_CHAR('S'): SETSORT(SORT_SHARE); break; case INPT_CHAR('u'): // u/U - sort on username case INPT_CHAR('U'): SETSORT(SORT_USER) break; case INPT_CHAR('D'): // D - sort on description SETSORT(SORT_DESC) break; case INPT_CHAR('T'): // T - sort on client (= tag) SETSORT(SORT_CLIENT) break; case INPT_CHAR('E'): // E - sort on email SETSORT(SORT_MAIL) break; case INPT_CHAR('C'): // C - sort on connection SETSORT(SORT_CONN) break; case INPT_CHAR('P'): // P - sort on IP SETSORT(SORT_IP) break; case INPT_CHAR('o'): // o - toggle sorting OPs before others t->opfirst = !t->opfirst; sort = TRUE; break; #undef SETSORT // Column visibility case INPT_CHAR('d'): // d (toggle description visibility) t->hide_desc = !t->hide_desc; break; case INPT_CHAR('t'): // t (toggle tag visibility) t->hide_tag = !t->hide_tag; break; case INPT_CHAR('e'): // e (toggle e-mail visibility) t->hide_mail = !t->hide_mail; break; case INPT_CHAR('c'): // c (toggle connection visibility) t->hide_conn = !t->hide_conn; break; case INPT_CHAR('p'): // p (toggle IP visibility) t->hide_ip = !t->hide_ip; break; case INPT_CTRL('j'): // newline case INPT_CHAR('i'): // i (toggle user info) t->details = !t->details; break; case INPT_CHAR('m'): // m (/msg user) if(!sel) ui_m(NULL, 0, "No user selected."); else uit_msg_open(sel->uid, tab); break; case INPT_CHAR('g'): // g (grant slot) if(!sel) ui_m(NULL, 0, "No user selected."); else { db_users_set(sel->hub->id, sel->uid, sel->name, db_users_get(sel->hub->id, sel->name) | DB_USERFLAG_GRANT); ui_m(NULL, 0, "Slot granted."); } break; case INPT_CHAR('b'): // b (/browse userlist) case INPT_CHAR('B'): // B (force /browse userlist) if(!sel) ui_m(NULL, 0, "No user selected."); else uit_fl_queue(sel->uid, key == INPT_CHAR('B'), NULL, tab, TRUE, FALSE); break; case INPT_CHAR('q'): // q - download filelist and match queue for selected user if(!sel) ui_m(NULL, 0, "No user selected."); else uit_fl_queue(sel->uid, FALSE, NULL, NULL, FALSE, TRUE); break; } if(sort) { g_sequence_sort(t->list->list, sort_func, tab); ui_listing_sorted(t->list); ui_mf(NULL, 0, "Ordering by %s (%s%s)", t->order == SORT_USER ? "user name" : t->order == SORT_SHARE ? "share size" : t->order == SORT_CONN ? "connection" : t->order == SORT_DESC ? "description" : t->order == SORT_MAIL ? "e-mail" : t->order == SORT_CLIENT? "tag" : "IP address", t->reverse ? "descending" : "ascending", t->opfirst ? ", OPs first" : ""); } } // Called when the hub is disconnected. All users should be removed in one go, // this is faster than a _userchange() for every user. void uit_userlist_disconnect(ui_tab_t *tab) { tab_t *t = (tab_t *)tab; g_sequence_free(t->list->list); ui_listing_free(t->list); t->list = ui_listing_create(g_sequence_new(NULL), NULL, t, get_name); } // Called from the hub tab when something changes to the user list. void uit_userlist_userchange(ui_tab_t *tab, int change, hub_user_t *user) { tab_t *t = (tab_t *)tab; if(change == UIHUB_UC_JOIN) { user->iter = g_sequence_insert_sorted(t->list->list, user, sort_func, t); ui_listing_inserted(t->list); } else if(change == UIHUB_UC_QUIT) { g_return_if_fail(g_sequence_get(user->iter) == (gpointer)user); ui_listing_remove(t->list, user->iter); g_sequence_remove(user->iter); } else { g_sequence_sort_changed(user->iter, sort_func, t); ui_listing_sorted(t->list); } } // Opens the user list for a hub and selects the user specified by uid or // user/utf8. Returns FALSE if a user was specified but could not be found. gboolean uit_userlist_open(hub_t *hub, guint64 uid, const char *user, gboolean utf8) { hub_user_t *u = !uid && !user ? NULL : uid ? g_hash_table_lookup(hub_uids, &uid) : utf8 ? hub_user_get(hub, user) : g_hash_table_lookup(hub->users, user); if((uid || user) && (!u || u->hub != hub)) return FALSE; ui_tab_t *ut = uit_hub_userlist(hub->tab); if(ut) ui_tab_cur = g_list_find(ui_tabs, ut); else { ut = uit_userlist_create(hub); ui_tab_open(ut, TRUE, hub->tab); uit_hub_set_userlist(hub->tab, ut); } if(u) { tab_t *t = (tab_t *)ut; // u->iter should be valid at this point. t->list->sel = u->iter; t->details = TRUE; } return TRUE; } ui_tab_type_t uit_userlist[1] = { { t_draw, t_title, t_key, t_close } }; ncdc-1.23.1/src/ui_colors.c0000644000175000017500000001170614245144561012372 00000000000000/* ncdc - NCurses Direct Connect client Copyright (c) 2011-2022 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "ncdc.h" #include "ui_colors.h" // colors #if INTERFACE #define COLOR_DEFAULT (-1) // name default value #define UI_COLORS \ C(list_default, "default")\ C(list_header, "default,bold")\ C(list_select, "default,bold")\ C(log_default, "default")\ C(log_highlight, "yellow,bold")\ C(log_join, "cyan,bold")\ C(log_nick, "default")\ C(log_ownnick, "default,bold")\ C(log_quit, "cyan")\ C(log_time, "black,bold")\ C(separator, "default,reverse")\ C(tab_active, "default,bold")\ C(tabprio_high, "magenta,bold")\ C(tabprio_low, "black,bold")\ C(tabprio_med, "cyan,bold")\ C(title, "default,reverse") enum ui_coltype { #define C(n, d) UIC_##n, UI_COLORS #undef C UIC_NONE }; struct ui_color_t { int var; short fg, bg, d_fg, d_bg; int x, d_x, a; }; struct ui_attr_t { char name[11]; gboolean color : 1; int attr; } struct ui_cursor_t { int x; int y; } #define UIC(n) (ui_colors[(ui_coltype)UIC_##n].a) #endif // INTERFACE ui_color_t ui_colors[] = { #define C(n, d) { VAR_color_##n }, UI_COLORS #undef C { -1 } }; ui_attr_t ui_attr_names[] = { { "black", TRUE, COLOR_BLACK }, { "blink", FALSE, A_BLINK }, { "blue", TRUE, COLOR_BLUE }, { "bold", FALSE, A_BOLD }, { "cyan", TRUE, COLOR_CYAN }, { "default", TRUE, COLOR_DEFAULT }, { "green", TRUE, COLOR_GREEN }, { "magenta", TRUE, COLOR_MAGENTA }, { "red", TRUE, COLOR_RED }, { "reverse", FALSE, A_REVERSE }, { "underline", FALSE, A_UNDERLINE }, { "white", TRUE, COLOR_WHITE }, { "yellow", TRUE, COLOR_YELLOW }, { "" } }; static ui_attr_t *ui_attr_by_name(const char *n) { ui_attr_t *a = ui_attr_names; for(; *a->name; a++) if(strcmp(a->name, n) == 0) return a; return NULL; } static char *ui_name_by_attr(int n) { ui_attr_t *a = ui_attr_names; for(; *a->name; a++) if(a->attr == n) return a->name; return NULL; } gboolean ui_color_str_parse(const char *str, short *fg, short *bg, int *x, GError **err) { int state = 0; // 0 = no fg, 1 = no bg, 2 = both short f = COLOR_DEFAULT, b = COLOR_DEFAULT; int a = 0; char **args = g_strsplit(str, ",", 0); char **arg = args; for(; arg && *arg; arg++) { g_strstrip(*arg); if(!**arg) continue; ui_attr_t *attr = ui_attr_by_name(*arg); if(!attr) { g_set_error(err, 1, 0, "Unknown color or attribute: %s", *arg); g_strfreev(args); return FALSE; } if(!attr->color) a |= attr->attr; else if(!state) { f = attr->attr; state++; } else if(state == 1) { b = attr->attr; state++; } else { g_set_error(err, 1, 0, "Don't know what to do with a third color: %s", *arg); g_strfreev(args); return FALSE; } } g_strfreev(args); if(fg) *fg = f; if(bg) *bg = b; if(x) *x = a; return TRUE; } char *ui_color_str_gen(int fd, int bg, int x) { static char buf[100]; // must be smaller than (max_color_name * 2) + (max_attr_name * 3) + 6 strcpy(buf, ui_name_by_attr(fd)); if(bg != COLOR_DEFAULT) { strcat(buf, ","); strcat(buf, ui_name_by_attr(bg)); } ui_attr_t *attr = ui_attr_names; for(; attr->name[0]; attr++) if(!attr->color && x & attr->attr) { strcat(buf, ","); strcat(buf, attr->name); } return buf; } // TODO: re-use color pairs when we have too many (>64) color groups void ui_colors_update() { int pair = 0; ui_color_t *c = ui_colors; for(; c->var>=0; c++) { g_warn_if_fail(ui_color_str_parse(var_get(0, c->var), &c->fg, &c->bg, &c->x, NULL)); init_pair(++pair, c->fg, c->bg); c->a = c->x | COLOR_PAIR(pair); } } void ui_colors_init() { if(!has_colors()) return; start_color(); use_default_colors(); ui_colors_update(); } ncdc-1.23.1/src/dl.c0000644000175000017500000010272114245144463010772 00000000000000/* ncdc - NCurses Direct Connect client Copyright (c) 2011-2022 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "ncdc.h" #include "dl.h" #if INTERFACE struct dl_user_dl_t { dl_t *dl; dl_user_t *u; char error; // DLE_* char *error_msg; }; #define DLU_NCO 0 // Not connected, ready for connection #define DLU_EXP 1 // Expecting a dl connection #define DLU_IDL 2 // dl connected, idle #define DLU_ACT 3 // dl connected, downloading #define DLU_WAI 4 // Not connected, waiting for reconnect timeout struct dl_user_t { int state; // DLU_* int timeout; // source id of the timeout function in DLU_WAI guint64 uid; cc_t *cc; // Always when state = IDL or ACT, may be set or NULL in EXP GSequence *queue; // list of dl_user_dl_t, ordered by dl_user_dl_sort() dl_user_dl_t *active; // when state = DLU_ACT, the dud that is being downloaded (NULL if it had been removed from the queue while downloading) }; /* State machine for dl_user.state: * * 8 /-----\ * .<--- | WAI | <-------------------------------. * / \-----/ | | | * | 2 | 4 | .<------. | 7 * v | | / 6 \ | * /-----\ 1 /-----\ 3 /-----\ 5 /-----\ * -> | NCO | ---------> | EXP | ----> | IDL | ----> | ACT | * \-----/ \-----/ \-----/ \-----/ * * 1. We're requesting a connect * 2. No reply, connection timed out or we lost the $Download game on NMDC * 3. Successful connection and handshake * 4. Idle timeout / user disconnect * 5. Start of download * 6. Download (chunk) finished * 7. Idle timeout / user disconnect / download aborted / no slots free / error while downloading * 8. Reconnect timeout expired * (currently hardcoded to 60 sec, probably want to make this configurable) */ // Note: The following numbers are also stored in the database. Keep this in // mind when changing or extending. (Both DLP_ and DLE_) #define DLP_ERR -65 // disabled due to (permanent) error #define DLP_OFF -64 // disabled by user #define DLP_VLOW -2 #define DLP_LOW -1 #define DLP_MED 0 #define DLP_HIGH 1 #define DLP_VHIGH 2 #define DLE_NONE 0 // No error #define DLE_INVTTHL 1 // TTHL data does not match the file root #define DLE_NOFILE 2 // User does not have the file at all #define DLE_IO_INC 3 // I/O error with incoming file #define DLE_IO_DEST 4 // I/O error when moving to destination file/dir #define DLE_HASH 5 // Hash check failed struct dl_t { gboolean islist : 1; gboolean hastthl : 1; gboolean flopen : 1; // For lists: Whether to open a browse tab after completed download gboolean flmatch : 1; // For lists: Whether to match queue after completed download gboolean hassize : 1; // For lists: Whether the size of the file list is known and validated gboolean allbusy : 1; // When no more unallocated blocks are available (maintained by dlfile.c) signed char prio; // DLP_* char error; // DLE_* unsigned char active_threads; // number of active downloading threads (maintained by dlfile.c) int incfd; // file descriptor for this file in (maintained by dlfile.c) char *error_msg; // if error != DLE_NONE char *flsel; // path to file/dir to select for filelists ui_tab_t *flpar; // parent of the file list browser tab for filelists (might be a dangling pointer!) char hash[24]; // TTH for files, tiger(uid) for filelists GPtrArray *u; // list of users who have this file (GSequenceIter pointers into dl_user.queue) guint64 size; // total size of the file guint64 have; // what we have so far guint64 hash_block; // number of bytes that each block represents char *inc; // path to the incomplete file (/) char *dest; // destination path GSequenceIter *iter; // used by ui_dl GSList *threads; // maintained by dlfile.c guint8 *bitmap; // Only used if hastthl, maintained by dlfile.c guint bitmap_src; // timeout source for flushing the bitmap, maintained by dlfile.c /* Maintained by dlfile.c, protects dl_t.{have,bitmap,bitmap_src} and * dlfile_thread_t.{allocated,avail,chunk}. * Some other fields are shared, too, but those are never modified while a * downloading thread is active and thus do not need synchronisation. These * include dl_t.{size,islist,hash,hash_block,incfd} and possibly more. * TODO: dl.have isn't always protected yet! */ GMutex lock; }; #endif // Minimum filesize for which we request TTHL data. If a file is smaller than // this, the TTHL data would simply add more overhead than it is worth. #define DL_MINTTHLSIZE (2048*1024) // Minimum TTHL block size we're interested in. If we get better granularity // than this, blocks will be combined to reduce the TTHL data. #define DL_MINBLOCKSIZE (1024*1024) // Download queue. // Key = dl->hash, Value = dl_t GHashTable *dl_queue = NULL; // uid -> dl_user lookup table. static GHashTable *queue_users = NULL; // Utility function that returns an error string for DLE_* errors. char *dl_strerror(char err, const char *sub) { static char buf[200]; char *par = err == DLE_NONE ? "No error" : err == DLE_INVTTHL ? "TTHL data does not match TTH root" : err == DLE_NOFILE ? "File not available from this user" : err == DLE_IO_INC ? "Error writing to temporary file" : err == DLE_IO_DEST ? "Error moving file to destination" : err == DLE_HASH ? "Hash error" : "Unknown error"; if(sub) g_snprintf(buf, 200, "%s: %s", par, sub); else g_snprintf(buf, 200, "%s.", par); return buf; } // dl_user_t related functions static gboolean dl_user_waitdone(gpointer dat); // Determine whether a dl_user_dl struct can be considered as "enabled". #define dl_user_dl_enabled(dud) (\ !dud->error && dud->dl->prio > DLP_OFF\ && ((!dud->dl->size && dud->dl->islist) || dud->dl->size != dud->dl->have)\ ) // Sort function for dl_user_dl structs. Items with a higher priority are // sorted before items with a lower priority. Never returns 0, so the order is // always predictable even if all items have the same priority. This function // is used both for sorting the queue of a single user, and to sort users // itself on their highest-priority file. // TODO: Give priority to small files (those that can be downloaded using a minislot) static gint dl_user_dl_sort(gconstpointer a, gconstpointer b, gpointer dat) { const dl_user_dl_t *x = a; const dl_user_dl_t *y = b; const dl_t *dx = x->dl; const dl_t *dy = y->dl; return // Disabled? Always last dl_user_dl_enabled(x) && !dl_user_dl_enabled(y) ? -1 : !dl_user_dl_enabled(x) && dl_user_dl_enabled(y) ? 1 // File lists get higher priority than normal files : dx->islist && !dy->islist ? -1 : !dx->islist && dy->islist ? 1 // Higher priority files get higher priority than lower priority ones (duh) : dx->prio > dy->prio ? -1 : dx->prio < dy->prio ? 1 // For equal priority: download in alphabetical order : strcmp(dx->dest, dy->dest); } // Frees a dl_user_dl struct static void dl_user_dl_free(gpointer x) { g_free(((dl_user_dl_t *)x)->error_msg); g_slice_free(dl_user_dl_t, x); } // Get the highest-priority file in the users' queue that is not already being // downloaded. This function can be assumed to be relatively fast, in most // cases the first iteration will be enough, in the worst case it at most // iterations. // Returns NULL if there is no dl item in the queue that is enabled and not // being downloaded. static dl_user_dl_t *dl_user_getdl(const dl_user_t *du) { GSequenceIter *i = g_sequence_get_begin_iter(du->queue); for(; !g_sequence_iter_is_end(i); i=g_sequence_iter_next(i)) { dl_user_dl_t *dud = g_sequence_get(i); if(dl_user_dl_enabled(dud) && !dud->dl->allbusy) return dud; } return NULL; } // Change the state of a user, use state=-1 when something is removed from // du->queue. static void dl_user_setstate(dl_user_t *du, int state) { // Handle reconnect timeout // x -> WAI if(state >= 0 && du->state != DLU_WAI && state == DLU_WAI) du->timeout = g_timeout_add_seconds_full(G_PRIORITY_LOW, 60, dl_user_waitdone, du, NULL); // WAI -> X else if(state >= 0 && du->state == DLU_WAI && state != DLU_WAI) g_source_remove(du->timeout); // ACT -> x if(state >= 0 && du->state == DLU_ACT && state != DLU_ACT && du->active) du->active = NULL; // Set state //g_debug("dlu:%"G_GINT64_MODIFIER"x: %d -> %d (active = %s)", du->uid, du->state, state, du->active ? "true":"false"); if(state >= 0) du->state = state; // Check whether there is any value in keeping this dl_user struct in memory if(du->state == DLU_NCO && !g_sequence_get_length(du->queue)) { g_hash_table_remove(queue_users, &du->uid); g_sequence_free(du->queue); g_slice_free(dl_user_t, du); return; } // Check whether we can initiate a download again. (We could be more // selective here to possibly decrease CPU usage, but oh well.) dl_queue_start(); } static gboolean dl_user_waitdone(gpointer dat) { dl_user_t *du = dat; g_return_val_if_fail(du->state == DLU_WAI, FALSE); dl_user_setstate(du, DLU_NCO); return FALSE; } // When called with NULL, this means that a connection attempt failed or we // somehow disconnected from the user. // Otherwise, it means that the cc connection with the user went into the IDLE // state, either after the handshake or after a completed download. void dl_user_cc(guint64 uid, cc_t *cc) { g_debug("dl:%016"G_GINT64_MODIFIER"x: cc = %s", uid, cc?"true":"false"); dl_user_t *du = g_hash_table_lookup(queue_users, &uid); if(!du) return; g_return_if_fail(!cc || du->state == DLU_NCO || du->state == DLU_EXP || du->state == DLU_ACT); du->cc = cc; dl_user_setstate(du, cc ? DLU_IDL : DLU_WAI); } // To be called when a user joins a hub. Checks whether we have something to // get from that user. May be called with uid=0 after joining a hub, in which // case all users in the queue will be checked. void dl_user_join(guint64 uid) { if(!uid || g_hash_table_lookup(queue_users, &uid)) dl_queue_start(); } // Adds a user to a dl item, making sure to create the user if it's not in the // queue yet. For internal use only, does not save the changes to the database // and does not call dl_queue_start(). static void dl_user_add(dl_t *dl, guint64 uid, char error, const char *error_msg) { g_return_if_fail(!dl->islist || dl->u->len == 0); // get or create dl_user struct dl_user_t *du = g_hash_table_lookup(queue_users, &uid); if(!du) { du = g_slice_new0(dl_user_t); du->state = DLU_NCO; du->uid = uid; du->queue = g_sequence_new(dl_user_dl_free); g_hash_table_insert(queue_users, &du->uid, du); } // create and fill dl_user_dl struct dl_user_dl_t *dud = g_slice_new0(dl_user_dl_t); dud->dl = dl; dud->u = du; dud->error = error; dud->error_msg = error_msg ? g_strdup(error_msg) : NULL; // Add to du->queue and dl->u g_ptr_array_add(dl->u, g_sequence_insert_sorted(du->queue, dud, dl_user_dl_sort, NULL)); uit_dl_dud_listchange(dud, UITDL_ADD); } // Remove a user (dl->u[i]) from a dl item, making sure to also remove it from // du->queue and possibly free the dl_user item if it's no longer useful. As // above, for internal use only. Does not save the changes to the database. static void dl_user_rm(dl_t *dl, int i) { GSequenceIter *dudi = g_ptr_array_index(dl->u, i); dl_user_dl_t *dud = g_sequence_get(dudi); dl_user_t *du = dud->u; // Make sure to disconnect the user if we happened to be actively downloading // the file from this user. if(du->active == dud) { cc_disconnect(du->cc, TRUE); du->active = NULL; } uit_dl_dud_listchange(dud, UITDL_DEL); g_sequence_remove(dudi); // dl_user_dl_free() will be called implicitly g_ptr_array_remove_index_fast(dl->u, i); dl_user_setstate(du, -1); } // Determining when and what to start downloading static gboolean dl_queue_needstart = FALSE; // Determines whether the user is a possible target to either connect to, or to // initiate a download with. static gboolean dl_queue_start_istarget(dl_user_t *du) { // User must be in the NCO/IDL state and we must have something to download // from them. if((du->state != DLU_NCO && du->state != DLU_IDL) || !dl_user_getdl(du)) return FALSE; // In the NCO state, the user must also be online, and the hub must be // properly logged in. Otherwise we won't be able to connect anyway. if(du->state == DLU_NCO) { hub_user_t *u = g_hash_table_lookup(hub_uids, &du->uid); if(!u || !u->hub->nick_valid) return FALSE; } // If the above holds, we're safe return TRUE; } // Starts a connection with a user or initiates a download if we're already // connected. static gboolean dl_queue_start_user(dl_user_t *du) { g_return_val_if_fail(dl_queue_start_istarget(du), FALSE); // If we're not connected yet, just connect if(du->state == DLU_NCO) { g_debug("dl:%016"G_GINT64_MODIFIER"x: trying to open a connection", du->uid); hub_user_t *u = g_hash_table_lookup(hub_uids, &du->uid); dl_user_setstate(du, DLU_EXP); hub_opencc(u->hub, u); return FALSE; } // Otherwise, initiate a download. dl_user_dl_t *dud = dl_user_getdl(du); g_return_val_if_fail(dud, FALSE); dl_t *dl = dud->dl; g_debug("dl:%016"G_GINT64_MODIFIER"x: using connection for %s", du->uid, dl->dest); // Update state and connect du->active = dud; dl_user_setstate(du, DLU_ACT); cc_download(du->cc, dl); return TRUE; } // Compares two dl_user structs by a "priority" to determine from whom to // download first. Note that users in the IDL state always get priority over // users in the NCO state, in order to prevent the situation that the // lower-priority user in the IDL state is connected to anyway in a next // iteration. Returns -1 if a has a higher priority than b. // This function assumes dl_queue_start_istarget() for both arguments. static gint dl_queue_start_cmp(gconstpointer a, gconstpointer b) { const dl_user_t *ua = a; const dl_user_t *ub = b; return -1*( ua->state == DLU_IDL && ub->state != DLU_IDL ? 1 : ua->state != DLU_IDL && ub->state == DLU_IDL ? -1 : dl_user_dl_sort(dl_user_getdl(ub), dl_user_getdl(ua), NULL) ); } // Initiates a new connection to a user or requests a file from an already // connected user, based on the current state of dl_user and dl structs. This // function is relatively slow, so is executed from a timeout to bulk-check // everything after some state variables have changed. Should not be called // directly, use dl_queue_start() instead. static gboolean dl_queue_start_do(gpointer dat) { int freeslots = var_get_int(0, VAR_download_slots); // Walk through all users in the queue and: // - determine possible targets to connect to or to start a transfer from // - determine the highest-priority target // - calculate freeslots GPtrArray *targets = g_ptr_array_new(); dl_user_t *du, *target = NULL; int target_i = 0; GHashTableIter iter; g_hash_table_iter_init(&iter, queue_users); while(g_hash_table_iter_next(&iter, NULL, (gpointer *)&du)) { if(du->state == DLU_ACT) freeslots--; if(dl_queue_start_istarget(du)) { if(!target || dl_queue_start_cmp(target, du) > 0) { target_i = targets->len; target = du; } g_ptr_array_add(targets, du); } } // Try to connect to the previously found highest-priority target, then go // through the list again to eliminate any users that may not be a target // anymore and to fetch a new highest-priority target. while(freeslots > 0 && target) { if(dl_queue_start_user(target) && !--freeslots) break; g_ptr_array_remove_index_fast(targets, target_i); int i = 0; target = NULL; while(i < targets->len) { du = g_ptr_array_index(targets, i); if(!dl_queue_start_istarget(du)) g_ptr_array_remove_index_fast(targets, i); else { if(!target || dl_queue_start_cmp(target, du) > 0) { target_i = i; target = du; } i++; } } } g_ptr_array_unref(targets); // Reset this value *after* performing all the checks and starts, to ignore // any dl_queue_start() calls while this function was working - this function // already takes those changes into account anyway. dl_queue_needstart = FALSE; return FALSE; } // Make sure dl_queue_start() can be called at any time that something changed // that might allow us to initiate a download again. Since this is a relatively // expensive operation, dl_queue_start() simply queues a dl_queue_start_do() // from a timer. // TODO: Make the timeout configurable? It's a tradeoff between download // management responsiveness and CPU usage. void dl_queue_start() { if(!dl_queue_needstart) { dl_queue_needstart = TRUE; g_timeout_add(500, dl_queue_start_do, NULL); } } // Adding stuff to the download queue // Adds a dl item to the queue. dl->inc will be determined and opened here. // dl->hastthl will be set if the file is small enough to not need TTHL data. // dl->u is also created here. static void dl_queue_insert(dl_t *dl, gboolean init) { // Set dl->hastthl for files smaller than MINTTHLSIZE. if(!dl->islist && !dl->hastthl && dl->size <= DL_MINTTHLSIZE) { dl->hastthl = TRUE; dl->hash_block = DL_MINTTHLSIZE; } // figure out dl->inc char hash[40] = {}; base32_encode(dl->hash, hash); dl->inc = g_build_filename(var_get(0, VAR_incoming_dir), hash, NULL); // create dl->u dl->u = g_ptr_array_new(); // insert in the global queue g_hash_table_insert(dl_queue, dl->hash, dl); uit_dl_listchange(dl, UITDL_ADD); // insert in the database if(!dl->islist && !init) db_dl_insert(dl->hash, dl->size, dl->dest, dl->prio, dl->error, dl->error_msg); // start download, if possible if(!init) dl_queue_start(); } // Add the file list of some user to the queue void dl_queue_addlist(hub_user_t *u, const char *sel, ui_tab_t *parent, gboolean open, gboolean match) { g_return_if_fail(u && u->hasinfo); dl_t *dl = g_slice_new0(dl_t); dl->islist = TRUE; g_mutex_init(&dl->lock); if(sel) dl->flsel = g_strdup(sel); dl->flpar = parent; dl->flopen = open; dl->flmatch = match; // figure out dl->hash tiger_ctx_t tg; tiger_init(&tg); tiger_update(&tg, (char *)&u->uid, 8); tiger_final(&tg, dl->hash); dl_t *dup = g_hash_table_lookup(dl_queue, dl->hash); if(dup) { if(open) dup->flopen = TRUE; if(match) dup->flmatch = TRUE; g_warning("dl:%016"G_GINT64_MODIFIER"x: files.xml.bz2 already in the queue, updating flags.", u->uid); g_slice_free(dl_t, dl); return; } // figure out dl->dest char *fn = g_strdup_printf("%016"G_GINT64_MODIFIER"x.xml.bz2", u->uid); dl->dest = g_build_filename(db_dir, "fl", fn, NULL); g_free(fn); // insert & start g_debug("dl:%016"G_GINT64_MODIFIER"x: queueing files.xml.bz2", u->uid); dl_queue_insert(dl, FALSE); dl_user_add(dl, u->uid, 0, NULL); } // Add a regular file to the queue. If there is another file in the queue with // the same filename, something else will be chosen instead. // Returns true if it was added, false if it was already in the queue. static gboolean dl_queue_addfile(guint64 uid, char *hash, guint64 size, char *fn) { if(g_hash_table_lookup(dl_queue, hash)) return FALSE; dl_t *dl = g_slice_new0(dl_t); g_mutex_init(&dl->lock); memcpy(dl->hash, hash, 24); dl->size = size; // Figure out dl->dest dl->dest = g_build_filename(var_get(0, VAR_download_dir), fn, NULL); // and add to the queue g_debug("dl:%016"G_GINT64_MODIFIER"x: queueing %s", uid, fn); dl_queue_insert(dl, FALSE); dl_user_add(dl, uid, 0, NULL); db_dl_adduser(dl->hash, uid, 0, NULL); // Empty files get a shortcut here if(!size) dlfile_finished(dl); return TRUE; } // Recursively adds a file or directory to the queue. *excl will only be // checked for files in subdirectories, if *fl is a file it will always be // added. void dl_queue_add_fl(guint64 uid, fl_list_t *fl, char *base, GRegex *excl) { // check excl if(base && excl && g_regex_match(excl, fl->name, 0, NULL)) { ui_mf(NULL, 0, "Ignoring `%s': excluded by regex.", fl->name); return; } { // don't download already shared files if download_shared is set to false. GSList *localfl = fl_local_from_tth(fl->tth); if(!var_get_bool(0, VAR_download_shared) && fl->hastth && localfl && localfl->data) { fl_list_t *localf = localfl->data; ui_mf(NULL, 0, "Ignoring `%s' : already shared as `%s'", fl->name, localf->name); return; } } char *name = base ? g_build_filename(base, fl->name, NULL) : g_strdup(fl->name); if(fl->isfile) { if(!dl_queue_addfile(uid, fl->tth, fl->size, name)) ui_mf(NULL, 0, "Ignoring `%s': already queued.", name); } else { int i; for(i=0; isub->len; i++) dl_queue_add_fl(uid, g_ptr_array_index(fl->sub, i), name, excl); } if(!base) ui_mf(NULL, 0, "%s added to queue.", name); g_free(name); } // Add a search result to the queue. (Only for files) void dl_queue_add_res(search_r_t *r) { char *name = strrchr(r->file, '/'); if(name) name++; else name = r->file; if(dl_queue_addfile(r->uid, r->tth, r->size, name)) ui_mf(NULL, 0, "%s added to queue.", name); else ui_m(NULL, 0, "Already queued."); } // Add a user to a dl item, if the file is in the queue and the user hasn't // been added yet. Returns: // -1 Not found in queue // 0 Found, but user already queued // 1 Found and user added to the queue int dl_queue_matchfile(guint64 uid, char *tth) { dl_t *dl = g_hash_table_lookup(dl_queue, tth); if(!dl) return -1; int i; for(i=0; iu->len; i++) if(((dl_user_dl_t *)g_sequence_get(g_ptr_array_index(dl->u, i)))->u->uid == uid) return 0; dl_user_add(dl, uid, 0, NULL); db_dl_adduser(dl->hash, uid, 0, NULL); dl_queue_start(); return 1; } // Recursively walks through the file list and adds the user to matching dl // items. Returns the number of items found, and the number of items for which // the user was added is stored in *added (should be initialized to zero). int dl_queue_match_fl(guint64 uid, fl_list_t *fl, int *added) { if(fl->isfile && fl->hastth) { int r = dl_queue_matchfile(uid, fl->tth); if(r == 1) (*added)++; return r >= 0 ? 1 : 0; } else { int n = 0; int i; for(i=0; isub->len; i++) n += dl_queue_match_fl(uid, g_ptr_array_index(fl->sub, i), added); return n; } } // Removing stuff from the queue and changing priorities // removes an item from the queue void dl_queue_rm(dl_t *dl) { // remove from the user info (this will also force a disconnect if the item // is being downloaded.) while(dl->u->len > 0) dl_user_rm(dl, 0); // remove from dl list if(g_hash_table_lookup(dl_queue, dl->hash)) { uit_dl_listchange(dl, UITDL_DEL); g_hash_table_remove(dl_queue, dl->hash); } // Don't do anything else if there is still an active downloading thread. // Wait until all threads stop this function is called again to actually free // and remove the stuff. if(dl->active_threads) return; // remove from the database if(!dl->islist) db_dl_rm(dl->hash); // free and remove dl struct // and free dlfile_rm(dl); g_ptr_array_unref(dl->u); g_free(dl->inc); g_free(dl->flsel); g_free(dl->dest); g_free(dl->error_msg); g_slice_free(dl_t, dl); } void dl_queue_setprio(dl_t *dl, signed char prio) { gboolean enabled = dl->prio <= DLP_OFF && prio > DLP_OFF; dl->prio = prio; db_dl_setstatus(dl->hash, dl->prio, dl->error, dl->error_msg); // Make sure the dl_user.queue lists are still in the correct order int i; for(i=0; iu->len; i++) g_sequence_sort_changed(g_ptr_array_index(dl->u, i), dl_user_dl_sort, NULL); // Start downloading or re-attempt finalization if it is enabled if(enabled) { if(!dl->active_threads && (dl->hassize || !dl->islist) && dl->have == dl->size) dlfile_finished(dl); else dl_queue_start(); } /* TODO: Disconnect active users if the dl item is disabled */ } void dl_queue_seterr(dl_t *dl, char e, const char *sub) { dl->error = e; g_free(dl->error_msg); dl->error_msg = sub ? g_strdup(sub) : NULL; dl_queue_setprio(dl, DLP_ERR); g_debug("Download of `%s' failed: %s", dl->dest, dl_strerror(e, sub)); ui_mf(uit_main_tab, 0, "Download of `%s' failed: %s", dl->dest, dl_strerror(e, sub)); } // Set a user-specific error. If tth = NULL, the error will be set for all // files in the queue. void dl_queue_setuerr(guint64 uid, char *tth, char e, const char *emsg) { dl_t *dl = tth ? g_hash_table_lookup(dl_queue, tth) : NULL; dl_user_t *du = g_hash_table_lookup(queue_users, &uid); if(!du || (tth && !dl)) return; g_debug("%016"G_GINT64_MODIFIER"x: Setting download error for `%s' to: %s", uid, dl?dl->dest:"all", dl_strerror(e, emsg)); // from a single dl item if(dl) { int i; for(i=0; iu->len; i++) { GSequenceIter *iter = g_ptr_array_index(dl->u, i); dl_user_dl_t *dud = g_sequence_get(iter); if(dud->u == du) { dud->error = e; g_free(dud->error_msg); dud->error_msg = emsg ? g_strdup(emsg) : NULL; g_sequence_sort_changed(iter, dl_user_dl_sort, NULL); break; } } // for all dl items } else { GSequenceIter *i = g_sequence_get_begin_iter(du->queue); for(; !g_sequence_iter_is_end(i); i=g_sequence_iter_next(i)) { dl_user_dl_t *dud = g_sequence_get(i); dud->error = e; g_free(dud->error_msg); dud->error_msg = emsg ? g_strdup(emsg) : NULL; } // Do the sort after looping through all items - looping through the list // while changing the ordering may cause problems. g_sequence_sort(du->queue, dl_user_dl_sort, NULL); } // update DB db_dl_setuerr(uid, tth, e, emsg); dl_queue_start(); } // Remove a user from the queue for a certain file. If tth = NULL, the user // will be removed from the queue entirely. void dl_queue_rmuser(guint64 uid, char *tth) { dl_t *dl = tth ? g_hash_table_lookup(dl_queue, tth) : NULL; dl_user_t *du = g_hash_table_lookup(queue_users, &uid); if(!du || (tth && !dl)) return; // from a single dl item if(dl) { int i; for(i=0; iu->len; i++) { if(((dl_user_dl_t *)g_sequence_get(g_ptr_array_index(dl->u, i)))->u == du) { dl_user_rm(dl, i); break; } } if(dl->islist && !dl->u->len) dl_queue_rm(dl); // from all dl items (may be fairly slow) } else { // The loop is written in this way because after calling dl_user_rm(): // 1. The current GSequenceIter is freed. // 2. The entire du struct and the GSequence may have been freed as well, // if there were no other items left in its queue. GSequenceIter *n, *i = g_sequence_get_begin_iter(du->queue); gboolean run = !g_sequence_iter_is_end(i); while(run) { n = g_sequence_iter_next(i); run = !g_sequence_iter_is_end(n); dl_t *dl = ((dl_user_dl_t *)g_sequence_get(i))->dl; int j; for(j=0; ju->len; j++) { if(g_ptr_array_index(dl->u, j) == i) { dl_user_rm(dl, j); break; } } if(dl->islist && !dl->u->len) dl_queue_rm(dl); i = n; } } // Remove from the database db_dl_rmuser(uid, tth); } // Managing of active downloads // Called when we've got a complete file void dl_finished(dl_t *dl) { g_debug("dl: download of `%s' finished, removing from queue", dl->dest); // open the file list if(dl->islist && dl->prio != DLP_ERR) { g_return_if_fail(dl->u->len == 1); // Ugly hack: make sure to not select the browse tab, if one is opened GList *cur = ui_tab_cur; uit_fl_queue(((dl_user_dl_t *)g_sequence_get(g_ptr_array_index(dl->u, 0)))->u->uid, FALSE, dl->flsel, dl->flpar, dl->flopen, dl->flmatch); ui_tab_cur = cur; } dl_queue_rm(dl); } // Called when we've received TTHL data. The *tthl data may be modified // in-place. void dl_settthl(guint64 uid, char *tth, char *tthl, int len) { dl_t *dl = g_hash_table_lookup(dl_queue, tth); dl_user_t *du = g_hash_table_lookup(queue_users, &uid); if(!dl || !du) return; g_return_if_fail(du->state == DLU_ACT); g_return_if_fail(!dl->islist); // We accidentally downloaded the TTHL from multiple users. Just discard this data. if(dl->hastthl) return; g_debug("dl:%016"G_GINT64_MODIFIER"x: Received TTHL data for %s (len = %d, bs = %"G_GUINT64_FORMAT")", uid, dl->dest, len, tth_blocksize(dl->size, len/24)); // Validate correctness with the root hash char root[24]; tth_root(tthl, len/24, root); if(memcmp(root, dl->hash, 24) != 0) { g_warning("dl:%016"G_GINT64_MODIFIER"x: Incorrect TTHL for %s.", uid, dl->dest); dl_queue_setuerr(uid, tth, DLE_INVTTHL, NULL); return; } // If the blocksize is smaller than MINBLOCKSIZE, combine blocks. guint64 bs = tth_blocksize(dl->size, len/24); unsigned int cl = 1; // number of blocks to combine into a single block while(bs < DL_MINBLOCKSIZE) { bs <<= 2; cl <<= 2; } int newlen = tth_num_blocks(dl->size, bs)*24; int i; // Shrink the TTHL data in-place. for(i=0; cl>1 && idest, newlen, bs); db_dl_settthl(tth, tthl, newlen); dl->hastthl = TRUE; dl->hash_block = bs; } // Loading/initializing the download queue on startup // Creates and inserts a dl_t item from the database in the queue void dl_load_dl(const char *tth, guint64 size, const char *dest, signed char prio, char error, const char *error_msg, int tthllen) { g_return_if_fail(dest); dl_t *dl = g_slice_new0(dl_t); g_mutex_init(&dl->lock); memcpy(dl->hash, tth, 24); dl->size = size; dl->prio = prio; dl->error = error; dl->error_msg = error_msg ? g_strdup(error_msg) : NULL; dl->dest = g_strdup(dest); if(dl->size < DL_MINTTHLSIZE) { dl->hastthl = TRUE; dl->hash_block = DL_MINTTHLSIZE; } else if(tthllen) { dl->hastthl = TRUE; dl->hash_block = tth_blocksize(dl->size, tthllen/24); } dl_queue_insert(dl, TRUE); } // Creates and adds a dl_user_t/dl_user_dl from the database in the queue void dl_load_dlu(const char *tth, guint64 uid, char error, const char *error_msg) { dl_t *dl = g_hash_table_lookup(dl_queue, tth); g_return_if_fail(dl); dl_user_add(dl, uid, error, error_msg); } void dl_init_global() { queue_users = g_hash_table_new(g_int64_hash, g_int64_equal); dl_queue = g_hash_table_new(g_int_hash, tiger_hash_equal); // load stuff from the database db_dl_getdls(dl_load_dl); db_dl_getdlus(dl_load_dlu); // load/check the data we've already downloaded dl_t *dl; GHashTableIter iter; g_hash_table_iter_init(&iter, dl_queue); while(g_hash_table_iter_next(&iter, NULL, (gpointer *)&dl)) dlfile_load(dl); // Delete old filelists dl_fl_clean(NULL); } void dl_close_global() { // Delete incomplete file lists. They won't be completed anyway. GHashTableIter iter; dl_t *dl; g_hash_table_iter_init(&iter, dl_queue); while(g_hash_table_iter_next(&iter, NULL, (gpointer *)&dl)) if(dl->islist) unlink(dl->inc); // Delete old filelists dl_fl_clean(NULL); } // Various cleanup/gc utilities // Removes old filelists from /fl/. Can be run from a timer. gboolean dl_fl_clean(gpointer dat) { char *dir = g_build_filename(db_dir, "fl", NULL); GDir *d = g_dir_open(dir, 0, NULL); if(!d) { g_free(dir); return TRUE; } const char *n; time_t ref = time(NULL) - var_get_int(0, VAR_filelist_maxage); while((n = g_dir_read_name(d))) { if(strcmp(n, ".") == 0 || strcmp(n, "..") == 0) continue; char *fn = g_build_filename(dir, n, NULL); struct stat st; if(stat(fn, &st) >= 0 && st.st_mtime < ref) unlink(fn); g_free(fn); } g_dir_close(d); g_free(dir); return TRUE; } // Removes unused files in . void dl_inc_clean() { char *dir = var_get(0, VAR_incoming_dir); GDir *d = g_dir_open(dir, 0, NULL); if(!d) return; const char *n; char hash[24]; while((n = g_dir_read_name(d))) { // Only consider files that we have created, which always happen to have a // base32-encoded hash as filename. if(!istth(n)) continue; base32_decode(n, hash); if(g_hash_table_lookup(dl_queue, hash)) continue; // not in the queue? delete. char *fn = g_build_filename(dir, n, NULL); unlink(fn); g_free(fn); } g_dir_close(d); } ncdc-1.23.1/src/ncdc.h0000644000175000017500000000377314245144523011313 00000000000000/* ncdc - NCurses Direct Connect client Copyright (c) 2011-2022 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_LINUX_SENDFILE # include #elif HAVE_BSD_SENDFILE # include # include #endif #include #include #include #include #include #include #include #include #include #include #ifndef _XOPEN_SOURCE_EXTENDED #define _XOPEN_SOURCE_EXTENDED #endif #include ncdc-1.23.1/src/proto.c0000644000175000017500000002522214245144532011533 00000000000000/* ncdc - NCurses Direct Connect client Copyright (c) 2011-2022 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "ncdc.h" #include "proto.h" // NMDC support char *charset_convert(hub_t *hub, gboolean to_utf8, const char *str) { char *fmt = var_get(hub->id, VAR_encoding); char *res = str_convert(to_utf8?"UTF-8":fmt, !to_utf8?"UTF-8":fmt, str); return res; } char *nmdc_encode_and_escape(hub_t *hub, const char *str) { char *enc = charset_convert(hub, FALSE, str); GString *dest = g_string_sized_new(strlen(enc)); char *tmp = enc; while(*tmp) { if(*tmp == '$') g_string_append(dest, "$"); else if(*tmp == '|') g_string_append(dest, "|"); else if(*tmp == '&' && (strncmp(tmp, "&", 5) == 0 || strncmp(tmp, "$", 5) == 0 || strncmp(tmp, "|", 6) == 0)) g_string_append(dest, "&"); else g_string_append_c(dest, *tmp); tmp++; } g_free(enc); return g_string_free(dest, FALSE); } char *nmdc_unescape_and_decode(hub_t *hub, const char *str) { GString *dest = g_string_sized_new(strlen(str)); while(*str) { if(strncmp(str, "$", 5) == 0) { g_string_append_c(dest, '$'); str += 5; } else if(strncmp(str, "|", 6) == 0) { g_string_append_c(dest, '|'); str += 6; } else if(strncmp(str, "&", 5) == 0) { g_string_append_c(dest, '&'); str += 5; } else { g_string_append_c(dest, *str); str++; } } char *dec = charset_convert(hub, TRUE, dest->str); g_string_free(dest, TRUE); return dec; } // Info & algorithm @ http://www.teamfair.info/wiki/index.php?title=Lock_to_key // This function modifies "lock" in-place for temporary data char *nmdc_lock2key(char *lock) { char n; int i; int len = strlen(lock); if(len < 3) return g_strdup("STUPIDKEY!"); // let's not crash on invalid data int fst = lock[0] ^ lock[len-1] ^ lock[len-2] ^ 5; for(i=len-1; i; i--) lock[i] = lock[i] ^ lock[i-1]; lock[0] = fst; for(i=0; i>4) & 0x0F); GString *key = g_string_sized_new(len+100); for(i=0; i> 0) & 0xFF;\ (str)[1] = ((sid)>> 8) & 0xFF;\ (str)[2] = ((sid)>>16) & 0xFF;\ (str)[3] = ((sid)>>24) & 0xFF;\ } while(0) // and the reverse #define ADC_DFCC(str) ((str)[0] + ((str)[1]<<8) + ((str)[2]<<16) + ((str)[3]<<24)) #define ADC_TOCMDV(a, b, c) ((a) + ((b)<<8) + ((c)<<16)) #define ADC_TOCMD(str) ADC_TOCMDV((str)[0], (str)[1], (str)[2]) enum adc_cmd_type { #define C(n, a, b, c) ADCC_##n = ADC_TOCMDV(a,b,c) // Base commands (copied from DC++ / AdcCommand.h) C(SUP, 'S','U','P'), // F,T,C - PROTOCOL, NORMAL C(STA, 'S','T','A'), // F,T,C,U - All C(INF, 'I','N','F'), // F,T,C - IDENTIFY, NORMAL C(MSG, 'M','S','G'), // F,T - NORMAL C(SCH, 'S','C','H'), // F,T,C - NORMAL (can be in U, but is discouraged) C(RES, 'R','E','S'), // F,T,C,U - NORMAL C(CTM, 'C','T','M'), // F,T - NORMAL C(RCM, 'R','C','M'), // F,T - NORMAL C(GPA, 'G','P','A'), // F - VERIFY C(PAS, 'P','A','S'), // T - VERIFY C(QUI, 'Q','U','I'), // F - IDENTIFY, VERIFY, NORMAL C(GET, 'G','E','T'), // C - NORMAL (extensions may use in it F/T as well) C(GFI, 'G','F','I'), // C - NORMAL C(SND, 'S','N','D'), // C - NORMAL (extensions may use it in F/T as well) C(SID, 'S','I','D') // F - PROTOCOL #undef C }; struct adc_cmd_t { char type; // B|C|D|E|F|H|I|U adc_cmd_type cmd; // ADCC_*, but can also be something else. Unhandled commands should be ignored anyway. int source; // Only when type = B|D|E|F int dest; // Only when type = D|E char **argv; int argc; }; // ADC Protocol states. #define ADC_S_PROTOCOL 0 #define ADC_S_IDENTIFY 1 #define ADC_S_VERIFY 2 #define ADC_S_NORMAL 3 #define ADC_S_DATA 4 #endif static gboolean int_in_array(const int *arr, int needle) { for(; arr&&*arr; arr++) if(*arr == needle) return TRUE; return FALSE; } gboolean adc_parse(const char *str, adc_cmd_t *c, int *feats, GError **err) { if(!g_utf8_validate(str, -1, NULL)) { g_set_error_literal(err, 1, 0, "Invalid encoding."); return FALSE; } if(strlen(str) < 4) { g_set_error_literal(err, 1, 0, "Message too short."); return FALSE; } if(*str != 'B' && *str != 'C' && *str != 'D' && *str != 'E' && *str != 'F' && *str != 'H' && *str != 'I' && *str != 'U') { g_set_error_literal(err, 1, 0, "Invalid ADC type"); return FALSE; } c->type = *str; c->cmd = ADC_TOCMD(str+1); const char *off = str+4; if(off[0] && off[0] != ' ') { g_set_error_literal(err, 1, 0, "Invalid characters after command."); return FALSE; } if(off[0]) off++; // type = U, first argument is source CID. But we don't handle that here. // type = B|D|E|F, first argument must be the source SID if(c->type == 'B' || c->type == 'D' || c->type == 'E' || c->type == 'F') { if(strlen(off) < 4) { g_set_error_literal(err, 1, 0, "Message too short"); return FALSE; } c->source = ADC_DFCC(off); if(off[4] && off[4] != ' ') { g_set_error_literal(err, 1, 0, "Invalid characters after argument."); return FALSE; } off += off[4] ? 5 : 4; } // type = D|E, next argument must be the destination SID if(c->type == 'D' || c->type == 'E') { if(strlen(off) < 4) { g_set_error_literal(err, 1, 0, "Message too short"); return FALSE; } c->dest = ADC_DFCC(off); if(off[4] && off[4] != ' ') { g_set_error_literal(err, 1, 0, "Invalid characters after argument."); return FALSE; } off += off[4] ? 5 : 4; } // type = F, next argument must be the feature list. We'll match this with // the 'feats' list (space separated list of FOURCCs, to make things easier) // to make sure it's correct. Some hubs broadcast F messages without actually // checking the listed features. :-/ if(c->type == 'F') { int l = strchr(off, ' ') ? strchr(off, ' ')-off : strlen(off); if((l % 5) != 0) { g_set_error_literal(err, 1, 0, "Message too short"); return FALSE; } int i; for(i=0; iargc = s ? g_strv_length(s) : 0; if(s) { char **a = g_new0(char *, c->argc+1); int i; for(i=0; iargc; i++) { a[i] = adc_unescape(s[i], FALSE); if(!a[i]) { g_set_error_literal(err, 1, 0, "Invalid escape in argument."); break; } } g_strfreev(s); if(i < c->argc) { g_strfreev(a); return FALSE; } c->argv = a; } else c->argv = NULL; return TRUE; } char *adc_getparam(char **a, char *name, char ***left) { while(a && *a) { if(**a && **a == name[0] && (*a)[1] == name[1]) { if(left) *left = a+1; return *a+2; } a++; } return NULL; } // Get all parameters with the given name. Return value should be g_free()'d, // NOT g_strfreev()'ed. char **adc_getparams(char **a, char *name) { int n = 10; char **res = g_new(char *, n); int i = 0; while(a && *a) { if(**a && **a == name[0] && (*a)[1] == name[1]) res[i++] = *a+2; if(i >= n) { n += 10; res = g_realloc(res, n*sizeof(char *)); } a++; } res[i] = NULL; if(res[0]) return res; g_free(res); return NULL; } GString *adc_generate(char type, int cmd, int source, int dest) { GString *c = g_string_sized_new(100); g_string_append_c(c, type); char r[5] = {}; ADC_EFCC(cmd, r); g_string_append(c, r); if(source) { g_string_append_c(c, ' '); ADC_EFCC(source, r); g_string_append(c, r); } if(dest) { g_string_append_c(c, ' '); ADC_EFCC(dest, r); g_string_append(c, r); } return c; } void adc_append(GString *c, const char *name, const char *arg) { g_string_append_c(c, ' '); if(name) g_string_append(c, name); char *enc = adc_escape(arg, FALSE); g_string_append(c, enc); g_free(enc); } ncdc-1.23.1/src/uit_conn.c0000644000175000017500000002650414310330714012202 00000000000000/* ncdc - NCurses Direct Connect client Copyright (c) 2011-2022 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "ncdc.h" #include "uit_conn.h" ui_tab_type_t uit_conn[1]; typedef struct tab_t { ui_tab_t tab; ui_listing_t *list; gboolean details : 1; gboolean s_conn : 1; gboolean s_idle : 1; gboolean s_upload : 1; gboolean s_download : 1; gboolean s_discon : 1; } tab_t; // There can only be at most one connections tab. static tab_t *conntab; static gint sort_func(gconstpointer da, gconstpointer db, gpointer dat) { const cc_t *a = da; const cc_t *b = db; int o = 0; if(!o && !a->nick != !b->nick) o = a->nick ? 1 : -1; if(!o && a->nick && b->nick) o = strcmp(a->nick, b->nick); if(!o && a->hub && b->hub) o = strcmp(a->hub->tab->name, b->hub->tab->name); return o; } static gboolean skip_func(ui_listing_t *ul, GSequenceIter *iter, void *dat) { tab_t *t = dat; cc_t *cc = g_sequence_get(iter); return cc->state == CCS_CONN || cc->state == CCS_HANDSHAKE ? !t->s_conn : cc->state == CCS_DISCONN ? !t->s_discon : cc->state == CCS_IDLE ? !t->s_idle : cc->dl ? !t->s_download : !t->s_upload; } ui_tab_t *uit_conn_create() { g_return_val_if_fail(!conntab, NULL); tab_t *t = conntab = g_new0(tab_t, 1); t->tab.name = "connections"; t->tab.type = uit_conn; t->s_conn = t->s_idle = t->s_upload = t->s_download = t->s_discon = TRUE; // sort the connection list g_sequence_sort(cc_list, sort_func, NULL); t->list = ui_listing_create(cc_list, skip_func, t, NULL); return (ui_tab_t *)t; } static void t_close(ui_tab_t *tab) { g_return_if_fail((ui_tab_t *)conntab == tab); ui_tab_remove(tab); ui_listing_free(conntab->list); g_free(conntab); conntab = NULL; } static char *t_title() { return g_strdup("Connection list"); } static void t_draw_row(ui_listing_t *list, GSequenceIter *iter, int row, void *dat) { cc_t *cc = g_sequence_get(iter); attron(iter == list->sel ? UIC(list_select) : UIC(list_default)); mvhline(row, 0, ' ', wincols); if(iter == list->sel) mvaddstr(row, 0, ">"); mvaddch(row, 2, cc->state == CCS_CONN ? 'C' : cc->state == CCS_DISCONN ? '-' : cc->state == CCS_HANDSHAKE ? 'H' : cc->state == CCS_IDLE ? 'I' : cc->dl ? 'D' : 'U'); mvaddch(row, 3, cc->tls ? 't' : ' '); char *tmp = cc->nick ? cc->nick : cc->remoteaddr; mvaddnstr(row, 5, tmp, str_offset_from_columns(tmp, 15)); if(cc->hub) mvaddnstr(row, 21, cc->hub->tab->name, str_offset_from_columns(cc->hub->tab->name, 11)); mvaddstr(row, 33, cc->last_length ? str_formatsize(cc->last_length) : "-"); if(cc->last_length && !cc->timeout_src) { float length = cc->last_length; mvprintw(row, 45, "%3.0f%%", (length-net_left(cc->net))*100.0f/length); } else mvaddstr(row, 45, " -"); if(cc->timeout_src) mvaddstr(row, 50, " -"); else mvprintw(row, 50, "%6d", ratecalc_rate(cc->dl ? net_rate_in(cc->net) : net_rate_out(cc->net))/1024); if(cc->err) { mvaddstr(row, 58, "Disconnected: "); addnstr(cc->err->message, str_offset_from_columns(cc->err->message, wincols-(58+14))); } else if(cc->last_file) { char *file = strrchr(cc->last_file, '/'); if(file) file++; else file = cc->last_file; mvaddnstr(row, 58, file, str_offset_from_columns(file, wincols-58)); } attroff(iter == list->sel ? UIC(list_select) : UIC(list_default)); } static void t_draw_details(tab_t *t, int l) { cc_t *cc = g_sequence_iter_is_end(t->list->sel) ? NULL : g_sequence_get(t->list->sel); if(!cc) { mvaddstr(l+1, 0, "Nothing selected."); return; } // labels attron(A_BOLD); mvaddstr(l+1, 3, "Username:"); mvaddstr(l+1, 43, "IP:"); mvaddstr(l+2, 8, "Hub:"); mvaddstr(l+2, 39, "Status:"); mvaddstr(l+3, 9, "Up:"); mvaddstr(l+3, 41, "Down:"); mvaddstr(l+4, 7, "Size:"); mvaddstr(l+5, 5, "Offset:"); mvaddstr(l+6, 6, "Chunk:"); mvaddstr(l+4, 37, "Progress:"); mvaddstr(l+5, 42, "ETA:"); mvaddstr(l+6, 41, "Idle:"); mvaddstr(l+7, 7, "File:"); mvaddstr(l+8, 1, "Last error:"); attroff(A_BOLD); // line 1 mvaddstr(l+1, 13, cc->nick ? cc->nick : "Unknown / connecting"); mvaddstr(l+1, 47, cc->remoteaddr); // line 2 mvaddstr(l+2, 13, cc->hub ? cc->hub->tab->name : "-"); mvaddstr(l+2, 47, cc->state == CCS_CONN ? "Connecting" : cc->state == CCS_DISCONN ? "Disconnected" : cc->state == CCS_HANDSHAKE ? "Handshake" : cc->state == CCS_IDLE ? "Idle" : cc->dl ? "Downloading" : "Uploading"); // line 3 mvprintw(l+3, 13, "%d KiB/s (%s)", ratecalc_rate(net_rate_out(cc->net))/1024, str_formatsize(ratecalc_total(net_rate_out(cc->net)))); mvprintw(l+3, 47, "%d KiB/s (%s)", ratecalc_rate(net_rate_in(cc->net))/1024, str_formatsize(ratecalc_total(net_rate_in(cc->net)))); // size / offset / chunk (line 4/5/6) mvaddstr(l+4, 13, cc->last_size ? str_formatsize(cc->last_size) : "-"); mvaddstr(l+5, 13, cc->last_size ? str_formatsize(cc->last_offset) : "-"); mvaddstr(l+6, 13, cc->last_length ? str_formatsize(cc->last_length) : "-"); // progress / eta / idle (line 4/5/6) if(cc->last_length && !cc->timeout_src) { float length = cc->last_length; mvprintw(l+4, 47, "%3.0f%%", (length-net_left(cc->net))*100.0f/length); } else mvaddstr(l+4, 47, "-"); if(cc->last_length && !cc->timeout_src) mvaddstr(l+5, 47, ratecalc_eta(cc->dl ? net_rate_in(cc->net) : net_rate_out(cc->net), net_left(cc->net))); else mvaddstr(l+5, 47, "-"); mvprintw(l+6, 47, "%ds", (int)(time(NULL)-net_last_activity(cc->net))); // line 7 if(cc->last_file) mvaddnstr(l+7, 13, cc->last_file, str_offset_from_columns(cc->last_file, wincols-13)); else mvaddstr(l+7, 13, "None."); // line 8 if(cc->err) mvaddnstr(l+8, 13, cc->err->message, str_offset_from_columns(cc->err->message, wincols-13)); else mvaddstr(l+8, 13, "-"); } static void t_draw(ui_tab_t *tab) { tab_t *t = (tab_t *)tab; // The skip function itself doesn't really change that often (only when user // modifies the filters). However, the state of the connections does change a // lot, and we're not notified on that. Luckily, _skipchanged() is fast so we // can call it on each redraw. ui_listing_skipchanged(t->list); attron(UIC(list_header)); mvhline(1, 0, ' ', wincols); mvaddstr(1, 2, "St Username"); mvaddstr(1, 21, "Hub"); mvaddstr(1, 33, "Chunk %"); mvaddstr(1, 50, " KiB/s"); mvaddstr(1, 58, "File"); attroff(UIC(list_header)); int bottom = t->details ? winrows-11 : winrows-3; ui_cursor_t cursor; ui_listing_draw(t->list, 2, bottom-1, &cursor, t_draw_row); // footer attron(UIC(separator)); mvhline(bottom, 0, ' ', wincols); #define S(name, str)\ if(t->s_##name)\ attron(A_BOLD);\ addstr(str);\ if(t->s_##name)\ attroff(A_BOLD);\ addch(' '); S(conn, "1:Connecting"); S(idle, "2:Idle"); S(upload, "3:Upload"); S(download, "4:Download"); S(discon, "5:Disconnected"); #undef S mvprintw(bottom, wincols-18, "%3d connections ", g_sequence_iter_get_position(g_sequence_get_end_iter(t->list->list))); attroff(UIC(separator)); // detailed info if(t->details) t_draw_details(t, bottom); move(cursor.y, cursor.x); } static void t_key(ui_tab_t *tab, guint64 key) { tab_t *t = (tab_t *)tab; if(ui_listing_key(t->list, key, (winrows-10)/2)) return; cc_t *cc = g_sequence_iter_is_end(t->list->sel) ? NULL : g_sequence_get(t->list->sel); switch(key) { case INPT_CHAR('?'): uit_main_keys("connections"); break; case INPT_CTRL('j'): // newline case INPT_CHAR('i'): // i - toggle detailed info t->details = !t->details; break; case INPT_CHAR('f'): // f - find user if(!cc) ui_m(NULL, 0, "Nothing selected."); else if(!cc->hub || !cc->uid) ui_m(NULL, 0, "User or hub unknown."); else if(!uit_userlist_open(cc->hub, cc->uid, NULL, FALSE)) ui_m(NULL, 0, "User has left the hub."); break; case INPT_CHAR('m'): // m - /msg user if(!cc) ui_m(NULL, 0, "Nothing selected."); else if(!cc->hub || !cc->uid) ui_m(NULL, 0, "User or hub unknown."); else if(!uit_msg_open(cc->uid, tab)) ui_m(NULL, 0, "User has left the hub."); break; case INPT_CHAR('d'): // d - disconnect if(!cc) ui_m(NULL, 0, "Nothing selected."); else if(net_is_idle(cc->net)) ui_m(NULL, 0, "Not connected."); else cc_disconnect(cc, FALSE); break; case INPT_CHAR('q'): // q - find queue item if(!cc) ui_m(NULL, 0, "Nothing selected."); else if(!cc->dl || !cc->last_file) ui_m(NULL, 0, "Not downloading a file."); else { dl_t *dl = g_hash_table_lookup(dl_queue, cc->last_hash); if(!dl) ui_m(NULL, 0, "File has been removed from the queue."); else uit_dl_open(dl, cc->uid, tab); } break; case INPT_CHAR('b'): // b (/browse userlist) case INPT_CHAR('B'): // B (force /browse userlist) if(!cc) ui_m(NULL, 0, "Nothing selected."); else if(!cc->hub || !cc->uid) ui_m(NULL, 0, "User or hub unknown."); else if(!g_hash_table_lookup(hub_uids, &cc->uid)) ui_m(NULL, 0, "User is not online."); else uit_fl_queue(cc->uid, key == INPT_CHAR('B'), NULL, tab, TRUE, FALSE); break; #define S(num, name)\ case INPT_CHAR('0'+num):\ t->s_##name = !t->s_##name;\ break; S(1, conn); S(2, idle); S(3, upload); S(4, download); S(5, discon); #undef S } } #if INTERFACE #define UITCONN_ADD 0 #define UITCONN_DEL 1 #define UITCONN_MOD 2 // when the nick or hub changes #endif void uit_conn_listchange(GSequenceIter *iter, int change) { if(!conntab) return; switch(change) { case UITCONN_ADD: g_sequence_sort_changed(iter, sort_func, NULL); ui_listing_inserted(conntab->list); break; case UITCONN_DEL: ui_listing_remove(conntab->list, iter); break; case UITCONN_MOD: g_sequence_sort_changed(iter, sort_func, NULL); ui_listing_sorted(conntab->list); break; } } // Opens the conn tab (if it's not open yet) and selects the specified cc item, // if that's not NULL. void uit_conn_open(cc_t *cc, ui_tab_t *parent) { if(conntab) ui_tab_cur = g_list_find(ui_tabs, conntab); else ui_tab_open(uit_conn_create(), TRUE, parent); // cc->iter should be valid at this point if(cc) conntab->list->sel = cc->iter; } ui_tab_type_t uit_conn[1] = { { t_draw, t_title, t_key, t_close } }; ncdc-1.23.1/src/ui_listing.c0000644000175000017500000002740714245144564012552 00000000000000/* ncdc - NCurses Direct Connect client Copyright (c) 2011-2022 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "ncdc.h" #include "ui_listing.h" // Generic listing "widget". // This widget allows easy listing, selecting and paging of (dynamic) GSequence // lists. The list is managed by the user, but the widget does need to be // notified of insertions and deletions. #if INTERFACE struct ui_listing_t { GSequence *list; GSequenceIter *sel; GSequenceIter *top; gboolean topisbegin; gboolean selisbegin; gboolean (*skip)(ui_listing_t *, GSequenceIter *, void *); void *dat; // fields needed for searching ui_textinput_t *search_box; gchar *query; gint match_start; gint match_end; const char *(*to_string)(GSequenceIter *); } #endif // error values for ui_listing_t.match_start #define REGEX_NO_MATCH -1 #define REGEX_ERROR -2 // TODO: This can be relatively slow (linear search), is used often but rarely // changes. Cache this in the struct if it becomes a problem. static GSequenceIter *ui_listing_getbegin(ui_listing_t *ul) { GSequenceIter *i = g_sequence_get_begin_iter(ul->list); while(!g_sequence_iter_is_end(i) && ul->skip && ul->skip(ul, i, ul->dat)) i = g_sequence_iter_next(i); return i; } static GSequenceIter *ui_listing_next(ui_listing_t *ul, GSequenceIter *i) { do i = g_sequence_iter_next(i); while(!g_sequence_iter_is_end(i) && ul->skip && ul->skip(ul, i, ul->dat)); return i; } static GSequenceIter *ui_listing_prev(ui_listing_t *ul, GSequenceIter *i) { GSequenceIter *begin = ui_listing_getbegin(ul); do i = g_sequence_iter_prev(i); while(!g_sequence_iter_is_begin(i) && i != begin && ul->skip && ul->skip(ul, i, ul->dat)); if(g_sequence_iter_is_begin(i)) i = begin; return i; } // update top/sel in case they used to be the start of the list but aren't anymore void ui_listing_inserted(ui_listing_t *ul) { GSequenceIter *begin = ui_listing_getbegin(ul); if(!!ul->topisbegin != !!(ul->top == begin)) ul->top = ui_listing_getbegin(ul); if(!!ul->selisbegin != !!(ul->sel == begin)) ul->sel = ui_listing_getbegin(ul); } // called after the order of the list has changed // update sel in case it used to be the start of the list but isn't anymore void ui_listing_sorted(ui_listing_t *ul) { if(!!ul->selisbegin != !!(ul->sel == ui_listing_getbegin(ul))) ul->sel = ui_listing_getbegin(ul); } static void ui_listing_updateisbegin(ui_listing_t *ul) { GSequenceIter *begin = ui_listing_getbegin(ul); ul->topisbegin = ul->top == begin; ul->selisbegin = ul->sel == begin; } // update top/sel in case one of them is removed. // call this before using g_sequence_remove() void ui_listing_remove(ui_listing_t *ul, GSequenceIter *iter) { if(ul->top == iter) ul->top = ui_listing_prev(ul, iter); if(ul->top == iter) ul->top = ui_listing_next(ul, iter); if(ul->sel == iter) { ul->sel = ui_listing_next(ul, iter); if(g_sequence_iter_is_end(ul->sel)) ul->sel = ui_listing_prev(ul, iter); if(ul->sel == iter) ul->sel = g_sequence_get_end_iter(ul->list); } ui_listing_updateisbegin(ul); } // called when the skip() function changes behaviour (i.e. some items that were // skipped aren't now or the other way around). void ui_listing_skipchanged(ui_listing_t *ul) { // sel got hidden? oops! if(!g_sequence_iter_is_end(ul->sel) && ul->skip(ul, ul->sel, ul->dat)) { ul->sel = ui_listing_next(ul, ul->sel); if(g_sequence_iter_is_end(ul->sel)) ul->sel = ui_listing_prev(ul, ul->sel); } // top got hidden? oops as well if(!g_sequence_iter_is_end(ul->top) && ul->skip(ul, ul->top, ul->dat)) ul->top = ui_listing_prev(ul, ul->top); ui_listing_updateisbegin(ul); } ui_listing_t *ui_listing_create(GSequence *list, gboolean (*skip)(ui_listing_t *, GSequenceIter *, void *), void *dat, const char *(*to_string)(GSequenceIter *)) { ui_listing_t *ul = g_slice_new0(ui_listing_t); ul->list = list; ul->sel = ul->top = ui_listing_getbegin(ul); ul->topisbegin = ul->selisbegin = TRUE; ul->skip = skip; ul->dat = dat; ul->search_box = NULL; ul->query = NULL; ul->match_start = REGEX_NO_MATCH; ul->to_string = to_string; return ul; } void ui_listing_free(ui_listing_t *ul) { if(ul->search_box) ui_textinput_free(ul->search_box); if(ul->query) g_free(ul->query); g_slice_free(ui_listing_t, ul); } // search next/previous static void ui_listing_search_advance(ui_listing_t *ul, GSequenceIter *startpos, gboolean prev) { if(g_sequence_iter_is_end(startpos) && g_sequence_iter_is_end((startpos = ui_listing_getbegin(ul)))) return; GRegex *regex = ul->query ? g_regex_new(ul->query, G_REGEX_CASELESS | G_REGEX_OPTIMIZE, 0, NULL) : NULL; if(!regex) { ul->match_start = REGEX_ERROR; return; } ul->match_start = REGEX_NO_MATCH; GSequenceIter *pos = startpos; do { const char *candidate = ul->to_string(pos); GMatchInfo *match_info; if(g_regex_match(regex, candidate, 0, &match_info)) { g_match_info_fetch_pos(match_info, 0, &ul->match_start, &ul->match_end); g_match_info_free(match_info); ul->sel = pos; break; } g_match_info_free(match_info); pos = (prev ? ui_listing_prev : ui_listing_next)(ul, pos); if(g_sequence_iter_is_begin(pos)) pos = ui_listing_prev(ul, g_sequence_get_end_iter(ul->list)); else if(g_sequence_iter_is_end(pos)) pos = ui_listing_getbegin(ul); } while(ul->match_start == REGEX_NO_MATCH && pos != startpos); g_regex_unref(regex); } // handle keys in search mode static void ui_listing_search(ui_listing_t *ul, guint64 key) { char *completed = NULL; ui_textinput_key(ul->search_box, key, &completed); g_free(ul->query); ul->query = completed ? completed : ui_textinput_get(ul->search_box); if(completed) { if(ul->match_start < 0) { g_free(ul->query); ul->query = NULL; } ui_textinput_free(ul->search_box); ul->search_box = NULL; ul->match_start = -1; ul->match_end = -1; } else ui_listing_search_advance(ul, ul->sel, FALSE); } gboolean ui_listing_key(ui_listing_t *ul, guint64 key, int page) { if(ul->search_box) { ui_listing_search(ul, key); return TRUE; } // stop highlighting ul->match_start = REGEX_NO_MATCH; switch(key) { case INPT_CHAR('/'): // start search mode if(ul->to_string) { if(ul->query) { g_free(ul->query); ul->query = NULL; } g_assert(!ul->search_box); ul->search_box = ui_textinput_create(FALSE, NULL); } break; case INPT_CHAR(','): // find next ui_listing_search_advance(ul, ui_listing_next(ul, ul->sel), FALSE); break; case INPT_CHAR('.'): // find previous ui_listing_search_advance(ul, ui_listing_prev(ul, ul->sel), TRUE); break; case INPT_KEY(KEY_NPAGE): { // page down int i = page; while(i-- && !g_sequence_iter_is_end(ul->sel)) ul->sel = ui_listing_next(ul, ul->sel); if(g_sequence_iter_is_end(ul->sel)) ul->sel = ui_listing_prev(ul, ul->sel); break; } case INPT_KEY(KEY_PPAGE): { // page up int i = page; GSequenceIter *begin = ui_listing_getbegin(ul); while(i-- && ul->sel != begin) ul->sel = ui_listing_prev(ul, ul->sel); break; } case INPT_KEY(KEY_DOWN): // item down case INPT_CHAR('j'): ul->sel = ui_listing_next(ul, ul->sel); if(g_sequence_iter_is_end(ul->sel)) ul->sel = ui_listing_prev(ul, ul->sel); break; case INPT_KEY(KEY_UP): // item up case INPT_CHAR('k'): ul->sel = ui_listing_prev(ul, ul->sel); break; case INPT_KEY(KEY_HOME): // home ul->sel = ui_listing_getbegin(ul); break; case INPT_KEY(KEY_END): // end ul->sel = ui_listing_prev(ul, g_sequence_get_end_iter(ul->list)); break; default: return FALSE; } ui_listing_updateisbegin(ul); return TRUE; } static void ui_listing_fixtop(ui_listing_t *ul, int height) { // sel before top? top = sel! if(g_sequence_iter_compare(ul->top, ul->sel) > 0) ul->top = ul->sel; // does sel still fit on the screen? int i = height; GSequenceIter *n = ul->top; while(n != ul->sel && i > 0) { n = ui_listing_next(ul, n); i--; } // Nope? Make sure it fits if(i <= 0) { n = ul->sel; for(i=0; itop = n; } // Make sure there's no empty space if we have enough rows to fill the screen i = height; n = ul->top; GSequenceIter *begin = ui_listing_getbegin(ul); while(!g_sequence_iter_is_end(n) && i-- > 0) n = ui_listing_next(ul, n); while(ul->top != begin && i-- > 0) ul->top = ui_listing_prev(ul, ul->top); } // Every item is assumed to occupy exactly one line. // Returns the relative position of the current page (in percent). // The selected row number is written to *cur, to be used with move(cur, 0). // TODO: The return value is only correct if no skip function is used or if // there are otherwise no hidden rows. It'll give a blatantly wrong number if // there are. int ui_listing_draw(ui_listing_t *ul, int top, int bottom, ui_cursor_t *cur, void (*cb)(ui_listing_t *, GSequenceIter *, int, void *)) { int search_box_height = !!ul->search_box; int listing_height = 1 + bottom - top - search_box_height; ui_listing_fixtop(ul, listing_height); if(cur) { cur->x = 0; cur->y = top; } // draw GSequenceIter *n = ul->top; while(top <= bottom - search_box_height && !g_sequence_iter_is_end(n)) { if(cur && n == ul->sel) cur->y = top; cb(ul, n, top, ul->dat); n = ui_listing_next(ul, n); top++; } if(ul->search_box) { const char *status; switch(ul->match_start) { case REGEX_NO_MATCH: status = "no match>"; break; case REGEX_ERROR: status = " invalid>"; break; default: status = " search>"; } mvaddstr(bottom, 0, status); ui_textinput_draw(ul->search_box, bottom, 10, wincols - 10, cur); } ui_listing_updateisbegin(ul); int last = g_sequence_iter_get_position(g_sequence_get_end_iter(ul->list)); return MIN(100, last ? (g_sequence_iter_get_position(ul->top)+listing_height)*100/last : 0); } void ui_listing_draw_match(ui_listing_t *ul, GSequenceIter *iter, int y, int x, int max) { const char *str = ul->to_string(iter); if(ul->sel == iter && ul->match_start >= 0) { int ofs1 = 0, ofs2 = ul->match_start, ofs3 = ul->match_end, width1 = substr_columns(str + ofs1, ofs2 - ofs1), width2 = substr_columns(str + ofs2, ofs3 - ofs2); mvaddnstr(y, x, str + ofs1, str_offset_from_columns(str + ofs1, MIN(width1, max))); x += width1, max -= width1; attron(A_REVERSE); mvaddnstr(y, x, str + ofs2, str_offset_from_columns(str + ofs2, MIN(width2, max))); x += width2, max -= width2; attroff(A_REVERSE); mvaddnstr(y, x, str + ofs3, str_offset_from_columns(str + ofs3, max)); } else { mvaddnstr(y, x, str, max); } } ncdc-1.23.1/src/util.c0000644000175000017500000007637314245144616011365 00000000000000/* ncdc - NCurses Direct Connect client Copyright (c) 2011-2022 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "ncdc.h" #include "util.h" #if INTERFACE // Get a string from a glib log level #define loglevel_to_str(level) (\ (level) & G_LOG_LEVEL_ERROR ? "ERROR" :\ (level) & G_LOG_LEVEL_CRITICAL ? "CRITICAL" :\ (level) & G_LOG_LEVEL_WARNING ? "WARNING" :\ (level) & G_LOG_LEVEL_MESSAGE ? "message" :\ (level) & G_LOG_LEVEL_INFO ? "info" : "debug") // number of columns of a gunichar #define gunichar_width(x) (g_unichar_iswide(x) ? 2 : g_unichar_iszerowidth(x) ? 0 : 1) #endif // Perform a binary search on a GPtrArray, returning the index of the found // item. The result is undefined if the array is not sorted according to `cmp'. // Returns -1 when nothing is found. int ptr_array_search(GPtrArray *a, gconstpointer v, GCompareFunc cmp) { if(!a->len) return -1; int b = 0; int e = a->len-1; while(b <= e) { int i = b + (e - b)/2; int r = cmp(g_ptr_array_index(a, i), v); if(r < 0) { // i < v, look into the upper half b = i+1; } else if(r > 0) { // i > v, look into the lower half e = i-1; } else // equivalent return i; } return -1; } // Adds an element to the array before the specified index. If i >= a->len, it // will be appended to the array. This function preserves the order of the // array: all elements after the specified index will be moved. void ptr_array_insert_before(GPtrArray *a, int i, gpointer v) { if(i >= a->len) { g_ptr_array_add(a, v); return; } // add dummy element to make sure the array has the correct size. The value // will be overwritten in the memmove(). g_ptr_array_add(a, NULL); memmove(a->pdata+i+1, a->pdata+i, sizeof(a->pdata)*(a->len-i-1)); a->pdata[i] = v; } // Equality functions for tiger and TTH hashes. Suitable for use in // GHashTables. gboolean tiger_hash_equal(gconstpointer a, gconstpointer b) { return memcmp(a, b, 24) == 0; } // Calculates the SHA-256 digest of a certificate. This digest can be used for // the KEYP ADC extension and general verification. void certificate_sha256(gnutls_datum_t cert, char *digest) { GChecksum *ctx = g_checksum_new(G_CHECKSUM_SHA256); g_checksum_update(ctx, cert.data, cert.size); gsize len = 32; g_checksum_get_digest(ctx, (guchar *)digest, &len); g_checksum_free(ctx); } // Simple single-pass in-place AES-128-CBC encryption using a 16-byte zero'd IV. void crypt_aes128cbc(gboolean encrypt, const char *key, size_t keylen, char *data, size_t len) { gnutls_cipher_hd_t ciph; char iv[16] = {}; gnutls_datum_t ivd = { (unsigned char *)iv, 16 }; gnutls_datum_t keyd = { (unsigned char *)key, keylen }; gnutls_cipher_init(&ciph, GNUTLS_CIPHER_AES_128_CBC, &keyd, &ivd); if(encrypt) g_warn_if_fail(gnutls_cipher_encrypt(ciph, data, len) == 0); else g_warn_if_fail(gnutls_cipher_decrypt(ciph, data, len) == 0); gnutls_cipher_deinit(ciph); } // strftime()-like formatting for localtime. The returned string should be // g_free()'d. This function assumes that the resulting string always contains // at least one character. char *localtime_fmt(const char *fmt) { #if GLIB_CHECK_VERSION(2,26,0) GDateTime *tm = g_date_time_new_now_local(); char *ts = g_date_time_format(tm, fmt); g_date_time_unref(tm); return ts; #else int n, len = 128; char *ts = g_malloc(128); // Usually more than enough time_t t = time(NULL); struct tm *tm = localtime(&t); while((n = strftime(ts, len, fmt, tm)) == 0 || n >= len) { g_return_val_if_fail(len < 102400, NULL); // sanity check len *= 2; ts = g_realloc(ts, len); } return ts; #endif } // Turns any path into an absolute path. Doesn't do any canonicalization. static char *path_absolute(const char *path) { char *p = NULL; if(path[0] == '~' && (!path[1] || path[1] == '/')) { const char *home = g_get_home_dir(); if(!home) return NULL; p = path[1] ? g_build_filename(home, path+1, NULL) : g_strdup(home); } else if(path[0] != '/') { char *cwd = g_get_current_dir(); if(!cwd) return NULL; if(!path[0] || (path[0] == '.' && !path[1])) // Handles "" and "." p = cwd; else { // Handles "./$stuff" and "$everythingelse" p = g_build_filename(cwd, path[0] == '.' && path[1] == '/' ? path+1 : path, NULL); g_free(cwd); } } else p = g_strdup(path); return p; } // Handy wrapper around the readlink() syscall. Returns a null-terminated // string that should be g_free()'d. static char *path_readlink(const char *path) { int len = 128; char *buf = g_malloc(len); while(1) { int n = readlink(path, buf, len); if(n >= 0 && n < len) { buf[n] = 0; return buf; } if(n < 0 && errno != ERANGE) { g_free(buf); return NULL; } // Gotta put a limit *somewhere*. if(len > 512*1024) { g_free(buf); errno = ENAMETOOLONG; return NULL; } len *= 2; buf = g_realloc(buf, len); } } // Canonicalize a path and expand symlinks. A portable implementation of // realpath(), but also expands ~. Return value should be g_free()'d. // Notes: // - The path argument is assumed to be in the filename encoding, and the // returned value will be in the filename encoding. // - An error is returned if the file/dir pointed to by path does not exist. // - If the path ends with '/' or '/.' or '/../$file' or similar constructs, // the final component is not validated to actually be a directory. // - path_expand("") = path_expand(".") char *path_expand(const char *path) { GString *cur; char *tail; int links = 32; // Probably should use LINK_MAX for this. char *p = path_absolute(path); if(!p) return NULL; resolve: cur = g_string_new("/"); tail = p; while(*tail) { char *comp = tail; tail = strchr(comp, '/'); if(!tail) tail = comp + strlen(comp); if(*tail) *(tail++) = 0; // We now have a zero-terminated component in *comp. if(!*comp) continue; if(*comp == '.' && comp[1] == 0) continue; if(*comp == '.' && comp[1] == '.' && !comp[2]) { char *prev = strrchr(cur->str, '/'); g_assert(prev); g_string_truncate(cur, prev == cur->str ? 1 : cur->len-strlen(prev)); continue; } // We now have a component that isn't "." or ".." if(cur->str[cur->len-1] != '/') g_string_append_c(cur, '/'); g_string_append(cur, comp); // Let's see if it's a symlink char *link = path_readlink(cur->str); if(!link && errno == EINVAL) // Nope, not a symlink continue; if(!link) { // Nope, we got an error instead. tail = NULL; break; } // Now we have a symlink. if(!--links) { g_free(link); errno = ELOOP; tail = NULL; break; } char *newp = NULL; if(*link == '/') newp = g_build_filename(link, tail, NULL); else { char *prev = strrchr(cur->str, '/'); g_assert(prev); g_string_truncate(cur, prev == cur->str ? 1 : cur->len-strlen(prev)); newp = g_build_filename(cur->str, link, tail, NULL); } g_string_free(cur, TRUE); g_free(link); g_free(p); p = newp; goto resolve; } g_free(p); if(tail) return g_string_free(cur, FALSE); g_string_free(cur, TRUE); return NULL; } // Expand and auto-complete a filesystem path. Given argument and returned // suggestions are UTF-8. void path_suggest(const char *opath, char **sug) { char *name, *dir = NULL; char *path = g_filename_from_utf8(opath, -1, NULL, NULL, NULL); if(!path) return; // special-case ~ and . if((path[0] == '~' || path[0] == '.') && (path[1] == 0 || (path[1] == '/' && path[2] == 0))) { name = path_expand(path); char *uname = name ? g_filename_to_utf8(name, -1, NULL, NULL, NULL) : NULL; if(uname) sug[0] = g_strconcat(name, "/", NULL); g_free(name); g_free(uname); goto path_suggest_f; } char *sep = strrchr(path, '/'); if(sep) { *sep = 0; name = sep+1; dir = path_expand(path[0] ? path : "/"); if(!dir) goto path_suggest_f; } else { name = path; dir = path_expand("."); } GDir *d = g_dir_open(dir, 0, NULL); if(!d) goto path_suggest_f; const char *n; int i = 0, len = strlen(name); while(i<20 && (n = g_dir_read_name(d))) { if(strcmp(n, ".") == 0 || strcmp(n, "..") == 0) continue; char *fn = g_build_filename(dir, n, NULL); char *ufn = g_filename_to_utf8(fn, -1, NULL, NULL, NULL); char *un = g_filename_to_utf8(n, -1, NULL, NULL, NULL); if(ufn && un && strncmp(un, name, len) == 0 && strlen(un) != len) sug[i++] = g_file_test(fn, G_FILE_TEST_IS_DIR) ? g_strconcat(ufn, "/", NULL) : g_strdup(ufn); g_free(un); g_free(ufn); g_free(fn); } g_dir_close(d); qsort(sug, i, sizeof(char *), cmpstringp); path_suggest_f: g_free(path); g_free(dir); } // Reads from fd until EOF and returns the number of lines found. Starts // counting after the first '\n' if start is FALSE. Returns -1 on error. static int file_count_lines(int fd) { char buf[1024]; int n = 0; int r; while((r = read(fd, buf, 1024)) > 0) while(r--) if(buf[r] == '\n') n++; return r == 0 ? MAX(0, n) : r; } // Skips 'skip' lines and reads n lines from fd. static char **file_read_lines(int fd, int skip, int n) { char buf[1024]; // skip 'skip' lines int r = 0; while(skip > 0 && (r = read(fd, buf, 1024)) > 0) { int i; for(i=0; i 0 && (sep = memchr(tmp, '\n', left)) != NULL) { int w = sep - tmp; g_string_append_len(cur, tmp, w); res[num++] = g_strdup(cur->str); g_string_assign(cur, ""); left -= w+1; tmp += w+1; } g_string_append_len(cur, tmp, left); } while(num < n && (r = read(fd, buf, 1024)) > 0); g_string_free(cur, TRUE); if(r < 0) { g_strfreev(res); return NULL; } return res; } // Read the last n lines from a file and return them in a string array. The // file must end with a newline, and only \n is recognized as one. Returns // NULL on error, with errno set. Can return an empty string array (result && // !*result). This isn't the fastest implementation available, but at least it // does not have to read the entire file. char **file_tail(const char *fn, int n) { if(n <= 0) return g_new0(char *, 1); char **ret = NULL; int fd = open(fn, O_RDONLY); if(fd < 0) return NULL; int backbytes = n*128; off_t offset; while((offset = lseek(fd, -backbytes, SEEK_END)) != (off_t)-1) { int lines = file_count_lines(fd); if(lines < 0) goto done; // not enough lines, try seeking back further if(offset > 0 && lines < n) backbytes *= 2; // otherwise, if we have enough lines seek again and fetch them else if(lseek(fd, offset, SEEK_SET) == (off_t)-1) goto done; else { ret = file_read_lines(fd, MAX(0, lines-n), MIN(lines+1, n)); goto done; } } // offset is -1 if we reach this. we may have been seeking to a negative // offset, so let's try from the beginning. if(errno == EINVAL) { if(lseek(fd, 0, SEEK_SET) == (off_t)-1) goto done; int lines = file_count_lines(fd); if(lines < 0 || lseek(fd, 0, SEEK_SET) == (off_t)-1) goto done; ret = file_read_lines(fd, MAX(0, lines-n), MIN(lines+1, n)); } done: close(fd); return ret; } // Move a file from one place to another. Uses rename() when possible, // otherwise falls back to slow file copying. Does not copy over stat() // information such as modification times or chmod. // In the case of an error, the 'from' file will remain unmodified, but the // 'to' file may (or may not) have been deleted if 'overwrite' was true. In // some rare situations (if unlink fails), it may also remain on the disk but // with corrupted contents. gboolean file_move(const char *from, const char *to, gboolean overwrite, GError **err) { if(!overwrite && g_file_test(to, G_FILE_TEST_EXISTS)) { g_set_error_literal(err, 1, g_file_error_from_errno(EEXIST), g_strerror(EEXIST)); return FALSE; } int r; do r = rename(from, to); while(r < 0 && errno == EINTR); if(!r) return TRUE; if(errno != EXDEV) { g_set_error_literal(err, 1, g_file_error_from_errno(errno), g_strerror(errno)); return FALSE; } // plain old copy fallback int fromfd = open(from, O_RDONLY); if(fromfd < 0) { g_set_error_literal(err, 1, g_file_error_from_errno(errno), g_strerror(errno)); return FALSE; } int tofd = open(to, O_WRONLY | O_CREAT | (overwrite ? 0 : O_EXCL), 0666); if(tofd < 0) { g_set_error_literal(err, 1, g_file_error_from_errno(errno), g_strerror(errno)); close(fromfd); return FALSE; } char buf[8*1024]; while(1) { r = read(fromfd, buf, sizeof(buf)); if(r < 0 && errno == EINTR) continue; if(r <= 0) break; char *p = buf; while(r > 0) { int w = write(tofd, p, r); if(w < 0 && errno == EINTR) continue; if(w < 0) goto err; r -= w; p += w; } } if(r == 0) { if(close(tofd) < 0 || unlink(from) < 0) goto err; close(fromfd); return TRUE; } err: g_set_error_literal(err, 1, g_file_error_from_errno(errno), g_strerror(errno)); close(fromfd); close(tofd); unlink(to); return FALSE; } // Bit array utility functions. These functions work on an array of guint8, // where each bit can be accessed with its own index. // This does not use, because those may require byte swapping to store in a // endian-neutral way. These bit arrays are naturally endian-neutral, so can be // stored and retreived easily. #if INTERFACE #define bita_size(n) ((n+7)/8) #define bita_new(n) g_new0(guint8, bita_size(n)) #define bita_free(a) g_free(a) #define bita_get(a, i) (((a)[i/8] >> (i&7)) & 1) #define bita_set(a, i) ((a)[i/8] |= 1<<(i&7)) #define bita_reset(a, i) ((a)[i/8] &= ~(1<<(i&7))) #define bita_val(a, i, t) (t ? bita_set(a, i) : bita_reset(a, i)) #endif #if INTERFACE // Tests whether a string is a valid base32-encoded string. #define isbase32(s) (strspn(s, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ234567") == strlen(s)) // Test whether a string is a valid TTH hash. I.e., whether it is a // base32-encoded 39-character string. #define istth(s) (strlen(s) == 39 && isbase32(s)) #define MAXCIDLEN 64 /* 512 bits */ // Test whether a string is a valid CID. I.e. a base32-encoded string between // 128 and MAXCIDLEN bits */ #define iscid(s) (strlen(s) >= 26 && strlen(s) <= MAXCIDLEN*8/5+1 && isbase32(s)) #endif // Generic base32 encoder. // from[len] (binary) -> to[ceil(len*8/5)] (ascii) void base32_encode_dat(const char *from, char *to, int len) { static char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; int i, bits = 0, idx = 0, value = 0; for(i=0; i 5) { to[idx++] = alphabet[(value >> (bits-5)) & 0x1F]; bits -= 5; } } if(bits > 0) to[idx++] = alphabet[(value << (5-bits)) & 0x1F]; } // from[24] (binary) -> to[39] (ascii - no padding zero will be added) void base32_encode(const char *from, char *to) { base32_encode_dat(from, to, 24); } // from[n] (ascii) -> to[floor(n*5/8)] (binary) // from must be zero-terminated. void base32_decode(const char *from, char *to) { int bits = 0, idx = 0, value = 0; while(*from) { value = (value << 5) | (*from <= '9' ? (26+(*from-'2')) : *from-'A'); bits += 5; while(bits >= 8) { to[idx++] = (value >> (bits-8)) & 0xFF; bits -= 8; } from++; } } // Handy wrappers for parsing and formatting IP addresses. struct in_addr ip4_any = {}; struct in6_addr ip6_any = {}; gboolean ip4_isvalid(const char *str) { struct in_addr a; return inet_pton(AF_INET, str, &a) == 1; } gboolean ip6_isvalid(const char *str) { struct in6_addr a; return inet_pton(AF_INET6, str, &a) == 1; } struct in_addr ip4_pack(const char *str) { struct in_addr a; return !str || inet_pton(AF_INET, str, &a) != 1 ? ip4_any : a; } struct in6_addr ip6_pack(const char *str) { struct in6_addr a; return !str || inet_pton(AF_INET6, str, &a) != 1 ? ip6_any : a; } /* Extra level of indirection because makeheaders doesn't like structs as function arguments */ const char *ip4__unpack(guint32 ip) { static char buf[64]; return inet_ntop(AF_INET, &ip, buf, sizeof(buf)); } const char *ip6__unpack(unsigned char ip[16]) { static char buf[64]; return inet_ntop(AF_INET6, ip, buf, sizeof(buf)); } struct sockaddr *ip4__sockaddr(guint32 ip, unsigned short port) { static struct sockaddr_in s = { .sin_family = AF_INET }; s.sin_port = htons(port); s.sin_addr.s_addr = ip; return (struct sockaddr *)&s; } struct sockaddr *ip6__sockaddr(unsigned char ip[16], unsigned short port) { static struct sockaddr_in6 s = { .sin6_family = AF_INET6 }; s.sin6_port = htons(port); memcpy(&s.sin6_addr, ip, 16); return (struct sockaddr *)&s; } #if INTERFACE #define ip4_unpack(ip) ip4__unpack((ip).s_addr) #define ip6_unpack(ip) ip6__unpack((ip).s6_addr) #define ip4_sockaddr(ip, port) ip4__sockaddr((ip).s_addr, port) #define ip6_sockaddr(ip, port) ip6__sockaddr((ip).s6_addr, port) #define ip4_cmp(a, b) memcmp(&(a), &(b), sizeof(struct in_addr)) #define ip6_cmp(a, b) memcmp(&(a), &(b), sizeof(struct in6_addr)) #define ip4_isany(ip) ((ip).s_addr == INADDR_ANY) #define ip6_isany(ip) (ip6_cmp(ip, in6addr_any) == 0) #endif // Handy functions to create and read arbitrary data to/from byte arrays. Data // is written to and read from a byte array sequentially. The data is stored as // efficient as possible, bit still adds padding to correctly align some values. // Usage: // GByteArray *a = g_byte_array_new(); // darray_init(a); // darray_add_int32(a, 43); // darray_add_string(a, "blah"); // char *v = g_byte_array_free(a, FALSE); // ...later: // int number = darray_get_int32(v); // char *thestring = darray_get_string(v); // g_free(v); // // So it's basically a method to efficiently pass around variable arguments to // functions without the restrictions imposed by stdarg.h. #if INTERFACE // For internal use #define darray_append_pad(v, a)\ int darray_pad = (((v)->len + (a)) & ~(a)) - (v)->len;\ gint64 darray_zero = 0;\ if(darray_pad)\ g_byte_array_append(v, (guint8 *)&darray_zero, darray_pad) // All values (not necessarily the v thing itself) are always evaluated once. #define darray_add_int32(v, i) do { guint32 darray_p=i; darray_append_pad(v, 3); g_byte_array_append(v, (guint8 *)&darray_p, 4); } while(0) #define darray_add_int64(v, i) do { guint64 darray_p=i; darray_append_pad(v, 7); g_byte_array_append(v, (guint8 *)&darray_p, 8); } while(0) #define darray_add_ptr(v, p) do { const void *darray_t=p; darray_append_pad(v, sizeof(void *)-1); g_byte_array_append(v, (guint8 *)&darray_t, sizeof(void *)); } while(0) #define darray_add_dat(v, b, l) do { int darray_i=l; darray_add_int32(v, darray_i); g_byte_array_append(v, (guint8 *)(b), darray_i); } while(0) #define darray_add_string(v, s) do { const char *darray_t=s; darray_add_dat(v, darray_t, strlen(darray_t)+1); } while(0) #define darray_init(v) darray_add_int32(v, 4) #define darray_get_int32(v) *((gint32 *)darray_get_raw(v, 4, 3)) #define darray_get_int64(v) *((gint64 *)darray_get_raw(v, 8, 7)) #define darray_get_ptr(v) *((void **)darray_get_raw(v, sizeof(void *), sizeof(void *)-1)) #define darray_get_string(v) darray_get_raw(v, darray_get_int32(v), 0) #endif // For use by the macros char *darray_get_raw(char *v, int i, int a) { int *d = (int *)v; d[0] += a; d[0] &= ~a; char *r = v + d[0]; d[0] += i; return r; } char *darray_get_dat(char *v, int *l) { int n = darray_get_int32(v); if(l) *l = n; return darray_get_raw(v, n, 0); } // Transfer / hashing rate calculation and limiting /* How to use this: * From main thread: * ratecalc_t thing; * ratecalc_init(&thing); * ratecalc_register(&thing, class); * From any thread (usually some worker thread): * ratecalc_add(&thing, bytes); * From any other thread (usually main thread): * rate = ratecalc_rate(&thing); * From main thread: * ratecalc_reset(&thing); * ratecalc_unregister(&thing); * * ratecalc_calc() should be called with a one-second interval */ #if INTERFACE // Rate calc classes #define RCC_NONE 1 #define RCC_HASH 2 #define RCC_UP 3 #define RCC_DOWN 4 #define RCC_MAX RCC_DOWN struct ratecalc_t { GMutex lock; // protects total, last, rate and burst gint64 total; gint64 last; int burst; int rate; int reg; // 0 = not registered, >1 = registered with class #n }; #define ratecalc_reset(rc) do {\ g_mutex_lock(&((rc)->lock));\ (rc)->total = (rc)->last = (rc)->rate = (rc)->burst = 0;\ g_mutex_unlock(&((rc)->lock));\ } while(0) #define ratecalc_init(rc) do {\ g_mutex_init(&((rc)->lock));\ ratecalc_unregister(rc);\ ratecalc_reset(rc);\ } while(0) // TODO: get some burst allocated upon registering? Otherwise a transfer will // block until _calc() has assigned some bandwidth to it... #define ratecalc_register(rc, n) do { if(!(rc)->reg) {\ ratecalc_list = g_slist_prepend(ratecalc_list, rc);\ (rc)->reg = n;\ } } while(0) // TODO: give rc->burst back to the class? (in particular the negative ones) #define ratecalc_unregister(rc) do {\ ratecalc_list = g_slist_remove(ratecalc_list, rc);\ (rc)->reg = (rc)->rate = (rc)->burst = 0;\ } while(0) #endif GSList *ratecalc_list = NULL; void ratecalc_add(ratecalc_t *rc, int b) { g_mutex_lock(&rc->lock); rc->total += b; rc->burst -= b; g_mutex_unlock(&rc->lock); } int ratecalc_rate(ratecalc_t *rc) { g_mutex_lock(&rc->lock); int r = rc->rate; g_mutex_unlock(&rc->lock); return r; } int ratecalc_burst(ratecalc_t *rc) { g_mutex_lock(&rc->lock); int r = rc->burst; g_mutex_unlock(&rc->lock); return r; } gint64 ratecalc_total(ratecalc_t *rc) { g_mutex_lock(&rc->lock); gint64 r = rc->total; g_mutex_unlock(&rc->lock); return r; } // Calculates rc->rate and rc->burst. void ratecalc_calc() { GSList *n; // Bytes allocated to each class int maxburst[RCC_MAX+1] = {}; maxburst[RCC_HASH] = var_get_int(0, VAR_hash_rate); maxburst[RCC_UP] = var_get_int(0, VAR_upload_rate); maxburst[RCC_DOWN] = var_get_int(0, VAR_download_rate); int i; for(i=0; i<=RCC_MAX; i++) if(!maxburst[i]) maxburst[i] = INT_MAX; int left[RCC_MAX+1]; // Number of bytes left to distribute int nums[RCC_MAX+1] = {}; // Number of rc structs with burst < max memcpy(left, maxburst, (RCC_MAX+1)*sizeof(int)); // Pass one: calculate rc->rate, substract negative burst values from left[] and calculate nums[]. for(n=ratecalc_list; n; n=n->next) { ratecalc_t *rc = n->data; g_mutex_lock(&rc->lock); gint64 diff = rc->total - rc->last; rc->rate = diff + ((rc->rate - diff) / 2); rc->last = rc->total; if(rc->burst < 0) { int sub = MIN(left[rc->reg], -rc->burst); left[rc->reg] -= sub; rc->burst += sub; } if(rc->burst < maxburst[rc->reg]) nums[rc->reg]++; else rc->burst = maxburst[rc->reg]; g_mutex_unlock(&rc->lock); } //g_debug("Num: %d - %d - %d", nums[2], nums[3], nums[4]); // Pass 2..i+1: distribute bandwidth from left[] among the ratecalc structures. // (The i variable is to limit the number of passes, otherwise it easily gets into an infinite loop) i = 3; while(i--) { int bwp[RCC_MAX+1] = {}; // average bandwidth-per-item gboolean c = FALSE; int j; for(j=2; j<=RCC_MAX; j++) { bwp[j] = nums[j] ? left[j]/nums[j] : 0; if(bwp[j] > 0) c = TRUE; } // If there's nothing to distribute, stop. if(!c) break; // Loop through the ratecalc structs and assign it some BW for(n=ratecalc_list; n; n=n->next) { ratecalc_t *rc = n->data; if(bwp[rc->reg] > 0) { g_mutex_lock(&rc->lock); int alloc = MIN(maxburst[rc->reg]-rc->burst, bwp[rc->reg]); //g_debug("Allocing class %d(num=%d), %d new bytes to %d", rc->reg, nums[rc->reg], alloc, rc->burst); rc->burst += alloc; left[rc->reg] -= alloc; g_mutex_unlock(&rc->lock); if(alloc > 0 && alloc < bwp[rc->reg]) nums[rc->reg]--; } } //g_debug("Left after #%d: %d - %d - %d", 3-i, left[2], left[3], left[4]); } //g_debug("Left after distribution: %d - %d - %d", left[2], left[3], left[4]); // TODO: distribute the last remaining BW on a first-find basis? } // calculates an ETA and formats it into a "?d ?h ?m ?s" thing char *ratecalc_eta(ratecalc_t *rc, guint64 left) { int sec = left / MAX(1, ratecalc_rate(rc)); return sec > 356*24*3600 ? "-" : str_formatinterval(sec); } // Log file writer. Prefixes all messages with a timestamp and allows the logs // to be rotated. #if INTERFACE struct logfile_t { int file; char *path; struct stat st; }; #endif static GSList *logfile_instances = NULL; // (Re-)opens the log file and checks for inode and file size changes. static void logfile_checkfile(logfile_t *l) { // stat gboolean restat = l->file < 0; struct stat st; if(l->file >= 0 && stat(l->path, &st) < 0) { g_warning("Unable to stat log file '%s': %s. Attempting to re-create it.", l->path, g_strerror(errno)); close(l->file); l->file = -1; restat = TRUE; } // if we have the log open, compare inode & size if(l->file >= 0 && (l->st.st_ino != st.st_ino || l->st.st_size > st.st_size)) { close(l->file); l->file = -1; } // if the log hadn't been opened or has been closed earlier, try to open it again if(l->file < 0) l->file = open(l->path, O_WRONLY|O_APPEND|O_CREAT, 0666); if(l->file < 0) g_warning("Unable to open log file '%s' for writing: %s", l->path, g_strerror(errno)); // stat again if we need to if(l->file >= 0 && restat && stat(l->path, &st) < 0) { g_warning("Unable to stat log file '%s': %s. Closing.", l->path, g_strerror(errno)); close(l->file); l->file = -1; } memcpy(&l->st, &st, sizeof(struct stat)); } logfile_t *logfile_create(const char *name) { logfile_t *l = g_slice_new0(logfile_t); l->file = -1; char *n = g_strconcat(name, ".log", NULL); l->path = g_build_filename(db_dir, "logs", n, NULL); g_free(n); logfile_checkfile(l); logfile_instances = g_slist_prepend(logfile_instances, l); return l; } void logfile_free(logfile_t *l) { if(!l) return; logfile_instances = g_slist_remove(logfile_instances, l); if(l->file >= 0) close(l->file); g_free(l->path); g_slice_free(logfile_t, l); } void logfile_add(logfile_t *l, const char *msg) { logfile_checkfile(l); if(l->file < 0) return; char *ts = localtime_fmt("[%F %H:%M:%S %Z]"); char *line = g_strdup_printf("%s %s\n", ts, msg); g_free(ts); int len = strlen(line); int wr = 0; int r = 1; while(wr < len && (r = write(l->file, line+wr, len-wr)) > 0) wr += r; g_free(line); if(r <= 0 && !strstr(msg, " (LOGERR)")) g_warning("Error writing to log file: %s (LOGERR)", g_strerror(errno)); } // Flush and re-open all opened log files. void logfile_global_reopen() { GSList *n = logfile_instances; for(; n; n=n->next) { logfile_t *l = n->data; if(l->file >= 0) { close(l->file); l->file = -1; } logfile_checkfile(l); } } // OS cache invalidation after reading data from a file. This is pretty much a // wrapper around posix_fadvise(), but multiple sequential reads are bulked // together in a single call to posix_fadvise(). This should work a lot better // than calling the function multiple times on smaller chunks if the OS // implementation works on page sizes internally. // // Usage: // int fd = open(..); // fadv_t a; // fadv_init(&a, fd, offset, flag); // while((int len = read(..)) > 0) // fadv_purge(&a, len); // fadv_close(&a); // close(fd); // // These functions are thread-safe, as long as they are not used on the same // struct from multiple threads at the same time. #if INTERFACE struct fadv_t { int fd; int chunk; int flag; guint64 offset; }; #ifdef HAVE_POSIX_FADVISE #define fadv_init(a, f, o, l) do {\ (a)->fd = f;\ (a)->chunk = 0;\ (a)->offset = o;\ (a)->flag = l;\ } while(0) #define fadv_close(a) fadv_purge(a, -1) #else // HAVE_POSIX_FADVISE // Some pointless assignments to make sure the compiler doesn't complain about // unused variables. #define fadv_init(a,f,o,l) ((a)->fd = 0) #define fadv_purge(a, l) ((a)->fd = 0) #define fadv_close(a) ((a)->fd = 0) #define fadv_oneshot(f,o,s,l) #endif #endif #ifdef HAVE_POSIX_FADVISE // call with length = -1 to force a flush void fadv_purge(fadv_t *a, int length) { if(length > 0) a->chunk += length; // flush every 5MB. Some magical value, don't think too much into it. if(a->chunk > 5*1024*1024 || (length < 0 && a->chunk > 0)) { if(var_ffc_get() & a->flag) posix_fadvise(a->fd, a->offset, a->chunk, POSIX_FADV_DONTNEED); a->offset += a->chunk; a->chunk = 0; } } /* A one-shot function to flush all pages that the given range resides on, * except for the last page if the range isn't page-aligned. */ void fadv_oneshot(int fd, guint64 off, size_t length, int flag) { if(!(var_ffc_get() & flag)) return; length += off & 511; off &= ~511; length &= ~511; if(length != off) posix_fadvise(fd, off, length, POSIX_FADV_DONTNEED); } #endif // A GSource implementation to attach raw fds as a source to the main loop. typedef struct fdsrc_t { GSource src; GPollFD fd; } fdsrc_t; static gboolean fdsrc_prepare(GSource *src, gint *timeout) { *timeout = -1; return FALSE; } static gboolean fdsrc_check(GSource *src) { return ((fdsrc_t *)src)->fd.revents > 0 ? TRUE : FALSE; } static gboolean fdsrc_dispatch(GSource *src, GSourceFunc cb, gpointer dat) { return cb(dat); } static void fdsrc_finalize(GSource *src) { } static GSourceFuncs fdsrc_funcs = { fdsrc_prepare, fdsrc_check, fdsrc_dispatch, fdsrc_finalize }; GSource *fdsrc_new(int fd, int ev) { fdsrc_t *src = (fdsrc_t *)g_source_new(&fdsrc_funcs, sizeof(fdsrc_t)); src->fd.fd = fd; src->fd.events = ev; src->fd.revents = 0; g_source_add_poll((GSource *)src, &src->fd); return (GSource *)src; } ncdc-1.23.1/missing0000755000175000017500000001533614314535761011046 00000000000000#! /bin/sh # Common wrapper for a few potentially missing GNU programs. scriptversion=2018-03-07.03; # UTC # Copyright (C) 1996-2021 Free Software Foundation, Inc. # Originally written by Fran,cois Pinard , 1996. # 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 2, 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 . # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that program. if test $# -eq 0; then echo 1>&2 "Try '$0 --help' for more information" exit 1 fi case $1 in --is-lightweight) # Used by our autoconf macros to check whether the available missing # script is modern enough. exit 0 ;; --run) # Back-compat with the calling convention used by older automake. shift ;; -h|--h|--he|--hel|--help) echo "\ $0 [OPTION]... PROGRAM [ARGUMENT]... Run 'PROGRAM [ARGUMENT]...', returning a proper advice when this fails due to PROGRAM being missing or too old. Options: -h, --help display this help and exit -v, --version output version information and exit Supported PROGRAM values: aclocal autoconf autoheader autom4te automake makeinfo bison yacc flex lex help2man Version suffixes to PROGRAM as well as the prefixes 'gnu-', 'gnu', and 'g' are ignored when checking the name. Send bug reports to ." exit $? ;; -v|--v|--ve|--ver|--vers|--versi|--versio|--version) echo "missing $scriptversion (GNU Automake)" exit $? ;; -*) echo 1>&2 "$0: unknown '$1' option" echo 1>&2 "Try '$0 --help' for more information" exit 1 ;; esac # Run the given program, remember its exit status. "$@"; st=$? # If it succeeded, we are done. test $st -eq 0 && exit 0 # Also exit now if we it failed (or wasn't found), and '--version' was # passed; such an option is passed most likely to detect whether the # program is present and works. case $2 in --version|--help) exit $st;; esac # Exit code 63 means version mismatch. This often happens when the user # tries to use an ancient version of a tool on a file that requires a # minimum version. if test $st -eq 63; then msg="probably too old" elif test $st -eq 127; then # Program was missing. msg="missing on your system" else # Program was found and executed, but failed. Give up. exit $st fi perl_URL=https://www.perl.org/ flex_URL=https://github.com/westes/flex gnu_software_URL=https://www.gnu.org/software program_details () { case $1 in aclocal|automake) echo "The '$1' program is part of the GNU Automake package:" echo "<$gnu_software_URL/automake>" echo "It also requires GNU Autoconf, GNU m4 and Perl in order to run:" echo "<$gnu_software_URL/autoconf>" echo "<$gnu_software_URL/m4/>" echo "<$perl_URL>" ;; autoconf|autom4te|autoheader) echo "The '$1' program is part of the GNU Autoconf package:" echo "<$gnu_software_URL/autoconf/>" echo "It also requires GNU m4 and Perl in order to run:" echo "<$gnu_software_URL/m4/>" echo "<$perl_URL>" ;; esac } give_advice () { # Normalize program name to check for. normalized_program=`echo "$1" | sed ' s/^gnu-//; t s/^gnu//; t s/^g//; t'` printf '%s\n' "'$1' is $msg." configure_deps="'configure.ac' or m4 files included by 'configure.ac'" case $normalized_program in autoconf*) echo "You should only need it if you modified 'configure.ac'," echo "or m4 files included by it." program_details 'autoconf' ;; autoheader*) echo "You should only need it if you modified 'acconfig.h' or" echo "$configure_deps." program_details 'autoheader' ;; automake*) echo "You should only need it if you modified 'Makefile.am' or" echo "$configure_deps." program_details 'automake' ;; aclocal*) echo "You should only need it if you modified 'acinclude.m4' or" echo "$configure_deps." program_details 'aclocal' ;; autom4te*) echo "You might have modified some maintainer files that require" echo "the 'autom4te' program to be rebuilt." program_details 'autom4te' ;; bison*|yacc*) echo "You should only need it if you modified a '.y' file." echo "You may want to install the GNU Bison package:" echo "<$gnu_software_URL/bison/>" ;; lex*|flex*) echo "You should only need it if you modified a '.l' file." echo "You may want to install the Fast Lexical Analyzer package:" echo "<$flex_URL>" ;; help2man*) echo "You should only need it if you modified a dependency" \ "of a man page." echo "You may want to install the GNU Help2man package:" echo "<$gnu_software_URL/help2man/>" ;; makeinfo*) echo "You should only need it if you modified a '.texi' file, or" echo "any other file indirectly affecting the aspect of the manual." echo "You might want to install the Texinfo package:" echo "<$gnu_software_URL/texinfo/>" echo "The spurious makeinfo call might also be the consequence of" echo "using a buggy 'make' (AIX, DU, IRIX), in which case you might" echo "want to install GNU make:" echo "<$gnu_software_URL/make/>" ;; *) echo "You might have modified some files without having the proper" echo "tools for further handling them. Check the 'README' file, it" echo "often tells you about the needed prerequisites for installing" echo "this package. You may also peek at any GNU archive site, in" echo "case some other package contains this missing '$1' program." ;; esac } give_advice "$1" | sed -e '1s/^/WARNING: /' \ -e '2,$s/^/ /' >&2 # Propagate the correct exit status (expected to be 127 for a program # not found, 63 for a program that failed due to version mismatch). exit $st # Local variables: # eval: (add-hook 'before-save-hook 'time-stamp) # time-stamp-start: "scriptversion=" # time-stamp-format: "%:y-%02m-%02d.%02H" # time-stamp-time-zone: "UTC0" # time-stamp-end: "; # UTC" # End: ncdc-1.23.1/README0000644000175000017500000000343614314536005010315 00000000000000ncdc 1.23.1 =========== DESCRIPTION ncdc is a modern and lightweight direct connect client with a friendly ncurses interface. Always make sure you run the latest version. You can check for updates and find more information at https://dev.yorhel.nl/ncdc REQUIREMENTS ncursesw zlib bzip2 sqlite >= 3.3.9 glib >= 2.32.0 gnutls >= 3.0 libmaxminddb (optional) BUILDING FROM A RELEASE TARBALL If you managed to fetch an ncdc tarball from somewhere, then you will need the following to build ncdc: - A C compiler - Development files for the libraries mentioned above - make - pkg-config - pod2man (optional, comes with a default Perl installation) - makeheaders (optional, included in the distribution if you don't have it) And the usual commands will get you up and running: ./configure --prefix=/usr make (sudo) make install BUILDING FROM THE GIT REPOSITORY To build the latest and greatest version from the git repository, you will need the stuff mentioned above in addition to GNU autoconf and automake. After checking out the git repo, run the following command: autoreconf -i ...and you can use same tricks to build ncdc as with using a release tarball. CROSS COMPILING You may run into some trouble when the binaries of the target system don't run on the build system. To avoid that, build the 'makeheaders' and 'gendoc' utilities manually before running the regular 'make'. For example: ./configure cc deps/makeheaders.c -o makeheaders cc -I. doc/gendoc.c -o gendoc make Replace 'cc' with a compiler that builds binaries that can be run on the build system. CONTACT Email: projects@yorhel.nl Web: https://dev.yorhel.nl/ncdc DC: adc://dc.blicky.net:2780/ or adcs://dc.blicky.net:2780/ ncdc-1.23.1/COPYING0000644000175000017500000000204514245144645010473 00000000000000Copyright (c) 2011-2022 Yoran Heling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ncdc-1.23.1/configure.ac0000644000175000017500000001144014314535754011727 00000000000000 AC_INIT([ncdc],[1.23.1],[projects@yorhel.nl]) AC_CONFIG_SRCDIR([src/ncdc.h]) AC_CONFIG_HEADERS([config.h]) m4_include([deps/lean.m4]) AM_INIT_AUTOMAKE([foreign subdir-objects]) PKG_PROG_PKG_CONFIG([0.18]) # Check for programs. AC_PROG_CC AC_PROG_INSTALL AC_PROG_RANLIB AC_SYS_LARGEFILE # Use silent building m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) # Check for pod2man AC_CHECK_PROG([have_pod2man],[pod2man],[yes],[no]) AM_CONDITIONAL([USE_POD2MAN], [test "x$have_pod2man" = "xyes"]) # Check for makeheaders. If the system does not provide it, compile our own copy in deps/ AC_CHECK_PROG([have_mh],[makeheaders],[yes],[no]) AM_CONDITIONAL([HAVE_MH], [test "x$have_mh" = "xyes"]) # Check for header files. AC_CHECK_HEADERS([zlib.h bzlib.h],[], AC_MSG_ERROR([Required header file not found])) # Check for posix_fadvise() AC_CHECK_FUNCS([posix_fadvise]) AC_SEARCH_LIBS([inet_pton], [nsl]) AC_SEARCH_LIBS([socket], [socket], [], [ AC_CHECK_LIB([socket], [socket], [LIBS="-lsocket -lnsl $LIBS"], [], [-lnsl])]) # Check for sendfile() support (not required) # The following checks are based on ProFTPD's configure.in, except ncdc only # supports the Linux and BSD variant at the moment, as those are the only two I # have tested so far. AC_CACHE_CHECK([which sendfile() implementation to use], pr_cv_sendfile_func, pr_cv_sendfile_func="none" # Linux if test "$pr_cv_sendfile_func" = "none"; then AC_LINK_IFELSE([AC_LANG_PROGRAM( [[ #include #include #include ]], [[ int i=0; off_t o=0; size_t c=0; (void)sendfile(i,i,&o,c); ]])], [pr_cv_sendfile_func="Linux"]) fi # BSD if test "$pr_cv_sendfile_func" = "none"; then AC_LINK_IFELSE([AC_LANG_PROGRAM( [[ #include #include #include ]], [[ int i=0; off_t o=0; size_t n=0; struct sf_hdtr h={}; (void)sendfile(i,i,o,n,&h,&o,i); ]])], [pr_cv_sendfile_func="BSD"]) fi ) # set defines if test "$pr_cv_sendfile_func" != none; then AC_DEFINE(HAVE_SENDFILE, 1, [Define if sendfile support.]) fi case "$pr_cv_sendfile_func" in "Linux") AC_DEFINE(HAVE_LINUX_SENDFILE, 1, [Define if using Linux sendfile support.]);; "BSD") AC_DEFINE(HAVE_BSD_SENDFILE, 1, [Define if using BSD sendfile support.]);; esac # Check for ncurses PKG_CHECK_MODULES(NCURSES, ncursesw,,[ PKG_CHECK_MODULES(NCURSES, ncurses,,[ AC_MSG_ERROR(ncurses library is required) ]) ]) # Check for zlib AC_CHECK_LIB([z], [deflate], [AC_SUBST([Z_LIBS],[-lz])], [AC_MSG_ERROR(zlib library is required)]) # Check for libbz2 AC_CHECK_LIB([bz2], [BZ2_bzReadOpen], [AC_SUBST([BZ2_LIBS],[-lbz2])], [AC_MSG_ERROR(bzip2 library is required)]) # Check for SQLite3 PKG_CHECK_EXISTS([sqlite3],[ PKG_CHECK_MODULES([SQLITE],[sqlite3]) ],[ AC_CHECK_HEADERS([sqlite3.h],[], [AC_MSG_ERROR([sqlite3 header file not found])]) AC_CHECK_LIB([sqlite3], [sqlite3_open], [AC_SUBST([SQLITE_LIBS],[-lsqlite3])], [AC_MSG_ERROR([sqlite3 library is required])]) ] ) # Check for modules PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.32 gthread-2.0]) PKG_CHECK_MODULES([GNUTLS], [gnutls >= 3.0]) AC_ARG_WITH([geoip], [AS_HELP_STRING([--with-geoip], [support for IP-to-country lookups @<:@default=no@:>@])], [], [with_geoip=no]) AS_IF([test "x$with_geoip" = xyes], [PKG_CHECK_MODULES([GEOIP], [libmaxminddb >= 1.0], [AC_DEFINE(USE_GEOIP, 1, [Use libmaxminddb for IP-to-country lookups])])]) # Check whether we should use the version string from AC_INIT, or use # git-describe to create one. This trick is copied from the pacman source. AC_ARG_ENABLE(git-version, AS_HELP_STRING([--enable-git-version], [enable use of git version in version string if available]), [wantgitver=$enableval], [wantgitver=yes]) usegitver=no if test "x$wantgitver" = "xyes" ; then AC_CHECK_PROGS([GIT], [git], [no]) test "x$GIT" != "xno" -a -d "$srcdir/.git" && usegitver=yes fi AM_CONDITIONAL(USE_GIT_VERSION, test "x$usegitver" = "xyes") # If we don't have pod2man and doc/ncdc.1 isn't available in the source # directory, throw a warning and just go ahead without installing the man page. installmanpage=yes if test "x$have_pod2man" = "xno" -a \! -s "$srcdir/doc/ncdc.1"; then echo "" echo "Note: Could not find doc/ncdc.1 in the source directory nor the pod2man" echo "utility on your system. No manual page will be installed." echo "" installmanpage=no fi AM_CONDITIONAL([INSTALL_MANPAGE], [test "x$installmanpage" = "xyes"]) AC_CONFIG_FILES([Makefile]) AC_OUTPUT ncdc-1.23.1/compile0000755000175000017500000001635014314535761011022 00000000000000#! /bin/sh # Wrapper for compilers which do not understand '-c -o'. scriptversion=2018-03-07.03; # UTC # Copyright (C) 1999-2021 Free Software Foundation, Inc. # Written by Tom Tromey . # # 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 2, 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 . # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that program. # This file is maintained in Automake, please report # bugs to or send patches to # . nl=' ' # We need space, tab and new line, in precisely that order. Quoting is # there to prevent tools from complaining about whitespace usage. IFS=" "" $nl" file_conv= # func_file_conv build_file lazy # Convert a $build file to $host form and store it in $file # Currently only supports Windows hosts. If the determined conversion # type is listed in (the comma separated) LAZY, no conversion will # take place. func_file_conv () { file=$1 case $file in / | /[!/]*) # absolute file, and not a UNC file if test -z "$file_conv"; then # lazily determine how to convert abs files case `uname -s` in MINGW*) file_conv=mingw ;; CYGWIN* | MSYS*) file_conv=cygwin ;; *) file_conv=wine ;; esac fi case $file_conv/,$2, in *,$file_conv,*) ;; mingw/*) file=`cmd //C echo "$file " | sed -e 's/"\(.*\) " *$/\1/'` ;; cygwin/* | msys/*) file=`cygpath -m "$file" || echo "$file"` ;; wine/*) file=`winepath -w "$file" || echo "$file"` ;; esac ;; esac } # func_cl_dashL linkdir # Make cl look for libraries in LINKDIR func_cl_dashL () { func_file_conv "$1" if test -z "$lib_path"; then lib_path=$file else lib_path="$lib_path;$file" fi linker_opts="$linker_opts -LIBPATH:$file" } # func_cl_dashl library # Do a library search-path lookup for cl func_cl_dashl () { lib=$1 found=no save_IFS=$IFS IFS=';' for dir in $lib_path $LIB do IFS=$save_IFS if $shared && test -f "$dir/$lib.dll.lib"; then found=yes lib=$dir/$lib.dll.lib break fi if test -f "$dir/$lib.lib"; then found=yes lib=$dir/$lib.lib break fi if test -f "$dir/lib$lib.a"; then found=yes lib=$dir/lib$lib.a break fi done IFS=$save_IFS if test "$found" != yes; then lib=$lib.lib fi } # func_cl_wrapper cl arg... # Adjust compile command to suit cl func_cl_wrapper () { # Assume a capable shell lib_path= shared=: linker_opts= for arg do if test -n "$eat"; then eat= else case $1 in -o) # configure might choose to run compile as 'compile cc -o foo foo.c'. eat=1 case $2 in *.o | *.[oO][bB][jJ]) func_file_conv "$2" set x "$@" -Fo"$file" shift ;; *) func_file_conv "$2" set x "$@" -Fe"$file" shift ;; esac ;; -I) eat=1 func_file_conv "$2" mingw set x "$@" -I"$file" shift ;; -I*) func_file_conv "${1#-I}" mingw set x "$@" -I"$file" shift ;; -l) eat=1 func_cl_dashl "$2" set x "$@" "$lib" shift ;; -l*) func_cl_dashl "${1#-l}" set x "$@" "$lib" shift ;; -L) eat=1 func_cl_dashL "$2" ;; -L*) func_cl_dashL "${1#-L}" ;; -static) shared=false ;; -Wl,*) arg=${1#-Wl,} save_ifs="$IFS"; IFS=',' for flag in $arg; do IFS="$save_ifs" linker_opts="$linker_opts $flag" done IFS="$save_ifs" ;; -Xlinker) eat=1 linker_opts="$linker_opts $2" ;; -*) set x "$@" "$1" shift ;; *.cc | *.CC | *.cxx | *.CXX | *.[cC]++) func_file_conv "$1" set x "$@" -Tp"$file" shift ;; *.c | *.cpp | *.CPP | *.lib | *.LIB | *.Lib | *.OBJ | *.obj | *.[oO]) func_file_conv "$1" mingw set x "$@" "$file" shift ;; *) set x "$@" "$1" shift ;; esac fi shift done if test -n "$linker_opts"; then linker_opts="-link$linker_opts" fi exec "$@" $linker_opts exit 1 } eat= case $1 in '') echo "$0: No command. Try '$0 --help' for more information." 1>&2 exit 1; ;; -h | --h*) cat <<\EOF Usage: compile [--help] [--version] PROGRAM [ARGS] Wrapper for compilers which do not understand '-c -o'. Remove '-o dest.o' from ARGS, run PROGRAM with the remaining arguments, and rename the output as expected. If you are trying to build a whole package this is not the right script to run: please start by reading the file 'INSTALL'. Report bugs to . EOF exit $? ;; -v | --v*) echo "compile $scriptversion" exit $? ;; cl | *[/\\]cl | cl.exe | *[/\\]cl.exe | \ icl | *[/\\]icl | icl.exe | *[/\\]icl.exe ) func_cl_wrapper "$@" # Doesn't return... ;; esac ofile= cfile= for arg do if test -n "$eat"; then eat= else case $1 in -o) # configure might choose to run compile as 'compile cc -o foo foo.c'. # So we strip '-o arg' only if arg is an object. eat=1 case $2 in *.o | *.obj) ofile=$2 ;; *) set x "$@" -o "$2" shift ;; esac ;; *.c) cfile=$1 set x "$@" "$1" shift ;; *) set x "$@" "$1" shift ;; esac fi shift done if test -z "$ofile" || test -z "$cfile"; then # If no '-o' option was seen then we might have been invoked from a # pattern rule where we don't need one. That is ok -- this is a # normal compilation that the losing compiler can handle. If no # '.c' file was seen then we are probably linking. That is also # ok. exec "$@" fi # Name of file we expect compiler to create. cofile=`echo "$cfile" | sed 's|^.*[\\/]||; s|^[a-zA-Z]:||; s/\.c$/.o/'` # Create the lock directory. # Note: use '[/\\:.-]' here to ensure that we don't use the same name # that we are using for the .o file. Also, base the name on the expected # object file name, since that is what matters with a parallel build. lockdir=`echo "$cofile" | sed -e 's|[/\\:.-]|_|g'`.d while true; do if mkdir "$lockdir" >/dev/null 2>&1; then break fi sleep 1 done # FIXME: race condition here if user kills between mkdir and trap. trap "rmdir '$lockdir'; exit 1" 1 2 15 # Run the compile. "$@" ret=$? if test -f "$cofile"; then test "$cofile" = "$ofile" || mv "$cofile" "$ofile" elif test -f "${cofile}bj"; then test "${cofile}bj" = "$ofile" || mv "${cofile}bj" "$ofile" fi rmdir "$lockdir" exit $ret # Local Variables: # mode: shell-script # sh-indentation: 2 # eval: (add-hook 'before-save-hook 'time-stamp) # time-stamp-start: "scriptversion=" # time-stamp-format: "%:y-%02m-%02d.%02H" # time-stamp-time-zone: "UTC0" # time-stamp-end: "; # UTC" # End: ncdc-1.23.1/configure0000755000175000017500000065007614314535760011363 00000000000000#! /bin/sh # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.71 for ncdc 1.23.1. # # Report bugs to . # # # Copyright (C) 1992-1996, 1998-2017, 2020-2021 Free Software Foundation, # Inc. # # # This configure script is free software; the Free Software Foundation # gives unlimited permission to copy, distribute and modify it. ## -------------------- ## ## M4sh Initialization. ## ## -------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh as_nop=: if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else $as_nop case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; esac fi # Reset variables that may have inherited troublesome values from # the environment. # IFS needs to be set, to space, tab, and newline, in precisely that order. # (If _AS_PATH_WALK were called with IFS unset, it would have the # side effect of setting IFS to empty, thus disabling word splitting.) # Quoting is to prevent editors from complaining about space-tab. as_nl=' ' export as_nl IFS=" "" $as_nl" PS1='$ ' PS2='> ' PS4='+ ' # Ensure predictable behavior from utilities with locale-dependent output. LC_ALL=C export LC_ALL LANGUAGE=C export LANGUAGE # We cannot yet rely on "unset" to work, but we need these variables # to be unset--not just set to an empty or harmless value--now, to # avoid bugs in old shells (e.g. pre-3.0 UWIN ksh). This construct # also avoids known problems related to "unset" and subshell syntax # in other old shells (e.g. bash 2.01 and pdksh 5.2.14). for as_var in BASH_ENV ENV MAIL MAILPATH CDPATH do eval test \${$as_var+y} \ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : done # Ensure that fds 0, 1, and 2 are open. if (exec 3>&0) 2>/dev/null; then :; else exec 0&1) 2>/dev/null; then :; else exec 1>/dev/null; fi if (exec 3>&2) ; then :; else exec 2>/dev/null; fi # The user is always right. if ${PATH_SEPARATOR+false} :; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || PATH_SEPARATOR=';' } fi # Find who we are. Look in the path if we contain no directory separator. as_myself= case $0 in #(( *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac test -r "$as_dir$0" && as_myself=$as_dir$0 && break done IFS=$as_save_IFS ;; esac # We did not find ourselves, most probably we were run as `sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then printf "%s\n" "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi # Use a proper internal environment variable to ensure we don't fall # into an infinite loop, continuously re-executing ourselves. if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then _as_can_reexec=no; export _as_can_reexec; # We cannot yet assume a decent shell, so we have to provide a # neutralization value for shells without unset; and this also # works around shells that cannot unset nonexistent variables. # Preserve -v and -x to the replacement shell. BASH_ENV=/dev/null ENV=/dev/null (unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV case $- in # (((( *v*x* | *x*v* ) as_opts=-vx ;; *v* ) as_opts=-v ;; *x* ) as_opts=-x ;; * ) as_opts= ;; esac exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} # Admittedly, this is quite paranoid, since all the known shells bail # out after a failed `exec'. printf "%s\n" "$0: could not re-execute with $CONFIG_SHELL" >&2 exit 255 fi # We don't want this to propagate to other subprocesses. { _as_can_reexec=; unset _as_can_reexec;} if test "x$CONFIG_SHELL" = x; then as_bourne_compatible="as_nop=: if test \${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which # is contrary to our usage. Disable this feature. alias -g '\${1+\"\$@\"}'='\"\$@\"' setopt NO_GLOB_SUBST else \$as_nop case \`(set -o) 2>/dev/null\` in #( *posix*) : set -o posix ;; #( *) : ;; esac fi " as_required="as_fn_return () { (exit \$1); } as_fn_success () { as_fn_return 0; } as_fn_failure () { as_fn_return 1; } as_fn_ret_success () { return 0; } as_fn_ret_failure () { return 1; } exitcode=0 as_fn_success || { exitcode=1; echo as_fn_success failed.; } as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } if ( set x; as_fn_ret_success y && test x = \"\$1\" ) then : else \$as_nop exitcode=1; echo positional parameters were not saved. fi test x\$exitcode = x0 || exit 1 blah=\$(echo \$(echo blah)) test x\"\$blah\" = xblah || exit 1 test -x / || exit 1" as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1" if (eval "$as_required") 2>/dev/null then : as_have_required=yes else $as_nop as_have_required=no fi if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null then : else $as_nop as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_found=false for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac as_found=: case $as_dir in #( /*) for as_base in sh bash ksh sh5; do # Try only shells that exist, to save several forks. as_shell=$as_dir$as_base if { test -f "$as_shell" || test -f "$as_shell.exe"; } && as_run=a "$as_shell" -c "$as_bourne_compatible""$as_required" 2>/dev/null then : CONFIG_SHELL=$as_shell as_have_required=yes if as_run=a "$as_shell" -c "$as_bourne_compatible""$as_suggested" 2>/dev/null then : break 2 fi fi done;; esac as_found=false done IFS=$as_save_IFS if $as_found then : else $as_nop if { test -f "$SHELL" || test -f "$SHELL.exe"; } && as_run=a "$SHELL" -c "$as_bourne_compatible""$as_required" 2>/dev/null then : CONFIG_SHELL=$SHELL as_have_required=yes fi fi if test "x$CONFIG_SHELL" != x then : export CONFIG_SHELL # We cannot yet assume a decent shell, so we have to provide a # neutralization value for shells without unset; and this also # works around shells that cannot unset nonexistent variables. # Preserve -v and -x to the replacement shell. BASH_ENV=/dev/null ENV=/dev/null (unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV case $- in # (((( *v*x* | *x*v* ) as_opts=-vx ;; *v* ) as_opts=-v ;; *x* ) as_opts=-x ;; * ) as_opts= ;; esac exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} # Admittedly, this is quite paranoid, since all the known shells bail # out after a failed `exec'. printf "%s\n" "$0: could not re-execute with $CONFIG_SHELL" >&2 exit 255 fi if test x$as_have_required = xno then : printf "%s\n" "$0: This script requires a shell more modern than all" printf "%s\n" "$0: the shells that I found on your system." if test ${ZSH_VERSION+y} ; then printf "%s\n" "$0: In particular, zsh $ZSH_VERSION has bugs and should" printf "%s\n" "$0: be upgraded to zsh 4.3.4 or later." else printf "%s\n" "$0: Please tell bug-autoconf@gnu.org and projects@yorhel.nl $0: about your system, including any error possibly output $0: before this message. Then install a modern shell, or $0: manually run the script under such a shell if you do $0: have one." fi exit 1 fi fi fi SHELL=${CONFIG_SHELL-/bin/sh} export SHELL # Unset more variables known to interfere with behavior of common tools. CLICOLOR_FORCE= GREP_OPTIONS= unset CLICOLOR_FORCE GREP_OPTIONS ## --------------------- ## ## M4sh Shell Functions. ## ## --------------------- ## # as_fn_unset VAR # --------------- # Portably unset VAR. as_fn_unset () { { eval $1=; unset $1;} } as_unset=as_fn_unset # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. as_fn_set_status () { return $1 } # as_fn_set_status # as_fn_exit STATUS # ----------------- # Exit the shell with STATUS, even in a "trap 0" or "set -e" context. as_fn_exit () { set +e as_fn_set_status $1 exit $1 } # as_fn_exit # as_fn_nop # --------- # Do nothing but, unlike ":", preserve the value of $?. as_fn_nop () { return $? } as_nop=as_fn_nop # as_fn_mkdir_p # ------------- # Create "$as_dir" as a directory, including parents if necessary. as_fn_mkdir_p () { case $as_dir in #( -*) as_dir=./$as_dir;; esac test -d "$as_dir" || eval $as_mkdir_p || { as_dirs= while :; do case $as_dir in #( *\'*) as_qdir=`printf "%s\n" "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" as_dir=`$as_dirname -- "$as_dir" || $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` test -d "$as_dir" && break done test -z "$as_dirs" || eval "mkdir $as_dirs" } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" } # as_fn_mkdir_p # as_fn_executable_p FILE # ----------------------- # Test if FILE is an executable regular file. as_fn_executable_p () { test -f "$1" && test -x "$1" } # as_fn_executable_p # as_fn_append VAR VALUE # ---------------------- # Append the text in VALUE to the end of the definition contained in VAR. Take # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null then : eval 'as_fn_append () { eval $1+=\$2 }' else $as_nop as_fn_append () { eval $1=\$$1\$2 } fi # as_fn_append # as_fn_arith ARG... # ------------------ # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null then : eval 'as_fn_arith () { as_val=$(( $* )) }' else $as_nop as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` } fi # as_fn_arith # as_fn_nop # --------- # Do nothing but, unlike ":", preserve the value of $?. as_fn_nop () { return $? } as_nop=as_fn_nop # as_fn_error STATUS ERROR [LINENO LOG_FD] # ---------------------------------------- # Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are # provided, also output the error to LOG_FD, referencing LINENO. Then exit the # script with STATUS, using 1 if that was 0. as_fn_error () { as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi printf "%s\n" "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then as_dirname=dirname else as_dirname=false fi as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits as_lineno_1=$LINENO as_lineno_1a=$LINENO as_lineno_2=$LINENO as_lineno_2a=$LINENO eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" && test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || { # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-) sed -n ' p /[$]LINENO/= ' <$as_myself | sed ' s/[$]LINENO.*/&-/ t lineno b :lineno N :loop s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ t loop s/-\n.*// ' >$as_me.lineno && chmod +x "$as_me.lineno" || { printf "%s\n" "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } # If we had to re-execute with $CONFIG_SHELL, we're ensured to have # already done that, so ensure we don't try to do so again and fall # in an infinite loop. This has already happened in practice. _as_can_reexec=no; export _as_can_reexec # Don't try to exec as it changes $[0], causing all sort of problems # (the dirname of $[0] is not the place where we might find the # original and so on. Autoconf is especially sensitive to this). . "./$as_me.lineno" # Exit status is that of the last command. exit } # Determine whether it's possible to make 'echo' print without a newline. # These variables are no longer used directly by Autoconf, but are AC_SUBSTed # for compatibility with existing Makefiles. ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) case `echo 'xy\c'` in *c*) ECHO_T=' ';; # ECHO_T is single tab character. xy) ECHO_C='\c';; *) echo `echo ksh88 bug on AIX 6.1` > /dev/null ECHO_T=' ';; esac;; *) ECHO_N='-n';; esac # For backward compatibility with old third-party macros, we provide # the shell variables $as_echo and $as_echo_n. New code should use # AS_ECHO(["message"]) and AS_ECHO_N(["message"]), respectively. as_echo='printf %s\n' as_echo_n='printf %s' rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir mkdir conf$$.dir 2>/dev/null fi if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. # In both cases, we have to default to `cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -pR' fi else as_ln_s='cp -pR' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null if mkdir -p . 2>/dev/null; then as_mkdir_p='mkdir -p "$as_dir"' else test -d ./-p && rmdir ./-p as_mkdir_p=false fi as_test_x='test -x' as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" # Sed expression to map a string onto a valid variable name. as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" test -n "$DJDIR" || exec 7<&0 &1 # Name of the host. # hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status, # so uname gets run too. ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` # # Initializations. # ac_default_prefix=/usr/local ac_clean_files= ac_config_libobj_dir=. LIBOBJS= cross_compiling=no subdirs= MFLAGS= MAKEFLAGS= # Identity of this package. PACKAGE_NAME='ncdc' PACKAGE_TARNAME='ncdc' PACKAGE_VERSION='1.23.1' PACKAGE_STRING='ncdc 1.23.1' PACKAGE_BUGREPORT='projects@yorhel.nl' PACKAGE_URL='' ac_unique_file="src/ncdc.h" ac_subst_vars='am__EXEEXT_FALSE am__EXEEXT_TRUE LTLIBOBJS LIBOBJS INSTALL_MANPAGE_FALSE INSTALL_MANPAGE_TRUE USE_GIT_VERSION_FALSE USE_GIT_VERSION_TRUE GIT GEOIP_LIBS GEOIP_CFLAGS GNUTLS_LIBS GNUTLS_CFLAGS GLIB_LIBS GLIB_CFLAGS SQLITE_LIBS SQLITE_CFLAGS BZ2_LIBS Z_LIBS NCURSES_LIBS NCURSES_CFLAGS HAVE_MH_FALSE HAVE_MH_TRUE have_mh USE_POD2MAN_FALSE USE_POD2MAN_TRUE have_pod2man RANLIB am__fastdepCC_FALSE am__fastdepCC_TRUE CCDEPMODE am__nodep AMDEPBACKSLASH AMDEP_FALSE AMDEP_TRUE am__include DEPDIR OBJEXT EXEEXT ac_ct_CC CPPFLAGS LDFLAGS CFLAGS CC PKG_CONFIG_LIBDIR PKG_CONFIG_PATH PKG_CONFIG AM_BACKSLASH AM_DEFAULT_VERBOSITY AM_DEFAULT_V AM_V CSCOPE ETAGS CTAGS am__untar am__tar AMTAR am__leading_dot SET_MAKE AWK mkdir_p MKDIR_P INSTALL_STRIP_PROGRAM STRIP install_sh MAKEINFO AUTOHEADER AUTOMAKE AUTOCONF ACLOCAL VERSION PACKAGE CYGPATH_W am__isrc INSTALL_DATA INSTALL_SCRIPT INSTALL_PROGRAM target_alias host_alias build_alias LIBS ECHO_T ECHO_N ECHO_C DEFS mandir localedir libdir psdir pdfdir dvidir htmldir infodir docdir oldincludedir includedir runstatedir localstatedir sharedstatedir sysconfdir datadir datarootdir libexecdir sbindir bindir program_transform_name prefix exec_prefix PACKAGE_URL PACKAGE_BUGREPORT PACKAGE_STRING PACKAGE_VERSION PACKAGE_TARNAME PACKAGE_NAME PATH_SEPARATOR SHELL am__quote' ac_subst_files='' ac_user_opts=' enable_option_checking enable_silent_rules enable_dependency_tracking enable_largefile with_geoip enable_git_version ' ac_precious_vars='build_alias host_alias target_alias PKG_CONFIG PKG_CONFIG_PATH PKG_CONFIG_LIBDIR CC CFLAGS LDFLAGS LIBS CPPFLAGS NCURSES_CFLAGS NCURSES_LIBS SQLITE_CFLAGS SQLITE_LIBS GLIB_CFLAGS GLIB_LIBS GNUTLS_CFLAGS GNUTLS_LIBS GEOIP_CFLAGS GEOIP_LIBS' # Initialize some variables set by options. ac_init_help= ac_init_version=false ac_unrecognized_opts= ac_unrecognized_sep= # The variables have the same names as the options, with # dashes changed to underlines. cache_file=/dev/null exec_prefix=NONE no_create= no_recursion= prefix=NONE program_prefix=NONE program_suffix=NONE program_transform_name=s,x,x, silent= site= srcdir= verbose= x_includes=NONE x_libraries=NONE # Installation directory options. # These are left unexpanded so users can "make install exec_prefix=/foo" # and all the variables that are supposed to be based on exec_prefix # by default will actually change. # Use braces instead of parens because sh, perl, etc. also accept them. # (The list follows the same order as the GNU Coding Standards.) bindir='${exec_prefix}/bin' sbindir='${exec_prefix}/sbin' libexecdir='${exec_prefix}/libexec' datarootdir='${prefix}/share' datadir='${datarootdir}' sysconfdir='${prefix}/etc' sharedstatedir='${prefix}/com' localstatedir='${prefix}/var' runstatedir='${localstatedir}/run' includedir='${prefix}/include' oldincludedir='/usr/include' docdir='${datarootdir}/doc/${PACKAGE_TARNAME}' infodir='${datarootdir}/info' htmldir='${docdir}' dvidir='${docdir}' pdfdir='${docdir}' psdir='${docdir}' libdir='${exec_prefix}/lib' localedir='${datarootdir}/locale' mandir='${datarootdir}/man' ac_prev= ac_dashdash= for ac_option do # If the previous option needs an argument, assign it. if test -n "$ac_prev"; then eval $ac_prev=\$ac_option ac_prev= continue fi case $ac_option in *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; *=) ac_optarg= ;; *) ac_optarg=yes ;; esac case $ac_dashdash$ac_option in --) ac_dashdash=yes ;; -bindir | --bindir | --bindi | --bind | --bin | --bi) ac_prev=bindir ;; -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) bindir=$ac_optarg ;; -build | --build | --buil | --bui | --bu) ac_prev=build_alias ;; -build=* | --build=* | --buil=* | --bui=* | --bu=*) build_alias=$ac_optarg ;; -cache-file | --cache-file | --cache-fil | --cache-fi \ | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) ac_prev=cache_file ;; -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) cache_file=$ac_optarg ;; --config-cache | -C) cache_file=config.cache ;; -datadir | --datadir | --datadi | --datad) ac_prev=datadir ;; -datadir=* | --datadir=* | --datadi=* | --datad=*) datadir=$ac_optarg ;; -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \ | --dataroo | --dataro | --datar) ac_prev=datarootdir ;; -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \ | --dataroot=* | --dataroo=* | --dataro=* | --datar=*) datarootdir=$ac_optarg ;; -disable-* | --disable-*) ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid feature name: \`$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval enable_$ac_useropt=no ;; -docdir | --docdir | --docdi | --doc | --do) ac_prev=docdir ;; -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*) docdir=$ac_optarg ;; -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv) ac_prev=dvidir ;; -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*) dvidir=$ac_optarg ;; -enable-* | --enable-*) ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid feature name: \`$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval enable_$ac_useropt=\$ac_optarg ;; -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ | --exec | --exe | --ex) ac_prev=exec_prefix ;; -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ | --exec=* | --exe=* | --ex=*) exec_prefix=$ac_optarg ;; -gas | --gas | --ga | --g) # Obsolete; use --with-gas. with_gas=yes ;; -help | --help | --hel | --he | -h) ac_init_help=long ;; -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) ac_init_help=recursive ;; -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) ac_init_help=short ;; -host | --host | --hos | --ho) ac_prev=host_alias ;; -host=* | --host=* | --hos=* | --ho=*) host_alias=$ac_optarg ;; -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht) ac_prev=htmldir ;; -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \ | --ht=*) htmldir=$ac_optarg ;; -includedir | --includedir | --includedi | --included | --include \ | --includ | --inclu | --incl | --inc) ac_prev=includedir ;; -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ | --includ=* | --inclu=* | --incl=* | --inc=*) includedir=$ac_optarg ;; -infodir | --infodir | --infodi | --infod | --info | --inf) ac_prev=infodir ;; -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) infodir=$ac_optarg ;; -libdir | --libdir | --libdi | --libd) ac_prev=libdir ;; -libdir=* | --libdir=* | --libdi=* | --libd=*) libdir=$ac_optarg ;; -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ | --libexe | --libex | --libe) ac_prev=libexecdir ;; -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ | --libexe=* | --libex=* | --libe=*) libexecdir=$ac_optarg ;; -localedir | --localedir | --localedi | --localed | --locale) ac_prev=localedir ;; -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*) localedir=$ac_optarg ;; -localstatedir | --localstatedir | --localstatedi | --localstated \ | --localstate | --localstat | --localsta | --localst | --locals) ac_prev=localstatedir ;; -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*) localstatedir=$ac_optarg ;; -mandir | --mandir | --mandi | --mand | --man | --ma | --m) ac_prev=mandir ;; -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) mandir=$ac_optarg ;; -nfp | --nfp | --nf) # Obsolete; use --without-fp. with_fp=no ;; -no-create | --no-create | --no-creat | --no-crea | --no-cre \ | --no-cr | --no-c | -n) no_create=yes ;; -no-recursion | --no-recursion | --no-recursio | --no-recursi \ | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) no_recursion=yes ;; -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ | --oldin | --oldi | --old | --ol | --o) ac_prev=oldincludedir ;; -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) oldincludedir=$ac_optarg ;; -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) ac_prev=prefix ;; -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) prefix=$ac_optarg ;; -program-prefix | --program-prefix | --program-prefi | --program-pref \ | --program-pre | --program-pr | --program-p) ac_prev=program_prefix ;; -program-prefix=* | --program-prefix=* | --program-prefi=* \ | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) program_prefix=$ac_optarg ;; -program-suffix | --program-suffix | --program-suffi | --program-suff \ | --program-suf | --program-su | --program-s) ac_prev=program_suffix ;; -program-suffix=* | --program-suffix=* | --program-suffi=* \ | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) program_suffix=$ac_optarg ;; -program-transform-name | --program-transform-name \ | --program-transform-nam | --program-transform-na \ | --program-transform-n | --program-transform- \ | --program-transform | --program-transfor \ | --program-transfo | --program-transf \ | --program-trans | --program-tran \ | --progr-tra | --program-tr | --program-t) ac_prev=program_transform_name ;; -program-transform-name=* | --program-transform-name=* \ | --program-transform-nam=* | --program-transform-na=* \ | --program-transform-n=* | --program-transform-=* \ | --program-transform=* | --program-transfor=* \ | --program-transfo=* | --program-transf=* \ | --program-trans=* | --program-tran=* \ | --progr-tra=* | --program-tr=* | --program-t=*) program_transform_name=$ac_optarg ;; -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd) ac_prev=pdfdir ;; -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*) pdfdir=$ac_optarg ;; -psdir | --psdir | --psdi | --psd | --ps) ac_prev=psdir ;; -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*) psdir=$ac_optarg ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil) silent=yes ;; -runstatedir | --runstatedir | --runstatedi | --runstated \ | --runstate | --runstat | --runsta | --runst | --runs \ | --run | --ru | --r) ac_prev=runstatedir ;; -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \ | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \ | --run=* | --ru=* | --r=*) runstatedir=$ac_optarg ;; -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) ac_prev=sbindir ;; -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ | --sbi=* | --sb=*) sbindir=$ac_optarg ;; -sharedstatedir | --sharedstatedir | --sharedstatedi \ | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ | --sharedst | --shareds | --shared | --share | --shar \ | --sha | --sh) ac_prev=sharedstatedir ;; -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ | --sha=* | --sh=*) sharedstatedir=$ac_optarg ;; -site | --site | --sit) ac_prev=site ;; -site=* | --site=* | --sit=*) site=$ac_optarg ;; -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) ac_prev=srcdir ;; -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) srcdir=$ac_optarg ;; -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ | --syscon | --sysco | --sysc | --sys | --sy) ac_prev=sysconfdir ;; -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) sysconfdir=$ac_optarg ;; -target | --target | --targe | --targ | --tar | --ta | --t) ac_prev=target_alias ;; -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) target_alias=$ac_optarg ;; -v | -verbose | --verbose | --verbos | --verbo | --verb) verbose=yes ;; -version | --version | --versio | --versi | --vers | -V) ac_init_version=: ;; -with-* | --with-*) ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid package name: \`$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval with_$ac_useropt=\$ac_optarg ;; -without-* | --without-*) ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid package name: \`$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval with_$ac_useropt=no ;; --x) # Obsolete; use --with-x. with_x=yes ;; -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ | --x-incl | --x-inc | --x-in | --x-i) ac_prev=x_includes ;; -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) x_includes=$ac_optarg ;; -x-libraries | --x-libraries | --x-librarie | --x-librari \ | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) ac_prev=x_libraries ;; -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) x_libraries=$ac_optarg ;; -*) as_fn_error $? "unrecognized option: \`$ac_option' Try \`$0 --help' for more information" ;; *=*) ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` # Reject names that are not valid shell variable names. case $ac_envvar in #( '' | [0-9]* | *[!_$as_cr_alnum]* ) as_fn_error $? "invalid variable name: \`$ac_envvar'" ;; esac eval $ac_envvar=\$ac_optarg export $ac_envvar ;; *) # FIXME: should be removed in autoconf 3.0. printf "%s\n" "$as_me: WARNING: you should use --build, --host, --target" >&2 expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && printf "%s\n" "$as_me: WARNING: invalid host type: $ac_option" >&2 : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" ;; esac done if test -n "$ac_prev"; then ac_option=--`echo $ac_prev | sed 's/_/-/g'` as_fn_error $? "missing argument to $ac_option" fi if test -n "$ac_unrecognized_opts"; then case $enable_option_checking in no) ;; fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; *) printf "%s\n" "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; esac fi # Check all directory arguments for consistency. for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ datadir sysconfdir sharedstatedir localstatedir includedir \ oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ libdir localedir mandir runstatedir do eval ac_val=\$$ac_var # Remove trailing slashes. case $ac_val in */ ) ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'` eval $ac_var=\$ac_val;; esac # Be sure to have absolute directory names. case $ac_val in [\\/$]* | ?:[\\/]* ) continue;; NONE | '' ) case $ac_var in *prefix ) continue;; esac;; esac as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" done # There might be people who depend on the old broken behavior: `$host' # used to hold the argument of --host etc. # FIXME: To remove some day. build=$build_alias host=$host_alias target=$target_alias # FIXME: To remove some day. if test "x$host_alias" != x; then if test "x$build_alias" = x; then cross_compiling=maybe elif test "x$build_alias" != "x$host_alias"; then cross_compiling=yes fi fi ac_tool_prefix= test -n "$host_alias" && ac_tool_prefix=$host_alias- test "$silent" = yes && exec 6>/dev/null ac_pwd=`pwd` && test -n "$ac_pwd" && ac_ls_di=`ls -di .` && ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || as_fn_error $? "working directory cannot be determined" test "X$ac_ls_di" = "X$ac_pwd_ls_di" || as_fn_error $? "pwd does not report name of working directory" # Find the source files, if location was not specified. if test -z "$srcdir"; then ac_srcdir_defaulted=yes # Try the directory containing this script, then the parent directory. ac_confdir=`$as_dirname -- "$as_myself" || $as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_myself" : 'X\(//\)[^/]' \| \ X"$as_myself" : 'X\(//\)$' \| \ X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X"$as_myself" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` srcdir=$ac_confdir if test ! -r "$srcdir/$ac_unique_file"; then srcdir=.. fi else ac_srcdir_defaulted=no fi if test ! -r "$srcdir/$ac_unique_file"; then test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" fi ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work" ac_abs_confdir=`( cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" pwd)` # When building in place, set srcdir=. if test "$ac_abs_confdir" = "$ac_pwd"; then srcdir=. fi # Remove unnecessary trailing slashes from srcdir. # Double slashes in file names in object file debugging info # mess up M-x gdb in Emacs. case $srcdir in */) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;; esac for ac_var in $ac_precious_vars; do eval ac_env_${ac_var}_set=\${${ac_var}+set} eval ac_env_${ac_var}_value=\$${ac_var} eval ac_cv_env_${ac_var}_set=\${${ac_var}+set} eval ac_cv_env_${ac_var}_value=\$${ac_var} done # # Report the --help message. # if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF \`configure' configures ncdc 1.23.1 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... To assign environment variables (e.g., CC, CFLAGS...), specify them as VAR=VALUE. See below for descriptions of some of the useful variables. Defaults for the options are specified in brackets. Configuration: -h, --help display this help and exit --help=short display options specific to this package --help=recursive display the short help of all the included packages -V, --version display version information and exit -q, --quiet, --silent do not print \`checking ...' messages --cache-file=FILE cache test results in FILE [disabled] -C, --config-cache alias for \`--cache-file=config.cache' -n, --no-create do not create output files --srcdir=DIR find the sources in DIR [configure dir or \`..'] Installation directories: --prefix=PREFIX install architecture-independent files in PREFIX [$ac_default_prefix] --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX [PREFIX] By default, \`make install' will install all the files in \`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify an installation prefix other than \`$ac_default_prefix' using \`--prefix', for instance \`--prefix=\$HOME'. For better control, use the options below. Fine tuning of the installation directories: --bindir=DIR user executables [EPREFIX/bin] --sbindir=DIR system admin executables [EPREFIX/sbin] --libexecdir=DIR program executables [EPREFIX/libexec] --sysconfdir=DIR read-only single-machine data [PREFIX/etc] --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] --localstatedir=DIR modifiable single-machine data [PREFIX/var] --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run] --libdir=DIR object code libraries [EPREFIX/lib] --includedir=DIR C header files [PREFIX/include] --oldincludedir=DIR C header files for non-gcc [/usr/include] --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] --datadir=DIR read-only architecture-independent data [DATAROOTDIR] --infodir=DIR info documentation [DATAROOTDIR/info] --localedir=DIR locale-dependent data [DATAROOTDIR/locale] --mandir=DIR man documentation [DATAROOTDIR/man] --docdir=DIR documentation root [DATAROOTDIR/doc/ncdc] --htmldir=DIR html documentation [DOCDIR] --dvidir=DIR dvi documentation [DOCDIR] --pdfdir=DIR pdf documentation [DOCDIR] --psdir=DIR ps documentation [DOCDIR] _ACEOF cat <<\_ACEOF Program names: --program-prefix=PREFIX prepend PREFIX to installed program names --program-suffix=SUFFIX append SUFFIX to installed program names --program-transform-name=PROGRAM run sed PROGRAM on installed program names _ACEOF fi if test -n "$ac_init_help"; then case $ac_init_help in short | recursive ) echo "Configuration of ncdc 1.23.1:";; esac cat <<\_ACEOF Optional Features: --disable-option-checking ignore unrecognized --enable/--with options --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) --enable-FEATURE[=ARG] include FEATURE [ARG=yes] --enable-silent-rules less verbose build output (undo: "make V=1") --disable-silent-rules verbose build output (undo: "make V=0") --enable-dependency-tracking do not reject slow dependency extractors --disable-dependency-tracking speeds up one-time build --disable-largefile omit support for large files --enable-git-version enable use of git version in version string if available Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) --with-geoip support for IP-to-country lookups [default=no] Some influential environment variables: PKG_CONFIG path to pkg-config utility PKG_CONFIG_PATH directories to add to pkg-config's search path PKG_CONFIG_LIBDIR path overriding pkg-config's built-in search path CC C compiler command CFLAGS C compiler flags LDFLAGS linker flags, e.g. -L if you have libraries in a nonstandard directory LIBS libraries to pass to the linker, e.g. -l CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I if you have headers in a nonstandard directory NCURSES_CFLAGS C compiler flags for NCURSES, overriding pkg-config NCURSES_LIBS linker flags for NCURSES, overriding pkg-config SQLITE_CFLAGS C compiler flags for SQLITE, overriding pkg-config SQLITE_LIBS linker flags for SQLITE, overriding pkg-config GLIB_CFLAGS C compiler flags for GLIB, overriding pkg-config GLIB_LIBS linker flags for GLIB, overriding pkg-config GNUTLS_CFLAGS C compiler flags for GNUTLS, overriding pkg-config GNUTLS_LIBS linker flags for GNUTLS, overriding pkg-config GEOIP_CFLAGS C compiler flags for GEOIP, overriding pkg-config GEOIP_LIBS linker flags for GEOIP, overriding pkg-config Use these variables to override the choices made by `configure' or to help it to find libraries and programs with nonstandard names/locations. Report bugs to . _ACEOF ac_status=$? fi if test "$ac_init_help" = "recursive"; then # If there are subdirs, report their specific --help. for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue test -d "$ac_dir" || { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } || continue ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_dir_suffix=/`printf "%s\n" "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. ac_top_builddir_sub=`printf "%s\n" "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; esac ;; esac ac_abs_top_builddir=$ac_pwd ac_abs_builddir=$ac_pwd$ac_dir_suffix # for backward compatibility: ac_top_builddir=$ac_top_build_prefix case $srcdir in .) # We are building in place. ac_srcdir=. ac_top_srcdir=$ac_top_builddir_sub ac_abs_top_srcdir=$ac_pwd ;; [\\/]* | ?:[\\/]* ) # Absolute name. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ac_abs_top_srcdir=$srcdir ;; *) # Relative name. ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_build_prefix$srcdir ac_abs_top_srcdir=$ac_pwd/$srcdir ;; esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix cd "$ac_dir" || { ac_status=$?; continue; } # Check for configure.gnu first; this name is used for a wrapper for # Metaconfig's "Configure" on case-insensitive file systems. if test -f "$ac_srcdir/configure.gnu"; then echo && $SHELL "$ac_srcdir/configure.gnu" --help=recursive elif test -f "$ac_srcdir/configure"; then echo && $SHELL "$ac_srcdir/configure" --help=recursive else printf "%s\n" "$as_me: WARNING: no configuration information is in $ac_dir" >&2 fi || ac_status=$? cd "$ac_pwd" || { ac_status=$?; break; } done fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF ncdc configure 1.23.1 generated by GNU Autoconf 2.71 Copyright (C) 2021 Free Software Foundation, Inc. This configure script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it. _ACEOF exit fi ## ------------------------ ## ## Autoconf initialization. ## ## ------------------------ ## # ac_fn_c_try_compile LINENO # -------------------------- # Try to compile conftest.$ac_ext, and return whether this succeeded. ac_fn_c_try_compile () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack rm -f conftest.$ac_objext conftest.beam if { { ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_compile") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { test -z "$ac_c_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext then : ac_retval=0 else $as_nop printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 fi eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_compile # ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES # ------------------------------------------------------- # Tests whether HEADER exists and can be compiled using the include files in # INCLUDES, setting the cache variable VAR accordingly. ac_fn_c_check_header_compile () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 printf %s "checking for $2... " >&6; } if eval test \${$3+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 #include <$2> _ACEOF if ac_fn_c_try_compile "$LINENO" then : eval "$3=yes" else $as_nop eval "$3=no" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext fi eval ac_res=\$$3 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 printf "%s\n" "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_check_header_compile # ac_fn_c_try_link LINENO # ----------------------- # Try to link conftest.$ac_ext, and return whether this succeeded. ac_fn_c_try_link () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack rm -f conftest.$ac_objext conftest.beam conftest$ac_exeext if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_link") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { test -z "$ac_c_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && { test "$cross_compiling" = yes || test -x conftest$ac_exeext } then : ac_retval=0 else $as_nop printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 fi # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would # interfere with the next link command; also delete a directory that is # left behind by Apple's compiler. We do this before executing the actions. rm -rf conftest.dSYM conftest_ipa8_conftest.oo eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_link # ac_fn_c_check_func LINENO FUNC VAR # ---------------------------------- # Tests whether FUNC exists, setting the cache variable VAR accordingly ac_fn_c_check_func () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 printf %s "checking for $2... " >&6; } if eval test \${$3+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Define $2 to an innocuous variant, in case declares $2. For example, HP-UX 11i declares gettimeofday. */ #define $2 innocuous_$2 /* System header to define __stub macros and hopefully few prototypes, which can conflict with char $2 (); below. */ #include #undef $2 /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char $2 (); /* The GNU C library defines this for functions which it implements to always fail with ENOSYS. Some functions are actually named something starting with __ and the normal name is an alias. */ #if defined __stub_$2 || defined __stub___$2 choke me #endif int main (void) { return $2 (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : eval "$3=yes" else $as_nop eval "$3=no" fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi eval ac_res=\$$3 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 printf "%s\n" "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_check_func ac_configure_args_raw= for ac_arg do case $ac_arg in *\'*) ac_arg=`printf "%s\n" "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; esac as_fn_append ac_configure_args_raw " '$ac_arg'" done case $ac_configure_args_raw in *$as_nl*) ac_safe_unquote= ;; *) ac_unsafe_z='|&;<>()$`\\"*?[ '' ' # This string ends in space, tab. ac_unsafe_a="$ac_unsafe_z#~" ac_safe_unquote="s/ '\\([^$ac_unsafe_a][^$ac_unsafe_z]*\\)'/ \\1/g" ac_configure_args_raw=` printf "%s\n" "$ac_configure_args_raw" | sed "$ac_safe_unquote"`;; esac cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. It was created by ncdc $as_me 1.23.1, which was generated by GNU Autoconf 2.71. Invocation command line was $ $0$ac_configure_args_raw _ACEOF exec 5>>config.log { cat <<_ASUNAME ## --------- ## ## Platform. ## ## --------- ## hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` uname -m = `(uname -m) 2>/dev/null || echo unknown` uname -r = `(uname -r) 2>/dev/null || echo unknown` uname -s = `(uname -s) 2>/dev/null || echo unknown` uname -v = `(uname -v) 2>/dev/null || echo unknown` /usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` /bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` /bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` /usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` /usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` /usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown` /bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` /usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` /bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` _ASUNAME as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac printf "%s\n" "PATH: $as_dir" done IFS=$as_save_IFS } >&5 cat >&5 <<_ACEOF ## ----------- ## ## Core tests. ## ## ----------- ## _ACEOF # Keep a trace of the command line. # Strip out --no-create and --no-recursion so they do not pile up. # Strip out --silent because we don't want to record it for future runs. # Also quote any args containing shell meta-characters. # Make two passes to allow for proper duplicate-argument suppression. ac_configure_args= ac_configure_args0= ac_configure_args1= ac_must_keep_next=false for ac_pass in 1 2 do for ac_arg do case $ac_arg in -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil) continue ;; *\'*) ac_arg=`printf "%s\n" "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; esac case $ac_pass in 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; 2) as_fn_append ac_configure_args1 " '$ac_arg'" if test $ac_must_keep_next = true; then ac_must_keep_next=false # Got value, back to normal. else case $ac_arg in *=* | --config-cache | -C | -disable-* | --disable-* \ | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ | -with-* | --with-* | -without-* | --without-* | --x) case "$ac_configure_args0 " in "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; esac ;; -* ) ac_must_keep_next=true ;; esac fi as_fn_append ac_configure_args " '$ac_arg'" ;; esac done done { ac_configure_args0=; unset ac_configure_args0;} { ac_configure_args1=; unset ac_configure_args1;} # When interrupted or exit'd, cleanup temporary files, and complete # config.log. We remove comments because anyway the quotes in there # would cause problems or look ugly. # WARNING: Use '\'' to represent an apostrophe within the trap. # WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. trap 'exit_status=$? # Sanitize IFS. IFS=" "" $as_nl" # Save into config.log some information that might help in debugging. { echo printf "%s\n" "## ---------------- ## ## Cache variables. ## ## ---------------- ##" echo # The following way of writing the cache mishandles newlines in values, ( for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do eval ac_val=\$$ac_var case $ac_val in #( *${as_nl}*) case $ac_var in #( *_cv_*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( *) { eval $ac_var=; unset $ac_var;} ;; esac ;; esac done (set) 2>&1 | case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #( *${as_nl}ac_space=\ *) sed -n \ "s/'\''/'\''\\\\'\'''\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p" ;; #( *) sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | sort ) echo printf "%s\n" "## ----------------- ## ## Output variables. ## ## ----------------- ##" echo for ac_var in $ac_subst_vars do eval ac_val=\$$ac_var case $ac_val in *\'\''*) ac_val=`printf "%s\n" "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac printf "%s\n" "$ac_var='\''$ac_val'\''" done | sort echo if test -n "$ac_subst_files"; then printf "%s\n" "## ------------------- ## ## File substitutions. ## ## ------------------- ##" echo for ac_var in $ac_subst_files do eval ac_val=\$$ac_var case $ac_val in *\'\''*) ac_val=`printf "%s\n" "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac printf "%s\n" "$ac_var='\''$ac_val'\''" done | sort echo fi if test -s confdefs.h; then printf "%s\n" "## ----------- ## ## confdefs.h. ## ## ----------- ##" echo cat confdefs.h echo fi test "$ac_signal" != 0 && printf "%s\n" "$as_me: caught signal $ac_signal" printf "%s\n" "$as_me: exit $exit_status" } >&5 rm -f core *.core core.conftest.* && rm -f -r conftest* confdefs* conf$$* $ac_clean_files && exit $exit_status ' 0 for ac_signal in 1 2 13 15; do trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal done ac_signal=0 # confdefs.h avoids OS command line length limits that DEFS can exceed. rm -f -r conftest* confdefs.h printf "%s\n" "/* confdefs.h */" > confdefs.h # Predefined preprocessor variables. printf "%s\n" "#define PACKAGE_NAME \"$PACKAGE_NAME\"" >>confdefs.h printf "%s\n" "#define PACKAGE_TARNAME \"$PACKAGE_TARNAME\"" >>confdefs.h printf "%s\n" "#define PACKAGE_VERSION \"$PACKAGE_VERSION\"" >>confdefs.h printf "%s\n" "#define PACKAGE_STRING \"$PACKAGE_STRING\"" >>confdefs.h printf "%s\n" "#define PACKAGE_BUGREPORT \"$PACKAGE_BUGREPORT\"" >>confdefs.h printf "%s\n" "#define PACKAGE_URL \"$PACKAGE_URL\"" >>confdefs.h # Let the site file select an alternate cache file if it wants to. # Prefer an explicitly selected file to automatically selected ones. if test -n "$CONFIG_SITE"; then ac_site_files="$CONFIG_SITE" elif test "x$prefix" != xNONE; then ac_site_files="$prefix/share/config.site $prefix/etc/config.site" else ac_site_files="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site" fi for ac_site_file in $ac_site_files do case $ac_site_file in #( */*) : ;; #( *) : ac_site_file=./$ac_site_file ;; esac if test -f "$ac_site_file" && test -r "$ac_site_file"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 printf "%s\n" "$as_me: loading site script $ac_site_file" >&6;} sed 's/^/| /' "$ac_site_file" >&5 . "$ac_site_file" \ || { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "failed to load site script $ac_site_file See \`config.log' for more details" "$LINENO" 5; } fi done if test -r "$cache_file"; then # Some versions of bash will fail to source /dev/null (special files # actually), so we avoid doing that. DJGPP emulates it as a regular file. if test /dev/null != "$cache_file" && test -f "$cache_file"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 printf "%s\n" "$as_me: loading cache $cache_file" >&6;} case $cache_file in [\\/]* | ?:[\\/]* ) . "$cache_file";; *) . "./$cache_file";; esac fi else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 printf "%s\n" "$as_me: creating cache $cache_file" >&6;} >$cache_file fi # Test code for whether the C compiler supports C89 (global declarations) ac_c_conftest_c89_globals=' /* Does the compiler advertise C89 conformance? Do not test the value of __STDC__, because some compilers set it to 0 while being otherwise adequately conformant. */ #if !defined __STDC__ # error "Compiler does not advertise C89 conformance" #endif #include #include struct stat; /* Most of the following tests are stolen from RCS 5.7 src/conf.sh. */ struct buf { int x; }; struct buf * (*rcsopen) (struct buf *, struct stat *, int); static char *e (p, i) char **p; int i; { return p[i]; } static char *f (char * (*g) (char **, int), char **p, ...) { char *s; va_list v; va_start (v,p); s = g (p, va_arg (v,int)); va_end (v); return s; } /* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has function prototypes and stuff, but not \xHH hex character constants. These do not provoke an error unfortunately, instead are silently treated as an "x". The following induces an error, until -std is added to get proper ANSI mode. Curiously \x00 != x always comes out true, for an array size at least. It is necessary to write \x00 == 0 to get something that is true only with -std. */ int osf4_cc_array ['\''\x00'\'' == 0 ? 1 : -1]; /* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters inside strings and character constants. */ #define FOO(x) '\''x'\'' int xlc6_cc_array[FOO(a) == '\''x'\'' ? 1 : -1]; int test (int i, double x); struct s1 {int (*f) (int a);}; struct s2 {int (*f) (double a);}; int pairnames (int, char **, int *(*)(struct buf *, struct stat *, int), int, int);' # Test code for whether the C compiler supports C89 (body of main). ac_c_conftest_c89_main=' ok |= (argc == 0 || f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]); ' # Test code for whether the C compiler supports C99 (global declarations) ac_c_conftest_c99_globals=' // Does the compiler advertise C99 conformance? #if !defined __STDC_VERSION__ || __STDC_VERSION__ < 199901L # error "Compiler does not advertise C99 conformance" #endif #include extern int puts (const char *); extern int printf (const char *, ...); extern int dprintf (int, const char *, ...); extern void *malloc (size_t); // Check varargs macros. These examples are taken from C99 6.10.3.5. // dprintf is used instead of fprintf to avoid needing to declare // FILE and stderr. #define debug(...) dprintf (2, __VA_ARGS__) #define showlist(...) puts (#__VA_ARGS__) #define report(test,...) ((test) ? puts (#test) : printf (__VA_ARGS__)) static void test_varargs_macros (void) { int x = 1234; int y = 5678; debug ("Flag"); debug ("X = %d\n", x); showlist (The first, second, and third items.); report (x>y, "x is %d but y is %d", x, y); } // Check long long types. #define BIG64 18446744073709551615ull #define BIG32 4294967295ul #define BIG_OK (BIG64 / BIG32 == 4294967297ull && BIG64 % BIG32 == 0) #if !BIG_OK #error "your preprocessor is broken" #endif #if BIG_OK #else #error "your preprocessor is broken" #endif static long long int bignum = -9223372036854775807LL; static unsigned long long int ubignum = BIG64; struct incomplete_array { int datasize; double data[]; }; struct named_init { int number; const wchar_t *name; double average; }; typedef const char *ccp; static inline int test_restrict (ccp restrict text) { // See if C++-style comments work. // Iterate through items via the restricted pointer. // Also check for declarations in for loops. for (unsigned int i = 0; *(text+i) != '\''\0'\''; ++i) continue; return 0; } // Check varargs and va_copy. static bool test_varargs (const char *format, ...) { va_list args; va_start (args, format); va_list args_copy; va_copy (args_copy, args); const char *str = ""; int number = 0; float fnumber = 0; while (*format) { switch (*format++) { case '\''s'\'': // string str = va_arg (args_copy, const char *); break; case '\''d'\'': // int number = va_arg (args_copy, int); break; case '\''f'\'': // float fnumber = va_arg (args_copy, double); break; default: break; } } va_end (args_copy); va_end (args); return *str && number && fnumber; } ' # Test code for whether the C compiler supports C99 (body of main). ac_c_conftest_c99_main=' // Check bool. _Bool success = false; success |= (argc != 0); // Check restrict. if (test_restrict ("String literal") == 0) success = true; char *restrict newvar = "Another string"; // Check varargs. success &= test_varargs ("s, d'\'' f .", "string", 65, 34.234); test_varargs_macros (); // Check flexible array members. struct incomplete_array *ia = malloc (sizeof (struct incomplete_array) + (sizeof (double) * 10)); ia->datasize = 10; for (int i = 0; i < ia->datasize; ++i) ia->data[i] = i * 1.234; // Check named initializers. struct named_init ni = { .number = 34, .name = L"Test wide string", .average = 543.34343, }; ni.number = 58; int dynamic_array[ni.number]; dynamic_array[0] = argv[0][0]; dynamic_array[ni.number - 1] = 543; // work around unused variable warnings ok |= (!success || bignum == 0LL || ubignum == 0uLL || newvar[0] == '\''x'\'' || dynamic_array[ni.number - 1] != 543); ' # Test code for whether the C compiler supports C11 (global declarations) ac_c_conftest_c11_globals=' // Does the compiler advertise C11 conformance? #if !defined __STDC_VERSION__ || __STDC_VERSION__ < 201112L # error "Compiler does not advertise C11 conformance" #endif // Check _Alignas. char _Alignas (double) aligned_as_double; char _Alignas (0) no_special_alignment; extern char aligned_as_int; char _Alignas (0) _Alignas (int) aligned_as_int; // Check _Alignof. enum { int_alignment = _Alignof (int), int_array_alignment = _Alignof (int[100]), char_alignment = _Alignof (char) }; _Static_assert (0 < -_Alignof (int), "_Alignof is signed"); // Check _Noreturn. int _Noreturn does_not_return (void) { for (;;) continue; } // Check _Static_assert. struct test_static_assert { int x; _Static_assert (sizeof (int) <= sizeof (long int), "_Static_assert does not work in struct"); long int y; }; // Check UTF-8 literals. #define u8 syntax error! char const utf8_literal[] = u8"happens to be ASCII" "another string"; // Check duplicate typedefs. typedef long *long_ptr; typedef long int *long_ptr; typedef long_ptr long_ptr; // Anonymous structures and unions -- taken from C11 6.7.2.1 Example 1. struct anonymous { union { struct { int i; int j; }; struct { int k; long int l; } w; }; int m; } v1; ' # Test code for whether the C compiler supports C11 (body of main). ac_c_conftest_c11_main=' _Static_assert ((offsetof (struct anonymous, i) == offsetof (struct anonymous, w.k)), "Anonymous union alignment botch"); v1.i = 2; v1.w.k = 5; ok |= v1.i != 5; ' # Test code for whether the C compiler supports C11 (complete). ac_c_conftest_c11_program="${ac_c_conftest_c89_globals} ${ac_c_conftest_c99_globals} ${ac_c_conftest_c11_globals} int main (int argc, char **argv) { int ok = 0; ${ac_c_conftest_c89_main} ${ac_c_conftest_c99_main} ${ac_c_conftest_c11_main} return ok; } " # Test code for whether the C compiler supports C99 (complete). ac_c_conftest_c99_program="${ac_c_conftest_c89_globals} ${ac_c_conftest_c99_globals} int main (int argc, char **argv) { int ok = 0; ${ac_c_conftest_c89_main} ${ac_c_conftest_c99_main} return ok; } " # Test code for whether the C compiler supports C89 (complete). ac_c_conftest_c89_program="${ac_c_conftest_c89_globals} int main (int argc, char **argv) { int ok = 0; ${ac_c_conftest_c89_main} return ok; } " # Auxiliary files required by this configure script. ac_aux_files="compile missing install-sh" # Locations in which to look for auxiliary files. ac_aux_dir_candidates="${srcdir}${PATH_SEPARATOR}${srcdir}/..${PATH_SEPARATOR}${srcdir}/../.." # Search for a directory containing all of the required auxiliary files, # $ac_aux_files, from the $PATH-style list $ac_aux_dir_candidates. # If we don't find one directory that contains all the files we need, # we report the set of missing files from the *first* directory in # $ac_aux_dir_candidates and give up. ac_missing_aux_files="" ac_first_candidate=: printf "%s\n" "$as_me:${as_lineno-$LINENO}: looking for aux files: $ac_aux_files" >&5 as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_found=false for as_dir in $ac_aux_dir_candidates do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac as_found=: printf "%s\n" "$as_me:${as_lineno-$LINENO}: trying $as_dir" >&5 ac_aux_dir_found=yes ac_install_sh= for ac_aux in $ac_aux_files do # As a special case, if "install-sh" is required, that requirement # can be satisfied by any of "install-sh", "install.sh", or "shtool", # and $ac_install_sh is set appropriately for whichever one is found. if test x"$ac_aux" = x"install-sh" then if test -f "${as_dir}install-sh"; then printf "%s\n" "$as_me:${as_lineno-$LINENO}: ${as_dir}install-sh found" >&5 ac_install_sh="${as_dir}install-sh -c" elif test -f "${as_dir}install.sh"; then printf "%s\n" "$as_me:${as_lineno-$LINENO}: ${as_dir}install.sh found" >&5 ac_install_sh="${as_dir}install.sh -c" elif test -f "${as_dir}shtool"; then printf "%s\n" "$as_me:${as_lineno-$LINENO}: ${as_dir}shtool found" >&5 ac_install_sh="${as_dir}shtool install -c" else ac_aux_dir_found=no if $ac_first_candidate; then ac_missing_aux_files="${ac_missing_aux_files} install-sh" else break fi fi else if test -f "${as_dir}${ac_aux}"; then printf "%s\n" "$as_me:${as_lineno-$LINENO}: ${as_dir}${ac_aux} found" >&5 else ac_aux_dir_found=no if $ac_first_candidate; then ac_missing_aux_files="${ac_missing_aux_files} ${ac_aux}" else break fi fi fi done if test "$ac_aux_dir_found" = yes; then ac_aux_dir="$as_dir" break fi ac_first_candidate=false as_found=false done IFS=$as_save_IFS if $as_found then : else $as_nop as_fn_error $? "cannot find required auxiliary files:$ac_missing_aux_files" "$LINENO" 5 fi # These three variables are undocumented and unsupported, # and are intended to be withdrawn in a future Autoconf release. # They can cause serious problems if a builder's source tree is in a directory # whose full name contains unusual characters. if test -f "${ac_aux_dir}config.guess"; then ac_config_guess="$SHELL ${ac_aux_dir}config.guess" fi if test -f "${ac_aux_dir}config.sub"; then ac_config_sub="$SHELL ${ac_aux_dir}config.sub" fi if test -f "$ac_aux_dir/configure"; then ac_configure="$SHELL ${ac_aux_dir}configure" fi # Check that the precious variables saved in the cache have kept the same # value. ac_cache_corrupted=false for ac_var in $ac_precious_vars; do eval ac_old_set=\$ac_cv_env_${ac_var}_set eval ac_new_set=\$ac_env_${ac_var}_set eval ac_old_val=\$ac_cv_env_${ac_var}_value eval ac_new_val=\$ac_env_${ac_var}_value case $ac_old_set,$ac_new_set in set,) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 printf "%s\n" "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} ac_cache_corrupted=: ;; ,set) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5 printf "%s\n" "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} ac_cache_corrupted=: ;; ,);; *) if test "x$ac_old_val" != "x$ac_new_val"; then # differences in whitespace do not lead to failure. ac_old_val_w=`echo x $ac_old_val` ac_new_val_w=`echo x $ac_new_val` if test "$ac_old_val_w" != "$ac_new_val_w"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5 printf "%s\n" "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} ac_cache_corrupted=: else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5 printf "%s\n" "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;} eval $ac_var=\$ac_old_val fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5 printf "%s\n" "$as_me: former value: \`$ac_old_val'" >&2;} { printf "%s\n" "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5 printf "%s\n" "$as_me: current value: \`$ac_new_val'" >&2;} fi;; esac # Pass precious variables to config.status. if test "$ac_new_set" = set; then case $ac_new_val in *\'*) ac_arg=$ac_var=`printf "%s\n" "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; *) ac_arg=$ac_var=$ac_new_val ;; esac case " $ac_configure_args " in *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. *) as_fn_append ac_configure_args " '$ac_arg'" ;; esac fi done if $ac_cache_corrupted; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 printf "%s\n" "$as_me: error: changes in the environment can compromise the build" >&2;} as_fn_error $? "run \`${MAKE-make} distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5 fi ## -------------------- ## ## Main body of script. ## ## -------------------- ## ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_config_headers="$ac_config_headers config.h" # autotools lean macros # hg 2012-09-01 05a8d3fa4611 # Copyright (c) 2012 Gregor Richards # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD # TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND # FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, # OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF # USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE # OF THIS SOFTWARE. # These macros make auto* tests faster by removing some of autoconf's most # absurd defaults. The basic principle is to not check for things that have no # alternatives. That is, don't perform a test if it's either going to pass and # affect nothing, or fail and just prevent you from building. These tests # provide very little real value since modern systems that they fail on are few # and far between. # automake's sanity checks provide nothing useful, since all they can do is # fail, sometimes spuriously, and prevent builds which may otherwise have # succeeded. # Checking for C89 compliance nowadays is just plain silly. # For the same reason, checking for C standard headers is usually stupid. # However, we simply avoid checking for them in the most ridiculous cases. # And add warnings for known-nasty builtin checks # POSIX says that make sets $(MAKE). That's good enough for me. # configure will simply fail, often spuriously, if you don't tell it that # you're cross compiling, so there's very little reason to explicitly check. # Allow the default GCC-and-compatible CFLAGS to be changed GCC_DEFAULT_CFLAGS="-g -O2" # The builtin -g test is simplified by avoiding rechecks for GCC (of course GCC # supports -g) # Option to force caching # Force the use of a cache file if we use subdirectories, as otherwise we # retest things in the subdirs. am__api_version='1.16' # Find a good install program. We prefer a C program (faster), # so one script is as good as another. But avoid the broken or # incompatible versions: # SysV /etc/install, /usr/sbin/install # SunOS /usr/etc/install # IRIX /sbin/install # AIX /bin/install # AmigaOS /C/install, which installs bootblocks on floppy discs # AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag # AFS /usr/afsws/bin/install, which mishandles nonexistent args # SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff" # OS/2's system install, which has a completely different semantic # ./install, which can be erroneously created by make from ./install.sh. # Reject install programs that cannot install multiple files. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5 printf %s "checking for a BSD-compatible install... " >&6; } if test -z "$INSTALL"; then if test ${ac_cv_path_install+y} then : printf %s "(cached) " >&6 else $as_nop as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac # Account for fact that we put trailing slashes in our PATH walk. case $as_dir in #(( ./ | /[cC]/* | \ /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \ ?:[\\/]os2[\\/]install[\\/]* | ?:[\\/]OS2[\\/]INSTALL[\\/]* | \ /usr/ucb/* ) ;; *) # OSF1 and SCO ODT 3.0 have their own names for install. # Don't use installbsd from OSF since it installs stuff as root # by default. for ac_prog in ginstall scoinst install; do for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_prog$ac_exec_ext"; then if test $ac_prog = install && grep dspmsg "$as_dir$ac_prog$ac_exec_ext" >/dev/null 2>&1; then # AIX install. It has an incompatible calling convention. : elif test $ac_prog = install && grep pwplus "$as_dir$ac_prog$ac_exec_ext" >/dev/null 2>&1; then # program-specific install script used by HP pwplus--don't use. : else rm -rf conftest.one conftest.two conftest.dir echo one > conftest.one echo two > conftest.two mkdir conftest.dir if "$as_dir$ac_prog$ac_exec_ext" -c conftest.one conftest.two "`pwd`/conftest.dir/" && test -s conftest.one && test -s conftest.two && test -s conftest.dir/conftest.one && test -s conftest.dir/conftest.two then ac_cv_path_install="$as_dir$ac_prog$ac_exec_ext -c" break 3 fi fi fi done done ;; esac done IFS=$as_save_IFS rm -rf conftest.one conftest.two conftest.dir fi if test ${ac_cv_path_install+y}; then INSTALL=$ac_cv_path_install else # As a last resort, use the slow shell script. Don't cache a # value for INSTALL within a source directory, because that will # break other packages using the cache if that directory is # removed, or if the value is a relative name. INSTALL=$ac_install_sh fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $INSTALL" >&5 printf "%s\n" "$INSTALL" >&6; } # Use test -z because SunOS4 sh mishandles braces in ${var-val}. # It thinks the first close brace ends the variable substitution. test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}' test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}' test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' test "$program_prefix" != NONE && program_transform_name="s&^&$program_prefix&;$program_transform_name" # Use a double $ so make ignores it. test "$program_suffix" != NONE && program_transform_name="s&\$&$program_suffix&;$program_transform_name" # Double any \ or $. # By default was `s,x,x', remove it if useless. ac_script='s/[\\$]/&&/g;s/;s,x,x,$//' program_transform_name=`printf "%s\n" "$program_transform_name" | sed "$ac_script"` # Expand $ac_aux_dir to an absolute path. am_aux_dir=`cd "$ac_aux_dir" && pwd` if test x"${MISSING+set}" != xset; then MISSING="\${SHELL} '$am_aux_dir/missing'" fi # Use eval to expand $SHELL if eval "$MISSING --is-lightweight"; then am_missing_run="$MISSING " else am_missing_run= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: 'missing' script is too old or missing" >&5 printf "%s\n" "$as_me: WARNING: 'missing' script is too old or missing" >&2;} fi if test x"${install_sh+set}" != xset; then case $am_aux_dir in *\ * | *\ *) install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;; *) install_sh="\${SHELL} $am_aux_dir/install-sh" esac fi # Installed binaries are usually stripped using 'strip' when the user # run "make install-strip". However 'strip' might not be the right # tool to use in cross-compilation environments, therefore Automake # will honor the 'STRIP' environment variable to overrule this program. if test "$cross_compiling" != no; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}strip", so it can be a program name with args. set dummy ${ac_tool_prefix}strip; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_STRIP+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$STRIP"; then ac_cv_prog_STRIP="$STRIP" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_STRIP="${ac_tool_prefix}strip" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi STRIP=$ac_cv_prog_STRIP if test -n "$STRIP"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $STRIP" >&5 printf "%s\n" "$STRIP" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test -z "$ac_cv_prog_STRIP"; then ac_ct_STRIP=$STRIP # Extract the first word of "strip", so it can be a program name with args. set dummy strip; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_STRIP+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$ac_ct_STRIP"; then ac_cv_prog_ac_ct_STRIP="$ac_ct_STRIP" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_STRIP="strip" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_STRIP=$ac_cv_prog_ac_ct_STRIP if test -n "$ac_ct_STRIP"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_STRIP" >&5 printf "%s\n" "$ac_ct_STRIP" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "x$ac_ct_STRIP" = x; then STRIP=":" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac STRIP=$ac_ct_STRIP fi else STRIP="$ac_cv_prog_STRIP" fi fi INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for a race-free mkdir -p" >&5 printf %s "checking for a race-free mkdir -p... " >&6; } if test -z "$MKDIR_P"; then if test ${ac_cv_path_mkdir+y} then : printf %s "(cached) " >&6 else $as_nop as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH$PATH_SEPARATOR/opt/sfw/bin do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_prog in mkdir gmkdir; do for ac_exec_ext in '' $ac_executable_extensions; do as_fn_executable_p "$as_dir$ac_prog$ac_exec_ext" || continue case `"$as_dir$ac_prog$ac_exec_ext" --version 2>&1` in #( 'mkdir ('*'coreutils) '* | \ 'BusyBox '* | \ 'mkdir (fileutils) '4.1*) ac_cv_path_mkdir=$as_dir$ac_prog$ac_exec_ext break 3;; esac done done done IFS=$as_save_IFS fi test -d ./--version && rmdir ./--version if test ${ac_cv_path_mkdir+y}; then MKDIR_P="$ac_cv_path_mkdir -p" else # As a last resort, use the slow shell script. Don't cache a # value for MKDIR_P within a source directory, because that will # break other packages using the cache if that directory is # removed, or if the value is a relative name. MKDIR_P="$ac_install_sh -d" fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $MKDIR_P" >&5 printf "%s\n" "$MKDIR_P" >&6; } for ac_prog in gawk mawk nawk awk do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_AWK+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$AWK"; then ac_cv_prog_AWK="$AWK" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_AWK="$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi AWK=$ac_cv_prog_AWK if test -n "$AWK"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $AWK" >&5 printf "%s\n" "$AWK" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$AWK" && break done ac_cv_prog_make_make_set=yes SET_MAKE= rm -rf .tst 2>/dev/null mkdir .tst 2>/dev/null if test -d .tst; then am__leading_dot=. else am__leading_dot=_ fi rmdir .tst 2>/dev/null # Check whether --enable-silent-rules was given. if test ${enable_silent_rules+y} then : enableval=$enable_silent_rules; fi case $enable_silent_rules in # ((( yes) AM_DEFAULT_VERBOSITY=0;; no) AM_DEFAULT_VERBOSITY=1;; *) AM_DEFAULT_VERBOSITY=1;; esac am_make=${MAKE-make} { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $am_make supports nested variables" >&5 printf %s "checking whether $am_make supports nested variables... " >&6; } if test ${am_cv_make_support_nested_variables+y} then : printf %s "(cached) " >&6 else $as_nop if printf "%s\n" 'TRUE=$(BAR$(V)) BAR0=false BAR1=true V=1 am__doit: @$(TRUE) .PHONY: am__doit' | $am_make -f - >/dev/null 2>&1; then am_cv_make_support_nested_variables=yes else am_cv_make_support_nested_variables=no fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $am_cv_make_support_nested_variables" >&5 printf "%s\n" "$am_cv_make_support_nested_variables" >&6; } if test $am_cv_make_support_nested_variables = yes; then AM_V='$(V)' AM_DEFAULT_V='$(AM_DEFAULT_VERBOSITY)' else AM_V=$AM_DEFAULT_VERBOSITY AM_DEFAULT_V=$AM_DEFAULT_VERBOSITY fi AM_BACKSLASH='\' if test "`cd $srcdir && pwd`" != "`pwd`"; then # Use -I$(srcdir) only when $(srcdir) != ., so that make's output # is not polluted with repeated "-I." am__isrc=' -I$(srcdir)' # test to see if srcdir already configured if test -f $srcdir/config.status; then as_fn_error $? "source directory already configured; run \"make distclean\" there first" "$LINENO" 5 fi fi # test whether we have cygpath if test -z "$CYGPATH_W"; then if (cygpath --version) >/dev/null 2>/dev/null; then CYGPATH_W='cygpath -w' else CYGPATH_W=echo fi fi # Define the identity of the package. PACKAGE='ncdc' VERSION='1.23.1' printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h printf "%s\n" "#define VERSION \"$VERSION\"" >>confdefs.h # Some tools Automake needs. ACLOCAL=${ACLOCAL-"${am_missing_run}aclocal-${am__api_version}"} AUTOCONF=${AUTOCONF-"${am_missing_run}autoconf"} AUTOMAKE=${AUTOMAKE-"${am_missing_run}automake-${am__api_version}"} AUTOHEADER=${AUTOHEADER-"${am_missing_run}autoheader"} MAKEINFO=${MAKEINFO-"${am_missing_run}makeinfo"} # For better backward compatibility. To be removed once Automake 1.9.x # dies out for good. For more background, see: # # mkdir_p='$(MKDIR_P)' # We need awk for the "check" target (and possibly the TAP driver). The # system "awk" is bad on some platforms. # Always define AMTAR for backward compatibility. Yes, it's still used # in the wild :-( We should find a proper way to deprecate it ... AMTAR='$${TAR-tar}' # We'll loop over all known methods to create a tar archive until one works. _am_tools='gnutar pax cpio none' am__tar='$${TAR-tar} chof - "$$tardir"' am__untar='$${TAR-tar} xf -' # Variables for tags utilities; see am/tags.am if test -z "$CTAGS"; then CTAGS=ctags fi if test -z "$ETAGS"; then ETAGS=etags fi if test -z "$CSCOPE"; then CSCOPE=cscope fi # POSIX will say in a future version that running "rm -f" with no argument # is OK; and we want to be able to make that assumption in our Makefile # recipes. So use an aggressive probe to check that the usage we want is # actually supported "in the wild" to an acceptable degree. # See automake bug#10828. # To make any issue more visible, cause the running configure to be aborted # by default if the 'rm' program in use doesn't match our expectations; the # user can still override this though. if rm -f && rm -fr && rm -rf; then : OK; else cat >&2 <<'END' Oops! Your 'rm' program seems unable to run without file operands specified on the command line, even when the '-f' option is present. This is contrary to the behaviour of most rm programs out there, and not conforming with the upcoming POSIX standard: Please tell bug-automake@gnu.org about your system, including the value of your $PATH and any error possibly output before this message. This can help us improve future automake versions. END if test x"$ACCEPT_INFERIOR_RM_PROGRAM" = x"yes"; then echo 'Configuration will proceed anyway, since you have set the' >&2 echo 'ACCEPT_INFERIOR_RM_PROGRAM variable to "yes"' >&2 echo >&2 else cat >&2 <<'END' Aborting the configuration process, to ensure you take notice of the issue. You can download and install GNU coreutils to get an 'rm' implementation that behaves properly: . If you want to complete the configuration process using your problematic 'rm' anyway, export the environment variable ACCEPT_INFERIOR_RM_PROGRAM to "yes", and re-run configure. END as_fn_error $? "Your 'rm' program is bad, sorry." "$LINENO" 5 fi fi if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}pkg-config", so it can be a program name with args. set dummy ${ac_tool_prefix}pkg-config; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_PKG_CONFIG+y} then : printf %s "(cached) " >&6 else $as_nop case $PKG_CONFIG in [\\/]* | ?:[\\/]*) ac_cv_path_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_PKG_CONFIG="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac fi PKG_CONFIG=$ac_cv_path_PKG_CONFIG if test -n "$PKG_CONFIG"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $PKG_CONFIG" >&5 printf "%s\n" "$PKG_CONFIG" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test -z "$ac_cv_path_PKG_CONFIG"; then ac_pt_PKG_CONFIG=$PKG_CONFIG # Extract the first word of "pkg-config", so it can be a program name with args. set dummy pkg-config; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_ac_pt_PKG_CONFIG+y} then : printf %s "(cached) " >&6 else $as_nop case $ac_pt_PKG_CONFIG in [\\/]* | ?:[\\/]*) ac_cv_path_ac_pt_PKG_CONFIG="$ac_pt_PKG_CONFIG" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_ac_pt_PKG_CONFIG="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac fi ac_pt_PKG_CONFIG=$ac_cv_path_ac_pt_PKG_CONFIG if test -n "$ac_pt_PKG_CONFIG"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_pt_PKG_CONFIG" >&5 printf "%s\n" "$ac_pt_PKG_CONFIG" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "x$ac_pt_PKG_CONFIG" = x; then PKG_CONFIG="" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac PKG_CONFIG=$ac_pt_PKG_CONFIG fi else PKG_CONFIG="$ac_cv_path_PKG_CONFIG" fi fi if test -n "$PKG_CONFIG"; then _pkg_min_version=0.18 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking pkg-config is at least version $_pkg_min_version" >&5 printf %s "checking pkg-config is at least version $_pkg_min_version... " >&6; } if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } PKG_CONFIG="" fi fi # Check for programs. ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. set dummy ${ac_tool_prefix}gcc; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}gcc" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf "%s\n" "$CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test -z "$ac_cv_prog_CC"; then ac_ct_CC=$CC # Extract the first word of "gcc", so it can be a program name with args. set dummy gcc; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_CC+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="gcc" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 printf "%s\n" "$ac_ct_CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "x$ac_ct_CC" = x; then CC="" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC fi else CC="$ac_cv_prog_CC" fi if test -z "$CC"; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. set dummy ${ac_tool_prefix}cc; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}cc" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf "%s\n" "$CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi fi if test -z "$CC"; then # Extract the first word of "cc", so it can be a program name with args. set dummy cc; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else ac_prog_rejected=no as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then if test "$as_dir$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then ac_prog_rejected=yes continue fi ac_cv_prog_CC="cc" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS if test $ac_prog_rejected = yes; then # We found a bogon in the path, so make sure we never use it. set dummy $ac_cv_prog_CC shift if test $# != 0; then # We chose a different compiler from the bogus one. # However, it has the same basename, so the bogon will be chosen # first if we set CC to just the basename; use the full file name. shift ac_cv_prog_CC="$as_dir$ac_word${1+' '}$@" fi fi fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf "%s\n" "$CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test -z "$CC"; then if test -n "$ac_tool_prefix"; then for ac_prog in cl.exe do # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. set dummy $ac_tool_prefix$ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CC="$ac_tool_prefix$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf "%s\n" "$CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$CC" && break done fi if test -z "$CC"; then ac_ct_CC=$CC for ac_prog in cl.exe do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_CC+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 printf "%s\n" "$ac_ct_CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$ac_ct_CC" && break done if test "x$ac_ct_CC" = x; then CC="" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC fi fi fi if test -z "$CC"; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}clang", so it can be a program name with args. set dummy ${ac_tool_prefix}clang; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}clang" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf "%s\n" "$CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test -z "$ac_cv_prog_CC"; then ac_ct_CC=$CC # Extract the first word of "clang", so it can be a program name with args. set dummy clang; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_CC+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="clang" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 printf "%s\n" "$ac_ct_CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "x$ac_ct_CC" = x; then CC="" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC fi else CC="$ac_cv_prog_CC" fi fi test -z "$CC" && { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "no acceptable C compiler found in \$PATH See \`config.log' for more details" "$LINENO" 5; } # Provide some information about the compiler. printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 set X $ac_compile ac_compiler=$2 for ac_option in --version -v -V -qversion -version; do { { ac_try="$ac_compiler $ac_option >&5" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_compiler $ac_option >&5") 2>conftest.err ac_status=$? if test -s conftest.err; then sed '10a\ ... rest of stderr output deleted ... 10q' conftest.err >conftest.er1 cat conftest.er1 >&5 fi rm -f conftest.er1 conftest.err printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } done cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out" # Try to create an executable without -o first, disregard a.out. # It will help us diagnose broken compilers, and finding out an intuition # of exeext. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5 printf %s "checking whether the C compiler works... " >&6; } ac_link_default=`printf "%s\n" "$ac_link" | sed 's/ -o *conftest[^ ]*//'` # The possible output files: ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*" ac_rmfiles= for ac_file in $ac_files do case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; * ) ac_rmfiles="$ac_rmfiles $ac_file";; esac done rm -f $ac_rmfiles if { { ac_try="$ac_link_default" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_link_default") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } then : # Autoconf-2.13 could set the ac_cv_exeext variable to `no'. # So ignore a value of `no', otherwise this would lead to `EXEEXT = no' # in a Makefile. We should not override ac_cv_exeext if it was cached, # so that the user can short-circuit this test for compilers unknown to # Autoconf. for ac_file in $ac_files '' do test -f "$ac_file" || continue case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; [ab].out ) # We found the default executable, but exeext='' is most # certainly right. break;; *.* ) if test ${ac_cv_exeext+y} && test "$ac_cv_exeext" != no; then :; else ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` fi # We set ac_cv_exeext here because the later test for it is not # safe: cross compilers may not add the suffix if given an `-o' # argument, so we may need to know it at that point already. # Even if this section looks crufty: it has the advantage of # actually working. break;; * ) break;; esac done test "$ac_cv_exeext" = no && ac_cv_exeext= else $as_nop ac_file='' fi if test -z "$ac_file" then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error 77 "C compiler cannot create executables See \`config.log' for more details" "$LINENO" 5; } else $as_nop { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5 printf %s "checking for C compiler default output file name... " >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 printf "%s\n" "$ac_file" >&6; } ac_exeext=$ac_cv_exeext rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out ac_clean_files=$ac_clean_files_save { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 printf %s "checking for suffix of executables... " >&6; } if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } then : # If both `conftest.exe' and `conftest' are `present' (well, observable) # catch `conftest.exe'. For instance with Cygwin, `ls conftest' will # work properly (i.e., refer to `conftest.exe'), while it won't with # `rm'. for ac_file in conftest.exe conftest conftest.*; do test -f "$ac_file" || continue case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` break;; * ) break;; esac done else $as_nop { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of executables: cannot compile and link See \`config.log' for more details" "$LINENO" 5; } fi rm -f conftest conftest$ac_cv_exeext { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 printf "%s\n" "$ac_cv_exeext" >&6; } rm -f conftest.$ac_ext EXEEXT=$ac_cv_exeext ac_exeext=$EXEEXT cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { FILE *f = fopen ("conftest.out", "w"); return ferror (f) || fclose (f) != 0; ; return 0; } _ACEOF ac_clean_files="$ac_clean_files conftest.out" if test "$cross_compiling" = "maybe"; then cross_compiling=yes fi rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out ac_clean_files=$ac_clean_files_save { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 printf %s "checking for suffix of object files... " >&6; } if test ${ac_cv_objext+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF rm -f conftest.o conftest.obj if { { ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_compile") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } then : for ac_file in conftest.o conftest.obj conftest.*; do test -f "$ac_file" || continue; case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;; *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'` break;; esac done else $as_nop printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of object files: cannot compile See \`config.log' for more details" "$LINENO" 5; } fi rm -f conftest.$ac_cv_objext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 printf "%s\n" "$ac_cv_objext" >&6; } OBJEXT=$ac_cv_objext ac_objext=$OBJEXT { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the compiler supports GNU C" >&5 printf %s "checking whether the compiler supports GNU C... " >&6; } if test ${ac_cv_c_compiler_gnu+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { #ifndef __GNUC__ choke me #endif ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_compiler_gnu=yes else $as_nop ac_compiler_gnu=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_cv_c_compiler_gnu=$ac_compiler_gnu fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 printf "%s\n" "$ac_cv_c_compiler_gnu" >&6; } ac_compiler_gnu=$ac_cv_c_compiler_gnu if test $ac_compiler_gnu = yes; then GCC=yes else GCC= fi if test "$GCC" = "yes"; then acx_lean_CFLAGS_set=${CFLAGS+set} ac_cv_prog_cc_g=yes if test "$acx_lean_CFLAGS_set" != "set"; then CFLAGS="$GCC_DEFAULT_CFLAGS" fi else ac_test_CFLAGS=${CFLAGS+y} ac_save_CFLAGS=$CFLAGS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 printf %s "checking whether $CC accepts -g... " >&6; } if test ${ac_cv_prog_cc_g+y} then : printf %s "(cached) " >&6 else $as_nop ac_save_c_werror_flag=$ac_c_werror_flag ac_c_werror_flag=yes ac_cv_prog_cc_g=no CFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_g=yes else $as_nop CFLAGS="" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : else $as_nop ac_c_werror_flag=$ac_save_c_werror_flag CFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_g=yes fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_c_werror_flag=$ac_save_c_werror_flag fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 printf "%s\n" "$ac_cv_prog_cc_g" >&6; } if test $ac_test_CFLAGS; then CFLAGS=$ac_save_CFLAGS elif test $ac_cv_prog_cc_g = yes; then if test "$GCC" = yes; then CFLAGS="-g -O2" else CFLAGS="-g" fi else if test "$GCC" = yes; then CFLAGS="-O2" else CFLAGS= fi fi fi ac_prog_cc_stdc=no if test x$ac_prog_cc_stdc = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C11 features" >&5 printf %s "checking for $CC option to enable C11 features... " >&6; } if test ${ac_cv_prog_cc_c11+y} then : printf %s "(cached) " >&6 else $as_nop ac_cv_prog_cc_c11=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_c_conftest_c11_program _ACEOF for ac_arg in '' -std=gnu11 do CC="$ac_save_CC $ac_arg" if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_c11=$ac_arg fi rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c11" != "xno" && break done rm -f conftest.$ac_ext CC=$ac_save_CC fi if test "x$ac_cv_prog_cc_c11" = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf "%s\n" "unsupported" >&6; } else $as_nop if test "x$ac_cv_prog_cc_c11" = x then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf "%s\n" "none needed" >&6; } else $as_nop { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c11" >&5 printf "%s\n" "$ac_cv_prog_cc_c11" >&6; } CC="$CC $ac_cv_prog_cc_c11" fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c11 ac_prog_cc_stdc=c11 fi fi if test x$ac_prog_cc_stdc = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C99 features" >&5 printf %s "checking for $CC option to enable C99 features... " >&6; } if test ${ac_cv_prog_cc_c99+y} then : printf %s "(cached) " >&6 else $as_nop ac_cv_prog_cc_c99=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_c_conftest_c99_program _ACEOF for ac_arg in '' -std=gnu99 -std=c99 -c99 -qlanglvl=extc1x -qlanglvl=extc99 -AC99 -D_STDC_C99= do CC="$ac_save_CC $ac_arg" if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_c99=$ac_arg fi rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c99" != "xno" && break done rm -f conftest.$ac_ext CC=$ac_save_CC fi if test "x$ac_cv_prog_cc_c99" = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf "%s\n" "unsupported" >&6; } else $as_nop if test "x$ac_cv_prog_cc_c99" = x then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf "%s\n" "none needed" >&6; } else $as_nop { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c99" >&5 printf "%s\n" "$ac_cv_prog_cc_c99" >&6; } CC="$CC $ac_cv_prog_cc_c99" fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c99 ac_prog_cc_stdc=c99 fi fi if test x$ac_prog_cc_stdc = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C89 features" >&5 printf %s "checking for $CC option to enable C89 features... " >&6; } if test ${ac_cv_prog_cc_c89+y} then : printf %s "(cached) " >&6 else $as_nop ac_cv_prog_cc_c89=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_c_conftest_c89_program _ACEOF for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" do CC="$ac_save_CC $ac_arg" if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_c89=$ac_arg fi rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c89" != "xno" && break done rm -f conftest.$ac_ext CC=$ac_save_CC fi if test "x$ac_cv_prog_cc_c89" = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf "%s\n" "unsupported" >&6; } else $as_nop if test "x$ac_cv_prog_cc_c89" = x then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf "%s\n" "none needed" >&6; } else $as_nop { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 printf "%s\n" "$ac_cv_prog_cc_c89" >&6; } CC="$CC $ac_cv_prog_cc_c89" fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c89 ac_prog_cc_stdc=c89 fi fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $CC understands -c and -o together" >&5 printf %s "checking whether $CC understands -c and -o together... " >&6; } if test ${am_cv_prog_cc_c_o+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF # Make sure it works both with $CC and with simple cc. # Following AC_PROG_CC_C_O, we do the test twice because some # compilers refuse to overwrite an existing .o file with -o, # though they will create one. am_cv_prog_cc_c_o=yes for am_i in 1 2; do if { echo "$as_me:$LINENO: $CC -c conftest.$ac_ext -o conftest2.$ac_objext" >&5 ($CC -c conftest.$ac_ext -o conftest2.$ac_objext) >&5 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } \ && test -f conftest2.$ac_objext; then : OK else am_cv_prog_cc_c_o=no break fi done rm -f core conftest* unset am_i fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $am_cv_prog_cc_c_o" >&5 printf "%s\n" "$am_cv_prog_cc_c_o" >&6; } if test "$am_cv_prog_cc_c_o" != yes; then # Losing compiler, so override with the script. # FIXME: It is wrong to rewrite CC. # But if we don't then we get into trouble of one sort or another. # A longer-term fix would be to have automake use am__CC in this case, # and then we could set am__CC="\$(top_srcdir)/compile \$(CC)" CC="$am_aux_dir/compile $CC" fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu DEPDIR="${am__leading_dot}deps" ac_config_commands="$ac_config_commands depfiles" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} supports the include directive" >&5 printf %s "checking whether ${MAKE-make} supports the include directive... " >&6; } cat > confinc.mk << 'END' am__doit: @echo this is the am__doit target >confinc.out .PHONY: am__doit END am__include="#" am__quote= # BSD make does it like this. echo '.include "confinc.mk" # ignored' > confmf.BSD # Other make implementations (GNU, Solaris 10, AIX) do it like this. echo 'include confinc.mk # ignored' > confmf.GNU _am_result=no for s in GNU BSD; do { echo "$as_me:$LINENO: ${MAKE-make} -f confmf.$s && cat confinc.out" >&5 (${MAKE-make} -f confmf.$s && cat confinc.out) >&5 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } case $?:`cat confinc.out 2>/dev/null` in #( '0:this is the am__doit target') : case $s in #( BSD) : am__include='.include' am__quote='"' ;; #( *) : am__include='include' am__quote='' ;; esac ;; #( *) : ;; esac if test "$am__include" != "#"; then _am_result="yes ($s style)" break fi done rm -f confinc.* confmf.* { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: ${_am_result}" >&5 printf "%s\n" "${_am_result}" >&6; } # Check whether --enable-dependency-tracking was given. if test ${enable_dependency_tracking+y} then : enableval=$enable_dependency_tracking; fi if test "x$enable_dependency_tracking" != xno; then am_depcomp="$ac_aux_dir/depcomp" AMDEPBACKSLASH='\' am__nodep='_no' fi if test "x$enable_dependency_tracking" != xno; then AMDEP_TRUE= AMDEP_FALSE='#' else AMDEP_TRUE='#' AMDEP_FALSE= fi depcc="$CC" am_compiler_list= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking dependency style of $depcc" >&5 printf %s "checking dependency style of $depcc... " >&6; } if test ${am_cv_CC_dependencies_compiler_type+y} then : printf %s "(cached) " >&6 else $as_nop if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then # We make a subdir and do the tests there. Otherwise we can end up # making bogus files that we don't know about and never remove. For # instance it was reported that on HP-UX the gcc test will end up # making a dummy file named 'D' -- because '-MD' means "put the output # in D". rm -rf conftest.dir mkdir conftest.dir # Copy depcomp to subdir because otherwise we won't find it if we're # using a relative directory. cp "$am_depcomp" conftest.dir cd conftest.dir # We will build objects and dependencies in a subdirectory because # it helps to detect inapplicable dependency modes. For instance # both Tru64's cc and ICC support -MD to output dependencies as a # side effect of compilation, but ICC will put the dependencies in # the current directory while Tru64 will put them in the object # directory. mkdir sub am_cv_CC_dependencies_compiler_type=none if test "$am_compiler_list" = ""; then am_compiler_list=`sed -n 's/^#*\([a-zA-Z0-9]*\))$/\1/p' < ./depcomp` fi am__universal=false case " $depcc " in #( *\ -arch\ *\ -arch\ *) am__universal=true ;; esac for depmode in $am_compiler_list; do # Setup a source with many dependencies, because some compilers # like to wrap large dependency lists on column 80 (with \), and # we should not choose a depcomp mode which is confused by this. # # We need to recreate these files for each test, as the compiler may # overwrite some of them when testing with obscure command lines. # This happens at least with the AIX C compiler. : > sub/conftest.c for i in 1 2 3 4 5 6; do echo '#include "conftst'$i'.h"' >> sub/conftest.c # Using ": > sub/conftst$i.h" creates only sub/conftst1.h with # Solaris 10 /bin/sh. echo '/* dummy */' > sub/conftst$i.h done echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf # We check with '-c' and '-o' for the sake of the "dashmstdout" # mode. It turns out that the SunPro C++ compiler does not properly # handle '-M -o', and we need to detect this. Also, some Intel # versions had trouble with output in subdirs. am__obj=sub/conftest.${OBJEXT-o} am__minus_obj="-o $am__obj" case $depmode in gcc) # This depmode causes a compiler race in universal mode. test "$am__universal" = false || continue ;; nosideeffect) # After this tag, mechanisms are not by side-effect, so they'll # only be used when explicitly requested. if test "x$enable_dependency_tracking" = xyes; then continue else break fi ;; msvc7 | msvc7msys | msvisualcpp | msvcmsys) # This compiler won't grok '-c -o', but also, the minuso test has # not run yet. These depmodes are late enough in the game, and # so weak that their functioning should not be impacted. am__obj=conftest.${OBJEXT-o} am__minus_obj= ;; none) break ;; esac if depmode=$depmode \ source=sub/conftest.c object=$am__obj \ depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \ >/dev/null 2>conftest.err && grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 && grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && grep $am__obj sub/conftest.Po > /dev/null 2>&1 && ${MAKE-make} -s -f confmf > /dev/null 2>&1; then # icc doesn't choke on unknown options, it will just issue warnings # or remarks (even with -Werror). So we grep stderr for any message # that says an option was ignored or not supported. # When given -MP, icc 7.0 and 7.1 complain thusly: # icc: Command line warning: ignoring option '-M'; no argument required # The diagnosis changed in icc 8.0: # icc: Command line remark: option '-MP' not supported if (grep 'ignoring option' conftest.err || grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else am_cv_CC_dependencies_compiler_type=$depmode break fi fi done cd .. rm -rf conftest.dir else am_cv_CC_dependencies_compiler_type=none fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $am_cv_CC_dependencies_compiler_type" >&5 printf "%s\n" "$am_cv_CC_dependencies_compiler_type" >&6; } CCDEPMODE=depmode=$am_cv_CC_dependencies_compiler_type if test "x$enable_dependency_tracking" != xno \ && test "$am_cv_CC_dependencies_compiler_type" = gcc3; then am__fastdepCC_TRUE= am__fastdepCC_FALSE='#' else am__fastdepCC_TRUE='#' am__fastdepCC_FALSE= fi if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}ranlib", so it can be a program name with args. set dummy ${ac_tool_prefix}ranlib; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_RANLIB+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$RANLIB"; then ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_RANLIB="${ac_tool_prefix}ranlib" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi RANLIB=$ac_cv_prog_RANLIB if test -n "$RANLIB"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $RANLIB" >&5 printf "%s\n" "$RANLIB" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test -z "$ac_cv_prog_RANLIB"; then ac_ct_RANLIB=$RANLIB # Extract the first word of "ranlib", so it can be a program name with args. set dummy ranlib; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_RANLIB+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$ac_ct_RANLIB"; then ac_cv_prog_ac_ct_RANLIB="$ac_ct_RANLIB" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_RANLIB="ranlib" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_RANLIB=$ac_cv_prog_ac_ct_RANLIB if test -n "$ac_ct_RANLIB"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_RANLIB" >&5 printf "%s\n" "$ac_ct_RANLIB" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "x$ac_ct_RANLIB" = x; then RANLIB=":" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac RANLIB=$ac_ct_RANLIB fi else RANLIB="$ac_cv_prog_RANLIB" fi # Check whether --enable-largefile was given. if test ${enable_largefile+y} then : enableval=$enable_largefile; fi if test "$enable_largefile" != no; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for special C compiler options needed for large files" >&5 printf %s "checking for special C compiler options needed for large files... " >&6; } if test ${ac_cv_sys_largefile_CC+y} then : printf %s "(cached) " >&6 else $as_nop ac_cv_sys_largefile_CC=no if test "$GCC" != yes; then ac_save_CC=$CC while :; do # IRIX 6.2 and later do not support large files by default, # so use the C compiler's -n32 option if that helps. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include /* Check that off_t can represent 2**63 - 1 correctly. We can't simply define LARGE_OFF_T to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ #define LARGE_OFF_T (((off_t) 1 << 31 << 31) - 1 + ((off_t) 1 << 31 << 31)) int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1]; int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : break fi rm -f core conftest.err conftest.$ac_objext conftest.beam CC="$CC -n32" if ac_fn_c_try_compile "$LINENO" then : ac_cv_sys_largefile_CC=' -n32'; break fi rm -f core conftest.err conftest.$ac_objext conftest.beam break done CC=$ac_save_CC rm -f conftest.$ac_ext fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sys_largefile_CC" >&5 printf "%s\n" "$ac_cv_sys_largefile_CC" >&6; } if test "$ac_cv_sys_largefile_CC" != no; then CC=$CC$ac_cv_sys_largefile_CC fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for _FILE_OFFSET_BITS value needed for large files" >&5 printf %s "checking for _FILE_OFFSET_BITS value needed for large files... " >&6; } if test ${ac_cv_sys_file_offset_bits+y} then : printf %s "(cached) " >&6 else $as_nop while :; do cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include /* Check that off_t can represent 2**63 - 1 correctly. We can't simply define LARGE_OFF_T to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ #define LARGE_OFF_T (((off_t) 1 << 31 << 31) - 1 + ((off_t) 1 << 31 << 31)) int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1]; int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_sys_file_offset_bits=no; break fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #define _FILE_OFFSET_BITS 64 #include /* Check that off_t can represent 2**63 - 1 correctly. We can't simply define LARGE_OFF_T to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ #define LARGE_OFF_T (((off_t) 1 << 31 << 31) - 1 + ((off_t) 1 << 31 << 31)) int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1]; int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_sys_file_offset_bits=64; break fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_cv_sys_file_offset_bits=unknown break done fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sys_file_offset_bits" >&5 printf "%s\n" "$ac_cv_sys_file_offset_bits" >&6; } case $ac_cv_sys_file_offset_bits in #( no | unknown) ;; *) printf "%s\n" "#define _FILE_OFFSET_BITS $ac_cv_sys_file_offset_bits" >>confdefs.h ;; esac rm -rf conftest* if test $ac_cv_sys_file_offset_bits = unknown; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for _LARGE_FILES value needed for large files" >&5 printf %s "checking for _LARGE_FILES value needed for large files... " >&6; } if test ${ac_cv_sys_large_files+y} then : printf %s "(cached) " >&6 else $as_nop while :; do cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include /* Check that off_t can represent 2**63 - 1 correctly. We can't simply define LARGE_OFF_T to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ #define LARGE_OFF_T (((off_t) 1 << 31 << 31) - 1 + ((off_t) 1 << 31 << 31)) int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1]; int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_sys_large_files=no; break fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #define _LARGE_FILES 1 #include /* Check that off_t can represent 2**63 - 1 correctly. We can't simply define LARGE_OFF_T to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ #define LARGE_OFF_T (((off_t) 1 << 31 << 31) - 1 + ((off_t) 1 << 31 << 31)) int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1]; int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_sys_large_files=1; break fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_cv_sys_large_files=unknown break done fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sys_large_files" >&5 printf "%s\n" "$ac_cv_sys_large_files" >&6; } case $ac_cv_sys_large_files in #( no | unknown) ;; *) printf "%s\n" "#define _LARGE_FILES $ac_cv_sys_large_files" >>confdefs.h ;; esac rm -rf conftest* fi fi # Use silent building # Check whether --enable-silent-rules was given. if test ${enable_silent_rules+y} then : enableval=$enable_silent_rules; fi case $enable_silent_rules in # ((( yes) AM_DEFAULT_VERBOSITY=0;; no) AM_DEFAULT_VERBOSITY=1;; *) AM_DEFAULT_VERBOSITY=0;; esac am_make=${MAKE-make} { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $am_make supports nested variables" >&5 printf %s "checking whether $am_make supports nested variables... " >&6; } if test ${am_cv_make_support_nested_variables+y} then : printf %s "(cached) " >&6 else $as_nop if printf "%s\n" 'TRUE=$(BAR$(V)) BAR0=false BAR1=true V=1 am__doit: @$(TRUE) .PHONY: am__doit' | $am_make -f - >/dev/null 2>&1; then am_cv_make_support_nested_variables=yes else am_cv_make_support_nested_variables=no fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $am_cv_make_support_nested_variables" >&5 printf "%s\n" "$am_cv_make_support_nested_variables" >&6; } if test $am_cv_make_support_nested_variables = yes; then AM_V='$(V)' AM_DEFAULT_V='$(AM_DEFAULT_VERBOSITY)' else AM_V=$AM_DEFAULT_VERBOSITY AM_DEFAULT_V=$AM_DEFAULT_VERBOSITY fi AM_BACKSLASH='\' # Check for pod2man # Extract the first word of "pod2man", so it can be a program name with args. set dummy pod2man; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_have_pod2man+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$have_pod2man"; then ac_cv_prog_have_pod2man="$have_pod2man" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_have_pod2man="yes" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_prog_have_pod2man" && ac_cv_prog_have_pod2man="no" fi fi have_pod2man=$ac_cv_prog_have_pod2man if test -n "$have_pod2man"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $have_pod2man" >&5 printf "%s\n" "$have_pod2man" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "x$have_pod2man" = "xyes"; then USE_POD2MAN_TRUE= USE_POD2MAN_FALSE='#' else USE_POD2MAN_TRUE='#' USE_POD2MAN_FALSE= fi # Check for makeheaders. If the system does not provide it, compile our own copy in deps/ # Extract the first word of "makeheaders", so it can be a program name with args. set dummy makeheaders; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_have_mh+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$have_mh"; then ac_cv_prog_have_mh="$have_mh" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_have_mh="yes" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_prog_have_mh" && ac_cv_prog_have_mh="no" fi fi have_mh=$ac_cv_prog_have_mh if test -n "$have_mh"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $have_mh" >&5 printf "%s\n" "$have_mh" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "x$have_mh" = "xyes"; then HAVE_MH_TRUE= HAVE_MH_FALSE='#' else HAVE_MH_TRUE='#' HAVE_MH_FALSE= fi # Check for header files. for ac_header in zlib.h bzlib.h do : as_ac_Header=`printf "%s\n" "ac_cv_header_$ac_header" | $as_tr_sh` ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" " " if eval test \"x\$"$as_ac_Header"\" = x"yes" then : cat >>confdefs.h <<_ACEOF #define `printf "%s\n" "HAVE_$ac_header" | $as_tr_cpp` 1 _ACEOF else $as_nop as_fn_error $? "Required header file not found" "$LINENO" 5 fi done # Check for posix_fadvise() ac_fn_c_check_func "$LINENO" "posix_fadvise" "ac_cv_func_posix_fadvise" if test "x$ac_cv_func_posix_fadvise" = xyes then : printf "%s\n" "#define HAVE_POSIX_FADVISE 1" >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for library containing inet_pton" >&5 printf %s "checking for library containing inet_pton... " >&6; } if test ${ac_cv_search_inet_pton+y} then : printf %s "(cached) " >&6 else $as_nop ac_func_search_save_LIBS=$LIBS cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ char inet_pton (); int main (void) { return inet_pton (); ; return 0; } _ACEOF for ac_lib in '' nsl do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi if ac_fn_c_try_link "$LINENO" then : ac_cv_search_inet_pton=$ac_res fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext if test ${ac_cv_search_inet_pton+y} then : break fi done if test ${ac_cv_search_inet_pton+y} then : else $as_nop ac_cv_search_inet_pton=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_inet_pton" >&5 printf "%s\n" "$ac_cv_search_inet_pton" >&6; } ac_res=$ac_cv_search_inet_pton if test "$ac_res" != no then : test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for library containing socket" >&5 printf %s "checking for library containing socket... " >&6; } if test ${ac_cv_search_socket+y} then : printf %s "(cached) " >&6 else $as_nop ac_func_search_save_LIBS=$LIBS cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ char socket (); int main (void) { return socket (); ; return 0; } _ACEOF for ac_lib in '' socket do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi if ac_fn_c_try_link "$LINENO" then : ac_cv_search_socket=$ac_res fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext if test ${ac_cv_search_socket+y} then : break fi done if test ${ac_cv_search_socket+y} then : else $as_nop ac_cv_search_socket=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_socket" >&5 printf "%s\n" "$ac_cv_search_socket" >&6; } ac_res=$ac_cv_search_socket if test "$ac_res" != no then : test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" else $as_nop { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for socket in -lsocket" >&5 printf %s "checking for socket in -lsocket... " >&6; } if test ${ac_cv_lib_socket_socket+y} then : printf %s "(cached) " >&6 else $as_nop ac_check_lib_save_LIBS=$LIBS LIBS="-lsocket -lnsl $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ char socket (); int main (void) { return socket (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_socket_socket=yes else $as_nop ac_cv_lib_socket_socket=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_socket_socket" >&5 printf "%s\n" "$ac_cv_lib_socket_socket" >&6; } if test "x$ac_cv_lib_socket_socket" = xyes then : LIBS="-lsocket -lnsl $LIBS" fi fi # Check for sendfile() support (not required) # The following checks are based on ProFTPD's configure.in, except ncdc only # supports the Linux and BSD variant at the moment, as those are the only two I # have tested so far. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking which sendfile() implementation to use" >&5 printf %s "checking which sendfile() implementation to use... " >&6; } if test ${pr_cv_sendfile_func+y} then : printf %s "(cached) " >&6 else $as_nop pr_cv_sendfile_func="none" # Linux if test "$pr_cv_sendfile_func" = "none"; then cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #include int main (void) { int i=0; off_t o=0; size_t c=0; (void)sendfile(i,i,&o,c); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : pr_cv_sendfile_func="Linux" fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi # BSD if test "$pr_cv_sendfile_func" = "none"; then cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #include int main (void) { int i=0; off_t o=0; size_t n=0; struct sf_hdtr h={}; (void)sendfile(i,i,o,n,&h,&o,i); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : pr_cv_sendfile_func="BSD" fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $pr_cv_sendfile_func" >&5 printf "%s\n" "$pr_cv_sendfile_func" >&6; } # set defines if test "$pr_cv_sendfile_func" != none; then printf "%s\n" "#define HAVE_SENDFILE 1" >>confdefs.h fi case "$pr_cv_sendfile_func" in "Linux") printf "%s\n" "#define HAVE_LINUX_SENDFILE 1" >>confdefs.h ;; "BSD") printf "%s\n" "#define HAVE_BSD_SENDFILE 1" >>confdefs.h ;; esac # Check for ncurses pkg_failed=no { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for NCURSES" >&5 printf %s "checking for NCURSES... " >&6; } if test -n "$NCURSES_CFLAGS"; then pkg_cv_NCURSES_CFLAGS="$NCURSES_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"ncursesw\""; } >&5 ($PKG_CONFIG --exists --print-errors "ncursesw") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_NCURSES_CFLAGS=`$PKG_CONFIG --cflags "ncursesw" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$NCURSES_LIBS"; then pkg_cv_NCURSES_LIBS="$NCURSES_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"ncursesw\""; } >&5 ($PKG_CONFIG --exists --print-errors "ncursesw") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_NCURSES_LIBS=`$PKG_CONFIG --libs "ncursesw" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then NCURSES_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "ncursesw" 2>&1` else NCURSES_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "ncursesw" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$NCURSES_PKG_ERRORS" >&5 pkg_failed=no { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for NCURSES" >&5 printf %s "checking for NCURSES... " >&6; } if test -n "$NCURSES_CFLAGS"; then pkg_cv_NCURSES_CFLAGS="$NCURSES_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"ncurses\""; } >&5 ($PKG_CONFIG --exists --print-errors "ncurses") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_NCURSES_CFLAGS=`$PKG_CONFIG --cflags "ncurses" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$NCURSES_LIBS"; then pkg_cv_NCURSES_LIBS="$NCURSES_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"ncurses\""; } >&5 ($PKG_CONFIG --exists --print-errors "ncurses") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_NCURSES_LIBS=`$PKG_CONFIG --libs "ncurses" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then NCURSES_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "ncurses" 2>&1` else NCURSES_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "ncurses" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$NCURSES_PKG_ERRORS" >&5 as_fn_error $? "ncurses library is required" "$LINENO" 5 elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } as_fn_error $? "ncurses library is required" "$LINENO" 5 else NCURSES_CFLAGS=$pkg_cv_NCURSES_CFLAGS NCURSES_LIBS=$pkg_cv_NCURSES_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } fi elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } pkg_failed=no { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for NCURSES" >&5 printf %s "checking for NCURSES... " >&6; } if test -n "$NCURSES_CFLAGS"; then pkg_cv_NCURSES_CFLAGS="$NCURSES_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"ncurses\""; } >&5 ($PKG_CONFIG --exists --print-errors "ncurses") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_NCURSES_CFLAGS=`$PKG_CONFIG --cflags "ncurses" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$NCURSES_LIBS"; then pkg_cv_NCURSES_LIBS="$NCURSES_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"ncurses\""; } >&5 ($PKG_CONFIG --exists --print-errors "ncurses") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_NCURSES_LIBS=`$PKG_CONFIG --libs "ncurses" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then NCURSES_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "ncurses" 2>&1` else NCURSES_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "ncurses" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$NCURSES_PKG_ERRORS" >&5 as_fn_error $? "ncurses library is required" "$LINENO" 5 elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } as_fn_error $? "ncurses library is required" "$LINENO" 5 else NCURSES_CFLAGS=$pkg_cv_NCURSES_CFLAGS NCURSES_LIBS=$pkg_cv_NCURSES_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } fi else NCURSES_CFLAGS=$pkg_cv_NCURSES_CFLAGS NCURSES_LIBS=$pkg_cv_NCURSES_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } fi # Check for zlib { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for deflate in -lz" >&5 printf %s "checking for deflate in -lz... " >&6; } if test ${ac_cv_lib_z_deflate+y} then : printf %s "(cached) " >&6 else $as_nop ac_check_lib_save_LIBS=$LIBS LIBS="-lz $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ char deflate (); int main (void) { return deflate (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_z_deflate=yes else $as_nop ac_cv_lib_z_deflate=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_z_deflate" >&5 printf "%s\n" "$ac_cv_lib_z_deflate" >&6; } if test "x$ac_cv_lib_z_deflate" = xyes then : Z_LIBS=-lz else $as_nop as_fn_error $? "zlib library is required" "$LINENO" 5 fi # Check for libbz2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for BZ2_bzReadOpen in -lbz2" >&5 printf %s "checking for BZ2_bzReadOpen in -lbz2... " >&6; } if test ${ac_cv_lib_bz2_BZ2_bzReadOpen+y} then : printf %s "(cached) " >&6 else $as_nop ac_check_lib_save_LIBS=$LIBS LIBS="-lbz2 $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ char BZ2_bzReadOpen (); int main (void) { return BZ2_bzReadOpen (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_bz2_BZ2_bzReadOpen=yes else $as_nop ac_cv_lib_bz2_BZ2_bzReadOpen=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_bz2_BZ2_bzReadOpen" >&5 printf "%s\n" "$ac_cv_lib_bz2_BZ2_bzReadOpen" >&6; } if test "x$ac_cv_lib_bz2_BZ2_bzReadOpen" = xyes then : BZ2_LIBS=-lbz2 else $as_nop as_fn_error $? "bzip2 library is required" "$LINENO" 5 fi # Check for SQLite3 if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"sqlite3\""; } >&5 ($PKG_CONFIG --exists --print-errors "sqlite3") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_failed=no { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for SQLITE" >&5 printf %s "checking for SQLITE... " >&6; } if test -n "$SQLITE_CFLAGS"; then pkg_cv_SQLITE_CFLAGS="$SQLITE_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"sqlite3\""; } >&5 ($PKG_CONFIG --exists --print-errors "sqlite3") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_SQLITE_CFLAGS=`$PKG_CONFIG --cflags "sqlite3" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$SQLITE_LIBS"; then pkg_cv_SQLITE_LIBS="$SQLITE_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"sqlite3\""; } >&5 ($PKG_CONFIG --exists --print-errors "sqlite3") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_SQLITE_LIBS=`$PKG_CONFIG --libs "sqlite3" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then SQLITE_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "sqlite3" 2>&1` else SQLITE_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "sqlite3" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$SQLITE_PKG_ERRORS" >&5 as_fn_error $? "Package requirements (sqlite3) were not met: $SQLITE_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables SQLITE_CFLAGS and SQLITE_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details." "$LINENO" 5 elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. Alternatively, you may set the environment variables SQLITE_CFLAGS and SQLITE_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . See \`config.log' for more details" "$LINENO" 5; } else SQLITE_CFLAGS=$pkg_cv_SQLITE_CFLAGS SQLITE_LIBS=$pkg_cv_SQLITE_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } fi else for ac_header in sqlite3.h do : ac_fn_c_check_header_compile "$LINENO" "sqlite3.h" "ac_cv_header_sqlite3_h" " " if test "x$ac_cv_header_sqlite3_h" = xyes then : printf "%s\n" "#define HAVE_SQLITE3_H 1" >>confdefs.h else $as_nop as_fn_error $? "sqlite3 header file not found" "$LINENO" 5 fi done { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for sqlite3_open in -lsqlite3" >&5 printf %s "checking for sqlite3_open in -lsqlite3... " >&6; } if test ${ac_cv_lib_sqlite3_sqlite3_open+y} then : printf %s "(cached) " >&6 else $as_nop ac_check_lib_save_LIBS=$LIBS LIBS="-lsqlite3 $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ char sqlite3_open (); int main (void) { return sqlite3_open (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_sqlite3_sqlite3_open=yes else $as_nop ac_cv_lib_sqlite3_sqlite3_open=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_sqlite3_sqlite3_open" >&5 printf "%s\n" "$ac_cv_lib_sqlite3_sqlite3_open" >&6; } if test "x$ac_cv_lib_sqlite3_sqlite3_open" = xyes then : SQLITE_LIBS=-lsqlite3 else $as_nop as_fn_error $? "sqlite3 library is required" "$LINENO" 5 fi fi # Check for modules pkg_failed=no { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for GLIB" >&5 printf %s "checking for GLIB... " >&6; } if test -n "$GLIB_CFLAGS"; then pkg_cv_GLIB_CFLAGS="$GLIB_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"glib-2.0 >= 2.32 gthread-2.0\""; } >&5 ($PKG_CONFIG --exists --print-errors "glib-2.0 >= 2.32 gthread-2.0") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_GLIB_CFLAGS=`$PKG_CONFIG --cflags "glib-2.0 >= 2.32 gthread-2.0" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$GLIB_LIBS"; then pkg_cv_GLIB_LIBS="$GLIB_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"glib-2.0 >= 2.32 gthread-2.0\""; } >&5 ($PKG_CONFIG --exists --print-errors "glib-2.0 >= 2.32 gthread-2.0") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_GLIB_LIBS=`$PKG_CONFIG --libs "glib-2.0 >= 2.32 gthread-2.0" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then GLIB_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "glib-2.0 >= 2.32 gthread-2.0" 2>&1` else GLIB_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "glib-2.0 >= 2.32 gthread-2.0" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$GLIB_PKG_ERRORS" >&5 as_fn_error $? "Package requirements (glib-2.0 >= 2.32 gthread-2.0) were not met: $GLIB_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables GLIB_CFLAGS and GLIB_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details." "$LINENO" 5 elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. Alternatively, you may set the environment variables GLIB_CFLAGS and GLIB_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . See \`config.log' for more details" "$LINENO" 5; } else GLIB_CFLAGS=$pkg_cv_GLIB_CFLAGS GLIB_LIBS=$pkg_cv_GLIB_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } fi pkg_failed=no { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for GNUTLS" >&5 printf %s "checking for GNUTLS... " >&6; } if test -n "$GNUTLS_CFLAGS"; then pkg_cv_GNUTLS_CFLAGS="$GNUTLS_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"gnutls >= 3.0\""; } >&5 ($PKG_CONFIG --exists --print-errors "gnutls >= 3.0") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_GNUTLS_CFLAGS=`$PKG_CONFIG --cflags "gnutls >= 3.0" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$GNUTLS_LIBS"; then pkg_cv_GNUTLS_LIBS="$GNUTLS_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"gnutls >= 3.0\""; } >&5 ($PKG_CONFIG --exists --print-errors "gnutls >= 3.0") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_GNUTLS_LIBS=`$PKG_CONFIG --libs "gnutls >= 3.0" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then GNUTLS_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "gnutls >= 3.0" 2>&1` else GNUTLS_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "gnutls >= 3.0" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$GNUTLS_PKG_ERRORS" >&5 as_fn_error $? "Package requirements (gnutls >= 3.0) were not met: $GNUTLS_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables GNUTLS_CFLAGS and GNUTLS_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details." "$LINENO" 5 elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. Alternatively, you may set the environment variables GNUTLS_CFLAGS and GNUTLS_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . See \`config.log' for more details" "$LINENO" 5; } else GNUTLS_CFLAGS=$pkg_cv_GNUTLS_CFLAGS GNUTLS_LIBS=$pkg_cv_GNUTLS_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } fi # Check whether --with-geoip was given. if test ${with_geoip+y} then : withval=$with_geoip; else $as_nop with_geoip=no fi if test "x$with_geoip" = xyes then : pkg_failed=no { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for GEOIP" >&5 printf %s "checking for GEOIP... " >&6; } if test -n "$GEOIP_CFLAGS"; then pkg_cv_GEOIP_CFLAGS="$GEOIP_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libmaxminddb >= 1.0\""; } >&5 ($PKG_CONFIG --exists --print-errors "libmaxminddb >= 1.0") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_GEOIP_CFLAGS=`$PKG_CONFIG --cflags "libmaxminddb >= 1.0" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$GEOIP_LIBS"; then pkg_cv_GEOIP_LIBS="$GEOIP_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libmaxminddb >= 1.0\""; } >&5 ($PKG_CONFIG --exists --print-errors "libmaxminddb >= 1.0") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_GEOIP_LIBS=`$PKG_CONFIG --libs "libmaxminddb >= 1.0" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then GEOIP_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libmaxminddb >= 1.0" 2>&1` else GEOIP_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libmaxminddb >= 1.0" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$GEOIP_PKG_ERRORS" >&5 as_fn_error $? "Package requirements (libmaxminddb >= 1.0) were not met: $GEOIP_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables GEOIP_CFLAGS and GEOIP_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details." "$LINENO" 5 elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. Alternatively, you may set the environment variables GEOIP_CFLAGS and GEOIP_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . See \`config.log' for more details" "$LINENO" 5; } else GEOIP_CFLAGS=$pkg_cv_GEOIP_CFLAGS GEOIP_LIBS=$pkg_cv_GEOIP_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } printf "%s\n" "#define USE_GEOIP 1" >>confdefs.h fi fi # Check whether we should use the version string from AC_INIT, or use # git-describe to create one. This trick is copied from the pacman source. # Check whether --enable-git-version was given. if test ${enable_git_version+y} then : enableval=$enable_git_version; wantgitver=$enableval else $as_nop wantgitver=yes fi usegitver=no if test "x$wantgitver" = "xyes" ; then for ac_prog in git do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_GIT+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$GIT"; then ac_cv_prog_GIT="$GIT" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_GIT="$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi GIT=$ac_cv_prog_GIT if test -n "$GIT"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $GIT" >&5 printf "%s\n" "$GIT" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$GIT" && break done test -n "$GIT" || GIT="no" test "x$GIT" != "xno" -a -d "$srcdir/.git" && usegitver=yes fi if test "x$usegitver" = "xyes"; then USE_GIT_VERSION_TRUE= USE_GIT_VERSION_FALSE='#' else USE_GIT_VERSION_TRUE='#' USE_GIT_VERSION_FALSE= fi # If we don't have pod2man and doc/ncdc.1 isn't available in the source # directory, throw a warning and just go ahead without installing the man page. installmanpage=yes if test "x$have_pod2man" = "xno" -a \! -s "$srcdir/doc/ncdc.1"; then echo "" echo "Note: Could not find doc/ncdc.1 in the source directory nor the pod2man" echo "utility on your system. No manual page will be installed." echo "" installmanpage=no fi if test "x$installmanpage" = "xyes"; then INSTALL_MANPAGE_TRUE= INSTALL_MANPAGE_FALSE='#' else INSTALL_MANPAGE_TRUE='#' INSTALL_MANPAGE_FALSE= fi ac_config_files="$ac_config_files Makefile" cat >confcache <<\_ACEOF # This file is a shell script that caches the results of configure # tests run on this system so they can be shared between configure # scripts and configure runs, see configure's option --config-cache. # It is not useful on other systems. If it contains results you don't # want to keep, you may remove or edit it. # # config.status only pays attention to the cache file if you give it # the --recheck option to rerun configure. # # `ac_cv_env_foo' variables (set or unset) will be overridden when # loading this file, other *unset* `ac_cv_foo' will be assigned the # following values. _ACEOF # The following way of writing the cache mishandles newlines in values, # but we know of no workaround that is simple, portable, and efficient. # So, we kill variables containing newlines. # Ultrix sh set writes to stderr and can't be redirected directly, # and sets the high bit in the cache file unless we assign to the vars. ( for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do eval ac_val=\$$ac_var case $ac_val in #( *${as_nl}*) case $ac_var in #( *_cv_*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( *) { eval $ac_var=; unset $ac_var;} ;; esac ;; esac done (set) 2>&1 | case $as_nl`(ac_space=' '; set) 2>&1` in #( *${as_nl}ac_space=\ *) # `set' does not quote correctly, so add quotes: double-quote # substitution turns \\\\ into \\, and sed turns \\ into \. sed -n \ "s/'/'\\\\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" ;; #( *) # `set' quotes correctly as required by POSIX, so do not add quotes. sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | sort ) | sed ' /^ac_cv_env_/b end t clear :clear s/^\([^=]*\)=\(.*[{}].*\)$/test ${\1+y} || &/ t end s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ :end' >>confcache if diff "$cache_file" confcache >/dev/null 2>&1; then :; else if test -w "$cache_file"; then if test "x$cache_file" != "x/dev/null"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 printf "%s\n" "$as_me: updating cache $cache_file" >&6;} if test ! -f "$cache_file" || test -h "$cache_file"; then cat confcache >"$cache_file" else case $cache_file in #( */* | ?:*) mv -f confcache "$cache_file"$$ && mv -f "$cache_file"$$ "$cache_file" ;; #( *) mv -f confcache "$cache_file" ;; esac fi fi else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 printf "%s\n" "$as_me: not updating unwritable cache $cache_file" >&6;} fi fi rm -f confcache test "x$prefix" = xNONE && prefix=$ac_default_prefix # Let make expand exec_prefix. test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' DEFS=-DHAVE_CONFIG_H ac_libobjs= ac_ltlibobjs= U= for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue # 1. Remove the extension, and $U if already installed. ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' ac_i=`printf "%s\n" "$ac_i" | sed "$ac_script"` # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR # will be set to the directory where LIBOBJS objects are built. as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo' done LIBOBJS=$ac_libobjs LTLIBOBJS=$ac_ltlibobjs if test -n "$EXEEXT"; then am__EXEEXT_TRUE= am__EXEEXT_FALSE='#' else am__EXEEXT_TRUE='#' am__EXEEXT_FALSE= fi if test -z "${AMDEP_TRUE}" && test -z "${AMDEP_FALSE}"; then as_fn_error $? "conditional \"AMDEP\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${am__fastdepCC_TRUE}" && test -z "${am__fastdepCC_FALSE}"; then as_fn_error $? "conditional \"am__fastdepCC\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${USE_POD2MAN_TRUE}" && test -z "${USE_POD2MAN_FALSE}"; then as_fn_error $? "conditional \"USE_POD2MAN\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${HAVE_MH_TRUE}" && test -z "${HAVE_MH_FALSE}"; then as_fn_error $? "conditional \"HAVE_MH\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${USE_GIT_VERSION_TRUE}" && test -z "${USE_GIT_VERSION_FALSE}"; then as_fn_error $? "conditional \"USE_GIT_VERSION\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${INSTALL_MANPAGE_TRUE}" && test -z "${INSTALL_MANPAGE_FALSE}"; then as_fn_error $? "conditional \"INSTALL_MANPAGE\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi : "${CONFIG_STATUS=./config.status}" ac_write_fail=0 ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files $CONFIG_STATUS" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 printf "%s\n" "$as_me: creating $CONFIG_STATUS" >&6;} as_write_fail=0 cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 #! $SHELL # Generated by $as_me. # Run this file to recreate the current configuration. # Compiler output produced by configure, useful for debugging # configure, is in config.log if it exists. debug=false ac_cs_recheck=false ac_cs_silent=false SHELL=\${CONFIG_SHELL-$SHELL} export SHELL _ASEOF cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 ## -------------------- ## ## M4sh Initialization. ## ## -------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh as_nop=: if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else $as_nop case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; esac fi # Reset variables that may have inherited troublesome values from # the environment. # IFS needs to be set, to space, tab, and newline, in precisely that order. # (If _AS_PATH_WALK were called with IFS unset, it would have the # side effect of setting IFS to empty, thus disabling word splitting.) # Quoting is to prevent editors from complaining about space-tab. as_nl=' ' export as_nl IFS=" "" $as_nl" PS1='$ ' PS2='> ' PS4='+ ' # Ensure predictable behavior from utilities with locale-dependent output. LC_ALL=C export LC_ALL LANGUAGE=C export LANGUAGE # We cannot yet rely on "unset" to work, but we need these variables # to be unset--not just set to an empty or harmless value--now, to # avoid bugs in old shells (e.g. pre-3.0 UWIN ksh). This construct # also avoids known problems related to "unset" and subshell syntax # in other old shells (e.g. bash 2.01 and pdksh 5.2.14). for as_var in BASH_ENV ENV MAIL MAILPATH CDPATH do eval test \${$as_var+y} \ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : done # Ensure that fds 0, 1, and 2 are open. if (exec 3>&0) 2>/dev/null; then :; else exec 0&1) 2>/dev/null; then :; else exec 1>/dev/null; fi if (exec 3>&2) ; then :; else exec 2>/dev/null; fi # The user is always right. if ${PATH_SEPARATOR+false} :; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || PATH_SEPARATOR=';' } fi # Find who we are. Look in the path if we contain no directory separator. as_myself= case $0 in #(( *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac test -r "$as_dir$0" && as_myself=$as_dir$0 && break done IFS=$as_save_IFS ;; esac # We did not find ourselves, most probably we were run as `sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then printf "%s\n" "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi # as_fn_error STATUS ERROR [LINENO LOG_FD] # ---------------------------------------- # Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are # provided, also output the error to LOG_FD, referencing LINENO. Then exit the # script with STATUS, using 1 if that was 0. as_fn_error () { as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi printf "%s\n" "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. as_fn_set_status () { return $1 } # as_fn_set_status # as_fn_exit STATUS # ----------------- # Exit the shell with STATUS, even in a "trap 0" or "set -e" context. as_fn_exit () { set +e as_fn_set_status $1 exit $1 } # as_fn_exit # as_fn_unset VAR # --------------- # Portably unset VAR. as_fn_unset () { { eval $1=; unset $1;} } as_unset=as_fn_unset # as_fn_append VAR VALUE # ---------------------- # Append the text in VALUE to the end of the definition contained in VAR. Take # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null then : eval 'as_fn_append () { eval $1+=\$2 }' else $as_nop as_fn_append () { eval $1=\$$1\$2 } fi # as_fn_append # as_fn_arith ARG... # ------------------ # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null then : eval 'as_fn_arith () { as_val=$(( $* )) }' else $as_nop as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` } fi # as_fn_arith if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then as_dirname=dirname else as_dirname=false fi as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits # Determine whether it's possible to make 'echo' print without a newline. # These variables are no longer used directly by Autoconf, but are AC_SUBSTed # for compatibility with existing Makefiles. ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) case `echo 'xy\c'` in *c*) ECHO_T=' ';; # ECHO_T is single tab character. xy) ECHO_C='\c';; *) echo `echo ksh88 bug on AIX 6.1` > /dev/null ECHO_T=' ';; esac;; *) ECHO_N='-n';; esac # For backward compatibility with old third-party macros, we provide # the shell variables $as_echo and $as_echo_n. New code should use # AS_ECHO(["message"]) and AS_ECHO_N(["message"]), respectively. as_echo='printf %s\n' as_echo_n='printf %s' rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir mkdir conf$$.dir 2>/dev/null fi if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. # In both cases, we have to default to `cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -pR' fi else as_ln_s='cp -pR' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null # as_fn_mkdir_p # ------------- # Create "$as_dir" as a directory, including parents if necessary. as_fn_mkdir_p () { case $as_dir in #( -*) as_dir=./$as_dir;; esac test -d "$as_dir" || eval $as_mkdir_p || { as_dirs= while :; do case $as_dir in #( *\'*) as_qdir=`printf "%s\n" "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" as_dir=`$as_dirname -- "$as_dir" || $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` test -d "$as_dir" && break done test -z "$as_dirs" || eval "mkdir $as_dirs" } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" } # as_fn_mkdir_p if mkdir -p . 2>/dev/null; then as_mkdir_p='mkdir -p "$as_dir"' else test -d ./-p && rmdir ./-p as_mkdir_p=false fi # as_fn_executable_p FILE # ----------------------- # Test if FILE is an executable regular file. as_fn_executable_p () { test -f "$1" && test -x "$1" } # as_fn_executable_p as_test_x='test -x' as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" # Sed expression to map a string onto a valid variable name. as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" exec 6>&1 ## ----------------------------------- ## ## Main body of $CONFIG_STATUS script. ## ## ----------------------------------- ## _ASEOF test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1 cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # Save the log message, to keep $0 and so on meaningful, and to # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" This file was extended by ncdc $as_me 1.23.1, which was generated by GNU Autoconf 2.71. Invocation command line was CONFIG_FILES = $CONFIG_FILES CONFIG_HEADERS = $CONFIG_HEADERS CONFIG_LINKS = $CONFIG_LINKS CONFIG_COMMANDS = $CONFIG_COMMANDS $ $0 $@ on `(hostname || uname -n) 2>/dev/null | sed 1q` " _ACEOF case $ac_config_files in *" "*) set x $ac_config_files; shift; ac_config_files=$*;; esac case $ac_config_headers in *" "*) set x $ac_config_headers; shift; ac_config_headers=$*;; esac cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 # Files that config.status was made for. config_files="$ac_config_files" config_headers="$ac_config_headers" config_commands="$ac_config_commands" _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 ac_cs_usage="\ \`$as_me' instantiates files and other configuration actions from templates according to the current configuration. Unless the files and actions are specified as TAGs, all are instantiated by default. Usage: $0 [OPTION]... [TAG]... -h, --help print this help, then exit -V, --version print version number and configuration settings, then exit --config print configuration, then exit -q, --quiet, --silent do not print progress messages -d, --debug don't remove temporary files --recheck update $as_me by reconfiguring in the same conditions --file=FILE[:TEMPLATE] instantiate the configuration file FILE --header=FILE[:TEMPLATE] instantiate the configuration header FILE Configuration files: $config_files Configuration headers: $config_headers Configuration commands: $config_commands Report bugs to ." _ACEOF ac_cs_config=`printf "%s\n" "$ac_configure_args" | sed "$ac_safe_unquote"` ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\''/g"` cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ ncdc config.status 1.23.1 configured by $0, generated by GNU Autoconf 2.71, with options \\"\$ac_cs_config\\" Copyright (C) 2021 Free Software Foundation, Inc. This config.status script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it." ac_pwd='$ac_pwd' srcdir='$srcdir' INSTALL='$INSTALL' MKDIR_P='$MKDIR_P' AWK='$AWK' test -n "\$AWK" || AWK=awk _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # The default lists apply if the user does not specify any file. ac_need_defaults=: while test $# != 0 do case $1 in --*=?*) ac_option=`expr "X$1" : 'X\([^=]*\)='` ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` ac_shift=: ;; --*=) ac_option=`expr "X$1" : 'X\([^=]*\)='` ac_optarg= ac_shift=: ;; *) ac_option=$1 ac_optarg=$2 ac_shift=shift ;; esac case $ac_option in # Handling of the options. -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) ac_cs_recheck=: ;; --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) printf "%s\n" "$ac_cs_version"; exit ;; --config | --confi | --conf | --con | --co | --c ) printf "%s\n" "$ac_cs_config"; exit ;; --debug | --debu | --deb | --de | --d | -d ) debug=: ;; --file | --fil | --fi | --f ) $ac_shift case $ac_optarg in *\'*) ac_optarg=`printf "%s\n" "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; '') as_fn_error $? "missing file argument" ;; esac as_fn_append CONFIG_FILES " '$ac_optarg'" ac_need_defaults=false;; --header | --heade | --head | --hea ) $ac_shift case $ac_optarg in *\'*) ac_optarg=`printf "%s\n" "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; esac as_fn_append CONFIG_HEADERS " '$ac_optarg'" ac_need_defaults=false;; --he | --h) # Conflict between --help and --header as_fn_error $? "ambiguous option: \`$1' Try \`$0 --help' for more information.";; --help | --hel | -h ) printf "%s\n" "$ac_cs_usage"; exit ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil | --si | --s) ac_cs_silent=: ;; # This is an error. -*) as_fn_error $? "unrecognized option: \`$1' Try \`$0 --help' for more information." ;; *) as_fn_append ac_config_targets " $1" ac_need_defaults=false ;; esac shift done ac_configure_extra_args= if $ac_cs_silent; then exec 6>/dev/null ac_configure_extra_args="$ac_configure_extra_args --silent" fi _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 if \$ac_cs_recheck; then set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion shift \printf "%s\n" "running CONFIG_SHELL=$SHELL \$*" >&6 CONFIG_SHELL='$SHELL' export CONFIG_SHELL exec "\$@" fi _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 exec 5>>config.log { echo sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX ## Running $as_me. ## _ASBOX printf "%s\n" "$ac_log" } >&5 _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 # # INIT-COMMANDS # AMDEP_TRUE="$AMDEP_TRUE" MAKE="${MAKE-make}" _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # Handling of arguments. for ac_config_target in $ac_config_targets do case $ac_config_target in "config.h") CONFIG_HEADERS="$CONFIG_HEADERS config.h" ;; "depfiles") CONFIG_COMMANDS="$CONFIG_COMMANDS depfiles" ;; "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; esac done # If the user did not use the arguments to specify the items to instantiate, # then the envvar interface is used. Set only those that are not. # We use the long form for the default assignment because of an extremely # bizarre bug on SunOS 4.1.3. if $ac_need_defaults; then test ${CONFIG_FILES+y} || CONFIG_FILES=$config_files test ${CONFIG_HEADERS+y} || CONFIG_HEADERS=$config_headers test ${CONFIG_COMMANDS+y} || CONFIG_COMMANDS=$config_commands fi # Have a temporary directory for convenience. Make it in the build tree # simply because there is no reason against having it here, and in addition, # creating and moving files from /tmp can sometimes cause problems. # Hook for its removal unless debugging. # Note that there is a small window in which the directory will not be cleaned: # after its creation but before its name has been assigned to `$tmp'. $debug || { tmp= ac_tmp= trap 'exit_status=$? : "${ac_tmp:=$tmp}" { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status ' 0 trap 'as_fn_exit 1' 1 2 13 15 } # Create a (secure) tmp directory for tmp files. { tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && test -d "$tmp" } || { tmp=./conf$$-$RANDOM (umask 077 && mkdir "$tmp") } || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5 ac_tmp=$tmp # Set up the scripts for CONFIG_FILES section. # No need to generate them if there are no CONFIG_FILES. # This happens for instance with `./config.status config.h'. if test -n "$CONFIG_FILES"; then ac_cr=`echo X | tr X '\015'` # On cygwin, bash can eat \r inside `` if the user requested igncr. # But we know of no other shell where ac_cr would be empty at this # point, so we can use a bashism as a fallback. if test "x$ac_cr" = x; then eval ac_cr=\$\'\\r\' fi ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' /dev/null` if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then ac_cs_awk_cr='\\r' else ac_cs_awk_cr=$ac_cr fi echo 'BEGIN {' >"$ac_tmp/subs1.awk" && _ACEOF { echo "cat >conf$$subs.awk <<_ACEOF" && echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' && echo "_ACEOF" } >conf$$subs.sh || as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'` ac_delim='%!_!# ' for ac_last_try in false false false false false :; do . ./conf$$subs.sh || as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X` if test $ac_delim_n = $ac_delim_num; then break elif $ac_last_try; then as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 else ac_delim="$ac_delim!$ac_delim _$ac_delim!! " fi done rm -f conf$$subs.sh cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK && _ACEOF sed -n ' h s/^/S["/; s/!.*/"]=/ p g s/^[^!]*!// :repl t repl s/'"$ac_delim"'$// t delim :nl h s/\(.\{148\}\)..*/\1/ t more1 s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/ p n b repl :more1 s/["\\]/\\&/g; s/^/"/; s/$/"\\/ p g s/.\{148\}// t nl :delim h s/\(.\{148\}\)..*/\1/ t more2 s/["\\]/\\&/g; s/^/"/; s/$/"/ p b :more2 s/["\\]/\\&/g; s/^/"/; s/$/"\\/ p g s/.\{148\}// t delim ' >$CONFIG_STATUS || ac_write_fail=1 rm -f conf$$subs.awk cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 _ACAWK cat >>"\$ac_tmp/subs1.awk" <<_ACAWK && for (key in S) S_is_set[key] = 1 FS = "" } { line = $ 0 nfields = split(line, field, "@") substed = 0 len = length(field[1]) for (i = 2; i < nfields; i++) { key = field[i] keylen = length(key) if (S_is_set[key]) { value = S[key] line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3) len += length(value) + length(field[++i]) substed = 1 } else len += 1 + keylen } print line } _ACAWK _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" else cat fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \ || as_fn_error $? "could not setup config files machinery" "$LINENO" 5 _ACEOF # VPATH may cause trouble with some makes, so we remove sole $(srcdir), # ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and # trailing colons and then remove the whole line if VPATH becomes empty # (actually we leave an empty line to preserve line numbers). if test "x$srcdir" = x.; then ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{ h s/// s/^/:/ s/[ ]*$/:/ s/:\$(srcdir):/:/g s/:\${srcdir}:/:/g s/:@srcdir@:/:/g s/^:*// s/:*$// x s/\(=[ ]*\).*/\1/ G s/\n// s/^[^=]*=[ ]*$// }' fi cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 fi # test -n "$CONFIG_FILES" # Set up the scripts for CONFIG_HEADERS section. # No need to generate them if there are no CONFIG_HEADERS. # This happens for instance with `./config.status Makefile'. if test -n "$CONFIG_HEADERS"; then cat >"$ac_tmp/defines.awk" <<\_ACAWK || BEGIN { _ACEOF # Transform confdefs.h into an awk script `defines.awk', embedded as # here-document in config.status, that substitutes the proper values into # config.h.in to produce config.h. # Create a delimiter string that does not exist in confdefs.h, to ease # handling of long lines. ac_delim='%!_!# ' for ac_last_try in false false :; do ac_tt=`sed -n "/$ac_delim/p" confdefs.h` if test -z "$ac_tt"; then break elif $ac_last_try; then as_fn_error $? "could not make $CONFIG_HEADERS" "$LINENO" 5 else ac_delim="$ac_delim!$ac_delim _$ac_delim!! " fi done # For the awk script, D is an array of macro values keyed by name, # likewise P contains macro parameters if any. Preserve backslash # newline sequences. ac_word_re=[_$as_cr_Letters][_$as_cr_alnum]* sed -n ' s/.\{148\}/&'"$ac_delim"'/g t rset :rset s/^[ ]*#[ ]*define[ ][ ]*/ / t def d :def s/\\$// t bsnl s/["\\]/\\&/g s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ D["\1"]=" \3"/p s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2"/p d :bsnl s/["\\]/\\&/g s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ D["\1"]=" \3\\\\\\n"\\/p t cont s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2\\\\\\n"\\/p t cont d :cont n s/.\{148\}/&'"$ac_delim"'/g t clear :clear s/\\$// t bsnlc s/["\\]/\\&/g; s/^/"/; s/$/"/p d :bsnlc s/["\\]/\\&/g; s/^/"/; s/$/\\\\\\n"\\/p b cont ' >$CONFIG_STATUS || ac_write_fail=1 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 for (key in D) D_is_set[key] = 1 FS = "" } /^[\t ]*#[\t ]*(define|undef)[\t ]+$ac_word_re([\t (]|\$)/ { line = \$ 0 split(line, arg, " ") if (arg[1] == "#") { defundef = arg[2] mac1 = arg[3] } else { defundef = substr(arg[1], 2) mac1 = arg[2] } split(mac1, mac2, "(") #) macro = mac2[1] prefix = substr(line, 1, index(line, defundef) - 1) if (D_is_set[macro]) { # Preserve the white space surrounding the "#". print prefix "define", macro P[macro] D[macro] next } else { # Replace #undef with comments. This is necessary, for example, # in the case of _POSIX_SOURCE, which is predefined and required # on some systems where configure will not decide to define it. if (defundef == "undef") { print "/*", prefix defundef, macro, "*/" next } } } { print } _ACAWK _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 as_fn_error $? "could not setup config headers machinery" "$LINENO" 5 fi # test -n "$CONFIG_HEADERS" eval set X " :F $CONFIG_FILES :H $CONFIG_HEADERS :C $CONFIG_COMMANDS" shift for ac_tag do case $ac_tag in :[FHLC]) ac_mode=$ac_tag; continue;; esac case $ac_mode$ac_tag in :[FHL]*:*);; :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;; :[FH]-) ac_tag=-:-;; :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; esac ac_save_IFS=$IFS IFS=: set x $ac_tag IFS=$ac_save_IFS shift ac_file=$1 shift case $ac_mode in :L) ac_source=$1;; :[FH]) ac_file_inputs= for ac_f do case $ac_f in -) ac_f="$ac_tmp/stdin";; *) # Look for the file first in the build tree, then in the source tree # (if the path is not absolute). The absolute path cannot be DOS-style, # because $ac_f cannot contain `:'. test -f "$ac_f" || case $ac_f in [\\/$]*) false;; *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; esac || as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;; esac case $ac_f in *\'*) ac_f=`printf "%s\n" "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac as_fn_append ac_file_inputs " '$ac_f'" done # Let's still pretend it is `configure' which instantiates (i.e., don't # use $as_me), people would be surprised to read: # /* config.h. Generated by config.status. */ configure_input='Generated from '` printf "%s\n" "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' `' by configure.' if test x"$ac_file" != x-; then configure_input="$ac_file. $configure_input" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 printf "%s\n" "$as_me: creating $ac_file" >&6;} fi # Neutralize special characters interpreted by sed in replacement strings. case $configure_input in #( *\&* | *\|* | *\\* ) ac_sed_conf_input=`printf "%s\n" "$configure_input" | sed 's/[\\\\&|]/\\\\&/g'`;; #( *) ac_sed_conf_input=$configure_input;; esac case $ac_tag in *:-:* | *:-) cat >"$ac_tmp/stdin" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; esac ;; esac ac_dir=`$as_dirname -- "$ac_file" || $as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$ac_file" : 'X\(//\)[^/]' \| \ X"$ac_file" : 'X\(//\)$' \| \ X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X"$ac_file" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` as_dir="$ac_dir"; as_fn_mkdir_p ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_dir_suffix=/`printf "%s\n" "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. ac_top_builddir_sub=`printf "%s\n" "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; esac ;; esac ac_abs_top_builddir=$ac_pwd ac_abs_builddir=$ac_pwd$ac_dir_suffix # for backward compatibility: ac_top_builddir=$ac_top_build_prefix case $srcdir in .) # We are building in place. ac_srcdir=. ac_top_srcdir=$ac_top_builddir_sub ac_abs_top_srcdir=$ac_pwd ;; [\\/]* | ?:[\\/]* ) # Absolute name. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ac_abs_top_srcdir=$srcdir ;; *) # Relative name. ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_build_prefix$srcdir ac_abs_top_srcdir=$ac_pwd/$srcdir ;; esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix case $ac_mode in :F) # # CONFIG_FILE # case $INSTALL in [\\/$]* | ?:[\\/]* ) ac_INSTALL=$INSTALL ;; *) ac_INSTALL=$ac_top_build_prefix$INSTALL ;; esac ac_MKDIR_P=$MKDIR_P case $MKDIR_P in [\\/$]* | ?:[\\/]* ) ;; */*) ac_MKDIR_P=$ac_top_build_prefix$MKDIR_P ;; esac _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # If the template does not know about datarootdir, expand it. # FIXME: This hack should be removed a few years after 2.60. ac_datarootdir_hack=; ac_datarootdir_seen= ac_sed_dataroot=' /datarootdir/ { p q } /@datadir@/p /@docdir@/p /@infodir@/p /@localedir@/p /@mandir@/p' case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in *datarootdir*) ac_datarootdir_seen=yes;; *@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 printf "%s\n" "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_datarootdir_hack=' s&@datadir@&$datadir&g s&@docdir@&$docdir&g s&@infodir@&$infodir&g s&@localedir@&$localedir&g s&@mandir@&$mandir&g s&\\\${datarootdir}&$datarootdir&g' ;; esac _ACEOF # Neutralize VPATH when `$srcdir' = `.'. # Shell code in configure.ac might set extrasub. # FIXME: do we really want to maintain this feature? cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_sed_extra="$ac_vpsub $extrasub _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 :t /@[a-zA-Z_][a-zA-Z_0-9]*@/!b s|@configure_input@|$ac_sed_conf_input|;t t s&@top_builddir@&$ac_top_builddir_sub&;t t s&@top_build_prefix@&$ac_top_build_prefix&;t t s&@srcdir@&$ac_srcdir&;t t s&@abs_srcdir@&$ac_abs_srcdir&;t t s&@top_srcdir@&$ac_top_srcdir&;t t s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t s&@builddir@&$ac_builddir&;t t s&@abs_builddir@&$ac_abs_builddir&;t t s&@abs_top_builddir@&$ac_abs_top_builddir&;t t s&@INSTALL@&$ac_INSTALL&;t t s&@MKDIR_P@&$ac_MKDIR_P&;t t $ac_datarootdir_hack " eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \ >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5 test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ "$ac_tmp/out"`; test -z "$ac_out"; } && { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined" >&5 printf "%s\n" "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined" >&2;} rm -f "$ac_tmp/stdin" case $ac_file in -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";; *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";; esac \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; :H) # # CONFIG_HEADER # if test x"$ac_file" != x-; then { printf "%s\n" "/* $configure_input */" >&1 \ && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" } >"$ac_tmp/config.h" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 if diff "$ac_file" "$ac_tmp/config.h" >/dev/null 2>&1; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: $ac_file is unchanged" >&5 printf "%s\n" "$as_me: $ac_file is unchanged" >&6;} else rm -f "$ac_file" mv "$ac_tmp/config.h" "$ac_file" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 fi else printf "%s\n" "/* $configure_input */" >&1 \ && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" \ || as_fn_error $? "could not create -" "$LINENO" 5 fi # Compute "$ac_file"'s index in $config_headers. _am_arg="$ac_file" _am_stamp_count=1 for _am_header in $config_headers :; do case $_am_header in $_am_arg | $_am_arg:* ) break ;; * ) _am_stamp_count=`expr $_am_stamp_count + 1` ;; esac done echo "timestamp for $_am_arg" >`$as_dirname -- "$_am_arg" || $as_expr X"$_am_arg" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$_am_arg" : 'X\(//\)[^/]' \| \ X"$_am_arg" : 'X\(//\)$' \| \ X"$_am_arg" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X"$_am_arg" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'`/stamp-h$_am_stamp_count ;; :C) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: executing $ac_file commands" >&5 printf "%s\n" "$as_me: executing $ac_file commands" >&6;} ;; esac case $ac_file$ac_mode in "depfiles":C) test x"$AMDEP_TRUE" != x"" || { # Older Autoconf quotes --file arguments for eval, but not when files # are listed without --file. Let's play safe and only enable the eval # if we detect the quoting. # TODO: see whether this extra hack can be removed once we start # requiring Autoconf 2.70 or later. case $CONFIG_FILES in #( *\'*) : eval set x "$CONFIG_FILES" ;; #( *) : set x $CONFIG_FILES ;; #( *) : ;; esac shift # Used to flag and report bootstrapping failures. am_rc=0 for am_mf do # Strip MF so we end up with the name of the file. am_mf=`printf "%s\n" "$am_mf" | sed -e 's/:.*$//'` # Check whether this is an Automake generated Makefile which includes # dependency-tracking related rules and includes. # Grep'ing the whole file directly is not great: AIX grep has a line # limit of 2048, but all sed's we know have understand at least 4000. sed -n 's,^am--depfiles:.*,X,p' "$am_mf" | grep X >/dev/null 2>&1 \ || continue am_dirpart=`$as_dirname -- "$am_mf" || $as_expr X"$am_mf" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$am_mf" : 'X\(//\)[^/]' \| \ X"$am_mf" : 'X\(//\)$' \| \ X"$am_mf" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X"$am_mf" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` am_filepart=`$as_basename -- "$am_mf" || $as_expr X/"$am_mf" : '.*/\([^/][^/]*\)/*$' \| \ X"$am_mf" : 'X\(//\)$' \| \ X"$am_mf" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X/"$am_mf" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` { echo "$as_me:$LINENO: cd "$am_dirpart" \ && sed -e '/# am--include-marker/d' "$am_filepart" \ | $MAKE -f - am--depfiles" >&5 (cd "$am_dirpart" \ && sed -e '/# am--include-marker/d' "$am_filepart" \ | $MAKE -f - am--depfiles) >&5 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } || am_rc=$? done if test $am_rc -ne 0; then { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "Something went wrong bootstrapping makefile fragments for automatic dependency tracking. If GNU make was not used, consider re-running the configure script with MAKE=\"gmake\" (or whatever is necessary). You can also try re-running configure with the '--disable-dependency-tracking' option to at least be able to build the package (albeit without support for automatic dependency tracking). See \`config.log' for more details" "$LINENO" 5; } fi { am_dirpart=; unset am_dirpart;} { am_filepart=; unset am_filepart;} { am_mf=; unset am_mf;} { am_rc=; unset am_rc;} rm -f conftest-deps.mk } ;; esac done # for ac_tag as_fn_exit 0 _ACEOF ac_clean_files=$ac_clean_files_save test $ac_write_fail = 0 || as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5 # configure is writing to config.log, and then calls config.status. # config.status does its own redirection, appending to config.log. # Unfortunately, on DOS this fails, as config.log is still kept open # by configure, so config.status won't be able to write to it; its # output is simply discarded. So we exec the FD to /dev/null, # effectively closing config.log, so it can be properly (re)opened and # appended to by config.status. When coming back to configure, we # need to make the FD available again. if test "$no_create" != yes; then ac_cs_success=: ac_config_status_args= test "$silent" = yes && ac_config_status_args="$ac_config_status_args --quiet" exec 5>/dev/null $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false exec 5>>config.log # Use ||, not &&, to avoid exiting from the if with $? = 1, which # would make configure fail if this is the last instruction. $ac_cs_success || as_fn_exit 1 fi if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 printf "%s\n" "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} fi ncdc-1.23.1/ChangeLog0000644000175000017500000004342214314535676011223 000000000000001.23.1 - 2022-09-27 - Fix buffer overflow on connections tab for incoming IPv6 connections in handshake phase 1.23 - 2022-05-30 - Bump minimum glib version to 2.32 - Re-open GeoIP database on SIGUSR1 - Add tls_policy=force setting - Fix TLS on Verlihub - Various minor language fixes - Add workaround rare compiler bug for aarch64 1.22.1 - 2019-06-03 - Fix segfault with ADC client connections 1.22 - 2019-04-30 - Add 'b' and 'B' keys to connections tab (Daniel Kamil Kozar) - Add 'max_ul_per_user' setting to support multiple upload slots per user (Daniel Kamil Kozar) - Add support for TLS ALPN (Denys Smirnov) - Fix build against ncurses with separate libtinfo (Lars Wendler) 1.21 - 2019-03-26 - Switch to libmaxminddb for GeoIP lookups - Replaced 'geoip_cc4' and 'geoip_cc6' settings with a single 'geoip_cc' - Mark already queued or shared files in search and file browser (Daniel Kamil Kozar) - Add 'download_shared' setting (Daniel Kamil Kozar) - Add 'show_free_slots' setting (Daniel Kamil Kozar) - Add support for the "Free Slots" ADC extension 1.20 - 2016-12-30 - Support bracketed paste mode in input handling (cologic) - Add 'geoip_cc4' and 'geoip_cc6' settings - Add 'log_hubchat' setting - Add 'local' option to 'active_ip' setting - Add support for multistream bzip2 filelists - Disable RC4 ciphers by default from tls_priority - Fix potential null pointer deference - Fix chmod of destination directories (Johannes Beisswenger) 1.19.1 - 2014-04-23 - Fix remote null pointer dereference - Searching now works in the search results list - Fix possible file corruption when moving file to destination - Fix error handling when finalizing a file download - Fix downloading of 0-byte files - Fix extremely slow /gc - Fix sendfile() with large files on 32-bit Linux - Fix minor display issue with multicolumn characters 1.19 - 2014-02-11 - Add search functionality to the file browser and user list (/,. keys) - Add geoip support (requires --with-geoip at configure) - Add 'download_segment' setting to change minimum segment size - Log hashing progress to stderr.log - Fix three (potential) security vulnerabilities - Fix downloading of file lists when other user has no free slots 1.18.1 - 2013-10-05 - Fix crash when downloading files from multiple sources - Use the yxml library to parse files.xml.bz2 files - Fix various XML conformance bugs in parsing files.xml.bz2 files 1.18 - 2013-09-25 - Add support for segmented downloading - Support $MyINFO without flags byte on NMDC hubs - Don't require pod2man on build - Fix tab-completion of nick names when full nick is specified - Fix cursor position on selected line in listings - Fix bug with schema-less /connect 1.17 - 2013-06-15 - Add 'q' key to user list for matching a users' files with download queue - Add transfers.log format documentation to manual page - Consider non-alphanumeric characters as word separators in input line - Fix outgoing UDP messages to respect local_address setting - Fix Alt+Backspace on xterm-like terminals - Fix handling of "." and ".." file/directory names in files.xml.bz2 - Fix possible crash when receiving unexpected encrypted search results - Fix sendfile() handling to use fallback on EOVERFLOW - Fix possible crash when logging UDP messages 1.16.1 - 2013-03-23 - Fix crash when opening connection on ADC in passive mode - Fix documentation of 'd' key in download_exclude setting 1.16 - 2013-03-21 - List of granted users is now remembered across restarts - Don't throttle users who are granted a slot - Support CIDs of variable size on ADC - Log, but otherwise ignore, DSTA messages on ADC - Fix possible crash with graceful disconnect on C-C connections - Fix bug with enabling active mode when active_ip is set - Fix reporting of active mode on NMDC hubs - Fix bug with the 'X' key on the queue tab - Fix idle disconnect timeout when a file transfer is active 1.15 - 2013-03-02 - IPv6 support - Significantly shorten certificate creation time with old GnuTLS versions - Always enable tls_policy and sudp_policy by default - Link against libgcrypt if detected GnuTLS is older than 3.0 - Add color_tab_active setting - Remove active_tls_port setting - Allow '-', '.' and '_' characters in hub names - Allow spaces before a command - Add Alt+backspace as alias for Ctrl+w - Add throttle for 'CGET tthl' requests - Don't throw away PMs from unknown users - Recognize mode field in $MyINFO without tag - Fix possible crash with C-C TLS and old GnuTLS versions - Fix old references to the removed ncdc-db-upgrade utility - Fix loading of file lists from Shareaza 2.6.0.0 and earlier - Fix handling of tab and carriage return in log window - Fix changing of download_dir/incoming_dir if either dir has been deleted - Fix compilation against glib < 2.26 - Fix unclean C-C TLS disconnect on timeout 1.14 - 2012-11-04 - Added BLOM support for ADC ('/hset adc_blom true' to enable it) - Added section on connection settings to man page - Fix incorrect char signedness assumption on ARM - Fix possible crash when downloading small files - Fix hub counts reported to the hub on login on ADC - Fix local time display issue when built against musl (0.9.6) - Removed legacy ncdc-db-upgrade utility 1.13 - 2012-08-16 - zlib library added as a required dependency - Purge empty directories from share by default - Added "share_emptydirs" setting - Disable tls_policy by default when using an old GnuTLS version - Improved support for group chat - Honor G_FILENAME_ENCODING for path autocomplete, /share and queued files - Use a default connection string on NMDC if no 'connection' has been set - Support ZLIG for partial file list transfers on ADC - Send more subdirectories in partial file list transfers - Removed use of system-provided realpath() - Don't allow /search with an empty string - Fix segfault on /search command without query - Fix display of 'sudp_policy' setting if SUDP is not supported - Fix --enable-git-version when cross-compiling 1.12 - 2012-07-10 - Don't follow symlinks in share by default - Added 'share_symlink' option - Added bell notification and 'notify_bell' option - Added 'sudp_policy' setting - List all configured hubs on '/open' - Added '/delhub' command to remove hub configuration - Added filtering options to connections tab - Added TLS support indication to user list - Added Alt+a key to cycle through tabs with recent activity - Allow binding to ports below 1024 - Add space after autocompleting a command - Fix uploading chunks of 2GiB and larger (bug #12) - Fix bug with duplicate directory detection in '/share' - Fix display of timer on search tab - ADC: Use shorter search token to save some bandwidth - Various attempts at cleaning up some code 1.11 - 2012-05-15 - Drop libxml2 in favour of custom XML parser & writer - Allow using a single listen port for TCP and TLS - Added support for encrypted UDP messages (ADC SUDP) - Included 'makeheaders' in the distribution - Removed GNU-specific extensions from the Makefile - Fix /disconnect to cancel automatic reconnect - Fix loading of file lists with invalid UTF-8 sequences - Fix ncurses detection on OpenIndiana - Fix use of TLS in passive mode on ADC - Fix configure warning when git could not be found 1.10 - 2012-05-03 - Rewrote network backend to use plain sockets instead of GIO - Added GnuTLS as required dependency - Removed GIO and glib-networking dependencies - Removed 'ncdc-gen-cert' utility - ncdc can now generate certs by itself - Enable client-to-client TLS by default - Added 'tls_priority' setting - Added 'reconnect_timeout' setting - Don't quit ncdc on Ctrl+C - Display age of file list in the title bar - Don't build the 'ncdc-db-upgrade' tool by default - Switched to a single top-level Makefile - Fix '/browse user -f' ('-f' argument after username) - Fix hub login when it checks for public hubs = 0 - Fix overflow of long tab titles - Fix loading of microdc2-generated file lists - Fix loading of file lists with an invalid character - Fix occasional crash when TLS is enabled - Fix transfer rate indication and limiting with TLS connections - Fix small memory leak when 'upload_rate' is set 1.9 - 2012-03-14 - Allow all 'active_' settings to be changed on a per-hub basis - Allow 'active_ip' to be unset and automatically get IP from hub - Added 'active_udp_port' and 'active_tcp_port' settings - Renamed 'active_bind' to 'local_address' and use it for outgoing connections as well - Display connection settings in hub info bar - Added '/listen' command to display currently used ports - Don't listen on TLS port when tls_policy is disabled - Added 'disconnect_offline' setting - Display '(global)' indicator when showing /hset variables - Don't strip whitespace from /say - Don't allow directory separator as /share name - Allow 'global.' and '#hubname.' prefix for /set keys - Fix display of long IP addresses on user list 1.8 - 2012-02-13 - Added bandwidth limiting (upload_rate and download_rate settings) - Added hash speed limiting (hash_rate setting) - Added 'm' key to connection tab to /msg selected user - Disable client-to-client TLS by default - Don't throw away some search results on NMDC - (Partially) fixed uploading of >2GB chunks - Fixed file descriptor leak when using the backlog feature - Fixed crash when opening invalid filelist from search twice - Use POD for the manual pages - Minor typo fixes 1.7 - 2011-12-30 - Split /set command in a /set (global) and /hset (hub) - File downloads are performed in a background thread - Added glob-style matching on /set and /hset keys - Added UTF-8 locale check - Added 'sendfile' setting - Added finer granularity for the flush_file_cache setting - Allow flush_file_cache to be enabled for downloads - Fix sending of $MyINFO with wrong public hub count - Fix incorrect inclusion of gdbm.h 1.6 - 2011-12-07 - Use SQLite3 for storage instead of GDBM - Converted config.ini to SQLite3 database - Added ncdc-db-upgrade utility - Session directory is architecture-independent - All data is safe against crashes and power failures - Added support for removing/adding directories without rehashing - Always match every file list on 'Q' key on TTH search - Immediately flush log entries to the kernel - Faster start-up - Added support for per-hub 'active_ip' settings - Allow interval notation when setting autorefresh - Broadcast SF (number of shared files) on ADC hubs - Combine TTH data for downloaded files to blocks of at least 1MiB - Increased hash buffer size (10KiB -> 512KiB) - Fix case-insensitivity of search results - Fix reporting of user state in pm tabs at hub disconnect - Fix generation of client certificates with openssl - Fix segfault with duplicate users on an ADC hub - Fix segfault when opening of a filelist fails - Fix base32 decoding bug (fixes login sequence on some ADC hubs) 1.5 - 2011-11-03 - Added filelist_maxage setting - Added flush_file_cache setting - Added /ungrant and improved /grant management - Added key to download queue to clear user state for all files - Added keys to search results to download file list and match queue - Select the right user when using the 'q' key in connection tab - Fixed possible crash when opening file list from search results - Fixed detection of incompatible session directory version 1.4 - 2011-10-26 - Added sorting functionality to file list - Added color settings: title, separator, list_default, list_header and list_select - Added "blink" color attribute - Allow /disconnect to be used on the main tab - Display number of matched and added items when using match queue feature - Use git-describe to create a version string, if available - Decreased memory usage for large file lists - Handle duplicate filenames in other users' file list - Fixed incorrect setting of the "Incomplete" flag in files.xml.bz2 - Fixed handling of the PM param in MSG commands on ADC - Fixed user change notifications for PM tabs 1.3 - 2011-10-14 - Added multi-source downloading - Added user information view and management keys to download queue tab - Added "search for alternative" key to queue, file browser and search tabs - Added "match queue" key to file browser and search tabs - Added ui_time_format setting - Added chat_only setting - Changed default value of color_log_time to dark grey - Improved tracking of a parent for each tab - Improved portability for Solaris - Fixed crash when closing a hub tab while it is connecting - Fixed crash when auto-completing settings without auto-completion - Fixed bug with file name display if download_dir ends with a slash - Fixed bug with uploading chunks larger than 2GiB - Fixed handling of directory search results on ADC 1.2 - 2011-09-25 - Fixed incorrect handling of outgoing NMDC connections 1.1 - 2011-09-25 - Select item in file browser when opened from a search result - Added active_bind setting - Added share_exclude setting - Added download_exclude setting - Added incoming_dir setting - Added autocompletion for the previous values of certain settings - Allow the "connection" setting to be used for ADC as well - Added IP column to user list - Allow sorting on description, email, tag and IP columns in user list - Display upload speeds in the user list of an ADC hub - Added TLS indication to connection list - Mark selected items bold in listings - Allow /reconnect on the main tab to reconnect all hubs - Added slash to base path in partial file lists - Added delay of 5 seconds before reconnecting to a hub - Added recognition of the AP param on ADC - Added support for UserIP2 on NMDC - Removed support for unexpected incoming NMDC connections 1.0 - 2011-09-16 - Added ncdc(1) and ncdc-gen-cert(1) manual pages - Documented settings (/help set ) - Documented key bindings (/help keys) - Improved line wrapping algorithm for the log window - Added support for client-to-client TLS on NMDC - Added support for the CGFI command on ADC - Throttle GET requests on the same file + offset - Fixed glib assertion failure when disabling active mode - Fixed downloading from clients using $ADCSND with -1 bytes - Fixed race condition in file uploading code - Fixed idle time calculation while connecting to another client - Properly include unistd.h in dl.c 0.9 - 2011-09-03 - Added TLS support (adcs://, nmdcs://, and ADC client-to-client) - Added tls_policy setting - Added KEYP support for ADC - Added warning when a hub changes TLS certificate - Display exact listen ports when enabling active mode 0.8 - 2011-08-26 - Added transfer log - Added log_downloads and log_uploads settings - Added day changed indicators to the log windows - Added common readline keys to the text input box - Changed /refresh shortcut from Ctrl+e/u to Alt+r - Allow join messages to work even when the join completion detection fails - Select parent tab when closing a userlist, PM or filelist tab - Re-open log files when receiving SIGUSR1 - Perform a clean shutdown when the terminal is closed - Fixed bug in formatting the title of a /search tab - Fixed log indent for non-ASCII nicks - Fixed log highlighting and indenting for /me messages 0.7 - 2011-08-17 - Added word wrapping for the log window - Added basic colors and nick highlighting to the log window - Allow colors to be changed with the /set command - Added backlog feature and setting - Added silent building to the configure script - Automatically re-open log files when they are moved/truncated externally - Accept 'nmdc://' URLs as alternative to 'dchub://' - Fixed spamming of useless $MyINFO and BINF commands every 5 minutes - Fixed minor memory leak when closing/clearing the log window 0.6 - 2011-08-08 - Added file searching, through a /search command - Added tab to display the search results - Listen for incoming messages on UDP in active mode - Allow specifying a hub address with /open - Fixed case-sensitivity of shared files - Various bugfixes and other improvements 0.5 - 2011-08-02 - Downloaded files are now TTH-checked - Added download queue priorities - Download queue items are automatically disabled on error - Improved error handling and reporting for downloads - Added download_slots setting - Use a separate thread to load other users' file list - Improved /gc to also clean up download queue related data - Decreased memory usage for large file lists - Improved error handling with sendfile() - Fixed downloading in passive mode on ADC hubs - Fixed adding a dir to the download queue while connected to the user - Fixed segfault when the userlist is open while disconnecting from a hub 0.4 - 2011-07-23 - Added file downloading support WARNING: Downloaded files are not TTH checked at this moment. - Added persistent download queue - Added busy indicators on start-up and with /gc - Added download speed indicator to status bar - Improved connection list interface - Improved performance of UI message handling - Fixed a remote crash - Fixed incorrect reporting of hub counters 0.3 - 2011-07-15 - Added file list browser - Added downloading of other people's file list - Added 'hubname' setting to rename hub tabs - Added -v, -c and -n commandline options - Added -n option to /open to prevent an autoconnect - Added referer notification - Improved handling of some ADC commands - Improved logging of debug messages - Fixed error when uploading an empty file list - Fixed display of join/quits on ADC hubs - Fixed several crashes 0.2 - 2011-06-27 - ADC support - Added slot granting and /grant command - Added /kick (for NMDC hubs) - Added /pm and /nick aliasses - Added support for passworded login - Added /me command (mostly useful for ADC hubs) - Added /whois command - Added 'share_hidden' option (default: false) - Improved minislots support - Added 'minislots' and 'minislot_size' options - Slightly improved user list and connection list - /set displays default values for unset options 0.1 - 2011-06-20 Initial version ncdc-1.23.1/config.h.in0000644000175000017500000000255014314535761011464 00000000000000/* config.h.in. Generated from configure.ac by autoheader. */ /* Define if using BSD sendfile support. */ #undef HAVE_BSD_SENDFILE /* Define to 1 if you have the header file. */ #undef HAVE_BZLIB_H /* Define if using Linux sendfile support. */ #undef HAVE_LINUX_SENDFILE /* Define to 1 if you have the `posix_fadvise' function. */ #undef HAVE_POSIX_FADVISE /* Define if sendfile support. */ #undef HAVE_SENDFILE /* Define to 1 if you have the header file. */ #undef HAVE_SQLITE3_H /* Define to 1 if you have the header file. */ #undef HAVE_ZLIB_H /* Name of package */ #undef PACKAGE /* Define to the address where bug reports for this package should be sent. */ #undef PACKAGE_BUGREPORT /* Define to the full name of this package. */ #undef PACKAGE_NAME /* Define to the full name and version of this package. */ #undef PACKAGE_STRING /* Define to the one symbol short name of this package. */ #undef PACKAGE_TARNAME /* Define to the home page for this package. */ #undef PACKAGE_URL /* Define to the version of this package. */ #undef PACKAGE_VERSION /* Use libmaxminddb for IP-to-country lookups */ #undef USE_GEOIP /* Version number of package */ #undef VERSION /* Number of bits in a file offset, on hosts where this is settable. */ #undef _FILE_OFFSET_BITS /* Define for large files, on AIX-style hosts. */ #undef _LARGE_FILES ncdc-1.23.1/depcomp0000755000175000017500000005602014314535761011017 00000000000000#! /bin/sh # depcomp - compile a program generating dependencies as side-effects scriptversion=2018-03-07.03; # UTC # Copyright (C) 1999-2021 Free Software Foundation, Inc. # 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 2, 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 . # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that program. # Originally written by Alexandre Oliva . case $1 in '') echo "$0: No command. Try '$0 --help' for more information." 1>&2 exit 1; ;; -h | --h*) cat <<\EOF Usage: depcomp [--help] [--version] PROGRAM [ARGS] Run PROGRAMS ARGS to compile a file, generating dependencies as side-effects. Environment variables: depmode Dependency tracking mode. source Source file read by 'PROGRAMS ARGS'. object Object file output by 'PROGRAMS ARGS'. DEPDIR directory where to store dependencies. depfile Dependency file to output. tmpdepfile Temporary file to use when outputting dependencies. libtool Whether libtool is used (yes/no). Report bugs to . EOF exit $? ;; -v | --v*) echo "depcomp $scriptversion" exit $? ;; esac # Get the directory component of the given path, and save it in the # global variables '$dir'. Note that this directory component will # be either empty or ending with a '/' character. This is deliberate. set_dir_from () { case $1 in */*) dir=`echo "$1" | sed -e 's|/[^/]*$|/|'`;; *) dir=;; esac } # Get the suffix-stripped basename of the given path, and save it the # global variable '$base'. set_base_from () { base=`echo "$1" | sed -e 's|^.*/||' -e 's/\.[^.]*$//'` } # If no dependency file was actually created by the compiler invocation, # we still have to create a dummy depfile, to avoid errors with the # Makefile "include basename.Plo" scheme. make_dummy_depfile () { echo "#dummy" > "$depfile" } # Factor out some common post-processing of the generated depfile. # Requires the auxiliary global variable '$tmpdepfile' to be set. aix_post_process_depfile () { # If the compiler actually managed to produce a dependency file, # post-process it. if test -f "$tmpdepfile"; then # Each line is of the form 'foo.o: dependency.h'. # Do two passes, one to just change these to # $object: dependency.h # and one to simply output # dependency.h: # which is needed to avoid the deleted-header problem. { sed -e "s,^.*\.[$lower]*:,$object:," < "$tmpdepfile" sed -e "s,^.*\.[$lower]*:[$tab ]*,," -e 's,$,:,' < "$tmpdepfile" } > "$depfile" rm -f "$tmpdepfile" else make_dummy_depfile fi } # A tabulation character. tab=' ' # A newline character. nl=' ' # Character ranges might be problematic outside the C locale. # These definitions help. upper=ABCDEFGHIJKLMNOPQRSTUVWXYZ lower=abcdefghijklmnopqrstuvwxyz digits=0123456789 alpha=${upper}${lower} if test -z "$depmode" || test -z "$source" || test -z "$object"; then echo "depcomp: Variables source, object and depmode must be set" 1>&2 exit 1 fi # Dependencies for sub/bar.o or sub/bar.obj go into sub/.deps/bar.Po. depfile=${depfile-`echo "$object" | sed 's|[^\\/]*$|'${DEPDIR-.deps}'/&|;s|\.\([^.]*\)$|.P\1|;s|Pobj$|Po|'`} tmpdepfile=${tmpdepfile-`echo "$depfile" | sed 's/\.\([^.]*\)$/.T\1/'`} rm -f "$tmpdepfile" # Avoid interferences from the environment. gccflag= dashmflag= # Some modes work just like other modes, but use different flags. We # parameterize here, but still list the modes in the big case below, # to make depend.m4 easier to write. Note that we *cannot* use a case # here, because this file can only contain one case statement. if test "$depmode" = hp; then # HP compiler uses -M and no extra arg. gccflag=-M depmode=gcc fi if test "$depmode" = dashXmstdout; then # This is just like dashmstdout with a different argument. dashmflag=-xM depmode=dashmstdout fi cygpath_u="cygpath -u -f -" if test "$depmode" = msvcmsys; then # This is just like msvisualcpp but w/o cygpath translation. # Just convert the backslash-escaped backslashes to single forward # slashes to satisfy depend.m4 cygpath_u='sed s,\\\\,/,g' depmode=msvisualcpp fi if test "$depmode" = msvc7msys; then # This is just like msvc7 but w/o cygpath translation. # Just convert the backslash-escaped backslashes to single forward # slashes to satisfy depend.m4 cygpath_u='sed s,\\\\,/,g' depmode=msvc7 fi if test "$depmode" = xlc; then # IBM C/C++ Compilers xlc/xlC can output gcc-like dependency information. gccflag=-qmakedep=gcc,-MF depmode=gcc fi case "$depmode" in gcc3) ## gcc 3 implements dependency tracking that does exactly what ## we want. Yay! Note: for some reason libtool 1.4 doesn't like ## it if -MD -MP comes after the -MF stuff. Hmm. ## Unfortunately, FreeBSD c89 acceptance of flags depends upon ## the command line argument order; so add the flags where they ## appear in depend2.am. Note that the slowdown incurred here ## affects only configure: in makefiles, %FASTDEP% shortcuts this. for arg do case $arg in -c) set fnord "$@" -MT "$object" -MD -MP -MF "$tmpdepfile" "$arg" ;; *) set fnord "$@" "$arg" ;; esac shift # fnord shift # $arg done "$@" stat=$? if test $stat -ne 0; then rm -f "$tmpdepfile" exit $stat fi mv "$tmpdepfile" "$depfile" ;; gcc) ## Note that this doesn't just cater to obsosete pre-3.x GCC compilers. ## but also to in-use compilers like IMB xlc/xlC and the HP C compiler. ## (see the conditional assignment to $gccflag above). ## There are various ways to get dependency output from gcc. Here's ## why we pick this rather obscure method: ## - Don't want to use -MD because we'd like the dependencies to end ## up in a subdir. Having to rename by hand is ugly. ## (We might end up doing this anyway to support other compilers.) ## - The DEPENDENCIES_OUTPUT environment variable makes gcc act like ## -MM, not -M (despite what the docs say). Also, it might not be ## supported by the other compilers which use the 'gcc' depmode. ## - Using -M directly means running the compiler twice (even worse ## than renaming). if test -z "$gccflag"; then gccflag=-MD, fi "$@" -Wp,"$gccflag$tmpdepfile" stat=$? if test $stat -ne 0; then rm -f "$tmpdepfile" exit $stat fi rm -f "$depfile" echo "$object : \\" > "$depfile" # The second -e expression handles DOS-style file names with drive # letters. sed -e 's/^[^:]*: / /' \ -e 's/^['$alpha']:\/[^:]*: / /' < "$tmpdepfile" >> "$depfile" ## This next piece of magic avoids the "deleted header file" problem. ## The problem is that when a header file which appears in a .P file ## is deleted, the dependency causes make to die (because there is ## typically no way to rebuild the header). We avoid this by adding ## dummy dependencies for each header file. Too bad gcc doesn't do ## this for us directly. ## Some versions of gcc put a space before the ':'. On the theory ## that the space means something, we add a space to the output as ## well. hp depmode also adds that space, but also prefixes the VPATH ## to the object. Take care to not repeat it in the output. ## Some versions of the HPUX 10.20 sed can't process this invocation ## correctly. Breaking it into two sed invocations is a workaround. tr ' ' "$nl" < "$tmpdepfile" \ | sed -e 's/^\\$//' -e '/^$/d' -e "s|.*$object$||" -e '/:$/d' \ | sed -e 's/$/ :/' >> "$depfile" rm -f "$tmpdepfile" ;; hp) # This case exists only to let depend.m4 do its work. It works by # looking at the text of this script. This case will never be run, # since it is checked for above. exit 1 ;; sgi) if test "$libtool" = yes; then "$@" "-Wp,-MDupdate,$tmpdepfile" else "$@" -MDupdate "$tmpdepfile" fi stat=$? if test $stat -ne 0; then rm -f "$tmpdepfile" exit $stat fi rm -f "$depfile" if test -f "$tmpdepfile"; then # yes, the sourcefile depend on other files echo "$object : \\" > "$depfile" # Clip off the initial element (the dependent). Don't try to be # clever and replace this with sed code, as IRIX sed won't handle # lines with more than a fixed number of characters (4096 in # IRIX 6.2 sed, 8192 in IRIX 6.5). We also remove comment lines; # the IRIX cc adds comments like '#:fec' to the end of the # dependency line. tr ' ' "$nl" < "$tmpdepfile" \ | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' \ | tr "$nl" ' ' >> "$depfile" echo >> "$depfile" # The second pass generates a dummy entry for each header file. tr ' ' "$nl" < "$tmpdepfile" \ | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' -e 's/$/:/' \ >> "$depfile" else make_dummy_depfile fi rm -f "$tmpdepfile" ;; xlc) # This case exists only to let depend.m4 do its work. It works by # looking at the text of this script. This case will never be run, # since it is checked for above. exit 1 ;; aix) # The C for AIX Compiler uses -M and outputs the dependencies # in a .u file. In older versions, this file always lives in the # current directory. Also, the AIX compiler puts '$object:' at the # start of each line; $object doesn't have directory information. # Version 6 uses the directory in both cases. set_dir_from "$object" set_base_from "$object" if test "$libtool" = yes; then tmpdepfile1=$dir$base.u tmpdepfile2=$base.u tmpdepfile3=$dir.libs/$base.u "$@" -Wc,-M else tmpdepfile1=$dir$base.u tmpdepfile2=$dir$base.u tmpdepfile3=$dir$base.u "$@" -M fi stat=$? if test $stat -ne 0; then rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" exit $stat fi for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" do test -f "$tmpdepfile" && break done aix_post_process_depfile ;; tcc) # tcc (Tiny C Compiler) understand '-MD -MF file' since version 0.9.26 # FIXME: That version still under development at the moment of writing. # Make that this statement remains true also for stable, released # versions. # It will wrap lines (doesn't matter whether long or short) with a # trailing '\', as in: # # foo.o : \ # foo.c \ # foo.h \ # # It will put a trailing '\' even on the last line, and will use leading # spaces rather than leading tabs (at least since its commit 0394caf7 # "Emit spaces for -MD"). "$@" -MD -MF "$tmpdepfile" stat=$? if test $stat -ne 0; then rm -f "$tmpdepfile" exit $stat fi rm -f "$depfile" # Each non-empty line is of the form 'foo.o : \' or ' dep.h \'. # We have to change lines of the first kind to '$object: \'. sed -e "s|.*:|$object :|" < "$tmpdepfile" > "$depfile" # And for each line of the second kind, we have to emit a 'dep.h:' # dummy dependency, to avoid the deleted-header problem. sed -n -e 's|^ *\(.*\) *\\$|\1:|p' < "$tmpdepfile" >> "$depfile" rm -f "$tmpdepfile" ;; ## The order of this option in the case statement is important, since the ## shell code in configure will try each of these formats in the order ## listed in this file. A plain '-MD' option would be understood by many ## compilers, so we must ensure this comes after the gcc and icc options. pgcc) # Portland's C compiler understands '-MD'. # Will always output deps to 'file.d' where file is the root name of the # source file under compilation, even if file resides in a subdirectory. # The object file name does not affect the name of the '.d' file. # pgcc 10.2 will output # foo.o: sub/foo.c sub/foo.h # and will wrap long lines using '\' : # foo.o: sub/foo.c ... \ # sub/foo.h ... \ # ... set_dir_from "$object" # Use the source, not the object, to determine the base name, since # that's sadly what pgcc will do too. set_base_from "$source" tmpdepfile=$base.d # For projects that build the same source file twice into different object # files, the pgcc approach of using the *source* file root name can cause # problems in parallel builds. Use a locking strategy to avoid stomping on # the same $tmpdepfile. lockdir=$base.d-lock trap " echo '$0: caught signal, cleaning up...' >&2 rmdir '$lockdir' exit 1 " 1 2 13 15 numtries=100 i=$numtries while test $i -gt 0; do # mkdir is a portable test-and-set. if mkdir "$lockdir" 2>/dev/null; then # This process acquired the lock. "$@" -MD stat=$? # Release the lock. rmdir "$lockdir" break else # If the lock is being held by a different process, wait # until the winning process is done or we timeout. while test -d "$lockdir" && test $i -gt 0; do sleep 1 i=`expr $i - 1` done fi i=`expr $i - 1` done trap - 1 2 13 15 if test $i -le 0; then echo "$0: failed to acquire lock after $numtries attempts" >&2 echo "$0: check lockdir '$lockdir'" >&2 exit 1 fi if test $stat -ne 0; then rm -f "$tmpdepfile" exit $stat fi rm -f "$depfile" # Each line is of the form `foo.o: dependent.h', # or `foo.o: dep1.h dep2.h \', or ` dep3.h dep4.h \'. # Do two passes, one to just change these to # `$object: dependent.h' and one to simply `dependent.h:'. sed "s,^[^:]*:,$object :," < "$tmpdepfile" > "$depfile" # Some versions of the HPUX 10.20 sed can't process this invocation # correctly. Breaking it into two sed invocations is a workaround. sed 's,^[^:]*: \(.*\)$,\1,;s/^\\$//;/^$/d;/:$/d' < "$tmpdepfile" \ | sed -e 's/$/ :/' >> "$depfile" rm -f "$tmpdepfile" ;; hp2) # The "hp" stanza above does not work with aCC (C++) and HP's ia64 # compilers, which have integrated preprocessors. The correct option # to use with these is +Maked; it writes dependencies to a file named # 'foo.d', which lands next to the object file, wherever that # happens to be. # Much of this is similar to the tru64 case; see comments there. set_dir_from "$object" set_base_from "$object" if test "$libtool" = yes; then tmpdepfile1=$dir$base.d tmpdepfile2=$dir.libs/$base.d "$@" -Wc,+Maked else tmpdepfile1=$dir$base.d tmpdepfile2=$dir$base.d "$@" +Maked fi stat=$? if test $stat -ne 0; then rm -f "$tmpdepfile1" "$tmpdepfile2" exit $stat fi for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" do test -f "$tmpdepfile" && break done if test -f "$tmpdepfile"; then sed -e "s,^.*\.[$lower]*:,$object:," "$tmpdepfile" > "$depfile" # Add 'dependent.h:' lines. sed -ne '2,${ s/^ *// s/ \\*$// s/$/:/ p }' "$tmpdepfile" >> "$depfile" else make_dummy_depfile fi rm -f "$tmpdepfile" "$tmpdepfile2" ;; tru64) # The Tru64 compiler uses -MD to generate dependencies as a side # effect. 'cc -MD -o foo.o ...' puts the dependencies into 'foo.o.d'. # At least on Alpha/Redhat 6.1, Compaq CCC V6.2-504 seems to put # dependencies in 'foo.d' instead, so we check for that too. # Subdirectories are respected. set_dir_from "$object" set_base_from "$object" if test "$libtool" = yes; then # Libtool generates 2 separate objects for the 2 libraries. These # two compilations output dependencies in $dir.libs/$base.o.d and # in $dir$base.o.d. We have to check for both files, because # one of the two compilations can be disabled. We should prefer # $dir$base.o.d over $dir.libs/$base.o.d because the latter is # automatically cleaned when .libs/ is deleted, while ignoring # the former would cause a distcleancheck panic. tmpdepfile1=$dir$base.o.d # libtool 1.5 tmpdepfile2=$dir.libs/$base.o.d # Likewise. tmpdepfile3=$dir.libs/$base.d # Compaq CCC V6.2-504 "$@" -Wc,-MD else tmpdepfile1=$dir$base.d tmpdepfile2=$dir$base.d tmpdepfile3=$dir$base.d "$@" -MD fi stat=$? if test $stat -ne 0; then rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" exit $stat fi for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" do test -f "$tmpdepfile" && break done # Same post-processing that is required for AIX mode. aix_post_process_depfile ;; msvc7) if test "$libtool" = yes; then showIncludes=-Wc,-showIncludes else showIncludes=-showIncludes fi "$@" $showIncludes > "$tmpdepfile" stat=$? grep -v '^Note: including file: ' "$tmpdepfile" if test $stat -ne 0; then rm -f "$tmpdepfile" exit $stat fi rm -f "$depfile" echo "$object : \\" > "$depfile" # The first sed program below extracts the file names and escapes # backslashes for cygpath. The second sed program outputs the file # name when reading, but also accumulates all include files in the # hold buffer in order to output them again at the end. This only # works with sed implementations that can handle large buffers. sed < "$tmpdepfile" -n ' /^Note: including file: *\(.*\)/ { s//\1/ s/\\/\\\\/g p }' | $cygpath_u | sort -u | sed -n ' s/ /\\ /g s/\(.*\)/'"$tab"'\1 \\/p s/.\(.*\) \\/\1:/ H $ { s/.*/'"$tab"'/ G p }' >> "$depfile" echo >> "$depfile" # make sure the fragment doesn't end with a backslash rm -f "$tmpdepfile" ;; msvc7msys) # This case exists only to let depend.m4 do its work. It works by # looking at the text of this script. This case will never be run, # since it is checked for above. exit 1 ;; #nosideeffect) # This comment above is used by automake to tell side-effect # dependency tracking mechanisms from slower ones. dashmstdout) # Important note: in order to support this mode, a compiler *must* # always write the preprocessed file to stdout, regardless of -o. "$@" || exit $? # Remove the call to Libtool. if test "$libtool" = yes; then while test "X$1" != 'X--mode=compile'; do shift done shift fi # Remove '-o $object'. IFS=" " for arg do case $arg in -o) shift ;; $object) shift ;; *) set fnord "$@" "$arg" shift # fnord shift # $arg ;; esac done test -z "$dashmflag" && dashmflag=-M # Require at least two characters before searching for ':' # in the target name. This is to cope with DOS-style filenames: # a dependency such as 'c:/foo/bar' could be seen as target 'c' otherwise. "$@" $dashmflag | sed "s|^[$tab ]*[^:$tab ][^:][^:]*:[$tab ]*|$object: |" > "$tmpdepfile" rm -f "$depfile" cat < "$tmpdepfile" > "$depfile" # Some versions of the HPUX 10.20 sed can't process this sed invocation # correctly. Breaking it into two sed invocations is a workaround. tr ' ' "$nl" < "$tmpdepfile" \ | sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' \ | sed -e 's/$/ :/' >> "$depfile" rm -f "$tmpdepfile" ;; dashXmstdout) # This case only exists to satisfy depend.m4. It is never actually # run, as this mode is specially recognized in the preamble. exit 1 ;; makedepend) "$@" || exit $? # Remove any Libtool call if test "$libtool" = yes; then while test "X$1" != 'X--mode=compile'; do shift done shift fi # X makedepend shift cleared=no eat=no for arg do case $cleared in no) set ""; shift cleared=yes ;; esac if test $eat = yes; then eat=no continue fi case "$arg" in -D*|-I*) set fnord "$@" "$arg"; shift ;; # Strip any option that makedepend may not understand. Remove # the object too, otherwise makedepend will parse it as a source file. -arch) eat=yes ;; -*|$object) ;; *) set fnord "$@" "$arg"; shift ;; esac done obj_suffix=`echo "$object" | sed 's/^.*\././'` touch "$tmpdepfile" ${MAKEDEPEND-makedepend} -o"$obj_suffix" -f"$tmpdepfile" "$@" rm -f "$depfile" # makedepend may prepend the VPATH from the source file name to the object. # No need to regex-escape $object, excess matching of '.' is harmless. sed "s|^.*\($object *:\)|\1|" "$tmpdepfile" > "$depfile" # Some versions of the HPUX 10.20 sed can't process the last invocation # correctly. Breaking it into two sed invocations is a workaround. sed '1,2d' "$tmpdepfile" \ | tr ' ' "$nl" \ | sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' \ | sed -e 's/$/ :/' >> "$depfile" rm -f "$tmpdepfile" "$tmpdepfile".bak ;; cpp) # Important note: in order to support this mode, a compiler *must* # always write the preprocessed file to stdout. "$@" || exit $? # Remove the call to Libtool. if test "$libtool" = yes; then while test "X$1" != 'X--mode=compile'; do shift done shift fi # Remove '-o $object'. IFS=" " for arg do case $arg in -o) shift ;; $object) shift ;; *) set fnord "$@" "$arg" shift # fnord shift # $arg ;; esac done "$@" -E \ | sed -n -e '/^# [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \ -e '/^#line [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \ | sed '$ s: \\$::' > "$tmpdepfile" rm -f "$depfile" echo "$object : \\" > "$depfile" cat < "$tmpdepfile" >> "$depfile" sed < "$tmpdepfile" '/^$/d;s/^ //;s/ \\$//;s/$/ :/' >> "$depfile" rm -f "$tmpdepfile" ;; msvisualcpp) # Important note: in order to support this mode, a compiler *must* # always write the preprocessed file to stdout. "$@" || exit $? # Remove the call to Libtool. if test "$libtool" = yes; then while test "X$1" != 'X--mode=compile'; do shift done shift fi IFS=" " for arg do case "$arg" in -o) shift ;; $object) shift ;; "-Gm"|"/Gm"|"-Gi"|"/Gi"|"-ZI"|"/ZI") set fnord "$@" shift shift ;; *) set fnord "$@" "$arg" shift shift ;; esac done "$@" -E 2>/dev/null | sed -n '/^#line [0-9][0-9]* "\([^"]*\)"/ s::\1:p' | $cygpath_u | sort -u > "$tmpdepfile" rm -f "$depfile" echo "$object : \\" > "$depfile" sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::'"$tab"'\1 \\:p' >> "$depfile" echo "$tab" >> "$depfile" sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::\1\::p' >> "$depfile" rm -f "$tmpdepfile" ;; msvcmsys) # This case exists only to let depend.m4 do its work. It works by # looking at the text of this script. This case will never be run, # since it is checked for above. exit 1 ;; none) exec "$@" ;; *) echo "Unknown depmode $depmode" 1>&2 exit 1 ;; esac exit 0 # Local Variables: # mode: shell-script # sh-indentation: 2 # eval: (add-hook 'before-save-hook 'time-stamp) # time-stamp-start: "scriptversion=" # time-stamp-format: "%:y-%02m-%02d.%02H" # time-stamp-time-zone: "UTC0" # time-stamp-end: "; # UTC" # End: