]> git.tuebingen.mpg.de Git - paraslash.git/commitdiff
paraslash 0.7.3 master v0.7.3
authorAndre Noll <maan@tuebingen.mpg.de>
Tue, 12 Mar 2024 18:02:39 +0000 (19:02 +0100)
committerAndre Noll <maan@tuebingen.mpg.de>
Tue, 12 Mar 2024 18:02:39 +0000 (19:02 +0100)
148 files changed:
.gitignore
Doxyfile
INSTALL
Makefile.in
Makefile.real
NEWS.md
aac_afh.c
aacdec_filter.c
acl.c
afh.c
afh.h
afh_common.c
afh_recv.c
afs.c
afs.h
aft.c
alsa_mix.c
alsa_write.c
amp_filter.c
ao_write.c
attribute.c
audioc.c
audiod.c
audiod.h
audiod_command.c
autogen.sh
base64.c
bitstream.c
blob.c
buffer_tree.c
check_wav.c
check_wav.h
chunk_queue.c
client.c
client_common.c
close_on_fork.c
command.c
compress_filter.c
configure.ac
crypt.h
crypt_common.c
daemon.c
daemon.h
dccp_recv.c
dccp_send.c
error.h
fd.c
fd.h
fec.c
fecdec_filter.c
file_write.c
filter.c
filter.h
filter_common.c
flac_afh.c
flacdec_filter.c
gcrypt.c
grab_client.c
gui.c
gui_theme.c
http_recv.c
http_send.c
imdct.c
interactive.c
interactive.h
list.h
m4/lls/audiod_cmd.suite.m4
m4/lls/include/com_ll.m4 [new file with mode: 0644]
m4/lls/makefile
m4/lls/mixer.suite.m4
m4/lls/play.suite.m4
m4/lls/server.suite.m4
m4/lls/server_cmd.suite.m4
m4/lls/upgrade_db.suite.m4 [new file with mode: 0644]
mixer.c
mm.c [deleted file]
mm.h [deleted file]
mood.c
mood.h [deleted file]
mp.c
mp.h
mp3_afh.c
mp3dec_filter.c
mp4.c [new file with mode: 0644]
mp4.h [new file with mode: 0644]
net.c
net.h
ogg_afh_common.c
ogg_afh_common.h
oggdec_filter.c
openssl.c
opus_afh.c
opusdec_filter.c
oss_mix.c
oss_write.c
para.h
play.c
playlist.c
prebuffer_filter.c
recv.c
recv.h
recv_common.c
resample_filter.c
ringbuffer.c
sched.c
sched.h
score.c
send.h
send_common.c
server.c
server.h
sideband.c
signal.c
signal.h
spx_afh.c
spxdec_filter.c
stdin.c
stdout.c
string.c
string.h
sync_filter.c
t/audio_files/short-44100-2.mp3 [new file with mode: 0644]
t/t0004-server.sh
t/test-lib.sh
udp_recv.c
udp_send.c
upgrade_db.c [new file with mode: 0644]
user_list.c
vss.c
wav_filter.c
web/about.in.html
web/documentation.in.html
web/download.in.html
web/footer.html
web/header.html
web/header2.html
web/images/paraslash.ico
web/images/paraslash.png [deleted file]
web/images/paraslash.svg [new file with mode: 0644]
web/manual.md
web/para.css
wma_afh.c
wmadec_filter.c
write.c
write.h
write_common.c
yy/mp.lex
yy/mp.y

index 10d2572d926ab11ec8de95fa32649aa00f4bf1c6..8f8d0af736cee205b638502171eea526d613cc22 100644 (file)
@@ -16,7 +16,6 @@ Makefile
 TODO
 paraslash-*.tar.bz2
 paraslash-*.tar.xz
-web/dia/overview.pdf
 *.swp
 *.rej
 *~
@@ -25,3 +24,4 @@ confdefs.h
 conftest
 conftest.c
 git-version.h
+*-local*
index b11683e424443baef8754b34097ad3cb8dbed4fe..58e8023973d510edbe95c74148b55b5ce6aa1796 100644 (file)
--- a/Doxyfile
+++ b/Doxyfile
@@ -1,4 +1,4 @@
-# Doxyfile 1.8.11
+# Doxyfile 1.8.17
 
 # This file describes the settings to be used by the documentation system
 # doxygen (www.doxygen.org) for a project.
 # Project related configuration options
 #---------------------------------------------------------------------------
 
-# This tag specifies the encoding used for all characters in the config file
-# that follow. The default is UTF-8 which is also the encoding used for all text
-# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv
-# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv
-# for the list of possible encodings.
+# This tag specifies the encoding used for all characters in the configuration
+# file that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# https://www.gnu.org/software/libiconv/ for the list of possible encodings.
 # The default value is: UTF-8.
 
 DOXYFILE_ENCODING      = UTF-8
@@ -93,6 +93,14 @@ ALLOW_UNICODE_NAMES    = NO
 
 OUTPUT_LANGUAGE        = English
 
+# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all generated output in the proper direction.
+# Possible values are: None, LTR, RTL and Context.
+# The default value is: None.
+
+OUTPUT_TEXT_DIRECTION  = None
+
 # If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
 # descriptions after the members that are listed in the file and class
 # documentation (similar to Javadoc). Set to NO to disable this.
@@ -179,6 +187,16 @@ SHORT_NAMES            = NO
 
 JAVADOC_AUTOBRIEF      = YES
 
+# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line
+# such as
+# /***************
+# as being the beginning of a Javadoc-style comment "banner". If set to NO, the
+# Javadoc-style will behave just like regular comments and it will not be
+# interpreted by doxygen.
+# The default value is: NO.
+
+JAVADOC_BANNER         = NO
+
 # If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
 # line (until the first dot) of a Qt-style comment as the brief description. If
 # set to NO, the Qt-style will behave just like regular Qt-style comments (thus
@@ -226,16 +244,15 @@ TAB_SIZE               = 8
 # will allow you to put the command \sideeffect (or @sideeffect) in the
 # documentation, which will result in a user-defined paragraph with heading
 # "Side Effects:". You can put \n's in the value part of an alias to insert
-# newlines.
+# newlines (in the resulting output). You can put ^^ in the value part of an
+# alias to insert a newline as if a physical newline was in the original file.
+# When you need a literal { or } or , in the value part of an alias you have to
+# escape them by means of a backslash (\), this can lead to conflicts with the
+# commands \{ and \} for these it is advised to use the version @{ and @} or use
+# a double escape (\\{ and \\})
 
 ALIASES                =
 
-# This tag can be used to specify a number of word-keyword mappings (TCL only).
-# A mapping has the form "name=value". For example adding "class=itcl::class"
-# will allow you to use the command class in the itcl::class meaning.
-
-TCL_SUBST              =
-
 # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
 # only. Doxygen will then generate output that is more tailored for C. For
 # instance, some of the names that are used will be different. The list of all
@@ -264,17 +281,26 @@ OPTIMIZE_FOR_FORTRAN   = NO
 
 OPTIMIZE_OUTPUT_VHDL   = NO
 
+# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice
+# sources only. Doxygen will then generate output that is more tailored for that
+# language. For instance, namespaces will be presented as modules, types will be
+# separated into more groups, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_SLICE  = NO
+
 # Doxygen selects the parser to use depending on the extension of the files it
 # parses. With this tag you can assign which parser to use for a given
 # extension. Doxygen has a built-in mapping, but you can override or extend it
 # using this tag. The format is ext=language, where ext is a file extension, and
-# language is one of the parsers supported by doxygen: IDL, Java, Javascript,
-# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran:
-# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran:
-# Fortran. In the later case the parser tries to guess whether the code is fixed
-# or free formatted code, this is the default for Fortran type files), VHDL. For
-# instance to make doxygen treat .inc files as Fortran files (default is PHP),
-# and .f files as C (default is Fortran), use: inc=Fortran f=C.
+# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,
+# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice,
+# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
+# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser
+# tries to guess whether the code is fixed or free formatted code, this is the
+# default for Fortran type files), VHDL, tcl. For instance to make doxygen treat
+# .inc files as Fortran files (default is PHP), and .f files as C (default is
+# Fortran), use: inc=Fortran f=C.
 #
 # Note: For files without extension you can use no_extension as a placeholder.
 #
@@ -285,7 +311,7 @@ EXTENSION_MAPPING      =
 
 # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
 # according to the Markdown format, which allows for more readable
-# documentation. See http://daringfireball.net/projects/markdown/ for details.
+# documentation. See https://daringfireball.net/projects/markdown/ for details.
 # The output of markdown processing is further processed by doxygen, so you can
 # mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
 # case of backward compatibilities issues.
@@ -293,6 +319,15 @@ EXTENSION_MAPPING      =
 
 MARKDOWN_SUPPORT       = YES
 
+# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up
+# to that level are automatically included in the table of contents, even if
+# they do not have an id attribute.
+# Note: This feature currently applies only to Markdown headings.
+# Minimum value: 0, maximum value: 99, default value: 5.
+# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
+
+TOC_INCLUDE_HEADINGS   = 5
+
 # When enabled doxygen tries to link words that correspond to documented
 # classes, or namespaces to their corresponding documentation. Such a link can
 # be prevented in individual cases by putting a % sign in front of the word or
@@ -318,7 +353,7 @@ BUILTIN_STL_SUPPORT    = NO
 CPP_CLI_SUPPORT        = NO
 
 # Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
-# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen
+# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen
 # will parse them like normal C++ but will assume all classes use public instead
 # of private inheritance when no explicit protection keyword is present.
 # The default value is: NO.
@@ -424,6 +459,12 @@ EXTRACT_ALL            = YES
 
 EXTRACT_PRIVATE        = NO
 
+# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual
+# methods of a class will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIV_VIRTUAL   = NO
+
 # If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
 # scope will be included in the documentation.
 # The default value is: NO.
@@ -478,8 +519,8 @@ HIDE_UNDOC_MEMBERS     = NO
 HIDE_UNDOC_CLASSES     = NO
 
 # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
-# (class|struct|union) declarations. If set to NO, these declarations will be
-# included in the documentation.
+# declarations. If set to NO, these declarations will be included in the
+# documentation.
 # The default value is: NO.
 
 HIDE_FRIEND_COMPOUNDS  = NO
@@ -502,7 +543,7 @@ INTERNAL_DOCS          = NO
 # names in lower-case letters. If set to YES, upper-case letters are also
 # allowed. This is useful if you have classes or files whose names only differ
 # in case and if your file system supports case sensitive file names. Windows
-# and Mac users are advised to set this option to NO.
+# (including Cygwin) ands Mac users are advised to set this option to NO.
 # The default value is: system dependent.
 
 CASE_SENSE_NAMES       = YES
@@ -689,7 +730,7 @@ LAYOUT_FILE            =
 # The CITE_BIB_FILES tag can be used to specify one or more bib files containing
 # the reference definitions. This must be a list of .bib files. The .bib
 # extension is automatically appended if omitted. This requires the bibtex tool
-# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info.
+# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info.
 # For LaTeX the style of the bibliography can be controlled using
 # LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
 # search path. See also \cite for info how to create references.
@@ -734,7 +775,8 @@ WARN_IF_DOC_ERROR      = YES
 # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
 # are documented, but have no documentation for their parameters or return
 # value. If set to NO, doxygen will only warn about wrong or incomplete
-# parameter documentation, but not about the absence of documentation.
+# parameter documentation, but not about the absence of documentation. If
+# EXTRACT_ALL is set to YES then this flag will automatically be disabled.
 # The default value is: NO.
 
 WARN_NO_PARAMDOC       = YES
@@ -776,7 +818,7 @@ INPUT                  = .
 # This tag can be used to specify the character encoding of the source files
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
 # libiconv (or the iconv built into libc) for the transcoding. See the libiconv
-# documentation (see: http://www.gnu.org/software/libiconv) for the list of
+# documentation (see: https://www.gnu.org/software/libiconv/) for the list of
 # possible encodings.
 # The default value is: UTF-8.
 
@@ -793,8 +835,10 @@ INPUT_ENCODING         = UTF-8
 # If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
 # *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
 # *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
-# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f, *.for, *.tcl,
-# *.vhd, *.vhdl, *.ucf, *.qsf, *.as and *.js.
+# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment),
+# *.doc (to be provided as doxygen C comment), *.txt (to be provided as doxygen
+# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f, *.for, *.tcl, *.vhd,
+# *.vhdl, *.ucf, *.qsf and *.ice.
 
 FILE_PATTERNS          = *.c \
                          *.h
@@ -950,7 +994,7 @@ INLINE_SOURCES         = NO
 STRIP_CODE_COMMENTS    = YES
 
 # If the REFERENCED_BY_RELATION tag is set to YES then for each documented
-# function all documented functions referencing it will be listed.
+# entity all documented functions referencing it will be listed.
 # The default value is: NO.
 
 REFERENCED_BY_RELATION = YES
@@ -982,12 +1026,12 @@ SOURCE_TOOLTIPS        = YES
 # If the USE_HTAGS tag is set to YES then the references to source code will
 # point to the HTML generated by the htags(1) tool instead of doxygen built-in
 # source browser. The htags tool is part of GNU's global source tagging system
-# (see http://www.gnu.org/software/global/global.html). You will need version
+# (see https://www.gnu.org/software/global/global.html). You will need version
 # 4.8.6 or higher.
 #
 # To use it do the following:
 # - Install the latest version of global
-# - Enable SOURCE_BROWSER and USE_HTAGS in the config file
+# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file
 # - Make sure the INPUT points to the root of the source tree
 # - Run doxygen as normal
 #
@@ -1127,7 +1171,7 @@ HTML_EXTRA_FILES       =
 # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
 # will adjust the colors in the style sheet and background images according to
 # this color. Hue is specified as an angle on a colorwheel, see
-# http://en.wikipedia.org/wiki/Hue for more information. For instance the value
+# https://en.wikipedia.org/wiki/Hue for more information. For instance the value
 # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
 # purple, and 360 is red again.
 # Minimum value: 0, maximum value: 359, default value: 220.
@@ -1163,6 +1207,17 @@ HTML_COLORSTYLE_GAMMA  = 80
 
 HTML_TIMESTAMP         = YES
 
+# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML
+# documentation will contain a main index with vertical navigation menus that
+# are dynamically created via JavaScript. If disabled, the navigation index will
+# consists of multiple levels of tabs that are statically embedded in every HTML
+# page. Disable this option to support browsers that do not have JavaScript,
+# like the Qt help browser.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_MENUS     = YES
+
 # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
 # documentation will contain sections that can be hidden and shown after the
 # page has loaded.
@@ -1186,13 +1241,13 @@ HTML_INDEX_NUM_ENTRIES = 100
 
 # If the GENERATE_DOCSET tag is set to YES, additional index files will be
 # generated that can be used as input for Apple's Xcode 3 integrated development
-# environment (see: http://developer.apple.com/tools/xcode/), introduced with
-# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a
+# environment (see: https://developer.apple.com/xcode/), introduced with OSX
+# 10.5 (Leopard). To create a documentation set, doxygen will generate a
 # Makefile in the HTML output directory. Running make will produce the docset in
 # that directory and running make install will install the docset in
 # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
-# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
-# for more information.
+# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy
+# genXcode/_index.html for more information.
 # The default value is: NO.
 # This tag requires that the tag GENERATE_HTML is set to YES.
 
@@ -1231,7 +1286,7 @@ DOCSET_PUBLISHER_NAME  = Publisher
 # If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
 # additional HTML index files: index.hhp, index.hhc, and index.hhk. The
 # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
-# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on
+# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on
 # Windows.
 #
 # The HTML Help Workshop contains a compiler that can convert all HTML output
@@ -1307,7 +1362,7 @@ QCH_FILE               =
 
 # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
 # Project output. For more information please see Qt Help Project / Namespace
-# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace).
+# (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).
 # The default value is: org.doxygen.Project.
 # This tag requires that the tag GENERATE_QHP is set to YES.
 
@@ -1315,7 +1370,7 @@ QHP_NAMESPACE          = org.doxygen.Project
 
 # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
 # Help Project output. For more information please see Qt Help Project / Virtual
-# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual-
+# Folders (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-
 # folders).
 # The default value is: doc.
 # This tag requires that the tag GENERATE_QHP is set to YES.
@@ -1324,7 +1379,7 @@ QHP_VIRTUAL_FOLDER     = doc
 
 # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
 # filter to add. For more information please see Qt Help Project / Custom
-# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-
 # filters).
 # This tag requires that the tag GENERATE_QHP is set to YES.
 
@@ -1332,7 +1387,7 @@ QHP_CUST_FILTER_NAME   =
 
 # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
 # custom filter to add. For more information please see Qt Help Project / Custom
-# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-
 # filters).
 # This tag requires that the tag GENERATE_QHP is set to YES.
 
@@ -1340,7 +1395,7 @@ QHP_CUST_FILTER_ATTRS  =
 
 # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
 # project's filter section matches. Qt Help Project / Filter Attributes (see:
-# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes).
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes).
 # This tag requires that the tag GENERATE_QHP is set to YES.
 
 QHP_SECT_FILTER_ATTRS  =
@@ -1433,7 +1488,7 @@ EXT_LINKS_IN_WINDOW    = NO
 
 FORMULA_FONTSIZE       = 10
 
-# Use the FORMULA_TRANPARENT tag to determine whether or not the images
+# Use the FORMULA_TRANSPARENT tag to determine whether or not the images
 # generated for formulas are transparent PNGs. Transparent PNGs are not
 # supported properly for IE 6.0, but are supported on all modern browsers.
 #
@@ -1444,8 +1499,14 @@ FORMULA_FONTSIZE       = 10
 
 FORMULA_TRANSPARENT    = YES
 
+# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands
+# to create new LaTeX commands to be used in formulas as building blocks. See
+# the section "Including formulas" for details.
+
+FORMULA_MACROFILE      =
+
 # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
-# http://www.mathjax.org) which uses client side Javascript for the rendering
+# https://www.mathjax.org) which uses client side JavaScript for the rendering
 # instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
 # installed or if you want to formulas look prettier in the HTML output. When
 # enabled you may also need to install MathJax separately and configure the path
@@ -1472,8 +1533,8 @@ MATHJAX_FORMAT         = HTML-CSS
 # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
 # Content Delivery Network so you can quickly see the result without installing
 # MathJax. However, it is strongly recommended to install a local copy of
-# MathJax from http://www.mathjax.org before deployment.
-# The default value is: http://cdn.mathjax.org/mathjax/latest.
+# MathJax from https://www.mathjax.org before deployment.
+# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/.
 # This tag requires that the tag USE_MATHJAX is set to YES.
 
 MATHJAX_RELPATH        = http://cdn.mathjax.org/mathjax/latest
@@ -1515,7 +1576,7 @@ MATHJAX_CODEFILE       =
 SEARCHENGINE           = NO
 
 # When the SERVER_BASED_SEARCH tag is enabled the search engine will be
-# implemented using a web server instead of a web client using Javascript. There
+# implemented using a web server instead of a web client using JavaScript. There
 # are two flavors of web server based searching depending on the EXTERNAL_SEARCH
 # setting. When disabled, doxygen will generate a PHP script for searching and
 # an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
@@ -1534,7 +1595,7 @@ SERVER_BASED_SEARCH    = NO
 #
 # Doxygen ships with an example indexer (doxyindexer) and search engine
 # (doxysearch.cgi) which are based on the open source search engine library
-# Xapian (see: http://xapian.org/).
+# Xapian (see: https://xapian.org/).
 #
 # See the section "External Indexing and Searching" for details.
 # The default value is: NO.
@@ -1547,7 +1608,7 @@ EXTERNAL_SEARCH        = NO
 #
 # Doxygen ships with an example indexer (doxyindexer) and search engine
 # (doxysearch.cgi) which are based on the open source search engine library
-# Xapian (see: http://xapian.org/). See the section "External Indexing and
+# Xapian (see: https://xapian.org/). See the section "External Indexing and
 # Searching" for details.
 # This tag requires that the tag SEARCHENGINE is set to YES.
 
@@ -1599,21 +1660,35 @@ LATEX_OUTPUT           = latex
 # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
 # invoked.
 #
-# Note that when enabling USE_PDFLATEX this option is only used for generating
-# bitmaps for formulas in the HTML output, but not in the Makefile that is
-# written to the output directory.
-# The default file is: latex.
+# Note that when not enabling USE_PDFLATEX the default is latex when enabling
+# USE_PDFLATEX the default is pdflatex and when in the later case latex is
+# chosen this is overwritten by pdflatex. For specific output languages the
+# default can have been set differently, this depends on the implementation of
+# the output language.
 # This tag requires that the tag GENERATE_LATEX is set to YES.
 
 LATEX_CMD_NAME         = latex
 
 # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
 # index for LaTeX.
+# Note: This tag is used in the Makefile / make.bat.
+# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file
+# (.tex).
 # The default file is: makeindex.
 # This tag requires that the tag GENERATE_LATEX is set to YES.
 
 MAKEINDEX_CMD_NAME     = makeindex
 
+# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to
+# generate index for LaTeX. In case there is no backslash (\) as first character
+# it will be automatically added in the LaTeX code.
+# Note: This tag is used in the generated output file (.tex).
+# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat.
+# The default value is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_MAKEINDEX_CMD    = makeindex
+
 # If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
 # documents. This may be useful for small projects and may help to save some
 # trees in general.
@@ -1629,7 +1704,7 @@ COMPACT_LATEX          = NO
 # The default value is: a4.
 # This tag requires that the tag GENERATE_LATEX is set to YES.
 
-PAPER_TYPE             = a4wide
+PAPER_TYPE             = a4
 
 # The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
 # that should be included in the LaTeX output. The package can be specified just
@@ -1734,7 +1809,7 @@ LATEX_SOURCE_CODE      = NO
 
 # The LATEX_BIB_STYLE tag can be used to specify the style to use for the
 # bibliography, e.g. plainnat, or ieeetr. See
-# http://en.wikipedia.org/wiki/BibTeX and \cite for more info.
+# https://en.wikipedia.org/wiki/BibTeX and \cite for more info.
 # The default value is: plain.
 # This tag requires that the tag GENERATE_LATEX is set to YES.
 
@@ -1748,6 +1823,14 @@ LATEX_BIB_STYLE        = plain
 
 LATEX_TIMESTAMP        = NO
 
+# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute)
+# path from which the emoji images will be read. If a relative path is entered,
+# it will be relative to the LATEX_OUTPUT directory. If left blank the
+# LATEX_OUTPUT directory will be used.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EMOJI_DIRECTORY  =
+
 #---------------------------------------------------------------------------
 # Configuration options related to the RTF output
 #---------------------------------------------------------------------------
@@ -1787,9 +1870,9 @@ COMPACT_RTF            = NO
 
 RTF_HYPERLINKS         = NO
 
-# Load stylesheet definitions from file. Syntax is similar to doxygen's config
-# file, i.e. a series of assignments. You only have to provide replacements,
-# missing definitions are set to their default value.
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# configuration file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
 #
 # See also section "Doxygen usage" for information on how to generate the
 # default style sheet that doxygen normally uses.
@@ -1798,8 +1881,8 @@ RTF_HYPERLINKS         = NO
 RTF_STYLESHEET_FILE    =
 
 # Set optional variables used in the generation of an RTF document. Syntax is
-# similar to doxygen's config file. A template extensions file can be generated
-# using doxygen -e rtf extensionFile.
+# similar to doxygen's configuration file. A template extensions file can be
+# generated using doxygen -e rtf extensionFile.
 # This tag requires that the tag GENERATE_RTF is set to YES.
 
 RTF_EXTENSIONS_FILE    =
@@ -1885,6 +1968,13 @@ XML_OUTPUT             = xml
 
 XML_PROGRAMLISTING     = YES
 
+# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include
+# namespace members in file scope as well, matching the HTML output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_NS_MEMB_FILE_SCOPE = NO
+
 #---------------------------------------------------------------------------
 # Configuration options related to the DOCBOOK output
 #---------------------------------------------------------------------------
@@ -1917,9 +2007,9 @@ DOCBOOK_PROGRAMLISTING = NO
 #---------------------------------------------------------------------------
 
 # If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
-# AutoGen Definitions (see http://autogen.sf.net) file that captures the
-# structure of the code including all documentation. Note that this feature is
-# still experimental and incomplete at the moment.
+# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures
+# the structure of the code including all documentation. Note that this feature
+# is still experimental and incomplete at the moment.
 # The default value is: NO.
 
 GENERATE_AUTOGEN_DEF   = NO
@@ -2019,8 +2109,7 @@ INCLUDE_FILE_PATTERNS  =
 # recursively expanded use the := operator instead of the = operator.
 # This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
 
-PREDEFINED             = __GNUC__=4 \
-                         __GNUC_MINOR__=4
+PREDEFINED             =
 
 # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
 # tag can be used to specify a list of macro names that should be expanded. The
@@ -2087,12 +2176,6 @@ EXTERNAL_GROUPS        = YES
 
 EXTERNAL_PAGES         = YES
 
-# The PERL_PATH should be the absolute path and name of the perl script
-# interpreter (i.e. the result of 'which perl').
-# The default file (with absolute path) is: /usr/bin/perl.
-
-PERL_PATH              = /usr/bin/perl
-
 #---------------------------------------------------------------------------
 # Configuration options related to the dot tool
 #---------------------------------------------------------------------------
@@ -2106,15 +2189,6 @@ PERL_PATH              = /usr/bin/perl
 
 CLASS_DIAGRAMS         = YES
 
-# You can define message sequence charts within doxygen comments using the \msc
-# command. Doxygen will then run the mscgen tool (see:
-# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the
-# documentation. The MSCGEN_PATH tag allows you to specify the directory where
-# the mscgen tool resides. If left empty the tool is assumed to be found in the
-# default search path.
-
-MSCGEN_PATH            =
-
 # You can include diagrams made with dia in doxygen documentation. Doxygen will
 # then run dia to produce the diagram and insert it in the documentation. The
 # DIA_PATH tag allows you to specify the directory where the dia binary resides.
@@ -2342,6 +2416,11 @@ DIAFILE_DIRS           =
 
 PLANTUML_JAR_PATH      =
 
+# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a
+# configuration file for plantuml.
+
+PLANTUML_CFG_FILE      =
+
 # When using plantuml, the specified paths are searched for files specified by
 # the !include statement in a plantuml block.
 
diff --git a/INSTALL b/INSTALL
index 4a86e967ee72955a32ed37e2fd88b849f808053e..45676b7ea44d0bb5cfc854272bdcbd5cf7a2e3a0 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -18,7 +18,7 @@ Installing paraslash from tarball
 
 Installing paraslash from git
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-       autoconf && autoheader && make && sudo make install
+       autoconf && autoheader && ./configure && make && sudo make install
 
 Example for cross-compiling
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
index d4a83a776eec0dfd602aa4329a72718cc15ea7f4..c618561d4dfeb18ebeb31f93a90ac69e32f84c63 100644 (file)
@@ -23,6 +23,7 @@ audiod_objs := @audiod_objs@
 audioc_objs := @audioc_objs@
 mixer_objs := @mixer_objs@
 server_objs := @server_objs@
+upgrade_db_objs := @upgrade_db_objs@
 write_objs := @write_objs@
 afh_objs := @afh_objs@
 play_objs := @play_objs@
@@ -67,4 +68,6 @@ curses_ldflags := @curses_ldflags@
 crypto_ldflags := @crypto_ldflags@
 iconv_ldflags := @iconv_ldflags@
 
+ENABLE_UBSAN := @ENABLE_UBSAN@
+
 include Makefile.real
index 7d4eff4fe2eaecc99fb83627a154b9c1672118ca..bd2bd9d95fe8356f09e756268340f2f4cad52706 100644 (file)
@@ -10,6 +10,7 @@ endif
 .SHELLFLAGS := -ec
 
 LOGLEVELS := LL_DEBUG,LL_INFO,LL_NOTICE,LL_WARNING,LL_ERROR,LL_CRIT,LL_EMERG
+SEVERITIES := \"debug\",\"info\",\"notice\",\"warning\",\"error\",\"crit\",\"emerg\"
 vardir := /var/paraslash
 mandir := $(datarootdir)/man/man1
 MKDIR_P := mkdir -p
@@ -20,7 +21,7 @@ uname_s := $(shell uname -s 2>/dev/null || echo "UNKNOWN_OS")
 uname_rs := $(shell uname -rs)
 cc_version := $(shell $(CC) --version | head -n 1)
 GIT_VERSION := $(shell ./GIT-VERSION-GEN git-version.h)
-COPYRIGHT_YEAR := 2019
+COPYRIGHT_YEAR := 2024
 
 ifeq ("$(origin O)", "command line")
        build_dir := $(O)
@@ -55,6 +56,7 @@ gui_objs += gui.lsg.o
 play_objs += $(addsuffix _cmd.lsg.o, recv filter play write) play.lsg.o
 recv_objs += recv_cmd.lsg.o recv.lsg.o
 server_objs += server_cmd.lsg.o server.lsg.o
+upgrade_db_objs += upgrade_db.lsg.o
 write_objs += write_cmd.lsg.o write.lsg.o
 
 cmd_suites := $(addsuffix _cmd, audiod server play recv filter write)
@@ -77,6 +79,7 @@ audiod_objs := $(addprefix $(object_dir)/, $(audiod_objs))
 audioc_objs := $(addprefix $(object_dir)/, $(audioc_objs))
 mixer_objs := $(addprefix $(object_dir)/, $(mixer_objs))
 server_objs := $(addprefix $(object_dir)/, $(server_objs))
+upgrade_db_objs := $(addprefix $(object_dir)/, $(upgrade_db_objs))
 write_objs := $(addprefix $(object_dir)/, $(write_objs))
 afh_objs := $(addprefix $(object_dir)/, $(afh_objs))
 play_objs := $(addprefix $(object_dir)/, $(play_objs))
@@ -110,13 +113,16 @@ CPPFLAGS += -DBINDIR='"$(bindir)"'
 CPPFLAGS += -DCOPYRIGHT_YEAR='"$(COPYRIGHT_YEAR)"'
 CPPFLAGS += -DBUILD_DATE='"$(build_date)"'
 CPPFLAGS += -DLOGLEVELS='$(LOGLEVELS)'
+CPPFLAGS += -DSEVERITIES=$(SEVERITIES)
 CPPFLAGS += -DUNAME_RS='"$(uname_rs)"'
 CPPFLAGS += -DCC_VERSION='"$(cc_version)"'
 CPPFLAGS += -I$(lls_suite_dir)
 CPPFLAGS += -I$(yy_build_dir)
 CPPFLAGS += $(lopsub_cppflags)
+CPPFLAGS += -Wunused-macros
 
 STRICT_CFLAGS += -fno-strict-aliasing
+STRICT_CFLAGS += -ftrapv
 STRICT_CFLAGS += -g
 STRICT_CFLAGS += -Os
 STRICT_CFLAGS += -Wundef -W -Wuninitialized
@@ -128,15 +134,20 @@ STRICT_CFLAGS += -Wredundant-decls
 STRICT_CFLAGS += -Wno-sign-compare -Wno-unknown-pragmas
 STRICT_CFLAGS += -Wdeclaration-after-statement
 STRICT_CFLAGS += -Wformat -Wformat-security -Wmissing-format-attribute
+STRICT_CFLAGS += -fdata-sections -ffunction-sections
+STRICT_CFLAGS += -Wstrict-prototypes
+STRICT_CFLAGS += -Wshadow
+
+LDFLAGS += -Wl,--gc-sections
+
+ifeq ($(ENABLE_UBSAN), yes)
+       STRICT_CFLAGS += -fsanitize=undefined
+       LDFLAGS += -lubsan
+endif
 
 ifeq ($(uname_s),Linux)
        # these cause warnings on *BSD
-       CPPFLAGS += -Wunused-macros
-       STRICT_CFLAGS += -fdata-sections -ffunction-sections
-       STRICT_CFLAGS += -Wstrict-prototypes
-       STRICT_CFLAGS += -Wshadow
        STRICT_CFLAGS += -Wunused -Wall
-       LDFLAGS += -Wl,--gc-sections
 endif
 
 cc-option = $(shell \
@@ -146,6 +157,7 @@ cc-option = $(shell \
 
 STRICT_CFLAGS += $(call cc-option, -Wformat-signedness)
 STRICT_CFLAGS += $(call cc-option, -Wdiscarded-qualifiers)
+STRICT_CFLAGS += $(call cc-option, -Wsuggest-attribute=malloc)
 
 # To put more focus on warnings, be less verbose as default
 # Use 'make V=1' to see the full commands
@@ -185,79 +197,54 @@ $(man_dir)/para_%.1: $(lls_suite_dir)/%.lsg.man \
 
 $(object_dir)/%.o: %.c | $(object_dir)
 
-$(object_dir)/opus%.o: CPPFLAGS += $(opus_cppflags)
-$(object_dir)/gui.o $(object_dir)/gui%.o \
-: CPPFLAGS += $(curses_cppflags)
-$(object_dir)/spx%.o: CPPFLAGS += $(speex_cppflags)
-$(object_dir)/flac%.o: CPPFLAGS += $(flac_cppflags)
-
-$(object_dir)/mp3_afh.o: CPPFLAGS += $(id3tag_cppflags)
-$(object_dir)/openssl.o: CPPFLAGS += $(openssl_cppflags)
-$(object_dir)/gcrypt.o: CPPFLAGS += $(gcrypt_cppflags)
-$(object_dir)/ao_write.o: CPPFLAGS += $(ao_cppflags)
-$(object_dir)/alsa%.o: CPPFLAGS += $(alsa_cppflags)
-
-$(object_dir)/interactive.o \
-: CPPFLAGS += $(readline_cppflags)
-
-$(object_dir)/resample_filter.o \
-: CPPFLAGS += $(samplerate_cppflags)
-
-$(object_dir)/oss_write.o \
-: CPPFLAGS += $(oss_cppflags)
-
-$(object_dir)/ao_write.o \
-: CPPFLAGS += $(ao_cppflags) $(pthread_cppflags)
-
-$(object_dir)/mp3dec_filter.o \
-: CPPFLAGS += $(mad_cppflags)
-
-$(object_dir)/aacdec_filter.o \
-$(object_dir)/aac_afh.o \
-: CPPFLAGS += $(faad_cppflags)
-
-$(object_dir)/ogg_afh.o \
-$(object_dir)/oggdec_filter.o \
-: CPPFLAGS += $(vorbis_cppflags)
-
-$(object_dir)/spx_common.o \
-$(object_dir)/spxdec_filter.o \
-$(object_dir)/spx_afh.o \
-$(object_dir)/oggdec_filter.o \
-$(object_dir)/ogg_afh.o \
-$(object_dir)/ogg_afh_common.o \
-$(object_dir)/opus%.o \
-: CPPFLAGS += $(ogg_cppflags)
-
-$(object_dir)/afs.o \
-$(object_dir)/aft.o \
-$(object_dir)/attribute.o \
-$(object_dir)/blob.o  \
-$(object_dir)/mood.o \
-$(object_dir)/playlist.o \
-$(object_dir)/score.o \
-$(object_dir)/server.o \
-$(object_dir)/vss.o \
-$(object_dir)/command.o \
-$(object_dir)/http_send.o \
-$(object_dir)/dccp_send.o \
-$(object_dir)/udp_send.o \
-$(object_dir)/send_common.o \
-$(object_dir)/mm.o \
-: CPPFLAGS += $(osl_cppflags)
-
-$(object_dir)/compress_filter.o: CFLAGS += -O3
+OD = $(addsuffix .d, $(addprefix $(dep_dir)/, $(1))) \
+       $(addsuffix .o, $(addprefix $(object_dir)/, $(1)))
+
+$(call OD, opus%): CPPFLAGS += $(opus_cppflags)
+$(call OD, gui gui%): CPPFLAGS += $(curses_cppflags)
+$(call OD, spx%): CPPFLAGS += $(speex_cppflags)
+$(call OD, flac%): CPPFLAGS += $(flac_cppflags)
+$(call OD, mp3_afh): CPPFLAGS += $(id3tag_cppflags)
+$(call OD, openssl): CPPFLAGS += $(openssl_cppflags)
+$(call OD, gcrypt): CPPFLAGS += $(gcrypt_cppflags)
+$(call OD, ao_write): CPPFLAGS += $(ao_cppflags)
+$(call OD, alsa%): CPPFLAGS += $(alsa_cppflags)
+$(call OD, interactive): CPPFLAGS += $(readline_cppflags)
+$(call OD, resample_filter): CPPFLAGS += $(samplerate_cppflags)
+$(call OD, oss_write): CPPFLAGS += $(oss_cppflags)
+$(call OD, ao_write): CPPFLAGS += $(ao_cppflags) $(pthread_cppflags)
+$(call OD, mp3dec_filter): CPPFLAGS += $(mad_cppflags)
+$(call OD, aacdec_filter aac_afh): CPPFLAGS += $(faad_cppflags)
+$(call OD, ogg_afh oggdec_filter): CPPFLAGS += $(vorbis_cppflags)
+$(call OD, spx_common spxdec_filter spx_afh oggdec_filter ogg_afh \
+       ogg_afh_common opus%): CPPFLAGS += $(ogg_cppflags)
+$(call OD, afs aft attribute blob mood playlist score server vss command \
+       http_send dccp_send udp_send send_common mm.o): \
+       CPPFLAGS += $(osl_cppflags)
+
+$(call OD, compress_filter): CFLAGS += -O3
+$(call OD, openssl): CFLAGS += -Wno-deprecated-declarations
 
 $(object_dir)/%.o: %.c | $(object_dir) $(dep_dir) $(lsg_h) $(yy_h)
+define CC_CMD
        $(call SAY, CC $<)
-       $(CC) -c -o $@ -MMD -MF $(dep_dir)/$(*F).d -MT $@ $(CPPFLAGS) \
-               $(STRICT_CFLAGS) $(CFLAGS) $<
+       $(CC) -c -o $(object_dir)/$(*F).o -MMD -MF \
+               $(dep_dir)/$(*F).d -MT $(object_dir)/$(*F).o \
+               $(CPPFLAGS) $(STRICT_CFLAGS) $(CFLAGS) $<
+endef
+CC_PREREQUISITES := %.c | $(object_dir) $(dep_dir) $(lsg_h) $(yy_h)
+# These two have the same prerequisites and the same recipe. There should be a
+# better way to write this.
+$(object_dir)/%.o: $(CC_PREREQUISITES)
+       $(CC_CMD)
+$(dep_dir)/%.d: $(CC_PREREQUISITES)
+       $(CC_CMD)
 
 para_recv para_afh para_play para_server: LDFLAGS += $(id3tag_ldflags)
 para_write para_play para_audiod \
 : LDFLAGS += $(ao_ldflags) $(pthread_ldflags)
 para_client para_audioc para_play : LDFLAGS += $(readline_ldflags)
-para_server: LDFLAGS += $(osl_ldflags)
+para_server para_upgrade_db: LDFLAGS += $(osl_ldflags)
 para_gui: LDFLAGS += $(curses_ldflags)
 para_server \
 para_client \
@@ -294,6 +281,7 @@ para_gui \
 para_play \
 para_recv \
 para_server \
+para_upgrade_db \
 para_write \
 : LDFLAGS += $(lopsub_ldflags)
 
@@ -329,7 +317,7 @@ distclean: clean
        $(call SAY, DISTCLEAN)
        rm -f Makefile autoscan.log config.status config.log
        rm -f config.h configure config.h.in
-maintainer-clean: distclean
+maintainer-clean: distclean test-clean
        $(call SAY, MAINTAINER-CLEAN)
        rm -f *.tar.bz2 *.tar.xz
        rm -f GPATH GRTAGS GSYMS GTAGS
diff --git a/NEWS.md b/NEWS.md
index fd587fab054dea33124918870b24351b20586fc6..d5812289640d71c52a60bd850928c72b3c5ba0b8 100644 (file)
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,9 +1,189 @@
 NEWS
 ====
 
-----------------------------------------------
-0.6.3 (to be announced) "generalized activity"
-----------------------------------------------
+---------------------------------------------
+0.7.4 (to be announced) "genetic contraction"
+---------------------------------------------
+
+Downloads:
+[tarball](./releases/paraslash-git.tar.xz)
+
+-----------------------------------------
+0.7.3 (2024-03-12) "weighted correctness"
+-----------------------------------------
+
+The highlight of this release is the new "ls --admissible=m/foo"
+feature described below. Other user-visible changes include minor
+additions to the "ls" and "select" server commands. The release also
+includes a fair number of cleanups for the crypto code and the file
+descriptor utilities, both without visible effects. Old ssh keys
+and outdated openssl library versions are now deprecated and cause
+warnings.
+
+- Old style PEM keys are now deprecated. They still work but their
+  use results in a run-time warning. The removal of PEM key support is
+  scheduled for paraslash-0.8.0.
+- Version 1.0 of the openssl library has been deprecated. A warning
+  is printed at compile-time on systems which have this outdated version
+  because it will no longer be supported once paraslash-0.8.0 comes out.
+- A spring cleanup for the senescent code in fd.c.
+- The --admissible option of the ls command now takes an optional
+  argument. When invoked like --admissible=m/foo, only files which are
+  admissible with respect to mood foo are listed.
+- The select server command is now quiet by default, The new --verbose
+  option can be used to show information about the newly loaded mood
+  or playlist.
+- The ls server command gained the --limit option to force a limit
+  on the number of files listed.
+- Cleanup of the openssl-specific code.
+
+Downloads:
+[tarball](./releases/paraslash-0.7.3.tar.xz),
+[signature](./releases/paraslash-0.7.3.tar.xz.asc)
+
+-------------------------------------
+0.7.2 (2023-03-08) "optical friction"
+-------------------------------------
+
+The improved error reporting of afs commands and the two new options
+for the sleep subcommand of para_mixer are the most prominent features
+of this minor release. The bulk of the changes are cleanups of the
+afs and net subsystems, which should both have no user-visible impact.
+
+- A major cleanup of the audio file selector.
+- The client no longer prints error messages from afs commands to
+  stdout but to stderr.
+- The sleep subcommand of para_mixer gained two options to control
+  the startup mood and the time period before fade-out starts. A bunch
+  of further improvements for this subcommand went in as well.
+- Minor cleanup of the net subsystem.
+- The openssl specific code now employs the EVP API to compute hashes.
+  It should compile without warnings against openssl-3.
+- The deprecated syntax for specifying negative offsets in the argument
+  to the "ff" server command has been removed.
+
+Downloads:
+[tarball](./releases/paraslash-0.7.2.tar.xz),
+[signature](./releases/paraslash-0.7.2.tar.xz.asc)
+
+--------------------------------------
+0.7.1 (2022-10-03) "digital spindrift"
+--------------------------------------
+
+The two new ll commands and the internal mp4ff library are the most
+user-visible changes of this release. On top of that there are two
+core changes which aim to improve the robustness of the code but which
+are otherwise invisible: the switch from select(2) to poll(2) and the
+revised memory allocation API which checks for integer overflows. The
+release also comes with a slight change to the build system and the
+usual mix of bug fixes and minor improvements not mentioned here.
+
+- The autogen.sh script now only creates the autoconf specific files
+  but no longer runs configure, make and the test suite.
+- A stripped down copy of the discontinued libmp4ff library has become
+  part of the paraslash code base. As a result it is no longer necessary
+  to install faad from source to get support for aac/m4a files. The
+  faad decoder package must still be installed.
+- The log level of the running daemon can now be changed with the
+  new ll command. It is available for para_server and para_audiod.
+- All calls to select(2) have been replaced by calls to poll(2)
+  to avoid known shortcomings of the select API.
+- All allocation functions now check for integer overflow. Since this
+  requires support from the compiler, the oldest supported gcc version
+  has been bumped to gcc-5.4 (released in 2015).
+
+Downloads:
+[tarball](./releases/paraslash-0.7.1.tar.xz),
+[signature](./releases/paraslash-0.7.1.tar.xz.asc)
+
+----------------------------------
+0.7.0 (2022-03-12) "seismic orbit"
+----------------------------------
+
+The major incompatible change which requires to bump the major version
+is the switch from sha1 to sha256, see below for details. However,
+there are many other improvements, the usual amount of bug fixes and
+a couple of new features.
+
+- Starting with paraslash-0.7.0, the sha256 hash value of each known
+  audio file is stored in the database while older versions employed the
+  sha1 hash algorithm which has been considered insecure since 2005
+  and should no longer be used today. The switch from sha1 to sha256
+  requires users to upgrade their database using the new para_upgrade_db
+  program, followed by re-adding all files to recompute the hashes. With
+  this approach all metadata stored in the database (last played date,
+  num played value, moods, playlists, attributes etc.) are kept. An
+  simpler alternative is to start over from scratch by running the
+  "init" command but this will lose these metadata.
+- Server and client now hash the session keys with sha256 rather
+  than sha1 during the initial handshake. This feature is optional and
+  backwards compatible: old clients can still connect to a new server
+  (using sha1). Also new clients can connect to an old server (again
+  using sha1).
+- The new "duration" keyword of the mood grammar makes it possible to
+  impose a constraint on the duration of the admissible files.
+- The long deprecated version 1 mood syntax is no longer supported.
+- Paraslash writers handle early end-of-file more gracefully.
+- The alsa writer no longer warns about spurious underruns.
+- The score formula  of the audio file selector has been reworked.
+- Cleanups of the doubly linked lists code.
+- New option for configure: --enable-ubsan to detect and report undefined
+  behaviour.
+- The "tasks" server command has been removed.
+- The fancy new logo and a minor overhaul of the web pages.
+
+Downloads:
+[tarball](./releases/paraslash-0.7.0.tar.xz),
+[signature](./releases/paraslash-0.7.0.tar.xz.asc)
+
+--------------------------------------
+0.6.4 (2021-11-04) "fuzzy calibration"
+--------------------------------------
+
+This point release contains a fair number of fixes but no new features.
+This marks the end of the 0.6 development, although paraslash-0.6 will
+still be supported for some time and subsequent maintenance releases
+may follow.
+
+- The udp sender no longer crashes when empty chunks are encountered.
+- Fix a double-free bug in the exit path of the server.
+- The "jmp" command now errors out when given a negative percentage.
+- A fix for a bug in para_afh which triggered on the attempt to modify
+  the tags of an invalid mp4 file.
+- A memory leak in para_afh has been fixed.
+- The udp sender no longer sends multiple EOF packets.
+- Some gcc warnings have been silenced.
+- Minor log level adjustments and documentation improvements.
+
+Downloads:
+[tarball](./releases/paraslash-0.6.4.tar.xz),
+[signature](./releases/paraslash-0.6.4.tar.xz.asc)
+
+---------------------------------------
+0.5.9 (2021-11-04) "reversed dimension"
+---------------------------------------
+
+This release contains a few important fixes which have accumulated in
+the maint branch. The paraslash-0.5.x series has now reached its end
+of life and will no longer be supported. All users should upgrade to
+a more recent version at this point.
+
+- Fix an issue with the bash completion script.
+- Initialize the random seed also when using libgrypt.
+- Fix some compiler warnings in the resample filter
+- Don't return spurious errors from the ff server command.
+
+Downloads:
+[tarball](./releases/paraslash-0.5.9.tar.bz2),
+[signature](./releases/paraslash-0.5.9.tar.bz2.asc)
+
+----------------------------------------
+0.6.3 (2021-02-18) "generalized activity"
+-----------------------------------------
+
+It has been a while since the last release, but paraslash-0.6.3
+finally has arrived. It contains a fair number of improvements,
+features and fixes all over the place.
 
 - The ff command now accepts a negative argument to instruct the
   virtual streaming system to jump backwards in the current audio
@@ -22,6 +202,18 @@ NEWS
 - para_client now supports RFC4716 private keys as generated with
   ssh-keygen -m RFC4716. In fact, this key format has been made the
   default, and the former PEM keys will be depreciated at some point.
+- The ogg audio format handlers learned to detect holes and now report
+  the correct duration also if ogg pages are missing in the file. This
+  affects ogg/vorbis ogg/speex and ogg/opus.
+- Robustness improvements for para_mixer.
+- A fix for an old bug that could cause the server to crash or report
+  garbage in its status output.
+- New para_play option: --end-of-playlist
+- Streaming m4a files over udp has been improved.
+
+Downloads:
+[tarball](./releases/paraslash-0.6.3.tar.xz),
+[signature](./releases/paraslash-0.6.3.tar.xz.asc)
 
 --------------------------------------
 0.6.2 (2018-06-30) "elastic diversity"
@@ -93,6 +285,23 @@ Downloads:
 [tarball](./releases/paraslash-0.6.1.tar.xz),
 [signature](./releases/paraslash-0.6.1.tar.xz.asc)
 
+---------------------------------------
+0.5.9 (2021-11-04) "reversed dimension"
+---------------------------------------
+This release contains a few important fixes which have accumulated in
+the maint branch. The paraslash-0.5.x series has now reached its end
+of life and will no longer be supported. All users should upgrade to
+a more recent version at this point.
+
+- Fix an issue with the bash completion script.
+- Initialize the random seed also when using libgrypt.
+- Fix some compiler warnings in the resample filter
+- Don't return spurious errors from the ff server command.
+
+Downloads:
+[tarball](./releases/paraslash-0.5.9.tar.bz2),
+[signature](./releases/paraslash-0.5.9.tar.bz2.asc)
+
 ---------------------------------------
 0.5.8 (2017-09-23) "branching parabola"
 ---------------------------------------
index 7f2a22a223ce733aa4b49d0a60ff63d1b95bf321..c4301a2f178257b19d56b3cf297da3e1eaff337c 100644 (file)
--- a/aac_afh.c
+++ b/aac_afh.c
 #include <neaacdec.h>
 
 #include "para.h"
-
-/* To get the mp4ff_tag_t and mp4ff_metadata_t typedefs. */
-#define USE_TAGGING
-#include <mp4ff.h>
-
+#include "mp4.h"
 #include "error.h"
 #include "portable_io.h"
 #include "afh.h"
@@ -26,63 +22,44 @@ struct aac_afh_context {
        const void *map;
        size_t mapsize;
        size_t fpos;
-       int32_t track;
-       mp4ff_t *mp4ff;
-       mp4AudioSpecificConfig masc;
-       mp4ff_callback_t cb;
+       struct mp4 *mp4;
+       struct mp4_callback cb;
 };
 
-static uint32_t aac_afh_read_cb(void *user_data, void *dest, uint32_t want)
+static ssize_t aac_afh_read_cb(void *user_data, void *dest, size_t want)
 {
        struct aac_afh_context *c = user_data;
-       uint32_t have, rv;
+       size_t have, rv;
 
-       if (want == 0 || c->fpos >= c->mapsize) {
-               PARA_INFO_LOG("failed attempt to read %u bytes @%zu\n", want,
-                       c->fpos);
-               errno = EAGAIN;
-               return -1;
-       }
+       if (want == 0 || c->fpos >= c->mapsize)
+               return 0;
        have = c->mapsize - c->fpos;
        rv = PARA_MIN(have, want);
-       PARA_DEBUG_LOG("reading %u bytes @%zu\n", rv, c->fpos);
+       PARA_DEBUG_LOG("reading %zu bytes @%zu\n", rv, c->fpos);
        memcpy(dest, c->map + c->fpos, rv);
        c->fpos += rv;
        return rv;
 }
 
-static uint32_t aac_afh_seek_cb(void *user_data, uint64_t pos)
+static off_t aac_afh_seek_cb(void *user_data, off_t offset, int whence)
 {
        struct aac_afh_context *c = user_data;
-       c->fpos = pos;
-       return 0;
-}
 
-static int32_t aac_afh_get_track(mp4ff_t *mp4ff, mp4AudioSpecificConfig *masc)
-{
-       int32_t i, rc, num_tracks = mp4ff_total_tracks(mp4ff);
-
-       assert(num_tracks >= 0);
-       for (i = 0; i < num_tracks; i++) {
-               unsigned char *buf = NULL;
-               unsigned buf_size = 0;
-
-               mp4ff_get_decoder_config(mp4ff, i, &buf, &buf_size);
-               if (buf) {
-                       rc = NeAACDecAudioSpecificConfig(buf, buf_size, masc);
-                       free(buf);
-                       if (rc < 0)
-                               continue;
-                       return i;
-               }
-       }
-       return -1; /* no audio track */
+       if (whence == SEEK_SET)
+               c->fpos = offset;
+       else if (whence == SEEK_CUR)
+               c->fpos += offset;
+       else if (whence == SEEK_END)
+               c->fpos = c->mapsize + offset;
+       else
+               assert(false);
+       return c->fpos;
 }
 
 static int aac_afh_open(const void *map, size_t mapsize, void **afh_context)
 {
        int ret;
-       struct aac_afh_context *c = para_malloc(sizeof(*c));
+       struct aac_afh_context *c = alloc(sizeof(*c));
 
        c->map = map;
        c->mapsize = mapsize;
@@ -91,18 +68,11 @@ static int aac_afh_open(const void *map, size_t mapsize, void **afh_context)
        c->cb.seek = aac_afh_seek_cb;
        c->cb.user_data = c;
 
-       ret = -E_MP4FF_OPEN;
-       c->mp4ff = mp4ff_open_read(&c->cb);
-       if (!c->mp4ff)
+       ret = mp4_open(&c->cb, &c->mp4);
+       if (ret < 0)
                goto free_ctx;
-       c->track = aac_afh_get_track(c->mp4ff, &c->masc);
-       ret = -E_MP4FF_TRACK;
-       if (c->track < 0)
-               goto close_mp4ff;
        *afh_context = c;
        return 0;
-close_mp4ff:
-       mp4ff_close(c->mp4ff);
 free_ctx:
        free(c);
        *afh_context = NULL;
@@ -112,51 +82,39 @@ free_ctx:
 static void aac_afh_close(void *afh_context)
 {
        struct aac_afh_context *c = afh_context;
-       mp4ff_close(c->mp4ff);
+       mp4_close(c->mp4);
        free(c);
 }
 
-/**
- * Libmp4ff function to reposition the file to the given sample.
- *
- * \param f The opaque handle returned by mp4ff_open_read().
- * \param track The number of the (audio) track.
- * \param sample Destination.
- *
- * We need this function to obtain the offset of the sample within the audio
- * file. Unfortunately, it is not exposed in the mp4ff header.
- *
- * \return This function always returns 0.
- */
-int32_t mp4ff_set_sample_position(mp4ff_t *f, const int32_t track, const int32_t sample);
-
-static int aac_afh_get_chunk(long unsigned chunk_num, void *afh_context,
-               const char **buf, size_t *len)
+static int aac_afh_get_chunk(uint32_t chunk_num, void *afh_context,
+               const char **buf, uint32_t *len)
 {
        struct aac_afh_context *c = afh_context;
-       int32_t ss;
+       uint32_t ss;
        size_t offset;
+       int ret;
 
-       assert(chunk_num <= INT_MAX);
-       /* this function always returns zero */
-       mp4ff_set_sample_position(c->mp4ff, c->track, chunk_num);
+       ret = mp4_set_sample_position(c->mp4, chunk_num);
+       if (ret < 0)
+               return ret;
        offset = c->fpos;
-       ss = mp4ff_read_sample_getsize(c->mp4ff, c->track, chunk_num);
-       if (ss <= 0)
-               return -E_MP4FF_BAD_SAMPLE;
-       assert(ss + offset <= c->mapsize);
+       ret = mp4_get_sample_size(c->mp4, chunk_num, &ss);
+       if (ret < 0)
+               return ret;
+       if (ss + offset > c->mapsize) /* file got truncated?! */
+               return -E_MP4_CORRUPT;
        *buf = c->map + offset;
        *len = ss;
        return 1;
 }
 
-static void _aac_afh_get_taginfo(const mp4ff_t *mp4ff, struct taginfo *tags)
+static void aac_afh_get_taginfo(const struct mp4 *mp4, struct taginfo *tags)
 {
-       mp4ff_meta_get_artist(mp4ff, &tags->artist);
-       mp4ff_meta_get_title(mp4ff, &tags->title);
-       mp4ff_meta_get_date(mp4ff, &tags->year);
-       mp4ff_meta_get_album(mp4ff, &tags->album);
-       mp4ff_meta_get_comment(mp4ff, &tags->comment);
+       tags->artist = mp4_get_tag_value(mp4, "artist");
+       tags->title = mp4_get_tag_value(mp4, "title");
+       tags->year = mp4_get_tag_value(mp4, "date");
+       tags->album = mp4_get_tag_value(mp4, "album");
+       tags->comment = mp4_get_tag_value(mp4, "comment");
 }
 
 /*
@@ -166,169 +124,125 @@ static int aac_get_file_info(char *map, size_t numbytes, __a_unused int fd,
                struct afh_info *afhi)
 {
        int ret;
-       int32_t rv;
        struct aac_afh_context *c;
-       int64_t tmp;
+       uint64_t milliseconds;
        const char *buf;
-       size_t sz;
-       uint32_t n;
+       uint32_t n, len;
 
        ret = aac_afh_open(map, numbytes, (void **)&c);
        if (ret < 0)
                return ret;
 
-       ret = -E_MP4FF_BAD_SAMPLERATE;
-       rv = mp4ff_get_sample_rate(c->mp4ff, c->track);
-       if (rv <= 0)
-               goto close;
-       afhi->frequency = rv;
-
-       ret = -E_MP4FF_BAD_CHANNEL_COUNT;
-       rv = mp4ff_get_channel_count(c->mp4ff, c->track);
-       if (rv <= 0)
-               goto close;
-       afhi->channels = rv;
-
-       ret = -E_MP4FF_BAD_SAMPLE_COUNT;
-       rv = mp4ff_num_samples(c->mp4ff, c->track);
-       if (rv <= 0)
-               goto close;
-       afhi->chunks_total = rv;
+       afhi->frequency = mp4_get_sample_rate(c->mp4);
+       assert(afhi->frequency > 0);
+       afhi->channels = mp4_get_channel_count(c->mp4);
+       assert(afhi->channels > 0);
+       afhi->chunks_total = mp4_num_samples(c->mp4);
+       assert(afhi->chunks_total > 0);
+
        afhi->max_chunk_size = 0;
        for (n = 0; n < afhi->chunks_total; n++) {
-               if (aac_afh_get_chunk(n, c, &buf, &sz) < 0)
-                       break;
-               afhi->max_chunk_size = PARA_MAX((size_t)afhi->max_chunk_size, sz);
+               ret = aac_afh_get_chunk(n, c, &buf, &len);
+               if (ret < 0)
+                       goto out;
+               afhi->max_chunk_size = PARA_MAX(afhi->max_chunk_size, len);
        }
-
-       tmp = c->masc.sbr_present_flag == 1? 2048 : 1024;
-       afhi->seconds_total = tmp * afhi->chunks_total / afhi->frequency;
-       ms2tv(1000 * tmp / afhi->frequency, &afhi->chunk_tv);
-
-       if (aac_afh_get_chunk(0, c, &buf, &sz) >= 0)
-               numbytes -= buf - map;
+       milliseconds = mp4_get_duration(c->mp4);
+       afhi->seconds_total = milliseconds / 1000;
+       ms2tv(milliseconds / afhi->chunks_total, &afhi->chunk_tv);
+       if (aac_afh_get_chunk(0, c, &buf, &len) < 0)
+               goto out;
+       numbytes -= buf - map;
        afhi->bitrate = 8 * numbytes / afhi->seconds_total / 1000;
-       _aac_afh_get_taginfo(c->mp4ff, &afhi->tags);
+       aac_afh_get_taginfo(c->mp4, &afhi->tags);
        ret = 1;
-close:
+out:
        aac_afh_close(c);
        return ret;
 }
 
-static uint32_t aac_afh_meta_read_cb(void *user_data, void *dest, uint32_t want)
+static ssize_t aac_afh_meta_read_cb(void *user_data, void *dest, size_t want)
 {
        int fd = *(int *)user_data;
        return read(fd, dest, want);
 }
 
-static uint32_t aac_afh_meta_seek_cb(void *user_data, uint64_t pos)
+static off_t aac_afh_meta_seek_cb(void *user_data, off_t offset, int whence)
 {
        int fd = *(int *)user_data;
-       return lseek(fd, pos, SEEK_SET);
+       off_t ret = lseek(fd, offset, whence);
+
+       assert(ret != (off_t)-1);
+       return ret;
 }
 
-static uint32_t aac_afh_meta_write_cb(void *user_data, void *dest, uint32_t want)
+static ssize_t aac_afh_meta_write_cb(void *user_data, void *dest, size_t count)
 {
        int fd = *(int *)user_data;
-       return write(fd, dest, want);
+       return write(fd, dest, count);
 }
 
-static uint32_t aac_afh_meta_truncate_cb(void *user_data)
+static int aac_afh_meta_truncate_cb(void *user_data)
 {
        int fd = *(int *)user_data;
        off_t offset = lseek(fd, 0, SEEK_CUR);
        return ftruncate(fd, offset);
 }
 
-static void replace_tag(mp4ff_tag_t *tag, const char *new_val, bool *found)
-{
-       free(tag->value);
-       tag->value = para_strdup(new_val);
-       *found = true;
-}
-
-static void add_tag(mp4ff_metadata_t *md, const char *item, const char *value)
+static void replace_or_add_tag(const char *item, const char *value,
+               struct mp4_metadata *meta)
 {
-       md->tags[md->count].item = para_strdup(item);
-       md->tags[md->count].value = para_strdup(value);
-       md->count++;
+       uint32_t n;
+       struct mp4_tag *t;
+
+       for (n = 0; n < meta->count; n++) {
+               t = meta->tags + n;
+               if (strcasecmp(t->item, item))
+                       continue;
+               free(t->value);
+               t->value = para_strdup(value);
+               return;
+       }
+       /* item not found, add new tag */
+       meta->tags = para_realloc(meta->tags, (meta->count + 1)
+               * sizeof(struct mp4_tag));
+       t = meta->tags + meta->count;
+       t->item = para_strdup(item);
+       t->value = para_strdup(value);
+       meta->count++;
 }
 
 static int aac_afh_rewrite_tags(const char *map, size_t mapsize,
                struct taginfo *tags, int fd, __a_unused const char *filename)
 {
-       int ret, i;
-       int32_t rv;
-       mp4ff_metadata_t metadata;
-       mp4ff_t *mp4ff;
-       mp4ff_callback_t cb = {
+       int ret;
+       struct mp4_metadata *metadata;
+       struct mp4 *mp4;
+       struct mp4_callback cb = {
                .read = aac_afh_meta_read_cb,
                .seek = aac_afh_meta_seek_cb,
                .write = aac_afh_meta_write_cb,
                .truncate = aac_afh_meta_truncate_cb,
                .user_data = &fd
        };
-       bool found_artist = false, found_title = false, found_album = false,
-               found_year = false, found_comment = false;
 
        ret = write_all(fd, map, mapsize);
        if (ret < 0)
                return ret;
        lseek(fd, 0, SEEK_SET);
 
-       mp4ff = mp4ff_open_read_metaonly(&cb);
-       if (!mp4ff)
-               return -E_MP4FF_OPEN;
-
-       ret = -E_MP4FF_META_READ;
-       rv = mp4ff_meta_get_num_items(mp4ff);
-       if (rv < 0)
-               goto close;
-       metadata.count = rv;
-       PARA_NOTICE_LOG("%d metadata item(s) found\n", rv);
-
-       metadata.tags = para_malloc((metadata.count + 5) * sizeof(mp4ff_tag_t));
-       for (i = 0; i < metadata.count; i++) {
-               mp4ff_tag_t *tag = metadata.tags + i;
-
-               ret = -E_MP4FF_META_READ;
-               if (mp4ff_meta_get_by_index(mp4ff, i,
-                               &tag->item, &tag->value) < 0)
-                       goto free_tags;
-               PARA_INFO_LOG("found: %s: %s\n", tag->item, tag->value);
-               if (!strcmp(tag->item, "artist"))
-                       replace_tag(tag, tags->artist, &found_artist);
-               else if (!strcmp(tag->item, "title"))
-                       replace_tag(tag, tags->title, &found_title);
-               else if (!strcmp(tag->item, "album"))
-                       replace_tag(tag, tags->album, &found_album);
-               else if (!strcmp(tag->item, "date"))
-                       replace_tag(tag, tags->year, &found_year);
-               else if (!strcmp(tag->item, "comment"))
-                       replace_tag(tag, tags->comment, &found_comment);
-       }
-       if (!found_artist)
-               add_tag(&metadata, "artist", tags->artist);
-       if (!found_title)
-               add_tag(&metadata, "title", tags->title);
-       if (!found_album)
-               add_tag(&metadata, "album", tags->album);
-       if (!found_year)
-               add_tag(&metadata, "date", tags->year);
-       if (!found_comment)
-               add_tag(&metadata, "comment", tags->comment);
-       ret = -E_MP4FF_META_WRITE;
-       if (mp4ff_meta_update(&cb, &metadata) < 0)
-               goto free_tags;
-       ret = 1;
-free_tags:
-       for (; i > 0; i--) {
-               free(metadata.tags[i - 1].item);
-               free(metadata.tags[i - 1].value);
-       }
-       free(metadata.tags);
-close:
-       mp4ff_close(mp4ff);
+       ret = mp4_open_meta(&cb, &mp4);
+       if (ret < 0)
+               return ret;
+       metadata = mp4_get_meta(mp4);
+       PARA_NOTICE_LOG("%u metadata item(s) found\n", metadata->count);
+       replace_or_add_tag("artist", tags->artist, metadata);
+       replace_or_add_tag("title", tags->title, metadata);
+       replace_or_add_tag("album", tags->album, metadata);
+       replace_or_add_tag("date", tags->year, metadata);
+       replace_or_add_tag("comment", tags->comment, metadata);
+       ret = mp4_update_meta(mp4);
+       mp4_close(mp4);
        return ret;
 }
 
index a2459d82b31991a8e9ac578e0559a398c83f4e10..87a7900af3ee9b43e1a661d66cdd54d1625b0952 100644 (file)
@@ -52,7 +52,7 @@ static int aacdec_execute(struct btr_node *btrn, const char *cmd, char **result)
 static void aacdec_open(struct filter_node *fn)
 {
        NeAACDecConfigurationPtr c;
-       struct private_aacdec_data *padd = para_calloc(sizeof(*padd));
+       struct private_aacdec_data *padd = zalloc(sizeof(*padd));
 
        padd->handle = NeAACDecOpen();
        c = NeAACDecGetCurrentConfiguration(padd->handle);
@@ -74,7 +74,7 @@ static void aacdec_close(struct filter_node *fn)
        fn->private_data = NULL;
 }
 
-static int aacdec_post_select(__a_unused struct sched *s, void *context)
+static int aacdec_post_monitor(__a_unused struct sched *s, void *context)
 {
        struct filter_node *fn = context;
        struct btr_node *btrn = fn->btrn;
@@ -136,7 +136,7 @@ next_buffer:
        consumed += frame_info.bytesconsumed;
        if (!frame_info.samples)
                goto success;
-       btrbuf = para_malloc(2 * frame_info.samples);
+       btrbuf = arr_alloc(2, frame_info.samples);
        for (i = 0; i < frame_info.samples; i++) {
                short sh = ((short *)outbuf)[i];
                write_int16_host_endian(btrbuf + loaded, sh);
@@ -158,7 +158,7 @@ err:
 const struct filter lsg_filter_cmd_com_aacdec_user_data = {
        .open = aacdec_open,
        .close = aacdec_close,
-       .pre_select = generic_filter_pre_select,
-       .post_select = aacdec_post_select,
+       .pre_monitor = generic_filter_pre_monitor,
+       .post_monitor = aacdec_post_monitor,
        .execute = aacdec_execute
 };
diff --git a/acl.c b/acl.c
index 2c90052658eda947ea586a55f292dc85f24e68f7..ddf93ecc6e083299daf7a573b0adce16c3b4051f 100644 (file)
--- a/acl.c
+++ b/acl.c
@@ -81,7 +81,7 @@ no_match:
  */
 void acl_add_entry(struct list_head *acl, char *addr, int netmask)
 {
-       struct access_info *ai = para_malloc(sizeof(struct access_info));
+       struct access_info *ai = alloc(sizeof(struct access_info));
 
        inet_pton(AF_INET, addr, &ai->addr);
        ai->netmask = netmask;
@@ -101,7 +101,7 @@ static void acl_del_entry(struct list_head *acl, char *addr, unsigned netmask)
        struct access_info *ai, *tmp;
        struct in_addr to_delete;
 
-       PARA_NOTICE_LOG("removing entries matching %s/%u\n", addr, netmask);
+       PARA_INFO_LOG("removing entries matching %s/%u\n", addr, netmask);
        inet_pton(AF_INET, addr, &to_delete);
 
        list_for_each_entry_safe(ai, tmp, acl, node) {
@@ -111,7 +111,7 @@ static void acl_del_entry(struct list_head *acl, char *addr, unsigned netmask)
                        const char *p = inet_ntop(AF_INET, &ai->addr.s_addr,
                                dst, sizeof(dst));
                        if (p)
-                               PARA_INFO_LOG("removing %s/%u\n", p,
+                               PARA_DEBUG_LOG("removing %s/%u\n", p,
                                        ai->netmask);
                        list_del(&ai->node);
                        free(ai);
diff --git a/afh.c b/afh.c
index 567b560a038f0923a663005b447a0fc688c57b2a..e419d2708d7224aaa5330515b11d45947c0d5fab 100644 (file)
--- a/afh.c
+++ b/afh.c
@@ -159,7 +159,7 @@ static void print_chunk_table(struct afh_info *afhi, int audio_format_id,
                struct timeval tv;
                long unsigned from, to;
                const char *buf;
-               size_t len;
+               uint32_t len;
                tv_scale(i, &afhi->chunk_tv, &tv);
                from = tv2ms(&tv);
                tv_scale(i + 1, &afhi->chunk_tv, &tv);
@@ -177,7 +177,7 @@ static void print_chunk_table(struct afh_info *afhi, int audio_format_id,
                printf("%td - %td", buf - (const char *)map,
                        buf + len - (const char *)map);
                if (!OPT_GIVEN(PARSER_FRIENDLY))
-                       printf(" (%zu)", len);
+                       printf(" (%u)", len);
                printf("\n");
        }
        afh_close(ctx, audio_format_id);
@@ -258,5 +258,6 @@ out:
                PARA_ERROR_LOG("%s\n", errctx);
        if (ret < 0)
                PARA_EMERG_LOG("%s\n", para_strerror(-ret));
+       lls_free_parse_result(lpr, CMD_PTR);
        return ret < 0? EXIT_FAILURE : EXIT_SUCCESS;
 }
diff --git a/afh.h b/afh.h
index 881db3c24217beff8d2885e1d62c05f3419aa404..ba72d80e5917b103e879ce221526d1a1a5e3fccc 100644 (file)
--- a/afh.h
+++ b/afh.h
@@ -58,8 +58,6 @@ struct afh_info {
 
 /** Data about the current audio file, passed from afs to server. */
 struct audio_file_data {
-       /** The open file descriptor to the current audio file. */
-       int fd;
        /** Vss needs this for streaming. */
        struct afh_info afhi;
        /**
@@ -113,8 +111,8 @@ struct audio_format_handler {
         * portion of the memory mapped audio file. The caller must not call
         * free() on it.
         */
-       int (*get_chunk)(long unsigned chunk_num, void *afh_context,
-               const char **buf, size_t *len);
+       int (*get_chunk)(uint32_t chunk_num, void *afh_context,
+               const char **buf, uint32_t *len);
        /** Deallocate the resources occupied by ->open(). */
        void (*close)(void *afh_context);
        /**
@@ -133,7 +131,7 @@ int compute_afhi(const char *path, char *data, size_t size,
 const char *audio_format_name(int);
 __must_check int afh_get_chunk(long unsigned chunk_num, struct afh_info *afhi,
                uint8_t audio_format_id, const void *map, size_t mapsize,
-               const char **buf, size_t *len, void **afh_context);
+               const char **buf, uint32_t *len, void **afh_context);
 void afh_close(void *afh_context, uint8_t audio_format_id);
 int32_t afh_get_start_chunk(int32_t approx_chunk_num,
                const struct afh_info *afhi, uint8_t audio_format_id);
index a267f58b106b9f0df09d3a8dad99368c3c519401..7e8f63d22a2126494d53e94457278be06142d4f7 100644 (file)
@@ -219,7 +219,7 @@ void clear_afhi(struct afh_info *afhi)
        free(afhi->tags.comment);
 }
 
-static inline size_t get_chunk_len(long unsigned chunk_num,
+static inline uint32_t get_chunk_len(long unsigned chunk_num,
                const struct afh_info *afhi)
 {
        return afhi->chunk_table[chunk_num + 1] - afhi->chunk_table[chunk_num];
@@ -247,7 +247,7 @@ static inline size_t get_chunk_len(long unsigned chunk_num,
  */
 __must_check int afh_get_chunk(long unsigned chunk_num, struct afh_info *afhi,
                uint8_t audio_format_id, const void *map, size_t mapsize,
-               const char **buf, size_t *len, void **afh_context)
+               const char **buf, uint32_t *len, void **afh_context)
 {
        struct audio_format_handler *afh = afl[audio_format_id];
 
index 4f8ff4974f018ef92e5055ac7a28bf337e3aa301..8449e787f96fa24b60c2c3af9ec5218eb264ff53 100644 (file)
@@ -75,7 +75,7 @@ static int afh_recv_open(struct receiver_node *rn)
 
        if (!fn || *fn == '\0')
                return -E_AFH_RECV_BAD_FILENAME;
-       rn->private_data = pard = para_calloc(sizeof(*pard));
+       rn->private_data = pard = zalloc(sizeof(*pard));
        afhi = &pard->afhi;
        ret = mmap_full_file(fn, O_RDONLY, &pard->map,
                &pard->map_size, &pard->fd);
@@ -142,14 +142,14 @@ static void afh_recv_close(struct receiver_node *rn)
        freep(&rn->private_data);
 }
 
-static void afh_recv_pre_select(struct sched *s, void *context)
+static void afh_recv_pre_monitor(struct sched *s, void *context)
 {
        struct receiver_node *rn = context;
        struct private_afh_recv_data *pard = rn->private_data;
        struct afh_info *afhi = &pard->afhi;
        struct lls_parse_result *lpr = rn->lpr;
        struct timeval chunk_time;
-       int state = generic_recv_pre_select(s, rn);
+       int state = generic_recv_pre_monitor(s, rn);
        unsigned j_given = RECV_CMD_OPT_GIVEN(AFH, JUST_IN_TIME, lpr);
 
        if (state <= 0)
@@ -163,7 +163,7 @@ static void afh_recv_pre_select(struct sched *s, void *context)
        sched_request_barrier_or_min_delay(&chunk_time, s);
 }
 
-static int afh_recv_post_select(__a_unused struct sched *s, void *context)
+static int afh_recv_post_monitor(__a_unused struct sched *s, void *context)
 {
        struct receiver_node *rn = context;
        struct lls_parse_result *lpr = rn->lpr;
@@ -174,6 +174,7 @@ static int afh_recv_post_select(__a_unused struct sched *s, void *context)
        char *buf;
        const char *start;
        size_t size;
+       uint32_t len;
        struct timeval chunk_time;
        unsigned j_given = RECV_CMD_OPT_GIVEN(AFH, JUST_IN_TIME, lpr);
        unsigned H_given = RECV_CMD_OPT_GIVEN(AFH, NO_HEADER, lpr);
@@ -187,7 +188,7 @@ static int afh_recv_post_select(__a_unused struct sched *s, void *context)
                        pard->map_size, &header, &size);
                if (size > 0) {
                        PARA_INFO_LOG("writing header (%zu bytes)\n", size);
-                       buf = para_malloc(size);
+                       buf = alloc(size);
                        memcpy(buf, header, size);
                        btr_add_output(buf, size, btrn);
                        afh_free_header(header, pard->audio_format_num);
@@ -197,14 +198,14 @@ static int afh_recv_post_select(__a_unused struct sched *s, void *context)
                long unsigned n;
                for (n = pard->first_chunk; n < pard->last_chunk; n++) {
                        ret = afh_get_chunk(n, afhi, pard->audio_format_num,
-                               pard->map, pard->map_size, &start, &size,
+                               pard->map, pard->map_size, &start, &len,
                                &pard->afh_context);
                        if (ret < 0)
                                goto out;
-                       PARA_DEBUG_LOG("adding %zu bytes\n", size);
-                       btr_add_output_dont_free(start, size, btrn);
+                       PARA_DEBUG_LOG("adding %u bytes\n", len);
+                       btr_add_output_dont_free(start, len, btrn);
                }
-               ret = -E_RECV_EOF;
+               ret = -E_EOF;
                goto out;
        }
        if (pard->current_chunk == pard->first_chunk)
@@ -218,14 +219,14 @@ static int afh_recv_post_select(__a_unused struct sched *s, void *context)
        }
        ret = afh_get_chunk(pard->current_chunk, afhi,
                pard->audio_format_num, pard->map,
-               pard->map_size, &start, &size,
+               pard->map_size, &start, &len,
                &pard->afh_context);
        if (ret < 0)
                goto out;
        PARA_DEBUG_LOG("adding chunk %u\n", pard->current_chunk);
-       btr_add_output_dont_free(start, size, btrn);
+       btr_add_output_dont_free(start, len, btrn);
        if (pard->current_chunk >= pard->last_chunk) {
-               ret = -E_RECV_EOF;
+               ret = -E_EOF;
                goto out;
        }
        pard->current_chunk++;
@@ -241,7 +242,7 @@ out:
 const struct receiver lsg_recv_cmd_com_afh_user_data = {
        .open = afh_recv_open,
        .close = afh_recv_close,
-       .pre_select = afh_recv_pre_select,
-       .post_select = afh_recv_post_select,
+       .pre_monitor = afh_recv_pre_monitor,
+       .post_monitor = afh_recv_post_monitor,
        .execute = afh_execute,
 };
diff --git a/afs.c b/afs.c
index d9cddeb87b356103164bee70ddf2534122c7923a..445d5871097b79cdcd14170c2a1f59998354dc98 100644 (file)
--- a/afs.c
+++ b/afs.c
 #include "afs.h"
 #include "net.h"
 #include "server.h"
+#include "daemon.h"
 #include "ipc.h"
 #include "list.h"
 #include "sched.h"
 #include "fd.h"
 #include "signal.h"
-#include "mood.h"
 #include "sideband.h"
 #include "command.h"
 
-/** The osl tables used by afs. \sa \ref blob.c. */
-enum afs_table_num {
-       /** Contains audio file information. See \ref aft.c. */
-       TBLNUM_AUDIO_FILES,
-       /** The table for the paraslash attributes. See \ref attribute.c. */
-       TBLNUM_ATTRIBUTES,
-       /**
-        * Paraslash's scoring system is based on Gaussian normal
-        * distributions, and the relevant data is stored in the rbtrees of an
-        * osl table containing only volatile columns. See \ref score.c for
-        * details.
-        */
-       TBLNUM_SCORES,
-       /**
-        * A standard blob table containing the mood definitions. For details
-        * see \ref mood.c.
-        */
-       TBLNUM_MOODS,
-       /** A blob table containing lyrics on a per-song basis. */
-       TBLNUM_LYRICS,
-       /** Another blob table for images (for example album cover art). */
-       TBLNUM_IMAGES,
-       /** Yet another blob table for storing standard playlists. */
-       TBLNUM_PLAYLIST,
-       /** How many tables are in use? */
-       NUM_AFS_TABLES
-};
-
-static struct afs_table afs_tables[NUM_AFS_TABLES] = {
-       [TBLNUM_AUDIO_FILES] = {.init = aft_init, .name = "audio_files"},
-       [TBLNUM_ATTRIBUTES] = {.init = attribute_init, .name = "attributes"},
-       [TBLNUM_SCORES] = {.init = score_init, .name = "scores"},
-       [TBLNUM_MOODS] = {.init = moods_init, .name = "moods"},
-       [TBLNUM_LYRICS] = {.init = lyrics_init, .name = "lyrics"},
-       [TBLNUM_IMAGES] = {.init = images_init, .name = "images"},
-       [TBLNUM_PLAYLIST] = {.init = playlists_init, .name = "playlists"},
+/**
+ * The array of tables of the audio file selector.
+ *
+ * We organize them in an array to be able to loop over all tables.
+ */
+static const struct afs_table {
+       /** The name is no table operation, so define it here. */
+       const char * const name;
+       /** The only way to invoke the ops is via this pointer. */
+       const struct afs_table_operations *ops;
+} afs_tables[] = {
+       {.name = "audio_files", .ops = &aft_ops},
+       {.name = "attributes", .ops = &attr_ops},
+       {.name = "scores", .ops = &score_ops},
+       {.name = "moods", .ops = &moods_ops},
+       {.name = "lyrics", .ops = &lyrics_ops},
+       {.name = "images", .ops = &images_ops},
+       {.name = "playlists", .ops = &playlists_ops},
 };
+/** Used to loop over the afs tables. */
+#define NUM_AFS_TABLES ARRAY_SIZE(afs_tables)
 
 struct command_task {
        /** The file descriptor for the local socket. */
@@ -91,32 +75,32 @@ static char *current_mop; /* mode or playlist specifier. NULL means dummy mood *
 extern uint32_t afs_socket_cookie;
 
 /**
- * Struct to let command handlers execute a callback in afs context.
- *
- * Commands that need to change the state of afs can't change the relevant data
- * structures directly because commands are executed in a child process, i.e.
- * they get their own virtual address space.
+ * Passed from command handlers to afs.
  *
- * This structure is used by \p send_callback_request() (executed from handler
- * context) in order to let the afs process call the specified function. An
- * instance of that structure is written to a shared memory area together with
- * the arguments to the callback function. The identifier of the shared memory
- * area is written to the command socket.
+ * Command handlers cannot change the afs database directly because they run in
+ * a separate process. The callback query structure circumvents this
+ * restriction as follows. To instruct the afs process to execute a particular
+ * function, the command hander writes an instance of this structure to a
+ * shared memory area, along with the arguments to the callback function. The
+ * identifier of the shared memory area is transferred to the afs process via
+ * the command socket.
  *
- * The afs process accepts connections on the command socket and reads the
- * shared memory id, attaches the corresponding area, calls the given handler to
- * perform the desired action and to optionally compute a result.
+ * The afs process reads the shared memory id from the command socket, attaches
+ * the corresponding area, and calls the callback function whose address is
+ * stored in the area.
  *
- * The result and a \p callback_result structure is then written to another
- * shared memory area. The identifier for that area is written to the handler's
- * command socket, so that the handler process can read the id, attach the
- * shared memory area and use the result.
+ * The command output, if any, is transferred back to the command handler in
+ * the same way: The afs process writes the output to a second shared memory
+ * area together with a fixed size metadata header whose format corresponds to
+ * the \ref callback_result structure. The identifier of this area is sent back
+ * to the command handler which attaches the area and forwards the output to
+ * the remote client.
  *
  * \sa \ref struct callback_result.
  */
 struct callback_query {
        /** The function to be called. */
-       afs_callback *handler;
+       afs_callback *cb;
        /** The number of bytes of the query */
        size_t query_size;
 };
@@ -207,7 +191,7 @@ int send_callback_request(afs_callback *f, struct osl_object *query,
        if (ret < 0)
                goto out;
        cq = query_shm;
-       cq->handler = f;
+       cq->cb = f;
        cq->query_size = query_shm_size - sizeof(*cq);
 
        if (query)
@@ -366,8 +350,8 @@ int for_each_matching_row(struct pattern_match_data *pmd)
  */
 int string_compare(const struct osl_object *obj1, const struct osl_object *obj2)
 {
-       const char *str1 = (const char *)obj1->data;
-       const char *str2 = (const char *)obj2->data;
+       const char *str1 = obj1->data;
+       const char *str2 = obj2->data;
        return strncmp(str1, str2, PARA_MIN(obj1->size, obj2->size));
 }
 
@@ -418,13 +402,13 @@ static int pass_afd(int fd, char *buf, size_t size)
  */
 static int open_next_audio_file(void)
 {
-       struct audio_file_data afd;
-       int ret, shmid;
+       int ret, shmid, fd;
        char buf[8];
 
-       ret = open_and_update_audio_file(&afd);
+       ret = open_and_update_audio_file(&fd);
        if (ret < 0) {
-               PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+               if (ret != -OSL_ERRNO_TO_PARA_ERROR(E_OSL_RB_KEY_NOT_FOUND))
+                       PARA_ERROR_LOG("%s\n", para_strerror(-ret));
                goto no_admissible_files;
        }
        shmid = ret;
@@ -434,8 +418,8 @@ static int open_next_audio_file(void)
        }
        *(uint32_t *)buf = NEXT_AUDIO_FILE;
        *(uint32_t *)(buf + 4) = (uint32_t)shmid;
-       ret = pass_afd(afd.fd, buf, 8);
-       close(afd.fd);
+       ret = pass_afd(fd, buf, 8);
+       close(fd);
        if (ret >= 0)
                return ret;
 destroy:
@@ -447,55 +431,48 @@ no_admissible_files:
        return write_all(server_socket, buf, 8);
 }
 
-/* Never fails if arg == NULL */
-static int activate_mood_or_playlist(const char *arg, int *num_admissible,
-               char **errmsg)
+static int activate_mood_or_playlist(const char *arg, struct para_buffer *pb)
 {
        enum play_mode mode;
        int ret;
+       char *msg;
 
-       if (!arg) {
-               ret = change_current_mood(NULL, NULL); /* always successful */
+       if (!arg) { /* load dummy mood */
+               ret = mood_load(NULL, NULL, &msg);
+               mode = PLAY_MODE_MOOD;
+       } else if (!strncmp(arg, "p/", 2)) {
+               ret = playlist_load(arg + 2, NULL, &msg);
+               mode = PLAY_MODE_PLAYLIST;
+       } else if (!strncmp(arg, "m/", 2)) {
+               ret = mood_load(arg + 2, NULL, &msg);
                mode = PLAY_MODE_MOOD;
        } else {
-               if (!strncmp(arg, "p/", 2)) {
-                       ret = playlist_open(arg + 2);
-                       if (ret < 0 && errmsg)
-                               *errmsg = make_message( "could not open %s",
-                                       arg);
-                       mode = PLAY_MODE_PLAYLIST;
-               } else if (!strncmp(arg, "m/", 2)) {
-                       ret = change_current_mood(arg + 2, errmsg);
-                       mode = PLAY_MODE_MOOD;
-               } else {
-                       if (errmsg)
-                               *errmsg = make_message("%s: parse error", arg);
-                       return -ERRNO_TO_PARA_ERROR(EINVAL);
-               }
-               if (ret < 0)
-                       return ret;
+               ret = -ERRNO_TO_PARA_ERROR(EINVAL);
+               msg = make_message("%s: parse error\n", arg);
        }
-       if (num_admissible)
-               *num_admissible = ret;
+       if (pb)
+               para_printf(pb, "%s", msg);
+       free(msg);
+       if (ret < 0)
+               return ret;
        current_play_mode = mode;
+       /*
+        * We get called with arg == current_mop from the signal dispatcher
+        * after SIGHUP and from the error path of the select command to
+        * re-select the current mood or playlist. In this case the assignment
+        * to current_mop below would result in a use-after-free condition.
+        */
        if (arg != current_mop) {
                free(current_mop);
-               if (arg) {
-                       current_mop = para_strdup(arg);
-                       mutex_lock(mmd_mutex);
-                       strncpy(mmd->afs_mode_string, arg,
-                               sizeof(mmd->afs_mode_string));
-                       mmd->afs_mode_string[sizeof(mmd->afs_mode_string) - 1] = '\0';
-                       mmd->events++;
-                       mutex_unlock(mmd_mutex);
-               } else {
-                       mutex_lock(mmd_mutex);
-                       strcpy(mmd->afs_mode_string, "dummy");
-                       mmd->events++;
-                       mutex_unlock(mmd_mutex);
-                       current_mop = NULL;
-               }
+               current_mop = arg? para_strdup(arg) : NULL;
        }
+       /* Notify the server about the mood/playlist change. */
+       mutex_lock(mmd_mutex);
+       strncpy(mmd->afs_mode_string, arg? arg: "dummy",
+               sizeof(mmd->afs_mode_string));
+       mmd->afs_mode_string[sizeof(mmd->afs_mode_string) - 1] = '\0';
+       mmd->events++;
+       mutex_unlock(mmd_mutex);
        return 1;
 }
 
@@ -548,77 +525,14 @@ static void flush_and_free_pb(struct para_buffer *pb)
        free(pb->buf);
 }
 
-static int com_select_callback(struct afs_callback_arg *aca)
-{
-       const struct lls_command *cmd = SERVER_CMD_CMD_PTR(SELECT);
-       const char *arg;
-       int num_admissible, ret;
-       char *errmsg;
-
-       ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr);
-       assert(ret >= 0);
-       arg = lls_input(0, aca->lpr);
-       ret = clear_score_table();
-       if (ret < 0) {
-               para_printf(&aca->pbout, "could not clear score table\n");
-               goto free_lpr;
-       }
-       if (current_play_mode == PLAY_MODE_MOOD)
-               close_current_mood();
-       else
-               playlist_close();
-       ret = activate_mood_or_playlist(arg, &num_admissible, &errmsg);
-       if (ret >= 0)
-               goto out;
-       /* ignore subsequent errors (but log them) */
-       para_printf(&aca->pbout, "%s\n", errmsg);
-       free(errmsg);
-       para_printf(&aca->pbout, "could not activate %s\n", arg);
-       if (current_mop && strcmp(current_mop, arg) != 0) {
-               int ret2;
-               para_printf(&aca->pbout, "switching back to %s\n", current_mop);
-               ret2 = activate_mood_or_playlist(current_mop, &num_admissible,
-                       &errmsg);
-               if (ret2 >= 0)
-                       goto out;
-               para_printf(&aca->pbout, "%s\n", errmsg);
-               free(errmsg);
-               para_printf(&aca->pbout, "could not reactivate %s: %s\n",
-                       current_mop, para_strerror(-ret2));
-       }
-       para_printf(&aca->pbout, "activating dummy mood\n");
-       activate_mood_or_playlist(NULL, &num_admissible, NULL);
-out:
-       para_printf(&aca->pbout, "activated %s (%d admissible file%s)\n",
-               current_mop? current_mop : "dummy mood", num_admissible,
-                       num_admissible == 1? "" : "s");
-free_lpr:
-       lls_free_parse_result(aca->lpr, cmd);
-       return ret;
-}
-
-static int com_select(struct command_context *cc, struct lls_parse_result *lpr)
-{
-       const struct lls_command *cmd = SERVER_CMD_CMD_PTR(SELECT);
-       char *errctx;
-       int ret = lls(lls_check_arg_count(lpr, 1, 1, &errctx));
-
-       if (ret < 0) {
-               send_errctx(cc, errctx);
-               return ret;
-       }
-       return send_lls_callback_request(com_select_callback, cmd, lpr, cc);
-}
-EXPORT_SERVER_CMD_HANDLER(select);
-
 static void init_admissible_files(const char *arg)
 {
-       int ret = activate_mood_or_playlist(arg, NULL, NULL);
+       int ret = activate_mood_or_playlist(arg, NULL);
        if (ret < 0) {
-               assert(arg);
-               PARA_WARNING_LOG("could not activate %s: %s\n", arg,
-                       para_strerror(-ret));
-               activate_mood_or_playlist(NULL, NULL, NULL);
+               PARA_WARNING_LOG("could not activate %s: %s\n", arg?
+                       arg : "dummy", para_strerror(-ret));
+               if (arg)
+                       activate_mood_or_playlist(NULL, NULL);
        }
 }
 
@@ -644,9 +558,9 @@ static char *database_dir;
 static void close_afs_tables(void)
 {
        int i;
-       PARA_NOTICE_LOG("closing afs_tables\n");
+       PARA_NOTICE_LOG("closing afs tables\n");
        for (i = 0; i < NUM_AFS_TABLES; i++)
-               afs_tables[i].close();
+               afs_tables[i].ops->close();
        free(database_dir);
        database_dir = NULL;
 }
@@ -659,33 +573,22 @@ static void get_database_dir(void)
                else {
                        char *home = para_homedir();
                        database_dir = make_message(
-                               "%s/.paraslash/afs_database-0.4", home);
+                               "%s/.paraslash/afs_database-0.7", home);
                        free(home);
                }
        }
        PARA_INFO_LOG("afs_database dir %s\n", database_dir);
 }
 
-static int make_database_dir(void)
-{
-       int ret;
-
-       get_database_dir();
-       ret = para_mkdir(database_dir, 0777);
-       if (ret >= 0 || ret == -ERRNO_TO_PARA_ERROR(EEXIST))
-               return 1;
-       return ret;
-}
-
 static int open_afs_tables(void)
 {
        int i, ret;
 
        get_database_dir();
-       PARA_NOTICE_LOG("opening %d osl tables in %s\n", NUM_AFS_TABLES,
+       PARA_NOTICE_LOG("opening %zu osl tables in %s\n", NUM_AFS_TABLES,
                database_dir);
        for (i = 0; i < NUM_AFS_TABLES; i++) {
-               ret = afs_tables[i].open(database_dir);
+               ret = afs_tables[i].ops->open(database_dir);
                if (ret >= 0)
                        continue;
                PARA_ERROR_LOG("could not open %s\n", afs_tables[i].name);
@@ -694,11 +597,11 @@ static int open_afs_tables(void)
        if (ret >= 0)
                return ret;
        while (i)
-               afs_tables[--i].close();
+               afs_tables[--i].ops->close();
        return ret;
 }
 
-static int afs_signal_post_select(struct sched *s, __a_unused void *context)
+static int afs_signal_post_monitor(struct sched *s, __a_unused void *context)
 {
        int signum, ret;
 
@@ -706,7 +609,7 @@ static int afs_signal_post_select(struct sched *s, __a_unused void *context)
                PARA_EMERG_LOG("para_server died\n");
                goto shutdown;
        }
-       signum = para_next_signal(&s->rfds);
+       signum = para_next_signal();
        if (signum == 0)
                return 0;
        if (signum == SIGHUP) {
@@ -734,8 +637,8 @@ static void register_signal_task(struct sched *s)
 
        signal_task->task = task_register(&(struct task_info) {
                .name = "signal",
-               .pre_select = signal_pre_select,
-               .post_select = afs_signal_post_select,
+               .pre_monitor = signal_pre_monitor,
+               .post_monitor = afs_signal_post_monitor,
                .context = signal_task,
 
        }, s);
@@ -753,15 +656,15 @@ struct afs_client {
        struct timeval connect_time;
 };
 
-static void command_pre_select(struct sched *s, void *context)
+static void command_pre_monitor(struct sched *s, void *context)
 {
        struct command_task *ct = context;
        struct afs_client *client;
 
-       para_fd_set(server_socket, &s->rfds, &s->max_fileno);
-       para_fd_set(ct->fd, &s->rfds, &s->max_fileno);
+       sched_monitor_readfd(server_socket, s);
+       sched_monitor_readfd(ct->fd, s);
        list_for_each_entry(client, &afs_client_list, node)
-               para_fd_set(client->fd, &s->rfds, &s->max_fileno);
+               sched_monitor_readfd(client->fd, s);
 }
 
 /**
@@ -814,6 +717,43 @@ err:
        return ret;
 }
 
+/**
+ * Format and send an error message to the command handler.
+ *
+ * To pass an error message from the callback of an afs command to the client,
+ * this function should be called. It formats the message into a buffer which
+ * is passed as a shared memory area to the command handler from where it
+ * propagates to the client.
+ *
+ * The message will be tagged with the ERROR_LOG sideband designator so that
+ * the client writes it to its stderr stream rather than to stdout as with
+ * aca->pbout. In analogy to the default Unix semantics of stderr, the message
+ * is sent without buffering.
+ *
+ * If sending the error message fails, an error is logged on the server side,
+ * but no other action is taken.
+ *
+ * \param aca Used to obtain the fd to send the shmid to.
+ * \param fmt Usual format string.
+ */
+__printf_2_3 void afs_error(const struct afs_callback_arg *aca,
+               const char *fmt,...)
+{
+       va_list argp;
+       char *msg;
+       unsigned n;
+       int ret;
+
+       va_start(argp, fmt);
+       n = xvasprintf(&msg, fmt, argp);
+       va_end(argp);
+       ret = pass_buffer_as_shm(aca->fd, SBD_ERROR_LOG, msg, n + 1);
+       if (ret < 0)
+               PARA_ERROR_LOG("Could not send %s: %s\n", msg,
+                       para_strerror(-ret));
+       free(msg);
+}
+
 static int call_callback(int fd, int query_shmid)
 {
        void *query_shm;
@@ -833,7 +773,7 @@ static int call_callback(int fd, int query_shmid)
                .fd = fd,
                .band = SBD_OUTPUT
        };
-       ret = cq->handler(&aca);
+       ret = cq->cb(&aca);
        ret2 = shm_detach(query_shm);
        if (ret2 < 0) {
                if (ret < 0) /* ignore (but log) detach error */
@@ -853,11 +793,11 @@ static int call_callback(int fd, int query_shmid)
        return ret;
 }
 
-static int execute_server_command(fd_set *rfds)
+static int execute_server_command(void)
 {
        char buf[8];
        size_t n;
-       int ret = read_nonblock(server_socket, buf, sizeof(buf) - 1, rfds, &n);
+       int ret = read_nonblock(server_socket, buf, sizeof(buf) - 1, &n);
 
        if (ret < 0 || n == 0)
                return ret;
@@ -868,13 +808,13 @@ static int execute_server_command(fd_set *rfds)
 }
 
 /* returns 0 if no data available, 1 else */
-static int execute_afs_command(int fd, fd_set *rfds)
+static int execute_afs_command(int fd)
 {
        uint32_t cookie;
        int query_shmid;
        char buf[sizeof(cookie) + sizeof(query_shmid)];
        size_t n;
-       int ret = read_nonblock(fd, buf, sizeof(buf), rfds, &n);
+       int ret = read_nonblock(fd, buf, sizeof(buf), &n);
 
        if (ret < 0)
                goto err;
@@ -908,7 +848,7 @@ err:
 /** Shutdown connection if query has not arrived until this many seconds. */
 #define AFS_CLIENT_TIMEOUT 3
 
-static int command_post_select(struct sched *s, void *context)
+static int command_post_monitor(struct sched *s, void *context)
 {
        struct command_task *ct = context;
        struct sockaddr_un unix_addr;
@@ -918,7 +858,7 @@ static int command_post_select(struct sched *s, void *context)
        ret = task_get_notification(ct->task);
        if (ret < 0)
                return ret;
-       ret = execute_server_command(&s->rfds);
+       ret = execute_server_command();
        if (ret < 0) {
                PARA_EMERG_LOG("%s\n", para_strerror(-ret));
                task_notify_all(s, -ret);
@@ -926,7 +866,7 @@ static int command_post_select(struct sched *s, void *context)
        }
        /* Check the list of connected clients. */
        list_for_each_entry_safe(client, tmp, &afs_client_list, node) {
-               ret = execute_afs_command(client->fd, &s->rfds);
+               ret = execute_afs_command(client->fd);
                if (ret == 0) { /* prevent bogus connection flooding */
                        struct timeval diff;
                        tv_diff(now, &client->connect_time, &diff);
@@ -939,7 +879,7 @@ static int command_post_select(struct sched *s, void *context)
                free(client);
        }
        /* Accept connections on the local socket. */
-       ret = para_accept(ct->fd, &s->rfds, &unix_addr, sizeof(unix_addr), &fd);
+       ret = para_accept(ct->fd, &unix_addr, sizeof(unix_addr), &fd);
        if (ret < 0)
                PARA_NOTICE_LOG("%s\n", para_strerror(-ret));
        if (ret <= 0)
@@ -950,7 +890,7 @@ static int command_post_select(struct sched *s, void *context)
                close(fd);
                return 0;
        }
-       client = para_malloc(sizeof(*client));
+       client = alloc(sizeof(*client));
        client->fd = fd;
        client->connect_time = *now;
        para_list_add(&client->node, &afs_client_list);
@@ -964,12 +904,20 @@ static void register_command_task(struct sched *s)
 
        ct->task = task_register(&(struct task_info) {
                .name = "afs command",
-               .pre_select = command_pre_select,
-               .post_select = command_post_select,
+               .pre_monitor = command_pre_monitor,
+               .post_monitor = command_post_monitor,
                .context = ct,
        }, s);
 }
 
+static int afs_poll(struct pollfd *fds, nfds_t nfds, int timeout)
+{
+       mutex_lock(mmd_mutex);
+       daemon_set_loglevel(mmd->loglevel);
+       mutex_unlock(mmd_mutex);
+       return xpoll(fds, nfds, timeout);
+}
+
 /**
  * Initialize the audio file selector process.
  *
@@ -978,12 +926,10 @@ static void register_command_task(struct sched *s)
 __noreturn void afs_init(int socket_fd)
 {
        static struct sched s;
-       int i, ret;
+       int ret;
 
        register_signal_task(&s);
-       INIT_LIST_HEAD(&afs_client_list);
-       for (i = 0; i < NUM_AFS_TABLES; i++)
-               afs_tables[i].init(&afs_tables[i]);
+       init_list_head(&afs_client_list);
        ret = open_afs_tables();
        if (ret < 0)
                goto out;
@@ -994,8 +940,8 @@ __noreturn void afs_init(int socket_fd)
        PARA_INFO_LOG("server_socket: %d\n", server_socket);
        init_admissible_files(OPT_STRING_VAL(AFS_INITIAL_MODE));
        register_command_task(&s);
-       s.default_timeout.tv_sec = 0;
-       s.default_timeout.tv_usec = 999 * 1000;
+       s.default_timeout = 1000;
+       s.poll_function = afs_poll;
        ret = write(socket_fd, "\0", 1);
        if (ret != 1) {
                if (ret == 0)
@@ -1005,7 +951,8 @@ __noreturn void afs_init(int socket_fd)
        }
        ret = schedule(&s);
        sched_shutdown(&s);
-       close_current_mood();
+       mood_unload(NULL);
+       playlist_unload(NULL);
 out_close:
        close_afs_tables();
 out:
@@ -1018,6 +965,57 @@ out:
        exit(EXIT_FAILURE);
 }
 
+static int com_select_callback(struct afs_callback_arg *aca)
+{
+       const struct lls_command *cmd = SERVER_CMD_CMD_PTR(SELECT);
+       const char *arg;
+       int ret;
+       struct para_buffer *pbout;
+
+       ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr);
+       assert(ret >= 0);
+       arg = lls_input(0, aca->lpr);
+       pbout = SERVER_CMD_OPT_GIVEN(SELECT, VERBOSE, aca->lpr)?
+               &aca->pbout : NULL;
+       score_clear();
+       if (current_play_mode == PLAY_MODE_MOOD)
+               mood_unload(NULL);
+       else
+               playlist_unload(NULL);
+       ret = activate_mood_or_playlist(arg, pbout);
+       if (ret >= 0)
+               goto free_lpr;
+       /* ignore subsequent errors (but log them) */
+       if (current_mop && strcmp(current_mop, arg) != 0) {
+               int ret2;
+               afs_error(aca, "switching back to %s\n", current_mop);
+               ret2 = activate_mood_or_playlist(current_mop, pbout);
+               if (ret2 >= 0)
+                       goto free_lpr;
+               afs_error(aca, "could not reactivate %s: %s\n", current_mop,
+                       para_strerror(-ret2));
+       }
+       activate_mood_or_playlist(NULL, pbout);
+free_lpr:
+       lls_free_parse_result(aca->lpr, cmd);
+       return ret;
+}
+
+static int com_select(struct command_context *cc, struct lls_parse_result *lpr)
+{
+       const struct lls_command *cmd = SERVER_CMD_CMD_PTR(SELECT);
+       char *errctx;
+       int ret = lls(lls_check_arg_count(lpr, 1, 1, &errctx));
+
+       if (ret < 0) {
+               send_errctx(cc, errctx);
+               return ret;
+       }
+       ret = send_lls_callback_request(com_select_callback, cmd, lpr, cc);
+       return ret == osl(-E_OSL_RB_KEY_NOT_FOUND)? -E_BAD_MOP : ret;
+}
+EXPORT_SERVER_CMD_HANDLER(select);
+
 static int com_init_callback(struct afs_callback_arg *aca)
 {
        uint32_t table_mask = *(uint32_t *)aca->query.data;
@@ -1026,16 +1024,15 @@ static int com_init_callback(struct afs_callback_arg *aca)
        close_afs_tables();
        get_database_dir();
        for (i = 0; i < NUM_AFS_TABLES; i++) {
-               struct afs_table *t = &afs_tables[i];
+               const struct afs_table *t = afs_tables + i;
 
                if (!(table_mask & (1 << i)))
                        continue;
-               if (!t->create)
+               if (!t->ops->create)
                        continue;
-               ret = t->create(database_dir);
+               ret = t->ops->create(database_dir);
                if (ret < 0) {
-                       para_printf(&aca->pbout, "cannot create table %s\n",
-                               t->name);
+                       afs_error(aca, "cannot create table %s\n", t->name);
                        goto out;
                }
                para_printf(&aca->pbout, "successfully created %s table\n",
@@ -1043,7 +1040,8 @@ static int com_init_callback(struct afs_callback_arg *aca)
        }
        ret = open_afs_tables();
        if (ret < 0)
-               para_printf(&aca->pbout, "cannot open afs tables\n");
+               afs_error(aca, "cannot open afs tables: %s\n",
+                       para_strerror(-ret));
 out:
        return ret;
 }
@@ -1056,14 +1054,15 @@ static int com_init(struct command_context *cc, struct lls_parse_result *lpr)
                .size = sizeof(table_mask)};
        unsigned num_inputs = lls_num_inputs(lpr);
 
-       ret = make_database_dir();
+       get_database_dir();
+       ret = para_mkdir(database_dir);
        if (ret < 0)
                return ret;
        if (num_inputs > 0) {
                table_mask = 0;
                for (i = 0; i < num_inputs; i++) {
                        for (j = 0; j < NUM_AFS_TABLES; j++) {
-                               struct afs_table *t = &afs_tables[j];
+                               const struct afs_table *t = afs_tables + j;
 
                                if (strcmp(lls_input(i, lpr), t->name))
                                        continue;
@@ -1137,10 +1136,10 @@ __must_check int afs_event(enum afs_events event, struct para_buffer *pb,
        int i, ret;
 
        for (i = 0; i < NUM_AFS_TABLES; i++) {
-               struct afs_table *t = &afs_tables[i];
-               if (!t->event_handler)
+               const struct afs_table *t = afs_tables + i;
+               if (!t->ops->event_handler)
                        continue;
-               ret = t->event_handler(event, pb, data);
+               ret = t->ops->event_handler(event, pb, data);
                if (ret < 0) {
                        PARA_CRIT_LOG("table %s, event %u: %s\n", t->name,
                                event, para_strerror(-ret));
diff --git a/afs.h b/afs.h
index cfa9cc6df4c11900739e9ff90fba0c4b30bf16e8..e8b8c865bda36b9f905b2c1cb435d03bc12d3cbd 100644 (file)
--- a/afs.h
+++ b/afs.h
@@ -73,19 +73,15 @@ struct afsi_change_event_data {
        struct afs_info *old_afsi;
 };
 
-/** Function pointers for table handling.  */
-struct afs_table {
-       /** Initializes the other pointers in this struct. */
-       void (*init)(struct afs_table *t);
-       /** The name of this table. */
-       const char *name;
-       /** Gets called on startup and on \p SIGHUP. */
+/** Methods for table startup/shutdown and event handling. */
+struct afs_table_operations {
+       /** Gets called on startup and on SIGHUP. */
        int (*open)(const char *base_dir);
-       /** Gets called on shutdown and on \p SIGHUP. */
+       /** Gets called on shutdown and on SIGHUP. */
        void (*close)(void);
-       /** Called by the \a init afs command. */
+       /** Called from the init command. */
        int (*create)(const char *);
-       /** Handles afs events. */
+       /** Handle events generated by other tables. See enum \ref afs_events. */
        int (*event_handler)(enum afs_events event, struct para_buffer *pb,
                void *data);
 };
@@ -173,6 +169,8 @@ struct afs_callback_arg {
 };
 
 /**
+ * The "top half" of an afs command.
+ *
  * Afs command handlers run as a process which is not related to the afs
  * process, i.e. they can not change the address space of afs directly.
  * Therefore afs commands typically consist of two functions: The command
@@ -183,9 +181,13 @@ struct afs_callback_arg {
 typedef int afs_callback(struct afs_callback_arg *aca);
 
 /**
+ * Dispatch the output of an afs callback.
+ *
  * Some AFS callbacks need to send data back to the command handler. Pointers
  * to this type of function are passed to \ref send_callback_request() and
- * related functions to dispatch the data in the command handler process.
+ * related functions to dispatch the data in the command handler process. Most
+ * (but not all) afs commands pass \ref afs_cb_result_handler(), which sends
+ * the output of the callback to the connected client.
  */
 typedef int callback_result_handler(struct osl_object *result, uint8_t band, void *private);
 int afs_cb_result_handler(struct osl_object *result, uint8_t band, void *private);
@@ -229,33 +231,36 @@ int send_callback_request(afs_callback *f, struct osl_object *query,
 int send_lls_callback_request(afs_callback *f,
                const struct lls_command * const cmd,
                struct lls_parse_result *lpr, void *private_result_data);
+__printf_2_3 void afs_error(const struct afs_callback_arg *aca,
+               const char *fmt,...);
 int string_compare(const struct osl_object *obj1, const struct osl_object *obj2);
 int for_each_matching_row(struct pattern_match_data *pmd);
 
 /* score */
-void score_init(struct afs_table *t);
-int admissible_file_loop(void *data, osl_rbtree_loop_func *func);
+extern const struct afs_table_operations score_ops;
+void score_open(struct osl_table **result);
+void score_close(struct osl_table *t);
+int score_loop(osl_rbtree_loop_func *func, struct osl_table *t, void *data);
 int score_get_best(struct osl_row **aft_row, long *score);
 int get_score_and_aft_row(struct osl_row *score_row, long *score, struct osl_row **aft_row);
-int score_add(const struct osl_row *row, long score);
+int score_add(const struct osl_row *aft_row, long score, struct osl_table *t);
 int score_update(const struct osl_row *aft_row, long new_score);
-int get_num_admissible_files(unsigned *num);
 int score_delete(const struct osl_row *aft_row);
-int clear_score_table(void);
-int row_belongs_to_score_table(const struct osl_row *aft_row, unsigned *rank);
+void score_clear(void);
+bool row_belongs_to_score_table(const struct osl_row *aft_row);
 
 /* attribute */
-void attribute_init(struct afs_table *t);
+extern const struct afs_table_operations attr_ops;
 void get_attribute_bitmap(const uint64_t *atts, char *buf); /* needed by com_ls() */
 int get_attribute_bitnum_by_name(const char *att_name, unsigned char *bitnum);
 int get_attribute_text(uint64_t *atts, const char *delim, char **text);
 int attribute_check_callback(struct afs_callback_arg *aca);
 
 /* aft */
-void aft_init(struct afs_table *t);
+extern const struct afs_table_operations aft_ops;
 int aft_get_row_of_path(const char *path, struct osl_row **row);
 int aft_check_attributes(uint64_t att_mask, struct para_buffer *pb);
-int open_and_update_audio_file(struct audio_file_data *afd);
+int open_and_update_audio_file(int *fd);
 int load_afd(int shmid, struct audio_file_data *afd);
 int get_afsi_of_row(const struct osl_row *row, struct afs_info *afsi);
 int get_afhi_of_row(const struct osl_row *row, struct afh_info *afhi);
@@ -264,9 +269,18 @@ int audio_file_loop(void *private_data, osl_rbtree_loop_func *func);
 int aft_check_callback(struct afs_callback_arg *aca);
 void free_status_items(void);
 
+/* mood */
+struct mood_instance;
+int mood_load(const char *mood_name, struct mood_instance **result, char **msg);
+int mood_loop(struct mood_instance *m, osl_rbtree_loop_func *func, void *data);
+void mood_unload(struct mood_instance *m);
+int mood_check_callback(struct afs_callback_arg *aca);
+
 /* playlist */
-int playlist_open(const char *name);
-void playlist_close(void);
+struct playlist_instance;
+int playlist_load(const char *name, struct playlist_instance **result, char **msg);
+int playlist_loop(struct playlist_instance *pi, osl_rbtree_loop_func *func, void *data);
+void playlist_unload(struct playlist_instance *pi);
 int playlist_check_callback(struct afs_callback_arg *aca);
 
 /** evaluates to 1 if x < y, to -1 if x > y and to 0 if x == y */
@@ -275,15 +289,15 @@ int playlist_check_callback(struct afs_callback_arg *aca);
 
 /** Define exported functions and a table pointer for an osl blob table. */
 #define DECLARE_BLOB_SYMBOLS(table_name, cmd_prefix) \
-       void table_name ## _init(struct afs_table *t); \
        int cmd_prefix ## _get_name_by_id(uint32_t id, char **name); \
        int cmd_prefix ## _get_def_by_id(uint32_t id, struct osl_object *def); \
-       int cmd_prefix ## _get_def_by_name(char *name, struct osl_object *def); \
+       int cmd_prefix ## _get_def_by_name(const char *name, struct osl_object *def); \
        int cmd_prefix ## _get_name_and_def_by_row(const struct osl_row *row, \
                char **name, struct osl_object *def); \
        int table_name ##_event_handler(enum afs_events event, \
                struct para_buffer *pb, void *data); \
-       extern struct osl_table *table_name ## _table;
+       extern struct osl_table *table_name ## _table; \
+       extern const struct afs_table_operations table_name ## _ops;
 
 /** \cond blob_symbols */
 DECLARE_BLOB_SYMBOLS(lyrics, lyr);
diff --git a/aft.c b/aft.c
index c04d4f9c99e89afb451afe94be7fcc64461b15e1..f1aca7fb8ccc3e31575303a4762d6a7ea35a054f 100644 (file)
--- a/aft.c
+++ b/aft.c
@@ -6,6 +6,7 @@
 #include <sys/mman.h>
 #include <fnmatch.h>
 #include <sys/shm.h>
+#include <dirent.h>
 #include <osl.h>
 #include <lopsub.h>
 
@@ -42,7 +43,7 @@ struct ls_data {
  */
 static struct osl_table *audio_file_table; /* NULL if table not open */
 static struct osl_row *current_aft_row; /* NULL if no audio file open */
-static unsigned char current_hash[HASH_SIZE]; /* only used on sighup */
+static unsigned char current_hash[HASH2_SIZE]; /* only used on sighup */
 
 static char *status_items;
 static char *parser_friendly_status_items;
@@ -226,7 +227,7 @@ static struct osl_column_description aft_cols[] = {
                .storage_flags = OSL_RBTREE | OSL_FIXED_SIZE | OSL_UNIQUE,
                .name = "hash",
                .compare_function = aft_hash_compare,
-               .data_size = HASH_SIZE
+               .data_size = HASH2_SIZE
        },
        [AFTCOL_PATH] = {
                .storage_type = OSL_MAPPED_STORAGE,
@@ -251,7 +252,7 @@ static struct osl_column_description aft_cols[] = {
 };
 
 static struct osl_table_description audio_file_table_desc = {
-       .name = "audio_files",
+       .name = "audio-files",
        .num_columns = NUM_AFT_COLUMNS,
        .flags = OSL_LARGE_TABLE,
        .column_descriptions = aft_cols
@@ -407,12 +408,12 @@ static void load_chunk_table(struct afh_info *afhi, const struct osl_object *ct)
        int i;
        size_t sz;
 
-       if (!ct->data || ct->size < 4) {
+       if (!ct->data || ct->size < 4 * (afhi->chunks_total + 1)) {
                afhi->chunk_table = NULL;
                return;
        }
        sz  = PARA_MIN(((size_t)afhi->chunks_total + 1) * 4, ct->size) + 1;
-       afhi->chunk_table = para_malloc(sz);
+       afhi->chunk_table = alloc(sz);
        for (i = 0; i <= afhi->chunks_total && i * 4 + 3 < ct->size; i++)
                afhi->chunk_table[i] = read_u32(ct->data + 4 * i);
 }
@@ -442,7 +443,7 @@ int aft_get_row_of_path(const char *path, struct osl_row **row)
  */
 static int aft_get_row_of_hash(unsigned char *hash, struct osl_row **row)
 {
-       const struct osl_object obj = {.data = hash, .size = HASH_SIZE};
+       const struct osl_object obj = {.data = hash, .size = HASH2_SIZE};
        return osl(osl_get_row(audio_file_table, AFTCOL_HASH, &obj, row));
 }
 
@@ -589,8 +590,10 @@ static int get_hash_of_row(const struct osl_row *row, unsigned char **hash)
 int get_afhi_of_row(const struct osl_row *row, struct afh_info *afhi)
 {
        struct osl_object obj;
-       int ret = osl(osl_get_object(audio_file_table, row, AFTCOL_AFHI,
-               &obj));
+       int ret;
+
+       assert(row);
+       ret = osl(osl_get_object(audio_file_table, row, AFTCOL_AFHI, &obj));
        if (ret < 0)
                return ret;
        load_afhi(obj.data, afhi);
@@ -719,7 +722,8 @@ __a_const static short unsigned get_duration_width(int seconds)
        return width + 6;
 }
 
-static void get_duration_buf(int seconds, char *buf, struct ls_options *opts)
+static void get_duration_buf(int seconds, char *buf, size_t bufsize,
+               struct ls_options *opts)
 {
        unsigned hours = seconds / 3600, mins = (seconds % 3600) / 60;
        short unsigned max_width;
@@ -727,10 +731,12 @@ static void get_duration_buf(int seconds, char *buf, struct ls_options *opts)
        if (!hours) { /* m:ss or mm:ss */
                max_width = opts->mode == LS_MODE_LONG?
                        opts->widths.duration_width : 4;
+               assert(max_width < bufsize - 1);
                sprintf(buf, "%*u:%02d", max_width - 3, mins, seconds % 60);
        } else { /* more than one hour => h:mm:ss, hh:mm:ss, hhh:mm:ss, ... */
                max_width = opts->mode == LS_MODE_LONG?
                        opts->widths.duration_width : 7;
+               assert(max_width < bufsize - 1);
                sprintf(buf, "%*u:%02u:%02d", max_width - 6, hours, mins,
                        seconds % 60);
        }
@@ -774,18 +780,17 @@ static void write_image_items(struct para_buffer *b, struct afs_info *afsi)
 static void write_filename_items(struct para_buffer *b, const char *path,
                bool basename)
 {
-       char *val;
+       const char *slash;
 
        if (basename) {
                WRITE_STATUS_ITEM(b, SI_basename, "%s\n", path);
                return;
        }
        WRITE_STATUS_ITEM(b, SI_path, "%s\n", path);
-       val = para_basename(path);
-       WRITE_STATUS_ITEM(b, SI_basename, "%s\n", val? val : "");
-       val = para_dirname(path);
-       WRITE_STATUS_ITEM(b, SI_directory, "%s\n", val? val : "");
-       free(val);
+       slash = strrchr(path, '/');
+       WRITE_STATUS_ITEM(b, SI_basename, "%s\n", slash? slash + 1 : path);
+       WRITE_STATUS_ITEM(b, SI_directory, "%.*s\n",
+               slash? (int)(slash - path) : (int)strlen(path), path);
 }
 
 static int print_chunk_table(struct ls_data *d, struct para_buffer *b)
@@ -795,6 +800,12 @@ static int print_chunk_table(struct ls_data *d, struct para_buffer *b)
        int ret, i;
        char *buf;
 
+       para_printf(b, "%s\nchunk_time: %lu:%lu\n", d->path,
+               (long unsigned) d->afhi.chunk_tv.tv_sec,
+               (long unsigned) d->afhi.chunk_tv.tv_usec
+       );
+       if (afh_supports_dynamic_chunks(d->afsi.audio_format_id))
+               return 0;
        ret = aft_get_row_of_hash(d->hash, &aft_row);
        if (ret < 0)
                return ret;
@@ -802,12 +813,7 @@ static int print_chunk_table(struct ls_data *d, struct para_buffer *b)
                AFTCOL_CHUNKS, &chunk_table_obj));
        if (ret < 0)
                return ret;
-       para_printf(b, "%s\n"
-               "chunk_time: %lu:%lu\nchunk_offsets: ",
-               d->path,
-               (long unsigned) d->afhi.chunk_tv.tv_sec,
-               (long unsigned) d->afhi.chunk_tv.tv_usec
-       );
+       para_printf(b, "chunk_offsets: ");
        buf = chunk_table_obj.data;
        for (
                i = 0;
@@ -833,7 +839,7 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts,
        char duration_buf[30]; /* nobody has an audio file long enough to overflow this */
        struct afs_info *afsi = &d->afsi;
        struct afh_info *afhi = &d->afhi;
-       char asc_hash[2 * HASH_SIZE + 1];
+       char asc_hash[2 * HASH2_SIZE + 1];
 
        if (opts->mode == LS_MODE_SHORT) {
                para_printf(b, "%s\n", d->path);
@@ -854,7 +860,8 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts,
                if (ret < 0)
                        goto out;
        }
-       get_duration_buf(afhi->seconds_total, duration_buf, opts);
+       get_duration_buf(afhi->seconds_total, duration_buf,
+               sizeof(duration_buf), opts);
        if (opts->mode == LS_MODE_LONG) {
                struct ls_widths *w = &opts->widths;
                if (lls_opt_given(r_a))
@@ -891,13 +898,13 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts,
                goto out;
        }
        if (opts->mode == LS_MODE_MBOX) {
-               const char *bn = para_basename(d->path);
+               const char *slash = strrchr(d->path, '/');
                para_printf(b,
                        "From foo@localhost %s\n"
                        "Received: from\nTo: bar\nFrom: a\n"
                        "Subject: %s\n\n",
                        last_played_time,
-                       bn? bn : "?");
+                       slash? slash + 1 : "?");
        }
        write_filename_items(b, d->path, lls_opt_given(r_b));
        if (lls_opt_given(r_a))
@@ -907,7 +914,7 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts,
                goto out;
        write_image_items(b, afsi);
        write_lyrics_items(b, afsi);
-       hash_to_asc(d->hash, asc_hash);
+       hash2_to_asc(d->hash, asc_hash);
        WRITE_STATUS_ITEM(b, SI_hash, "%s\n", asc_hash);
        WRITE_STATUS_ITEM(b, SI_bitrate, "%dkbit/s\n", afhi->bitrate);
        WRITE_STATUS_ITEM(b, SI_format, "%s\n",
@@ -1018,7 +1025,14 @@ out:
 /**
  * Open the audio file with highest score and set up an afd structure.
  *
- * \param afd Result pointer.
+ * This determines and opens the next audio file, verifies that it did not
+ * change by comparing the recomputed the hash value of the file contents
+ * against the value stored in the audio file table. If all goes well, it
+ * creates a shared memory area containing the serialized version of the afd
+ * structure, including the chunk table, if any. The caller can then send the
+ * ID of this area and the open fd to the server process.
+ *
+ * \param fd Result pointer for the file descriptor of the audio file.
  *
  * On success, the numplayed field of the audio file selector info is increased
  * and the lastplayed time is set to the current time. Finally, the score of
@@ -1026,37 +1040,54 @@ out:
  *
  * \return Positive shmid on success, negative on errors.
  */
-int open_and_update_audio_file(struct audio_file_data *afd)
+int open_and_update_audio_file(int *fd)
 {
-       unsigned char file_hash[HASH_SIZE];
+       unsigned char file_hash[HASH2_SIZE];
        struct osl_object afsi_obj;
        struct afs_info new_afsi;
        int ret;
        struct afsi_change_event_data aced;
        struct osl_object map, chunk_table_obj;
        struct ls_data *d = &status_item_ls_data;
+       unsigned char *tmp_hash;
+       struct audio_file_data afd;
 again:
        ret = score_get_best(&current_aft_row, &d->score);
        if (ret < 0)
                return ret;
-       ret = get_hash_of_row(current_aft_row, &d->hash);
+       /*
+        * get_hash_of_row() and get_audio_file_path_of_row() initialize
+        * their pointer argument to point to memory-mapped files. These pointers
+        * become stale after a new audio file has been added or after the
+        * server process received SIGHUP. For in both cases libosl unmaps and
+        * remaps the underlying database files, and this remapping may well
+        * change the starting address of the mapping. To avoid stale pointer
+        * references we create copies on the heap.
+        */
+       ret = get_hash_of_row(current_aft_row, &tmp_hash);
        if (ret < 0)
                return ret;
+       if (!d->hash)
+               d->hash = alloc(HASH2_SIZE);
+       memcpy(d->hash, tmp_hash, HASH2_SIZE);
+       free(d->path);
        ret = get_audio_file_path_of_row(current_aft_row, &d->path);
        if (ret < 0)
                return ret;
        PARA_NOTICE_LOG("%s\n", d->path);
+       d->path = para_strdup(d->path);
+
        ret = get_afsi_object_of_row(current_aft_row, &afsi_obj);
        if (ret < 0)
                return ret;
        ret = load_afsi(&d->afsi, &afsi_obj);
        if (ret < 0)
                return ret;
-       ret = get_afhi_of_row(current_aft_row, &afd->afhi);
+       ret = get_afhi_of_row(current_aft_row, &afd.afhi);
        if (ret < 0)
                return ret;
-       d->afhi = afd->afhi;
-       d->afhi.chunk_table = afd->afhi.chunk_table = NULL;
+       d->afhi = afd.afhi;
+       d->afhi.chunk_table = afd.afhi.chunk_table = NULL;
        ret = osl(osl_open_disk_object(audio_file_table, current_aft_row,
                AFTCOL_CHUNKS, &chunk_table_obj));
        if (ret < 0) {
@@ -1068,11 +1099,11 @@ again:
        } else {
                PARA_INFO_LOG("chunk table: %zu bytes\n", chunk_table_obj.size);
        }
-       ret = mmap_full_file(d->path, O_RDONLY, &map.data, &map.size, &afd->fd);
+       ret = mmap_full_file(d->path, O_RDONLY, &map.data, &map.size, fd);
        if (ret < 0)
                goto out;
-       hash_function(map.data, map.size, file_hash);
-       ret = hash_compare(file_hash, d->hash);
+       hash2_function(map.data, map.size, file_hash);
+       ret = hash2_compare(file_hash, d->hash);
        para_munmap(map.data, map.size);
        if (ret) {
                ret = -E_HASH_MISMATCH;
@@ -1083,8 +1114,8 @@ again:
        new_afsi.last_played = time(NULL);
        save_afsi(&new_afsi, &afsi_obj); /* in-place update */
 
-       afd->audio_format_id = d->afsi.audio_format_id;
-       load_chunk_table(&afd->afhi, &chunk_table_obj);
+       afd.audio_format_id = d->afsi.audio_format_id;
+       load_chunk_table(&afd.afhi, &chunk_table_obj);
        aced.aft_row = current_aft_row;
        aced.old_afsi = &d->afsi;
        /*
@@ -1094,9 +1125,9 @@ again:
        ret = afs_event(AFSI_CHANGE, NULL, &aced);
        if (ret < 0)
                goto out;
-       ret = save_afd(afd);
+       ret = save_afd(&afd);
 out:
-       free(afd->afhi.chunk_table);
+       free(afd.afhi.chunk_table);
        if (chunk_table_obj.data)
                osl_close_disk_object(&chunk_table_obj);
        if (ret < 0) {
@@ -1111,7 +1142,7 @@ out:
 static int ls_hash_compare(const void *a, const void *b)
 {
        struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b;
-       return memcmp(d1->hash, d2->hash, HASH_SIZE);
+       return memcmp(d1->hash, d2->hash, HASH2_SIZE);
 }
 
 static int ls_audio_format_compare(const void *a, const void *b)
@@ -1195,7 +1226,7 @@ static int sort_matching_paths(struct ls_options *options)
        int (*compar)(const void *, const void *);
        int i;
 
-       options->data_ptr = para_malloc(nmemb * sizeof(*options->data_ptr));
+       options->data_ptr = arr_alloc(nmemb, sizeof(*options->data_ptr));
        for (i = 0; i < nmemb; i++)
                options->data_ptr[i] = options->data + i;
 
@@ -1288,8 +1319,8 @@ static int prepare_ls_row(struct osl_row *row, void *ls_opts)
        if (options->num_matching_paths > options->array_size) {
                options->array_size++;
                options->array_size *= 2;
-               options->data = para_realloc(options->data, options->array_size
-                       sizeof(*options->data));
+               options->data = arr_realloc(options->data, options->array_size,
+                       sizeof(*options->data));
        }
        d = options->data + tmp;
        ret = get_afsi_of_row(aft_row, &d->afsi);
@@ -1331,28 +1362,67 @@ err:
        return ret;
 }
 
+static int mop_loop(const char *arg, struct afs_callback_arg *aca,
+               struct ls_options *opts)
+{
+       int ret;
+       char *msg;
+
+       if (!arg || strcmp(arg, ".") == 0)
+               return score_loop(prepare_ls_row, NULL, opts);
+       if (!strncmp(arg, "m/", 2)) {
+               struct mood_instance *m;
+               ret = mood_load(arg + 2, &m, &msg);
+               if (ret < 0)
+                       afs_error(aca, "%s", msg);
+               free(msg);
+               if (ret < 0)
+                       return ret;
+               ret = mood_loop(m, prepare_ls_row, opts);
+               mood_unload(m);
+               return ret;
+       }
+       if (!strncmp(arg, "p/", 2)) {
+               struct playlist_instance *pi;
+               ret = playlist_load(arg + 2, &pi, &msg);
+               if (ret < 0)
+                       afs_error(aca, "%s", msg);
+               free(msg);
+               if (ret < 0)
+                       return ret;
+               ret = playlist_loop(pi, prepare_ls_row, opts);
+               playlist_unload(pi);
+               return ret;
+       }
+       afs_error(aca, "bad mood/playlist specifier: %s\n", arg);
+       return -ERRNO_TO_PARA_ERROR(EINVAL);
+}
+
 static int com_ls_callback(struct afs_callback_arg *aca)
 {
        const struct lls_command *cmd = SERVER_CMD_CMD_PTR(LS);
        struct ls_options *opts = aca->query.data;
-       int i = 0, ret;
+       int ret;
        time_t current_time;
-       const struct lls_opt_result *r_r;
+       const struct lls_opt_result *r_r, *r_a;
+       uint32_t limit, k, n;
 
        ret = lls_deserialize_parse_result(
                (char *)aca->query.data + sizeof(*opts), cmd, &opts->lpr);
        assert(ret >= 0);
        r_r = SERVER_CMD_OPT_RESULT(LS, REVERSE, opts->lpr);
-
+       r_a = SERVER_CMD_OPT_RESULT(LS, ADMISSIBLE, opts->lpr);
        aca->pbout.flags = (opts->mode == LS_MODE_PARSER)? PBF_SIZE_PREFIX : 0;
-       if (admissible_only(opts))
-               ret = admissible_file_loop(opts, prepare_ls_row);
-       else
+       if (admissible_only(opts)) {
+               const char *arg = lls_string_val(0, r_a);
+               ret = mop_loop(arg, aca, opts);
+       } else
                ret = osl(osl_rbtree_loop(audio_file_table, AFTCOL_PATH, opts,
                        prepare_ls_row));
        if (ret < 0)
                goto out;
-       if (opts->num_matching_paths == 0) {
+       n = opts->num_matching_paths;
+       if (n == 0) {
                ret = lls_num_inputs(opts->lpr) > 0? -E_NO_MATCH : 0;
                goto out;
        }
@@ -1360,20 +1430,14 @@ static int com_ls_callback(struct afs_callback_arg *aca)
        if (ret < 0)
                goto out;
        time(&current_time);
-       if (lls_opt_given(r_r))
-               for (i = opts->num_matching_paths - 1; i >= 0; i--) {
-                       ret = print_list_item(opts->data_ptr[i], opts,
-                               &aca->pbout, current_time);
-                       if (ret < 0)
-                               goto out;
-               }
-       else
-               for (i = 0; i < opts->num_matching_paths; i++) {
-                       ret = print_list_item(opts->data_ptr[i], opts,
-                               &aca->pbout, current_time);
-                       if (ret < 0)
-                               goto out;
-               }
+       limit = SERVER_CMD_UINT32_VAL(LS, LIMIT, opts->lpr);
+       for (k = 0; k < n && (limit == 0 || k < limit); k++) {
+               uint32_t idx = lls_opt_given(r_r)? n - 1 - k : k;
+               ret = print_list_item(opts->data_ptr[idx], opts, &aca->pbout,
+                       current_time);
+               if (ret < 0)
+                       goto out;
+       }
 out:
        lls_free_parse_result(opts->lpr, cmd);
        free(opts->data);
@@ -1395,7 +1459,7 @@ static int com_ls(struct command_context *cc, struct lls_parse_result *lpr)
        ret = lls_serialize_parse_result(lpr, cmd, NULL, &query.size);
        assert(ret >= 0);
        query.size += sizeof(*opts);
-       query.data = para_malloc(query.size);
+       query.data = alloc(query.size);
        opts = query.data;
        memset(opts, 0, sizeof(*opts));
        slpr = query.data + sizeof(*opts);
@@ -1414,11 +1478,11 @@ static int com_ls(struct command_context *cc, struct lls_parse_result *lpr)
                else if (!strcmp(val, "m") || !strcmp(val, "mbox"))
                        opts->mode = LS_MODE_MBOX;
                else if (!strcmp(val, "c") || !strcmp(val, "chunk-table"))
-                       opts->mode = LS_MODE_MBOX;
+                       opts->mode = LS_MODE_CHUNKS;
                else if (!strcmp(val, "p") || !strcmp(val, "parser-friendly"))
                        opts->mode = LS_MODE_PARSER;
                else {
-                       ret = -E_AFT_SYNTAX;
+                       ret = -ERRNO_TO_PARA_ERROR(EINVAL);
                        goto out;
                }
        }
@@ -1449,7 +1513,7 @@ static int com_ls(struct command_context *cc, struct lls_parse_result *lpr)
                else if (!strcmp(val, "h") || !strcmp(val, "hash"))
                        opts->sorting = LS_SORT_BY_HASH;
                else {
-                       ret = -E_AFT_SYNTAX;
+                       ret = -ERRNO_TO_PARA_ERROR(EINVAL);
                        goto out;
                }
        }
@@ -1506,7 +1570,7 @@ enum com_add_buffer_offsets {
        /** The hash of the audio file being added. */
        CAB_HASH_OFFSET = 13,
        /** Start of the path of the audio file. */
-       CAB_PATH_OFFSET = (CAB_HASH_OFFSET + HASH_SIZE),
+       CAB_PATH_OFFSET = (CAB_HASH_OFFSET + HASH2_SIZE),
 };
 
 /*
@@ -1524,12 +1588,12 @@ static void save_add_callback_buffer(unsigned char *hash, const char *path,
        size_t afhi_size = sizeof_afhi_buf(afhi);
        size_t size = CAB_PATH_OFFSET + path_len + afhi_size
                + sizeof_chunk_table(afhi) + slpr_size;
-       char *buf = para_malloc(size);
+       char *buf = alloc(size);
        uint32_t pos;
 
        assert(size <= ~(uint32_t)0);
        write_u8(buf + CAB_AUDIO_FORMAT_ID_OFFSET, audio_format_num);
-       memcpy(buf + CAB_HASH_OFFSET, hash, HASH_SIZE);
+       memcpy(buf + CAB_HASH_OFFSET, hash, HASH2_SIZE);
        strcpy(buf + CAB_PATH_OFFSET, path);
        pos = CAB_PATH_OFFSET + path_len;
        write_u32(buf + CAB_AFHI_OFFSET_POS, pos);
@@ -1605,10 +1669,11 @@ static int com_add_callback(struct afs_callback_arg *aca)
        struct osl_row *hs;
        struct osl_object objs[NUM_AFT_COLUMNS];
        unsigned char *hash;
-       char asc[2 * HASH_SIZE + 1];
+       char asc[2 * HASH2_SIZE + 1];
        int ret;
        char afsi_buf[AFSI_SIZE];
-       char *slpr = buf + read_u32(buf + CAB_LPR_OFFSET);
+       uint32_t slpr_offset = read_u32(buf + CAB_LPR_OFFSET);
+       char *slpr = buf + slpr_offset;
        struct afs_info default_afsi = {.last_played = 0};
        uint16_t afhi_offset, chunks_offset;
        const struct lls_command *cmd = SERVER_CMD_CMD_PTR(ADD);
@@ -1620,9 +1685,9 @@ static int com_add_callback(struct afs_callback_arg *aca)
        r_v = SERVER_CMD_OPT_RESULT(ADD, VERBOSE, aca->lpr);
 
        hash = (unsigned char *)buf + CAB_HASH_OFFSET;
-       hash_to_asc(hash, asc);
+       hash2_to_asc(hash, asc);
        objs[AFTCOL_HASH].data = buf + CAB_HASH_OFFSET;
-       objs[AFTCOL_HASH].size = HASH_SIZE;
+       objs[AFTCOL_HASH].size = HASH2_SIZE;
 
        path = buf + CAB_PATH_OFFSET;
        objs[AFTCOL_PATH].data = path;
@@ -1676,6 +1741,7 @@ static int com_add_callback(struct afs_callback_arg *aca)
        /* no hs or force mode, child must have sent afhi */
        afhi_offset = read_u32(buf + CAB_AFHI_OFFSET_POS);
        chunks_offset = read_u32(buf + CAB_CHUNKS_OFFSET_POS);
+       assert(chunks_offset <= slpr_offset);
 
        objs[AFTCOL_AFHI].data = buf + afhi_offset;
        objs[AFTCOL_AFHI].size = chunks_offset - afhi_offset;
@@ -1683,14 +1749,14 @@ static int com_add_callback(struct afs_callback_arg *aca)
        if (!objs[AFTCOL_AFHI].size) /* "impossible" */
                goto out;
        objs[AFTCOL_CHUNKS].data = buf + chunks_offset;
-       objs[AFTCOL_CHUNKS].size = aca->query.size - chunks_offset;
+       objs[AFTCOL_CHUNKS].size = slpr_offset - chunks_offset;
        if (pb && !hs) { /* update pb's hash */
-               char old_asc[2 * HASH_SIZE + 1];
+               char old_asc[2 * HASH2_SIZE + 1];
                unsigned char *old_hash;
                ret = get_hash_of_row(pb, &old_hash);
                if (ret < 0)
                        goto out;
-               hash_to_asc(old_hash, old_asc);
+               hash2_to_asc(old_hash, old_asc);
                if (lls_opt_given(r_v))
                        para_printf(&aca->pbout, "file change: %s -> %s\n",
                                old_asc, asc);
@@ -1732,7 +1798,7 @@ static int com_add_callback(struct afs_callback_arg *aca)
        ret = afs_event(AUDIO_FILE_ADD, &aca->pbout, aft_row);
 out:
        if (ret < 0)
-               para_printf(&aca->pbout, "could not add %s\n", path);
+               afs_error(aca, "could not add %s\n", path);
        lls_free_parse_result(aca->lpr, cmd);
        return ret;
 }
@@ -1790,7 +1856,7 @@ static int add_one_audio_file(const char *path, void *private_data)
        struct afh_info afhi, *afhi_ptr = NULL;
        struct osl_row *pb = NULL, *hs = NULL; /* path brother/hash sister */
        struct osl_object map, obj = {.data = NULL}, query;
-       unsigned char hash[HASH_SIZE];
+       unsigned char hash[HASH2_SIZE];
        bool a_given = SERVER_CMD_OPT_GIVEN(ADD, ALL, pad->lpr);
        bool f_given = SERVER_CMD_OPT_GIVEN(ADD, FORCE, pad->lpr);
        bool l_given = SERVER_CMD_OPT_GIVEN(ADD, LAZY, pad->lpr);
@@ -1818,11 +1884,11 @@ static int add_one_audio_file(const char *path, void *private_data)
        ret = mmap_full_file(path, O_RDONLY, &map.data, &map.size, &fd);
        if (ret < 0)
                goto out_free;
-       hash_function(map.data, map.size, hash);
+       hash2_function(map.data, map.size, hash);
 
        /* Check whether the database contains a file with the same hash. */
        query.data = hash;
-       query.size = HASH_SIZE;
+       query.size = HASH2_SIZE;
        ret = send_callback_request(hash_sister_callback, &query,
                get_row_pointer_from_result, &hs);
        if (ret < 0)
@@ -1874,6 +1940,53 @@ out_free:
        return send_ret;
 }
 
+/*
+ * Call back once for each regular file below a directory.
+ *
+ * Traverse the given directory recursively and call the supplied callback for
+ * each regular file encountered. The first argument to the callback will be
+ * the path to the regular file and the second argument will be the data
+ * pointer. All file types except regular files and directories are ignored. In
+ * particular, symlinks are not followed. Subdirectories are ignored silently
+ * if the calling process has insufficient access permissions.
+ */
+static int for_each_file_in_dir(const char *dirname,
+               int (*func)(const char *, void *), void *data)
+{
+       int ret;
+       DIR *dir;
+       struct dirent *entry;
+
+       dir = opendir(dirname);
+       if (!dir)
+               return errno == EACCES? 1 : -ERRNO_TO_PARA_ERROR(errno);
+       /* scan cwd recursively */
+       while ((entry = readdir(dir))) {
+               char *tmp;
+               struct stat s;
+
+               if (!strcmp(entry->d_name, "."))
+                       continue;
+               if (!strcmp(entry->d_name, ".."))
+                       continue;
+               tmp = make_message("%s/%s", dirname, entry->d_name);
+               ret = 0;
+               if (lstat(tmp, &s) != -1) {
+                       if (S_ISREG(s.st_mode))
+                               ret = func(tmp, data);
+                       else if (S_ISDIR(s.st_mode))
+                               ret = for_each_file_in_dir(tmp, func, data);
+               }
+               free(tmp);
+               if (ret < 0)
+                       goto out;
+       }
+       ret = 1;
+out:
+       closedir(dir);
+       return ret;
+}
+
 static int com_add(struct command_context *cc, struct lls_parse_result *lpr)
 {
        int i, ret;
@@ -1950,12 +2063,12 @@ static int touch_audio_file(__a_unused struct osl_table *table,
 
        ret = get_afsi_object_of_row(row, &obj);
        if (ret < 0) {
-               para_printf(&aca->pbout, "cannot touch %s\n", name);
+               afs_error(aca, "cannot touch %s\n", name);
                return ret;
        }
        ret = load_afsi(&old_afsi, &obj);
        if (ret < 0) {
-               para_printf(&aca->pbout, "cannot touch %s\n", name);
+               afs_error(aca, "cannot touch %s\n", name);
                return ret;
        }
        new_afsi = old_afsi;
@@ -2009,7 +2122,7 @@ static int com_touch_callback(struct afs_callback_arg *aca)
                uint32_t id = lls_uint32_val(0, r_i);
                ret = img_get_name_by_id(id, NULL);
                if (ret < 0) {
-                       para_printf(&aca->pbout, "invalid image ID: %u\n", id);
+                       afs_error(aca, "invalid image ID: %u\n", id);
                        return ret;
                }
        }
@@ -2018,7 +2131,7 @@ static int com_touch_callback(struct afs_callback_arg *aca)
                uint32_t id = lls_uint32_val(0, r_y);
                ret = lyr_get_name_by_id(id, NULL);
                if (ret < 0) {
-                       para_printf(&aca->pbout, "invalid lyrics ID: %u\n", id);
+                       afs_error(aca, "invalid lyrics ID: %u\n", id);
                        return ret;
                }
        }
@@ -2061,7 +2174,7 @@ static int remove_audio_file(__a_unused struct osl_table *table,
                return ret;
        ret = osl(osl_del_row(audio_file_table, row));
        if (ret < 0)
-               para_printf(&aca->pbout, "cannot remove %s\n", name);
+               afs_error(aca, "cannot remove %s\n", name);
        return ret;
 }
 
@@ -2291,13 +2404,13 @@ static int com_setatt_callback(struct afs_callback_arg *aca)
                                goto out; /* no attribute modifier given */
                        goto set_atts;
                }
-               p = para_malloc(len);
+               p = alloc(len);
                memcpy(p, arg, len - 1);
                p[len - 1] = '\0';
                ret = get_attribute_bitnum_by_name(p, &bitnum);
                free(p);
                if (ret < 0) {
-                       para_printf(&aca->pbout, "invalid argument: %s\n", arg);
+                       afs_error(aca, "invalid argument: %s\n", arg);
                        goto out;
                }
                if (c == '+')
@@ -2487,7 +2600,7 @@ static void aft_close(void)
                        PARA_WARNING_LOG("hash lookup failure\n");
                        current_aft_row = NULL;
                } else
-                       memcpy(current_hash, p, HASH_SIZE);
+                       memcpy(current_hash, p, HASH2_SIZE);
        }
        osl_close_table(audio_file_table, OSL_MARK_CLEAN);
        audio_file_table = NULL;
@@ -2518,10 +2631,12 @@ static int aft_open(const char *dir)
                PARA_NOTICE_LOG("current audio file hash lookup: success\n");
                return 1;
        }
-       PARA_NOTICE_LOG("failed to open audio file table\n");
        audio_file_table = NULL;
-       if (ret == -OSL_ERRNO_TO_PARA_ERROR(E_OSL_NOENT))
+       if (ret == -OSL_ERRNO_TO_PARA_ERROR(E_OSL_NOENT)) {
+               PARA_WARNING_LOG("no audio file table\n");
                return 1;
+       }
+       PARA_NOTICE_LOG("failed to open audio file table\n");
        return ret;
 }
 
@@ -2579,7 +2694,8 @@ static int aft_event_handler(enum afs_events event, struct para_buffer *pb,
                ret = get_audio_file_path_of_row(current_aft_row, &path);
                if (ret < 0)
                        return ret;
-               status_item_ls_data.path = path;
+               free(status_item_ls_data.path);
+               status_item_ls_data.path = para_strdup(path);
                make_status_items();
                return 1;
        } case AFHI_CHANGE: {
@@ -2601,8 +2717,14 @@ static int aft_event_handler(enum afs_events event, struct para_buffer *pb,
                /*
                 * These events are rare. We don't bother to check whether the
                 * current status items are affected and simply recreate them
-                * every time.
+                * whenever an audio file is open.
                 */
+               if (!current_aft_row)
+                       return 0;
+               ret = get_afhi_of_row(current_aft_row,
+                       &status_item_ls_data.afhi);
+               if (ret < 0)
+                       return ret;
                make_status_items();
                return 0;
        } default:
@@ -2610,15 +2732,10 @@ static int aft_event_handler(enum afs_events event, struct para_buffer *pb,
        }
 }
 
-/**
- * Initialize the audio file table.
- *
- * \param t Pointer to the structure to be initialized.
- */
-void aft_init(struct afs_table *t)
-{
-       t->open = aft_open;
-       t->close = aft_close;
-       t->create = aft_create;
-       t->event_handler = aft_event_handler;
-}
+/** The audio file table contains information about known audio files. */
+const struct afs_table_operations aft_ops = {
+       .open = aft_open,
+       .close = aft_close,
+       .create = aft_create,
+       .event_handler = aft_event_handler,
+};
index 1d81e5d9dbabbcef772d182eabe6a03ba5a51915..af4adc46382da236e7fec5786fa3d40234f16546 100644 (file)
@@ -52,7 +52,7 @@ static int alsa_mix_open(const char *dev, struct mixer_handle **handle)
 
        PARA_INFO_LOG("snd_mixer_{open,attach,register,load}\n");
        *handle = NULL;
-       h = para_calloc(sizeof(*h));
+       h = zalloc(sizeof(*h));
        h->card = para_strdup(dev? dev : "hw:0");
        ret = snd_mixer_open(&h->mixer, 0);
        if (ret < 0) {
index 363e39301651bc47881f850f0235c13f6a016c62..53d7b1b454932c36b3e578d794d688cee6cf7270 100644 (file)
@@ -48,7 +48,7 @@ struct private_alsa_write_data {
        /* time until buffer underrun occurs, in milliseconds */
        unsigned buffer_time;
        struct timeval drain_barrier;
-       /* File descriptor for select(). */
+       /* File descriptor to monitor for reading. */
        int poll_fd;
 };
 
@@ -202,7 +202,7 @@ out:
        return ret;
 }
 
-static void alsa_write_pre_select(struct sched *s, void *context)
+static void alsa_write_pre_monitor(struct sched *s, void *context)
 {
        struct pollfd pfd;
        struct writer_node *wn = context;
@@ -230,7 +230,7 @@ static void alsa_write_pre_select(struct sched *s, void *context)
                return;
        }
        pad->poll_fd = pfd.fd;
-       para_fd_set(pfd.fd, &s->rfds, &s->max_fileno);
+       sched_monitor_readfd(pfd.fd, s);
 }
 
 static void alsa_close(struct writer_node *wn)
@@ -240,6 +240,8 @@ static void alsa_close(struct writer_node *wn)
 
        if (!pad)
                return;
+       if (!pad->handle)
+               goto free_pad;
        /*
         * It's OK to have a blocking operation here because we already made
         * sure that the PCM output buffer is (nearly) empty.
@@ -248,10 +250,11 @@ static void alsa_close(struct writer_node *wn)
        snd_pcm_drain(pad->handle);
        snd_pcm_close(pad->handle);
        snd_config_update_free_global();
+free_pad:
        free(pad);
 }
 
-static int alsa_write_post_select(__a_unused struct sched *s, void *context)
+static int alsa_write_post_monitor(__a_unused struct sched *s, void *context)
 {
        struct writer_node *wn = context;
        struct private_alsa_write_data *pad = wn->private_data;
@@ -266,13 +269,15 @@ static int alsa_write_post_select(__a_unused struct sched *s, void *context)
                goto err;
 again:
        ret = btr_node_status(btrn, wn->min_iqs, BTR_NT_LEAF);
+       if (ret < 0)
+               goto err;
        if (ret == 0)
                return 0;
        btr_merge(btrn, wn->min_iqs);
        bytes = btr_next_buffer(btrn, &data);
        if (ret < 0 || bytes < wn->min_iqs) { /* eof */
                assert(btr_no_parent(btrn));
-               ret = -E_WRITE_COMMON_EOF;
+               ret = -E_EOF;
                if (!pad)
                        goto err;
                /* wait until pending frames are played */
@@ -291,22 +296,24 @@ again:
 
                if (bytes == 0) /* no data available */
                        return 0;
-               pad = wn->private_data = para_calloc(sizeof(*pad));
-               get_btr_sample_rate(btrn, &val);
+               pad = wn->private_data = zalloc(sizeof(*pad));
+               ret = get_btr_sample_rate(btrn, &val);
+               if (ret < 0)
+                       goto err;
                pad->sample_rate = val;
-               get_btr_channels(btrn, &val);
+               ret = get_btr_channels(btrn, &val);
+               if (ret < 0)
+                       goto err;
                pad->channels = val;
-               get_btr_sample_format(btrn, &val);
+               ret = get_btr_sample_format(btrn, &val);
+               if (ret < 0)
+                       goto err;
                pad->sample_format = get_alsa_pcm_format(val);
-
                PARA_INFO_LOG("%u channel(s), %uHz\n", pad->channels,
                        pad->sample_rate);
                ret = alsa_init(wn);
-               if (ret < 0) {
-                       free(wn->private_data);
-                       wn->private_data = NULL;
+               if (ret < 0)
                        goto err;
-               }
                wn->min_iqs = pad->bytes_per_frame;
                goto again;
        }
@@ -314,7 +321,7 @@ again:
        frames = snd_pcm_writei(pad->handle, data, frames);
        if (frames == 0 || frames == -EAGAIN) {
                char buf[100];
-               if (pad->poll_fd >= 0 && FD_ISSET(pad->poll_fd, &s->rfds))
+               if (pad->poll_fd >= 0 && sched_read_ok(pad->poll_fd, s))
                        if (read(pad->poll_fd, buf, 100))
                                do_nothing;
                return 0;
@@ -324,7 +331,11 @@ again:
                goto again;
        }
        if (frames == -EPIPE) {
-               PARA_WARNING_LOG("underrun (tried to write %zu bytes)\n", bytes);
+               snd_pcm_status_t *status;
+               snd_pcm_status_malloc(&status);
+               if (snd_pcm_status_get_state(status) == SND_PCM_STATE_XRUN)
+                       PARA_WARNING_LOG("underrun\n");
+               snd_pcm_status_free(status);
                snd_pcm_prepare(pad->handle);
                return 0;
        }
@@ -338,7 +349,7 @@ err:
 
 struct writer lsg_write_cmd_com_alsa_user_data = {
 
-       .pre_select = alsa_write_pre_select,
-       .post_select = alsa_write_post_select,
+       .pre_monitor = alsa_write_pre_monitor,
+       .post_monitor = alsa_write_post_monitor,
        .close = alsa_close,
 };
index 61b1653ea069ce479b6b609da5703fc57bc717c1..be69ad67a8d78d419f3147546785130e42c7b80b 100644 (file)
@@ -29,7 +29,7 @@ static void amp_close(struct filter_node *fn)
 
 static void amp_open(struct filter_node *fn)
 {
-       struct private_amp_data *pad = para_calloc(sizeof(*pad));
+       struct private_amp_data *pad = zalloc(sizeof(*pad));
        unsigned given = FILTER_CMD_OPT_GIVEN(AMP, AMP, fn->lpr);
        uint32_t amp_arg = FILTER_CMD_OPT_UINT32_VAL(AMP, AMP, fn->lpr);
 
@@ -43,7 +43,7 @@ static void amp_open(struct filter_node *fn)
                pad->amp, pad->amp / 64.0 + 1.0);
 }
 
-static int amp_post_select(__a_unused struct sched *s, void *context)
+static int amp_post_monitor(__a_unused struct sched *s, void *context)
 {
        struct filter_node *fn = context;
        struct private_amp_data *pad = fn->private_data;
@@ -67,14 +67,14 @@ next_buffer:
        in_bytes = btr_next_buffer(btrn, (char **)&in);
        len = in_bytes / 2;
        if (len == 0) { /* eof and in_bytes == 1 */
-               ret = -E_AMP_EOF;
+               ret = -E_EOF;
                goto err;
        }
 
        if (inplace)
                out = in;
        else
-               out = para_malloc(len * 2);
+               out = alloc(len * 2);
 
        for (i = 0; i < len; i++) {
                int x = (in[i] * factor) >> 6;
@@ -100,6 +100,6 @@ err:
 const struct filter lsg_filter_cmd_com_amp_user_data = {
        .open = amp_open,
        .close = amp_close,
-       .pre_select = generic_filter_pre_select,
-       .post_select = amp_post_select,
+       .pre_monitor = generic_filter_pre_monitor,
+       .post_monitor = amp_post_monitor,
 };
index 447dea84c00a468330fe8229bfd70b74292264bd..43a58dd6d5a364f2370659608302e6c7d9f24324 100644 (file)
@@ -42,7 +42,7 @@ static void aow_close(struct writer_node *wn)
        ao_shutdown();
 }
 
-static void aow_pre_select(struct sched *s, void *context)
+static void aow_pre_monitor(struct sched *s, void *context)
 {
        struct writer_node *wn = context;
        struct private_aow_data *pawd = wn->private_data;
@@ -184,7 +184,7 @@ static int aow_init(struct writer_node *wn, unsigned sample_rate,
        ao_info *info;
        const struct lls_opt_result *r;
        unsigned n;
-       struct private_aow_data *pawd = para_malloc(sizeof(*pawd));
+       struct private_aow_data *pawd = alloc(sizeof(*pawd));
 
        ao_initialize();
        aow_show_drivers();
@@ -266,7 +266,7 @@ static void *aow_play(void *priv)
                                if (frames > 0)
                                        break;
                                /* eof and less than a single frame available */
-                               ret = -E_WRITE_COMMON_EOF;
+                               ret = -E_EOF;
                                goto fail;
                        }
                        /*
@@ -342,7 +342,7 @@ fail:
        return -E_AO_PTHREAD;
 }
 
-static int aow_post_select(__a_unused struct sched *s, void *context)
+static int aow_post_monitor(__a_unused struct sched *s, void *context)
 {
        struct writer_node *wn = context;
        struct private_aow_data *pawd = wn->private_data;
@@ -357,9 +357,15 @@ static int aow_post_select(__a_unused struct sched *s, void *context)
                        goto remove_btrn;
                if (ret == 0)
                        return 0;
-               get_btr_sample_rate(wn->btrn, &rate);
-               get_btr_channels(wn->btrn, &ch);
-               get_btr_sample_format(wn->btrn, &format);
+               ret = get_btr_sample_rate(wn->btrn, &rate);
+               if (ret < 0)
+                       goto remove_btrn;
+               ret = get_btr_channels(wn->btrn, &ch);
+               if (ret < 0)
+                       goto remove_btrn;
+               ret = get_btr_sample_format(wn->btrn, &format);
+               if (ret < 0)
+                       goto remove_btrn;
                ret = aow_init(wn, rate, ch, format);
                if (ret < 0)
                        goto remove_btrn;
@@ -382,7 +388,7 @@ static int aow_post_select(__a_unused struct sched *s, void *context)
        if (!wn->btrn) {
                if (!pawd->thread_btrn) {
                        pthread_join(pawd->thread, NULL);
-                       return -E_AO_EOF;
+                       return -E_EOF;
                }
                PARA_INFO_LOG("waiting for play thread to terminate\n");
                return 0;
@@ -415,7 +421,7 @@ out:
 
 struct writer lsg_write_cmd_com_ao_user_data = {
        .close = aow_close,
-       .pre_select = aow_pre_select,
-       .post_select = aow_post_select,
+       .pre_monitor = aow_pre_monitor,
+       .post_monitor = aow_post_monitor,
 };
 
index f7091727bc5b62518f91436de9428ba6c14b5a86..51630b257f7d88829c657361b06c1fd09d008326 100644 (file)
@@ -104,7 +104,7 @@ int get_attribute_bitnum_by_name(const char *att_name, unsigned char *bitnum)
        return 1;
 }
 
-/** Data passed to the action function of lsatt */
+/* Data passed to the action function of lsatt */
 static int print_attribute(struct osl_table *table, struct osl_row *row,
                const char *name, void *data)
 {
@@ -119,7 +119,7 @@ static int print_attribute(struct osl_table *table, struct osl_row *row,
        }
        ret = osl(osl_get_object(table, row, ATTCOL_BITNUM, &bitnum_obj));
        if (ret < 0) {
-               para_printf(&aca->pbout, "%s: %s\n", name, para_strerror(-ret));
+               afs_error(aca, "%s: %s\n", name, para_strerror(-ret));
                return ret;
        }
        para_printf(&aca->pbout, "%u\t%s\n", *(unsigned char*)bitnum_obj.data,
@@ -168,11 +168,6 @@ static int com_lsatt(struct command_context *cc, struct lls_parse_result *lpr)
 }
 EXPORT_SERVER_CMD_HANDLER(lsatt);
 
-struct addatt_event_data {
-       const char *name;
-       unsigned char bitnum;
-};
-
 static int com_addatt_callback(struct afs_callback_arg *aca)
 {
        const struct lls_command *cmd = SERVER_CMD_CMD_PTR(ADDATT);
@@ -188,12 +183,10 @@ static int com_addatt_callback(struct afs_callback_arg *aca)
                struct osl_object objs[NUM_ATT_COLUMNS];
                struct osl_row *row;
                unsigned char bitnum;
-               struct addatt_event_data aed;
 
                len = strlen(name);
                if (len == 0 || name[len - 1] == '-' || name[len - 1] == '+') {
-                       para_printf(&aca->pbout,
-                               "invalid attribute name: %s\n", name);
+                       afs_error(aca, "invalid attribute name: %s\n", name);
                        continue;
                }
                ret = get_attribute_bitnum_by_name(name, &bitnum);
@@ -225,17 +218,16 @@ static int com_addatt_callback(struct afs_callback_arg *aca)
                ret = osl(osl_add_row(attribute_table, objs));
                if (ret < 0)
                        goto out;
-               aed.name = name;
-               aed.bitnum = bitnum;
-               ret = afs_event(ATTRIBUTE_ADD, &aca->pbout, &aed);
+               ret = afs_event(ATTRIBUTE_ADD, &aca->pbout, NULL);
                if (ret < 0)
                        goto out;
                greatest_att_bitnum = PARA_MAX(greatest_att_bitnum, (int)bitnum);
        }
 out:
        if (ret < 0)
-               para_printf(&aca->pbout, "error while adding %s\n",
+               afs_error(aca, "error while adding %s\n",
                        lls_input(i, aca->lpr));
+       lls_free_parse_result(aca->lpr, cmd);
        return ret;
 }
 
@@ -276,7 +268,7 @@ static int com_mvatt_callback(struct afs_callback_arg *aca)
        ret = osl(osl_update_object(attribute_table, row, ATTCOL_NAME, &obj));
 out:
        if (ret < 0)
-               para_printf(&aca->pbout, "cannot rename %s to %s\n", old, new);
+               afs_error(aca, "cannot rename %s to %s\n", old, new);
        else
                ret = afs_event(ATTRIBUTE_RENAME, &aca->pbout, NULL);
        lls_free_parse_result(aca->lpr, cmd);
@@ -305,13 +297,13 @@ static int remove_attribute(struct osl_table *table, struct osl_row *row,
 
        ret = get_attribute_bitnum_by_name(name, &red.bitnum);
        if (ret < 0) {
-               para_printf(&aca->pbout, "cannot remove %s\n", name);
+               afs_error(aca, "cannot remove %s\n", name);
                return ret;
        }
        para_printf(&aca->pbout, "removing attribute %s\n", name);
        ret = osl(osl_del_row(table, row));
        if (ret < 0) {
-               para_printf(&aca->pbout, "cannot remove %s\n", name);
+               afs_error(aca, "cannot remove %s\n", name);
                return ret;
        }
        return afs_event(ATTRIBUTE_REMOVE, &aca->pbout, &red);
@@ -469,26 +461,12 @@ int attribute_check_callback(struct afs_callback_arg *aca)
        return aft_check_attributes(att_mask, &aca->pbout);
 }
 
-/**
- * Close the attribute table.
- *
- * \sa \ref osl_close_table().
- */
 static void attribute_close(void)
 {
        osl_close_table(attribute_table, OSL_MARK_CLEAN);
        attribute_table = NULL;
 }
 
-/**
- * Open the attribute table.
- *
- * \param dir The database directory.
- *
- * \return Positive on success, negative on errors.
- *
- * \sa \ref osl_open_table().
- */
 static int attribute_open(const char *dir)
 {
        int ret;
@@ -512,14 +490,9 @@ static int attribute_create(const char *dir)
        return osl(osl_create_table(&attribute_table_desc));
 }
 
-/**
- * Initialize the attribute table structure.
- *
- * \param t The table structure to initialize.
- */
-void attribute_init(struct afs_table *t)
-{
-       t->open = attribute_open;
-       t->close = attribute_close;
-       t->create = attribute_create;
-}
+/** The attribute table stores name/bitnum pairs. */
+const struct afs_table_operations attr_ops = { /* no event handler */
+       .open = attribute_open,
+       .close = attribute_close,
+       .create = attribute_create,
+};
index af67063367044b675877173a1ed351bdd28f2dd1..f2e4cb91de228dcd78cae534e08d76e8514b6d88 100644 (file)
--- a/audioc.c
+++ b/audioc.c
@@ -107,6 +107,12 @@ static void help_completer(struct i9e_completion_info *ci,
        cr->matches = i9e_complete_commands(ci->word, audiod_completers);
 }
 
+static void ll_completer(struct i9e_completion_info *ci,
+               struct i9e_completion_result *cr)
+{
+       i9e_ll_completer(ci, cr);
+}
+
 static void version_completer(struct i9e_completion_info *ci,
                struct i9e_completion_result *cr)
 {
@@ -143,17 +149,17 @@ static struct i9e_completer audiod_completers[] = {
        {.name = NULL}
 };
 
-static void audioc_pre_select(struct sched *s, void *context)
+static void audioc_pre_monitor(struct sched *s, void *context)
 {
        struct audioc_task *at = context;
        int ret = btr_node_status(at->btrn, 0, BTR_NT_ROOT);
 
        if (ret < 0)
                sched_min_delay(s);
-       para_fd_set(at->fd, &s->rfds, &s->max_fileno);
+       sched_monitor_readfd(at->fd, s);
 }
 
-static int audioc_post_select(struct sched *s, void *context)
+static int audioc_post_monitor(struct sched *s, void *context)
 {
        char *buf = NULL;
        struct audioc_task *at = context;
@@ -162,14 +168,14 @@ static int audioc_post_select(struct sched *s, void *context)
 
        if (ret < 0)
                goto out;
-       if (!FD_ISSET(at->fd, &s->rfds))
+       if (!sched_read_ok(at->fd, s))
                return 0;
        bufsize = PARA_MAX(1024U, OPT_UINT32_VAL(BUFSIZE));
-       buf = para_malloc(bufsize);
+       buf = alloc(bufsize);
        ret = recv_bin_buffer(at->fd, buf, bufsize);
        PARA_DEBUG_LOG("recv: %d\n", ret);
        if (ret == 0)
-               ret = -E_AUDIOC_EOF;
+               ret = -E_EOF;
        if (ret < 0)
                goto out;
        btr_add_output(buf, ret, at->btrn);
@@ -211,8 +217,8 @@ static int audioc_i9e_line_handler(char *line)
                EMBRACE(.name = "audioc line handler"));
        at->task = task_register(&(struct task_info) {
                .name = "audioc",
-               .pre_select = audioc_pre_select,
-               .post_select = audioc_post_select,
+               .pre_monitor = audioc_pre_monitor,
+               .post_monitor = audioc_post_monitor,
                .context = at,
        }, &sched);
        i9e_attach_to_stdout(at->btrn);
@@ -250,9 +256,9 @@ __noreturn static void interactive_session(void)
        sigemptyset(&act.sa_mask);
        act.sa_flags = 0;
        sigaction(SIGINT, &act, NULL);
-       sched.select_function = i9e_select;
+       sched.poll_function = i9e_poll;
 
-       sched.default_timeout.tv_sec = 1;
+       sched.default_timeout = 1000;
        ret = i9e_open(&ici, &sched);
        if (ret < 0)
                goto out;
@@ -364,7 +370,7 @@ int main(int argc, char *argv[])
        if (ret < 0)
                goto out;
        bufsize = PARA_MAX(1024U, OPT_UINT32_VAL(BUFSIZE));
-       buf = para_malloc(bufsize);
+       buf = alloc(bufsize);
        do {
                size_t n = ret = recv_bin_buffer(fd, buf, bufsize);
                if (ret <= 0)
index a5a774376b37c50eba92a76a02b1389bd6df67f4..7c223995a9a29cec3c8e8ae5497c3be58655f63c 100644 (file)
--- a/audiod.c
+++ b/audiod.c
@@ -44,8 +44,6 @@ static struct lls_parse_result *lpr;
 #define OPT_GIVEN(_name) (lls_opt_given(OPT_RESULT(_name)))
 #define OPT_STRING_VAL(_name) (lls_string_val(0, OPT_RESULT(_name)))
 #define OPT_UINT32_VAL(_name) (lls_uint32_val(0, OPT_RESULT(_name)))
-#define ENUM_STRING_VAL(_name) (lls_enum_string_val(OPT_UINT32_VAL(_name), \
-       lls_opt(LSG_AUDIOD_PARA_AUDIOD_OPT_ ## _name, CMD_PTR)))
 
 __printf_2_3 void (*para_log)(int, const char*, ...) = daemon_log;
 /** define the array containing all supported audio formats */
@@ -123,7 +121,7 @@ enum vss_status_flags {
  * This is needed also in audiod_command.c (for the tasks command), so it can
  * not be made static.
  */
-struct sched sched = {.max_fileno = 0};
+struct sched sched = {.timeout = 0};
 
 /* The task for obtaining para_server's status (para_client stat). */
 struct status_task {
@@ -292,7 +290,7 @@ static int get_play_time_slot_num(void)
  *
  * \return A string that must be freed by the caller.
  */
-char *get_time_string(void)
+__malloc char *get_time_string(void)
 {
        int ret, seconds = 0, length = stat_task->length_seconds;
        struct timeval *tmp, sum, sss, /* server stream start */
@@ -389,11 +387,11 @@ static void parse_config_or_die(void)
                        para_strerror(-ret));
                exit(EXIT_FAILURE);
        }
-       daemon_set_loglevel(ENUM_STRING_VAL(LOGLEVEL));
+       daemon_set_loglevel(OPT_UINT32_VAL(LOGLEVEL));
        n = OPT_GIVEN(USER_ALLOW);
        if (n == 0)
                return;
-       uid_whitelist = para_malloc(n * sizeof(uid_t));
+       uid_whitelist = arr_alloc(n, sizeof(uid_t));
        for (i = 0; i < n; i++) {
                const char *arg = lls_string_val(i, OPT_RESULT(USER_ALLOW));
                int32_t val;
@@ -445,7 +443,8 @@ static void close_receiver(int slot_num)
        task_reap(&s->receiver_node->task);
        free(s->receiver_node);
        s->receiver_node = NULL;
-       stat_task->current_audio_format_num = -1;
+       if (audiod_status == AUDIOD_ON)
+               stat_task->current_audio_format_num = -1;
        tv_add(now, &(struct timeval)EMBRACE(0, 200 * 1000),
                &a->restart_barrier);
 }
@@ -479,6 +478,23 @@ static void close_writers(struct slot_info *s)
        s->wns = NULL;
 }
 
+static void notify_writers(int error)
+{
+       int i;
+
+       FOR_EACH_SLOT(i) {
+               struct slot_info *s = slot + i;
+               struct audio_format_info *a;
+               int j;
+
+               if (s->format < 0)
+                       continue;
+               a = afi + s->format;
+               for (j = 0; j < a->num_writers; j++)
+                       task_notify(s->wns[j].task, error);
+       }
+}
+
 static void close_filters(struct slot_info *s)
 {
        int i;
@@ -546,7 +562,7 @@ static void open_filters(struct slot_info *s)
                return;
        PARA_INFO_LOG("opening %s filters\n", audio_formats[s->format]);
        assert(s->fns == NULL);
-       s->fns = para_calloc(nf * sizeof(struct filter_node));
+       s->fns = zalloc(nf * sizeof(struct filter_node));
        parent = s->receiver_node->btrn;
        for (i = 0; i < nf; i++) {
                char buf[20];
@@ -566,8 +582,8 @@ static void open_filters(struct slot_info *s)
                sprintf(buf, "%s (slot %d)", name, (int)(s - slot));
                fn->task = task_register(&(struct task_info) {
                        .name = buf,
-                       .pre_select = f->pre_select,
-                       .post_select = f->post_select,
+                       .pre_monitor = f->pre_monitor,
+                       .post_monitor = f->post_monitor,
                        .context = fn,
                }, &sched);
                parent = fn->btrn;
@@ -584,7 +600,7 @@ static void open_writers(struct slot_info *s)
        struct btr_node *parent = s->fns[a->num_filters - 1].btrn;
 
        assert(s->wns == NULL);
-       s->wns = para_calloc(PARA_MAX(1U, a->num_writers)
+       s->wns = zalloc(PARA_MAX(1U, a->num_writers)
                * sizeof(struct writer_node));
        for (i = 0; i < a->num_writers; i++) {
                wn = s->wns + i;
@@ -611,7 +627,7 @@ static int open_receiver(int format)
        if (ret < 0)
                return ret;
        slot_num = ret;
-       rn = para_calloc(sizeof(*rn));
+       rn = zalloc(sizeof(*rn));
        rn->receiver = r;
        rn->lpr = a->receiver_lpr;
        rn->btrn = btr_new_node(&(struct btr_node_description)
@@ -630,8 +646,8 @@ static int open_receiver(int format)
                audio_formats[format], name, slot_num);
        rn->task = task_register(&(struct task_info) {
                .name = name,
-               .pre_select = r->pre_select,
-               .post_select = r->post_select,
+               .pre_monitor = r->pre_monitor,
+               .post_monitor = r->post_monitor,
                .context = rn,
        }, &sched);
        return slot_num;
@@ -731,8 +747,8 @@ static void compute_time_diff(const struct timeval *status_time)
        if (count > 5) {
                int s = tv_diff(&diff, &stat_task->sa_time_diff, &tmp);
                if (tv_diff(&max_deviation, &tmp, NULL) < 0)
-                       PARA_WARNING_LOG("time diff jump: %lums\n",
-                               s * tv2ms(&tmp));
+                       PARA_WARNING_LOG("time diff jump: %c%lums\n",
+                               s < 0? '-' : '+', tv2ms(&tmp));
        }
        count++;
        sa_time_diff_sign = tv_convex_combination(
@@ -801,7 +817,7 @@ static int parse_stream_command(const char *txt, const char **cmd)
                return -E_MISSING_COLON;
        *cmd = p + 1;
        len = p - txt;
-       re = malloc(len + 1);
+       re = alloc(len + 1);
        strncpy(re, txt, len);
        re[len] = '\0';
        ret = get_matching_audio_format_nums(re);
@@ -817,12 +833,9 @@ static int add_filter(int format, const char *cmdline)
        struct lls_parse_result *flpr;
 
        filter_num = filter_setup(cmdline, &cfg, &flpr);
-       a->filter_lpr = para_realloc(a->filter_lpr,
-               (nf + 1) * sizeof(flpr));
-       a->filter_conf = para_realloc(a->filter_conf,
-               (nf + 1) * sizeof(void *));
-       a->filter_nums = para_realloc(a->filter_nums,
-               (nf + 1) * sizeof(unsigned));
+       a->filter_lpr = arr_realloc(a->filter_lpr, nf + 1, sizeof(flpr));
+       a->filter_conf = arr_realloc(a->filter_conf, nf + 1, sizeof(void *));
+       a->filter_nums = arr_realloc(a->filter_nums, nf + 1, sizeof(unsigned));
 
        a->filter_nums[nf] = filter_num;
        a->filter_conf[nf] = cfg;
@@ -868,8 +881,8 @@ static int parse_writer_args(void)
                if (a->num_writers > 0)
                        continue; /* already set up */
                a->num_writers = 1;
-               a->wids = para_malloc(sizeof(int));
-               a->writer_lpr = para_malloc(sizeof(struct lls_parse_result *));
+               a->wids = alloc(sizeof(int));
+               a->writer_lpr = alloc(sizeof(struct lls_parse_result *));
                a->wids[0] = check_writer_arg_or_die(NULL, a->writer_lpr);
                PARA_INFO_LOG("%s writer: %s (default)\n", audio_formats[i],
                        writer_name(a->wids[0]));
@@ -945,7 +958,7 @@ static int init_default_filters(void)
                 */
                if (strcmp(name, "udp") == 0 || strcmp(name, "dccp") == 0) {
                        tmp = para_strdup("fecdec");
-                       add_filter(i, tmp);
+                       ret = add_filter(i, tmp);
                        free(tmp);
                        if (ret < 0)
                                goto out;
@@ -1037,7 +1050,7 @@ static void init_local_socket(struct command_task *ct)
        exit(EXIT_FAILURE);
 }
 
-static int signal_post_select(struct sched *s, void *context)
+static int signal_post_monitor(struct sched *s, void *context)
 {
        struct signal_task *st = context;
        int ret, signum;
@@ -1045,25 +1058,25 @@ static int signal_post_select(struct sched *s, void *context)
        ret = task_get_notification(st->task);
        if (ret < 0)
                return ret;
-       signum = para_next_signal(&s->rfds);
+       signum = para_next_signal();
        switch (signum) {
        case SIGINT:
        case SIGTERM:
        case SIGHUP:
-               PARA_NOTICE_LOG("received signal %d\n", signum);
+               PARA_WARNING_LOG("terminating on signal %d\n", signum);
                task_notify_all(s, E_AUDIOD_SIGNAL);
                return -E_AUDIOD_SIGNAL;
        }
        return 0;
 }
 
-static void command_pre_select(struct sched *s, void *context)
+static void command_pre_monitor(struct sched *s, void *context)
 {
        struct command_task *ct = context;
-       para_fd_set(ct->fd, &s->rfds, &s->max_fileno);
+       sched_monitor_readfd(ct->fd, s);
 }
 
-static int command_post_select(struct sched *s, void *context)
+static int command_post_monitor(struct sched *s, void *context)
 {
        int ret;
        struct command_task *ct = context;
@@ -1074,9 +1087,9 @@ static int command_post_select(struct sched *s, void *context)
        ret = task_get_notification(ct->task);
        if (ret < 0)
                return ret;
-       ret = handle_connect(ct->fd, &s->rfds);
+       ret = dispatch_local_connection(ct->fd);
        if (ret < 0) {
-               PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+               PARA_NOTICE_LOG("%s\n", para_strerror(-ret));
                if (ret == -E_AUDIOD_TERM) {
                        task_notify_all(s, -ret);
                        return ret;
@@ -1114,8 +1127,8 @@ static void init_command_task(struct command_task *ct)
 
        ct->task = task_register(&(struct task_info) {
                .name = "command",
-               .pre_select = command_pre_select,
-               .post_select = command_post_select,
+               .pre_monitor = command_pre_monitor,
+               .post_monitor = command_post_monitor,
                .context = ct,
        }, &sched);
 }
@@ -1217,8 +1230,9 @@ static void start_stop_decoders(void)
        struct slot_info *sl;
 
        close_unused_slots();
-       if (audiod_status != AUDIOD_ON ||
-                       !(stat_task->vss_status & VSS_STATUS_FLAG_PLAYING))
+       if (audiod_status != AUDIOD_ON)
+               return notify_writers(E_NOT_PLAYING);
+       if (!(stat_task->vss_status & VSS_STATUS_FLAG_PLAYING))
                return notify_receivers(E_NOT_PLAYING);
        if (!must_start_decoder())
                return;
@@ -1235,7 +1249,7 @@ static void start_stop_decoders(void)
        audiod_status_dump(true);
 }
 
-static void status_pre_select(struct sched *s, void *context)
+static void status_pre_monitor(struct sched *s, void *context)
 {
        struct status_task *st = context;
        int i, ret, cafn = stat_task->current_audio_format_num;
@@ -1267,7 +1281,7 @@ min_delay:
 }
 
 /* restart the client task if necessary */
-static int status_post_select(struct sched *s, void *context)
+static int status_post_monitor(struct sched *s, void *context)
 {
        struct status_task *st = context;
        int ret;
@@ -1358,8 +1372,8 @@ static void init_status_task(struct status_task *st)
 
        stat_task->task = task_register(&(struct task_info) {
                .name = "stat",
-               .pre_select = status_pre_select,
-               .post_select = status_post_select,
+               .pre_monitor = status_pre_monitor,
+               .post_monitor = status_post_monitor,
                .context = stat_task,
        }, &sched);
 }
@@ -1443,7 +1457,7 @@ int main(int argc, char *argv[])
        ret = lls(lls_parse(argc, argv, CMD_PTR, &lpr, &errctx));
        if (ret < 0)
                goto out;
-       daemon_set_loglevel(ENUM_STRING_VAL(LOGLEVEL));
+       daemon_set_loglevel(OPT_UINT32_VAL(LOGLEVEL));
        daemon_drop_privileges_or_die(OPT_STRING_VAL(USER),
                OPT_STRING_VAL(GROUP));
        version_handle_flag("audiod", OPT_GIVEN(VERSION));
@@ -1486,13 +1500,12 @@ int main(int argc, char *argv[])
 
        signal_task->task = task_register(&(struct task_info) {
                .name = "signal",
-               .pre_select = signal_pre_select,
-               .post_select = signal_post_select,
+               .pre_monitor = signal_pre_monitor,
+               .post_monitor = signal_post_monitor,
                .context = signal_task,
        }, &sched);
 
-       sched.default_timeout.tv_sec = 2;
-       sched.default_timeout.tv_usec = 999 * 1000;
+       sched.default_timeout = 2999;
        ret = schedule(&sched);
        audiod_cleanup();
        sched_shutdown(&sched);
index b40fdd6743faa4650888c2bdf69756d6c12e40d8..39beda1bd4223d145fb327f07be4c5e5d4845d94 100644 (file)
--- a/audiod.h
+++ b/audiod.h
@@ -15,11 +15,11 @@ extern int audiod_status;
 struct btr_node *audiod_get_btr_root(void);
 __malloc char *audiod_get_decoder_flags(void);
 void clear_and_dump_items(void);
-char *get_time_string(void);
+__malloc char *get_time_string(void);
 bool uid_is_whitelisted(uid_t uid);
 
 /* defined in audiod_command.c */
 void audiod_status_dump(bool force);
 void close_stat_clients(void);
-int handle_connect(int accept_fd, fd_set *rfds);
+int dispatch_local_connection(int accept_fd);
 void stat_client_write_item(int item_num);
index bb54dfab87f7965a18f6cccbe6bc4077c147fa29..5f0b35a5dc0f3e4f02d134faa2ef828c6b357763 100644 (file)
@@ -81,27 +81,12 @@ static int num_clients;
 /** The list of all status items used by para_{server,audiod,gui}. */
 const char *status_item_list[] = {STATUS_ITEMS};
 
-static void dump_stat_client_list(void)
-{
-       struct stat_client *sc;
-
-       list_for_each_entry(sc, &client_list, node)
-               PARA_INFO_LOG("stat client on fd %d\n", sc->fd);
-}
-/**
- * Add a status client to the list.
- *
- * \param fd The file descriptor of the client.
- * \param mask Bitfield of status items for this client.
- * \param parser_friendly Enable parser-friendly output mode.
- *
- * Only those status items having the bit set in \a mask will be
- * sent to the client.
+/*
+ * Add a status client to the global client list and increment num_clients.
  *
- * \return Positive value on success, or -E_TOO_MANY_CLIENTS if
- * the number of connected clients exceeds #MAX_STAT_CLIENTS.
+ * The mask parameter specifies which status items are sent to the client.
  */
-static int stat_client_add(int fd, uint64_t mask, int parser_friendly)
+static int stat_client_add(int fd, uint64_t mask, bool parser_friendly)
 {
        struct stat_client *new_client;
        int ret;
@@ -114,14 +99,13 @@ static int stat_client_add(int fd, uint64_t mask, int parser_friendly)
        ret = dup(fd);
        if (ret < 0)
                return -ERRNO_TO_PARA_ERROR(errno);
-       new_client = para_calloc(sizeof(*new_client));
+       new_client = zalloc(sizeof(*new_client));
        new_client->fd = ret;
        PARA_INFO_LOG("adding client on fd %d\n", new_client->fd);
        new_client->item_mask = mask;
        if (parser_friendly)
                new_client->flags = SCF_PARSER_FRIENDLY;
        para_list_add(&new_client->node, &client_list);
-       dump_stat_client_list();
        num_clients++;
        return 1;
 }
@@ -180,20 +164,12 @@ void stat_client_write_item(int item_num)
                        continue;
                /* write error or short write */
                close_stat_client(sc);
-               dump_stat_client_list();
        }
        free(pb.buf);
        free(pfpb.buf);
 }
 
-/**
- * Check if string is a known status item.
- *
- * \param item Buffer containing the text to check.
- *
- * \return If \a item is a valid status item, the number of that status item is
- * returned. Otherwise, this function returns \p -E_UNKNOWN_STAT_ITEM.
- */
+/* Check if the given string is a known status item and return its index. */
 static int stat_item_valid(const char *item)
 {
        int i;
@@ -240,6 +216,42 @@ static int com_help(int fd, struct lls_parse_result *lpr)
 }
 EXPORT_AUDIOD_CMD_HANDLER(help)
 
+static int com_ll(int fd, struct lls_parse_result *lpr)
+{
+       unsigned ll;
+       char *errctx;
+       const char *sev[] = {SEVERITIES};
+       const char *arg;
+       int ret = lls(lls_check_arg_count(lpr, 0, 1, &errctx));
+
+       if (ret < 0) {
+               char *tmp = make_message("%s\n", errctx);
+               free(errctx);
+               client_write(fd, tmp);
+               free(tmp);
+               return ret;
+       }
+       if (lls_num_inputs(lpr) == 0) {
+               char *msg;
+               ll = daemon_get_loglevel();
+               msg = make_message("%s\n", sev[ll]);
+               ret = client_write(fd, msg);
+               free(msg);
+               return ret;
+       }
+       arg = lls_input(0, lpr);
+       for (ll = 0; ll < NUM_LOGLEVELS; ll++) {
+               if (!strcmp(arg, sev[ll]))
+                       break;
+       }
+       if (ll >= NUM_LOGLEVELS)
+               return -ERRNO_TO_PARA_ERROR(EINVAL);
+       PARA_INFO_LOG("new log level: %s\n", sev[ll]);
+       daemon_set_loglevel(ll);
+       return 1;
+}
+EXPORT_AUDIOD_CMD_HANDLER(ll)
+
 static int com_tasks(int fd, __a_unused struct lls_parse_result *lpr)
 {
        int ret;
@@ -255,7 +267,8 @@ EXPORT_AUDIOD_CMD_HANDLER(tasks)
 
 static int com_stat(int fd, struct lls_parse_result *lpr)
 {
-       int i, ret, parser_friendly = 0;
+       int i, ret;
+       bool parser_friendly = false;
        uint64_t mask = 0;
        const uint64_t one = 1;
        struct para_buffer b = {.flags = 0};
@@ -267,7 +280,7 @@ static int com_stat(int fd, struct lls_parse_result *lpr)
                return ret;
        r = lls_opt_result(LSG_AUDIOD_CMD_STAT_OPT_PARSER_FRIENDLY, lpr);
        if (lls_opt_given(r) > 0) {
-               parser_friendly = 1;
+               parser_friendly = true;
                b.flags = PBF_SIZE_PREFIX;
        }
        num_inputs = lls_num_inputs(lpr);
@@ -360,10 +373,9 @@ EXPORT_AUDIOD_CMD_HANDLER(version)
  * Handle arriving connections on the local socket.
  *
  * \param accept_fd The fd to accept connections on.
- * \param rfds If \a accept_fd is not set in \a rfds, do nothing.
  *
- * This is called in each iteration of the select loop. If there is an incoming
- * connection on \a accept_fd, this function reads the command sent by the peer,
+ * This is called in each iteration of the main loop of the scheduler. If there
+ * is an incoming connection, the function reads the command sent by the peer,
  * checks the connecting user's permissions by using unix socket credentials
  * (if supported by the OS) and calls the corresponding command handler if
  * permissions are OK.
@@ -372,8 +384,8 @@ EXPORT_AUDIOD_CMD_HANDLER(version)
  * connection to accept.
  *
  * \sa \ref para_accept(), \ref recv_cred_buffer().
- * */
-int handle_connect(int accept_fd, fd_set *rfds)
+ */
+int dispatch_local_connection(int accept_fd)
 {
        int argc, ret, clifd;
        char buf[MAXLINE], **argv = NULL;
@@ -384,7 +396,7 @@ int handle_connect(int accept_fd, fd_set *rfds)
        char *errctx = NULL;
        const struct audiod_command_info *aci;
 
-       ret = para_accept(accept_fd, rfds, &unix_addr, sizeof(struct sockaddr_un), &clifd);
+       ret = para_accept(accept_fd, &unix_addr, sizeof(struct sockaddr_un), &clifd);
        if (ret <= 0)
                return ret;
        ret = recv_cred_buffer(clifd, buf, sizeof(buf) - 1);
index caf1401d8bf02e2d299bdcdf58ae902cfb260092..cbfea7fdfb2ea5e2b8ad84c8faba815b67b8a966 100755 (executable)
@@ -1,28 +1,8 @@
 #!/bin/sh
-# check if we have multiple processors/cores
-n=$(nproc 2>/dev/null)
-if [ -z "$n" ]; then
-       n=$(grep ^processor /proc/cpuinfo 2>/dev/null | wc -l)
-       [ $n -eq 0 ] && n=1
-fi
-# If we are compiling with distcc, try to guess a reasonable number
-# based on (a) the number of cores on this machine and (b) the number
-# of words in the DISTCC_HOSTS variable.
-d="$(echo $DISTCC_HOSTS | wc -w)"
-n=$(($n + 2 * $n * $d))
-echo preparing, parallel=$n...
-if test -f Makefile; then
-       make maintainer-clean > /dev/null 2>&1
-fi
 autom4te \
        --language=autoconf \
        --output=configure \
        --no-cache \
        --warnings=all \
-       configure.ac
+       configure.ac &&
 autoheader
-echo configuring...
-./configure $@ > /dev/null
-echo compiling...
-make clean > /dev/null 2>&1
-make -j $n > /dev/null && make check
index 122465f8feea79cf06d09bfd759d2446ee0f3f12..ac6f65aa85d469df3ba4a70a058ed85b31eb6d11 100644 (file)
--- a/base64.c
+++ b/base64.c
@@ -79,7 +79,7 @@ int base64_decode(char const *src, size_t encoded_size, char **result,
 
        if (encoded_size == (size_t)-1)
                encoded_size = strlen(src);
-       target = para_malloc(BASE64_MAX_DECODED_SIZE(encoded_size) + 1);
+       target = alloc(BASE64_MAX_DECODED_SIZE(encoded_size) + 1);
 
        for (
                i = 0, j = 0, state = 0;
index dfc1e55e67aced611014fc90b86128e8bd61a457..a1c7c943138d41993807109be09969a58e914a0b 100644 (file)
@@ -46,8 +46,8 @@ static void alloc_table(struct vlc *vlc, int size)
        vlc->table_size += size;
        if (vlc->table_size > vlc->table_allocated) {
                vlc->table_allocated += (1 << vlc->bits);
-               vlc->table = para_realloc(vlc->table,
-                       sizeof(int16_t) * 2 * vlc->table_allocated);
+               vlc->table = arr_realloc(vlc->table, vlc->table_allocated,
+                       sizeof(int16_t) * 2);
        }
 }
 
diff --git a/blob.c b/blob.c
index 4ecbc45bb15f42c15342e7900f4de94522ef45bb..1802de5d31f89bf6fe951c5977cbef4967f9c11e 100644 (file)
--- a/blob.c
+++ b/blob.c
@@ -100,7 +100,7 @@ static int print_blob(struct osl_table *table, struct osl_row *row,
        }
        ret = osl(osl_get_object(table, row, BLOBCOL_ID, &obj));
        if (ret < 0) {
-               para_printf(&aca->pbout, "cannot list %s\n", name);
+               afs_error(aca, "cannot list %s\n", name);
                return ret;
        }
        id = read_u32(obj.data);
@@ -210,7 +210,7 @@ static int remove_blob(struct osl_table *table, struct osl_row *row,
        int ret = osl(osl_del_row(table, row));
 
        if (ret < 0) {
-               para_printf(&aca->pbout, "cannot remove %s\n", name);
+               afs_error(aca, "cannot remove %s\n", name);
                return ret;
        }
        return 1;
@@ -338,7 +338,7 @@ static int com_addblob_callback(__a_unused const struct lls_command * const cmd,
        ret = afs_event(BLOB_ADD, NULL, table);
 out:
        if (ret < 0)
-               para_printf(&aca->pbout, "cannot add %s\n", name);
+               afs_error(aca, "cannot add %s\n", name);
        else
                para_printf(&aca->pbout, "added %s as id %u\n", name, id);
        return ret;
@@ -403,7 +403,7 @@ static int stdin_command(struct command_context *cc,
        if (ret < 0)
                return ret;
        query.size = len + 1 + stdin_obj.size;
-       query.data = para_malloc(query.size);
+       query.data = alloc(query.size);
        memcpy(query.data, lls_input(0, lpr), len + 1);
        if (stdin_obj.size > 0)
                memcpy((char *)query.data + len + 1, stdin_obj.data,
@@ -446,15 +446,14 @@ static int com_mvblob_callback(const struct lls_command * const cmd,
        ret = osl(osl_get_row(table, BLOBCOL_NAME, &obj, &row));
 
        if (ret < 0) {
-               para_printf(&aca->pbout, "cannot find source blob %s\n", src);
+               afs_error(aca, "cannot find source blob %s\n", src);
                goto out;
        }
        obj.data = (char *)dest;
        obj.size = strlen(dest) + 1;
        ret = osl(osl_update_object(table, row, BLOBCOL_NAME, &obj));
        if (ret < 0) {
-               para_printf(&aca->pbout, "cannot rename blob %s to %s\n",
-                       src, dest);
+               afs_error(aca, "cannot rename blob %s to %s\n", src, dest);
                goto out;
        }
        ret = afs_event(BLOB_RENAME, NULL, table);
@@ -520,11 +519,11 @@ static int blob_get_name_by_id(struct osl_table *table, uint32_t id,
                return blob_get_name_by_id(table_name ## _table, id, name); \
        }
 
-static int blob_get_def_by_name(struct osl_table *table, char *name,
+static int blob_get_def_by_name(struct osl_table *table, const char *name,
                struct osl_object *def)
 {
        struct osl_row *row;
-       struct osl_object obj = {.data = name, .size = strlen(name) + 1};
+       struct osl_object obj = {.data = (void *)name, .size = strlen(name) + 1};
        int ret;
 
        def->data = NULL;
@@ -538,7 +537,7 @@ static int blob_get_def_by_name(struct osl_table *table, char *name,
 
 /** Define the \p get_def_by_id function for this blob type. */
 #define DEFINE_GET_DEF_BY_NAME(table_name, cmd_prefix) \
-       int cmd_prefix ## _get_def_by_name(char *name, struct osl_object *def) \
+       int cmd_prefix ## _get_def_by_name(const char *name, struct osl_object *def) \
        { \
                return blob_get_def_by_name(table_name ## _table, name, def); \
        }
@@ -625,25 +624,21 @@ static int blob_open(struct osl_table **table,
                        &table_name ## _table_desc, dir); \
        }
 
-
-/** Define the \p init function for this blob type. */
-#define DEFINE_BLOB_INIT(table_name) \
-       void table_name ## _init(struct afs_table *t) \
-       { \
-               t->open = table_name ## _open; \
-               t->close = table_name ## _close; \
-               t->create = table_name ## _create;\
-               t->event_handler = table_name ##_event_handler; \
-               table_name ## _table = NULL; \
-       }
-
+/** Blob tables map integers to blobs. */
+#define DEFINE_BLOB_AFS_TABLE_OPS(table_name) \
+       const struct afs_table_operations table_name ## _ops = { \
+               .open = table_name ## _open, \
+               .close = table_name ## _close, \
+               .create = table_name ## _create, \
+               .event_handler = table_name ##_event_handler, \
+       };
 
 /** Define all functions for this blob type. */
 #define DEFINE_BLOB_FUNCTIONS(table_name, short_name, c_short_name) \
        DEFINE_BLOB_OPEN(table_name) \
        DEFINE_BLOB_CLOSE(table_name) \
        DEFINE_BLOB_CREATE(table_name) \
-       DEFINE_BLOB_INIT(table_name) \
+       DEFINE_BLOB_AFS_TABLE_OPS(table_name) \
        DEFINE_BLOB_COMMAND(ls, LS, table_name, short_name, c_short_name) \
        DEFINE_BLOB_COMMAND(cat, CAT, table_name, short_name, c_short_name) \
        DEFINE_BLOB_COMMAND(add, ADD, table_name, short_name, c_short_name) \
index 8a3175133e69aa57eff7da5155341cbf05532d03..35353f56c9f69cb99be6bb6a319f680876d4a9cc 100644 (file)
@@ -72,8 +72,8 @@ struct btr_pool *btr_pool_new(const char *name, size_t area_size)
        struct btr_pool *btrp;
 
        PARA_INFO_LOG("%s, %zu bytes\n", name, area_size);
-       btrp = para_malloc(sizeof(*btrp));
-       btrp->area_start = para_malloc(area_size);
+       btrp = alloc(sizeof(*btrp));
+       btrp->area_start = alloc(area_size);
        btrp->area_end = btrp->area_start + area_size;
        btrp->rhead = btrp->area_start;
        btrp->whead = btrp->area_start;
@@ -262,7 +262,7 @@ static void btr_pool_deallocate(struct btr_pool *btrp, size_t size)
  */
 struct btr_node *btr_new_node(struct btr_node_description *bnd)
 {
-       struct btr_node *btrn = para_malloc(sizeof(*btrn));
+       struct btr_node *btrn = alloc(sizeof(*btrn));
 
        btrn->name = para_strdup(bnd->name);
        btrn->parent = bnd->parent;
@@ -270,8 +270,8 @@ struct btr_node *btr_new_node(struct btr_node_description *bnd)
        btrn->context = bnd->context;
        btrn->start.tv_sec = 0;
        btrn->start.tv_usec = 0;
-       INIT_LIST_HEAD(&btrn->children);
-       INIT_LIST_HEAD(&btrn->input_queue);
+       init_list_head(&btrn->children);
+       init_list_head(&btrn->input_queue);
        if (!bnd->child) {
                if (bnd->parent) {
                        list_add_tail(&btrn->node, &bnd->parent->children);
@@ -307,7 +307,7 @@ out:
  */
 static struct btr_buffer *new_btrb(char *buf, size_t size)
 {
-       struct btr_buffer *btrb = para_calloc(sizeof(*btrb));
+       struct btr_buffer *btrb = zalloc(sizeof(*btrb));
 
        btrb->buf = buf;
        btrb->size = size;
@@ -354,7 +354,7 @@ static void add_btrb_to_children(struct btr_buffer *btrb,
        if (btrn->start.tv_sec == 0)
                btrn->start = *now;
        FOR_EACH_CHILD(ch, btrn) {
-               struct btr_buffer_reference *br = para_calloc(sizeof(*br));
+               struct btr_buffer_reference *br = zalloc(sizeof(*br));
                br->btrb = btrb;
                br->consumed = consumed;
                list_add_tail(&br->node, &ch->input_queue);
@@ -570,7 +570,7 @@ bool btr_no_parent(struct btr_node *btrn)
  * buffer.
  *
  * Since the buffer tree may change at any time, this function should be called
- * during each post_select call.
+ * during each post_monitor call.
  *
  * \return True if \a btrn has no siblings.
  */
@@ -1007,12 +1007,12 @@ next:
        if (!wbr) {
                /* Make a new wrap buffer combining buf1 and buf2. */
                sz = sz1 + sz2;
-               buf = para_malloc(sz);
+               buf = alloc(sz);
                PARA_DEBUG_LOG("merging input buffers: (%p:%zu, %p:%zu) -> %p:%zu\n",
                        buf1, sz1, buf2, sz2, buf, sz);
                memcpy(buf, buf1, sz1);
                memcpy(buf + sz1, buf2, sz2);
-               br = para_calloc(sizeof(*br));
+               br = zalloc(sizeof(*br));
                br->btrb = new_btrb(buf, sz);
                br->btrb->refcount = 1;
                br->consumed = 0;
@@ -1067,13 +1067,13 @@ static int merge_input(struct btr_node *btrn)
        assert(i == 2);
        /* make a new btrb that combines the two buffers and a br to it. */
        sz = szs[0] + szs[1];
-       buf = para_malloc(sz);
+       buf = alloc(sz);
        PARA_DEBUG_LOG("%s: memory merging input buffers: (%zu, %zu) -> %zu\n",
                btrn->name, szs[0], szs[1], sz);
        memcpy(buf, bufs[0], szs[0]);
        memcpy(buf + szs[0], bufs[1], szs[1]);
 
-       br = para_calloc(sizeof(*br));
+       br = zalloc(sizeof(*br));
        br->btrb = new_btrb(buf, sz);
        br->btrb->refcount = 1;
 
@@ -1181,7 +1181,7 @@ struct btr_node *btr_search_node(const char *name, struct btr_node *root)
  * \param type The supposed type of \a btrn.
  *
  * Most users of the buffer tree subsystem call this function from both
- * their pre_select and the post_select methods.
+ * their ->pre_monitor() and ->post_monitor() methods.
  *
  * \return Negative if an error condition was detected, zero if there
  * is nothing to do and positive otherwise.
@@ -1208,7 +1208,7 @@ int btr_node_status(struct btr_node *btrn, size_t min_iqs,
        if (type != BTR_NT_LEAF && btr_no_children(btrn))
                return -E_BTR_NO_CHILD;
        if (type != BTR_NT_ROOT && btr_eof(btrn))
-               return -E_BTR_EOF;
+               return -E_EOF;
 
        if (btr_get_output_queue_size(btrn) > BTRN_MAX_PENDING)
                return 0;
index 89ebdacc0805e9575acea92d09ed3b2dd81ebac3..3789f30aa5a20ef5433df83362a6be02563c8d17 100644 (file)
@@ -39,15 +39,15 @@ struct check_wav_context {
 };
 
 /**
- * Set select timeout according to the given context.
+ * Request a minimal timeout if not idle.
  *
- * \param s Contains the timeval that should be set.
- * \param cwc Contains a pointer to the buffer tree node.
+ * \param s The scheduler instance.
+ * \param cwc The buffer tree node is derived from this.
  *
- * This requests a minimal timeout from the scheduler if btrn of \a cwc is not
- * idle.
+ * If no data is available and the buffer tree node is not in error state, the
+ * function does nothing.
  */
-void check_wav_pre_select(struct sched *s, struct check_wav_context *cwc)
+void check_wav_pre_monitor(struct sched *s, struct check_wav_context *cwc)
 {
        int ret = btr_node_status(cwc->btrn, cwc->min_iqs, BTR_NT_INTERNAL);
        if (ret != 0)
@@ -121,7 +121,7 @@ out:
  *
  * \return Standard.
  */
-int check_wav_post_select(struct check_wav_context *cwc)
+int check_wav_post_monitor(struct check_wav_context *cwc)
 {
        struct btr_node *btrn = cwc->btrn;
        unsigned char *a;
@@ -198,8 +198,8 @@ out:
  * children of this node can figure out channel count, sample rate, etc.
  *
  * \return The (opaque) handle of the newly created check_wav instance. It is
- * supposed to be passed to \ref check_wav_pre_select() and \ref
- * check_wav_post_select().
+ * supposed to be passed to \ref check_wav_pre_monitor() and \ref
+ * check_wav_post_monitor().
  *
  * \sa \ref btr_new_node.
  */
@@ -207,7 +207,7 @@ struct check_wav_context *check_wav_init(struct btr_node *parent,
                struct btr_node *child, struct wav_params *params,
                struct btr_node **cw_btrn)
 {
-       struct check_wav_context *cwc = para_calloc(sizeof(*cwc));
+       struct check_wav_context *cwc = zalloc(sizeof(*cwc));
 
        cwc->state = CWS_NEED_HEADER;
        cwc->min_iqs = WAV_HEADER_LEN;
@@ -225,7 +225,7 @@ struct check_wav_context *check_wav_init(struct btr_node *parent,
  *
  * \param cwc Determines the instance to shut down.
  *
- * This function may only be called after check_wav_post_select() has returned
+ * This function may only be called after check_wav_post_monitor() has returned
  * negative.
  */
 void check_wav_shutdown(struct check_wav_context *cwc)
index 79b11962baea9b3fe743f08ad68bca6a70e552bc..e6188c5243db16d82c524fc47f4066c8f9aec9df 100644 (file)
@@ -42,6 +42,6 @@ struct wav_params {
 struct check_wav_context *check_wav_init(struct btr_node *parent,
                struct btr_node *child, struct wav_params *params,
                struct btr_node **cw_btrn);
-void check_wav_pre_select(struct sched *s, struct check_wav_context *cwc);
-int check_wav_post_select(struct check_wav_context *cwc);
+void check_wav_pre_monitor(struct sched *s, struct check_wav_context *cwc);
+int check_wav_post_monitor(struct check_wav_context *cwc);
 void check_wav_shutdown(struct check_wav_context *cwc);
index 08f57e9d1ecf9bae1cadead0757fe29418c446c3..1f11359fcb16e4e55073a5fd36d5d5cff7f5504c 100644 (file)
@@ -52,7 +52,7 @@ int cq_enqueue(struct chunk_queue *cq, const char *buf, size_t num_bytes)
 
        if (cq->num_pending + num_bytes > cq->max_pending)
                return -E_QUEUE;
-       qc = para_malloc(sizeof(struct queued_chunk));
+       qc = alloc(sizeof(struct queued_chunk));
        cq->num_pending += num_bytes;
        qc->buf = buf;
        qc->num_bytes = num_bytes;
@@ -130,8 +130,8 @@ int cq_get(struct queued_chunk *qc, const char **buf, size_t *num_bytes)
  */
 struct chunk_queue *cq_new(size_t max_pending)
 {
-       struct chunk_queue *cq = para_malloc(sizeof(*cq));
-       INIT_LIST_HEAD(&cq->q);
+       struct chunk_queue *cq = alloc(sizeof(*cq));
+       init_list_head(&cq->q);
        cq->max_pending = max_pending;
        cq->num_pending = 0;
        return cq;
index f72719f27df46b012165a9e8c8bde3586d602a95..84b7580cf9554c433651da1f3eed28a6f1f39a74 100644 (file)
--- a/client.c
+++ b/client.c
@@ -42,7 +42,7 @@ struct exec_task {
        size_t result_size;
 };
 
-static void exec_pre_select(struct sched *s, void *context)
+static void exec_pre_monitor(struct sched *s, void *context)
 {
        struct exec_task *et = context;
        int ret = btr_node_status(et->btrn, 0, BTR_NT_LEAF);
@@ -51,7 +51,7 @@ static void exec_pre_select(struct sched *s, void *context)
                sched_min_delay(s);
 }
 
-static int exec_post_select(__a_unused struct sched *s, void *context)
+static int exec_post_monitor(__a_unused struct sched *s, void *context)
 {
        struct exec_task *et = context;
        struct btr_node *btrn = et->btrn;
@@ -100,7 +100,7 @@ static int create_merged_lpr(const char *line)
        /*
         * The original lpr for the interactive session has no non-option
         * arguments. We create a fresh lpr from the words in "line" and merge
-        * it with the orignal lpr.
+        * it with the original lpr.
         */
        ret = lls(lls_parse(argc, argv, cmd, &argv_lpr, &errctx));
        free_argv(argv);
@@ -123,7 +123,7 @@ fail:
 static int execute_client_command(const char *cmd, char **result)
 {
        int ret;
-       struct sched command_sched = {.default_timeout = {.tv_sec = 1}};
+       struct sched command_sched = {.default_timeout = 1000};
        struct exec_task exec_task = {
                .result_buf = para_strdup(""),
                .result_size = 1,
@@ -138,8 +138,8 @@ static int execute_client_command(const char *cmd, char **result)
                EMBRACE(.name = "exec_collect"));
        exec_task.task = task_register(&(struct task_info) {
                .name = "client exec",
-               .pre_select = exec_pre_select,
-               .post_select = exec_post_select,
+               .pre_monitor = exec_pre_monitor,
+               .post_monitor = exec_post_monitor,
                .context = &exec_task,
        }, &command_sched);
        ret = client_connect(ct, &command_sched, NULL, exec_task.btrn);
@@ -243,10 +243,15 @@ I9E_DUMMY_COMPLETER(term);
 I9E_DUMMY_COMPLETER(stop);
 I9E_DUMMY_COMPLETER(addatt);
 I9E_DUMMY_COMPLETER(init);
-I9E_DUMMY_COMPLETER(tasks);
 
 static struct i9e_completer completers[];
 
+static void ll_completer(struct i9e_completion_info *ci,
+               struct i9e_completion_result *cr)
+{
+       i9e_ll_completer(ci, cr);
+}
+
 static void help_completer(struct i9e_completion_info *ci,
                struct i9e_completion_result *cr)
 {
@@ -342,7 +347,7 @@ static void setatt_completer(struct i9e_completion_info *ci,
        if (ret < 0)
                goto out;
        num_atts = ret;
-       sl = para_realloc(sl, (2 * num_atts + 1) * sizeof(char *));
+       sl = arr_realloc(sl, 2 * num_atts + 1, sizeof(char *));
        for (i = 0; i < num_atts; i++) {
                char *orig = sl[i];
                sl[i] = make_message("%s+", orig);
@@ -436,7 +441,7 @@ static void select_completer(struct i9e_completion_info *ci,
                goto free_moods;
        num_pl = ret;
        n = num_moods + num_pl;
-       mops = para_malloc((n + 1) * sizeof(char *));
+       mops = arr_alloc(n + 1, sizeof(char *));
        for (i = 0; i < num_moods; i++)
                mops[i] = make_message("m/%s", moods[i]);
        for (i = 0; i < num_pl; i++)
@@ -533,7 +538,7 @@ __noreturn static void interactive_session(void)
        sigemptyset(&act.sa_mask);
        act.sa_flags = 0;
        sigaction(SIGINT, &act, NULL);
-       sched.select_function = i9e_select;
+       sched.poll_function = i9e_poll;
 
        ret = i9e_open(&ici, &sched);
        if (ret < 0)
@@ -579,7 +584,7 @@ struct supervisor_task {
        struct task *task;
 };
 
-static int supervisor_post_select(struct sched *s, void *context)
+static int supervisor_post_monitor(struct sched *s, void *context)
 {
        struct supervisor_task *svt = context;
        int ret = task_status(ct->task);
@@ -625,7 +630,7 @@ int main(int argc, char *argv[])
        int ret;
 
        crypt_init();
-       sched.default_timeout.tv_sec = 1;
+       sched.default_timeout = 1000;
 
        ret = client_parse_config(argc, argv, &ct, &client_loglevel);
        if (ret < 0)
@@ -649,7 +654,7 @@ int main(int argc, char *argv[])
                EMBRACE(.name = "stdout", .parent = ct->btrn[0]));
        supervisor_task.task = task_register(&(struct task_info) {
                .name = "supervisor",
-               .post_select = supervisor_post_select,
+               .post_monitor = supervisor_post_monitor,
                .context = &supervisor_task,
        }, &sched);
 
@@ -661,8 +666,6 @@ int main(int argc, char *argv[])
                        /* these are not errors */
                        case -E_SERVER_CMD_SUCCESS:
                        case -E_EOF:
-                       case -E_SERVER_EOF:
-                       case -E_BTR_EOF:
                                ret = 0;
                                break;
                        default: ret = -E_SERVER_CMD_FAILURE;
index a7eee85d3cc0d5cf94073ca39c6223a7c4a639d4..fe8234f98fe90f6ce74ea5e22699fbfc0059a042 100644 (file)
@@ -50,15 +50,14 @@ void client_close(struct client_task *ct)
 }
 
 /*
- * The preselect hook for server commands.
+ * This function asks the scheduler to monitor a file descriptor which
+ * corresponds to an active connection. The descriptor is monitored for either
+ * reading or writing, depending on the state of the connection.
  *
- * The task pointer must contain a pointer to the initialized client data
- * structure as it is returned by client_open().
- *
- * This function checks the state of the connection and adds the file descriptor
- * of the connection to the read or write fd set of s accordingly.
+ * The context pointer is assumed to refer to a client task structure that was
+ * initialized earlier by client_open().
  */
-static void client_pre_select(struct sched *s, void *context)
+static void client_pre_monitor(struct sched *s, void *context)
 {
        int ret;
        struct client_task *ct = context;
@@ -69,13 +68,13 @@ static void client_pre_select(struct sched *s, void *context)
        case CL_CONNECTED:
        case CL_SENT_AUTH:
        case CL_SENT_CH_RESPONSE:
-               para_fd_set(ct->scc.fd, &s->rfds, &s->max_fileno);
+               sched_monitor_readfd(ct->scc.fd, s);
                return;
 
        case CL_RECEIVED_WELCOME:
        case CL_RECEIVED_PROCEED:
        case CL_RECEIVED_CHALLENGE:
-               para_fd_set(ct->scc.fd, &s->wfds, &s->max_fileno);
+               sched_monitor_writefd(ct->scc.fd, s);
                return;
 
        case CL_SENDING:
@@ -84,16 +83,16 @@ static void client_pre_select(struct sched *s, void *context)
                        if (ret < 0)
                                sched_min_delay(s);
                        else if (ret > 0)
-                               para_fd_set(ct->scc.fd, &s->wfds, &s->max_fileno);
+                               sched_monitor_writefd(ct->scc.fd, s);
                }
-               /* fallthrough */
+               __attribute__ ((fallthrough));
        case CL_EXECUTING:
                if (ct->btrn[0]) {
                        ret = btr_node_status(ct->btrn[0], 0, BTR_NT_ROOT);
                        if (ret < 0)
                                sched_min_delay(s);
                        else if (ret > 0)
-                               para_fd_set(ct->scc.fd, &s->rfds, &s->max_fileno);
+                               sched_monitor_readfd(ct->scc.fd, s);
                }
                return;
        }
@@ -126,8 +125,7 @@ static int send_sb(struct client_task *ct, int channel, void *buf, size_t numbyt
        return 0;
 }
 
-static int recv_sb(struct client_task *ct, fd_set *rfds,
-               struct sb_buffer *result)
+static int recv_sb(struct client_task *ct, struct sb_buffer *result)
 {
        int ret;
        size_t n;
@@ -135,8 +133,6 @@ static int recv_sb(struct client_task *ct, fd_set *rfds,
        void *trafo_context;
        struct iovec iov;
 
-       if (!FD_ISSET(ct->scc.fd, rfds))
-               return 0;
        if (ct->status < CL_SENT_CH_RESPONSE)
                trafo = trafo_context = NULL;
        else {
@@ -147,7 +143,7 @@ static int recv_sb(struct client_task *ct, fd_set *rfds,
                ct->sbc[0] = sb_new_recv(0, trafo, trafo_context);
 again:
        sb_get_recv_buffer(ct->sbc[0], &iov);
-       ret = read_nonblock(ct->scc.fd, iov.iov_base, iov.iov_len, rfds, &n);
+       ret = read_nonblock(ct->scc.fd, iov.iov_base, iov.iov_len, &n);
        if (ret < 0) {
                sb_free(ct->sbc[0]);
                ct->sbc[0] = NULL;
@@ -249,7 +245,7 @@ static int send_sb_command(struct client_task *ct)
 
        for (i = 0; i < num_inputs; i++)
                len += strlen(lls_input(i, ct->lpr)) + 1;
-       p = command = para_malloc(len);
+       p = command = alloc(len);
        for (i = 0; i < num_inputs; i++) {
                const char *str = lls_input(i, ct->lpr);
                strcpy(p, str);
@@ -259,15 +255,29 @@ static int send_sb_command(struct client_task *ct)
        return send_sb(ct, 0, command, len, SBD_COMMAND, false);
 }
 
+/* Find out if the given string is contained in the features vector. */
+static bool has_feature(const char *feature, struct client_task *ct)
+{
+       if (!ct->features)
+               return false;
+       for (int i = 0; ct->features[i]; i++)
+               if (strcmp(feature, ct->features[i]) == 0)
+                       return true;
+       return false;
+}
+
 /*
- * The post select hook for client commands.
+ * This function reads or writes to the socket file descriptor which
+ * corresponds to an established connection between the client and the server.
+ * It depends on the current state of the connection and on the readiness of
+ * the socket file descriptor which type of I/O is going to be performed.
+ * Besides the initial handshake and authentication, the function sends the
+ * server command and receives the output from the server, if any.
  *
- * Depending on the current state of the connection and the status of the read
- * and write fd sets of s, this function performs the necessary steps to
- * authenticate the connection, to send the command given by t->private_data
- * and to receive para_server's output, if any.
+ * The context pointer refers to a client task structure that was initialized
+ * earlier by client_open().
  */
-static int client_post_select(struct sched *s, void *context)
+static int client_post_monitor(struct sched *s, void *context)
 {
        struct client_task *ct = context;
        int ret = 0;
@@ -281,23 +291,32 @@ static int client_post_select(struct sched *s, void *context)
                return 0;
        switch (ct->status) {
        case CL_CONNECTED: /* receive welcome message */
-               ret = read_nonblock(ct->scc.fd, buf, sizeof(buf), &s->rfds, &n);
+               ret = read_nonblock(ct->scc.fd, buf, sizeof(buf), &n);
                if (ret < 0 || n == 0)
                        goto out;
                ct->features = parse_features(buf);
                ct->status = CL_RECEIVED_WELCOME;
                return 0;
        case CL_RECEIVED_WELCOME: /* send auth command */
-               if (!FD_ISSET(ct->scc.fd, &s->wfds))
+               {
+               /*
+                * Use sha256 iff the server announced the feature. After 0.7.0
+                * we may request and use the feature unconditionally. After
+                * 0.8.0 we no longer need to request the feature.
+                */
+               bool has_sha256;
+               if (!sched_write_ok(ct->scc.fd, s))
                        return 0;
-               sprintf(buf, AUTH_REQUEST_MSG "%s sideband,aes_ctr128",
-                       ct->user);
+               has_sha256 = has_feature("sha256", ct);
+               sprintf(buf, AUTH_REQUEST_MSG "%s%s", ct->user, has_sha256?
+                       " sha256" : "");
                PARA_INFO_LOG("--> %s\n", buf);
                ret = write_buffer(ct->scc.fd, buf);
                if (ret < 0)
                        goto out;
                ct->status = CL_SENT_AUTH;
                return 0;
+               }
        case CL_SENT_AUTH:
                /*
                 * Receive challenge and session keys, decrypt the challenge and
@@ -308,7 +327,7 @@ static int client_post_select(struct sched *s, void *context)
                unsigned char crypt_buf[1024];
                struct sb_buffer sbb;
 
-               ret = recv_sb(ct, &s->rfds, &sbb);
+               ret = recv_sb(ct, &sbb);
                if (ret <= 0)
                        goto out;
                if (sbb.band != SBD_CHALLENGE) {
@@ -323,19 +342,31 @@ static int client_post_select(struct sched *s, void *context)
                free(sbb.iov.iov_base);
                if (ret < 0)
                        goto out;
-               ct->challenge_hash = para_malloc(HASH_SIZE);
-               hash_function((char *)crypt_buf, APC_CHALLENGE_SIZE, ct->challenge_hash);
-               ct->scc.send = sc_new(crypt_buf + APC_CHALLENGE_SIZE, SESSION_KEY_LEN);
-               ct->scc.recv = sc_new(crypt_buf + APC_CHALLENGE_SIZE + SESSION_KEY_LEN,
-                       SESSION_KEY_LEN);
-               hash_to_asc(ct->challenge_hash, buf);
+               ct->challenge_hash = alloc(HASH2_SIZE);
+               if (has_feature("sha256", ct)) {
+                       hash2_function((char *)crypt_buf, APC_CHALLENGE_SIZE,
+                               ct->challenge_hash);
+                       hash2_to_asc(ct->challenge_hash, buf);
+               } else {
+                       hash_function((char *)crypt_buf, APC_CHALLENGE_SIZE,
+                               ct->challenge_hash);
+                       hash_to_asc(ct->challenge_hash, buf);
+               }
+               ct->scc.send = sc_new(crypt_buf + APC_CHALLENGE_SIZE,
+                        SESSION_KEY_LEN);
+               ct->scc.recv = sc_new(crypt_buf + APC_CHALLENGE_SIZE
+                       + SESSION_KEY_LEN, SESSION_KEY_LEN);
                PARA_INFO_LOG("--> %s\n", buf);
                ct->status = CL_RECEIVED_CHALLENGE;
                return 0;
                }
        case CL_RECEIVED_CHALLENGE:
-               ret = send_sb(ct, 0, ct->challenge_hash, HASH_SIZE,
-                       SBD_CHALLENGE_RESPONSE, false);
+               if (has_feature("sha256", ct))
+                       ret = send_sb(ct, 0, ct->challenge_hash, HASH2_SIZE,
+                               SBD_CHALLENGE_RESPONSE, false);
+               else
+                       ret = send_sb(ct, 0, ct->challenge_hash, HASH_SIZE,
+                               SBD_CHALLENGE_RESPONSE, false);
                if (ret != 0)
                        ct->challenge_hash = NULL;
                if (ret <= 0)
@@ -345,7 +376,7 @@ static int client_post_select(struct sched *s, void *context)
        case CL_SENT_CH_RESPONSE: /* read server response */
                {
                struct sb_buffer sbb;
-               ret = recv_sb(ct, &s->rfds, &sbb);
+               ret = recv_sb(ct, &sbb);
                if (ret <= 0)
                        goto out;
                free(sbb.iov.iov_base);
@@ -357,7 +388,7 @@ static int client_post_select(struct sched *s, void *context)
                }
        case CL_RECEIVED_PROCEED: /* concat args and send command */
                {
-               if (!FD_ISSET(ct->scc.fd, &s->wfds))
+               if (!sched_write_ok(ct->scc.fd, s))
                        return 0;
                ret = send_sb_command(ct);
                if (ret <= 0)
@@ -370,16 +401,16 @@ static int client_post_select(struct sched *s, void *context)
                        char *buf2;
                        size_t sz;
                        ret = btr_node_status(ct->btrn[1], 0, BTR_NT_LEAF);
-                       if (ret == -E_BTR_EOF) {
+                       if (ret == -E_EOF) {
                                /* empty blob data packet indicates EOF */
                                PARA_INFO_LOG("blob sent\n");
                                ret = send_sb(ct, 1, NULL, 0, SBD_BLOB_DATA, true);
                                if (ret >= 0)
-                                       ret = -E_BTR_EOF;
+                                       ret = -E_EOF;
                        }
                        if (ret < 0)
                                goto close1;
-                       if (ret > 0 && FD_ISSET(ct->scc.fd, &s->wfds)) {
+                       if (ret > 0 && sched_write_ok(ct->scc.fd, s)) {
                                sz = btr_next_buffer(ct->btrn[1], &buf2);
                                assert(sz);
                                ret = send_sb(ct, 1, buf2, sz, SBD_BLOB_DATA, true);
@@ -389,15 +420,15 @@ static int client_post_select(struct sched *s, void *context)
                                        btr_consume(ct->btrn[1], sz);
                        }
                }
-               /* fall through */
+               __attribute__ ((fallthrough));
        case CL_EXECUTING:
                if (ct->btrn[0]) {
                        ret = btr_node_status(ct->btrn[0], 0, BTR_NT_ROOT);
                        if (ret < 0)
                                goto close0;
-                       if (ret > 0 && FD_ISSET(ct->scc.fd, &s->rfds)) {
+                       if (ret > 0 && sched_read_ok(ct->scc.fd, s)) {
                                struct sb_buffer sbb;
-                               ret = recv_sb(ct, &s->rfds, &sbb);
+                               ret = recv_sb(ct, &sbb);
                                if (ret < 0)
                                        goto close0;
                                if (ret > 0) {
@@ -426,8 +457,7 @@ out:
                return 0;
        btr_remove_node(&ct->btrn[0]);
        btr_remove_node(&ct->btrn[1]);
-       if (ret != -E_SERVER_CMD_SUCCESS && ret != -E_SERVER_CMD_FAILURE)
-               PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+       PARA_NOTICE_LOG("closing connection (%s)\n", para_strerror(-ret));
        if (ct->scc.fd >= 0) {
                close(ct->scc.fd);
                ct->scc.fd = -1;
@@ -463,7 +493,7 @@ int client_connect(struct client_task *ct, struct sched *s,
 
        PARA_NOTICE_LOG("connecting %s:%u\n", host, port);
        ct->scc.fd = -1;
-       ret = para_connect_simple(IPPROTO_TCP, host, port);
+       ret = para_connect(IPPROTO_TCP, host, port);
        if (ret < 0)
                return ret;
        ct->scc.fd = ret;
@@ -478,8 +508,8 @@ int client_connect(struct client_task *ct, struct sched *s,
 
        ct->task = task_register(&(struct task_info) {
                .name = "client",
-               .pre_select = client_pre_select,
-               .post_select = client_post_select,
+               .pre_monitor = client_pre_monitor,
+               .post_monitor = client_post_monitor,
                .context = ct,
        }, s);
        return 1;
@@ -551,8 +581,9 @@ int client_parse_config(int argc, char *argv[], struct client_task **ct_ptr,
        if (CLIENT_OPT_GIVEN(KEY_FILE, lpr))
                kf = para_strdup(CLIENT_OPT_STRING_VAL(KEY_FILE, lpr));
        else {
+               struct stat statbuf;
                kf = make_message("%s/.paraslash/key.%s", home, user);
-               if (!file_exists(kf)) {
+               if (stat(kf, &statbuf) != 0) { /* assume file does not exist */
                        free(kf);
                        kf = make_message("%s/.ssh/id_rsa", home);
                }
@@ -560,7 +591,7 @@ int client_parse_config(int argc, char *argv[], struct client_task **ct_ptr,
        PARA_INFO_LOG("user: %s\n", user);
        PARA_INFO_LOG("key file: %s\n", kf);
        PARA_INFO_LOG("loglevel: %d\n", ll);
-       ct = para_calloc(sizeof(*ct));
+       ct = zalloc(sizeof(*ct));
        ct->scc.fd = -1;
        ct->lpr = lpr;
        ct->key_file = kf;
index 28c5eabb1ae9a4821b60b638be559e837dc54f36..809f027e278d44f4413482ba433b8893b8be0843 100644 (file)
@@ -31,10 +31,10 @@ struct close_on_fork {
  */
 void add_close_on_fork_list(int fd)
 {
-       struct close_on_fork *cof = para_malloc(sizeof(struct close_on_fork));
+       struct close_on_fork *cof = alloc(sizeof(struct close_on_fork));
 
        if (!initialized) {
-               INIT_LIST_HEAD(&close_on_fork_list);
+               init_list_head(&close_on_fork_list);
                initialized = 1;
        }
        cof->fd = fd;
index 63f0f1659f64aa60cd8b2c1bf399aed97993af18..60c2aeba4300124c51af13d03c522fe2c2bbb1f1 100644 (file)
--- a/command.c
+++ b/command.c
@@ -6,14 +6,10 @@
 #include <sys/socket.h>
 #include <regex.h>
 #include <signal.h>
-#include <sys/types.h>
-#include <osl.h>
 #include <arpa/inet.h>
-#include <sys/un.h>
 #include <netdb.h>
 #include <lopsub.h>
 
-#include "server.lsg.h"
 #include "para.h"
 #include "error.h"
 #include "lsu.h"
 #include "command.h"
 #include "string.h"
 #include "afh.h"
-#include "afs.h"
 #include "net.h"
 #include "server.h"
 #include "list.h"
-#include "send.h"
 #include "sched.h"
+#include "send.h"
 #include "vss.h"
 #include "daemon.h"
 #include "fd.h"
@@ -52,12 +47,14 @@ extern struct misc_meta_data *mmd;
 int send_afs_status(struct command_context *cc, int parser_friendly);
 static bool subcmd_should_die;
 
+/*
+ * Don't call PARA_XXX_LOG() here as we might already hold the log mutex. See
+ * generic_signal_handler() for details.
+ */
 static void command_handler_sighandler(int s)
 {
-       if (s != SIGTERM)
-               return;
-       PARA_EMERG_LOG("terminating on signal %d\n", SIGTERM);
-       subcmd_should_die = true;
+       if (s == SIGTERM)
+               subcmd_should_die = true;
 }
 
 /*
@@ -83,7 +80,7 @@ static char *vss_status_tohuman(unsigned int flags)
  */
 static char *vss_get_status_flags(unsigned int flags)
 {
-       char *msg = para_malloc(5 * sizeof(char));
+       char *msg = alloc(5 * sizeof(char));
 
        msg[0] = (flags & VSS_PLAYING)? 'P' : '_';
        msg[1] = (flags & VSS_NOMORE)? 'O' : '_';
@@ -395,7 +392,6 @@ static int com_si(struct command_context *cc,
                "server_pid: %d\n"
                "afs_pid: %d\n"
                "connections (active/accepted/total): %u/%u/%u\n"
-               "current loglevel: %s\n"
                "supported audio formats: %s\n",
                ut, mmd->num_played,
                (int)getppid(),
@@ -403,7 +399,6 @@ static int com_si(struct command_context *cc,
                mmd->active_connections,
                mmd->num_commands,
                mmd->num_connects,
-               ENUM_STRING_VAL(LOGLEVEL),
                AUDIO_FORMAT_HANDLERS
        );
        mutex_unlock(mmd_mutex);
@@ -512,6 +507,7 @@ static int com_stat(struct command_context *cc, struct lls_parse_result *lpr)
         * while we sleep.
         */
        para_block_signal(SIGTERM);
+       para_block_signal(SIGUSR1);
        for (;;) {
                sigset_t set;
                /*
@@ -543,8 +539,10 @@ static int com_stat(struct command_context *cc, struct lls_parse_result *lpr)
                 * open a race window similar to the one described above.
                 */
                pselect(1, NULL, NULL, NULL, &ts, &set);
-               if (subcmd_should_die)
+               if (subcmd_should_die) {
+                       PARA_EMERG_LOG("terminating on SIGTERM\n");
                        goto out;
+               }
                ret = -E_SERVER_CRASH;
                if (getppid() == 1)
                        goto out;
@@ -554,7 +552,7 @@ out:
 }
 EXPORT_SERVER_CMD_HANDLER(stat);
 
-const char *aux_info_cb(unsigned cmd_num, bool verbose)
+static const char *aux_info_cb(unsigned cmd_num, bool verbose)
 {
        static char result[80];
        unsigned perms = server_command_perms[cmd_num];
@@ -594,9 +592,56 @@ static int com_hup(__a_unused struct command_context *cc,
 }
 EXPORT_SERVER_CMD_HANDLER(hup);
 
+static int com_ll(struct command_context *cc, struct lls_parse_result *lpr)
+{
+       unsigned ll, perms;
+       char *errctx;
+       const char *sev[] = {SEVERITIES}, *arg;
+       int ret = lls(lls_check_arg_count(lpr, 0, 1, &errctx));
+
+       if (ret < 0) {
+               send_errctx(cc, errctx);
+               return ret;
+       }
+       if (lls_num_inputs(lpr) == 0) { /* reporting is an unprivileged op. */
+               const char *severity;
+               mutex_lock(mmd_mutex);
+               severity = sev[mmd->loglevel];
+               mutex_unlock(mmd_mutex);
+               return send_sb_va(&cc->scc, SBD_OUTPUT, "%s\n", severity);
+       }
+       /*
+        * Changing the loglevel changes the state of both the afs and the vss,
+        * so we require both AFS_WRITE and VSS_WRITE.
+        */
+       perms = AFS_WRITE | VSS_WRITE;
+       if ((cc->u->perms & perms) != perms)
+               return -ERRNO_TO_PARA_ERROR(EPERM);
+       arg = lls_input(0, lpr);
+       for (ll = 0; ll < NUM_LOGLEVELS; ll++)
+               if (!strcmp(arg, sev[ll]))
+                       break;
+       if (ll >= NUM_LOGLEVELS)
+               return -ERRNO_TO_PARA_ERROR(EINVAL);
+       PARA_INFO_LOG("new log level: %s\n", sev[ll]);
+       /* Ask the server and afs processes to adjust their log level. */
+       mutex_lock(mmd_mutex);
+       mmd->loglevel = ll;
+       mutex_unlock(mmd_mutex);
+       return 1;
+}
+EXPORT_SERVER_CMD_HANDLER(ll);
+
 static int com_term(__a_unused struct command_context *cc,
                __a_unused struct lls_parse_result *lpr)
 {
+       /*
+        * The server catches SIGTERM and propagates this signal to all its
+        * children. We are about to exit anyway, but we'd leak tons of memory
+        * if being terminated by the signal. So we ignore the signal here and
+        * terminate via the normal exit path, deallocating all memory.
+        */
+       para_sigaction(SIGTERM, SIG_IGN);
        kill(getppid(), SIGTERM);
        return 1;
 }
@@ -665,7 +710,7 @@ static int com_ff(struct command_context *cc, struct lls_parse_result *lpr)
 {
        long promille;
        int i, ret;
-       char c, *errctx;
+       char *errctx;
 
        ret = lls(lls_check_arg_count(lpr, 1, 1, &errctx));
        if (ret < 0) {
@@ -673,21 +718,8 @@ static int com_ff(struct command_context *cc, struct lls_parse_result *lpr)
                return ret;
        }
        ret = para_atoi32(lls_input(0, lpr), &i);
-       if (ret < 0) {
-               if (ret != -E_ATOI_JUNK_AT_END)
-                       return ret;
-               /*
-                * Compatibility code to keep the historic syntax (ff 30-)
-                * working. This can be removed after 0.7.0.
-                */
-               ret = sscanf(lls_input(0, lpr), "%i%c", &i, &c);
-               if (ret <= 0)
-                       return -E_COMMAND_SYNTAX;
-               if (ret > 1 && c == '-') {
-                       PARA_WARNING_LOG("use of obsolete syntax\n");
-                       i = -i;
-               }
-       }
+       if (ret < 0)
+               return ret;
        mutex_lock(mmd_mutex);
        ret = -E_NO_AUDIO_FILE;
        if (!mmd->afd.afhi.chunks_total || !mmd->afd.afhi.seconds_total)
@@ -717,8 +749,7 @@ EXPORT_SERVER_CMD_HANDLER(ff);
 
 static int com_jmp(struct command_context *cc, struct lls_parse_result *lpr)
 {
-       long unsigned int i;
-       int ret;
+       int i, ret;
        char *errctx;
 
        ret = lls(lls_check_arg_count(lpr, 1, 1, &errctx));
@@ -726,18 +757,16 @@ static int com_jmp(struct command_context *cc, struct lls_parse_result *lpr)
                send_errctx(cc, errctx);
                return ret;
        }
-       if (sscanf(lls_input(0, lpr), "%lu", &i) <= 0)
+       if (sscanf(lls_input(0, lpr), "%d", &i) <= 0)
+               return -ERRNO_TO_PARA_ERROR(EINVAL);
+       if (i < 0 || i > 100)
                return -ERRNO_TO_PARA_ERROR(EINVAL);
        mutex_lock(mmd_mutex);
        ret = -E_NO_AUDIO_FILE;
        if (!mmd->afd.afhi.chunks_total)
                goto out;
-       if (i > 100)
-               i = 100;
-       PARA_INFO_LOG("jumping to %lu%%\n", i);
+       PARA_INFO_LOG("jumping to %d%%\n", i);
        mmd->repos_request = (mmd->afd.afhi.chunks_total * i + 50) / 100;
-       PARA_INFO_LOG("sent: %lu, offset before jmp: %li\n",
-               mmd->chunks_sent, mmd->offset);
        mmd->new_vss_status_flags |= VSS_REPOS;
        mmd->new_vss_status_flags &= ~VSS_NEXT;
        ret = 1;
@@ -748,14 +777,6 @@ out:
 }
 EXPORT_SERVER_CMD_HANDLER(jmp);
 
-/* deprecated, does nothing */
-static int com_tasks(__a_unused struct command_context *cc,
-               __a_unused struct lls_parse_result *lpr)
-{
-       return 1;
-}
-EXPORT_SERVER_CMD_HANDLER(tasks);
-
 static void reset_signals(void)
 {
        para_sigaction(SIGCHLD, SIG_IGN);
@@ -765,7 +786,7 @@ static void reset_signals(void)
 }
 
 struct connection_features {
-       int dummy; /* none at the moment */
+       bool sha256_requested; /* can be removed after 0.7.0 */
 };
 
 static int parse_auth_request(char *buf, int len, const struct user **u,
@@ -791,10 +812,13 @@ static int parse_auth_request(char *buf, int len, const struct user **u,
                p++;
                create_argv(p, ",", &features);
                for (i = 0; features[i]; i++) {
-                       if (strcmp(features[i], "sideband") == 0)
-                               continue;
-                       if (strcmp(features[i], "aes_ctr128") == 0)
-                               continue;
+                       /*
+                        * ->sha256_requested can go away after 0.7.0 so that
+                        * sha256 is used unconditionally, but we need to
+                        * accept the feature request until 0.9.0.
+                        */
+                       if (strcmp(features[i], "sha256") == 0)
+                               cf->sha256_requested = true;
                        else {
                                ret = -E_BAD_FEATURE;
                                goto out;
@@ -832,13 +856,13 @@ static int run_command(struct command_context *cc, struct iovec *iov)
        }
        perms = server_command_perms[ret];
        if ((perms & cc->u->perms) != perms)
-               return -E_PERM;
+               return -ERRNO_TO_PARA_ERROR(EPERM);
        lcmd = lls_cmd(ret, server_cmd_suite);
        end = iov->iov_base + iov->iov_len;
        for (i = 0; p < end; i++)
                p += strlen(p) + 1;
        argc = i;
-       argv = para_malloc((argc + 1) * sizeof(char *));
+       argv = arr_alloc(argc + 1, sizeof(char *));
        for (i = 0, p = iov->iov_base; p < end; i++) {
                argv[i] = para_strdup(p);
                p += strlen(p) + 1;
@@ -870,19 +894,19 @@ static int run_command(struct command_context *cc, struct iovec *iov)
  * Whenever para_server accepts an incoming tcp connection on the port it
  * listens on, it forks and the resulting child calls this function.
  *
- * An RSA-based challenge/response is used to authenticate the peer. It that
+ * An RSA-based challenge/response is used to authenticate the peer. If the
  * authentication succeeds, a random session key is generated and sent back to
  * the peer, encrypted with its RSA public key. From this point on, all
- * transfers are crypted with this session key.
+ * transfers are encrypted with this session key using a stream cipher.
  *
  * Next it is checked if the peer supplied a valid server command or a command
  * for the audio file selector. If yes, and if the user has sufficient
- * permissions to execute that command, the function calls the corresponding
- * command handler which does argument checking and further processing.
+ * permissions to execute this command, the function calls the corresponding
+ * command handler which performs argument checking and further processing.
  *
- * In order to cope with DOS attacks, a timeout is set up which terminates
- * the function if the connection was not authenticated when the timeout
- * expires.
+ * To cope with DOS attacks, a timer is set up right after the fork. If the
+ * connection was still not authenticated when the timeout expires, the child
+ * process is terminated.
  *
  * \return Standard.
  *
@@ -892,8 +916,8 @@ int handle_connect(int fd)
 {
        int ret;
        unsigned char rand_buf[APC_CHALLENGE_SIZE + 2 * SESSION_KEY_LEN];
-       unsigned char challenge_hash[HASH_SIZE];
-       char *command = NULL, *buf = para_malloc(HANDSHAKE_BUFSIZE) /* must be on the heap */;
+       unsigned char challenge_hash[HASH2_SIZE];
+       char *command = NULL, *buf = alloc(HANDSHAKE_BUFSIZE) /* must be on the heap */;
        size_t numbytes;
        struct command_context cc_struct = {.u = NULL}, *cc = &cc_struct;
        struct iovec iov;
@@ -908,7 +932,7 @@ int handle_connect(int fd)
        /* send Welcome message */
        ret = write_va_buffer(fd, "This is para_server, version "
                PACKAGE_VERSION ".\n"
-               "Features: sideband,aes_ctr128\n"
+               "Features: sha256\n" /* no longer announce this after 0.8.0 */
        );
        if (ret < 0)
                goto net_err;
@@ -956,11 +980,19 @@ int handle_connect(int fd)
         * of the random data.
         */
        ret = -E_BAD_AUTH;
-       if (numbytes != HASH_SIZE)
-               goto net_err;
-       hash_function((char *)rand_buf, APC_CHALLENGE_SIZE, challenge_hash);
-       if (memcmp(challenge_hash, buf, HASH_SIZE))
-               goto net_err;
+       if (cf.sha256_requested) {
+               if (numbytes != HASH2_SIZE)
+                       goto net_err;
+               hash2_function((char *)rand_buf, APC_CHALLENGE_SIZE, challenge_hash);
+               if (memcmp(challenge_hash, buf, HASH2_SIZE))
+                       goto net_err;
+       } else { /* old client. This can be removed after 0.7.0 */
+               if (numbytes != HASH_SIZE)
+                       goto net_err;
+               hash_function((char *)rand_buf, APC_CHALLENGE_SIZE, challenge_hash);
+               if (memcmp(challenge_hash, buf, HASH_SIZE))
+                       goto net_err;
+       }
        /* auth successful */
        alarm(0);
        PARA_INFO_LOG("good auth for %s\n", cc->u->name);
index 15bed6dfa33c896421ab17a89ec426669cf4b025..1bce35f53f9ef3411a57bc6ea4564df6eea419b7 100644 (file)
@@ -37,7 +37,7 @@ static void compress_close(struct filter_node *fn)
        free(fn->private_data);
 }
 
-static int compress_post_select(__a_unused struct sched *s, void *context)
+static int compress_post_monitor(__a_unused struct sched *s, void *context)
 {
        struct filter_node *fn = context;
        struct private_compress_data *pcd = fn->private_data;
@@ -59,14 +59,14 @@ next_buffer:
        btr_merge(btrn, fn->min_iqs);
        length = btr_next_buffer(btrn, &inbuf) & ~(size_t)1;
        if (length == 0) { /* eof and 1 byte available */
-               ret = -E_COMPRESS_EOF;
+               ret = -E_EOF;
                goto err;
        }
        ip = (int16_t *)inbuf;
        if (inplace)
                op = ip;
        else
-               op = para_malloc(length);
+               op = alloc(length);
        for (i = 0; i < length / 2; i++) {
                /* be careful in that heat, my dear */
                int sample = *ip++;
@@ -79,7 +79,7 @@ next_buffer:
                sample *= pcd->current_gain;
                sample >>= inertia + 1;
                if (sample > 32767) { /* clip */
-                       PARA_WARNING_LOG("clip: %d\n", sample);
+                       PARA_NOTICE_LOG("clip: %d\n", sample);
                        sample = 32767;
                        pcd->current_gain = (3 * pcd->current_gain +
                                (1 << inertia)) / 4;
@@ -116,7 +116,7 @@ err:
 
 static void compress_open(struct filter_node *fn)
 {
-       struct private_compress_data *pcd = para_calloc(sizeof(*pcd));
+       struct private_compress_data *pcd = zalloc(sizeof(*pcd));
        uint32_t inertia = U32_OPTVAL(INERTIA, fn->lpr);
        uint32_t aggressiveness = U32_OPTVAL(AGGRESSIVENESS, fn->lpr);
 
@@ -162,6 +162,6 @@ const struct filter lsg_filter_cmd_com_compress_user_data = {
        .setup = compress_setup,
        .open = compress_open,
        .close = compress_close,
-       .pre_select = generic_filter_pre_select,
-       .post_select = compress_post_select,
+       .pre_monitor = generic_filter_pre_monitor,
+       .post_monitor = compress_post_monitor,
 };
index d993e526c8a30bdab55da1dd5a9df6f1ead5b320..92560e00ec50b7fa7ae4fcfd7d8966bdca2c63bf 100644 (file)
@@ -102,9 +102,14 @@ if test $HAVE_OPENSSL = yes; then
        if test "$ac_cv_have_decl_RSA_set0_key" != "$ac_cv_lib_crypto_RSA_set0_key"; then
                AC_MSG_ERROR([openssl header/library mismatch])
        fi
-       test "$ac_cv_have_decl_RSA_set0_key" = yes &&
+       if test "$ac_cv_have_decl_RSA_set0_key" = yes; then
                AC_DEFINE([HAVE_RSA_SET0_KEY], [1], [openssl >= 1.1])
-
+       else
+               AC_MSG_WARN([
+       Old openssl library detected. Support for openssl-1.0 and earlier
+       will be removed in the next major paraslash release. Please upgrade
+       your openssl installation.])
+       fi
        HAVE_CRYPTO_CLEANUP_ALL_EX_DATA=yes
        AC_CHECK_DECL([CRYPTO_cleanup_all_ex_data], [],
                [HAVE_CRYPTO_CLEANUP_ALL_EX_DATA=no],
@@ -114,6 +119,15 @@ if test $HAVE_OPENSSL = yes; then
        test $HAVE_CRYPTO_CLEANUP_ALL_EX_DATA = yes &&
                AC_DEFINE([HAVE_CRYPTO_CLEANUP_ALL_EX_DATA], [1],
                        [not available on FreeBSD 12])
+       HAVE_OPENSSL_THREAD_STOP=yes
+       AC_CHECK_DECL([OPENSSL_thread_stop], [],
+               [HAVE_OPENSSL_THREAD_STOP=no],
+               [#include <openssl/crypto.h>])
+       AC_CHECK_LIB([crypto], [OPENSSL_thread_stop], [],
+               [HAVE_OPENSSL_THREAD_STOP=no])
+       test $HAVE_OPENSSL_THREAD_STOP = yes &&
+               AC_DEFINE([HAVE_OPENSSL_THREAD_STOP], [1],
+                       [not available on openssl-1.0])
 fi
 UNSTASH_FLAGS
 ######################################################################### gcrypt
@@ -282,12 +296,10 @@ AC_DEFUN([NEED_FLAC_OBJECTS], [{
 }])
 ########################################################################### faad
 STASH_FLAGS
-LIB_ARG_WITH([faad], [-lfaad -lmp4ff])
+LIB_ARG_WITH([faad], [-lfaad])
 HAVE_FAAD=yes
 AC_CHECK_HEADER(neaacdec.h, [], HAVE_FAAD=no)
-AC_CHECK_HEADER(mp4ff.h, [], HAVE_FAAD=no)
 AC_CHECK_LIB([faad], [NeAACDecOpen], [], HAVE_FAAD=no)
-AC_CHECK_LIB([mp4ff], [mp4ff_meta_get_artist], [], HAVE_FAAD=no)
 LIB_SUBST_FLAGS(faad)
 UNSTASH_FLAGS
 ########################################################################### mad
@@ -364,11 +376,16 @@ AC_CHECK_HEADER(samplerate.h, [], HAVE_SAMPLERATE=no)
 AC_CHECK_LIB([samplerate], [src_process], [], HAVE_SAMPLERATE=no)
 LIB_SUBST_FLAGS(samplerate)
 UNSTASH_FLAGS
+######################################################################### ubsan
+AC_ARG_ENABLE([ubsan], [AS_HELP_STRING(--enable-ubsan,
+       [Detect and report undefined behaviour.])],
+       [ENABLE_UBSAN=yes], [ENABLE_UBSAN=no])
+AC_SUBST(ENABLE_UBSAN)
 ######################################################################### server
 if test -n "$CRYPTOLIB" && test $HAVE_OSL = yes && test -n "$BISON" && \
                test -n "$FLEX"; then
        build_server="yes"
-       executables="$executables server"
+       executables="$executables server upgrade_db"
        server_errlist_objs="
                server
                afh_common
@@ -382,7 +399,6 @@ if test -n "$CRYPTOLIB" && test $HAVE_OSL = yes && test -n "$BISON" && \
                daemon
                http_send
                close_on_fork
-               mm
                crypt_common
                base64
                ipc
@@ -421,13 +437,24 @@ if test -n "$CRYPTOLIB" && test $HAVE_OSL = yes && test -n "$BISON" && \
        NEED_OPUS_OBJECTS() && server_errlist_objs="$server_errlist_objs opus_afh opus_common"
        NEED_FLAC_OBJECTS && server_errlist_objs="$server_errlist_objs flac_afh"
        if test $HAVE_FAAD = yes; then
-               server_errlist_objs="$server_errlist_objs aac_afh"
+               server_errlist_objs="$server_errlist_objs aac_afh mp4"
        fi
        server_objs="$server_errlist_objs"
        AC_SUBST(server_objs, add_dot_o($server_objs))
 else
        build_server="no"
 fi
+############################################################# upgrade_db
+upgrade_db_objs='
+       crypt_common
+       exec
+       fd
+       string
+       upgrade_db
+       version
+       base64
+'
+AC_SUBST(upgrade_db_objs, add_dot_o($upgrade_db_objs))
 ############################################################# client
 if test -n "$CRYPTOLIB"; then
        build_client="yes"
@@ -562,7 +589,7 @@ fi
 if test $HAVE_OSS = yes -o $HAVE_ALSA = yes; then
        build_mixer="yes"
        executables="$executables mixer"
-       mixer_errlist_objs="mixer exec string fd lsu version"
+       mixer_errlist_objs="mixer exec string fd time lsu version"
        if test $HAVE_OSS = yes; then
                mixer_errlist_objs="$mixer_errlist_objs oss_mix"
        fi
@@ -669,7 +696,7 @@ NEED_OPUS_OBJECTS && recv_errlist_objs="$recv_errlist_objs opus_afh opus_common"
 NEED_FLAC_OBJECTS && recv_errlist_objs="$recv_errlist_objs flac_afh"
 
 if test $HAVE_FAAD = yes; then
-       recv_errlist_objs="$recv_errlist_objs aac_afh"
+       recv_errlist_objs="$recv_errlist_objs aac_afh mp4"
 fi
 recv_objs="$recv_errlist_objs"
 AC_SUBST(recv_objs, add_dot_o($recv_objs))
@@ -704,7 +731,7 @@ NEED_FLAC_OBJECTS && {
        audio_format_handlers="$audio_format_handlers flac"
 }
 if test $HAVE_FAAD = yes; then
-       afh_errlist_objs="$afh_errlist_objs aac_afh"
+       afh_errlist_objs="$afh_errlist_objs aac_afh mp4"
        audio_format_handlers="$audio_format_handlers aac"
 fi
 
@@ -762,7 +789,7 @@ NEED_FLAC_OBJECTS && {
        play_errlist_objs="$play_errlist_objs flacdec_filter flac_afh"
 }
 if test $HAVE_FAAD = yes; then
-       play_errlist_objs="$play_errlist_objs aac_afh aacdec_filter"
+       play_errlist_objs="$play_errlist_objs aac_afh aacdec_filter mp4"
 fi
 if test $HAVE_MAD = yes; then
        play_errlist_objs="$play_errlist_objs mp3dec_filter"
@@ -818,6 +845,7 @@ audioc_errlist_objs="
        lsu
        net
        fd
+       time
        version
 "
 if test $HAVE_READLINE = yes; then
@@ -825,7 +853,6 @@ if test $HAVE_READLINE = yes; then
                buffer_tree
                interactive
                sched
-               time
        "
 fi
 audioc_objs="$audioc_errlist_objs"
@@ -846,6 +873,7 @@ id3 version 2 support: $HAVE_ID3TAG
 faad: $HAVE_FAAD
 audio format handlers: $audio_format_handlers
 
+exe: $executables
 para_server: $build_server
 para_gui: $build_gui
 para_mixer: $build_mixer
diff --git a/crypt.h b/crypt.h
index 85629591880ad03f2d2d010f194667be1db4c121..5578cd563fae4dc24d673e653dcf21329eba90bc 100644 (file)
--- a/crypt.h
+++ b/crypt.h
@@ -48,7 +48,7 @@ int apc_priv_decrypt(const char *key_file, unsigned char *outbuf,
  * \param key_file The file containing the key.
  * \param result The key structure is returned here.
  *
- * \return The size of the key on success, negative on errors.
+ * \return The size of the key in bytes on success, negative on errors.
  */
 int apc_get_pubkey(const char *key_file, struct asymmetric_key **result);
 
@@ -158,7 +158,7 @@ _static_inline_ void sc_trafo(struct iovec *src, struct iovec *dst,
 /**
  * Deallocate a stream cipher structure.
  *
- * \param sc A stream cipher previously obtained by sc_new().
+ * \param sc A stream cipher previously obtained by \ref sc_new().
  */
 void sc_free(struct stream_cipher *sc);
 
@@ -188,7 +188,7 @@ void hash_function(const char *data, unsigned long len, unsigned char *hash);
  * will be filled by the function with the ascii representation of the hash
  * value given by \a hash, and a terminating \p NULL byte.
  */
-void hash_to_asc(unsigned char *hash, char *asc);
+void hash_to_asc(const unsigned char *hash, char *asc);
 
 /**
  * Compare two hashes.
@@ -199,4 +199,43 @@ void hash_to_asc(unsigned char *hash, char *asc);
  * \return 1, -1, or zero, depending on whether \a h1 is greater than,
  * less than or equal to h2, respectively.
  */
-int hash_compare(unsigned char *h1, unsigned char *h2);
+int hash_compare(const unsigned char *h1, const unsigned char *h2);
+
+/** Size of the hash value in bytes. */
+#define HASH2_SIZE 32
+
+/**
+ * Compute the hash2 of the given input data.
+ *
+ * \param data Pointer to the data to compute the hash value from.
+ * \param len The length of \a data in bytes.
+ * \param hash Result pointer.
+ *
+ * \a hash must point to an area at least \p HASH2_SIZE bytes large.
+ *
+ * \sa sha(3), openssl(1).
+ * */
+void hash2_function(const char *data, unsigned long len, unsigned char *hash);
+
+/**
+ * Convert a hash2 value to ascii format.
+ *
+ * \param hash the hash value.
+ * \param asc Result pointer.
+ *
+ * \a asc must point to an area of at least 2 * \p HASH2_SIZE + 1 bytes which
+ * will be filled by the function with the ascii representation of the hash
+ * value given by \a hash, and a terminating \p NULL byte.
+ */
+void hash2_to_asc(const unsigned char *hash, char *asc);
+
+/**
+ * Compare two version 2 hashes.
+ *
+ * \param h1 Pointer to the first hash value.
+ * \param h2 Pointer to the second hash value.
+ *
+ * \return 1, -1, or zero, depending on whether \a h1 is greater than,
+ * less than or equal to h2, respectively.
+ */
+int hash2_compare(const unsigned char *h1, const unsigned char *h2);
index c1e40d920ff425ccfdcc536c5fb69cd0422472ca..286ebe38f2215b879c3171183490b2199779f500 100644 (file)
@@ -135,7 +135,7 @@ int check_private_key_file(const char *file)
        return 1;
 }
 
-void hash_to_asc(unsigned char *hash, char *asc)
+void hash_to_asc(const unsigned char *hash, char *asc)
 {
        int i;
        const char hexchar[] = "0123456789abcdef";
@@ -147,7 +147,7 @@ void hash_to_asc(unsigned char *hash, char *asc)
        asc[2 * HASH_SIZE] = '\0';
 }
 
-int hash_compare(unsigned char *h1, unsigned char *h2)
+int hash_compare(const unsigned char *h1, const unsigned char *h2)
 {
        int i;
 
@@ -160,6 +160,31 @@ int hash_compare(unsigned char *h1, unsigned char *h2)
        return 0;
 }
 
+void hash2_to_asc(const unsigned char *hash, char *asc)
+{
+       int i;
+       const char hexchar[] = "0123456789abcdef";
+
+       for (i = 0; i < HASH2_SIZE; i++) {
+               asc[2 * i] = hexchar[hash[i] >> 4];
+               asc[2 * i + 1] = hexchar[hash[i] & 0xf];
+       }
+       asc[2 * HASH2_SIZE] = '\0';
+}
+
+int hash2_compare(const unsigned char *h1, const unsigned char *h2)
+{
+       int i;
+
+       for (i = 0; i < HASH2_SIZE; i++) {
+               if (h1[i] < h2[i])
+                       return -1;
+               if (h1[i] > h2[i])
+                       return 1;
+       }
+       return 0;
+}
+
 /**
  * Check header of an openssh private key and compute bignum offset.
  *
@@ -270,7 +295,7 @@ int decode_private_key(const char *key_file, unsigned char **result,
                key_type = PKT_PEM;
                begin = map + strlen(PRIVATE_PEM_KEY_HEADER);
                footer = strstr(map, PRIVATE_PEM_KEY_FOOTER);
-               PARA_INFO_LOG("detected legacy PEM key %s\n", key_file);
+               PARA_WARNING_LOG("detected legacy PEM key %s\n", key_file);
        } else if (strncmp(map, PRIVATE_OPENSSH_KEY_HEADER,
                        strlen(PRIVATE_OPENSSH_KEY_HEADER)) == 0) {
                key_type = PKT_OPENSSH;
@@ -292,7 +317,7 @@ int decode_private_key(const char *key_file, unsigned char **result,
                goto unmap;
 
        key_size = footer - begin;
-       key = para_malloc(key_size + 1);
+       key = alloc(key_size + 1);
        for (i = 0, j = 0; begin + i < footer; i++) {
                if (para_isspace(begin[i]))
                        continue;
index a4e2f3193730d456ce9908eb55888cdb759154e0..d8f598bef0c2eb1709fa46169c314e17a212d7b8 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -58,24 +58,30 @@ static void daemon_set_default_log_colors(void)
 }
 
 /**
- * Set the color for one loglevel.
+ * Set the color for log messages of the given severity level.
  *
- * \param arg Must be of the form "ll:[fg [bg]] [attr]".
+ * \param arg Must be of the form "severity:[fg [bg]] [attr]".
  */
 void daemon_set_log_color_or_die(const char *arg)
 {
+       unsigned ll;
+       const char * const sev[] = {SEVERITIES};
        char *p = strchr(arg, ':');
-       int ret, ll;
 
        if (!p)
                goto err;
-       ret = get_loglevel_by_name(arg);
-       if (ret < 0)
-               goto err;
-       ll = ret;
-       p++;
-       color_parse_or_die(p, me->log_colors[ll]);
-       return;
+       for (ll = 0; ll < NUM_LOGLEVELS; ll++) {
+               const char *name = sev[ll];
+               /*
+                * Parse only the first part of the string so that, for
+                * example, the argument "info:something_else" is recognized.
+                * Note that the string comparison is performed
+                * case-insensitively.
+                */
+               if (strncasecmp(arg, name, strlen(name)))
+                       continue;
+               return color_parse_or_die(p + 1, me->log_colors[ll]);
+       }
 err:
        PARA_EMERG_LOG("%s: invalid color argument\n", arg);
        exit(EXIT_FAILURE);
@@ -134,16 +140,29 @@ void daemon_set_logfile(const char *logfile_name)
 }
 
 /**
- * Suppress log messages with severity lower than the given loglevel.
+ * Control the verbosity for logging.
  *
- * \param loglevel The smallest level that should be logged.
+ * This instructs the daemon to not log subsequent messages whose severity is
+ * lower than the given value.
+ *
+ * \param loglevel The new log level.
  */
-void daemon_set_loglevel(const char *loglevel)
+void daemon_set_loglevel(int loglevel)
 {
-       int ret = get_loglevel_by_name(loglevel);
+       assert(loglevel >= 0);
+       assert(loglevel < NUM_LOGLEVELS);
+       me->loglevel = loglevel;
+}
 
-       assert(ret >= 0);
-       me->loglevel = ret;
+/**
+ * Get the current log level of the daemon.
+ *
+ * \return Greater or equal than zero and less than NUM_LOGLEVELS. This
+ * function never fails.
+ */
+int daemon_get_loglevel(void)
+{
+       return me->loglevel;
 }
 
 /**
@@ -263,9 +282,9 @@ void daemon_close_log(void)
 }
 
 /**
- * fopen() the logfile in append mode.
+ * Open the logfile in append mode.
  *
- * \return Either succeeds or exits.
+ * This function either succeeds or exits.
  */
 void daemon_open_log_or_die(void)
 {
index b530b0d76b48b673fd7ae7626c48a6dd91ab7751..54879924954d0352d25cec928c1bcff9a6f9e58e 100644 (file)
--- a/daemon.h
+++ b/daemon.h
@@ -13,7 +13,8 @@ __malloc char *daemon_get_uptime_str(const struct timeval *current_time);
 void daemon_set_logfile(const char *logfile_name);
 void daemon_set_hooks(void (*pre_log_hook)(void), void (*post_log_hook)(void));
 void daemon_set_flag(unsigned flag);
-void daemon_set_loglevel(const char *loglevel);
+int daemon_get_loglevel(void);
+void daemon_set_loglevel(int loglevel);
 bool daemon_init_colors_or_die(int color_arg, int color_arg_auto,
                int color_arg_no, bool logfile_given);
 void daemon_set_log_color_or_die(const char *arg);
index 639c93fcbf99bc93ddc054ad43d03f30811270ac..0b20bcc8b1149132c4a1fa81a238c4479cb60df6 100644 (file)
 #include "net.h"
 #include "fd.h"
 
+#ifndef DCCP_SOCKOPT_CCID
+#define DCCP_SOCKOPT_CCID 13 /**< Sets both TX/RX CCID. */
+#endif
+
 static void dccp_recv_close(struct receiver_node *rn)
 {
        if (rn->fd > 0)
@@ -59,6 +63,9 @@ static int dccp_recv_ccid_support_check(const struct lls_parse_result *lpr)
        return 1;
 }
 
+/** Flowopt shortcut */
+#define OPT_ADD(fo, lev, opt, val, len) flowopt_add(fo, lev, opt, #opt, val, len)
+
 static int dccp_recv_open(struct receiver_node *rn)
 {
        struct lls_parse_result *lpr = rn->lpr;
@@ -76,14 +83,14 @@ static int dccp_recv_open(struct receiver_node *rn)
        /* Copy CCID preference list (u8 array required) */
        given = lls_opt_given(r_c);
        if (given) {
-               ccids = para_malloc(given);
+               ccids = alloc(given);
                fo = flowopt_new();
                for (i = 0; i < given; i++)
                        ccids[i] = lls_int32_val(i, r_c);
                OPT_ADD(fo, SOL_DCCP, DCCP_SOCKOPT_CCID, ccids, i);
        }
 
-       fd = makesock(IPPROTO_DCCP, 0, host, port, fo);
+       fd = makesock(IPPROTO_DCCP, false, host, port, fo);
        flowopt_cleanup(fo);
        free(ccids);
        if (fd < 0)
@@ -109,16 +116,16 @@ err:
        return ret;
 }
 
-static void dccp_recv_pre_select(struct sched *s, void *context)
+static void dccp_recv_pre_monitor(struct sched *s, void *context)
 {
        struct receiver_node *rn = context;
 
-       if (generic_recv_pre_select(s, rn) <= 0)
+       if (generic_recv_pre_monitor(s, rn) <= 0)
                return;
-       para_fd_set(rn->fd, &s->rfds, &s->max_fileno);
+       sched_monitor_readfd(rn->fd, s);
 }
 
-static int dccp_recv_post_select(struct sched *s, void *context)
+static int dccp_recv_post_monitor(__a_unused struct sched *s, void *context)
 {
        struct receiver_node *rn = context;
        struct btr_node *btrn = rn->btrn;
@@ -136,7 +143,7 @@ static int dccp_recv_post_select(struct sched *s, void *context)
        ret = -E_DCCP_OVERRUN;
        if (iovcnt == 0)
                goto out;
-       ret = readv_nonblock(rn->fd, iov, iovcnt, &s->rfds, &num_bytes);
+       ret = readv_nonblock(rn->fd, iov, iovcnt, &num_bytes);
        if (num_bytes == 0)
                goto out;
        if (num_bytes <= iov[0].iov_len) /* only the first buffer was filled */
@@ -154,6 +161,6 @@ out:
 const struct receiver lsg_recv_cmd_com_dccp_user_data = {
        .open = dccp_recv_open,
        .close = dccp_recv_close,
-       .pre_select = dccp_recv_pre_select,
-       .post_select = dccp_recv_post_select,
+       .pre_monitor = dccp_recv_pre_monitor,
+       .post_monitor = dccp_recv_post_monitor,
 };
index 55f189ad4eb01632dbcd653e104ef7f0cfe54ff8..6182c964fde04719ee4736dc43482caf434ee395 100644 (file)
@@ -24,8 +24,8 @@
 #include "net.h"
 #include "server.h"
 #include "list.h"
-#include "send.h"
 #include "sched.h"
+#include "send.h"
 #include "vss.h"
 #include "fd.h"
 
@@ -36,16 +36,19 @@ struct dccp_fec_client {
        struct fec_client *fc;
 };
 
-static void dccp_pre_select(int *max_fileno, fd_set *rfds,
-               __a_unused fd_set *wfds)
+static void dccp_pre_monitor(struct sched *s)
 {
        unsigned n;
 
        FOR_EACH_LISTEN_FD(n, dss)
                if (dss->listen_fds[n] >= 0)
-                       para_fd_set(dss->listen_fds[n], rfds, max_fileno);
+                       sched_monitor_readfd(dss->listen_fds[n], s);
 }
 
+#ifndef DCCP_SOCKOPT_TX_CCID
+#define DCCP_SOCKOPT_TX_CCID 14 /**< Set/get the TX CCID. */
+#endif
+
 /**
  * Query the TX CCID used on the sender-client half connection.
  * \param sockfd Server socket descriptor to query (after accept(2)).
@@ -90,6 +93,13 @@ static void dccp_shutdown(void)
        free_sender_status(dss);
 }
 
+#ifndef DCCP_SOCKOPT_GET_CUR_MPS
+#define DCCP_SOCKOPT_GET_CUR_MPS 5 /**< Max packet size, RFC 4340, 14. */
+#endif
+
+/** Estimated worst-case length of a DCCP header including options. */
+#define DCCP_MAX_HEADER 128
+
 /** * Obtain current MPS according to RFC 4340, sec. 14. */
 static int dccp_init_fec(struct sender_client *sc)
 {
@@ -119,14 +129,14 @@ static void dccp_send_fec(struct sender_client *sc, char *buf, size_t len)
                dccp_shutdown_client(sc);
 }
 
-static void dccp_post_select(fd_set *rfds, __a_unused fd_set *wfds)
+static void dccp_post_monitor(__a_unused struct sched *s)
 {
        struct sender_client *sc;
        struct dccp_fec_client *dfc;
        int tx_ccid;
        uint32_t k, n;
 
-       sc = accept_sender_client(dss, rfds);
+       sc = accept_sender_client(dss);
        if (!sc)
                return;
 
@@ -148,7 +158,7 @@ static void dccp_post_select(fd_set *rfds, __a_unused fd_set *wfds)
                shutdown_client(sc, dss);
                return;
        }
-       dfc = para_calloc(sizeof(*dfc));
+       dfc = zalloc(sizeof(*dfc));
        sc->private_data = dfc;
        k = OPT_UINT32_VAL(DCCP_DATA_SLICES_PER_GROUP);
        n = OPT_UINT32_VAL(DCCP_SLICES_PER_GROUP);
@@ -232,6 +242,8 @@ static void dccp_send_init(void)
                OPT_RESULT(DCCP_LISTEN_ADDRESS),
                OPT_UINT32_VAL(DCCP_PORT), OPT_UINT32_VAL(DCCP_MAX_CLIENTS),
                OPT_GIVEN(DCCP_DEFAULT_DENY));
+       if (OPT_GIVEN(DCCP_NO_AUTOSTART))
+               return;
        generic_com_on(dss, IPPROTO_DCCP);
 }
 
@@ -247,8 +259,8 @@ const struct sender dccp_sender = {
        .name = "dccp",
        .init = dccp_send_init,
        .shutdown = dccp_shutdown,
-       .pre_select = dccp_pre_select,
-       .post_select = dccp_post_select,
+       .pre_monitor = dccp_pre_monitor,
+       .post_monitor = dccp_post_monitor,
        .shutdown_clients = dccp_shutdown_clients,
        .client_cmds = {
                [SENDER_on] = dccp_com_on,
diff --git a/error.h b/error.h
index 8ef9e37d9cc4630178641b59146e0e77f4c0567e..8805c9c7a4c0de6fe958e1523df883ad47effaad 100644 (file)
--- a/error.h
+++ b/error.h
@@ -2,6 +2,7 @@
 
 /** \file error.h List of error codes and messages. */
 
+/** \cond para_error */
 /** Codes and messages. */
 #define PARA_ERRORS \
        PARA_ERROR(SUCCESS, "success"), \
        PARA_ERROR(AFS_SHORT_READ, "short read from afs socket"), \
        PARA_ERROR(AFS_SIGNAL, "afs caught deadly signal"), \
        PARA_ERROR(AFS_SOCKET, "afs socket not writable"), \
-       PARA_ERROR(AFT_SYNTAX, "audio file table syntax error"), \
        PARA_ERROR(ALSA, "alsa error"), \
        PARA_ERROR(ALSA_MIX_GET_VAL, "could not read control element state"), \
        PARA_ERROR(ALSA_MIX_OPEN, "could not open mixer"), \
        PARA_ERROR(ALSA_MIX_RANGE, "value control element out of range"), \
        PARA_ERROR(ALSA_MIX_SET_VAL, "could not set control element state"), \
-       PARA_ERROR(AMP_EOF, "amp: end of file"), \
        PARA_ERROR(AMP_ZERO_AMP, "no amplification necessary"), \
        PARA_ERROR(AO_APPEND_OPTION, "ao append option: memory allocation failure"), \
        PARA_ERROR(AO_BAD_DRIVER, "ao: invalid driver"), \
        PARA_ERROR(AO_BAD_OPTION, "ao option is not of type key:value"), \
        PARA_ERROR(AO_DEFAULT_DRIVER, "ao: no usable output device"), \
-       PARA_ERROR(AO_EOF, "ao: end of file"), \
        PARA_ERROR(AO_FILE_NOT_SUPP, "ao: file io drivers not supported"), \
        PARA_ERROR(AO_OPEN_LIVE, "ao: could not open audio device"), \
        PARA_ERROR(AO_PLAY, "ao_play() failed"), \
        PARA_ERROR(AO_PTHREAD, "pthread error"), \
-       PARA_ERROR(ARG_NOT_FOUND, "argument not found in arg vector"), \
        PARA_ERROR(ASN1_PARSE, "could not parse ASN.1 key"), \
        PARA_ERROR(ATOI_JUNK_AT_END, "further characters after number"), \
        PARA_ERROR(ATOI_NO_DIGITS, "no digits found in string"), \
        PARA_ERROR(ATOI_OVERFLOW, "value too large"), \
        PARA_ERROR(ATTR_SYNTAX, "attribute syntax error"), \
        PARA_ERROR(ATT_TABLE_FULL, "no more space left in attribute table"), \
-       PARA_ERROR(AUDIOC_EOF, "audioc: end of file"), \
        PARA_ERROR(AUDIOD_OFF, "audiod switched off"), \
        PARA_ERROR(AUDIOD_SIGNAL, "caught deadly signal"), \
        PARA_ERROR(AUDIOD_TERM, "terminating on user request"), \
@@ -52,7 +48,7 @@
        PARA_ERROR(BAD_CT, "invalid chunk table or bad FEC configuration"), \
        PARA_ERROR(BAD_FEATURE, "invalid feature request"), \
        PARA_ERROR(BAD_FEC_HEADER, "invalid fec header"), \
-       PARA_ERROR(BAD_LL, "invalid loglevel"), \
+       PARA_ERROR(BAD_MOP, "invalid mood or playlist"), \
        PARA_ERROR(BAD_PATH, "invalid path"), \
        PARA_ERROR(BAD_PRIVATE_KEY, "invalid private key"), \
        PARA_ERROR(BAD_SAMPLE_FORMAT, "sample format not supported"), \
@@ -66,7 +62,6 @@
        PARA_ERROR(BIGNUM, "bignum error"), \
        PARA_ERROR(BLINDING, "failed to activate key blinding"), \
        PARA_ERROR(BLOB_SYNTAX, "blob syntax error"), \
-       PARA_ERROR(BTR_EOF, "buffer tree: end of file"), \
        PARA_ERROR(BTR_NAVAIL, "btr node: value currently unavailable"), \
        PARA_ERROR(BTR_NO_CHILD, "btr node has no children"), \
        PARA_ERROR(CHILD_CONTEXT, "now running in child context"), \
@@ -74,7 +69,6 @@
        PARA_ERROR(CLIENT_SYNTAX, "syntax error"), \
        PARA_ERROR(CLIENT_WRITE, "client write error"), \
        PARA_ERROR(COMMAND_SYNTAX, "syntax error in command"), \
-       PARA_ERROR(COMPRESS_EOF, "compress: end of file"), \
        PARA_ERROR(CREATE_OPUS_DECODER, "could not create opus decoder"), \
        PARA_ERROR(DCCP_OVERRUN, "dccp output buffer buffer overrun"), \
        PARA_ERROR(DECRYPT, "decrypt error"), \
@@ -83,8 +77,8 @@
        PARA_ERROR(EMPTY, "file is empty"), \
        PARA_ERROR(ENCRYPT, "encrypt error"), \
        PARA_ERROR(EOF, "end of file"), \
+       PARA_ERROR(EOP, "end of playlist"), \
        PARA_ERROR(FEC_BAD_IDX, "invalid index vector"), \
-       PARA_ERROR(FECDEC_EOF, "received eof packet"), \
        PARA_ERROR(FECDEC_OVERRUN, "fecdec output buffer overrun"), \
        PARA_ERROR(FEC_PARMS, "invalid fec parameters"), \
        PARA_ERROR(FEC_PIVOT, "pivot column not found"), \
@@ -97,7 +91,6 @@
        PARA_ERROR(FLAC_CHAIN_READ, "could not read meta chain"), \
        PARA_ERROR(FLACDEC_DECODER_ALLOC, "could not allocate stream decoder"), \
        PARA_ERROR(FLACDEC_DECODER_INIT, "could not init stream decoder"), \
-       PARA_ERROR(FLACDEC_EOF, "flacdec encountered end of file condition"), \
        PARA_ERROR(FLAC_DECODE_POS, "could not get decode position"), \
        PARA_ERROR(FLAC_ITER_ALLOC, "could not allocate meta iterator"), \
        PARA_ERROR(FLAC_REPLACE_COMMENT, "could not replace vorbis comment"), \
        PARA_ERROR(HEADER_BITRATE, "invalid header bitrate"), \
        PARA_ERROR(HEADER_FREQ, "invalid header frequency"), \
        PARA_ERROR(HTTP_RECV_OVERRUN, "http_recv: output buffer overrun"), \
-       PARA_ERROR(I9E_EOF, "end of input"), \
        PARA_ERROR(I9E_SETUPTERM, "failed to set up terminal"), \
        PARA_ERROR(I9E_TERM_RQ, "received termination request"), \
        PARA_ERROR(ID3_ATTACH, "could not attach id3 frame"), \
        PARA_ERROR(MAKESOCK, "makesock error"), \
        PARA_ERROR(MAX_CLIENTS, "maximal number of clients exceeded"), \
        PARA_ERROR(MISSING_COLON, "syntax error: missing colon"), \
-       PARA_ERROR(MOOD_SYNTAX, "mood syntax error"), \
        PARA_ERROR(MOOD_PARSE, "mood parse error"), \
        PARA_ERROR(MP3DEC_CORRUPT, "too many corrupt frames"), \
-       PARA_ERROR(MP3DEC_EOF, "mp3dec: end of file"), \
        PARA_ERROR(MP3_INFO, "could not read mp3 info"), \
-       PARA_ERROR(MP4FF_BAD_CHANNEL_COUNT, "mp4ff: invalid number of channels"), \
-       PARA_ERROR(MP4FF_BAD_SAMPLE, "mp4ff: invalid sample number"), \
-       PARA_ERROR(MP4FF_BAD_SAMPLERATE, "mp4ff: invalid sample rate"), \
-       PARA_ERROR(MP4FF_BAD_SAMPLE_COUNT, "mp4ff: invalid number of samples"), \
-       PARA_ERROR(MP4FF_META_READ, "mp4ff: could not read mp4 metadata"), \
-       PARA_ERROR(MP4FF_META_WRITE, "mp4ff: could not update mp4 metadata"), \
-       PARA_ERROR(MP4FF_OPEN, "mp4ff: open failed"), \
-       PARA_ERROR(MP4FF_TRACK, "mp4ff: no audio track"), \
+       PARA_ERROR(MP4_READ, "mp4: read error or unexpected end of file"), \
+       PARA_ERROR(MP4_CORRUPT, "invalid/corrupt mp4 file"), \
+       PARA_ERROR(MP4_BAD_SAMPLE, "mp4: invalid sample number"), \
+       PARA_ERROR(MP4_BAD_SAMPLERATE, "mp4: invalid sample rate"), \
+       PARA_ERROR(MP4_BAD_SAMPLE_COUNT, "mp4: invalid number of samples"), \
+       PARA_ERROR(MP4_TRACK, "mp4: no audio track"), \
+       PARA_ERROR(MP4_MISSING_ATOM, "mp4: essential atom not found"), \
        PARA_ERROR(MPI_SCAN, "could not scan multi-precision integer"), \
        PARA_ERROR(NAME_TOO_LONG, "name too long for struct sockaddr_un"), \
        PARA_ERROR(NO_AFHI, "audio format handler info required"), \
        PARA_ERROR(NO_AUDIO_FILE, "no audio file"), \
        PARA_ERROR(NOFD, "did not receive open fd from afs"), \
        PARA_ERROR(NO_MATCH, "no matches"), \
-       PARA_ERROR(NO_MOOD, "no mood available"), \
        PARA_ERROR(NO_MORE_SLOTS, "no more empty slots"), \
        PARA_ERROR(NOT_PLAYING, "not playing"), \
        PARA_ERROR(NO_VALID_FILES, "no valid file found in playlist"), \
        PARA_ERROR(OGGDEC_VERSION, "vorbis version mismatch"), \
        PARA_ERROR(OGG_EMPTY, "no ogg pages found"), \
        PARA_ERROR(OGG_PACKET_IN, "ogg_stream_packetin() failed"), \
-       PARA_ERROR(OGG_STREAM_FLUSH, "ogg_stream_flush() failed"), \
        PARA_ERROR(OGG_SYNC, "internal ogg storage overflow"), \
        PARA_ERROR(OPENSSH_PARSE, "could not parse openssh private key"), \
        PARA_ERROR(OPUS_COMMENT, "invalid or corrupted opus comment"), \
        PARA_ERROR(OPUS_HEADER, "invalid opus header"), \
        PARA_ERROR(OPUS_SET_GAIN, "opus: could not set gain"), \
        PARA_ERROR(PATH_FOUND, ""), /* not really an error */ \
-       PARA_ERROR(PERM, "permission denied"), \
        PARA_ERROR(PLAYLIST_EMPTY, "attempted to load empty playlist"), \
-       PARA_ERROR(PLAYLIST_LOADED, ""), /* not really an error */ \
        PARA_ERROR(PREBUFFER_SUCCESS, "prebuffering complete"), \
        PARA_ERROR(PRIVATE_KEY, "can not read private key"), \
        PARA_ERROR(QUEUE, "packet queue overrun"), \
        PARA_ERROR(READ_PATTERN, "did not read expected pattern"), \
-       PARA_ERROR(RECV_EOF, "end of file"), \
        PARA_ERROR(RECVMSG, "recvmsg() failed"), \
        PARA_ERROR(REGEX, "regular expression error"), \
-       PARA_ERROR(RESAMPLE_EOF, "resample filter: end of file"), \
        PARA_ERROR(RSA, "RSA error"), \
        PARA_ERROR(RSA_DECODE, "RSA decoding error"), \
        PARA_ERROR(SB_PACKET_SIZE, "invalid sideband packet size or protocol error"), \
        PARA_ERROR(SERVER_CMD_FAILURE, "command failed"), \
        PARA_ERROR(SERVER_CMD_SUCCESS, "command terminated successfully"), \
        PARA_ERROR(SERVER_CRASH, "para_server crashed -- can not live without it"), \
-       PARA_ERROR(SERVER_EOF, "connection closed by para_server"), \
        PARA_ERROR(SEXP_BUILD, "could not build S-expression"), \
        PARA_ERROR(SEXP_DECRYPT, "could not decrypt S-expression"), \
        PARA_ERROR(SEXP_ENCRYPT, "could not encrypt S-expression"), \
        PARA_ERROR(VORBIS_COMMENTHEADER, "could not create vorbis comment header"), \
        PARA_ERROR(VORBIS, "vorbis synthesis header-in error (not vorbis?)"), \
        PARA_ERROR(WAV_BAD_FC, "invalid filter configuration"), \
-       PARA_ERROR(WAV_EOF, "wav filter: end of file"), \
        PARA_ERROR(WAV_SUCCESS, "successfully wrote wav header"), \
        PARA_ERROR(WMA_BAD_PARAMS, "invalid WMA parameters"), \
        PARA_ERROR(WMA_BAD_SUPERFRAME, "invalid superframe"), \
        PARA_ERROR(WMA_BLOCK_SIZE, "invalid block size"), \
-       PARA_ERROR(WMADEC_EOF, "wmadec: end of file"), \
        PARA_ERROR(WMA_NO_GUID, "audio stream guid not found"), \
        PARA_ERROR(WMA_OUTPUT_SPACE, "insufficient output space"), \
-       PARA_ERROR(WRITE_COMMON_EOF, "end of file"), \
 
 /**
  * This is temporarily defined to expand to its first argument (prefixed by
  * 'E_') and gets later redefined to expand to the error text only
  */
 #define PARA_ERROR(err, msg) E_ ## err
+/**
+ * Numeric error codes.
+ *
+ * Public functions which can fail return the negated value of one of the
+ * constants defined here to indicate the cause of the error.
+ *
+ * \sa \ref para_strerror().
+ */
 enum para_error_codes {PARA_ERRORS};
 #undef PARA_ERROR
 #define PARA_ERROR(err, msg) msg
@@ -259,6 +246,7 @@ enum para_error_codes {PARA_ERRORS};
 extern const char * const para_errlist[];
 /** Exactly one .c file per executable must define the array. */
 #define DEFINE_PARA_ERRLIST const char * const para_errlist[] = {PARA_ERRORS}
+/** \endcond para_error */
 
 /**
  * This bit indicates whether a number is considered a system error number
@@ -302,18 +290,20 @@ static const char *weak_lls_strerror(int) __attribute__ ((weakref("lls_strerror"
  */
 _static_inline_ const char *para_strerror(int num)
 {
+       unsigned idx = num & ~((1U << OSL_ERROR_BIT) | (1U << LLS_ERROR_BIT)
+               | (1U << SYSTEM_ERROR_BIT));
        assert(num > 0);
        if (IS_OSL_ERROR(num)) {
                assert(weak_osl_strerror);
-               return weak_osl_strerror(num & ~(1U << OSL_ERROR_BIT));
+               return weak_osl_strerror(idx);
        }
        if (IS_LLS_ERROR(num)) {
                assert(weak_lls_strerror);
-               return weak_lls_strerror(num & ~(1U << LLS_ERROR_BIT));
+               return weak_lls_strerror(idx);
        }
        if (IS_SYSTEM_ERROR(num))
-               return strerror(num & ~(1U << SYSTEM_ERROR_BIT));
-       return para_errlist[num];
+               return strerror(idx);
+       return para_errlist[idx];
 }
 
 /**
diff --git a/fd.c b/fd.c
index 33891d2e6c9f1c3568428b1df93b4b92e295ef61..1af902f9af440cbf570377ed219eea08e76417a2 100644 (file)
--- a/fd.c
+++ b/fd.c
@@ -37,26 +37,27 @@ int xrename(const char *oldpath, const char *newpath)
 }
 
 /**
- * Write an array of buffers to a file descriptor.
+ * Write an array of buffers, handling non-fatal errors.
  *
- * \param fd The file descriptor.
+ * \param fd The file descriptor to write to.
  * \param iov Pointer to one or more buffers.
  * \param iovcnt The number of buffers.
  *
- * EAGAIN/EWOULDBLOCK is not considered a fatal error condition. For example
- * DCCP CCID3 has a sending wait queue which fills up and is emptied
- * asynchronously. The EAGAIN case means that there is currently no space in
- * the wait queue, but this can change at any moment.
+ * EAGAIN, EWOULDBLOCK and EINTR are not considered error conditions. If a
+ * write operation fails with EAGAIN or EWOULDBLOCK, the number of bytes that
+ * have been written so far is returned. In the EINTR case the operation is
+ * retried. Short writes are handled by issuing a subsequent write operation
+ * for the remaining part.
  *
  * \return Negative on fatal errors, number of bytes written else.
  *
  * For blocking file descriptors, this function returns either the sum of all
- * buffer sizes, or the error code of the fatal error that caused the last
- * write call to fail.
+ * buffer sizes or a negative error code which indicates the fatal error that
+ * caused a write call to fail.
  *
- * For nonblocking file descriptors there is a third possibility: Any positive
- * return value less than the sum of the buffer sizes indicates that some bytes
- * have been written but the next write would block.
+ * For nonblocking file descriptors there is a third possibility: Any
+ * non-negative return value less than the sum of the buffer sizes indicates
+ * that a write operation returned EAGAIN/EWOULDBLOCK.
  *
  * \sa writev(2), \ref xwrite().
  */
@@ -126,14 +127,15 @@ int xwrite(int fd, const char *buf, size_t len)
 }
 
 /**
- * Write all data to a file descriptor.
+ * Write to a file descriptor, fail on short writes.
  *
  * \param fd The file descriptor.
- * \param buf The buffer to be sent.
- * \param len The length of \a buf.
+ * \param buf The buffer to be written.
+ * \param len The length of the buffer.
  *
- * This is like \ref xwrite() but returns \p -E_SHORT_WRITE if not
- * all data could be written.
+ * For blocking file descriptors this function behaves identical to \ref
+ * xwrite(). For non-blocking file descriptors it returns -E_SHORT_WRITE
+ * (rather than a value less than len) if not all data could be written.
  *
  * \return Number of bytes written on success, negative error code else.
  */
@@ -149,12 +151,20 @@ int write_all(int fd, const char *buf, size_t len)
 }
 
 /**
- * Write a buffer given by a format string.
+ * A fprintf-like function for raw file descriptors.
+ *
+ * This function creates a string buffer according to the given format and
+ * writes this buffer to a file descriptor.
  *
  * \param fd The file descriptor.
  * \param fmt A format string.
  *
+ * The difference to fprintf(3) is that the first argument is a file
+ * descriptor, not a FILE pointer. This function does not rely on stdio.
+ *
  * \return The return value of the underlying call to \ref write_all().
+ *
+ * \sa fprintf(3), \ref xvasprintf().
  */
 __printf_2_3 int write_va_buffer(int fd, const char *fmt, ...)
 {
@@ -176,14 +186,11 @@ __printf_2_3 int write_va_buffer(int fd, const char *fmt, ...)
  * \param fd The file descriptor to read from.
  * \param iov Scatter/gather array used in readv().
  * \param iovcnt Number of elements in \a iov.
- * \param rfds An optional fd set pointer.
  * \param num_bytes Result pointer. Contains the number of bytes read from \a fd.
  *
- * If rfds is not NULL and the (non-blocking) file descriptor fd is not set in
- * rfds, this function returns early without doing anything. Otherwise it tries
- * to read up to sz bytes from fd, where sz is the sum of the lengths of all
- * vectors in iov. Like \ref xwrite(), EAGAIN and EINTR are not considered
- * error conditions. However, EOF is.
+ * This function tries to read up to sz bytes from fd, where sz is the sum of
+ * the lengths of all vectors in iov. Like \ref xwrite(), EAGAIN and EINTR are
+ * not considered error conditions. However, EOF is.
  *
  * \return Zero or a negative error code. If the underlying call to readv(2)
  * returned zero (indicating an end of file condition) or failed for some
@@ -197,24 +204,12 @@ __printf_2_3 int write_va_buffer(int fd, const char *fmt, ...)
  *
  * \sa \ref xwrite(), read(2), readv(2).
  */
-int readv_nonblock(int fd, struct iovec *iov, int iovcnt, fd_set *rfds,
-               size_t *num_bytes)
+int readv_nonblock(int fd, struct iovec *iov, int iovcnt, size_t *num_bytes)
 {
        int ret, i, j;
 
        *num_bytes = 0;
-       /*
-        * Avoid a shortcoming of select(): Reads from a non-blocking fd might
-        * return EAGAIN even if FD_ISSET() returns true. However, FD_ISSET()
-        * returning false definitely means that no data can currently be read.
-        * This is the common case, so it is worth to avoid the overhead of the
-        * read() system call in this case.
-        */
-       if (rfds && !FD_ISSET(fd, rfds))
-               return 0;
-
        for (i = 0, j = 0; i < iovcnt;) {
-
                /* fix up the first iov */
                assert(j < iov[i].iov_len);
                iov[i].iov_base += j;
@@ -251,7 +246,6 @@ int readv_nonblock(int fd, struct iovec *iov, int iovcnt, fd_set *rfds,
  * \param fd The file descriptor to read from.
  * \param buf The buffer to read data to.
  * \param sz The size of \a buf.
- * \param rfds \see \ref readv_nonblock().
  * \param num_bytes \see \ref readv_nonblock().
  *
  * This is a simple wrapper for readv_nonblock() which uses an iovec with a single
@@ -259,102 +253,51 @@ int readv_nonblock(int fd, struct iovec *iov, int iovcnt, fd_set *rfds,
  *
  * \return The return value of the underlying call to readv_nonblock().
  */
-int read_nonblock(int fd, void *buf, size_t sz, fd_set *rfds, size_t *num_bytes)
+int read_nonblock(int fd, void *buf, size_t sz, size_t *num_bytes)
 {
        struct iovec iov = {.iov_base = buf, .iov_len = sz};
-       return readv_nonblock(fd, &iov, 1, rfds, num_bytes);
+       return readv_nonblock(fd, &iov, 1, num_bytes);
 }
 
 /**
- * Read a buffer and check its content for a pattern.
+ * Read a buffer and compare its contents to a string, ignoring case.
  *
- * \param fd The file descriptor to receive from.
- * \param pattern The expected pattern.
- * \param bufsize The size of the internal buffer.
- * \param rfds Passed to read_nonblock().
- *
- * This function tries to read at most \a bufsize bytes from the non-blocking
- * file descriptor \a fd. If at least \p strlen(\a pattern) bytes have been
- * received, the beginning of the received buffer is compared with \a pattern,
- * ignoring case.
+ * \param fd The file descriptor to read from.
+ * \param expectation The expected string to compare to.
  *
- * \return Positive if \a pattern was received, negative on errors, zero if no data
- * was available to read.
+ * The given file descriptor is expected to be in non-blocking mode. The string
+ * comparison is performed using strncasecmp(3).
  *
- * \sa \ref read_nonblock(), \sa strncasecmp(3).
+ * \return Zero if no data was available, positive if a buffer was read whose
+ * contents compare as equal to the expected string, negative otherwise.
+ * Possible errors: (a) not enough data was read, (b) the buffer contents
+ * compared as non-equal, (c) a read error occurred. In the first two cases,
+ * -E_READ_PATTERN is returned. In the read error case the (negative) return
+ * value of the underlying call to \ref read_nonblock() is returned.
  */
-int read_pattern(int fd, const char *pattern, size_t bufsize, fd_set *rfds)
+int read_and_compare(int fd, const char *expectation)
 {
-       size_t n, len;
-       char *buf = para_malloc(bufsize + 1);
-       int ret = read_nonblock(fd, buf, bufsize, rfds, &n);
+       size_t n, len = strlen(expectation);
+       char *buf = alloc(len + 1);
+       int ret = read_nonblock(fd, buf, len, &n);
 
-       buf[n] = '\0';
        if (ret < 0)
                goto out;
+       buf[n] = '\0';
        ret = 0;
        if (n == 0)
                goto out;
        ret = -E_READ_PATTERN;
-       len = strlen(pattern);
        if (n < len)
                goto out;
-       if (strncasecmp(buf, pattern, len) != 0)
+       if (strncasecmp(buf, expectation, len) != 0)
                goto out;
        ret = 1;
 out:
-       if (ret < 0) {
-               PARA_NOTICE_LOG("%s\n", para_strerror(-ret));
-               PARA_NOTICE_LOG("recvd %zu bytes: %s\n", n, buf);
-       }
        free(buf);
        return ret;
 }
 
-/**
- * Check whether a file exists.
- *
- * \param fn The file name.
- *
- * \return True iff file exists.
- */
-bool file_exists(const char *fn)
-{
-       struct stat statbuf;
-
-       return !stat(fn, &statbuf);
-}
-
-/**
- * Paraslash's wrapper for select(2).
- *
- * It calls select(2) (with no exceptfds) and starts over if select() was
- * interrupted by a signal.
- *
- * \param n The highest-numbered descriptor in any of the two sets, plus 1.
- * \param readfds fds that should be checked for readability.
- * \param writefds fds that should be checked for writablility.
- * \param timeout_tv upper bound on the amount of time elapsed before select()
- * returns.
- *
- * \return The return value of the underlying select() call on success, the
- * negative system error code on errors.
- *
- * All arguments are passed verbatim to select(2).
- * \sa select(2) select_tut(2).
- */
-int para_select(int n, fd_set *readfds, fd_set *writefds,
-               struct timeval *timeout_tv)
-{
-       int ret;
-       do
-               ret = select(n, readfds, writefds, NULL, timeout_tv);
-       while (ret < 0 && errno == EINTR);
-       if (ret < 0)
-               return -ERRNO_TO_PARA_ERROR(errno);
-       return ret;
-}
-
 /**
  * Set a file descriptor to blocking mode.
  *
@@ -391,34 +334,6 @@ __must_check int mark_fd_nonblocking(int fd)
        return 1;
 }
 
-/**
- * Set a file descriptor in a fd_set.
- *
- * \param fd The file descriptor to be set.
- * \param fds The file descriptor set.
- * \param max_fileno Highest-numbered file descriptor.
- *
- * This wrapper for FD_SET() passes its first two arguments to \p FD_SET. Upon
- * return, \a max_fileno contains the maximum of the old_value and \a fd.
- *
- * \sa \ref para_select.
-*/
-void para_fd_set(int fd, fd_set *fds, int *max_fileno)
-{
-       assert(fd >= 0 && fd < FD_SETSIZE);
-#if 0
-       {
-               int flags = fcntl(fd, F_GETFL);
-               if (!(flags & O_NONBLOCK)) {
-                       PARA_EMERG_LOG("fd %d is a blocking file descriptor\n", fd);
-                       exit(EXIT_FAILURE);
-               }
-       }
-#endif
-       FD_SET(fd, fds);
-       *max_fileno = PARA_MAX(*max_fileno, fd);
-}
-
 /**
  * Paraslash's wrapper for mmap.
  *
@@ -472,86 +387,32 @@ int para_open(const char *path, int flags, mode_t mode)
 }
 
 /**
- * Wrapper for chdir(2).
- *
- * \param path The specified directory.
- *
- * \return Standard.
- */
-int para_chdir(const char *path)
-{
-       int ret = chdir(path);
-
-       if (ret >= 0)
-               return 1;
-       return -ERRNO_TO_PARA_ERROR(errno);
-}
-
-/**
- * Save the cwd and open a given directory.
+ * Create a directory, don't fail if it already exists.
  *
- * \param dirname Path to the directory to open.
- * \param dir Result pointer.
- * \param cwd File descriptor of the current working directory.
- *
- * \return Standard.
- *
- * Opening the current directory (".") and calling fchdir() to return is
- * usually faster and more reliable than saving cwd in some buffer and calling
- * chdir() afterwards.
- *
- * If \a cwd is not \p NULL "." is opened and the resulting file descriptor is
- * stored in \a cwd. If the function returns success, and \a cwd is not \p
- * NULL, the caller must close this file descriptor (probably after calling
- * fchdir(*cwd)).
- *
- * On errors, the function undos everything, so the caller needs neither close
- * any files, nor change back to the original working directory.
+ * \param path Name of the directory to create.
  *
- * \sa getcwd(3).
+ * This function passes the fixed mode value 0777 to mkdir(3) (which consults
+ * the file creation mask and restricts this value).
  *
+ * \return Zero if the path already existed as a directory or as a symbolic
+ * link which leads to a directory, one if the path did not exist and the
+ * directory has been created successfully, negative error code else.
  */
-static int para_opendir(const char *dirname, DIR **dir, int *cwd)
+int para_mkdir(const char *path)
 {
-       int ret;
+       /*
+        * We call opendir(3) rather than relying on stat(2) because this way
+        * we don't need extra code to get the symlink case right.
+        */
+       DIR *dir = opendir(path);
 
-       *dir = NULL;
-       if (cwd) {
-               ret = para_open(".", O_RDONLY, 0);
-               if (ret < 0)
-                       return ret;
-               *cwd = ret;
-       }
-       ret = para_chdir(dirname);
-       if (ret < 0)
-               goto close_cwd;
-       *dir = opendir(".");
-       if (*dir)
-               return 1;
-       ret = -ERRNO_TO_PARA_ERROR(errno);
-       /* Ignore return value of fchdir() and close(). We're busted anyway. */
-       if (cwd) {
-               int __a_unused ret2 = fchdir(*cwd); /* STFU, gcc */
+       if (dir) {
+               closedir(dir);
+               return 0;
        }
-close_cwd:
-       if (cwd)
-               close(*cwd);
-       return ret;
-}
-
-/**
- * A wrapper for mkdir(2).
- *
- * \param path Name of the directory to create.
- * \param mode The permissions to use.
- *
- * \return Standard.
- */
-int para_mkdir(const char *path, mode_t mode)
-{
-       if (!mkdir(path, mode))
-               return 1;
-       return -ERRNO_TO_PARA_ERROR(errno);
+       if (errno != ENOENT)
+               return -ERRNO_TO_PARA_ERROR(errno);
+       return mkdir(path, 0777) == 0? 1 : -ERRNO_TO_PARA_ERROR(errno);
 }
 
 /**
@@ -624,22 +485,62 @@ out:
  * \param start The start address of the memory mapping.
  * \param length The size of the mapping.
  *
- * \return Standard.
+ * If NULL is passed as the start address, the length value is ignored and the
+ * function does nothing.
+ *
+ * \return Zero if NULL was passed, one if the memory area was successfully
+ * unmapped, a negative error code otherwise.
  *
  * \sa munmap(2), \ref mmap_full_file().
  */
 int para_munmap(void *start, size_t length)
 {
-       int err;
-
        if (!start)
                return 0;
        if (munmap(start, length) >= 0)
                return 1;
-       err = errno;
-       PARA_ERROR_LOG("munmap (%p/%zu) failed: %s\n", start, length,
-               strerror(err));
-       return -ERRNO_TO_PARA_ERROR(err);
+       return -ERRNO_TO_PARA_ERROR(errno);
+}
+
+/**
+ * Simple wrapper for poll(2).
+ *
+ * It calls poll(2) and starts over if the call was interrupted by a signal.
+ *
+ * \param fds See poll(2).
+ * \param nfds See poll(2).
+ * \param timeout See poll(2).
+ *
+ * \return The return value of the underlying poll() call on success, the
+ * negative paraslash error code on errors.
+ *
+ * All arguments are passed verbatim to poll(2).
+ */
+int xpoll(struct pollfd *fds, nfds_t nfds, int timeout)
+{
+       int ret;
+
+       do
+               ret = poll(fds, nfds, timeout);
+       while (ret < 0 && errno == EINTR);
+       return ret < 0? -ERRNO_TO_PARA_ERROR(errno) : ret;
+}
+
+/**
+ * Check a file descriptor for readability.
+ *
+ * \param fd The file descriptor.
+ *
+ * \return positive if fd is ready for reading, zero if it isn't, negative if
+ * an error occurred.
+ *
+ * \sa \ref write_ok().
+ */
+int read_ok(int fd)
+{
+       struct pollfd pfd = {.fd = fd, .events = POLLIN};
+       int ret = xpoll(&pfd, 1, 0);
+       return ret < 0? ret : pfd.revents & POLLIN;
 }
 
 /**
@@ -649,18 +550,14 @@ int para_munmap(void *start, size_t length)
  *
  * \return positive if fd is ready for writing, zero if it isn't, negative if
  * an error occurred.
+ *
+ * \sa \ref read_ok().
  */
-
 int write_ok(int fd)
 {
-       struct timeval tv;
-       fd_set wfds;
-
-       FD_ZERO(&wfds);
-       FD_SET(fd, &wfds);
-       tv.tv_sec = 0;
-       tv.tv_usec = 0;
-       return para_select(fd + 1, NULL, &wfds, &tv);
+       struct pollfd pfd = {.fd = fd, .events = POLLOUT};
+       int ret = xpoll(&pfd, 1, 0);
+       return ret < 0? ret : pfd.revents & POLLOUT;
 }
 
 /**
@@ -681,64 +578,3 @@ void valid_fd_012(void)
                }
        }
 }
-
-/**
- * Traverse the given directory recursively.
- *
- * \param dirname The directory to traverse.
- * \param func The function to call for each entry.
- * \param private_data Pointer to an arbitrary data structure.
- *
- * For each regular file under \a dirname, the supplied function \a func is
- * called.  The full path of the regular file and the \a private_data pointer
- * are passed to \a func. Directories for which the calling process has no
- * permissions to change to are silently ignored.
- *
- * \return Standard.
- */
-int for_each_file_in_dir(const char *dirname,
-               int (*func)(const char *, void *), void *private_data)
-{
-       DIR *dir;
-       struct dirent *entry;
-       int cwd_fd, ret = para_opendir(dirname, &dir, &cwd_fd);
-
-       if (ret < 0)
-               return ret == -ERRNO_TO_PARA_ERROR(EACCES)? 1 : ret;
-       /* scan cwd recursively */
-       while ((entry = readdir(dir))) {
-               mode_t m;
-               char *tmp;
-               struct stat s;
-
-               if (!strcmp(entry->d_name, "."))
-                       continue;
-               if (!strcmp(entry->d_name, ".."))
-                       continue;
-               if (lstat(entry->d_name, &s) == -1)
-                       continue;
-               m = s.st_mode;
-               if (!S_ISREG(m) && !S_ISDIR(m))
-                       continue;
-               tmp = make_message("%s/%s", dirname, entry->d_name);
-               if (!S_ISDIR(m)) {
-                       ret = func(tmp, private_data);
-                       free(tmp);
-                       if (ret < 0)
-                               goto out;
-                       continue;
-               }
-               /* directory */
-               ret = for_each_file_in_dir(tmp, func, private_data);
-               free(tmp);
-               if (ret < 0)
-                       goto out;
-       }
-       ret = 1;
-out:
-       closedir(dir);
-       if (fchdir(cwd_fd) < 0 && ret >= 0)
-               ret = -ERRNO_TO_PARA_ERROR(errno);
-       close(cwd_fd);
-       return ret;
-}
diff --git a/fd.h b/fd.h
index c9e79426f5c27ebf621367ce24b1c073c4e97e23..e4f3090301b94c90c96452a94fb4b5fa11b7d801 100644 (file)
--- a/fd.h
+++ b/fd.h
@@ -5,29 +5,24 @@
 int xrename(const char *oldpath, const char *newpath);
 int write_all(int fd, const char *buf, size_t len);
 __printf_2_3 int write_va_buffer(int fd, const char *fmt, ...);
-bool file_exists(const char *);
-int para_select(int n, fd_set *readfds, fd_set *writefds,
-               struct timeval *timeout_tv);
+int xpoll(struct pollfd *fds, nfds_t nfds, int timeout);
 __must_check int mark_fd_nonblocking(int fd);
 __must_check int mark_fd_blocking(int fd);
-void para_fd_set(int fd, fd_set *fds, int *max_fileno);
 int para_mmap(size_t length, int prot, int flags, int fd, void *map);
 int para_open(const char *path, int flags, mode_t mode);
-int para_mkdir(const char *path, mode_t mode);
-int para_chdir(const char *path);
+int para_mkdir(const char *path);
 int mmap_full_file(const char *filename, int open_mode, void **map,
        size_t *size, int *fd_ptr);
 int para_munmap(void *start, size_t length);
+int read_ok(int fd);
 int write_ok(int fd);
 void valid_fd_012(void);
-int readv_nonblock(int fd, struct iovec *iov, int iovcnt, fd_set *rfds,
-               size_t *num_bytes);
-int read_nonblock(int fd, void *buf, size_t sz, fd_set *rfds, size_t *num_bytes);
-int read_pattern(int fd, const char *pattern, size_t bufsize, fd_set *rfds);
+int readv_nonblock(int fd, struct iovec *iov, int iovcnt, size_t *num_bytes);
+int read_nonblock(int fd, void *buf, size_t sz, size_t *num_bytes);
+int read_and_compare(int fd, const char *expectation);
 int xwrite(int fd, const char *buf, size_t len);
 int xwritev(int fd, struct iovec *iov, int iovcnt);
-int for_each_file_in_dir(const char *dirname,
-               int (*func)(const char *, void *), void *private_data);
+
 /**
  * Write a \p NULL-terminated buffer.
  *
diff --git a/fec.c b/fec.c
index 2301cc8d2d4653b8bc82a8bcc46b5867b5d7af14..932e0693e3786fc28b5a180428a9b296534a1fd5 100644 (file)
--- a/fec.c
+++ b/fec.c
@@ -97,7 +97,7 @@ static void init_mul_table(void)
 
 static unsigned char *alloc_matrix(int rows, int cols)
 {
-       return para_malloc(rows * cols);
+       return arr_alloc(rows, cols);
 }
 
 /*
@@ -245,9 +245,9 @@ static void matmul(unsigned char *a, unsigned char *b, unsigned char *c,
 static int invert_mat(unsigned char *src, int k)
 {
        int irow, icol, row, col, ix, error;
-       int *indxc = para_malloc(k * sizeof(int));
-       int *indxr = para_malloc(k * sizeof(int));
-       int *ipiv = para_malloc(k * sizeof(int)); /* elements used as pivots */
+       int *indxc = arr_alloc(k, sizeof(int));
+       int *indxr = arr_alloc(k, sizeof(int));
+       int *ipiv = arr_alloc(k, sizeof(int)); /* elements used as pivots */
        unsigned char c, *p, *id_row = alloc_matrix(1, k),
                *temp_row = alloc_matrix(1, k);
 
@@ -371,9 +371,9 @@ static void invert_vdm(unsigned char *src, int k)
         * c holds the coefficient of P(x) = Prod (x - p_i), i=0..k-1
         * b holds the coefficient for the matrix inversion
         */
-       c = para_malloc(k);
-       b = para_malloc(k);
-       p = para_malloc(k);
+       c = alloc(k);
+       b = alloc(k);
+       p = alloc(k);
 
        for (j = 1, i = 0; i < k; i++, j += k) {
                c[i] = 0;
@@ -466,7 +466,7 @@ int fec_new(int k, int n, struct fec_parms **result)
 
        if (k < 1 || k > GF_SIZE + 1 || n > GF_SIZE + 1 || k > n)
                return -E_FEC_PARMS;
-       parms = para_malloc(sizeof(struct fec_parms));
+       parms = alloc(sizeof(struct fec_parms));
        parms->k = k;
        parms->n = n;
        parms->enc_matrix = alloc_matrix(n, k);
@@ -607,10 +607,10 @@ int fec_decode(struct fec_parms *parms, unsigned char **data, int *idx,
        if (ret < 0)
                return ret;
        /* do the actual decoding */
-       slice = para_malloc(k * sizeof(unsigned char *));
+       slice = arr_alloc(k, sizeof(unsigned char *));
        for (row = 0; row < k; row++) {
                if (idx[row] >= k) {
-                       slice[row] = para_calloc(sz);
+                       slice[row] = zalloc(sz);
                        for (col = 0; col < k; col++)
                                addmul(slice[row], data[col],
                                        m_dec[row * k + col], sz);
index 13d4f7b22f6a46da75ef4b629275d603fc5f39d1..375f4c0afb244b52e5d7b437a6b612c3da9d7f5b 100644 (file)
@@ -225,8 +225,8 @@ static int add_slice(char *buf, struct fecdec_group *fg)
        }
        if (fg->num_slices == 0) {
                fg->num_slices = fg->h.slices_per_group;
-               fg->idx = para_malloc(fg->num_slices * sizeof(int));
-               fg->data = para_calloc(fg->num_slices * sizeof(unsigned char *));
+               fg->idx = arr_alloc(fg->num_slices, sizeof(int));
+               fg->data = arr_zalloc(fg->num_slices, sizeof(unsigned char *));
        }
        r = fg->num_received_slices;
        /* Check if we already have this slice. */
@@ -236,7 +236,7 @@ static int add_slice(char *buf, struct fecdec_group *fg)
                return 0;
        }
        fg->idx[r] = slice_num;
-       fg->data[r] = para_malloc(fg->h.slice_bytes);
+       fg->data[r] = alloc(fg->h.slice_bytes);
        memcpy(fg->data[r], buf, fg->h.slice_bytes);
        fg->num_received_slices++;
        return 1;
@@ -360,7 +360,7 @@ static int read_fec_header(char *buf, size_t len, struct fec_header *h)
        h->bos = read_u8(buf + 22);
        h->header_stream = read_u8(buf + 23);
        if (!memcmp(buf, FEC_EOF_PACKET, FEC_EOF_PACKET_LEN))
-               return -E_FECDEC_EOF;
+               return -E_EOF;
 //     PARA_DEBUG_LOG("group %u, slize %u, slices per group: %u\n",
 //             h->group_num, h->slice_num, h->slices_per_group);
        return 1;
@@ -431,7 +431,7 @@ static void fecdec_close(struct filter_node *fn)
        fn->private_data = NULL;
 }
 
-static int fecdec_post_select(__a_unused struct sched *s, void *context)
+static int fecdec_post_monitor(__a_unused struct sched *s, void *context)
 {
        struct filter_node *fn = context;
        struct btr_node *btrn = fn->btrn;
@@ -471,14 +471,14 @@ out:
 static void fecdec_open(struct filter_node *fn)
 {
        struct private_fecdec_data *pfd;
-       pfd = para_calloc(sizeof(*pfd));
+       pfd = zalloc(sizeof(*pfd));
        fn->private_data = pfd;
        fn->min_iqs = FEC_HEADER_SIZE;
 }
 
 const struct filter lsg_filter_cmd_com_fecdec_user_data = {
        .open = fecdec_open,
-       .pre_select = generic_filter_pre_select,
-       .post_select = fecdec_post_select,
+       .pre_monitor = generic_filter_pre_monitor,
+       .post_monitor = fecdec_post_monitor,
        .close = fecdec_close,
 };
index 9a5ed5d7fab7dc01fe46fa5ecab6940ea3a28828..ba902070d9498677e94872860aeb9bbcbcd5235e 100644 (file)
@@ -64,12 +64,12 @@ static int prepare_output_file(struct writer_node *wn)
                close(fd);
                return ret;
        }
-       pfwd = wn->private_data = para_calloc(sizeof(*pfwd));
+       pfwd = wn->private_data = zalloc(sizeof(*pfwd));
        pfwd->fd = fd;
        return 1;
 }
 
-static void file_write_pre_select(struct sched *s, void *context)
+static void file_write_pre_monitor(struct sched *s, void *context)
 {
        struct writer_node *wn = context;
        struct private_file_write_data *pfwd = wn->private_data;
@@ -79,7 +79,7 @@ static void file_write_pre_select(struct sched *s, void *context)
                return;
        if (ret < 0 || !pfwd)
                return sched_min_delay(s);
-       para_fd_set(pfwd->fd, &s->wfds, &s->max_fileno);
+       sched_monitor_writefd(pfwd->fd, s);
 }
 
 static void file_write_close(struct writer_node *wn)
@@ -92,7 +92,7 @@ static void file_write_close(struct writer_node *wn)
        free(pfwd);
 }
 
-static int file_write_post_select(__a_unused struct sched *s, void *context)
+static int file_write_post_monitor(__a_unused struct sched *s, void *context)
 {
        struct writer_node *wn = context;
        struct private_file_write_data *pfwd = wn->private_data;
@@ -111,7 +111,7 @@ static int file_write_post_select(__a_unused struct sched *s, void *context)
                ret = prepare_output_file(wn);
                goto out;
        }
-       if (!FD_ISSET(pfwd->fd, &s->wfds))
+       if (!sched_write_ok(pfwd->fd, s))
                return 0;
        bytes = btr_next_buffer(btrn, &buf);
        assert(bytes > 0);
@@ -128,7 +128,7 @@ out:
 
 /** the init function of the file writer */
 struct writer lsg_write_cmd_com_file_user_data = {
-       .pre_select = file_write_pre_select,
-       .post_select = file_write_post_select,
+       .pre_monitor = file_write_pre_monitor,
+       .post_monitor = file_write_post_monitor,
        .close = file_write_close,
 };
index d4a2423904ca96f87f1ec11600f1c67e2b101ae1..722cb16fb35bfaec09bbaca046ebf62931df8036 100644 (file)
--- a/filter.c
+++ b/filter.c
@@ -120,14 +120,14 @@ int main(int argc, char *argv[])
                EMBRACE(.name = "stdin"));
        stdin_task_register(sit, &s);
 
-       fns = para_malloc(OPT_GIVEN(FILTER) * sizeof(*fns));
+       fns = arr_alloc(OPT_GIVEN(FILTER), sizeof(*fns));
        for (i = 0, parent = sit->btrn; i < OPT_GIVEN(FILTER); i++) {
                const char *fa = lls_string_val(i, OPT_RESULT(FILTER));
                const char *name;
                struct filter_node *fn;
                struct task_info ti;
 
-               fn = fns[i] = para_calloc(sizeof(*fn));
+               fn = fns[i] = zalloc(sizeof(*fn));
                fn->filter_num = filter_setup(fa, &fn->conf, &filter_lpr);
                name = filter_name(fn->filter_num);
                fn->lpr = filter_lpr;
@@ -137,8 +137,8 @@ int main(int argc, char *argv[])
                        EMBRACE(.name = name, .parent = parent,
                        .handler = f->execute, .context = fn));
                ti.name = name;
-               ti.pre_select = f->pre_select;
-               ti.post_select = f->post_select;
+               ti.pre_monitor = f->pre_monitor;
+               ti.post_monitor = f->post_monitor;
                ti.context = fn;
                if (f->open)
                        f->open(fn);
@@ -149,8 +149,7 @@ int main(int argc, char *argv[])
                EMBRACE(.name = "stdout", .parent = parent));
        stdout_task_register(sot, &s);
 
-       s.default_timeout.tv_sec = 1;
-       s.default_timeout.tv_usec = 0;
+       s.default_timeout = 1000;
        btr_log_tree(sit->btrn, LL_INFO);
        ret = schedule(&s);
        sched_shutdown(&s);
index 69d4dfee8534941ab74bb17d7d2a5ff8e23eed6b..77057e6a7d2b9a997219d907b3ddd459716283ad 100644 (file)
--- a/filter.h
+++ b/filter.h
@@ -28,16 +28,18 @@ struct filter_node {
 };
 
 /**
- * The structure associated with a paraslash filter.
+ * Describes a method to convert audio data.
  *
- * Paraslash filters are "modules" which transform an audio stream. struct
- * filter contains methods which are implemented by each filter.
+ * Paraslash filters are "modules" which transform the data of an audio stream.
+ * This structure contains the methods which have to be implemented by each
+ * filter.
  *
- * Note: As several instances of the same filter may be running at the same
- * time, all these filter functions must be reentrant; no static non-constant
- * variables may be used.
+ * As several instances of the same filter may be running at the same time, all
+ * filter methods must be reentrant and no static non-constant variables must
+ * be used.
  *
- * \sa \ref filter_node.
+ * \sa \ref filter_node, struct \ref receiver, struct \ref writer, struct \ref
+ * sched.
  */
 struct filter {
        /**
@@ -81,24 +83,10 @@ struct filter {
         * This should free whatever ->setup() has allocated.
         */
        void (*teardown)(const struct lls_parse_result *lpr, void *conf);
-       /**
-        * Set scheduler timeout and add file descriptors to fd sets.
-        *
-        * This function controls the timeout value for the next call to
-        * select(2). It may decrease the current timeout but shall never
-        * increase it. The second purpose of this function is to add file
-        * descriptors to the two fd sets of the sched structure. The
-        * descriptors in these sets will be watched by the subsequent
-        * select(2) call.
-        */
-       void (*pre_select)(struct sched *s, void *context);
-       /**
-        * Convert (filter) the given data.
-        *
-        * Pointer to the converting function of the filter. On errors, the
-        * post_select function is supposed to return a negative error code.
-        */
-       int (*post_select)(struct sched *s, void *context);
+       /** Force a zero timeout if data is available in the buffer tree. */
+       void (*pre_monitor)(struct sched *s, void *context);
+       /** Convert (filter) input data into output data. */
+       int (*post_monitor)(struct sched *s, void *context);
        /**
         * Answer a buffer tree query.
         *
@@ -124,7 +112,7 @@ int filter_setup(const char *fa, void **conf, struct lls_parse_result **lprp);
 #define FILTER_CMD_OPT_STRING_VAL(_cmd, _opt, _lpr) \
        (lls_string_val(0, FILTER_CMD_OPT_RESULT(_cmd, _opt, _lpr)))
 
-void generic_filter_pre_select(struct sched *s, void *context);
+void generic_filter_pre_monitor(struct sched *s, void *context);
 int decoder_execute(const char *cmd, unsigned sample_rate, unsigned channels,
                char **result);
 
index add788a8f30465251ff111822b7131cd2e94054b..f48e457005ca3510fb51d5e5f95405af15d927c1 100644 (file)
@@ -169,17 +169,16 @@ void print_filter_list(void)
 }
 
 /**
- * Set select timeout of the scheduler.
+ * Request a minimal timeout if not idle.
  *
- * \param s The scheduler.
- * \param context Pointer to the filter node (task context).
+ * \param s The scheduler instance.
+ * \param context Pointer to the filter node.
  *
- * This looks at the status of the btr node of the filter. If data is available
- * in the input queue of the filter, or if an error occurred, a minimal timeout
- * for the next select call is requested from the scheduler. Otherwise the
- * scheduler timeout is left unchanged.
+ * If the buffer tree node of the given filter node has data available (or is
+ * in error state) a minimal I/O timeout is requested from the scheduler.
+ * Otherwise the function does nothing.
  */
-void generic_filter_pre_select(struct sched *s, void *context)
+void generic_filter_pre_monitor(struct sched *s, void *context)
 {
        struct filter_node *fn = context;
 
index 6e23683937f6932aaa739095ef8281fc9075da55..2b5b6c1d9aa758e3d2e769756cc3d75f3bb57120 100644 (file)
@@ -374,8 +374,8 @@ static int flac_afh_read_chunks(struct private_flac_afh_data *pfad)
                        goto free_decoder;
                if (c >= chunk_table_size) {
                        chunk_table_size = 2 * chunk_table_size + 100;
-                       afhi->chunk_table = para_realloc(afhi->chunk_table,
-                               chunk_table_size * sizeof(uint32_t));
+                       afhi->chunk_table = arr_realloc(afhi->chunk_table,
+                               chunk_table_size, sizeof(uint32_t));
                }
                afhi->chunk_table[c] = pos;
 
@@ -481,7 +481,7 @@ static int flac_rewrite_tags(const char *map, size_t map_bytes,
        FLAC__Metadata_Iterator *iter;
        FLAC__StreamMetadata *b = NULL;
        FLAC__bool ok;
-       struct private_flac_afh_data *pfad = para_calloc(sizeof(*pfad));
+       struct private_flac_afh_data *pfad = zalloc(sizeof(*pfad));
 
        pfad->map = map;
        pfad->map_bytes = map_bytes;
index 6a3a8effaf83b3b9e333e429762f0ff2d3938afe..fb8ebf15d1ec664e39923c58515a3704441ab0da 100644 (file)
@@ -135,7 +135,7 @@ static FLAC__StreamDecoderWriteStatus write_cb(
        struct btr_node *btrn = fn->btrn;
        size_t k, n = frame->header.blocksize;
        unsigned channels = FLAC__stream_decoder_get_channels(decoder);
-       char *outbuffer = para_malloc(n * channels * 2);
+       char *outbuffer = arr_alloc(n, channels * 2);
 
        if (channels == 1) {
                for (k = 0; k < n; k++) {
@@ -205,7 +205,7 @@ static bool output_queue_full(struct btr_node *btrn)
        return btr_get_output_queue_size(btrn) > FLACDEC_MAX_OUTPUT_SIZE;
 }
 
-static void flacdec_pre_select(struct sched *s, void *context)
+static void flacdec_pre_monitor(struct sched *s, void *context)
 {
        struct filter_node *fn = context;
        struct private_flacdec_data *pfd = fn->private_data;
@@ -221,7 +221,7 @@ static void flacdec_pre_select(struct sched *s, void *context)
                return sched_min_delay(s);
 }
 
-static int flacdec_post_select(__a_unused struct sched *s, void *context)
+static int flacdec_post_monitor(__a_unused struct sched *s, void *context)
 {
        struct filter_node *fn = context;
        struct private_flacdec_data *pfd = fn->private_data;
@@ -232,7 +232,7 @@ static int flacdec_post_select(__a_unused struct sched *s, void *context)
        if (output_queue_full(btrn))
                return 0;
        ret = btr_node_status(btrn, fn->min_iqs, BTR_NT_INTERNAL);
-       if (ret < 0 && ret != -E_BTR_EOF) /* fatal error */
+       if (ret < 0 && ret != -E_EOF) /* fatal error */
                goto out;
        if (ret <= 0 && !pfd->have_more) /* nothing to do */
                goto out;
@@ -248,7 +248,7 @@ static int flacdec_post_select(__a_unused struct sched *s, void *context)
        pfd->have_more = false;
        FLAC__stream_decoder_process_single(pfd->decoder);
        state = FLAC__stream_decoder_get_state(pfd->decoder);
-       ret = -E_FLACDEC_EOF;
+       ret = -E_EOF;
        if (state == FLAC__STREAM_DECODER_END_OF_STREAM)
                goto out;
        if (state == FLAC__STREAM_DECODER_ABORTED) {
@@ -286,7 +286,7 @@ static void flacdec_close(struct filter_node *fn)
 
 static void flacdec_open(struct filter_node *fn)
 {
-       struct private_flacdec_data *pfd = para_calloc(sizeof(*pfd));
+       struct private_flacdec_data *pfd = zalloc(sizeof(*pfd));
        fn->private_data = pfd;
        fn->min_iqs = 0;
 }
@@ -294,7 +294,7 @@ static void flacdec_open(struct filter_node *fn)
 const struct filter lsg_filter_cmd_com_flacdec_user_data = {
        .open = flacdec_open,
        .close = flacdec_close,
-       .pre_select = flacdec_pre_select,
-       .post_select = flacdec_post_select,
+       .pre_monitor = flacdec_pre_monitor,
+       .post_monitor = flacdec_post_monitor,
        .execute = flacdec_execute,
 };
index dbe4900862fef83a552d9c60d9ac21d4c8253d5e..b46f8f9555824eb86b7bbf600b4352558ed2db26 100644 (file)
--- a/gcrypt.c
+++ b/gcrypt.c
@@ -46,6 +46,22 @@ void hash_function(const char *data, unsigned long len, unsigned char *hash)
        gcry_md_close(handle);
 }
 
+void hash2_function(const char *data, unsigned long len, unsigned char *hash)
+{
+       gcry_error_t gret;
+       gcry_md_hd_t handle;
+       unsigned char *md;
+
+       gret = gcry_md_open(&handle, GCRY_MD_SHA256, 0);
+       assert(gret == 0);
+       gcry_md_write(handle, data, (size_t)len);
+       gcry_md_final(handle);
+       md = gcry_md_read(handle, GCRY_MD_SHA256);
+       assert(md);
+       memcpy(hash, md, HASH2_SIZE);
+       gcry_md_close(handle);
+}
+
 void get_random_bytes_or_die(unsigned char *buf, int num)
 {
        gcry_randomize(buf, (size_t)num, GCRY_STRONG_RANDOM);
@@ -98,7 +114,6 @@ void crypt_shutdown(void)
 
 struct asymmetric_key {
        gcry_sexp_t sexp;
-       int num_bytes;
 };
 
 static const char *gcrypt_strerror(gcry_error_t gret)
@@ -390,7 +405,7 @@ static int get_private_key(const char *key_file, struct asymmetric_key **result)
                ret = -E_SEXP_BUILD;
                goto free_params;
        }
-       key = para_malloc(sizeof(*key));
+       key = alloc(sizeof(*key));
        key->sexp = sexp;
        *result = key;
        ret = bits;
@@ -440,11 +455,10 @@ int apc_get_pubkey(const char *key_file, struct asymmetric_key **result)
                goto release_n;
        }
        PARA_INFO_LOG("successfully read %u bit ssh public key\n", bits);
-       key = para_malloc(sizeof(*key));
-       key->num_bytes = ret;
+       key = alloc(sizeof(*key));
        key->sexp = sexp;
        *result = key;
-       ret = bits;
+       ret = bits / 8;
 release_n:
        gcry_mpi_release(n);
 release_e:
@@ -548,8 +562,6 @@ int apc_pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf,
        size_t nbytes;
        int ret;
 
-       PARA_INFO_LOG("encrypting %u byte input with %d-byte key\n", len, pub->num_bytes);
-
        /* get pub key */
        pub_key = gcry_sexp_find_token(pub->sexp, "public-key", 0);
        if (!pub_key)
@@ -608,7 +620,7 @@ struct stream_cipher {
 struct stream_cipher *sc_new(const unsigned char *data, int len)
 {
        gcry_error_t gret;
-       struct stream_cipher *sc = para_malloc(sizeof(*sc));
+       struct stream_cipher *sc = alloc(sizeof(*sc));
 
        assert(len >= 2 * AES_CRT128_BLOCK_SIZE);
        gret = gcry_cipher_open(&sc->handle, GCRY_CIPHER_AES128,
index 8370649301906eaef68a4c30d1b1f3975283dea8..1019e579a39d8559b2aecc658c7651fd6b7704dc 100644 (file)
@@ -89,7 +89,7 @@ err:
        return -E_GC_WRITE;
 }
 
-static void gc_pre_select(struct sched *s, void *context)
+static void gc_pre_monitor(struct sched *s, void *context)
 {
        struct grab_client *gc = context;
        int ret = btr_node_status(gc->btrn, 0, BTR_NT_LEAF);
@@ -98,14 +98,14 @@ static void gc_pre_select(struct sched *s, void *context)
                return;
        if (ret < 0)
                sched_min_delay(s);
-       para_fd_set(gc->fd, &s->wfds, &s->max_fileno);
+       sched_monitor_writefd(gc->fd, s);
 }
 
 /*
- * We need this forward declaration as post_select() needs
+ * We need this forward declaration as gc_post_monitor() needs
  * activate_grab_client and vice versa.
  */
-static int gc_post_select(struct sched *s, void *context);
+static int gc_post_monitor(struct sched *s, void *context);
 
 /**
  * Move a grab client to the active list and start it.
@@ -129,8 +129,8 @@ static void gc_activate(struct grab_client *gc, struct sched *s)
 
        gc->task = task_register(&(struct task_info) {
                .name = name,
-               .pre_select = gc_pre_select,
-               .post_select = gc_post_select,
+               .pre_monitor = gc_pre_monitor,
+               .post_monitor = gc_post_monitor,
                .context = gc,
        }, s);
 }
@@ -171,7 +171,7 @@ static int gc_close(struct grab_client *gc, int err)
                /*
                 * We must not free the gc structure here as it contains ->task
                 * which is still used because this function is called from
-                * post_select().
+                * post_monitor().
                 */
                close(gc->fd);
                gc->fd = -1;
@@ -182,7 +182,7 @@ static int gc_close(struct grab_client *gc, int err)
        return 0;
 }
 
-static int gc_post_select(__a_unused struct sched *s, void *context)
+static int gc_post_monitor(__a_unused struct sched *s, void *context)
 {
        struct grab_client *gc = context;
        struct btr_node *btrn = gc->btrn;
@@ -261,7 +261,7 @@ static int gc_check_args(struct lls_parse_result *lpr, struct grab_client *gc)
 int grab_client_new(int fd, struct lls_parse_result *lpr, struct sched *s)
 {
        int ret;
-       struct grab_client *gc = para_calloc(sizeof(struct grab_client));
+       struct grab_client *gc = zalloc(sizeof(struct grab_client));
 
        ret = gc_check_args(lpr, gc);
        if (ret < 0)
diff --git a/gui.c b/gui.c
index a4f1717c2a996568d2fe36cc3a010f165ff1bcd9..66fb7870bd65868a750b5918747da59ad4681b6d 100644 (file)
--- a/gui.c
+++ b/gui.c
@@ -192,7 +192,7 @@ static bool curses_active(void)
 }
 
 /* taken from mutt */
-static char *km_keyname(int c)
+static const char *km_keyname(int c)
 {
        static char buf[10];
 
@@ -431,7 +431,7 @@ static void rb_add_entry(int color, char *msg)
 
        if (strwidth(msg, &len) < 0)
                return;
-       new = para_malloc(sizeof(struct rb_entry));
+       new = alloc(sizeof(struct rb_entry));
        new->color = color;
        new->len = len;
        new->msg = msg;
@@ -609,19 +609,19 @@ static void clear_all_items(void)
        }
 }
 
-static void status_pre_select(struct sched *s, void *context)
+static void status_pre_monitor(struct sched *s, void *context)
 {
        struct status_task *st = context;
 
        if (st->fd >= 0)
-               para_fd_set(st->fd, &s->rfds, &s->max_fileno);
+               sched_monitor_readfd(st->fd, s);
        if (task_get_notification(st->task) < 0)
                return sched_min_delay(s);
        if (st->fd < 0)
                sched_request_barrier_or_min_delay(&st->next_exec, s);
 }
 
-static int status_post_select(struct sched *s, void *context)
+static int status_post_monitor(__a_unused struct sched *s, void *context)
 {
        struct status_task *st = context;
        size_t sz;
@@ -667,7 +667,7 @@ static int status_post_select(struct sched *s, void *context)
        }
        assert(st->loaded < st->bufsize);
        ret = read_nonblock(st->fd, st->buf + st->loaded,
-               st->bufsize - st->loaded, &s->rfds, &sz);
+               st->bufsize - st->loaded, &sz);
        st->loaded += sz;
        ret2 = for_each_stat_item(st->buf, st->loaded, update_item);
        if (ret < 0 || ret2 < 0) {
@@ -892,9 +892,9 @@ static void reread_conf(void)
 }
 
 /* React to various signal-related events. */
-static int signal_post_select(struct sched *s, __a_unused void *context)
+static int signal_post_monitor(struct sched *s, __a_unused void *context)
 {
-       int ret = para_next_signal(&s->rfds);
+       int ret = para_next_signal();
 
        if (ret <= 0)
                return 0;
@@ -931,18 +931,18 @@ static enum exec_status exec_status(void)
        return EXEC_IDLE;
 }
 
-static void exec_pre_select(struct sched *s, void *context)
+static void exec_pre_monitor(struct sched *s, void *context)
 {
        struct exec_task *et = context;
        if (exec_fds[0] >= 0)
-               para_fd_set(exec_fds[0], &s->rfds, &s->max_fileno);
+               sched_monitor_readfd(exec_fds[0], s);
        if (exec_fds[1] >= 0)
-               para_fd_set(exec_fds[1], &s->rfds, &s->max_fileno);
+               sched_monitor_readfd(exec_fds[1], s);
        if (task_get_notification(et->task) < 0)
                sched_min_delay(s);
 }
 
-static int exec_post_select(struct sched *s, void *context)
+static int exec_post_monitor(__a_unused struct sched *s, void *context)
 {
        struct exec_task *ct = context;
        int i, ret;
@@ -963,7 +963,7 @@ static int exec_post_select(struct sched *s, void *context)
                        continue;
                ret = read_nonblock(exec_fds[i],
                        ct->command_buf[i] + ct->cbo[i],
-                       COMMAND_BUF_SIZE - 1 - ct->cbo[i], &s->rfds, &sz);
+                       COMMAND_BUF_SIZE - 1 - ct->cbo[i], &sz);
                ct->cbo[i] += sz;
                sz = ct->cbo[i];
                ct->cbo[i] = for_each_line(ct->flags[i], ct->command_buf[i],
@@ -992,10 +992,10 @@ static int exec_post_select(struct sched *s, void *context)
        return 0;
 }
 
-static void input_pre_select(struct sched *s, __a_unused void *context)
+static void input_pre_monitor(struct sched *s, __a_unused void *context)
 {
        if (exec_status() != EXEC_XCMD)
-               para_fd_set(STDIN_FILENO, &s->rfds, &s->max_fileno);
+               sched_monitor_readfd(STDIN_FILENO, s);
        if (window_update_needed())
                sched_min_delay(s);
 }
@@ -1049,6 +1049,7 @@ static void handle_command(int c)
 {
        int i;
        const struct lls_opt_result *lor = OPT_RESULT(KEY_MAP);
+       const char *keyname = km_keyname(c);
 
        /* first check user-defined key bindings */
        FOR_EACH_KEY_MAP(i) {
@@ -1059,7 +1060,7 @@ static void handle_command(int c)
                        free(tmp);
                        return;
                }
-               if (strcmp(tmp, km_keyname(c))) {
+               if (strcmp(tmp, keyname)) {
                        free(tmp);
                        continue;
                }
@@ -1079,16 +1080,16 @@ static void handle_command(int c)
        }
        /* not found, check internal key bindings */
        for (i = 0; command_list[i].handler; i++) {
-               if (!strcmp(km_keyname(c), command_list[i].key)) {
+               if (!strcmp(keyname, command_list[i].key)) {
                        command_list[i].handler();
                        return;
                }
        }
        print_in_bar(COLOR_ERRMSG, "key '%s' is not bound, press ? for help",
-               km_keyname(c));
+               keyname);
 }
 
-static int input_post_select(__a_unused struct sched *s,
+static int input_post_monitor(__a_unused struct sched *s,
                __a_unused void *context)
 {
        int ret;
@@ -1114,7 +1115,7 @@ static int input_post_select(__a_unused struct sched *s,
        ret = wgetch(top.win);
        if (ret == ERR)
                return 0;
-       if (ret == KEY_RESIZE) /* already handled in signal_post_select() */
+       if (ret == KEY_RESIZE) /* already handled in signal_post_monitor() */
                return 0;
        if (exs == EXEC_IDLE)
                handle_command(ret);
@@ -1165,6 +1166,7 @@ static void com_cancel_scroll(void)
        }
        scroll_position = 0;
        redraw_bot_win();
+       print_in_bar(COLOR_MSG, " ");
 }
 
 static void com_page_down(void)
@@ -1266,6 +1268,12 @@ err_out:
        print_in_bar(COLOR_ERRMSG, "top of buffer is shown\n");
 }
 
+static void print_ll_msg(void)
+{
+       const char *sev[] = {SEVERITIES};
+       print_in_bar(COLOR_MSG, "new loglevel: %s\n", sev[loglevel]);
+}
+
 static void com_ll_decr(void)
 {
        if (loglevel <= LL_DEBUG) {
@@ -1274,7 +1282,7 @@ static void com_ll_decr(void)
                return;
        }
        loglevel--;
-       print_in_bar(COLOR_MSG, "loglevel set to %d\n", loglevel);
+       print_ll_msg();
 }
 
 static void com_ll_incr(void)
@@ -1285,7 +1293,7 @@ static void com_ll_incr(void)
                return;
        }
        loglevel++;
-       print_in_bar(COLOR_MSG, "loglevel set to %d\n", loglevel);
+       print_ll_msg();
 }
 
 static void com_reread_conf(void)
@@ -1390,26 +1398,26 @@ static int setup_tasks_and_schedule(void)
        struct status_task status_task = {.fd = -1};
        struct input_task input_task = {.task = NULL};
        struct signal_task *signal_task;
-       struct sched sched = {.default_timeout = {.tv_sec = 1}};
+       struct sched sched = {.default_timeout = 1000};
 
        exec_task.task = task_register(&(struct task_info) {
                .name = "exec",
-               .pre_select = exec_pre_select,
-               .post_select = exec_post_select,
+               .pre_monitor = exec_pre_monitor,
+               .post_monitor = exec_post_monitor,
                .context = &exec_task,
        }, &sched);
 
        status_task.task = task_register(&(struct task_info) {
                .name = "status",
-               .pre_select = status_pre_select,
-               .post_select = status_post_select,
+               .pre_monitor = status_pre_monitor,
+               .post_monitor = status_post_monitor,
                .context = &status_task,
        }, &sched);
 
        input_task.task = task_register(&(struct task_info) {
                .name = "input",
-               .pre_select = input_pre_select,
-               .post_select = input_post_select,
+               .pre_monitor = input_pre_monitor,
+               .post_monitor = input_post_monitor,
                .context = &input_task,
        }, &sched);
 
@@ -1421,8 +1429,8 @@ static int setup_tasks_and_schedule(void)
        para_install_sighandler(SIGWINCH);
        signal_task->task = task_register(&(struct task_info) {
                .name = "signal",
-               .pre_select = signal_pre_select,
-               .post_select = signal_post_select,
+               .pre_monitor = signal_pre_monitor,
+               .post_monitor = signal_post_monitor,
                .context = signal_task,
        }, &sched);
        ret = schedule(&sched);
index 745abe286c3c3ce8f966660e6907e2417e588c4a..81bbe0f6760b82f88fb9f97f45f2f5a42ed494ab 100644 (file)
@@ -122,16 +122,16 @@ static void init_theme_colorful_blackness(struct gui_theme *t)
        d[SI_status_flags].align = LEFT;
        d[SI_status_flags].x = 11;
        d[SI_status_flags].y = 17;
-       d[SI_status_flags].len = 10;
+       d[SI_status_flags].len = 8;
 
        d[SI_image_id].prefix = "img: ";
        d[SI_image_id].postfix = "";
        d[SI_image_id].color.fg = COLOR_RED;
        d[SI_image_id].color.bg = COLOR_BLACK;
        d[SI_image_id].align = CENTER;
-       d[SI_image_id].x = 21;
+       d[SI_image_id].x = 19;
        d[SI_image_id].y = 17;
-       d[SI_image_id].len = 10;
+       d[SI_image_id].len = 12;
 
        d[SI_lyrics_id].prefix = "lyr: ";
        d[SI_lyrics_id].postfix = "";
@@ -149,14 +149,14 @@ static void init_theme_colorful_blackness(struct gui_theme *t)
        d[SI_format].align = CENTER;
        d[SI_format].x = 42;
        d[SI_format].y = 17;
-       d[SI_format].len = 18;
+       d[SI_format].len = 16;
 
        d[SI_num_played].prefix = "#";
        d[SI_num_played].postfix = "";
        d[SI_num_played].color.fg = COLOR_RED;
        d[SI_num_played].color.bg = COLOR_BLACK;
        d[SI_num_played].align = LEFT;
-       d[SI_num_played].x = 60;
+       d[SI_num_played].x = 58;
        d[SI_num_played].y = 17;
        d[SI_num_played].len = 5;
 
@@ -266,7 +266,7 @@ static void init_theme_colorful_blackness(struct gui_theme *t)
        d[SI_amplification].align = RIGHT;
        d[SI_amplification].x = 92;
        d[SI_amplification].y = 27;
-       d[SI_amplification].len = 8;
+       d[SI_amplification].len = 9;
 
        d[SI_techinfo].prefix = "";
        d[SI_techinfo].postfix = "";
index 1fb60bad810d86dbbb0098a3714c2caa3d9e835b..8d2add19091f4894814c1aeeaeeefee5d5c8c19e 100644 (file)
@@ -56,17 +56,17 @@ static char *make_request_msg(void)
        return ret;
 }
 
-static void http_recv_pre_select(struct sched *s, void *context)
+static void http_recv_pre_monitor(struct sched *s, void *context)
 {
        struct receiver_node *rn = context;
        struct private_http_recv_data *phd = rn->private_data;
 
-       if (generic_recv_pre_select(s, rn) <= 0)
+       if (generic_recv_pre_monitor(s, rn) <= 0)
                return;
        if  (phd->status == HTTP_CONNECTED)
-               para_fd_set(rn->fd, &s->wfds, &s->max_fileno);
+               sched_monitor_writefd(rn->fd, s);
        else
-               para_fd_set(rn->fd, &s->rfds, &s->max_fileno);
+               sched_monitor_readfd(rn->fd, s);
 }
 
 /*
@@ -74,7 +74,7 @@ static void http_recv_pre_select(struct sched *s, void *context)
  * area with data read from the socket. In any case, update the state of the
  * connection if necessary.
  */
-static int http_recv_post_select(struct sched *s, void *context)
+static int http_recv_post_monitor(struct sched *s, void *context)
 {
        struct receiver_node *rn = context;
        struct private_http_recv_data *phd = rn->private_data;
@@ -93,7 +93,7 @@ static int http_recv_post_select(struct sched *s, void *context)
                return 0;
        if (phd->status == HTTP_CONNECTED) {
                char *rq;
-               if (!FD_ISSET(rn->fd, &s->wfds))
+               if (!sched_write_ok(rn->fd, s))
                        return 0;
                rq = make_request_msg();
                PARA_INFO_LOG("sending http request\n");
@@ -105,7 +105,7 @@ static int http_recv_post_select(struct sched *s, void *context)
                return 0;
        }
        if (phd->status == HTTP_SENT_GET_REQUEST) {
-               ret = read_pattern(rn->fd, HTTP_OK_MSG, strlen(HTTP_OK_MSG), &s->rfds);
+               ret = read_and_compare(rn->fd, HTTP_OK_MSG);
                if (ret < 0) {
                        PARA_ERROR_LOG("did not receive HTTP OK message\n");
                        goto out;
@@ -120,7 +120,7 @@ static int http_recv_post_select(struct sched *s, void *context)
        iovcnt = btr_pool_get_buffers(rn->btrp, iov);
        if (iovcnt == 0)
                goto out;
-       ret = readv_nonblock(rn->fd, iov, iovcnt, &s->rfds, &num_bytes);
+       ret = readv_nonblock(rn->fd, iov, iovcnt, &num_bytes);
        if (num_bytes == 0)
                goto out;
        if (num_bytes <= iov[0].iov_len) /* only the first buffer was filled */
@@ -150,7 +150,7 @@ static int http_recv_open(struct receiver_node *rn)
        struct lls_parse_result *lpr = rn->lpr;
        const char *r_i = RECV_CMD_OPT_STRING_VAL(HTTP, HOST, lpr);
        uint32_t r_p = RECV_CMD_OPT_UINT32_VAL(HTTP, PORT, lpr);
-       int fd, ret = para_connect_simple(IPPROTO_TCP, r_i, r_p);
+       int fd, ret = para_connect(IPPROTO_TCP, r_i, r_p);
 
        if (ret < 0)
                return ret;
@@ -160,7 +160,7 @@ static int http_recv_open(struct receiver_node *rn)
                close(fd);
                return ret;
        }
-       rn->private_data = phd = para_calloc(sizeof(struct private_http_recv_data));
+       rn->private_data = phd = zalloc(sizeof(struct private_http_recv_data));
        rn->fd = fd;
        phd->status = HTTP_CONNECTED;
        rn->btrp = btr_pool_new("http_recv", 320 * 1024);
@@ -170,6 +170,6 @@ static int http_recv_open(struct receiver_node *rn)
 const struct receiver lsg_recv_cmd_com_http_user_data = {
        .open = http_recv_open,
        .close = http_recv_close,
-       .pre_select = http_recv_pre_select,
-       .post_select = http_recv_post_select,
+       .pre_monitor = http_recv_pre_monitor,
+       .post_monitor = http_recv_post_monitor,
 };
index c6b9decca941489c82b629982ddb6d6dbdb2ea33..429b4662a70497d522fd68a0e3467941e7edc959 100644 (file)
@@ -20,8 +20,8 @@
 #include "server.h"
 #include "http.h"
 #include "list.h"
-#include "send.h"
 #include "sched.h"
+#include "send.h"
 #include "vss.h"
 #include "close_on_fork.h"
 #include "fd.h"
@@ -158,7 +158,7 @@ static void http_send(long unsigned current_chunk,
        }
 }
 
-static void http_post_select(fd_set *rfds, __a_unused fd_set *wfds)
+static void http_post_monitor(__a_unused struct sched *s)
 {
        struct sender_client *sc, *tmp;
        struct private_http_sender_data *phsd;
@@ -170,7 +170,7 @@ static void http_post_select(fd_set *rfds, __a_unused fd_set *wfds)
                case HTTP_STREAMING: /* nothing to do */
                        break;
                case HTTP_CONNECTED: /* need to recv get request */
-                       ret = read_pattern(sc->fd, HTTP_GET_MSG, MAXLINE, rfds);
+                       ret = read_and_compare(sc->fd, HTTP_GET_MSG);
                        if (ret < 0)
                                phsd->status = HTTP_INVALID_GET_REQUEST;
                        else if (ret > 0) {
@@ -188,15 +188,15 @@ static void http_post_select(fd_set *rfds, __a_unused fd_set *wfds)
                        break;
                }
        }
-       sc = accept_sender_client(hss, rfds);
+       sc = accept_sender_client(hss);
        if (!sc)
                return;
-       phsd = para_malloc(sizeof(*phsd));
+       phsd = alloc(sizeof(*phsd));
        sc->private_data = phsd;
        phsd->status = HTTP_CONNECTED;
 }
 
-static void http_pre_select(int *max_fileno, fd_set *rfds, fd_set *wfds)
+static void http_pre_monitor(struct sched *s)
 {
        struct sender_client *sc, *tmp;
        unsigned n;
@@ -204,15 +204,15 @@ static void http_pre_select(int *max_fileno, fd_set *rfds, fd_set *wfds)
        FOR_EACH_LISTEN_FD(n, hss) {
                if (hss->listen_fds[n] < 0)
                        continue;
-               para_fd_set(hss->listen_fds[n], rfds, max_fileno);
+               sched_monitor_readfd(hss->listen_fds[n], s);
        }
        list_for_each_entry_safe(sc, tmp, &hss->client_list, node) {
                struct private_http_sender_data *phsd = sc->private_data;
                if (phsd->status == HTTP_CONNECTED) /* need to recv get request */
-                       para_fd_set(sc->fd, rfds, max_fileno);
+                       sched_monitor_readfd(sc->fd, s);
                if (phsd->status == HTTP_GOT_GET_REQUEST ||
                                phsd->status == HTTP_INVALID_GET_REQUEST)
-                       para_fd_set(sc->fd, wfds, max_fileno);
+                       sched_monitor_writefd(sc->fd, s);
        }
 }
 
@@ -274,8 +274,8 @@ const struct sender http_sender = {
        .name = "http",
        .init = http_send_init,
        .shutdown = http_shutdown,
-       .pre_select = http_pre_select,
-       .post_select = http_post_select,
+       .pre_monitor = http_pre_monitor,
+       .post_monitor = http_post_monitor,
        .send = http_send,
        .shutdown_clients = http_shutdown_clients,
        .client_cmds = {
diff --git a/imdct.c b/imdct.c
index 93577b5451a0cbb8c8da58e997a9f4d77179243e..2e1089f1f818be4bcdeb20be3d4d7a865943e00e 100644 (file)
--- a/imdct.c
+++ b/imdct.c
@@ -336,7 +336,7 @@ static int fft_init(struct fft_context *s, int nbits)
        s->nbits = nbits;
        n = 1 << nbits;
 
-       s->revtab = para_malloc(n * sizeof(uint16_t));
+       s->revtab = arr_alloc(n, sizeof(uint16_t));
        for (j = 4; j <= nbits; j++) {
                int k = 1 << j;
                double freq = 2 * M_PI / k;
@@ -366,13 +366,13 @@ int imdct_init(int nbits, struct mdct_context **result)
        double alpha;
        struct mdct_context *s;
 
-       s = para_calloc(sizeof(*s));
+       s = zalloc(sizeof(*s));
        n = 1 << nbits;
        s->nbits = nbits;
        s->n = n;
        n4 = n >> 2;
-       s->tcos = para_malloc(n4 * sizeof(fftsample_t));
-       s->tsin = para_malloc(n4 * sizeof(fftsample_t));
+       s->tcos = arr_alloc(n4, sizeof(fftsample_t));
+       s->tsin = arr_alloc(n4, sizeof(fftsample_t));
 
        for (i = 0; i < n4; i++) {
                alpha = 2 * M_PI * (i + 1.0 / 8.0) / n;
index a8197308e8323c8fe78546117cc2e9479df0fbfd..1376cf1d72739b3f7c9c3218dd96666a5d475812 100644 (file)
@@ -45,7 +45,7 @@ static struct i9e_private i9e_private, *i9ep = &i9e_private;
  * running.
  *
  * \return A negative return value of zero means the i9e task terminated. Only
- * in this case it is safe to call ie9_close().
+ * in this case it is safe to call i9e_close().
  */
 int i9e_get_error(void)
 {
@@ -189,8 +189,6 @@ static char **i9e_completer(const char *text, int start, __a_unused int end)
  *
  * This function attaches the i9e input queue to an output queue of \a
  * producer.
- *
- * \return Standard.
  */
 void i9e_attach_to_stdout(struct btr_node *producer)
 {
@@ -258,18 +256,6 @@ static void clear_bottom_line(void)
        rl_point = point;
 }
 
-static bool input_available(void)
-{
-       fd_set rfds;
-       struct timeval tv = {0, 0};
-       int ret;
-
-       FD_ZERO(&rfds);
-       FD_SET(i9ep->ici->fds[0], &rfds);
-       ret = para_select(1, &rfds, NULL, &tv);
-       return ret > 0;
-}
-
 static void i9e_line_handler(char *line)
 {
        int ret;
@@ -294,14 +280,14 @@ free_line:
        free(line);
 }
 
-static int i9e_post_select(__a_unused struct sched *s, __a_unused void *context)
+static int i9e_post_monitor(__a_unused struct sched *s, __a_unused void *context)
 {
        int ret;
        struct i9e_client_info *ici = i9ep->ici;
        char *buf;
        size_t sz, consumed = 0;
 
-       ret = -E_I9E_EOF;
+       ret = -E_EOF;
        if (i9ep->input_eof)
                goto rm_btrn;
        ret = -E_I9E_TERM_RQ;
@@ -310,24 +296,29 @@ static int i9e_post_select(__a_unused struct sched *s, __a_unused void *context)
        ret = 0;
        if (i9ep->caught_sigint)
                goto rm_btrn;
-       while (input_available()) {
+       while (read_ok(i9ep->ici->fds[0]) > 0) {
                if (i9ep->stdout_btrn) {
-                       unsigned len = i9ep->key_sequence_length;
-                       assert(len < sizeof(i9ep->key_sequence) - 1);
-                       buf = i9ep->key_sequence + len;
-                       ret = read(i9ep->ici->fds[0], buf, 1);
-                       if (ret < 0) {
-                               ret = -ERRNO_TO_PARA_ERROR(errno);
-                               goto rm_btrn;
+                       while (i9ep->key_sequence_length < sizeof(i9ep->key_sequence) - 1) {
+                               buf = i9ep->key_sequence + i9ep->key_sequence_length;
+                               ret = read(i9ep->ici->fds[0], buf, 1);
+                               if (ret < 0) {
+                                       ret = -ERRNO_TO_PARA_ERROR(errno);
+                                       goto rm_btrn;
+                               }
+                               if (ret == 0) {
+                                       ret = -E_EOF;
+                                       goto rm_btrn;
+                               }
+                               buf[1] = '\0';
+                               i9ep->key_sequence_length++;
+                               rl_stuff_char((int)(unsigned char)*buf);
+                               rl_callback_read_char();
+                               if (read_ok(i9ep->ici->fds[0]) <= 0)
+                                       break;
                        }
-                       ret = -E_I9E_EOF;
-                       if (ret == 0)
-                               goto rm_btrn;
-                       buf[1] = '\0';
-                       i9ep->key_sequence_length++;
-                       rl_stuff_char((int)(unsigned char)*buf);
-               }
-               rl_callback_read_char();
+                       i9ep->key_sequence_length = 0;
+               } else
+                       rl_callback_read_char();
                ret = 0;
        }
        if (!i9ep->stdout_btrn)
@@ -369,7 +360,7 @@ out:
        return ret;
 }
 
-static void i9e_pre_select(struct sched *s, __a_unused void *context)
+static void i9e_pre_monitor(struct sched *s, __a_unused void *context)
 {
        int ret;
 
@@ -384,7 +375,7 @@ static void i9e_pre_select(struct sched *s, __a_unused void *context)
                        return;
                }
                if (ret > 0)
-                       para_fd_set(i9ep->ici->fds[1], &s->wfds, &s->max_fileno);
+                       sched_monitor_writefd(i9ep->ici->fds[1], s);
        }
        /*
         * fd[0] might have been reset to blocking mode if our job was moved to
@@ -395,7 +386,7 @@ static void i9e_pre_select(struct sched *s, __a_unused void *context)
        if (ret < 0)
                PARA_WARNING_LOG("set to nonblock failed: (fd0 %d, %s)\n",
                        i9ep->ici->fds[0], para_strerror(-ret));
-       para_fd_set(i9ep->ici->fds[0], &s->rfds, &s->max_fileno);
+       sched_monitor_readfd(i9ep->ici->fds[0], s);
 }
 
 static void update_winsize(void)
@@ -472,8 +463,8 @@ int i9e_open(struct i9e_client_info *ici, struct sched *s)
                return ret;
        i9ep->task = task_register(&(struct task_info) {
                .name = "i9e",
-               .pre_select = i9e_pre_select,
-               .post_select = i9e_post_select,
+               .pre_monitor = i9e_pre_monitor,
+               .post_monitor = i9e_post_monitor,
                .context = i9ep,
        }, s);
 
@@ -556,7 +547,7 @@ __printf_2_3 void i9e_log(int ll, const char* fmt,...)
  * the given text. If the length of this text exceeds the width of the
  * terminal, the text is shortened by leaving out a part in the middle.
  */
-void ie9_print_status_bar(char *buf, unsigned len)
+void i9e_print_status_bar(char *buf, unsigned len)
 {
        size_t x = i9ep->num_columns, y = (x - 4) / 2;
 
@@ -600,23 +591,21 @@ void i9e_signal_dispatch(int sig_num)
 }
 
 /**
- * Wrapper for select(2) which does not restart on interrupts.
+ * Wrapper for poll(2) which handles EINTR and returns paraslash error codes.
  *
- * \param n \sa \ref para_select().
- * \param readfds \sa \ref para_select().
- * \param writefds \sa \ref para_select().
- * \param timeout_tv \sa \ref para_select().
+ * \param fds See poll(2).
+ * \param nfds See poll(2).
+ * \param timeout See poll(2).
  *
- * \return \sa \ref para_select().
+ * \return See poll(2).
  *
- * The only difference between this function and \ref para_select() is that
- * \ref i9e_select() returns zero if the select call returned \p EINTR.
+ * The only difference between this function and \ref xpoll() is that \ref
+ * i9e_poll() returns zero if the system call was interrupted while xpoll()
+ * restarts the system call in this case.
  */
-int i9e_select(int n, fd_set *readfds, fd_set *writefds,
-               struct timeval *timeout_tv)
+int i9e_poll(struct pollfd *fds, nfds_t nfds, int timeout)
 {
-       int ret = select(n, readfds, writefds, NULL, timeout_tv);
-
+       int ret = poll(fds, nfds, timeout);
        if (ret < 0) {
                if (errno == EINTR)
                        ret = 0;
@@ -645,7 +634,7 @@ int i9e_select(int n, fd_set *readfds, fd_set *writefds,
 int i9e_extract_completions(const char *word, char **string_list,
                char ***result)
 {
-       char **matches = para_malloc(sizeof(char *));
+       char **matches = alloc(sizeof(char *));
        int match_count = 0, matches_len = 1;
        char **p;
        int len = strlen(word);
@@ -656,8 +645,8 @@ int i9e_extract_completions(const char *word, char **string_list,
                match_count++;
                if (match_count >= matches_len) {
                        matches_len *= 2;
-                       matches = para_realloc(matches,
-                               matches_len * sizeof(char *));
+                       matches = arr_realloc(matches, matches_len,
+                               sizeof(char *));
                }
                matches[match_count - 1] = para_strdup(*p);
        }
@@ -693,7 +682,7 @@ char **i9e_complete_commands(const char *word, struct i9e_completer *completers)
                if (is_prefix(word, cmd, len))
                        match_count++;
        }
-       matches = para_malloc((match_count + 1) * sizeof(*matches));
+       matches = arr_alloc(match_count + 1, sizeof(*matches));
        for (i = 0, match_count = 0; (cmd = completers[i].name); i++)
                if (is_prefix(word, cmd, len))
                        matches[match_count++] = para_strdup(cmd);
@@ -777,7 +766,7 @@ int i9e_print_completions(struct i9e_completer *completers)
        if (*p == ' ')
                p++;
        n = end - p + 1;
-       ci.word = para_malloc(n + 1);
+       ci.word = alloc(n + 1);
        strncpy(ci.word, p, n);
        ci.word[n] = '\0';
 create_matches:
@@ -805,3 +794,25 @@ create_matches:
        free(ci.word);
        return ret;
 }
+
+/**
+ * Complete on severity strings.
+ *
+ * \param ci See struct \ref i9e_completer.
+ * \param cr See struct \ref i9e_completer.
+ *
+ * This is used by para_client and para_audioc which need the same completion
+ * primitive for the ll server/audiod command. Both define their own completer
+ * which is implemented as a trivial wrapper that calls this function.
+ */
+void i9e_ll_completer(struct i9e_completion_info *ci,
+               struct i9e_completion_result *cr)
+{
+       char *sev[] = {SEVERITIES, NULL};
+
+       if (ci->word_num != 1) {
+               cr->matches = NULL;
+               return;
+       }
+       i9e_extract_completions(ci->word, sev, &cr->matches);
+}
index 40ff294018b221708dd0d1db9cac23c80fb70533..6ef7f8e259b0045a5c808c01560b5a81166b01fd 100644 (file)
@@ -80,12 +80,11 @@ struct i9e_client_info {
 
 int i9e_open(struct i9e_client_info *ici, struct sched *s);
 void i9e_attach_to_stdout(struct btr_node *producer);
-void ie9_print_status_bar(char *buf, unsigned len);
+void i9e_print_status_bar(char *buf, unsigned len);
 void i9e_close(void);
 void i9e_signal_dispatch(int sig_num);
 __printf_2_3 void i9e_log(int ll, const char* fmt,...);
-int i9e_select(int n, fd_set *readfds, fd_set *writefds,
-               struct timeval *timeout_tv);
+int i9e_poll(struct pollfd *fds, nfds_t nfds, int timeout);
 int i9e_extract_completions(const char *word, char **string_list,
                char ***result);
 char **i9e_complete_commands(const char *word, struct i9e_completer *completers);
@@ -93,3 +92,5 @@ void i9e_complete_option(char **opts, struct i9e_completion_info *ci,
                struct i9e_completion_result *cr);
 int i9e_print_completions(struct i9e_completer *completers);
 int i9e_get_error(void);
+void i9e_ll_completer(struct i9e_completion_info *ci,
+               struct i9e_completion_result *cr);
diff --git a/list.h b/list.h
index 66c6d91525031261ff94cd3028e672cb340c06cd..78c302fa322fe6bc2dae2926e95e58189c10c944 100644 (file)
--- a/list.h
+++ b/list.h
  * Copied from the Linux kernel source tree, version 2.6.13.
  *
  * Licensed under the GPL v2 as per the whole kernel source tree.
- *
  */
 
-/** \file list.h doubly linked list implementation */
+/** \file list.h Doubly linked list implementation. */
 
 #include <stddef.h> /* offsetof */
 
-/** get the struct this entry is embedded in */
+/** Get the struct this entry is embedded in. */
 #define container_of(ptr, type, member) ({                      \
        const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
        (type *)( (char *)__mptr - offsetof(type,member) );})
 
-/**
- * Non-NULL pointers that will result in page faults under normal
- * circumstances, used to verify that nobody uses non-initialized list entries.
- * Used for poisoning the \a next pointer of struct list_head.
- */
-#define LIST_POISON1  ((void *) 0x00100100)
-/** Non-null pointer, used for poisoning the \a prev pointer of struct
- * list_head
- */
-#define LIST_POISON2  ((void *) 0x00200200)
-
-/** Simple doubly linked list implementation. */
+/** A list head is just a pair of pointers. */
 struct list_head {
-       /** pointer to the next list entry */
+       /** Pointer to the next list entry. */
        struct list_head *next;
-       /** pointer to the previous list entry */
+       /** Pointer to the previous list entry. */
        struct list_head *prev;
 };
 
 /** Define an initialized list head. */
-#define INITIALIZED_LIST_HEAD(name) struct list_head name = { &(name), &(name) }
-
-
-/** must be called before using any other list functions */
-#define INIT_LIST_HEAD(ptr) do { \
-       (ptr)->next = (ptr); (ptr)->prev = (ptr); \
-} while (0)
+#define INITIALIZED_LIST_HEAD(name) struct list_head name = {&(name), &(name)}
 
-
-/*
- * Some of the internal functions ("__xxx") are useful when
- * manipulating whole lists rather than single entries, as
- * sometimes we already know the next/prev entries and we can
- * generate better code by using them directly rather than
- * using the generic single-entry routines.
- */
-
-
-/*
- * Insert a new entry between two known consecutive entries.
- *
- * This is only for internal list manipulation where we know
- * the prev/next entries already!
- */
-static inline void __list_add(struct list_head *new,
-                             struct list_head *prev,
-                             struct list_head *next)
+/** This must be called before using any other list functions. */
+static inline void init_list_head(struct list_head *head)
 {
-       next->prev = new;
-       new->next = next;
-       new->prev = prev;
-       prev->next = new;
+       head->next = head;
+       head->prev = head;
 }
 
 /**
- * add a new entry
+ * Insert a new entry after the specified head.
  *
- * \param new new entry to be added
- * \param head list head to add it after
+ * \param entry The new entry to add.
+ * \param head The list head to add it after.
  *
- * Insert a new entry after the specified head.
  * This is good for implementing stacks.
  */
-static inline void para_list_add(struct list_head *new, struct list_head *head)
+static inline void para_list_add(struct list_head *entry, struct list_head *head)
 {
-       __list_add(new, head, head->next);
+       entry->prev = head;
+       entry->next = head->next;
+       head->next->prev = entry;
+       head->next = entry;
 }
 
 /**
- * add a new entry
+ * Insert a new entry before the specified head.
  *
- * \param new new entry to be added
- * \param head list head to add it before
+ * \param entry The new entry to add.
+ * \param head list head to add it before.
  *
- * Insert a new entry before the specified head.
  * This is useful for implementing queues.
  */
-static inline void list_add_tail(struct list_head *new, struct list_head *head)
+static inline void list_add_tail(struct list_head *entry, struct list_head *head)
 {
-       __list_add(new, head->prev, head);
+       entry->prev = head->prev;
+       entry->next = head;
+       head->prev->next = entry;
+       head->prev = entry;
 }
 
-/*
- * Delete a list entry by making the prev/next entries
- * point to each other.
+/**
+ * Delete an entry from a list.
+ *
+ * \param entry The element to delete.
  *
- * This is only for internal list manipulation where we know
- * the prev/next entries already!
+ * The list entry is in an undefined state after this and \ref list_empty()
+ * does not return true.
  */
-static inline void __list_del(struct list_head * prev, struct list_head * next)
+static inline void list_del(struct list_head *entry)
 {
-       next->prev = prev;
-       prev->next = next;
+       entry->prev->next = entry->next;
+       entry->next->prev = entry->prev;
+       /*
+        * These non-NULL pointers result in page faults when dereferenced.
+        * This helps to catch bugs resulting from using deleted list heads.
+        */
+       entry->next = (void *)0x00100100;
+       entry->prev = (void *)0x00200200;
 }
 
 /**
- * Delete entry from list.
- *
- * \param entry the element to delete from the list.
+ * Delete an entry from one list and add it as another list's head.
  *
- * Note: list_empty on entry does not return true after this, the entry is
- * in an undefined state.
+ * \param entry The entry to move.
+ * \param head The head that will precede our entry.
  */
-static inline void list_del(struct list_head *entry)
+static inline void list_move(struct list_head *entry, struct list_head *head)
 {
-       __list_del(entry->prev, entry->next);
-       entry->next = LIST_POISON1;
-       entry->prev = LIST_POISON2;
+        list_del(entry);
+        para_list_add(entry, head);
 }
 
 /**
- * delete from one list and add as another's head
+ * Test whether a list contains no entries.
  *
- * \param list: the entry to move
- * \param head: the head that will precede our entry
+ * \param head The list to test.
  */
-static inline void list_move(struct list_head *list, struct list_head *head)
+static inline int list_empty(const struct list_head *head)
 {
-        __list_del(list->prev, list->next);
-        para_list_add(list, head);
+       return head->next == head;
 }
 
 /**
- * test whether a list is empty
+ * Test whether a list has just one entry.
  *
- * \param head the list to test.
+ * \param head The list to test.
  */
-static inline int list_empty(const struct list_head *head)
+static inline int list_is_singular(const struct list_head *head)
 {
-       return head->next == head;
+       return !list_empty(head) && (head->next == head->prev);
 }
 
 /**
- * get the struct for this entry
+ * Get the struct in which this entry is embedded in.
  *
- * \param ptr the &struct list_head pointer.
- * \param type the type of the struct this is embedded in.
- * \param member the name of the list_struct within the struct.
+ * \param ptr The list head pointer.
+ * \param type The type of containing structure.
+ * \param member The name of the list head member within the structure.
  */
-#define list_entry(ptr, type, member) \
-       container_of(ptr, type, member)
+#define list_entry(ptr, type, member) container_of(ptr, type, member)
 
 /**
- * iterate over list of given type
+ * Iterate over a list.
  *
- * \param pos the type * to use as a loop counter.
- * \param head the head for your list.
- * \param member the name of the list_struct within the struct.
+ * \param pos A struct pointer which serves as the iterator.
+ * \param head The head of the list.
+ * \param member The name of the list head member within the structure.
  */
 #define list_for_each_entry(pos, head, member)                         \
        for (pos = list_entry((head)->next, typeof(*pos), member);      \
@@ -169,49 +137,27 @@ static inline int list_empty(const struct list_head *head)
             pos = list_entry(pos->member.next, typeof(*pos), member))
 
 /**
- * iterate over list of given type safe against removal of list entry
+ * Iterate over list, safe against removal of list entry.
  *
- * \param pos the type * to use as a loop counter.
- * \param n another type * to use as temporary storage
- * \param head the head for your list.
- * \param member the name of the list_struct within the struct.
+ * \param pos The iterator struct pointer.
+ * \param n A second struct pointer which is used as temporary storage.
+ * \param head The head of the list.
+ * \param member The name of the list head member within the structure.
  */
 #define list_for_each_entry_safe(pos, n, head, member)                 \
        for (pos = list_entry((head)->next, typeof(*pos), member),      \
                n = list_entry(pos->member.next, typeof(*pos), member); \
             &pos->member != (head);                                    \
             pos = n, n = list_entry(n->member.next, typeof(*n), member))
-/**
- * iterate backwards over list of given type safe against removal of list entry
- * \param pos the type * to use as a loop counter.
- * \param n another type * to use as temporary storage
- * \param head the head for your list.
- * \param member the name of the list_struct within the struct.
- */
-#define list_for_each_entry_safe_reverse(pos, n, head, member)         \
-       for (pos = list_entry((head)->prev, typeof(*pos), member),      \
-               n = list_entry(pos->member.prev, typeof(*pos), member); \
-            &pos->member != (head);                                    \
-            pos = n, n = list_entry(n->member.prev, typeof(*n), member))
 
 /**
- * Get the first element from a list
- * \param ptr the list head to take the element from.
+ * Get the first element of a list.
+ *
+ * \param ptr The list head to take the element from.
  * \param type The type of the struct this is embedded in.
  * \param member The name of the list_struct within the struct.
  *
- * Note that list is expected to be not empty.
+ * Note that the list is expected to be non-empty.
  */
 #define list_first_entry(ptr, type, member) \
-        list_entry((ptr)->next, type, member)
-
-/**
- * Test whether a list has just one entry.
- *
- * \param head The list to test.
- */
-static inline int list_is_singular(const struct list_head *head)
-{
-       return !list_empty(head) && (head->next == head->prev);
-}
-
+       list_entry((ptr)->next, type, member)
index 80ae2e42be9773f0e619e1746b9bfdefbe02f2c4..bd2a23e2354dde09cc8090a08ccf6ad31d7ac9b9 100644 (file)
@@ -51,6 +51,8 @@ caption = list of audiod commands
                short_opt = o
                summary = One-shot mode: Stop grabbing if audio file changes
 
+m4_include(`com_ll.m4')
+
 [subcommand off]
        purpose = deactivate para_audiod
        [description]
diff --git a/m4/lls/include/com_ll.m4 b/m4/lls/include/com_ll.m4
new file mode 100644 (file)
index 0000000..d7576ea
--- /dev/null
@@ -0,0 +1,10 @@
+[subcommand ll]
+       purpose = Query or set the log level of the daemon
+       m4_ifelse(SUITE, `server_cmd', `aux_info = NO_PERMISSION_REQUIRED')
+       non-opts-name = [severity]
+       [description]
+               If no argument is given, the command prints the severity string (one
+               of the possible string arguments to --loglevel) which corresponds to
+               the current loglevel. Otherwise, if the given argument is a severity
+               string, the current log level is set accordingly.
+       [/description]
index daf6de92525d334d3c1791e4d0b53907ddd7fb7f..a13f079e9b60195240bb48f3b847978263a486bc 100644 (file)
@@ -11,6 +11,7 @@ $(lls_suite_dir)/%.suite: $(lls_m4_dir)/%.suite.m4 | $(lls_suite_dir)
        $(call SAY, M4 $<)
        $(M4) -Pg -I $(lls_m4_include_dir) -D GIT_VERSION=$(GIT_VERSION) \
                -D COPYRIGHT_YEAR=$(COPYRIGHT_YEAR) -D LOGLEVELS=$(LOGLEVELS) \
+               -D SUITE=$(basename $(notdir $<)) \
                $< > $@
 
 $(lls_suite_dir)/%.lsg.c: $(lls_suite_dir)/%.suite
index 3019f7693a1d986f53cfcea45d13e572a4295a5f..e366a201569e538bc374f5356bf3d1c957164955 100644 (file)
@@ -147,11 +147,10 @@ caption = List of subcommands
 [subcommand sleep]
        purpose = stream, fade out, sleep, fade in
        [description]
-               Change to the initial volume and select the initial mood/playlist.
-               Fade out to the given fade-out volume in the specified time. Switch
-               to the sleep mood/playlist and wait until wake time minus fade-in
-               time. Finally, switch to the wake mood/playlist and fade in to the
-               fade-in volume.
+               Set the initial volume and mood, start playing and sleep. Then switch
+               to the fade-out mood and fade to the fade-out volume. Next, switch to
+               the sleep mood and wait until wake time minus fade-in time. Finally,
+               switch to the wake mood and fade in to the fade-in volume.
        [/description]
        [option ivol]
                summary = set initial volume
@@ -165,6 +164,26 @@ caption = List of subcommands
                        channel part may be omitted, in which case the default channel is
                        used. This option may be given multiple times.
                [/help]
+       [option initial-mood]
+               summary = mood or playlist to start with
+               arg_info = required_arg
+               arg_type = string
+               typestr = mood_spec
+               [help]
+                       This mood or playlist is selected right after setting the initial
+                       volume and before fade-out starts. If unset, fade-out starts
+                       immediately.
+               [/help]
+       [option initial-delay]
+               summary = time before fade-out starts.
+               arg_info = required_arg
+               arg_type = uint32
+               typestr = seconds
+               default_val = 0
+               [help]
+                       If left at the default, no initial delay occurs even if an initial
+                       mood is given.
+               [/help]
        [option fo-mood]
                summary = mood or playlist for fade-out
                arg_info = required_arg
index 57a9377eb19030cd9bbe1761d1b72b1dcce69aaa..f2220f147224b3e82ffca770e2c5417a7293a79f 100644 (file)
@@ -26,6 +26,16 @@ version-string = GIT_VERSION()
        [option randomize]
                short_opt = z
                summary = randomize playlist at startup
+       [option end-of-playlist]
+               summary = what to do after the last file has been played
+               arg_info = required_arg
+               arg_type = string
+               typestr = behaviour
+               values = {
+                       EOP_LOOP = "loop",
+                       EOP_STOP = "stop",
+                       EOP_QUIT = "quit"
+                }
        [option key-map]
                short_opt = k
                summary = map a key to a command
index be8f02f5fa4bda5101f0b2ffa67f5e4332ec0b64..1c941c433b8fcd753beb09f1c619e1976e8c5817 100644 (file)
@@ -133,7 +133,7 @@ version-string = GIT_VERSION()
                typestr = directory
                [help]
                        The directory which contains the database for the audio file
-                       selector. The default is ~/.paraslash/afs_database-0.4.
+                       selector. The default is ~/.paraslash/afs_database-0.7.
 
                        If no database was found, the "init" command must be executed to
                        initialize the database. Once initialized, audio files may added with
@@ -273,6 +273,11 @@ version-string = GIT_VERSION()
                [help]
                        See --http-access for details.
                [/help]
+       [option dccp-no-autostart]
+               summary = do not open the DCCP port on startup
+               [help]
+                       This is like --http-no-autostart but applies to the dccp sender.
+               [/help]
        [option dccp-max-clients]
                summary = maximal number of simultaneous dccp connections
                arg_info = required_arg
index 2a39907cee624219d1fb166b07dbf84288f67bb4..02afaabb5252a0580711547ad399cef932cfcadc 100644 (file)
@@ -169,6 +169,8 @@ aux_info_prefix = Permissions:
                playlists. Otherwise only the given tables are created.
        [/description]
 
+m4_include(`com_ll.m4')
+
 [subcommand jmp]
        purpose = reposition the current stream
        non-opts-name = n
@@ -220,6 +222,16 @@ aux_info_prefix = Permissions:
                        also given), chunk time and chunk offsets.
 
                [/help]
+       [option limit]
+               short_opt = L
+               summary = list at most this many files
+               arg_type = uint32
+               arg_info = required_arg
+               typestr = num
+               [help]
+                       An argument of zero means "unlimited". This is also the default which
+                       applies if the option is not given.
+               [/help]
        [option basename]
                short_opt = b
                summary = list and match basenames only
@@ -231,9 +243,19 @@ aux_info_prefix = Permissions:
        [option admissible]
                short_opt = a
                summary = list only admissible files
+               arg_type = string
+               arg_info = optional_arg
+               typestr = specifier/name
+               default_val = .
                [help]
-                       List only files which are admissible with respect to the current mood
-                       or playlist.
+                       If the optional argument is supplied, it must be of the form "p/foo"
+                       or "m/bar" (which refer to the playlist named "foo" and the mood named
+                       "bar", respectively). The command then restricts its output to the set
+                       of files which are admissible with respect to the thusly identified
+                       mood or playlist.
+
+                       If no argument is given, or if the argument is the special value ".",
+                       the current mood or playlist is assumed.
                [/help]
        [option reverse]
                short_opt = r
@@ -405,6 +427,9 @@ aux_info_prefix = Permissions:
 
                activates the mood named 'foo'.
        [/description]
+       [option verbose]
+               short_opt = v
+               summary = print information about the loaded mood or playlist
 
 [subcommand sender]
        purpose = control paraslash senders
@@ -500,15 +525,6 @@ aux_info_prefix = Permissions:
                the vss status flags, effectively stopping playback.
        [/description]
 
-[subcommand tasks]
-       purpose = list active server tasks (deprecated)
-       aux_info = NO_PERMISSION_REQUIRED
-       [description]
-               This used to print the ID, the status and the name of each task,
-               mainly for debugging purposes. As of version 0.6.2, the subcommand
-               prints nothing. It will be removed in 0.7.0. Don't use.
-       [/description]
-
 [subcommand term]
        purpose = ask the server to terminate
        aux_info = VSS_READ | VSS_WRITE
diff --git a/m4/lls/upgrade_db.suite.m4 b/m4/lls/upgrade_db.suite.m4
new file mode 100644 (file)
index 0000000..7f46b74
--- /dev/null
@@ -0,0 +1,33 @@
+m4_define(PROGRAM, para_upgrade_db)
+[suite upgrade_db]
+version-string = GIT_VERSION()
+[supercommand para_upgrade_db]
+       purpose = upgrade the paraslash database to version 0.7
+       [description]
+               The database format changes with paraslash-0.7.0. This program converts
+               the database from the older 0.4 format that was used in paraslash 0.4.x
+               through 0.6.x. In has to be executed only once.
+       [/description]
+       m4_include(common-option-section.m4)
+       m4_include(help.m4)
+       m4_include(detailed-help.m4)
+       m4_include(version.m4)
+       m4_include(loglevel.m4)
+       [option src-database-dir]
+               summary = location of the old afs database
+               arg_info = required_arg
+               arg_type = string
+               typestr = directory
+               [help]
+                       The directory which contains the database to be converted. The default
+                       is ~/.paraslash/afs_database-0.4.
+               [/help]
+       [option dst-database-dir]
+               summary = location of the new afs database
+               arg_info = required_arg
+               arg_type = string
+               typestr = directory
+               [help]
+                       The directory which contains the converted database after the program
+                       has terminated. The default is ~/.paraslash/afs_database-0.7.
+               [/help]
diff --git a/mixer.c b/mixer.c
index b14bb76ebe150864ae261da3c668c0be885e2f74..dda7fc1df729722266db7136733a28ac2391e977 100644 (file)
--- a/mixer.c
+++ b/mixer.c
@@ -40,7 +40,7 @@ static struct lls_parse_result *lpr, *sub_lpr;
 #define OPT_STRING_VAL(_cmd, _opt) (lls_string_val(0, OPT_RESULT(_cmd, _opt)))
 #define OPT_UINT32_VAL(_cmd, _opt) (lls_uint32_val(0, OPT_RESULT(_cmd, _opt)))
 
-typedef int (*mixer_subcommand_handler_t)(const struct mixer *, struct mixer_handle *);
+typedef int (*mixer_subcommand_handler_t)(const struct mixer *);
 
 #define EXPORT_CMD(_cmd) const mixer_subcommand_handler_t \
        lsg_mixer_com_ ## _cmd ## _user_data = &com_ ## _cmd;
@@ -153,19 +153,45 @@ sleep:
        return ret;
 }
 
-static int com_fade(const struct mixer *m, struct mixer_handle *h)
+static int open_mixer_and_set_channel(const struct mixer *m, struct mixer_handle **h)
+{
+       int ret;
+
+       ret = m->open(OPT_STRING_VAL(PARA_MIXER, MIXER_DEVICE), h);
+       if (ret < 0)
+               return ret;
+       ret = set_channel(m, *h, OPT_STRING_VAL(PARA_MIXER, MIXER_CHANNEL));
+       if (ret == -E_BAD_CHANNEL) {
+               char *channels = m->get_channels(*h);
+               printf("Available channels: %s\n", channels);
+               free(channels);
+       }
+       if (ret < 0)
+               m->close(h);
+       return ret;
+}
+
+static int com_fade(const struct mixer *m)
 {
        uint32_t new_vol = OPT_UINT32_VAL(FADE, FADE_VOL);
        uint32_t fade_time = OPT_UINT32_VAL(FADE, FADE_TIME);
-       return fade(m, h, new_vol, fade_time);
+       struct mixer_handle *h;
+       int ret;
+
+       ret = open_mixer_and_set_channel(m, &h);
+       if (ret < 0)
+               return ret;
+       ret = fade(m, h, new_vol, fade_time);
+       m->close(&h);
+       return ret;
 }
 EXPORT_CMD(fade);
 
-static void client_cmd(const char *cmd)
+static void run(const char *exe, const char *cmd)
 {
        int ret, status, fds[3] = {0, 0, 0};
        pid_t pid;
-       char *cmdline = make_message(BINDIR "/para_client %s", cmd);
+       char *cmdline = make_message("%s %s", exe, cmd);
 
        PARA_NOTICE_LOG("%s\n", cmdline);
        ret = para_exec_cmdline_pid(&pid, cmdline, fds);
@@ -189,6 +215,16 @@ fail:
        exit(EXIT_FAILURE);
 }
 
+static void client_cmd(const char *cmd)
+{
+       run(BINDIR "/para_client", cmd);
+}
+
+static void audioc_cmd(const char *cmd)
+{
+       run(BINDIR "/para_audioc", cmd);
+}
+
 static void change_afs_mode(const char *afs_mode)
 {
        char *cmd;
@@ -237,13 +273,28 @@ static int set_initial_volume(const struct mixer *m, struct mixer_handle *h)
        return 1;
 }
 
-static int com_sleep(const struct mixer *m, struct mixer_handle *h)
+static void stop(const struct mixer *m, struct mixer_handle *h)
+{
+       int ret, old_vol = 0;
+
+       ret = m->get(h);
+       if (ret > 0)
+               old_vol = ret;
+       fade(m, h, 0, 3);
+       audioc_cmd("off");
+       client_cmd("stop");
+       audioc_cmd("on");
+       m->set(h, old_vol);
+}
+
+static int com_sleep(const struct mixer *m)
 {
        time_t t1, wake_time_epoch;
        unsigned int delay;
        struct tm *tm;
        int ret;
        const char *wake_time = OPT_STRING_VAL(SLEEP, WAKE_TIME);
+       const char *initial_mood = OPT_STRING_VAL(SLEEP, INITIAL_MOOD);
        const char *fo_mood = OPT_STRING_VAL(SLEEP, FO_MOOD);
        const char *fi_mood = OPT_STRING_VAL(SLEEP, FI_MOOD);
        const char *sleep_mood = OPT_STRING_VAL(SLEEP, SLEEP_MOOD);
@@ -252,9 +303,13 @@ static int com_sleep(const struct mixer *m, struct mixer_handle *h)
        int fiv = OPT_UINT32_VAL(SLEEP, FI_VOL);
        int fov = OPT_UINT32_VAL(SLEEP, FO_VOL);
        int32_t hour, min = 0;
-       char *tmp;
-       char *wt = para_strdup(wake_time + (wake_time[0] == '+'));
+       char *tmp, *wt;
+       struct mixer_handle *h;
 
+       ret = open_mixer_and_set_channel(m, &h);
+       if (ret < 0)
+               return ret;
+       wt = para_strdup(wake_time + (wake_time[0] == '+'));
        /* calculate wake time */
        time(&t1);
        tmp = strchr(wt, ':');
@@ -264,13 +319,13 @@ static int com_sleep(const struct mixer *m, struct mixer_handle *h)
                ret = para_atoi32(tmp, &min);
                if (ret < 0) {
                        free(wt);
-                       return ret;
+                       goto close_mixer;
                }
        }
        ret = para_atoi32(wt, &hour);
        free(wt);
        if (ret < 0)
-               return ret;
+               goto close_mixer;
        if (wake_time[0] == '+') { /* relative */
                t1 += hour * 60 * 60 + min * 60;
                tm = localtime(&t1);
@@ -286,31 +341,42 @@ static int com_sleep(const struct mixer *m, struct mixer_handle *h)
        }
        wake_time_epoch = mktime(tm);
        PARA_INFO_LOG("waketime: %d:%02d\n", tm->tm_hour, tm->tm_min);
-       client_cmd("stop");
-       sleep(1);
+       stop(m, h);
+       ret = set_initial_volume(m, h);
+       if (ret < 0)
+               goto close_mixer;
+       /*
+        * Setting the volume invalidates the current channel setting, so we
+        * have to set it again.
+        */
+       ret = set_channel(m, h, OPT_STRING_VAL(PARA_MIXER, MIXER_CHANNEL));
+       if (ret < 0)
+               goto close_mixer;
+       delay = OPT_UINT32_VAL(SLEEP, INITIAL_DELAY);
+       if (delay > 0 && initial_mood && *initial_mood) {
+               change_afs_mode(initial_mood);
+               client_cmd("play");
+               sleep(delay);
+               stop(m, h);
+       }
        if (fot && fo_mood && *fo_mood) {
-               ret = set_initial_volume(m, h);
-               if (ret < 0)
-                       return ret;
                change_afs_mode(fo_mood);
                client_cmd("play");
-               ret = set_channel(m, h, OPT_STRING_VAL(PARA_MIXER, MIXER_CHANNEL));
-               if (ret < 0)
-                       return ret;
                ret = fade(m, h, fov, fot);
                if (ret < 0)
-                       return ret;
+                       goto close_mixer;
        } else {
                ret = m->set(h, fov);
                if (ret < 0)
-                       return ret;
+                       goto close_mixer;
        }
        if (sleep_mood && *sleep_mood) {
                change_afs_mode(sleep_mood);
                if (!fot || !fo_mood) /* currently stopped */
                        client_cmd("play");
        } else if (fot && fo_mood && *fo_mood) /* currently playing */
-               client_cmd("stop");
+               stop(m, h);
+       m->close(&h);
        if (!fit || !fi_mood || !*fi_mood) /* nothing to do */
                return 1;
        for (;;) {
@@ -324,25 +390,33 @@ static int com_sleep(const struct mixer *m, struct mixer_handle *h)
                sleep(delay);
        }
        change_afs_mode(fi_mood);
+       ret = open_mixer_and_set_channel(m, &h);
+       if (ret < 0)
+               return ret;
        if (sleep_mood && *sleep_mood) /* currently playing */
-               client_cmd("next");
-       else /* currently stopped */
-               client_cmd("play");
+               stop(m, h);
+       client_cmd("play");
        ret = fade(m, h, fiv, fit);
-       PARA_INFO_LOG("fade complete, returning\n");
+close_mixer:
+       m->close(&h);
        return ret;
 }
 EXPORT_CMD(sleep);
 
-static int com_snooze(const struct mixer *m, struct mixer_handle *h)
+static int com_snooze(const struct mixer *m)
 {
        int ret, val;
+       struct mixer_handle *h;
 
+       ret = open_mixer_and_set_channel(m, &h);
+       if (ret < 0)
+               return ret;
+       ret = 1;
        if (OPT_UINT32_VAL(SNOOZE, SO_TIME) == 0)
-               return 1;
+               goto close_mixer;
        ret = m->get(h);
        if (ret < 0)
-               return ret;
+               goto close_mixer;
        val = ret;
        if (val < OPT_UINT32_VAL(SNOOZE, SO_VOL))
                ret = m->set(h, OPT_UINT32_VAL(SNOOZE, SO_VOL));
@@ -350,20 +424,35 @@ static int com_snooze(const struct mixer *m, struct mixer_handle *h)
                ret = fade(m, h, OPT_UINT32_VAL(SNOOZE, SO_VOL),
                        OPT_UINT32_VAL(SNOOZE, SO_TIME));
        if (ret < 0)
-               return ret;
+               goto close_mixer;
        client_cmd("pause");
        PARA_NOTICE_LOG("%" PRIu32 " seconds snooze time...\n",
                OPT_UINT32_VAL(SNOOZE, SNOOZE_TIME));
+       m->close(&h);
        sleep(OPT_UINT32_VAL(SNOOZE, SNOOZE_TIME));
        client_cmd("play");
-       return fade(m, h, OPT_UINT32_VAL(SNOOZE, SI_VOL),
+       ret = open_mixer_and_set_channel(m, &h);
+       if (ret < 0)
+               goto close_mixer;
+       ret = fade(m, h, OPT_UINT32_VAL(SNOOZE, SI_VOL),
                OPT_UINT32_VAL(SNOOZE, SI_TIME));
+close_mixer:
+       m->close(&h);
+       return ret;
 }
 EXPORT_CMD(snooze);
 
-static int com_set(const struct mixer *m, struct mixer_handle *h)
+static int com_set(const struct mixer *m)
 {
-       return m->set(h, OPT_UINT32_VAL(SET, VAL));
+       struct mixer_handle *h;
+       int ret;
+
+       ret = open_mixer_and_set_channel(m, &h);
+       if (ret < 0)
+               return ret;
+       ret = m->set(h, OPT_UINT32_VAL(SET, VAL));
+       m->close(&h);
+       return ret;
 }
 EXPORT_CMD(set);
 
@@ -402,9 +491,7 @@ static void show_subcommands(void)
        }
 }
 
-
-static int com_help(__a_unused const struct mixer *m,
-               __a_unused struct mixer_handle *h)
+static int com_help(__a_unused const struct mixer *m)
 {
        const struct lls_command *cmd;
        const struct lls_opt_result *r_l = OPT_RESULT(HELP, LONG);
@@ -487,7 +574,6 @@ int main(int argc, char *argv[])
        char *errctx;
        const char *subcmd;
        const struct mixer *m;
-       struct mixer_handle *h;
        unsigned n;
 
        ret = lls(lls_parse(argc, argv, cmd, &lpr, &errctx));
@@ -515,23 +601,10 @@ int main(int argc, char *argv[])
        if (ret < 0)
                goto free_lpr;
        ret = parse_and_merge_config_file(cmd);
-       if (ret < 0)
-               goto free_lpr;
-       m = get_mixer_or_die();
-       ret = m->open(OPT_STRING_VAL(PARA_MIXER, MIXER_DEVICE), &h);
        if (ret < 0)
                goto free_sub_lpr;
-       ret = set_channel(m, h, OPT_STRING_VAL(PARA_MIXER, MIXER_CHANNEL));
-       if (ret == -E_BAD_CHANNEL) {
-               char *channels = m->get_channels(h);
-               printf("Available channels: %s\n", channels);
-               free(channels);
-       }
-       if (ret < 0)
-               goto close_mixer;
-       ret = (*(mixer_subcommand_handler_t *)(lls_user_data(cmd)))(m, h);
-close_mixer:
-       m->close(&h);
+       m = get_mixer_or_die();
+       ret = (*(mixer_subcommand_handler_t *)(lls_user_data(cmd)))(m);
 free_sub_lpr:
        lls_free_parse_result(sub_lpr, cmd);
 free_lpr:
diff --git a/mm.c b/mm.c
deleted file mode 100644 (file)
index 358783a..0000000
--- a/mm.c
+++ /dev/null
@@ -1,375 +0,0 @@
-/* Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
-
-/** \file mm.c Paraslash's mood methods. */
-
-#include <regex.h>
-#include <fnmatch.h>
-#include <osl.h>
-#include <lopsub.h>
-
-#include "para.h"
-#include "error.h"
-#include "string.h"
-#include "afh.h"
-#include "afs.h"
-#include "mm.h"
-
-/** The comparators for numeric mood methods (year, bitrate, ...). */
-#define MOOD_COMPARATORS \
-       MC(LESS, <) \
-       MC(LESS_OR_EQUAL, <=) \
-       MC(EQUAL, =) \
-       MC(EQUAL2, ==) \
-       MC(NOT_EQUAL, !=) \
-       MC(NOT_EQUAL2, <>) \
-       MC(GREATER, >) \
-       MC(GREATER_OR_EQUAL, >=) \
-
-/** Prefix mood comparator name with "_MC", example: MC_LESS. */
-#define MC(a, b) MC_ ## a,
-/** Each mood comparator is identified by an integer of this type. */
-enum mood_comparator_id {MOOD_COMPARATORS NUM_MOOD_COMPARATORS};
-#undef MC
-/** Stringfied mood comparator, example: "<". */
-#define MC(a, b) # b,
-/** Array of mood comparators represented as C strings ("<", "<=", ...). */
-static const char *mood_comparators[] = {MOOD_COMPARATORS};
-#undef MC
-
-static int parse_mood_comparator(const char *word)
-{
-       int i;
-
-       for (i = 0; i < NUM_MOOD_COMPARATORS; i++)
-               if (!strcmp(word, mood_comparators[i]))
-                       return i;
-       return -E_MOOD_SYNTAX;
-}
-
-struct mm_compare_num_data {
-       /** <, <=, =, !=, >=, or >. */
-       enum mood_comparator_id id;
-       /** The value given at the mood line. */
-       int32_t arg;
-};
-
-static int mm_compare_num_score_function(int32_t val,
-               const struct mm_compare_num_data *cnd)
-{
-       int res;
-       int32_t arg = cnd->arg;
-
-       switch (cnd->id) {
-       case MC_LESS:
-               res = val < arg; break;
-       case MC_LESS_OR_EQUAL:
-               res = val <= arg; break;
-       case MC_EQUAL:
-       case MC_EQUAL2:
-               res = val == arg; break;
-       case MC_NOT_EQUAL:
-       case MC_NOT_EQUAL2:
-               res = val != arg; break;
-       case MC_GREATER:
-               res = val > arg; break;
-       case MC_GREATER_OR_EQUAL:
-               res = val >= arg; break;
-       default:
-               PARA_EMERG_LOG("BUG: invalid mood comparator\n");
-               exit(EXIT_FAILURE);
-       }
-       return res? 100 : -100;
-}
-
-static int mm_compare_num_parser(int argc, char **argv, void **private)
-{
-       int ret;
-       enum mood_comparator_id id;
-       int32_t arg;
-       struct mm_compare_num_data *cnd;
-       if (argc != 2)
-               return -E_MOOD_SYNTAX;
-       ret = parse_mood_comparator(argv[1]);
-       if (ret < 0)
-               return ret;
-       id = ret;
-       ret = para_atoi32(argv[2], &arg);
-       if (ret < 0)
-               return ret;
-       cnd = para_malloc(sizeof(struct mm_compare_num_data));
-       cnd->id = id;
-       cnd->arg = arg;
-       *private = cnd;
-       return 1;
-}
-
-static int mm_regex_parser(int argc, char **argv, void **private)
-{
-       regex_t *preg;
-       int ret;
-
-       if (argc != 1)
-               return -E_MOOD_SYNTAX;
-       preg = para_malloc(sizeof(*preg));
-       ret = para_regcomp(preg, argv[1], REG_EXTENDED | REG_NOSUB);
-       if (ret < 0) {
-               free(preg);
-               return ret;
-       }
-       *private = preg;
-       return 1;
-}
-
-static int mm_regex_score_function(const regex_t *preg, const char *pattern)
-{
-       return regexec(preg, pattern, 0, NULL, 0) == 0? 100 : -100;
-}
-
-static void mm_regex_cleanup(void *private)
-{
-       regex_t *preg = private;
-       regfree(preg);
-       free(preg);
-}
-
-static int mm_artist_matches_score_function(__a_unused const char *path,
-               __a_unused const struct afs_info *afsi,
-               const struct afh_info *afhi,
-               const void *private)
-{
-       return mm_regex_score_function(private, afhi->tags.artist);
-}
-
-static int mm_title_matches_score_function(__a_unused const char *path,
-               __a_unused const struct afs_info *afsi,
-               const struct afh_info *afhi,
-               const void *private)
-{
-       return mm_regex_score_function(private, afhi->tags.title);
-}
-
-static int mm_album_matches_score_function(__a_unused const char *path,
-               __a_unused const struct afs_info *afsi,
-               const struct afh_info *afhi,
-               const void *private)
-{
-       return mm_regex_score_function(private, afhi->tags.album);
-}
-
-static int mm_comment_matches_score_function(__a_unused const char *path,
-               __a_unused const struct afs_info *afsi,
-               const struct afh_info *afhi,
-               const void *private)
-{
-       return mm_regex_score_function(private, afhi->tags.comment);
-}
-
-static int mm_bitrate_score_function(__a_unused const char *path,
-               __a_unused const struct afs_info *afsi,
-               const struct afh_info *afhi,
-               const void *private)
-{
-       return mm_compare_num_score_function(afhi->bitrate, private);
-}
-
-static int mm_frequency_score_function(__a_unused const char *path,
-               __a_unused const struct afs_info *afsi,
-               const struct afh_info *afhi,
-               const void *private)
-{
-       return mm_compare_num_score_function(afhi->frequency, private);
-}
-
-static int mm_channels_score_function(__a_unused const char *path,
-               __a_unused const struct afs_info *afsi,
-               const struct afh_info *afhi,
-               const void *private)
-{
-       return mm_compare_num_score_function(afhi->channels, private);
-}
-
-static int mm_image_id_score_function(__a_unused const char *path,
-               const struct afs_info *afsi,
-               __a_unused const struct afh_info *afhi,
-               const void *private)
-{
-       return mm_compare_num_score_function(afsi->image_id, private);
-}
-
-static int mm_lyrics_id_score_function(__a_unused const char *path,
-               const struct afs_info *afsi,
-               __a_unused const struct afh_info *afhi,
-               const void *private)
-{
-       return mm_compare_num_score_function(afsi->lyrics_id, private);
-}
-
-static int mm_num_played_score_function(__a_unused const char *path,
-               const struct afs_info *afsi,
-               __a_unused const struct afh_info *afhi,
-               const void *private)
-{
-       return mm_compare_num_score_function(afsi->num_played, private);
-}
-
-struct mm_year_data {
-       /** Comparator and year given at the mood line. */
-       struct mm_compare_num_data *cnd;
-       /** Used to detect Y2K issues. */
-       int32_t current_year;
-};
-
-static int mm_year_parser(int argc, char **argv, void **private)
-{
-       int ret;
-       struct mm_year_data *mmyd = para_malloc(sizeof(*mmyd));
-       time_t current_time;
-       struct tm *gmt;
-
-       ret = mm_compare_num_parser(argc, argv, (void **)&mmyd->cnd);
-       if (ret < 0)
-               goto err;
-       current_time = time(NULL);
-       gmt = gmtime(&current_time);
-       /* tm_year is the number of years since 1900 */
-       mmyd->current_year = gmt->tm_year + 1900;
-       *private = mmyd;
-       return 1;
-err:
-       free(mmyd);
-       return ret;
-}
-
-static int mm_year_score_function(__a_unused const char *path,
-               __a_unused const struct afs_info *afsi,
-               const struct afh_info *afhi,
-               const void *private)
-{
-       const struct mm_year_data *mmyd = private;
-       int32_t tag_year;
-       int ret = para_atoi32(afhi->tags.year, &tag_year);
-
-       if (ret < 0) /* year tag not present or not a number */
-               return -100;
-       if (tag_year < 0)
-               return -100;
-       /* try to work around Y2K issues */
-       if (tag_year < 100) {
-               tag_year += 1900;
-               if (tag_year + 100 <= mmyd->current_year)
-                       tag_year += 100; /* assume tag_year >= 2000 */
-       }
-       return mm_compare_num_score_function(tag_year, mmyd->cnd);
-}
-
-static void mm_year_cleanup(void *private)
-{
-       struct mm_year_data *mmyd = private;
-
-       free(mmyd->cnd);
-       free(mmyd);
-}
-
-static int mm_no_attributes_set_parser(int argc, __a_unused char **argv,
-               __a_unused void **ignored)
-{
-       return argc? -E_MOOD_SYNTAX : 1;
-}
-
-static int mm_no_attributes_set_score_function(__a_unused const char *path,
-               const struct afs_info *afsi,
-               __a_unused const struct afh_info *afhi,
-               __a_unused const void *data)
-{
-       if (!afsi->attributes)
-               return 100;
-       return -100;
-}
-
-static int mm_path_matches_score_function(const char *path,
-               __a_unused const struct afs_info *afsi,
-               __a_unused const struct afh_info *afhi,
-               const void *data)
-{
-       if (fnmatch(data, path, 0))
-               return -100;
-       return 100;
-}
-
-static int mm_path_matches_parser(int argc, char **argv, void **data)
-{
-       if (argc != 1)
-               return -E_MOOD_SYNTAX;
-       *data = para_strdup(argv[1]);
-       return 1;
-}
-
-static void mm_path_matches_cleanup(void *data)
-{
-       free(data);
-}
-
-static int mm_is_set_parser(int argc, char **argv, void **bitnum)
-{
-       int ret;
-       unsigned char c, *res;
-
-       if (argc != 1)
-               return -E_MOOD_SYNTAX;
-       ret = get_attribute_bitnum_by_name(argv[1], &c);
-       if (ret < 0)
-               return ret;
-       res = para_malloc(1);
-       *res = c;
-       *bitnum = res;
-       return 1;
-}
-
-static int mm_is_set_score_function(__a_unused const char *path,
-               __a_unused const struct afs_info *afsi,
-               __a_unused const struct afh_info *afhi,
-               const void *data)
-{
-       const unsigned char *bn = data;
-       if (afsi->attributes & (1ULL << *bn))
-               return 100;
-       return -100;
-}
-
-#define DEFINE_MOOD_METHOD(_name) \
-.parser = mm_ ## _name ## _parser, \
-.score_function = mm_ ## _name ## _score_function, \
-.name = #_name
-
-#define DEFINE_MOOD_METHOD_WITH_CLEANUP(_name) \
-       DEFINE_MOOD_METHOD(_name), \
-       .cleanup = mm_ ## _name ## _cleanup
-
-#define DEFINE_REGEX_MOOD_METHOD(_name) \
-       .name = #_name, \
-       .parser = mm_regex_parser, \
-       .score_function = mm_ ## _name ## _score_function, \
-       .cleanup = mm_regex_cleanup
-
-#define DEFINE_COMPARE_NUM_MOOD_METHOD(_name) \
-       .name = #_name, \
-       .parser = mm_compare_num_parser, \
-       .score_function = mm_ ## _name ## _score_function
-
-const struct mood_method mood_methods[] = {
-       {DEFINE_MOOD_METHOD(no_attributes_set)},
-       {DEFINE_MOOD_METHOD(is_set)},
-       {DEFINE_MOOD_METHOD_WITH_CLEANUP(path_matches)},
-       {DEFINE_MOOD_METHOD_WITH_CLEANUP(year)},
-       {DEFINE_REGEX_MOOD_METHOD(artist_matches)},
-       {DEFINE_REGEX_MOOD_METHOD(title_matches)},
-       {DEFINE_REGEX_MOOD_METHOD(album_matches)},
-       {DEFINE_REGEX_MOOD_METHOD(comment_matches)},
-       {DEFINE_COMPARE_NUM_MOOD_METHOD(bitrate)},
-       {DEFINE_COMPARE_NUM_MOOD_METHOD(frequency)},
-       {DEFINE_COMPARE_NUM_MOOD_METHOD(channels)},
-       {DEFINE_COMPARE_NUM_MOOD_METHOD(num_played)},
-       {DEFINE_COMPARE_NUM_MOOD_METHOD(image_id)},
-       {DEFINE_COMPARE_NUM_MOOD_METHOD(lyrics_id)},
-       {.parser = NULL}
-};
diff --git a/mm.h b/mm.h
deleted file mode 100644 (file)
index 28038e7..0000000
--- a/mm.h
+++ /dev/null
@@ -1,62 +0,0 @@
-/* Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
-
-/** \file mm.h Symbols and declarations for mood methods. */
-
-/**
- * Assign scores according to a mood_method.
- *
- * Each mood_method has its own mood_score_function. The first three parameters
- * passed to that function are informations about the audio file whose score is
- * to be computed. The data argument depends on the mood method this function
- * is used for. It usually is the argument given at the end of a mood line.
- *
- * Mood score functions must return values between -100 and +100 inclusively.
- * Boolean score functions should always return either -100 or +100.
- *
- * \sa struct \ref mood_method, \ref mood_parser.
- */
-typedef int mood_score_function(const char *path, const struct afs_info *afsi,
-               const struct afh_info *afhi, const void *data);
-
-/**
- * Pre-process a mood line.
- *
- * The mood_parser of a mood_method is called once at mood open time for each
- * line of the current mood definition that contains the mood_method's name as
- * a keyword. The line is passed to the mood_parser as the first argument. The
- * mood_parser must determine whether the line is syntactically correct and
- * return a positive value if so and a negative value otherwise.
- *
- * Some mood parsers pre-process the data given in the mood line to compute a
- * structure which depends of the particular mood_method and which is used
- * later in the mood_score_function of the mood_method. The mood_parser may
- * store a pointer to its structure via the void** pointer.
- *
- * \sa \ref mood_cleanup_function, \ref mood_score_function.
- */
-typedef int mood_parser(int, char **, void **);
-
-/**
- * Deallocate resources which were allocated by the mood_parser.
- *
- * Function to free the resources allocated in \ref mood_method::parser. The
- * argument is a pointer to mood method specific data returned by ->parser().
- */
-typedef void mood_cleanup_function(void *);
-
-/**
- * Used for scoring and to determine whether a file is admissible.
- */
-struct mood_method {
-       /** The name of the method. */
-       const char *name;
-       /** Pointer to the mood parser. */
-       mood_parser *parser;
-       /** Pointer to the score function */
-       mood_score_function *score_function;
-       /** Optional cleanup function. */
-       mood_cleanup_function *cleanup;
-};
-
-/** The array of available mood methods. */
-extern const struct mood_method mood_methods[];
diff --git a/mood.c b/mood.c
index a63d4d2af5d10d7b64c319d915e00b9b7ea62e89..1e15ef0e081480381fcbc4fdad2179848f429c1f 100644 (file)
--- a/mood.c
+++ b/mood.c
@@ -12,8 +12,6 @@
 #include "afh.h"
 #include "afs.h"
 #include "list.h"
-#include "mm.h"
-#include "mood.h"
 
 /*
  * Mood parser API. It's overkill to have an own header file for
@@ -39,55 +37,40 @@ struct afs_statistics {
        int64_t num_played_qd;
        /** Quadratic deviation of last played time. */
        int64_t last_played_qd;
+       /** Correction factor for the num played score. */
+       int64_t num_played_correction;
+       /** Correction factor for the last played score. */
+       int64_t last_played_correction;
+       /** Common divisor of the correction factors. */
+       int64_t normalization_divisor;
        /** Number of admissible files */
        unsigned num;
 };
-static struct afs_statistics statistics;
 
 /**
- * Each line of the current mood corresponds to a mood_item.
- */
-struct mood_item {
-       /** The method this line is referring to. */
-       const struct mood_method *method;
-       /** The data structure computed by the mood parser. */
-       void *parser_data;
-       /** The given score value, or zero if none was given. */
-       int32_t score_arg;
-       /** Non-zero if random scoring was requested. */
-       int random_score;
-       /** Whether the "not" keyword was given in the mood line. */
-       int logical_not;
-       /** The position in the list of items. */
-       struct list_head mood_item_node;
-};
-
-/*
- * Created from the mood definition by \ref change_current_mood().
+ * Stores an instance of a loaded mood (parser and statistics).
  *
- * When a mood is opened, each line of its definition is investigated, and a
- * corresponding mood item is produced. Each mood line starts with accept,
- * deny, or score which determines the type of the mood line. For each such
- * type a linked list is maintained whose entries are the mood items.
+ * A structure of this type is allocated and initialized when a mood is loaded.
  */
-struct mood {
-       /** The name of this mood. */
+struct mood_instance {
+       /** NULL means that this is the "dummy" mood. */
        char *name;
-       /** The list of mood items of type \p accept. */
-       struct list_head accept_list;
-       /** The list of mood items of type \p deny. */
-       struct list_head deny_list;
-       /** The list of mood items of type \p score. */
-       struct list_head score_list;
-       /* Only used for version 2 moods. */
+       /** Bison's abstract syntax tree, used to determine admissibility. */
        struct mp_context *parser_context;
+       /** To compute the score. */
+       struct afs_statistics stats;
+       /** NULL means to operate on the global score table. */
+       struct osl_table *score_table;
 };
 
 /*
- * If current_mood is NULL then no mood is currently open. If
- * current_mood->name is NULL, the dummy mood is currently open.
+ * If current_mood is NULL then no mood is currently loaded. If
+ * current_mood->name is NULL, the current mood is the dummy mood.
+ *
+ * The statistics are adjusted dynamically through this pointer as files are
+ * added, removed or played.
  */
-static struct mood *current_mood;
+static struct mood_instance *current_mood;
 
 /*
  * Find the position of the most-significant set bit.
@@ -146,338 +129,76 @@ __a_const static uint64_t int_sqrt(uint64_t x)
        return res;
 }
 
-/*
- * Returns true if row matches, false if it does not match. In any case score
- * and score_arg_sum are set/increased accordingly.
- */
-static bool get_item_score(struct mood_item *item, const struct afs_info *afsi,
-               const struct afh_info *afhi, const char *path, long *score,
-               long *score_arg_sum)
-{
-       int ret;
-       bool match = true;
-
-       *score_arg_sum += item->random_score? 100 : PARA_ABS(item->score_arg);
-       ret = 100;
-       if (item->method) {
-               ret = item->method->score_function(path, afsi, afhi,
-                       item->parser_data);
-               if ((ret < 0 && !item->logical_not) || (ret >= 0 && item->logical_not))
-                       match = false;
-       }
-       if (item->random_score)
-               *score = PARA_ABS(ret) * para_random(100);
-       else
-               *score = PARA_ABS(ret) * item->score_arg;
-       return match;
-}
-
-/* returns 1 if row admissible, 0 if not, negative on errors */
-static int row_is_admissible(const struct osl_row *aft_row, struct mood *m,
-               long *scorep)
+static void destroy_mood(struct mood_instance *m)
 {
-       struct mood_item *item;
-       int ret;
-       bool match;
-       long score_arg_sum = 0, score = 0, item_score;
-       struct afs_info afsi;
-       struct afh_info afhi;
-       char *path;
-
-       if (!m)
-               return -E_NO_MOOD;
-       if (m->parser_context) {
-               *scorep = 0;
-               return mp_eval_row(aft_row, m->parser_context);
-       }
-       ret = get_afsi_of_row(aft_row, &afsi);
-       if (ret < 0)
-               return ret;
-       ret = get_afhi_of_row(aft_row, &afhi);
-       if (ret < 0)
-               return ret;
-       ret = get_audio_file_path_of_row(aft_row, &path);
-       if (ret < 0)
-               return ret;
-       /* reject audio file if it matches any entry in the deny list */
-       list_for_each_entry(item, &m->deny_list, mood_item_node) {
-               match = get_item_score(item, &afsi, &afhi, path, &item_score,
-                       &score_arg_sum);
-               if (match) /* not admissible */
-                       return 0;
-               score += item_score;
-       }
-       match = false;
-       list_for_each_entry(item, &m->accept_list, mood_item_node) {
-               ret = get_item_score(item, &afsi, &afhi, path, &item_score,
-                       &score_arg_sum);
-               if (ret == 0)
-                       continue;
-               match = true;
-               score += item_score;
-       }
-       /* reject if there is no matching entry in the accept list */
-       if (!match && !list_empty(&m->accept_list))
-               return 0;
-       list_for_each_entry(item, &m->score_list, mood_item_node) {
-               match = get_item_score(item, &afsi, &afhi, path, &item_score,
-                       &score_arg_sum);
-               if (match)
-                       score += item_score;
-       }
-       if (score_arg_sum)
-               score /= score_arg_sum;
-       *scorep = score;
-       return 1;
-}
-
-static void cleanup_list_entry(struct mood_item *item)
-{
-       if (item->method && item->method->cleanup)
-               item->method->cleanup(item->parser_data);
-       else
-               free(item->parser_data);
-       list_del(&item->mood_item_node);
-       free(item);
-}
-
-static void destroy_mood(struct mood *m)
-{
-       struct mood_item *tmp, *item;
-
        if (!m)
                return;
-       list_for_each_entry_safe(item, tmp, &m->accept_list, mood_item_node)
-               cleanup_list_entry(item);
-       list_for_each_entry_safe(item, tmp, &m->deny_list, mood_item_node)
-               cleanup_list_entry(item);
-       list_for_each_entry_safe(item, tmp, &m->score_list, mood_item_node)
-               cleanup_list_entry(item);
-       free(m->name);
        mp_shutdown(m->parser_context);
+       if (m->score_table)
+               score_close(m->score_table);
+       free(m->name);
        free(m);
 }
 
-static struct mood *alloc_new_mood(const char *name)
+static struct mood_instance *alloc_new_mood(const char *name)
 {
-       struct mood *m = para_calloc(sizeof(struct mood));
+       struct mood_instance *m = zalloc(sizeof(*m));
+
        if (name)
                m->name = para_strdup(name);
-       INIT_LIST_HEAD(&m->accept_list);
-       INIT_LIST_HEAD(&m->deny_list);
-       INIT_LIST_HEAD(&m->score_list);
+       m->stats.normalization_divisor = 1;
        return m;
 }
 
-/** The different types of a mood line. */
-enum mood_line_type {
-       /** Invalid. */
-       ML_INVALID,
-       /** Accept line. */
-       ML_ACCEPT,
-       /** Deny line. */
-       ML_DENY,
-       /** Score line. */
-       ML_SCORE
-};
-
-/** Data passed to the parser of a mood line. */
-struct mood_line_parser_data {
-       /** The mood this mood line belongs to. */
-       struct mood *m;
-       /** The line number in the mood definition. */
-       unsigned line_num;
-};
-
-/*
- * <accept [with score <score>] | deny [with score <score>]  | score <score>>
- *     [if] [not] <mood_method> [options]
- * <score> is either an integer or "random" which assigns a random score to
- * all matching files
- */
-static int parse_mood_line(char *mood_line, void *data)
+static int init_mood_parser(const char *mood_name, struct mood_instance **m,
+               char **err)
 {
-       struct mood_line_parser_data *mlpd = data;
-       char **argv;
-       unsigned num_words;
-       char **w;
-       int i, ret;
-       enum mood_line_type mlt = ML_INVALID;
-       struct mood_item *mi = NULL;
-
-       mlpd->line_num++;
-       ret = create_argv(mood_line, " \t", &argv);
-       if (ret < 0)
-               return ret;
-       num_words = ret;
-       if (!num_words) /* empty line */
-               goto out;
-       w = argv;
-       if (**w == '#') /* comment */
-               goto out;
-       if (!strcmp(*w, "accept"))
-               mlt = ML_ACCEPT;
-       else if (!strcmp(*w, "deny"))
-               mlt = ML_DENY;
-       else if (!strcmp(*w, "score"))
-               mlt = ML_SCORE;
-       ret = -E_MOOD_SYNTAX;
-       if (mlt == ML_INVALID)
-               goto out;
-       mi = para_calloc(sizeof(struct mood_item));
-       if (mlt != ML_SCORE) {
-               ret = -E_MOOD_SYNTAX;
-               w++;
-               if (!*w)
-                       goto out;
-               if (strcmp(*w, "with"))
-                       goto check_for_if;
-               w++;
-               if (!*w)
-                       goto out;
-               if (strcmp(*w, "score"))
-                       goto out;
-       }
-       if (mlt == ML_SCORE || !strcmp(*w, "score")) {
-               ret = -E_MOOD_SYNTAX;
-               w++;
-               if (!*w)
-                       goto out;
-               if (strcmp(*w, "random")) {
-                       mi->random_score = 0;
-                       ret = para_atoi32(*w, &mi->score_arg);
-                       if (ret < 0)
-                               goto out;
-               } else {
-                       mi->random_score = 1;
-                       if (!*(w + 1))
-                       goto success; /* the line "score random" is valid */
-               }
-       } else
-               mi->score_arg = 0;
-       ret = -E_MOOD_SYNTAX;
-       w++;
-       if (!*w)
-               goto out;
-check_for_if:
-       if (!strcmp(*w, "if")) {
-               ret = -E_MOOD_SYNTAX;
-               w++;
-               if (!*w)
-                       goto out;
-       }
-       if (!strcmp(*w, "not")) {
-               ret = -E_MOOD_SYNTAX;
-               w++;
-               if (!*w)
-                       goto out;
-               mi->logical_not = 1;
-       } else
-               mi->logical_not = 0;
-       for (i = 0; mood_methods[i].parser; i++) {
-               if (strcmp(*w, mood_methods[i].name))
-                       continue;
-               break;
-       }
-       ret = -E_MOOD_SYNTAX;
-       if (!mood_methods[i].parser)
-               goto out;
-       ret = mood_methods[i].parser(num_words - 1 - (w - argv), w,
-               &mi->parser_data);
-       if (ret < 0)
-               goto out;
-       mi->method = &mood_methods[i];
-success:
-       if (mlpd->m) {
-               if (mlt == ML_ACCEPT)
-                       para_list_add(&mi->mood_item_node, &mlpd->m->accept_list);
-               else if (mlt == ML_DENY)
-                       para_list_add(&mi->mood_item_node, &mlpd->m->deny_list);
-               else
-                       para_list_add(&mi->mood_item_node, &mlpd->m->score_list);
-       }
-       PARA_DEBUG_LOG("%s entry added, method: %p\n", mlt == ML_ACCEPT? "accept" :
-               (mlt == ML_DENY? "deny" : "score"), mi->method);
-       ret = 1;
-out:
-       free_argv(argv);
-       if (mi && (ret < 0 || !mlpd->m)) { /* mi was not added to any list */
-               free(mi->parser_data);
-               free(mi);
-       }
-       return ret;
-}
-
-static int load_mood(const struct osl_row *mood_row, struct mood **m,
-               char **errmsg)
-{
-       char *mood_name;
        struct osl_object mood_def;
-       struct mood_line_parser_data mlpd = {.line_num = 0};
        int ret;
 
-       *m = NULL;
-       ret = mood_get_name_and_def_by_row(mood_row, &mood_name, &mood_def);
-       if (ret < 0) {
-               if (errmsg)
-                       *errmsg = make_message(
-                               "could not read mood definition");
-               return ret;
+       if (!*mood_name) {
+               if (err)
+                       *err = make_message("empty mood name\n");
+               return -ERRNO_TO_PARA_ERROR(EINVAL);
        }
-       assert(*mood_name);
-       mlpd.m = alloc_new_mood(mood_name);
-       ret = for_each_line(FELF_READ_ONLY, mood_def.data, mood_def.size,
-               parse_mood_line, &mlpd);
+       ret = mood_get_def_by_name(mood_name, &mood_def);
        if (ret < 0) {
-               PARA_INFO_LOG("opening version 2 mood %s\n", mlpd.m->name);
-               ret = mp_init(mood_def.data, mood_def.size, &mlpd.m->parser_context,
-                       errmsg);
-               if (ret < 0)
-                       destroy_mood(mlpd.m);
-       } else {
-               PARA_WARNING_LOG("loaded version 1 mood %s\n", mlpd.m->name);
-               PARA_WARNING_LOG("please convert to version 2\n");
-               ret = 1;
+               if (err)
+                       *err = make_message("could not read mood definition\n");
+               return ret;
        }
+       *m = alloc_new_mood(mood_name);
+       PARA_INFO_LOG("loading mood %s\n", mood_name);
+       ret = mp_init(mood_def.data, mood_def.size, &(*m)->parser_context, err);
        osl_close_disk_object(&mood_def);
-       if (ret >= 0)
-               *m = mlpd.m;
+       if (ret < 0)
+               destroy_mood(*m);
        return ret;
 }
 
 static int check_mood(struct osl_row *mood_row, void *data)
 {
-       struct para_buffer *pb = data;
-       char *mood_name;
+       struct afs_callback_arg *aca = data;
+       char *mood_name, *errmsg;
        struct osl_object mood_def;
-       struct mood_line_parser_data mlpd = {.line_num = 0};
-
+       struct mood_instance *m;
        int ret = mood_get_name_and_def_by_row(mood_row, &mood_name, &mood_def);
 
        if (ret < 0) {
-               para_printf(pb, "cannot read mood\n");
+               afs_error(aca, "cannot read mood\n");
                return ret;
        }
        if (!*mood_name) /* ignore dummy row */
                goto out;
-       ret = for_each_line(FELF_READ_ONLY, mood_def.data, mood_def.size,
-               parse_mood_line, &mlpd);
+       m = alloc_new_mood("check");
+       ret = mp_init(mood_def.data, mood_def.size, &m->parser_context,
+               &errmsg);
        if (ret < 0) {
-               char *errmsg;
-               struct mood *m = alloc_new_mood("check");
-               ret = mp_init(mood_def.data, mood_def.size, &m->parser_context,
-                       &errmsg);
-               if (ret < 0) {
-                       para_printf(pb, "%s: %s\n", mood_name, errmsg);
-                       free(errmsg);
-                       para_printf(pb, "%s\n", para_strerror(-ret));
-               } else
-                       destroy_mood(m);
-       } else {
-               para_printf(pb, "%s: v1 mood, please convert to v2\n",
-                       mood_name);
-
-       }
+               afs_error(aca, "%s: %s\n%s\n", mood_name, errmsg,
+                       para_strerror(-ret));
+               free(errmsg);
+       } else
+               destroy_mood(m);
        ret = 1; /* don't fail the loop on invalid mood definitions */
 out:
        osl_close_disk_object(&mood_def);
@@ -487,7 +208,7 @@ out:
 /**
  * Check all moods for syntax errors.
  *
- * \param aca Only ->pbout is used for diagnostics.
+ * \param aca Output goes to ->pbout, errors to ->fd on the error band.
  *
  * \return Negative on fatal errors. Inconsistent mood definitions are not
  * considered an error.
@@ -495,27 +216,67 @@ out:
 int mood_check_callback(struct afs_callback_arg *aca)
 {
        para_printf(&aca->pbout, "checking moods...\n");
-       return osl(osl_rbtree_loop(moods_table, BLOBCOL_ID, &aca->pbout,
-               check_mood));
+       return osl(osl_rbtree_loop(moods_table, BLOBCOL_ID, aca, check_mood));
 }
 
-static int64_t normalized_value(int64_t x, int64_t n, int64_t sum, int64_t qd)
-{
-       if (!n || !qd)
-               return 0;
-       return 100 * (n * x - sum) / (int64_t)int_sqrt(n) / (int64_t)int_sqrt(qd);
-}
-
-static long compute_score(struct afs_info *afsi, long mood_score)
+/*
+ * The normalized num_played and last_played values are defined as
+ *
+ *     nn := -(np - mean_n) / sigma_n and nl := -(lp - mean_l) / sigma_l
+ *
+ *  For a (hypothetical) file with np = 0 and lp = now we thus have
+ *
+ *     nn =  mean_n / sigma_n =: hn > 0
+ *     nl = -(now - mean_l) / sigma_l =: hl < 0
+ *
+ * We design the score function so that both contributions get the same
+ * weight. Define the np and lp score of an arbitrary file as
+ *
+ *     sn := nn * -hl and sl := nl * hn
+ *
+ * Example:
+ *     num_played mean/sigma: 87/14
+ *     last_played mean/sigma: 45/32 days
+ *
+ *     We have hn = 87 / 14 = 6.21 and hl = -45 / 32 = -1.41. Multiplying
+ *     nn of every file with the correction factor 1.41 and nl with
+ *     6.21 makes the weight of the two contributions equal.
+ *
+ * The total score s := sn + sl has the representation
+ *
+ *     s = -cn * (np - mean_n) - cl * (lp - mean_l)
+ *
+ * with positive correction factors
+ *
+ *     cn = (now - mean_l) / (sqrt(ql) * sqrt(qn) / n)
+ *     cl = mean_n / (sqrt(ql) * sqrt(qn) / n)
+ *
+ * where ql and qn are the quadratic deviations stored in the statistics
+ * structure and n is the number of admissible files. To avoid integer
+ * overflows and rounding errors we store the common divisor of the
+ * correction factors separately.
+ */
+static long compute_score(struct afs_info *afsi,
+               const struct afs_statistics *stats)
 {
-       mood_score -= normalized_value(afsi->num_played, statistics.num,
-               statistics.num_played_sum, statistics.num_played_qd);
-       mood_score -= normalized_value(afsi->last_played, statistics.num,
-               statistics.last_played_sum, statistics.last_played_qd);
-       return mood_score / 3;
+       int64_t mean_n, mean_l,score_n, score_l;
+
+       assert(stats->normalization_divisor > 0);
+       assert(stats->num > 0);
+       mean_n = stats->num_played_sum / stats->num;
+       mean_l = stats->last_played_sum / stats->num;
+
+       score_n = -((int64_t)afsi->num_played - mean_n)
+               * stats->num_played_correction
+               / stats->normalization_divisor;
+       score_l = -((int64_t)afsi->last_played - mean_l)
+               * stats->last_played_correction
+               / stats->normalization_divisor;
+       return (score_n + score_l) / 2;
 }
 
-static int add_afs_statistics(const struct osl_row *row)
+static int add_afs_statistics(const struct osl_row *row,
+               struct afs_statistics *stats)
 {
        uint64_t n, x, s, q;
        struct afs_info afsi;
@@ -524,120 +285,100 @@ static int add_afs_statistics(const struct osl_row *row)
        ret = get_afsi_of_row(row, &afsi);
        if (ret < 0)
                return ret;
-       n = statistics.num;
+       n = stats->num;
        x = afsi.last_played;
-       s = statistics.last_played_sum;
+       s = stats->last_played_sum;
        if (n > 0) {
                q = (x > s / n)? x - s / n : s / n - x;
-               statistics.last_played_qd += q * q * n / (n + 1);
+               stats->last_played_qd += q * q * n / (n + 1);
        }
-       statistics.last_played_sum += x;
+       stats->last_played_sum += x;
 
        x = afsi.num_played;
-       s = statistics.num_played_sum;
+       s = stats->num_played_sum;
        if (n > 0) {
                q = (x > s / n)? x - s / n : s / n - x;
-               statistics.num_played_qd += q * q * n / (n + 1);
+               stats->num_played_qd += q * q * n / (n + 1);
        }
-       statistics.num_played_sum += x;
-       statistics.num++;
+       stats->num_played_sum += x;
+       stats->num++;
        return 1;
 }
 
 static int del_afs_statistics(const struct osl_row *row)
 {
+       struct afs_statistics *stats = &current_mood->stats;
        uint64_t n, s, q, a, new_s;
        struct afs_info afsi;
        int ret;
        ret = get_afsi_of_row(row, &afsi);
        if (ret < 0)
                return ret;
-       n = statistics.num;
+       n = stats->num;
        assert(n);
        if (n == 1) {
-               memset(&statistics, 0, sizeof(statistics));
+               memset(stats, 0, sizeof(*stats));
+               stats->normalization_divisor = 1;
                return 1;
        }
 
-       s = statistics.last_played_sum;
-       q = statistics.last_played_qd;
+       s = stats->last_played_sum;
+       q = stats->last_played_qd;
        a = afsi.last_played;
        new_s = s - a;
-       statistics.last_played_sum = new_s;
-       statistics.last_played_qd = q + s * s / n - a * a
+       stats->last_played_sum = new_s;
+       stats->last_played_qd = q + s * s / n - a * a
                - new_s * new_s / (n - 1);
 
-       s = statistics.num_played_sum;
-       q = statistics.num_played_qd;
+       s = stats->num_played_sum;
+       q = stats->num_played_qd;
        a = afsi.num_played;
        new_s = s - a;
-       statistics.num_played_sum = new_s;
-       statistics.num_played_qd = q + s * s / n - a * a
+       stats->num_played_sum = new_s;
+       stats->num_played_qd = q + s * s / n - a * a
                - new_s * new_s / (n - 1);
 
-       statistics.num--;
+       stats->num--;
        return 1;
 }
 
 /*
- * At mood open time we determine the set of admissible files for the given
- * mood. The mood score of each admissible file is computed by adding up all
- * mood item scores. Next, we update the afs statistics and append a struct
- * admissible_file_info to a temporary array.
- *
- * When all files have been processed in this way, the final score of each
- * admissible file is computed by adding the dynamic score (which depends on
- * the afs_statistics and the current time) to the mood score. Finally, all
- * audio files in the temporary array are added to the score table and the
- * array is freed.
+ * At mood load time we determine the set of admissible files for the given
+ * mood where each file is identified by a pointer to a row of the audio file
+ * table. In the first pass the pointers are added to a temporary array and
+ * statistics are computed. When all admissible files have been processed in
+ * this way, the score of each admissible file is computed and the (row, score)
+ * pair is added to the score table. This has to be done in a second pass
+ * since the score depends on the statistics. Finally, the array is freed.
  */
-struct admissible_file_info
-{
-       /** The admissible audio file. */
-       struct osl_row *aft_row;
-       /** Its score. */
-       long score;
-};
-
-/** The temporary array of admissible files. */
 struct admissible_array {
        /** Files are admissible wrt. this mood. */
-       struct mood *m;
+       struct mood_instance *m;
        /** The size of the array */
        unsigned size;
        /** Pointer to the array of admissible files. */
-       struct admissible_file_info *array;
+       struct osl_row **array;
 };
 
-/**
- * Add an entry to the array of admissible files.
- *
- * \param aft_row The audio file to be added.
- * \param private_data Pointer to a struct admissible_file_info.
- *
- * \return 1 if row admissible, 0 if not, negative on errors.
+/*
+ * Check whether the given audio file is admissible. If it is, add it to array
+ * of admissible files.
  */
 static int add_if_admissible(struct osl_row *aft_row, void *data)
 {
        struct admissible_array *aa = data;
-       int ret;
-       long score = 0;
+       struct afs_statistics *stats = &aa->m->stats;
 
-       ret = row_is_admissible(aft_row, aa->m, &score);
-       if (ret <= 0)
-               return ret;
-       if (statistics.num >= aa->size) {
+       if (!mp_eval_row(aft_row, aa->m->parser_context))
+               return 0;
+       if (stats->num >= aa->size) {
                aa->size *= 2;
                aa->size += 100;
-               aa->array = para_realloc(aa->array,
-                       aa->size * sizeof(struct admissible_file_info));
+               aa->array = arr_realloc(aa->array, aa->size,
+                       sizeof(struct osl_row *));
        }
-       aa->array[statistics.num].aft_row = aft_row;
-       aa->array[statistics.num].score = score;
-       ret = add_afs_statistics(aft_row);
-       if (ret < 0)
-               return ret;
-       return 1;
+       aa->array[stats->num] = aft_row;
+       return add_afs_statistics(aft_row, stats);
 }
 
 /**
@@ -682,29 +423,25 @@ _static_inline_ int64_t update_quadratic_deviation(int64_t n, int64_t old_qd,
        return old_qd + delta * (sigma - 2 * old_sum / n - delta / n);
 }
 
-static int update_afs_statistics(struct afs_info *old_afsi,
+static void update_afs_statistics(struct afs_info *old_afsi,
                struct afs_info *new_afsi)
 {
-       unsigned n;
-       int ret = get_num_admissible_files(&n);
-
-       if (ret < 0)
-               return ret;
-       assert(n);
-
-       statistics.last_played_qd = update_quadratic_deviation(n,
-               statistics.last_played_qd, old_afsi->last_played,
-               new_afsi->last_played, statistics.last_played_sum);
-       statistics.last_played_sum += new_afsi->last_played - old_afsi->last_played;
-
-       statistics.num_played_qd = update_quadratic_deviation(n,
-               statistics.num_played_qd, old_afsi->num_played,
-               new_afsi->num_played, statistics.num_played_sum);
-       statistics.num_played_sum += new_afsi->num_played - old_afsi->num_played;
-       return 1;
+       struct afs_statistics *stats = &current_mood->stats;
+
+       assert(stats->num > 0);
+       stats->last_played_qd = update_quadratic_deviation(stats->num,
+               stats->last_played_qd, old_afsi->last_played,
+               new_afsi->last_played, stats->last_played_sum);
+       stats->last_played_sum += new_afsi->last_played - old_afsi->last_played;
+
+       stats->num_played_qd = update_quadratic_deviation(stats->num,
+               stats->num_played_qd, old_afsi->num_played,
+               new_afsi->num_played, stats->num_played_sum);
+       stats->num_played_sum += new_afsi->num_played - old_afsi->num_played;
 }
 
-static int add_to_score_table(const struct osl_row *aft_row, long mood_score)
+static int add_to_score_table(const struct osl_row *aft_row,
+               struct mood_instance *m)
 {
        long score;
        struct afs_info afsi;
@@ -712,8 +449,8 @@ static int add_to_score_table(const struct osl_row *aft_row, long mood_score)
 
        if (ret < 0)
                return ret;
-       score = compute_score(&afsi, mood_score);
-       return score_add(aft_row, score);
+       score = compute_score(&afsi, &m->stats);
+       return score_add(aft_row, score, m->score_table);
 }
 
 static int delete_from_statistics_and_score_table(const struct osl_row *aft_row)
@@ -725,23 +462,18 @@ static int delete_from_statistics_and_score_table(const struct osl_row *aft_row)
 }
 
 /**
- * Delete one entry from the statistics and from the score table.
+ * Delete an audio file from the score table and update mood statistics.
  *
- * \param aft_row The audio file which is no longer admissible.
+ * \param aft_row Identifies the row to delete.
  *
- * \return Positive on success, negative on errors.
+ * \return Standard.
  *
  * \sa \ref score_delete().
  */
 static int mood_delete_audio_file(const struct osl_row *aft_row)
 {
-       int ret;
-
-       ret = row_belongs_to_score_table(aft_row, NULL);
-       if (ret < 0)
-               return ret;
-       if (!ret) /* not admissible, nothing to do */
-               return 1;
+       if (!row_belongs_to_score_table(aft_row))
+               return 0;
        return delete_from_statistics_and_score_table(aft_row);
 }
 
@@ -760,189 +492,225 @@ static int mood_update_audio_file(const struct osl_row *aft_row,
                struct afs_info *old_afsi)
 {
        long score, percent;
-       int ret, is_admissible, was_admissible = 0;
+       int ret;
+       bool is_admissible, was_admissible;
        struct afs_info afsi;
-       unsigned rank;
 
        if (!current_mood)
                return 1; /* nothing to do */
-       ret = row_belongs_to_score_table(aft_row, &rank);
-       if (ret < 0)
-               return ret;
-       was_admissible = ret;
-       ret = row_is_admissible(aft_row, current_mood, &score);
-       if (ret < 0)
-               return ret;
-       is_admissible = (ret > 0);
+       was_admissible = row_belongs_to_score_table(aft_row);
+       is_admissible = mp_eval_row(aft_row, current_mood->parser_context);
        if (!was_admissible && !is_admissible)
                return 1;
        if (was_admissible && !is_admissible)
                return delete_from_statistics_and_score_table(aft_row);
        if (!was_admissible && is_admissible) {
-               ret = add_afs_statistics(aft_row);
+               ret = add_afs_statistics(aft_row, &current_mood->stats);
                if (ret < 0)
                        return ret;
-               return add_to_score_table(aft_row, score);
+               return add_to_score_table(aft_row, current_mood);
        }
        /* update score */
        ret = get_afsi_of_row(aft_row, &afsi);
        if (ret < 0)
                return ret;
-       if (old_afsi) {
-               ret = update_afs_statistics(old_afsi, &afsi);
-               if (ret < 0)
-                       return ret;
-       }
-       score = compute_score(&afsi, score);
+       if (old_afsi)
+               update_afs_statistics(old_afsi, &afsi);
+       score = compute_score(&afsi, &current_mood->stats);
        PARA_DEBUG_LOG("score: %li\n", score);
        percent = (score + 100) / 3;
        if (percent > 100)
                percent = 100;
        else if (percent < 0)
                percent = 0;
-       PARA_DEBUG_LOG("moving from rank %u to %li%%\n", rank, percent);
+       PARA_DEBUG_LOG("moving to %li%%\n", percent);
        return score_update(aft_row, percent);
 }
 
-static void log_statistics(void)
+/* sse: seconds since epoch. */
+static char *get_statistics(struct mood_instance *m, int64_t sse)
 {
-       unsigned n = statistics.num;
+       unsigned n = m->stats.num;
        int mean_days, sigma_days;
-       /*
-        * We can not use the "now" pointer from sched.c here because we are
-        * called before schedule(), which initializes "now".
-        */
-       struct timeval rnow;
 
-       assert(current_mood);
-       PARA_NOTICE_LOG("loaded mood %s\n", current_mood->name?
-               current_mood->name : "(dummy)");
-       if (!n) {
-               PARA_WARNING_LOG("no admissible files\n");
-               return;
-       }
-       PARA_NOTICE_LOG("%u admissible files\n", statistics.num);
-       clock_get_realtime(&rnow);
-       mean_days = (rnow.tv_sec - statistics.last_played_sum / n) / 3600 / 24;
-       sigma_days = int_sqrt(statistics.last_played_qd / n) / 3600 / 24;
-       PARA_NOTICE_LOG("last_played mean/sigma: %d/%d days\n", mean_days, sigma_days);
-       PARA_NOTICE_LOG("num_played mean/sigma: %llu/%llu\n",
-               (long long unsigned)statistics.num_played_sum / n,
-               (long long unsigned)int_sqrt(statistics.num_played_qd / n));
+       if (n == 0)
+               return make_message("no admissible files\n");
+       mean_days = (sse - m->stats.last_played_sum / n) / 3600 / 24;
+       sigma_days = int_sqrt(m->stats.last_played_qd / n) / 3600 / 24;
+       return make_message(
+               "loaded mood %s (%u files)\n"
+               "last_played mean/sigma: %d/%d days\n"
+               "num_played mean/sigma: %" PRId64 "/%" PRIu64 "\n"
+               "correction factor ratio: %.2lf\n"
+       ,
+               m->name? m->name : "(dummy)",
+               n,
+               mean_days, sigma_days,
+               m->stats.num_played_sum / n,
+               int_sqrt(m->stats.num_played_qd / n),
+               86400.0 * m->stats.last_played_correction /
+                       m->stats.num_played_correction
+       );
 }
 
 /**
- * Close the current mood.
+ * Free all resources of a mood instance.
+ *
+ * \param m As obtained by \ref mood_load(). If NULL, unload the current mood.
  *
- * Frees all resources of the current mood.
+ * It's OK to call this with m == NULL even if no current mood is loaded.
  */
-void close_current_mood(void)
+void mood_unload(struct mood_instance *m)
 {
+       if (m)
+               return destroy_mood(m);
        destroy_mood(current_mood);
        current_mood = NULL;
-       memset(&statistics, 0, sizeof(statistics));
+}
+
+static void compute_correction_factors(int64_t sse, struct afs_statistics *s)
+{
+       if (s->num > 0) {
+               s->normalization_divisor = int_sqrt(s->last_played_qd)
+                       * int_sqrt(s->num_played_qd) / s->num / 100;
+               s->num_played_correction = sse - s->last_played_sum / s->num;
+               s->last_played_correction = s->num_played_sum / s->num;
+       }
+       if (s->num_played_correction == 0)
+               s->num_played_correction = 1;
+       if (s->normalization_divisor == 0)
+               s->normalization_divisor = 1;
+       if (s->last_played_correction == 0)
+               s->last_played_correction = 1;
 }
 
 /**
- * Change the current mood.
+ * Populate a score table with admissible files for the given mood.
+ *
+ * This consults the mood table to initialize the mood parser with the mood
+ * expression stored in the blob object which corresponds to the given name. A
+ * score table is allocated and populated with references to those entries of
+ * the audio file table which evaluate as admissible with respect to the mood
+ * expression. For each audio file a score value is computed and stored along
+ * with the file reference.
+ *
+ * \param mood_name The name of the mood to load.
+ * \param result Opaque, refers to the mood parser and the score table.
+ * \param msg Error message or mood info is returned here.
  *
- * \param mood_name The name of the mood to open.
- * \param errmsg Error description is returned here.
+ * If the mood name is NULL, the dummy mood is loaded. This mood regards every
+ * audio file as admissible.
  *
- * If \a mood_name is \a NULL, load the dummy mood that accepts every audio file
- * and uses a scoring method based only on the \a last_played information.
+ * A NULL result pointer instructs the function to operate on the current mood.
+ * That is, on the mood instance which is used by the server to select the next
+ * audio file for streaming. In this mode of operation, the mood which was
+ * active before the call, if any, is unloaded on success.
  *
- * The errmsg pointer may be NULL, in which case no error message will be
- * returned. If a non-NULL pointer is given, the caller must free *errmsg.
+ * If result is not NULL, the current mood is unaffected and *result points to
+ * an initialized mood instance on success. The caller can pass this reference
+ * to \ref mood_loop() to iterate over the admissible files, and should call
+ * \ref mood_unload() to free the mood instance afterwards.
  *
- * If there is already an open mood, it will be closed first.
+ * If the message pointer is not NULL, a suitable message is returned there in
+ * all cases. The caller must free this string.
  *
- * \return Positive on success, negative on errors. Loading the dummy mood
- * always succeeds.
+ * \return The number of admissible files on success, negative on errors. On
+ * errors, the current mood remains unaffected even if result is NULL. It is
+ * not considered an error if no files are admissible.
  *
- * \sa struct \ref afs_info::last_played, \ref mp_eval_row().
+ * \sa \ref mp_eval_row().
  */
-int change_current_mood(const char *mood_name, char **errmsg)
+int mood_load(const char *mood_name, struct mood_instance **result, char **msg)
 {
        int i, ret;
-       struct admissible_array aa = {
-               .size = 0,
-               .array = NULL
-       };
+       struct admissible_array aa = {.size = 0};
+       /*
+        * We can not use the "now" pointer from sched.c here because we are
+        * called before schedule(), which initializes "now".
+        */
+       struct timeval rnow;
 
        if (mood_name) {
-               struct mood *m;
-               struct osl_row *row;
-               struct osl_object obj = {
-                       .data = (char *)mood_name,
-                       .size = strlen(mood_name) + 1
-               };
-               ret = osl(osl_get_row(moods_table, BLOBCOL_NAME, &obj, &row));
-               if (ret < 0) {
-                       if (errmsg)
-                               *errmsg = make_message("no such mood: %s",
-                                       mood_name);
-                       return ret;
-               }
-               ret = load_mood(row, &m, errmsg);
+               ret = init_mood_parser(mood_name, &aa.m, msg);
                if (ret < 0)
                        return ret;
-               close_current_mood();
-               current_mood = m;
-       } else { /* load dummy mood */
-               close_current_mood();
-               current_mood = alloc_new_mood(NULL);
-       }
-       aa.m = current_mood;
+       } else /* load dummy mood */
+               aa.m = alloc_new_mood(NULL);
        PARA_NOTICE_LOG("computing statistics of admissible files\n");
        ret = audio_file_loop(&aa, add_if_admissible);
        if (ret < 0) {
-               if (errmsg)
-                       *errmsg = make_message("audio file loop failed");
-               return ret;
+               if (msg) /* false if we are called via the event handler */
+                       *msg = make_message("audio file loop failed\n");
+               goto out;
        }
-       for (i = 0; i < statistics.num; i++) {
-               struct admissible_file_info *a = aa.array + i;
-               ret = add_to_score_table(a->aft_row, a->score);
+       clock_get_realtime(&rnow);
+       compute_correction_factors(rnow.tv_sec, &aa.m->stats);
+       if (result)
+               score_open(&aa.m->score_table);
+       for (i = 0; i < aa.m->stats.num; i++) {
+               ret = add_to_score_table(aa.array[i], aa.m);
                if (ret < 0) {
-                       if (errmsg)
-                               *errmsg = make_message(
-                                       "could not add row to score table");
+                       if (msg)
+                               *msg = make_message(
+                                       "could not add row to score table\n");
                        goto out;
                }
        }
-       log_statistics();
-       ret = statistics.num;
+       /* success */
+       if (msg)
+               *msg = get_statistics(aa.m, rnow.tv_sec);
+       ret = aa.m->stats.num;
+       if (result)
+               *result = aa.m;
+       else {
+               mood_unload(NULL);
+               current_mood = aa.m;
+       }
+       ret = 1;
 out:
        free(aa.array);
+       if (ret <= 0) /* error, or no admissible files */
+               destroy_mood(aa.m);
        return ret;
 }
 
-/*
- * Close and re-open the current mood.
+/**
+ * Iterate over the admissible files of a mood instance.
+ *
+ * This wrapper around \ref score_loop() is the mood counterpart of \ref
+ * playlist_loop().
  *
- * This function is called on events which render the current list of
- * admissible files useless, for example if an attribute is removed from the
- * attribute table.
+ * \param m Determines the score table to iterate. Must not be NULL.
+ * \param func See \ref score_loop().
+ * \param data See \ref score_loop().
  *
- * If no mood is currently open, the function returns success.
+ * \return See \ref score_loop(), \ref playlist_loop().
+ */
+int mood_loop(struct mood_instance *m, osl_rbtree_loop_func *func, void *data)
+{
+       return score_loop(func, m->score_table, data);
+}
+
+/*
+ * Empty the score table and start over.
+ *
+ * This function is called on events which render the current set of admissible
+ * files invalid, for example if an attribute is removed from the attribute
+ * table.
  */
 static int reload_current_mood(void)
 {
        int ret;
        char *mood_name = NULL;
 
-       ret = clear_score_table();
-       if (ret < 0)
-               return ret;
-       if (!current_mood)
-               return 1;
+       assert(current_mood);
+       score_clear();
        PARA_NOTICE_LOG("reloading %s\n", current_mood->name?
                current_mood->name : "(dummy)");
        if (current_mood->name)
                mood_name = para_strdup(current_mood->name);
-       close_current_mood();
-       ret = change_current_mood(mood_name, NULL);
+       mood_unload(NULL);
+       ret = mood_load(mood_name, NULL, NULL);
        free(mood_name);
        return ret;
 }
@@ -954,9 +722,17 @@ static int reload_current_mood(void)
  * \param pb Unused.
  * \param data Its type depends on the event.
  *
- * This function performs actions required due to the occurrence of the given
- * event. Possible actions include reload of the current mood and update of the
- * score of an audio file.
+ * This function updates the score table according to the event that has
+ * occurred. Two actions are possible: (a) reload the current mood, or (b)
+ * add/remove/update the row of the score table which corresponds to the audio
+ * file that has been modified or whose afs info has been changed. It depends
+ * on the type of the event which action (if any) is performed.
+ *
+ * The callbacks of command handlers such as com_add() or com_touch() which
+ * modify the audio file table call this function. The virtual streaming system
+ * also calls this after it has updated the afs info of the file it is about to
+ * stream (the one with the highest score). If the file stays admissible, its
+ * score is recomputed so that a different file is picked next time.
  *
  * \return Standard.
  */
diff --git a/mood.h b/mood.h
deleted file mode 100644 (file)
index fcfe1ef..0000000
--- a/mood.h
+++ /dev/null
@@ -1,7 +0,0 @@
-/* Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
-
-/** \file mood.h Public functions of mood.c. */
-
-int change_current_mood(const char *mood_name, char **errmsg);
-void close_current_mood(void);
-int mood_check_callback(struct afs_callback_arg *aca);
diff --git a/mp.c b/mp.c
index bade05bcf866103b5bd794af4ae1ae6ce8cb5b72..a068b043faf3c09b72befde39771058108f1aec9 100644 (file)
--- a/mp.c
+++ b/mp.c
@@ -90,7 +90,7 @@ unsigned parse_quoted_string(const char *src, const char quote_chars[2],
 
        assert(len >= 2);
        assert(src[0] == quote_chars[0]);
-       p = dst = para_malloc(len - 1);
+       p = dst = alloc(len - 1);
        backslash = false;
        for (n = 1;; n++) {
                char c;
@@ -389,6 +389,27 @@ MP_AFHI(frequency)
 MP_AFHI(channels)
 /** \endcond */
 
+/**
+ * Return the duration of the audio file from the afh info structure.
+ *
+ * \param ctx See \ref mp_path().
+ *
+ * The duration is computed by multiplying the number of chunks and the
+ * duration of one chunk.
+ *
+ * \return The approximate number of milliseconds.
+ */
+int64_t mp_duration(struct mp_context *ctx)
+{
+       struct timeval tmp;
+       int ret = get_afhi(ctx);
+
+       if (ret < 0)
+               return 0;
+       tv_scale(ctx->afhi.chunks_total, &ctx->afhi.chunk_tv, &tmp);
+       return tv2ms(&tmp);
+}
+
 /**
  * Define a function which extracts and returns the value of a meta tag.
  *
@@ -491,13 +512,13 @@ int mp_init(const char *definition, int nbytes, struct mp_context **result,
        struct mp_context *ctx;
        struct yy_buffer_state *buffer_state;
 
+       *result = NULL;
        if (!definition || nbytes == 0) { /* dummy mood */
                if (errmsg)
                        *errmsg = NULL;
-               *result = NULL;
                return 0;
        }
-       ctx = para_calloc(sizeof(*ctx));
+       ctx = zalloc(sizeof(*ctx));
        ctx->errmsg = NULL;
        ctx->ast = NULL;
 
@@ -510,6 +531,7 @@ int mp_init(const char *definition, int nbytes, struct mp_context **result,
        mp_yy_delete_buffer(buffer_state, scanner);
        mp_yylex_destroy(scanner);
        if (ctx->errmsg) { /* parse error */
+               mp_free_ast(ctx->ast);
                if (errmsg)
                        *errmsg = ctx->errmsg;
                else
@@ -535,12 +557,14 @@ int mp_init(const char *definition, int nbytes, struct mp_context **result,
  * function returns true (without looking at the audio file metadata) to
  * indicate that the given audio file should be considered admissible.
  *
- * \sa \ref change_current_mood(), \ref mp_eval_ast().
+ * \sa \ref mood_load(), \ref mp_eval_ast().
  */
 bool mp_eval_row(const struct osl_row *aft_row, struct mp_context *ctx)
 {
        if (!ctx) /* dummy mood */
                return true;
+       if (!ctx->ast) /* empty mood */
+               return true;
        assert(aft_row);
        ctx->aft_row = aft_row;
        ctx->have_afsi = false;
diff --git a/mp.h b/mp.h
index febbe3240f49ba81b08ca2168b10dd1189fc3f54..891bfb054e2f4cf1a922e7434a8467808f2e474b 100644 (file)
--- a/mp.h
+++ b/mp.h
@@ -139,6 +139,7 @@ bool mp_is_set(const char *attr, struct mp_context *ctx);
 char *mp_path(struct mp_context *ctx);
 int64_t mp_year(struct mp_context *ctx);
 int64_t mp_num_attributes_set(struct mp_context *ctx);
+int64_t mp_duration(struct mp_context *ctx);
 
 /* Generated with MP_AFSI() */
 /** \cond MP_AFSI */
index f279cd5752ad8be4798ff5c0fc3fecd9c212b67a..56f28a09d8eb9fd18a9ba2262f14a58c6cec7997 100644 (file)
--- a/mp3_afh.c
+++ b/mp3_afh.c
@@ -37,9 +37,6 @@ struct mp3header {
        unsigned int freq;
        unsigned int padding;
        unsigned int mode;
-       unsigned int copyright;
-       unsigned int original;
-       unsigned int emphasis;
 };
 
 static const int frequencies[3][4] = {
@@ -348,7 +345,7 @@ static int mp3_rewrite_tags(const char *map, size_t mapsize,
        if (ret < 0)
                goto out;
        new_v2size = id3_tag_render(v2_tag, NULL);
-       v2_buffer = para_malloc(new_v2size);
+       v2_buffer = alloc(new_v2size);
        id3_tag_render(v2_tag, v2_buffer);
        PARA_INFO_LOG("writing v2 tag (%lu bytes)\n", new_v2size);
        ret = write_all(fd, (char *)v2_buffer, new_v2size);
@@ -473,7 +470,7 @@ static int frame_length(struct mp3header *header)
                + header->padding;
 }
 
-static int compare_headers(struct mp3header *h1,struct mp3header *h2)
+static int compare_headers(struct mp3header *h1, struct mp3header *h2)
 {
        if ((*(unsigned int*)h1) == (*(unsigned int*)h2))
                return 1;
@@ -481,10 +478,7 @@ static int compare_headers(struct mp3header *h1,struct mp3header *h2)
                        (h1->layer == h2->layer) &&
                        (h1->crc == h2->crc) &&
                        (h1->freq == h2->freq) &&
-                       (h1->mode == h2->mode) &&
-                       (h1->copyright == h2->copyright) &&
-                       (h1->original == h2->original) &&
-                       (h1->emphasis == h2->emphasis))
+                       (h1->mode == h2->mode))
                return 1;
        return 0;
 }
@@ -598,7 +592,7 @@ static int mp3_read_info(unsigned char *map, size_t numbytes, int fd,
        const char *tag_versions[] = {"no", "id3v1", "id3v2", "id3v1+id3v2"};
 
        afhi->chunks_total = 0;
-       afhi->chunk_table = para_malloc(chunk_table_size * sizeof(uint32_t));
+       afhi->chunk_table = arr_alloc(chunk_table_size, sizeof(uint32_t));
        while (1) {
                int freq, br;
                struct timeval tmp, cct; /* current chunk time */
@@ -633,8 +627,8 @@ static int mp3_read_info(unsigned char *map, size_t numbytes, int fd,
                total_time = tmp;
                if (afhi->chunks_total >= chunk_table_size) {
                        chunk_table_size *= 2;
-                       afhi->chunk_table = para_realloc(afhi->chunk_table,
-                               chunk_table_size * sizeof(uint32_t));
+                       afhi->chunk_table = arr_realloc(afhi->chunk_table,
+                               chunk_table_size, sizeof(uint32_t));
                }
                afhi->chunk_table[afhi->chunks_total] = fpos;
                afhi->chunks_total++;
@@ -675,7 +669,7 @@ static int mp3_get_file_info(char *map, size_t numbytes, int fd,
        ret = mp3_read_info((unsigned char *)map, numbytes, fd, afhi);
        if (ret < 0)
                return ret;
-       if (afhi->seconds_total < 2 || !afhi->chunks_total)
+       if (afhi->chunks_total == 0)
                return -E_MP3_INFO;
        return 1;
 }
index ccb1553b820357e13f2cafe2c0e8a7e0502ac8fd..d40df85edbe24b9dc54f01fc9305a1575c022507 100644 (file)
@@ -73,7 +73,7 @@ static void mp3dec_close(struct filter_node *fn)
 
 #define MP3DEC_MAX_FRAME 8192
 
-static int mp3dec_post_select(__a_unused struct sched *s, void *context)
+static int mp3dec_post_monitor(__a_unused struct sched *s, void *context)
 {
        struct filter_node *fn = context;
        int i, ret;
@@ -93,7 +93,7 @@ next_buffer:
        btr_merge(btrn, fn->min_iqs);
        len = btr_next_buffer(btrn, &inbuffer);
        /*
-        * Decode at most 8K in one go to give the post_select() functions of
+        * Decode at most 8K in one go to give the post_monitor() functions of
         * other buffer tree nodes a chance to run. This is necessary to avoid
         * buffer underruns on slow machines.
         */
@@ -105,7 +105,7 @@ next_frame:
                mp3dec_consume(btrn, &pmd->stream, len);
                if (pmd->stream.error == MAD_ERROR_BUFLEN) {
                        if (len == iqs && btr_no_parent(btrn)) {
-                               ret = -E_MP3DEC_EOF;
+                               ret = -E_EOF;
                                goto err;
                        }
                        fn->min_iqs += 100;
@@ -127,7 +127,7 @@ decode:
                        goto err;
                mad_stream_sync(&pmd->stream);
                if (pmd->stream.error == MAD_ERROR_BUFLEN) {
-                       ret = -E_MP3DEC_EOF;
+                       ret = -E_EOF;
                        if (len == iqs && btr_no_parent(btrn))
                                goto err;
                        fn->min_iqs += 100;
@@ -144,7 +144,7 @@ decode:
        }
        fn->min_iqs = 0;
        mad_synth_frame(&pmd->synth, &pmd->frame);
-       outbuffer = para_malloc(pmd->synth.pcm.length * 2 * pmd->channels);
+       outbuffer = arr_alloc(pmd->synth.pcm.length, 2 * pmd->channels);
        loaded = 0;
        for (i = 0; i < pmd->synth.pcm.length; i++) {
                int sample = MAD_TO_SHORT(pmd->synth.pcm.samples[0][i]);
@@ -166,7 +166,7 @@ err:
 
 static void mp3dec_open(struct filter_node *fn)
 {
-       struct private_mp3dec_data *pmd = para_calloc(sizeof(*pmd));
+       struct private_mp3dec_data *pmd = zalloc(sizeof(*pmd));
 
        fn->private_data = pmd;
        mad_stream_init(&pmd->stream);
@@ -187,7 +187,7 @@ static int mp3dec_execute(struct btr_node *btrn, const char *cmd, char **result)
 const struct filter lsg_filter_cmd_com_mp3dec_user_data = {
        .open = mp3dec_open,
        .close = mp3dec_close,
-       .pre_select = generic_filter_pre_select,
-       .post_select = mp3dec_post_select,
+       .pre_monitor = generic_filter_pre_monitor,
+       .post_monitor = mp3dec_post_monitor,
        .execute = mp3dec_execute,
 };
diff --git a/mp4.c b/mp4.c
new file mode 100644 (file)
index 0000000..5ca1307
--- /dev/null
+++ b/mp4.c
@@ -0,0 +1,1053 @@
+/*
+ * Copyright (C) 2003-2005 M. Bakker, Nero AG, http://www.nero.com
+ * FAAD2 - Freeware Advanced Audio (AAC) Decoder including SBR decoding
+ *
+ * See file COPYING.
+ */
+
+/** \file mp4.c Paraslash's internal mp4 parser. */
+
+/*
+ * This is a stripped down version of the former mp4ff library which used to be
+ * part of the faad decoder project but was removed from the faad code base in
+ * 2017. The original code has been cleaned up substantially and the public API
+ * has been documented. See the git commit log for details.
+ */
+
+#include <regex.h>
+
+#include "para.h"
+#include "error.h"
+#include "portable_io.h"
+#include "string.h"
+#include "mp4.h"
+
+/**
+ * The three states of the mp4 parser. The parser only loads the audio specific
+ * values and tables when it is in the second state.
+ */
+enum audio_track_state {
+       /** We haven't encountered an mp4a atom so far. */
+       ATS_INITIAL,
+       /** We have seen an mp4a atom but no subsequent trak atom yet. */
+       ATS_SEEN_MP4A,
+       /** A trak atom was seen *after* the mp4a atom. */
+       ATS_TRACK_CHANGE,
+};
+
+struct mp4_track {
+       /* determines which atoms we still need to parse. */
+       enum audio_track_state state;
+
+       /* mp4a */
+       uint16_t channel_count;
+       uint16_t sample_rate;
+
+       /* stsz */
+       uint32_t stsz_sample_size;
+       uint32_t stsz_sample_count;
+       uint32_t *stsz_table;
+
+       /* stts */
+       uint32_t stts_entry_count;
+       uint32_t *stts_sample_count;
+
+       /* stsc */
+       uint32_t stsc_entry_count;
+       uint32_t *stsc_first_chunk;
+       uint32_t *stsc_samples_per_chunk;
+
+       /* stsc */
+       uint32_t stco_entry_count;
+       uint32_t *stco_chunk_offset;
+
+       /* mdhd */
+       uint32_t time_scale;
+       uint64_t duration;
+};
+
+struct mp4 {
+       const struct mp4_callback *cb;
+
+       uint64_t moov_offset;
+       uint64_t moov_size;
+       uint64_t meta_offset;
+       uint32_t meta_size;
+       uint64_t ilst_offset;
+       uint32_t ilst_size;
+       uint64_t udta_offset;
+       uint32_t udta_size;
+
+       uint8_t last_atom;
+       struct mp4_track track;
+       struct mp4_metadata meta;
+};
+
+/*
+ * Returns -E_MP4_READ, 0, or 1 on errors/EOF/success. Partial reads followed
+ * by EOF or read errors are treated as errors.
+ */
+static int read_data(struct mp4 *f, void *data, size_t size)
+{
+       while (size > 0) {
+               ssize_t ret = f->cb->read(f->cb->user_data, data, size);
+               if (ret < 0 && errno == EINTR)
+                       continue;
+               /* regard EAGAIN as an error as reads should be blocking. */
+               if (ret <= 0)
+                       return ret < 0? -E_MP4_READ : 0;
+               size -= ret;
+       }
+       return 1;
+}
+
+static int read_int64(struct mp4 *f, uint64_t *result)
+{
+       uint8_t data[8];
+       int ret = read_data(f, data, 8);
+
+       if (ret > 0)
+               *result = read_u64_be(data);
+       return ret;
+}
+
+static int read_int32(struct mp4 *f, uint32_t *result)
+{
+       uint8_t data[4];
+       int ret = read_data(f, data, 4);
+
+       if (ret > 0)
+               *result = read_u32_be(data);
+       return ret;
+}
+
+static int read_int16(struct mp4 *f, uint16_t *result)
+{
+       uint8_t data[2];
+       int ret = read_data(f, data, 2);
+
+       if (ret > 0)
+               *result = read_u16_be(data);
+       return ret;
+}
+
+/** A macro defining the atoms we care about. It gets expanded twice. */
+#define ATOM_ITEMS \
+       ATOM_ITEM(MOOV, 'm', 'o', 'o', 'v') /* movie (top-level container) */ \
+       ATOM_ITEM(TRAK, 't', 'r', 'a', 'k') /* container for a single track */ \
+       ATOM_ITEM(MDIA, 'm', 'd', 'i', 'a') /* media information */ \
+       ATOM_ITEM(MINF, 'm', 'i', 'n', 'f') /* extends mdia */ \
+       ATOM_ITEM(STBL, 's', 't', 'b', 'l') /* sample table container */ \
+       ATOM_ITEM(UDTA, 'u', 'd', 't', 'a') /* user data */ \
+       ATOM_ITEM(ILST, 'i', 'l', 's', 't') /* iTunes Metadata list */ \
+       ATOM_ITEM(ARTIST, 0xa9, 'A', 'R', 'T') /* artist */ \
+       ATOM_ITEM(TITLE, 0xa9, 'n', 'a', 'm') /* title */ \
+       ATOM_ITEM(ALBUM, 0xa9, 'a', 'l', 'b') /* album */ \
+       ATOM_ITEM(DATE, 0xa9, 'd', 'a', 'y') /* date */ \
+       ATOM_ITEM(COMMENT, 0xa9, 'c', 'm', 't') /* comment */ \
+       ATOM_ITEM(MDHD, 'm', 'd', 'h', 'd') /* track header */ \
+       ATOM_ITEM(STSD, 's', 't', 's', 'd') /* sample description box */ \
+       ATOM_ITEM(STTS, 's', 't', 't', 's') /* time to sample box */ \
+       ATOM_ITEM(STSZ, 's', 't', 's', 'z') /* sample size box */ \
+       ATOM_ITEM(STCO, 's', 't', 'c', 'o') /* chunk offset box */ \
+       ATOM_ITEM(STSC, 's', 't', 's', 'c') /* sample to chunk box */ \
+       ATOM_ITEM(MP4A, 'm', 'p', '4', 'a') /* mp4 audio */ \
+       ATOM_ITEM(META, 'm', 'e', 't', 'a') /* iTunes Metadata box */ \
+       ATOM_ITEM(DATA, 'd', 'a', 't', 'a') /* iTunes Metadata data box */ \
+
+/** For the C enumeration we concatenate ATOM_ with the first argument. */
+#define ATOM_ITEM(_name, a, b, c, d) ATOM_ ## _name,
+/** The enumeration of interesting atoms. */
+enum atom {ATOM_ITEMS};
+#undef ATOM_ITEM
+
+/** A cpp version of read_u32_be(). */
+#define ATOM_VALUE(a, b, c, d) ((a << 24) + (b << 16) + (c << 8) + d)
+
+static uint8_t atom_name_to_type(uint8_t *p)
+{
+       /** Expands to an instance of the following unnamed structure. */
+       #define ATOM_ITEM(_name, a, b, c, d) \
+               {.name = # _name, .val = ATOM_VALUE(a, b, c, d)},
+       static const struct {
+               const char *name;
+               uint32_t val;
+       } atom_table[] = {ATOM_ITEMS};
+       #undef ATOM_ITEM
+       uint32_t val = read_u32_be(p);
+
+       for (uint8_t n = 0; n < ARRAY_SIZE(atom_table); n++)
+               if (val == atom_table[n].val)
+                       return n;
+       return 255;
+}
+
+/* read atom header, atom size is returned with header included. */
+static int atom_read_header(struct mp4 *f, uint8_t *atom_type,
+               uint8_t *header_size, uint64_t *atom_size)
+{
+       uint32_t size;
+       int ret;
+       uint8_t atom_header[8];
+
+       ret = read_data(f, atom_header, 8);
+       if (ret <= 0)
+               return ret;
+       size = read_u32_be(atom_header);
+       if (size == 1) { /* 64 bit atom size */
+               if (header_size)
+                       *header_size = 16;
+               ret = read_int64(f, atom_size);
+               if (ret <= 0)
+                       return ret;
+       } else {
+               if (header_size)
+                       *header_size = 8;
+               *atom_size = size;
+       }
+       *atom_type = atom_name_to_type(atom_header + 4);
+       return 1;
+}
+
+static off_t get_position(const struct mp4 *f)
+{
+       return f->cb->seek(f->cb->user_data, 0, SEEK_CUR);
+}
+
+static void set_position(struct mp4 *f, off_t position)
+{
+       f->cb->seek(f->cb->user_data, position, SEEK_SET);
+}
+
+static void skip_bytes(struct mp4 *f, off_t num_skip)
+{
+       f->cb->seek(f->cb->user_data, num_skip, SEEK_CUR);
+}
+
+static int read_stsz(struct mp4 *f)
+{
+       int ret;
+       struct mp4_track *t = &f->track;
+
+       if (t->state != ATS_SEEN_MP4A || t->stsz_table)
+               return 1;
+       skip_bytes(f, 4); /* version (1), flags (3) */
+       ret = read_int32(f, &t->stsz_sample_size);
+       if (ret <= 0)
+               return ret;
+       ret = read_int32(f, &t->stsz_sample_count);
+       if (ret <= 0)
+               return ret;
+       if (t->stsz_sample_size != 0)
+               return 1;
+       t->stsz_table = arr_alloc(t->stsz_sample_count, sizeof(int32_t));
+       for (uint32_t n = 0; n < t->stsz_sample_count; n++) {
+               ret = read_int32(f, &t->stsz_table[n]);
+               if (ret <= 0)
+                       return ret;
+       }
+       return 1;
+}
+
+static int read_stts(struct mp4 *f)
+{
+       int ret;
+       struct mp4_track *t = &f->track;
+
+       if (t->state != ATS_SEEN_MP4A || t->stts_sample_count)
+               return 1;
+       skip_bytes(f, 4); /* version (1), flags (3) */
+       ret = read_int32(f, &t->stts_entry_count);
+       if (ret <= 0)
+               return ret;
+       t->stts_sample_count = arr_alloc(t->stts_entry_count, sizeof(int32_t));
+       for (uint32_t n = 0; n < t->stts_entry_count; n++) {
+               ret = read_int32(f, &t->stts_sample_count[n]);
+               if (ret <= 0)
+                       return ret;
+               skip_bytes(f, 4); /* sample delta */
+       }
+       return 1;
+}
+
+static int read_stsc(struct mp4 *f)
+{
+       int ret;
+       struct mp4_track *t = &f->track;
+
+       if (t->state != ATS_SEEN_MP4A)
+               return 1;
+       if (t->stsc_first_chunk || t->stsc_samples_per_chunk)
+               return 1;
+       skip_bytes(f, 4); /* version (1), flags (3) */
+       ret = read_int32(f, &t->stsc_entry_count);
+       if (ret <= 0)
+               return ret;
+       t->stsc_first_chunk = arr_alloc(t->stsc_entry_count, sizeof(int32_t));
+       t->stsc_samples_per_chunk = arr_alloc(t->stsc_entry_count,
+               sizeof (int32_t));
+       for (uint32_t n = 0; n < t->stsc_entry_count; n++) {
+               ret = read_int32(f, &t->stsc_first_chunk[n]);
+               if (ret <= 0)
+                       return ret;
+               ret = read_int32(f, &t->stsc_samples_per_chunk[n]);
+               if (ret <= 0)
+                       return ret;
+               skip_bytes(f, 4); /* sample desc index */
+       }
+       return 1;
+}
+
+static int read_stco(struct mp4 *f)
+{
+       int ret;
+       struct mp4_track *t = &f->track;
+
+       if (t->state != ATS_SEEN_MP4A || t->stco_chunk_offset)
+               return 1;
+       skip_bytes(f, 4); /* version (1), flags (3) */
+       ret = read_int32(f, &t->stco_entry_count);
+       if (ret <= 0)
+               return ret;
+       t->stco_chunk_offset = arr_alloc(t->stco_entry_count, sizeof(int32_t));
+       for (uint32_t n = 0; n < t->stco_entry_count; n++) {
+               ret = read_int32(f, &t->stco_chunk_offset[n]);
+               if (ret <= 0)
+                       return ret;
+       }
+       return 1;
+}
+
+static int read_stsd(struct mp4 *f)
+{
+       int ret;
+       uint32_t entry_count;
+
+       if (f->track.state != ATS_INITIAL)
+               return 1;
+       skip_bytes(f, 4); /* version (1), flags (3) */
+       ret = read_int32(f, &entry_count);
+       if (ret <= 0)
+               return ret;
+       for (uint32_t n = 0; n < entry_count; n++) {
+               uint64_t skip = get_position(f);
+               uint64_t size;
+               uint8_t atom_type = 0;
+               ret = atom_read_header(f, &atom_type, NULL, &size);
+               if (ret <= 0)
+                       return ret;
+               skip += size;
+               if (atom_type == ATOM_MP4A) {
+                       f->track.state = ATS_SEEN_MP4A;
+                       /* reserved (6), data reference index (2), reserved (8) */
+                       skip_bytes(f, 16);
+                       ret = read_int16(f, &f->track.channel_count);
+                       if (ret <= 0)
+                               return ret;
+                       skip_bytes(f, 6);
+                       ret = read_int16(f, &f->track.sample_rate);
+                       if (ret <= 0)
+                               return ret;
+               }
+               set_position(f, skip);
+       }
+       return 1;
+}
+
+static const char *get_metadata_name(uint8_t atom_type)
+{
+       switch (atom_type) {
+       case ATOM_TITLE: return "title";
+       case ATOM_ARTIST: return "artist";
+       case ATOM_ALBUM: return "album";
+       case ATOM_DATE: return "date";
+       case ATOM_COMMENT: return "comment";
+       default: return "unknown";
+       }
+}
+
+static int parse_tag(struct mp4 *f, uint8_t parent, int32_t size)
+{
+       int ret;
+       uint64_t subsize, sumsize;
+       char *value = NULL;
+       uint32_t len = 0;
+       uint64_t destpos;
+       struct mp4_tag *tag;
+
+       for (
+               sumsize = 0;
+               sumsize < size;
+               set_position(f, destpos), sumsize += subsize
+       ) {
+               uint8_t atom_type;
+               uint8_t header_size = 0;
+               ret = atom_read_header(f, &atom_type, &header_size, &subsize);
+               if (ret <= 0)
+                       goto fail;
+               destpos = get_position(f) + subsize - header_size;
+               if (atom_type != ATOM_DATA)
+                       continue;
+               skip_bytes(f, 8); /* version (1), flags (3), reserved (4) */
+               ret = -E_MP4_CORRUPT;
+               if (subsize < header_size + 8 || subsize > UINT_MAX)
+                       goto fail;
+               len = subsize - (header_size + 8);
+               free(value);
+               value = alloc(len + 1);
+               ret = read_data(f, value, len);
+               if (ret <= 0)
+                       goto fail;
+               value[len] = '\0';
+       }
+       if (!value)
+               return -E_MP4_CORRUPT;
+       f->meta.tags = para_realloc(f->meta.tags, (f->meta.count + 1)
+               * sizeof(struct mp4_tag));
+       tag = f->meta.tags + f->meta.count;
+       tag->item = para_strdup(get_metadata_name(parent));
+       tag->value = value;
+       f->meta.count++;
+       return 1;
+fail:
+       free(value);
+       return ret;
+}
+
+static int read_mdhd(struct mp4 *f)
+{
+       int ret;
+       uint32_t version;
+       struct mp4_track *t = &f->track;
+
+       if (t->state != ATS_INITIAL)
+               return 1;
+       ret = read_int32(f, &version);
+       if (ret <= 0)
+               return ret;
+       if (version == 1) {
+               skip_bytes(f, 16); /* creation time (8), modification time (8) */
+               ret = read_int32(f, &t->time_scale);
+               if (ret <= 0)
+                       return ret;
+               ret = read_int64(f, &t->duration);
+               if (ret <= 0)
+                       return ret;
+       } else { /* version == 0 */
+               uint32_t temp;
+
+               skip_bytes(f, 8); /* creation time (4), modification time (4) */
+               ret = read_int32(f, &t->time_scale);
+               if (ret <= 0)
+                       return ret;
+               ret = read_int32(f, &temp);
+               if (ret <= 0)
+                       return ret;
+               t->duration = (temp == (uint32_t) (-1))?
+                       (uint64_t) (-1) : (uint64_t) (temp);
+       }
+       skip_bytes(f, 4);
+       return 1;
+}
+
+static int read_ilst(struct mp4 *f, int32_t size)
+{
+       int ret;
+       uint64_t sumsize = 0;
+
+       while (sumsize < size) {
+               uint8_t atom_type;
+               uint64_t subsize, destpos;
+               uint8_t header_size = 0;
+               ret = atom_read_header(f, &atom_type, &header_size, &subsize);
+               if (ret <= 0)
+                       return ret;
+               destpos = get_position(f) + subsize - header_size;
+               switch (atom_type) {
+               case ATOM_ARTIST:
+               case ATOM_TITLE:
+               case ATOM_ALBUM:
+               case ATOM_COMMENT:
+               case ATOM_DATE:
+                       ret = parse_tag(f, atom_type, subsize - header_size);
+                       if (ret <= 0)
+                               return ret;
+               }
+               set_position(f, destpos);
+               sumsize += subsize;
+       }
+       return 1;
+}
+
+static int read_meta(struct mp4 *f, uint64_t size)
+{
+       int ret;
+       uint64_t subsize, sumsize = 0;
+       uint8_t atom_type;
+       uint8_t header_size = 0;
+
+       skip_bytes(f, 4); /* version (1), flags (3) */
+       while (sumsize < (size - (header_size + 4))) {
+               ret = atom_read_header(f, &atom_type, &header_size, &subsize);
+               if (ret <= 0)
+                       return ret;
+               if (subsize <= header_size + 4)
+                       return 1;
+               if (atom_type == ATOM_ILST) {
+                       f->ilst_offset = get_position(f) - header_size;
+                       f->ilst_size = subsize;
+                       ret = read_ilst(f, subsize - (header_size + 4));
+                       if (ret <= 0)
+                               return ret;
+               } else
+                       set_position(f, get_position(f) + subsize - header_size);
+               sumsize += subsize;
+       }
+       return 1;
+}
+
+static bool need_atom(uint8_t atom_type, bool meta_only)
+{
+       /* these are needed in any case */
+       switch (atom_type) {
+       case ATOM_STSD:
+       case ATOM_META:
+       case ATOM_TRAK:
+       case ATOM_MDIA:
+       case ATOM_MINF:
+       case ATOM_STBL:
+       case ATOM_UDTA:
+               return true;
+       }
+       /* meta-only opens don't need anything else */
+       if (meta_only)
+               return false;
+       /* these are only required for regular opens */
+       switch (atom_type) {
+       case ATOM_STTS:
+       case ATOM_STSZ:
+       case ATOM_STCO:
+       case ATOM_STSC:
+       case ATOM_MDHD:
+               return true;
+       }
+       return false;
+}
+
+/* parse atoms that are sub atoms of other atoms */
+static int parse_sub_atoms(struct mp4 *f, uint64_t total_size, bool meta_only)
+{
+       int ret;
+       uint64_t dest, size, end = get_position(f) + total_size;
+
+       for (dest = get_position(f); dest < end; set_position(f, dest)) {
+               uint8_t header_size, atom_type;
+               ret = atom_read_header(f, &atom_type, &header_size, &size);
+               if (ret <= 0)
+                       return ret;
+               if (size == 0)
+                       return -E_MP4_CORRUPT;
+               dest = get_position(f) + size - header_size;
+               if (atom_type == ATOM_TRAK && f->track.state == ATS_SEEN_MP4A) {
+                       f->track.state = ATS_TRACK_CHANGE;
+                       continue;
+               }
+               if (atom_type == ATOM_UDTA) {
+                       f->udta_offset = get_position(f) - header_size;
+                       f->udta_size = size;
+               }
+               if (!need_atom(atom_type, meta_only))
+                       continue;
+               switch (atom_type) {
+               case ATOM_STSZ: ret = read_stsz(f); break;
+               case ATOM_STTS: ret = read_stts(f); break;
+               case ATOM_STSC: ret = read_stsc(f); break;
+               case ATOM_STCO: ret = read_stco(f); break;
+               case ATOM_STSD: ret = read_stsd(f); break;
+               case ATOM_MDHD: ret = read_mdhd(f); break;
+               case ATOM_META:
+                       f->meta_offset = get_position(f) - header_size;
+                       f->meta_size = size;
+                       ret = read_meta(f, size);
+                       break;
+               default:
+                       ret = parse_sub_atoms(f, size - header_size, meta_only);
+               }
+               if (ret <= 0)
+                       return ret;
+       }
+       return 1;
+}
+
+/**
+ * Deallocate all resources associated with an mp4 file handle.
+ *
+ * \param f File handle returned by \ref mp4_open() or \ref mp4_open_meta().
+ *
+ * This frees the metadata items and various tables which were allocated when
+ * the file was opened. The given file handle must not be NULL.
+ */
+void mp4_close(struct mp4 *f)
+{
+       free(f->track.stsz_table);
+       free(f->track.stts_sample_count);
+       free(f->track.stsc_first_chunk);
+       free(f->track.stsc_samples_per_chunk);
+       free(f->track.stco_chunk_offset);
+       for (uint32_t n = 0; n < f->meta.count; n++) {
+               free(f->meta.tags[n].item);
+               free(f->meta.tags[n].value);
+       }
+       free(f->meta.tags);
+       free(f);
+}
+
+static int open_file(const struct mp4_callback *cb, bool meta_only, struct mp4 **result)
+{
+       int ret;
+       uint64_t size;
+       uint8_t atom_type, header_size;
+       struct mp4 *f = zalloc(sizeof(*f));
+
+       f->cb = cb;
+       while ((ret = atom_read_header(f, &atom_type, &header_size, &size)) > 0) {
+               f->last_atom = atom_type;
+               if (atom_type != ATOM_MOOV || size <= header_size) { /* skip */
+                       set_position(f, get_position(f) + size - header_size);
+                       continue;
+               }
+               f->moov_offset = get_position(f) - header_size;
+               f->moov_size = size;
+               ret = parse_sub_atoms(f, size - header_size, meta_only);
+               if (ret <= 0)
+                       break;
+       }
+       if (ret < 0)
+               goto fail;
+       ret = -E_MP4_TRACK;
+       if (f->track.channel_count == 0)
+               goto fail;
+       ret = -E_MP4_BAD_SAMPLERATE;
+       if (f->track.sample_rate == 0)
+               goto fail;
+       ret = -E_MP4_MISSING_ATOM;
+       if (f->udta_size == 0 || f->meta_size == 0 || f->ilst_size == 0)
+               goto fail;
+       *result = f;
+       return 1;
+fail:
+       *result = NULL;
+       mp4_close(f);
+       return ret;
+}
+
+/**
+ * Read the audio track and the metadata of an mp4 file.
+ *
+ * \param cb Only the ->read() and ->seek() methods need to be supplied.
+ * \param result Initialized to a non-NULL pointer iff the function succeeds.
+ *
+ * This detects and parses the first audio track and the metadata information
+ * of the mp4 file. Various error checks are performed after the mp4 atoms have
+ * been parsed successfully.
+ *
+ * This function does not modify the file. However, if the caller intents to
+ * update the metadata later, the ->write() and ->truncate() methods must be
+ * supplied in the callback structure.
+ *
+ * \return Standard. Several errors are possible.
+ *
+ * \sa \ref mp4_open_meta().
+ */
+int mp4_open(const struct mp4_callback *cb, struct mp4 **result)
+{
+       struct mp4 *f;
+       int ret;
+
+       *result = NULL;
+       ret = open_file(cb, false, &f);
+       if (ret < 0)
+               return ret;
+       ret = -E_MP4_BAD_SAMPLE_COUNT;
+       if (f->track.stsz_sample_count == 0)
+               goto fail;
+       ret = -E_MP4_CORRUPT;
+       if (f->track.time_scale == 0)
+               goto fail;
+       *result = f;
+       return 1;
+fail:
+       mp4_close(f);
+       return ret;
+}
+
+static int32_t chunk_of_sample(const struct mp4 *f, int32_t sample,
+               int32_t *chunk)
+{
+       const struct mp4_track *t = &f->track;
+       uint32_t *fc = t->stsc_first_chunk, *spc = t->stsc_samples_per_chunk;
+       uint32_t chunk1, chunk1samples, n, total, k;
+
+       for (k = 1, total = 0; k < t->stsc_entry_count; k++, total += n) {
+               n = (fc[k] - fc[k - 1]) * spc[k - 1]; /* number of samples */
+               if (sample < total + n)
+                       break;
+       }
+       chunk1 = fc[k - 1];
+       chunk1samples = spc[k - 1];
+       if (chunk1samples != 0)
+               *chunk = (sample - total) / chunk1samples + chunk1;
+       else
+               *chunk = 1;
+       return total + (*chunk - chunk1) * chunk1samples;
+}
+
+/**
+ * Compute the duration of an mp4 file.
+ *
+ * \param f See \ref mp4_close().
+ *
+ * \return The number of milliseconds of the audio track. This function never
+ * fails.
+ */
+uint64_t mp4_get_duration(const struct mp4 *f)
+{
+       const struct mp4_track *t = &f->track;
+
+       return t->duration * 1000 / t->time_scale;
+}
+
+/**
+ * Reposition the read/write file offset.
+ *
+ * \param f See \ref mp4_close().
+ * \param sample The number of the sample to reposition to.
+ *
+ * The given sample number must be within range, i.e., strictly less than the
+ * value returned by \ref mp4_num_samples().
+ *
+ * \return Standard. The only possible error is an invalid sample number.
+ */
+int mp4_set_sample_position(struct mp4 *f, uint32_t sample)
+{
+       const struct mp4_track *t = &f->track;
+       int32_t offset, chunk, chunk_sample;
+       uint32_t n, srs; /* sample range size */
+
+       if (sample >= t->stsz_sample_count)
+               return -ERRNO_TO_PARA_ERROR(EINVAL);
+       chunk_sample = chunk_of_sample(f, sample, &chunk);
+       if (t->stsz_sample_size > 0)
+               srs = (sample - chunk_sample) * t->stsz_sample_size;
+       else {
+               for (srs = 0, n = chunk_sample; n < sample; n++)
+                       srs += t->stsz_table[n];
+       }
+       if (t->stco_entry_count > 0 && chunk > t->stco_entry_count)
+               offset = t->stco_chunk_offset[t->stco_entry_count - 1];
+       else if (t->stco_entry_count > 0)
+               offset = t->stco_chunk_offset[chunk - 1];
+       else
+               offset = 8;
+       set_position(f, offset + srs);
+       return 1;
+}
+
+/**
+ * Look up and return the size of the given sample in the stsz table.
+ *
+ * \param f See \ref mp4_close().
+ * \param sample The sample number of interest.
+ * \param result Sample size is returned here.
+ *
+ * For the sample argument the restriction mentioned in the documentation of
+ * \ref mp4_set_sample_position() applies as well.
+ *
+ * \return Standard. Like for \ref mp4_set_sample_position(), EINVAL is the
+ * only possible error.
+ */
+int mp4_get_sample_size(const struct mp4 *f, uint32_t sample, uint32_t *result)
+{
+       const struct mp4_track *t = &f->track;
+
+       if (sample >= t->stsz_sample_count)
+               return -ERRNO_TO_PARA_ERROR(EINVAL);
+       if (t->stsz_sample_size != 0)
+               *result = t->stsz_sample_size;
+       else
+               *result = t->stsz_table[sample];
+       return 1;
+}
+
+/**
+ * Return the sample rate stored in the stsd atom.
+ *
+ * \param f See \ref mp4_close().
+ *
+ * The sample rate is a property of the audio track of the mp4 file and is thus
+ * independent of the sample number.
+ *
+ * \return The function always returns a positive value because the open
+ * operation fails if the sample rate happens to be zero. A typical value is
+ * 44100.
+ */
+uint16_t mp4_get_sample_rate(const struct mp4 *f)
+{
+       return f->track.sample_rate;
+}
+
+/**
+ * Return the number of channels of the audio track.
+ *
+ * \param f See \ref mp4_close().
+ *
+ * \return The returned channel count is guaranteed to be positive because the
+ * open operation fails if the mp4a atom is missing or contains a zero channel
+ * count.
+ */
+uint16_t mp4_get_channel_count(const struct mp4 *f)
+{
+       return f->track.channel_count;
+}
+
+/**
+ * Return the number of samples of the audio track.
+ *
+ * \param f See \ref mp4_close().
+ *
+ * \return The sample count is read from the stsz atom during open.
+ */
+uint32_t mp4_num_samples(const struct mp4 *f)
+{
+       return f->track.stsz_sample_count;
+}
+
+/**
+ * Open an mp4 file in metadata-only mode.
+ *
+ * \param cb See \ref mp4_open().
+ * \param result See \ref mp4_open().
+ *
+ * This is similar to \ref mp4_open() but is cheaper because it only parses the
+ * metadata of the mp4 file. The only functions that can subsequently be called
+ * with the file handle returned here are \ref mp4_get_meta() and \ref
+ * mp4_update_meta().
+ *
+ * \return Standard.
+ *
+ * \sa \ref mp4_open(). The comment about ->write() and ->truncate() applies to
+ * this function as well.
+ */
+int mp4_open_meta(const struct mp4_callback *cb, struct mp4 **result)
+{
+       struct mp4 *f;
+       int ret = open_file(cb, true, &f);
+
+       if (ret < 0)
+               return ret;
+       *result = f;
+       return 1;
+}
+
+/**
+ * Return the metadata of an mp4 file.
+ *
+ * \param f See \ref mp4_close().
+ *
+ * The caller is allowed to add, delete or modify the entries of the returned
+ * structure with the intention to pass the modified version to \ref
+ * mp4_update_meta().
+ *
+ * \return This never returns NULL, even if the file contains no metadata tag
+ * items. However, the meta count will be zero and the ->tags pointer NULL in
+ * this case.
+ */
+struct mp4_metadata *mp4_get_meta(struct mp4 *f)
+{
+       return &f->meta;
+}
+
+/** Total length of an on-disk metadata tag. */
+#define TAG_LEN(_len) (24 + (_len))
+static void create_ilst(const struct mp4_metadata *meta, uint8_t *out)
+{
+       for (unsigned n = 0; n < meta->count; n++) {
+               struct mp4_tag *tag = meta->tags + n;
+               unsigned len = strlen(tag->value);
+               const char *atom_name;
+
+               if (!strcasecmp(tag->item, "title"))
+                       atom_name = "\xA9" "nam";
+               else if (!strcasecmp(tag->item, "artist"))
+                       atom_name = "\xA9" "ART";
+               else if (!strcasecmp(tag->item, "album"))
+                       atom_name = "\xA9" "alb";
+               else if (!strcasecmp(tag->item, "date"))
+                       atom_name = "\xA9" "day";
+               else if (!strcasecmp(tag->item, "comment"))
+                       atom_name = "\xA9" "cmt";
+               else
+                       assert(false);
+               write_u32_be(out, TAG_LEN(len));
+               memcpy(out + 4, atom_name, 4);
+               write_u32_be(out + 8, 8 /* data atom header */
+                       + 8 /* flags + reserved */
+                       + len);
+               memcpy(out + 12, "data", 4);
+               write_u32_be(out + 16, 1); /* flags */
+               write_u32_be(out + 20, 0); /* reserved */
+               memcpy(out + 24, tag->value, len);
+               out += TAG_LEN(len);
+       }
+}
+
+static void *modify_moov(struct mp4 *f, uint32_t *out_size)
+{
+       int ret;
+       uint64_t total_base = f->moov_offset + 8;
+       uint32_t total_size = f->moov_size - 8;
+       uint32_t new_ilst_size = 0;
+       void *out_buffer;
+       uint8_t *p_out;
+       int32_t size_delta;
+       uint32_t tmp;
+
+       for (unsigned n = 0; n < f->meta.count; n++)
+               new_ilst_size += TAG_LEN(strlen(f->meta.tags[n].value));
+       size_delta = new_ilst_size - (f->ilst_size - 8);
+       *out_size = total_size + size_delta;
+       out_buffer = alloc(*out_size);
+       p_out = out_buffer;
+       set_position(f, total_base);
+       ret = read_data(f, p_out, f->udta_offset - total_base);
+       if (ret <= 0)
+               return NULL;
+       p_out += f->udta_offset - total_base;
+       ret = read_int32(f, &tmp);
+       if (ret <= 0)
+               return NULL;
+       write_u32_be(p_out, tmp + size_delta);
+       p_out += 4;
+       ret = read_data(f, p_out, 4);
+       if (ret <= 0)
+               return NULL;
+       p_out += 4;
+       ret = read_data(f, p_out, f->meta_offset - f->udta_offset - 8);
+       if (ret <= 0)
+               return NULL;
+       p_out += f->meta_offset - f->udta_offset - 8;
+       ret = read_int32(f, &tmp);
+       if (ret <= 0)
+               return NULL;
+       write_u32_be(p_out, tmp + size_delta);
+       p_out += 4;
+       ret = read_data(f, p_out, 4);
+       if (ret <= 0)
+               return NULL;
+       p_out += 4;
+       ret = read_data(f, p_out, f->ilst_offset - f->meta_offset - 8);
+       if (ret <= 0)
+               return NULL;
+       p_out += f->ilst_offset - f->meta_offset - 8;
+       ret = read_int32(f, &tmp);
+       if (ret <= 0)
+               return NULL;
+       write_u32_be(p_out, tmp + size_delta);
+       p_out += 4;
+       ret = read_data(f, p_out, 4);
+       if (ret <= 0)
+               return NULL;
+       p_out += 4;
+       create_ilst(&f->meta, p_out);
+       p_out += new_ilst_size;
+       set_position(f, f->ilst_offset + f->ilst_size);
+       ret = read_data(f, p_out, total_size - (f->ilst_offset - total_base)
+               - f->ilst_size);
+       if (ret <= 0)
+               return NULL;
+       return out_buffer;
+}
+
+static int write_data(struct mp4 *f, void *data, size_t size)
+{
+       while (size > 0) {
+               ssize_t ret = f->cb->write(f->cb->user_data, data, size);
+               if (ret < 0) {
+                       if (errno == EINTR)
+                               continue;
+                       return -ERRNO_TO_PARA_ERROR(errno);
+               }
+               size -= ret;
+       }
+       return 1;
+}
+
+/**
+ * Write back the modified metadata items to the mp4 file.
+ *
+ * This is the only public function which modifies the contents of an mp4 file.
+ * This is achieved by calling the ->write() and ->truncate() methods of the
+ * callback structure passed to \ref mp4_open() or \ref mp4_open_meta().
+ *
+ * \param f See \ref mp4_close().
+ *
+ * The modified metadata structure does not need to be supplied to this
+ * function because it is part of the mp4 structure.
+ *
+ * \return Standard.
+ */
+int mp4_update_meta(struct mp4 *f)
+{
+       void *new_moov_data;
+       uint32_t new_moov_size;
+       uint8_t buf[8] = "----moov";
+       int ret;
+
+       set_position(f, 0);
+       new_moov_data = modify_moov(f, &new_moov_size);
+       if (!new_moov_data ) {
+               mp4_close(f);
+               return 0;
+       }
+       if (f->last_atom != ATOM_MOOV) {
+               set_position(f, f->moov_offset + 4);
+               ret = write_data(f, "free", 4); /* rename old moov to free */
+               if (ret < 0)
+                       goto free_moov;
+               /* write new moov atom at EOF */
+               f->cb->seek(f->cb->user_data, 0, SEEK_END);
+       } else /* overwrite old moov atom */
+               set_position(f, f->moov_offset);
+       write_u32_be(buf, new_moov_size + 8);
+       ret = write_data(f, buf, sizeof(buf));
+       if (ret < 0)
+               goto free_moov;
+       ret = write_data(f, new_moov_data, new_moov_size);
+       if (ret < 0)
+               goto free_moov;
+       ret = f->cb->truncate(f->cb->user_data);
+       if (ret < 0)
+               ret = -ERRNO_TO_PARA_ERROR(errno);
+free_moov:
+       free(new_moov_data);
+       return ret;
+}
+
+/**
+ * Return the value of the given tag item.
+ *
+ * \param f See \ref mp4_close().
+ * \param item "artist", "title", "album", "comment", or "date".
+ *
+ * \return The function returns NULL if the given item is not in the above
+ * list. Otherwise, if the file does not contain a tag for the given item, the
+ * function also returns NULL. Otherwise a copy of the tag value is returned
+ * and the caller should free this memory when it is no longer needed.
+ */
+__malloc char *mp4_get_tag_value(const struct mp4 *f, const char *item)
+{
+       for (unsigned n = 0; n < f->meta.count; n++)
+               if (!strcasecmp(f->meta.tags[n].item, item))
+                       return para_strdup(f->meta.tags[n].value);
+       return NULL;
+}
diff --git a/mp4.h b/mp4.h
new file mode 100644 (file)
index 0000000..c36a1f8
--- /dev/null
+++ b/mp4.h
@@ -0,0 +1,87 @@
+/** \file mp4.h Public API of the mp4 parser. */
+
+/**
+ * Callbacks provided by the user of the mp4 parsing API.
+ *
+ * A pointer to this structure is passed to the two public open functions. If
+ * the file is opened in read-only mode, the ->write() and ->truncate() methods
+ * won't be called and may thus be NULL. The ->read() and ->seek() methods
+ * must be supplied for either open type.
+ *
+ * All methods are supposed to work like their corresponding system calls.
+ * That is, they should return non-negative for success and -1 on failure. In
+ * the error case errno is expected to be set accordingly.
+ *
+ * \sa \ref mp4_open(), \ref mp4_open_meta().
+ */
+struct mp4_callback {
+       /** This pointer is propagated to each call of all methods. */
+       void *user_data;
+       /**
+        * This should return the number of bytes read on success. Short reads
+        * are OK: the function may return less than length.
+        */
+       ssize_t (*read)(void *user_data, void *buffer, size_t length);
+       /**
+        * This method is assumed to succeed. The implementation should simply
+        * abort on errors. Note that offsets beyond EOF must not be regarded
+        * as invalid arguments.
+        */
+       off_t (*seek)(void *user_data, off_t offset, int whence);
+       /**
+        * Like the write() system call, this should return the number of bytes
+        * written. Short writes are OK: the function may return less than
+        * count.
+        */
+       ssize_t (*write)(void *user_data, void *buffer, size_t count);
+       /**
+        * Unlike the truncate system call, this function does not receive an
+        * offset. The method is expected to truncate the file to the offset
+        * given by the current file position instead.
+        */
+       int (*truncate)(void *user_data);
+};
+
+/** Specifies one metadata tag. Both fields are 0-terminated strings. */
+struct mp4_tag {
+       /** The item name: "artist", "title", "album", "comment", or "date". */
+       char *item;
+       /** An arbitrary string value. */
+       char *value;
+};
+
+/**
+ * An array of name/value pairs.
+ *
+ * This structure is initialized when the mp4 file is opened in either mode.
+ * If the file contains metadata items other than the standard five, those
+ * non-standard items are not included in the array. After a successful open, a
+ * pointer to the metadata structure can be obtained via \ref mp4_get_meta().
+ */
+struct mp4_metadata {
+       /** It's OK to change this, for example by calling realloc(). */
+       struct mp4_tag *tags;
+       /** The number of entries of the array. */
+       unsigned count;
+};
+
+/**
+ * The mp4 file handle.
+ *
+ * A pointer to this opaque structure is returned by the two open functions.
+ * All other functions of the mp4 API receive a pointer of this type.
+ */
+struct mp4;
+
+int mp4_set_sample_position(struct mp4 *f, uint32_t sample);
+int mp4_open(const struct mp4_callback *cb, struct mp4 **result);
+void mp4_close(struct mp4 *f);
+int mp4_get_sample_size(const struct mp4 *f, uint32_t sample, uint32_t *result);
+uint16_t mp4_get_sample_rate(const struct mp4 *f);
+uint16_t mp4_get_channel_count(const struct mp4 *f);
+uint32_t mp4_num_samples(const struct mp4 *f);
+uint64_t mp4_get_duration(const struct mp4 *f);
+int mp4_open_meta(const struct mp4_callback *cb, struct mp4 **result);
+struct mp4_metadata *mp4_get_meta(struct mp4 *f);
+int mp4_update_meta(struct mp4 *f);
+__malloc char *mp4_get_tag_value(const struct mp4 *f, const char *item);
diff --git a/net.c b/net.c
index 91200fc040bcfebafccf9e737fb65514af9ff8f2..9b3624428d5e8a0f63df7d814783df20afa54385 100644 (file)
--- a/net.c
+++ b/net.c
 #include "list.h"
 #include "fd.h"
 
+/* Whether the given address conforms to the IPv4 address format. */
+static inline bool is_valid_ipv4_address(const char *address)
+{
+       struct in_addr test_it;
+       return inet_pton(AF_INET, address, &test_it) != 0;
+}
+
 /**
  * Parse and validate IPv4 address/netmask string.
  *
@@ -58,13 +65,6 @@ failed:
        return NULL;
 }
 
-
-/**
- * Match string as a candidate IPv4 address.
- *
- * \param address The string to match.
- * \return True if \a address has "dot-quad" format.
- */
 static bool is_v4_dot_quad(const char *address)
 {
        bool result;
@@ -77,6 +77,13 @@ static bool is_v4_dot_quad(const char *address)
        return result;
 }
 
+/* Whether a string conforms to IPv6 address format (RFC 4291). */
+static inline bool is_valid_ipv6_address(const char *address)
+{
+       struct in6_addr test_it;
+       return inet_pton(AF_INET6, address, &test_it) != 0;
+}
+
 /**
  * Perform basic syntax checking on the host-part of an URL:
  *
@@ -183,7 +190,7 @@ failed:
  * \return In all cases the returned string is a allocated with malloc(3) and
  * has to be freed by the caller.
  */
-char *format_url(const char *url, int default_port)
+__malloc char *format_url(const char *url, int default_port)
 {
        char host[MAX_HOSTLEN];
        int url_port;
@@ -205,7 +212,7 @@ char *format_url(const char *url, int default_port)
  * \param transport Transport protocol name (e.g. "udp", "tcp"), or NULL.
  * \return Pointer to static result buffer.
  *
- * \sa getservent(3), services(5), nsswitch.conf(5).
+ * \sa getservbyport(3), services(5), nsswitch.conf(5).
  */
 const char *stringify_port(int port, const char *transport)
 {
@@ -224,12 +231,13 @@ const char *stringify_port(int port, const char *transport)
        return service;
 }
 
-/**
- * Determine the socket type for a given layer-4 protocol.
- *
- * \param l4type The symbolic name of the transport-layer protocol.
- *
- * \sa ip(7), socket(2).
+#ifndef SOCK_DCCP
+#define SOCK_DCCP 6 /**< Linux socket type. */
+#endif
+
+/*
+ * Determine the socket type, given the symbolic name of the transport-layer
+ * protocol. See ip(7), socket(2).
  */
 static inline int sock_type(const unsigned l4type)
 {
@@ -241,9 +249,7 @@ static inline int sock_type(const unsigned l4type)
        return -1;              /* not supported here */
 }
 
-/**
- * Pretty-print transport-layer name.
- */
+/* Pretty-print transport-layer name. */
 static const char *layer4_name(const unsigned l4type)
 {
        switch (l4type) {
@@ -273,7 +279,12 @@ struct pre_conn_opt {
        struct list_head node;          /**< FIFO, as sockopt order matters. */
 };
 
-/** FIFO list of pre-connection socket options to be set */
+/**
+ * List of pre-connection socket options to be set.
+ *
+ * This list contains transport-layer independent encapsulation of socket
+ * options that need to be registered prior to setting up a connection.
+ */
 struct flowopts {
        struct list_head sockopts;
 };
@@ -286,9 +297,9 @@ struct flowopts {
  */
 struct flowopts *flowopt_new(void)
 {
-       struct flowopts *new = para_malloc(sizeof(*new));
+       struct flowopts *new = alloc(sizeof(*new));
 
-       INIT_LIST_HEAD(&new->sockopts);
+       init_list_head(&new->sockopts);
        return new;
 }
 
@@ -307,7 +318,7 @@ struct flowopts *flowopt_new(void)
 void flowopt_add(struct flowopts *fo, int lev, int opt,
                const char *name, const void *val, int len)
 {
-       struct pre_conn_opt *new = para_malloc(sizeof(*new));
+       struct pre_conn_opt *new = alloc(sizeof(*new));
 
        new->sock_option = opt;
        new->sock_level  = lev;
@@ -317,7 +328,7 @@ void flowopt_add(struct flowopts *fo, int lev, int opt,
                new->opt_val = NULL;
                new->opt_len = 0;
        } else {
-               new->opt_val = para_malloc(len);
+               new->opt_val = alloc(len);
                new->opt_len = len;
                memcpy(new->opt_val, val, len);
        }
@@ -325,7 +336,7 @@ void flowopt_add(struct flowopts *fo, int lev, int opt,
        list_add_tail(&new->node, &fo->sockopts);
 }
 
-/** Set the entire bunch of pre-connection options at once. */
+/* Set the entire bunch of pre-connection options at once. */
 static void flowopt_setopts(int sockfd, struct flowopts *fo)
 {
        struct pre_conn_opt *pc;
@@ -509,7 +520,7 @@ int makesock(unsigned l4type, bool passive, const char *host, uint16_t port_numb
        if (ai)
                freeaddrinfo(ai);
        if (ret < 0) {
-               PARA_ERROR_LOG("can not create %s socket %s#%d.\n",
+               PARA_NOTICE_LOG("can not create %s socket %s#%d.\n",
                layer4_name(l4type), host? host : (passive?
                "[loopback]" : "[localhost]"), port_number);
        }
@@ -571,11 +582,7 @@ int para_listen_simple(unsigned l4type, uint16_t port)
        return para_listen(l4type, NULL, port);
 }
 
-/**
- * Determine IPv4/v6 socket address length.
- * \param sa Container of IPv4 or IPv6 address.
- * \return Address-family dependent address length.
- */
+/* Compute the address-family dependent address length of an IPv4/v6 socket. */
 static socklen_t salen(const struct sockaddr *sa)
 {
        assert(sa->sa_family == AF_INET || sa->sa_family == AF_INET6);
@@ -585,7 +592,7 @@ static socklen_t salen(const struct sockaddr *sa)
                : sizeof(struct sockaddr_in);
 }
 
-/** True if @ss holds a v6-mapped-v4 address (RFC 4291, 2.5.5.2) */
+/* True if ss holds a v6-mapped-v4 address (RFC 4291, 2.5.5.2) */
 static bool SS_IS_ADDR_V4MAPPED(const struct sockaddr_storage *ss)
 {
        const struct sockaddr_in6 *ia6 = (const struct sockaddr_in6 *)ss;
@@ -593,10 +600,10 @@ static bool SS_IS_ADDR_V4MAPPED(const struct sockaddr_storage *ss)
        return ss->ss_family == AF_INET6 && IN6_IS_ADDR_V4MAPPED(&ia6->sin6_addr);
 }
 
-/**
+/*
  * Process IPv4/v6 address, turn v6-mapped-v4 address into normal IPv4 address.
- * \param ss Container of IPv4/6 address.
- * \return Pointer to normalized address (may be static storage).
+ * ss: Container of IPv4/6 address.
+ * Returns: Pointer to normalized address (may be static storage).
  *
  * \sa RFC 3493.
  */
@@ -617,7 +624,7 @@ normalize_ip_address(const struct sockaddr_storage *ss)
        return (const struct sockaddr *)ss;
 }
 
-/**
+/*
  * Generic/fallback MTU values
  *
  * These are taken from RFC 1122, RFC 2460, and RFC 5405.
@@ -632,7 +639,7 @@ static inline int generic_mtu(const int af_type)
        return af_type == AF_INET6 ? 1280 : 576;
 }
 
-/** Crude approximation of IP header overhead - neglecting options. */
+/* Crude approximation of IP header overhead - neglecting options. */
 static inline int estimated_header_overhead(const int af_type)
 {
        return af_type == AF_INET6 ? 40 : 20;
@@ -801,25 +808,21 @@ int recv_buffer(int fd, char *buf, size_t size)
  * Wrapper around the accept system call.
  *
  * \param fd The listening socket.
- * \param rfds An optional fd_set pointer.
  * \param addr Structure which is filled in with the address of the peer socket.
  * \param size Should contain the size of the structure pointed to by \a addr.
  * \param new_fd Result pointer.
  *
- * Accept incoming connections on \a addr, retry if interrupted. If \a rfds is
- * not \p NULL, return 0 if \a fd is not set in \a rfds without calling accept().
+ * Accept incoming connections on addr, retry if interrupted.
  *
  * \return Negative on errors, zero if no connections are present to be accepted,
  * one otherwise.
  *
  * \sa accept(2).
  */
-int para_accept(int fd, fd_set *rfds, void *addr, socklen_t size, int *new_fd)
+int para_accept(int fd, void *addr, socklen_t size, int *new_fd)
 {
        int ret;
 
-       if (rfds && !FD_ISSET(fd, rfds))
-               return 0;
        do
                ret = accept(fd, (struct sockaddr *) addr, &size);
        while (ret < 0 && errno == EINTR);
@@ -833,6 +836,10 @@ int para_accept(int fd, fd_set *rfds, void *addr, socklen_t size, int *new_fd)
        return -ERRNO_TO_PARA_ERROR(errno);
 }
 
+#ifndef DCCP_SOCKOPT_AVAILABLE_CCIDS
+#define DCCP_SOCKOPT_AVAILABLE_CCIDS 12 /**< List of supported CCIDs. */
+#endif
+
 /**
  * Probe the list of DCCP CCIDs configured on this host.
  * \param ccid_array Pointer to return statically allocated array in.
@@ -847,7 +854,7 @@ int dccp_available_ccids(uint8_t **ccid_array)
        socklen_t nccids = sizeof(ccids);
        int ret, fd;
 
-       ret = fd = makesock(IPPROTO_DCCP, 1, NULL, 0, NULL);
+       ret = fd = makesock(IPPROTO_DCCP, true /* passive */, NULL, 0, NULL);
        if (ret < 0)
                return ret;
 
@@ -865,6 +872,18 @@ int dccp_available_ccids(uint8_t **ccid_array)
        return nccids;
 }
 
+/**
+ * The buffer size of the sun_path component of struct sockaddr_un.
+ *
+ * While glibc doesn't define UNIX_PATH_MAX, it documents it has being limited
+ * to 108 bytes. On NetBSD it is only 104 bytes though. We trust UNIX_PATH_MAX
+ * if it is defined and use the size of the ->sun_path member otherwise. This
+ * should be safe everywhere.
+ */
+#ifndef UNIX_PATH_MAX
+#define UNIX_PATH_MAX (sizeof(((struct sockaddr_un *)0)->sun_path))
+#endif
+
 /*
  * Prepare a structure for AF_UNIX socket addresses.
  *
diff --git a/net.h b/net.h
index 2256f376497b89d3382e2bf80ce09725ef64ab02..33acfc890495087991c964397371e4edf9b574f1 100644 (file)
--- a/net.h
+++ b/net.h
 /* Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 /** \file net.h exported symbols from net.c */
 
-/**
- * The buffer size of the sun_path component of struct sockaddr_un.
- *
- * While glibc doesn't define \p UNIX_PATH_MAX, it documents it has being
- * limited to 108 bytes. On NetBSD it is only 104 bytes though. We trust \p
- * UNIX_PATH_MAX if it is defined and use the size of the ->sun_path member
- * otherwise. This should be safe everywhere.
- */
-#ifndef UNIX_PATH_MAX
-#define UNIX_PATH_MAX (sizeof(((struct sockaddr_un *)0)->sun_path))
-#endif
-
 /* Userland defines for Linux DCCP support. */
 
-#ifndef IPPROTO_DCCP
-#define IPPROTO_DCCP 33 /**< IANA assigned value. */
-#endif
-
-#ifndef SOCK_DCCP
-#define SOCK_DCCP 6 /**< Linux socket type. */
-#endif
-
-#ifndef DCCP_SOCKOPT_RX_CCID
-/** Per-connection CCID support (set/get the RX CCID, since v2.6.30-rc1). */
-#define DCCP_SOCKOPT_RX_CCID 15
-#endif
-
 #ifndef SOL_DCCP
 #define SOL_DCCP 269 /**< Linux socket level. */
 #endif
 
-#ifndef DCCP_SOCKOPT_GET_CUR_MPS
-#define DCCP_SOCKOPT_GET_CUR_MPS  5 /**< Max packet size, RFC 4340, 14. */
-#endif
-
-#ifndef DCCP_SOCKOPT_AVAILABLE_CCIDS
-#define DCCP_SOCKOPT_AVAILABLE_CCIDS 12 /**< List of supported CCIDs. */
-#endif
-
-#ifndef DCCP_SOCKOPT_CCID
-#define DCCP_SOCKOPT_CCID 13 /**< Sets both TX/RX CCID. */
-#endif
-
-#ifndef DCCP_SOCKOPT_TX_CCID
-#define DCCP_SOCKOPT_TX_CCID 14 /**< Set/get the TX CCID. */
-#endif
-
 /** The maximum length of the host component in an URL. */
 #define MAX_HOSTLEN 256
 
-/**
- * Flowopts: Transport-layer independent encapsulation of socket options
- *           that need to be registered prior to setting up a connection.
- */
+/* Opaque, only known to net.c. */
 struct flowopts;
 
-extern struct flowopts *flowopt_new(void);
-extern void flowopt_add(struct flowopts *fo, int level, int opt,
+struct flowopts *flowopt_new(void);
+void flowopt_add(struct flowopts *fo, int level, int opt,
                const char *name, const void *val, int len);
 void flowopt_cleanup(struct flowopts *fo);
-/** Flowopt shortcut macros */
-#define OPT_ADD(fo, lev, opt, val, len)        flowopt_add(fo, lev, opt, #opt, val, len)
 
 /**
  * Functions to parse and validate (parts of) URLs.
  */
-extern char *parse_cidr(const char *cidr,
-                       char *addr, ssize_t addrlen, int32_t *netmask);
-extern char *parse_url(const char *url,
-                      char *host, ssize_t hostlen, int32_t *port);
-char *format_url(const char *url, int default_port);
-extern const char *stringify_port(int port, const char *transport);
-/**
- * Ensure that string conforms to the IPv4 address format.
- *
- * \param address The address string to check.
- *
- * \return 1 if \a address conforms to the IPv4 address format, else 0.
- */
-_static_inline_ bool is_valid_ipv4_address(const char *address)
-{
-       struct in_addr test_it;
-
-       return inet_pton(AF_INET, address, &test_it) != 0;
-}
-
-/**
- * Ensure that string conforms to IPv6 address format.
- *
- * \param address The address string to check.
- *
- * \return 1 if string has a valid IPv6 address syntax, 0 if not.
- * \sa RFC 4291.
- */
-_static_inline_ bool is_valid_ipv6_address(const char *address)
-{
-       struct in6_addr test_it;
-
-       return inet_pton(AF_INET6, address, &test_it) != 0;
-}
+char *parse_cidr(const char *cidr,
+               char *addr, ssize_t addrlen, int32_t *netmask);
+char *parse_url(const char *url,
+               char *host, ssize_t hostlen, int32_t *port);
+__malloc char *format_url(const char *url, int default_port);
+const char *stringify_port(int port, const char *transport);
 
 int lookup_address(unsigned l4type, bool passive, const char *host,
                int port_number, struct addrinfo **result);
@@ -114,10 +40,9 @@ int makesock(unsigned l4type, bool passive, const char *host,
 int makesock_addrinfo(unsigned l4type, bool passive, struct addrinfo *ai,
                struct flowopts *fo);
 
-static inline int para_connect_simple(unsigned l4type,
-                                     const char *host, uint16_t port)
+static inline int para_connect(unsigned l4type, const char *host, uint16_t port)
 {
-       return makesock(l4type, 0, host, port, NULL);
+       return makesock(l4type, false, host, port, NULL);
 }
 
 void extract_v4_addr(const struct sockaddr_storage *ss, struct in_addr *ia);
@@ -133,17 +58,17 @@ int para_listen(unsigned l4type, const char *addr, uint16_t port);
 int para_listen_simple(unsigned l4type, uint16_t port);
 
 /** Pretty-printing of IPv4/6 socket addresses */
-extern char *remote_name(int sockfd);
+char *remote_name(int sockfd);
 
 /**
  * Determining maximum payload (packet) size
  */
-extern int generic_max_transport_msg_size(int sockfd);
+int generic_max_transport_msg_size(int sockfd);
 
 int recv_bin_buffer(int fd, char *buf, size_t size);
 int recv_buffer(int fd, char *buf, size_t size);
 
-int para_accept(int fd, fd_set *rfds, void *addr, socklen_t size, int *new_fd);
+int para_accept(int fd, void *addr, socklen_t size, int *new_fd);
 int create_local_socket(const char *name);
 int connect_local_socket(const char *name);
 int recv_cred_buffer(int, char *, size_t);
@@ -152,8 +77,6 @@ ssize_t send_cred_buffer(int, char*);
 /**
  * Functions and definitions to support \p IPPROTO_DCCP
  */
-/** Estimated worst-case length of a DCCP header including options. */
-#define DCCP_MAX_HEADER                128
 /** Hardcoded maximum number of separate CCID modules compiled into a host. */
 #define DCCP_MAX_HOST_CCIDS    20
-extern int dccp_available_ccids(uint8_t **ccid_array);
+int dccp_available_ccids(uint8_t **ccid_array);
index 62cde3d465ab7c86a23d6c539b8a2b447d465f78..0a27a4acebc1652da03dea1573bf337eaf9c52a9 100644 (file)
@@ -124,8 +124,9 @@ int oac_get_file_info(char *map, size_t numbytes, struct afh_info *afhi,
        ogg_sync_state oss;
        ogg_page op;
        char *buf;
-       int ret, i, j, frames_per_chunk, ct_size;
-       long long unsigned num_frames = 0;
+       int ret, i, j, frames_per_chunk, ct_size, prev_pageno = 0;
+       long long unsigned granule_skip = 0, num_frames = 0;
+       int64_t granule = 0, prev_granule = 0;
 
        ogg_sync_init(&oss);
        ret = -E_OGG_SYNC;
@@ -145,8 +146,17 @@ int oac_get_file_info(char *map, size_t numbytes, struct afh_info *afhi,
        oss.returned = 0;
        oss.fill = numbytes;
        /* count ogg pages and get duration of the file */
-       for (i = 0; ogg_sync_pageseek(&oss, &op) > 0; i++)
-               num_frames = ogg_page_granulepos(&op);
+       for (i = 0; ogg_sync_pageseek(&oss, &op) > 0; i++) {
+               int this_pageno = ogg_page_pageno(&op);
+               int64_t this_granule = ogg_page_granulepos(&op);
+               if (this_granule >= 0)
+                       granule = this_granule;
+               if (i > 0 && this_pageno != prev_pageno + 1) /* hole */
+                       granule_skip += granule - prev_granule;
+               prev_pageno = this_pageno;
+               prev_granule = granule;
+       }
+       num_frames = granule - granule_skip;
        PARA_INFO_LOG("%d pages, %llu frames\n", i, num_frames);
        ret = -E_OGG_EMPTY;
        if (i == 0)
@@ -157,21 +167,21 @@ int oac_get_file_info(char *map, size_t numbytes, struct afh_info *afhi,
        PARA_INFO_LOG("%" PRIu32 " seconds, %d frames/chunk\n",
                afhi->seconds_total, frames_per_chunk);
        ct_size = 250;
-       afhi->chunk_table = para_malloc(ct_size * sizeof(uint32_t));
+       afhi->chunk_table = arr_alloc(ct_size, sizeof(uint32_t));
        afhi->chunk_table[0] = 0;
        afhi->chunk_table[1] = afhi->header_len;
        oss.returned = afhi->header_len;
        oss.fill = numbytes;
        for (j = 1; ogg_sync_pageseek(&oss, &op) > 0; /* nothing */) {
-               int granule = ogg_page_granulepos(&op);
+               granule = ogg_page_granulepos(&op);
 
                while (granule >= (j + 1) * frames_per_chunk) {
                        j++;
                        if (j >= ct_size) {
                                ct_size *= 2;
-                               afhi->chunk_table = para_realloc(
+                               afhi->chunk_table = arr_realloc(
                                        afhi->chunk_table,
-                                       ct_size * sizeof(uint32_t));
+                                       ct_size, sizeof(uint32_t));
                        }
                        afhi->chunk_table[j] = oss.returned;
                }
@@ -355,9 +365,9 @@ struct oac_custom_header {
  *
  * \sa \ref oac_custom_header_init().
  */
-struct oac_custom_header *oac_custom_header_new(void)
+__malloc struct oac_custom_header *oac_custom_header_new(void)
 {
-       return para_calloc(sizeof(struct oac_custom_header));
+       return zalloc(sizeof(struct oac_custom_header));
 }
 
 /**
index e0cf2d40c3ffb825c9f19338aa792558cb1c8742..03bf88b5794b0374fba493a79e502e4a89a686ca 100644 (file)
@@ -5,7 +5,7 @@
  * handlers that use the ogg container format.
  */
 
-struct oac_custom_header *oac_custom_header_new(void);
+__malloc struct oac_custom_header *oac_custom_header_new(void);
 void oac_custom_header_init(int serial, struct oac_custom_header *h);
 int oac_custom_header_append(ogg_packet *op, struct oac_custom_header *h);
 void oac_custom_header_flush(struct oac_custom_header *h);
index 708a27e52b68c1c2add01a3b3fe39f721485e39f..b1aec4bc2c4cb0049ed942d3b7e7708b2de84688 100644 (file)
@@ -88,7 +88,7 @@ static const ov_callbacks ovc = {
 
 static void ogg_open(struct filter_node *fn)
 {
-       fn->private_data = para_calloc(sizeof(struct private_oggdec_data));
+       fn->private_data = zalloc(sizeof(struct private_oggdec_data));
        fn->min_iqs = 8000;
 }
 
@@ -121,7 +121,7 @@ static int ogg_init(struct filter_node *fn)
        struct btr_node *btrn = fn->btrn;
        int ret, oret;
        size_t iqs;
-       struct OggVorbis_File *vf = para_malloc(sizeof(*vf));
+       struct OggVorbis_File *vf = alloc(sizeof(*vf));
 
        PARA_NOTICE_LOG("iqs: %zu, min_iqs: %zu, opening ov callbacks\n",
                btr_get_input_queue_size(btrn), fn->min_iqs);
@@ -178,13 +178,13 @@ out:
 
 /**
   * Allocate chunks of this size and produce at most one chunk of output per
-  * ->post_select() invocation. If the buffer could only be filled partially
+  * ->post_monitor() invocation. If the buffer could only be filled partially
   * due to insufficient input being available, it is shrunk to the real output
   * size and the resized buffer is fed into the output queue.
   */
 #define OGGDEC_OUTPUT_CHUNK_SIZE (32 * 1024)
 
-static void ogg_pre_select(struct sched *s, void *context)
+static void ogg_pre_monitor(struct sched *s, void *context)
 {
        struct filter_node *fn = context;
        struct private_oggdec_data *pod = fn->private_data;
@@ -201,7 +201,7 @@ static void ogg_pre_select(struct sched *s, void *context)
        sched_min_delay(s);
 }
 
-static int ogg_post_select(__a_unused struct sched *s, void *context)
+static int ogg_post_monitor(__a_unused struct sched *s, void *context)
 {
        struct filter_node *fn = context;
        struct private_oggdec_data *pod = fn->private_data;
@@ -211,7 +211,7 @@ static int ogg_post_select(__a_unused struct sched *s, void *context)
 
        ret = btr_node_status(btrn, fn->min_iqs, BTR_NT_INTERNAL);
        if (ret < 0) {
-               if (ret != -E_BTR_EOF) /* fatal error */
+               if (ret != -E_EOF) /* fatal error */
                        goto out;
                if (fn->min_iqs == 0 && !pod->have_more) /* EOF */
                        goto out;
@@ -228,7 +228,7 @@ static int ogg_post_select(__a_unused struct sched *s, void *context)
                goto out;
        }
        have = 0;
-       buf = para_malloc(OGGDEC_OUTPUT_CHUNK_SIZE);
+       buf = alloc(OGGDEC_OUTPUT_CHUNK_SIZE);
        for (;;) {
                ret = ov_read(pod->vf, buf + have, OGGDEC_OUTPUT_CHUNK_SIZE - have,
                        ENDIAN, 2 /* 16 bit */, 1 /* signed */, NULL);
@@ -262,7 +262,7 @@ out:
 const struct filter lsg_filter_cmd_com_oggdec_user_data = {
        .open = ogg_open,
        .close = ogg_close,
-       .pre_select = ogg_pre_select,
-       .post_select = ogg_post_select,
+       .pre_monitor = ogg_pre_monitor,
+       .post_monitor = ogg_post_monitor,
        .execute = oggdec_execute
 };
index 5f04c845c6ec1e8b5d837f827233df425033fad4..f696cd9e83606bc4e6bdd89d666f0885575f1d9f 100644 (file)
--- a/openssl.c
+++ b/openssl.c
@@ -11,6 +11,7 @@
 #include <openssl/sha.h>
 #include <openssl/bn.h>
 #include <openssl/aes.h>
+#include <openssl/evp.h>
 
 #include "para.h"
 #include "error.h"
@@ -36,12 +37,8 @@ void get_random_bytes_or_die(unsigned char *buf, int num)
 }
 
 /*
- * Read 64 bytes from /dev/urandom and add them to the SSL PRNG. Seed the PRNG
- * used by random(3) with a random seed obtained from SSL. If /dev/urandom is
- * not readable, the function calls exit().
- *
- * \sa RAND_load_file(3), \ref get_random_bytes_or_die(), srandom(3),
- * random(3), \ref para_random().
+ * Read 64 bytes from /dev/urandom and add them to the SSL PRNG. Then seed the
+ * PRNG used by random(3) with a random seed obtained from SSL.
  */
 void crypt_init(void)
 {
@@ -60,6 +57,12 @@ void crypt_shutdown(void)
 #ifdef HAVE_CRYPTO_CLEANUP_ALL_EX_DATA
        CRYPTO_cleanup_all_ex_data();
 #endif
+#ifdef HAVE_OPENSSL_THREAD_STOP /* openssl-1.1 or later */
+       OPENSSL_thread_stop();
+#else /* openssl-1.0 */
+       ERR_remove_thread_state(NULL);
+#endif
+       EVP_cleanup();
 }
 
 /*
@@ -94,7 +97,7 @@ static int read_bignum(const unsigned char *buf, size_t len, BIGNUM **result)
        return bnsize + 4;
 }
 
-static int read_rsa_bignums(const unsigned char *blob, int blen, RSA **result)
+static int read_public_key(const unsigned char *blob, int blen, RSA **result)
 {
        int ret;
        RSA *rsa;
@@ -146,7 +149,7 @@ bio_free:
        return *rsa? RSA_size(*rsa) : -E_PRIVATE_KEY;
 }
 
-static int read_private_rsa_params(const unsigned char *blob,
+static int read_openssh_private_key(const unsigned char *blob,
                const unsigned char *end, RSA **result)
 {
        int ret;
@@ -213,11 +216,11 @@ static int read_private_rsa_params(const unsigned char *blob,
        rsa->n = n;
        rsa->e = e;
        rsa->d = d;
+       rsa->iqmp = iqmp;
        rsa->p = p;
        rsa->q = q;
        rsa->dmp1 = dmp1;
        rsa->dmq1 = dmq1;
-       rsa->iqmp = iqmp;
 #endif
        *result = rsa;
        ret = 1;
@@ -264,7 +267,7 @@ static int get_private_key(const char *path, RSA **rsa)
                if (ret < 0)
                        goto free_blob;
                PARA_INFO_LOG("reading RSA params at offset %d\n", ret);
-               ret = read_private_rsa_params(blob + ret, end, rsa);
+               ret = read_openssh_private_key(blob + ret, end, rsa);
        } else
                ret = read_pem_private_key(path, rsa);
 free_blob:
@@ -277,34 +280,34 @@ int apc_get_pubkey(const char *key_file, struct asymmetric_key **result)
        unsigned char *blob;
        size_t decoded_size;
        int ret;
-       struct asymmetric_key *key = para_malloc(sizeof(*key));
+       struct asymmetric_key *pub = alloc(sizeof(*pub));
 
        ret = decode_public_key(key_file, &blob, &decoded_size);
        if (ret < 0)
                goto out;
-       ret = read_rsa_bignums(blob + ret, decoded_size - ret, &key->rsa);
+       ret = read_public_key(blob + ret, decoded_size - ret, &pub->rsa);
        if (ret < 0)
                goto free_blob;
-       ret = RSA_size(key->rsa);
+       ret = RSA_size(pub->rsa);
        assert(ret > 0);
-       *result = key;
+       *result = pub;
 free_blob:
        free(blob);
 out:
        if (ret < 0) {
-               free(key);
+               free(pub);
                *result = NULL;
                PARA_ERROR_LOG("can not load key %s\n", key_file);
        }
        return ret;
 }
 
-void apc_free_pubkey(struct asymmetric_key *key)
+void apc_free_pubkey(struct asymmetric_key *pub)
 {
-       if (!key)
+       if (!pub)
                return;
-       RSA_free(key->rsa);
-       free(key);
+       RSA_free(pub->rsa);
+       free(pub);
 }
 
 int apc_priv_decrypt(const char *key_file, unsigned char *outbuf,
@@ -318,7 +321,7 @@ int apc_priv_decrypt(const char *key_file, unsigned char *outbuf,
                return ret;
        if (inlen < 0)
                return -E_RSA;
-       priv = para_malloc(sizeof(*priv));
+       priv = alloc(sizeof(*priv));
        ret = get_private_key(key_file, &priv->rsa);
        if (ret < 0) {
                free(priv);
@@ -360,7 +363,7 @@ struct stream_cipher {
 
 struct stream_cipher *sc_new(const unsigned char *data, int len)
 {
-       struct stream_cipher *sc = para_malloc(sizeof(*sc));
+       struct stream_cipher *sc = alloc(sizeof(*sc));
 
        assert(len >= 2 * AES_CRT128_BLOCK_SIZE);
        sc->aes = EVP_CIPHER_CTX_new();
@@ -384,7 +387,7 @@ static void aes_ctr128_crypt(EVP_CIPHER_CTX *ctx, struct iovec *src,
 
        *dst = (typeof(*dst)) {
                /* Add one for the terminating zero byte. */
-               .iov_base = para_malloc(inlen + 1),
+               .iov_base = alloc(inlen + 1),
                .iov_len = inlen
        };
        ret = EVP_EncryptUpdate(ctx, dst->iov_base, &outlen, src->iov_base, inlen);
@@ -403,8 +406,24 @@ void sc_crypt(struct stream_cipher *sc, struct iovec *src, struct iovec *dst)
 
 void hash_function(const char *data, unsigned long len, unsigned char *hash)
 {
-       SHA_CTX c;
-       SHA1_Init(&c);
-       SHA1_Update(&c, data, len);
-       SHA1_Final(hash, &c);
+       EVP_MD_CTX *c = EVP_MD_CTX_new();
+       int ret = EVP_DigestInit_ex(c, EVP_sha1(), NULL);
+       assert(ret != 0);
+       ret = EVP_DigestUpdate(c, data, len);
+       assert(ret != 0);
+       ret = EVP_DigestFinal_ex(c, hash, NULL);
+       assert(ret != 0);
+       EVP_MD_CTX_free(c);
+}
+
+void hash2_function(const char *data, unsigned long len, unsigned char *hash)
+{
+       EVP_MD_CTX *c = EVP_MD_CTX_new();
+       int ret = EVP_DigestInit_ex(c, EVP_sha256(), NULL);
+       assert(ret != 0);
+       ret = EVP_DigestUpdate(c, data, len);
+       assert(ret != 0);
+       ret = EVP_DigestFinal_ex(c, hash, NULL);
+       assert(ret != 0);
+       EVP_MD_CTX_free(c);
 }
index dca6cfbad1ce3ff8cdf6d820b01bf33bf9a02110..0a291bb16c18b6705b70ff0260e97b502068f500 100644 (file)
@@ -179,7 +179,7 @@ static size_t opus_make_meta_packet(struct taginfo *tags, char **result)
        }
        PARA_DEBUG_LOG("meta packet size: %zu bytes\n", sz);
        /* terminating zero byte for the last sprintf() */
-       buf = p = para_malloc(sz + 1);
+       buf = p = alloc(sz + 1);
        memcpy(p, OPUS_COMMENT_HEADER, strlen(OPUS_COMMENT_HEADER));
        p += strlen(OPUS_COMMENT_HEADER);
        write_u32(p, comment_sz);
index 10ed394d295072d909441fe211fbcdc695ff62d0..f36990faf43a4110c8e7f7dc2f822f5a8e09046c 100644 (file)
@@ -86,7 +86,7 @@ static int opusdec_execute(struct btr_node *btrn, const char *cmd,
 
 static void opusdec_open(struct filter_node *fn)
 {
-       struct opusdec_context *ctx = para_calloc(sizeof(*ctx));
+       struct opusdec_context *ctx = zalloc(sizeof(*ctx));
 
        ogg_sync_init(&ctx->oy);
        fn->private_data = ctx;
@@ -153,7 +153,7 @@ static void opusdec_add_output(short *pcm, int frames_available,
 
        if (tmp_skip > 0) {
                short *in = pcm + ctx->channels * tmp_skip;
-               short *out = para_malloc(bytes);
+               short *out = alloc(bytes);
                memcpy(out, in, bytes);
                free(pcm);
                pcm = out;
@@ -193,7 +193,7 @@ static int decode_packet(struct opusdec_context *ctx, ogg_packet *op,
        /* don't care for anything except opus eos */
        if (op->e_o_s && ctx->os.serialno == ctx->opus_serialno)
                ctx->eos = true;
-       output = para_malloc(sizeof(short) * MAX_FRAME_SIZE * ctx->channels);
+       output = arr_alloc(sizeof(short) * ctx->channels, MAX_FRAME_SIZE);
        ret = opus_multistream_decode(ctx->st, (unsigned char *)op->packet,
                op->bytes, output, MAX_FRAME_SIZE, 0);
        if (ret < 0) {
@@ -207,7 +207,7 @@ static int decode_packet(struct opusdec_context *ctx, ogg_packet *op,
 
 #define OPUSDEC_MAX_OUTPUT_SIZE (1024 * 1024)
 
-static int opusdec_post_select(__a_unused struct sched *s, void *context)
+static int opusdec_post_monitor(__a_unused struct sched *s, void *context)
 {
        struct filter_node *fn = context;
        struct opusdec_context *ctx = fn->private_data;
@@ -217,7 +217,7 @@ static int opusdec_post_select(__a_unused struct sched *s, void *context)
 
        ret = btr_node_status(btrn, fn->min_iqs, BTR_NT_INTERNAL);
        if (ret < 0) {
-               if (ret != -E_BTR_EOF) /* fatal error */
+               if (ret != -E_EOF) /* fatal error */
                        goto out;
                if (!ctx->have_more) /* EOF */
                        goto out;
@@ -269,7 +269,7 @@ out:
        return ret;
 }
 
-static void opusdec_pre_select(struct sched *s, void *context)
+static void opusdec_pre_monitor(struct sched *s, void *context)
 {
        struct filter_node *fn = context;
        struct opusdec_context *ctx = fn->private_data;
@@ -286,7 +286,7 @@ static void opusdec_pre_select(struct sched *s, void *context)
 const struct filter lsg_filter_cmd_com_opusdec_user_data = {
        .open = opusdec_open,
        .close = opusdec_close,
-       .pre_select = opusdec_pre_select,
-       .post_select = opusdec_post_select,
+       .pre_monitor = opusdec_pre_monitor,
+       .post_monitor = opusdec_post_monitor,
        .execute = opusdec_execute,
 };
index f80301e9df282c13ef0e7d8ab2a025dd61624ba7..0814336fdc20fcebce15f344910c658726be831c 100644 (file)
--- a/oss_mix.c
+++ b/oss_mix.c
@@ -56,7 +56,7 @@ static int oss_mix_open(const char *dev, struct mixer_handle **handle)
                PARA_ERROR_LOG("could not open %s\n", dev);
                return ret;
        }
-       h = para_malloc(sizeof(*h));
+       h = alloc(sizeof(*h));
        h->fd = ret;
        *handle = h;
        return 1;
index 311a514dc86ff6bfb01abf53d16a75672827af12..4ea85afa73f45023c8f22ffe54e875343ea3d13e 100644 (file)
@@ -61,7 +61,7 @@ static int get_oss_format(enum sample_format sf)
        }
 }
 
-static void oss_pre_select(struct sched *s, void *context)
+static void oss_pre_monitor(struct sched *s, void *context)
 {
        struct writer_node *wn = context;
        struct private_oss_write_data *powd = wn->private_data;
@@ -71,7 +71,7 @@ static void oss_pre_select(struct sched *s, void *context)
                return;
        if (ret < 0 || !powd)
                return sched_min_delay(s);
-       para_fd_set(powd->fd, &s->wfds, &s->max_fileno);
+       sched_monitor_writefd(powd->fd, s);
 }
 
 static void oss_close(struct writer_node *wn)
@@ -101,7 +101,7 @@ static int oss_init(struct writer_node *wn, unsigned sample_rate,
 {
        int ret, format;
        unsigned ch, rate;
-       struct private_oss_write_data *powd = para_calloc(sizeof(*powd));
+       struct private_oss_write_data *powd = zalloc(sizeof(*powd));
        const char *dev = WRITE_CMD_OPT_STRING_VAL(OSS, DEVICE, wn->lpr);
 
        PARA_INFO_LOG("opening %s\n", dev);
@@ -178,7 +178,7 @@ err_free:
        return ret;
 }
 
-static int oss_post_select(__a_unused struct sched *s, void *context)
+static int oss_post_monitor(__a_unused struct sched *s, void *context)
 {
        struct writer_node *wn = context;
        struct private_oss_write_data *powd = wn->private_data;
@@ -199,9 +199,15 @@ static int oss_post_select(__a_unused struct sched *s, void *context)
 
                if (sound_device_is_busy())
                        return 0;
-               get_btr_sample_rate(btrn, &rate);
-               get_btr_channels(btrn, &ch);
-               get_btr_sample_format(btrn, &format);
+               ret = get_btr_sample_rate(btrn, &rate);
+               if (ret < 0)
+                       goto out;
+               ret = get_btr_channels(btrn, &ch);
+               if (ret < 0)
+                       goto out;
+               ret = get_btr_sample_format(btrn, &format);
+               if (ret < 0)
+                       goto out;
                ret = oss_init(wn, rate, ch, format);
                if (ret < 0)
                        goto out;
@@ -212,11 +218,11 @@ static int oss_post_select(__a_unused struct sched *s, void *context)
        bytes = btr_next_buffer(btrn, &data);
        frames = bytes / powd->bytes_per_frame;
        if (!frames) { /* eof and less than a single frame available */
-               ret = -E_WRITE_COMMON_EOF;
+               ret = -E_EOF;
                goto out;
        }
        ret = 0;
-       if (!FD_ISSET(powd->fd, &s->wfds))
+       if (!sched_write_ok(powd->fd, s))
                goto out;
        /* get maximal number of bytes that can be written */
        ret = ioctl(powd->fd, SNDCTL_DSP_GETOSPACE, &abi);
@@ -239,7 +245,7 @@ out:
 }
 
 const struct writer lsg_write_cmd_com_oss_user_data = {
-       .pre_select = oss_pre_select,
-       .post_select = oss_post_select,
+       .pre_monitor = oss_pre_monitor,
+       .post_monitor = oss_post_monitor,
        .close = oss_close,
 };
diff --git a/para.h b/para.h
index b406818bd52a27bfc1794e0c95d8f9c0e9ef12a0..280c282323db369e7d0e84173324016aa425a92d 100644 (file)
--- a/para.h
+++ b/para.h
@@ -20,6 +20,8 @@
 #include <stdbool.h>
 #include <inttypes.h>
 #include <sys/uio.h>
+#include <poll.h>
+
 #include "gcc-compat.h"
 
 /** used in various contexts */
@@ -44,7 +46,6 @@
        typeof(x) _x = (x); \
        _x > 0? _x : -_x; })
 
-
 extern __printf_2_3 void (*para_log)(int, const char*, ...);
 /**
  * Define a standard log function that always writes to stderr.
@@ -221,6 +222,7 @@ enum loglevels {LOGLEVELS, NUM_LOGLEVELS};
 #define PARA_CRIT_LOG(f,...) para_log(LL_CRIT, "%s: " f, __FUNCTION__, ## __VA_ARGS__)
 #define PARA_EMERG_LOG(f,...) para_log(LL_EMERG, "%s: " f, __FUNCTION__, ## __VA_ARGS__)
 
+/** \cond status_items */
 #define STATUS_ITEMS \
        STATUS_ITEM(basename) \
        STATUS_ITEM(status) \
@@ -268,6 +270,7 @@ enum loglevels {LOGLEVELS, NUM_LOGLEVELS};
 enum status_items {STATUS_ITEMS NUM_STAT_ITEMS};
 #undef STATUS_ITEM
 #define STATUS_ITEM(_name) #_name,
+/** \endcond status items */
 
 extern const char *status_item_list[];
 /** Loop over each status item. */
diff --git a/play.c b/play.c
index 86edc4d4e4f47bc1bd38197ed5982e97df9f72cf..bd183b6b8dd48906e08cf142d7ff8c1e05aad48a 100644 (file)
--- a/play.c
+++ b/play.c
@@ -7,6 +7,7 @@
 #include <lopsub.h>
 
 #include "recv_cmd.lsg.h"
+#include "filter_cmd.lsg.h"
 #include "play_cmd.lsg.h"
 #include "write_cmd.lsg.h"
 #include "play.lsg.h"
 #include "write.h"
 #include "fd.h"
 
-/**
- * Besides playback tasks which correspond to the receiver/filter/writer nodes,
- * para_play creates two further tasks: The play task and the i9e task. It is
- * important whether a function can be called in the context of para_play or
- * i9e or both. As a rule, all command handlers are called only in i9e context via
- * the line handler (input mode) or the key handler (command mode) below.
- *
- * Playlist handling is done exclusively in play context.
- */
-
 /** Array of error strings. */
 DEFINE_PARA_ERRLIST;
 
@@ -50,7 +41,7 @@ static struct lls_parse_result *play_lpr;
  * Describes a request to change the state of para_play.
  *
  * There is only one variable of this type: \a rq of the global play task
- * structure. Command handlers only set this variable and the post_select()
+ * structure. Command handlers only set this variable and the post_monitor()
  * function of the play task investigates its value during each iteration of
  * the scheduler run and performs the actual work.
  */
@@ -109,10 +100,6 @@ typedef int (*play_cmd_handler_t)(struct lls_parse_result *lpr);
 struct play_command_info {
        play_cmd_handler_t handler;
 };
-#define EXPORT_PLAY_CMD_HANDLER(_cmd) \
-       const struct play_command_info lsg_play_cmd_com_ ## _cmd ## _user_data = { \
-               .handler = com_ ## _cmd \
-       };
 
 static int loglevel = LL_WARNING;
 
@@ -121,7 +108,7 @@ INIT_STDERR_LOGGING(loglevel);
 
 char *stat_item_values[NUM_STAT_ITEMS] = {NULL};
 
-static struct sched sched = {.max_fileno = 0};
+static struct sched sched;
 static struct play_task play_task, *pt = &play_task;
 
 #define AFH_RECV_CMD (lls_cmd(LSG_RECV_CMD_CMD_AFH, recv_cmd_suite))
@@ -196,6 +183,7 @@ static char get_playback_state(void)
        assert(false);
 };
 
+/* returns number of milliseconds */
 static long unsigned get_play_time(void)
 {
        char state = get_playback_state();
@@ -205,21 +193,20 @@ static long unsigned get_play_time(void)
                return 0;
        if (pt->num_chunks == 0 || pt->seconds == 0)
                return 0;
-       /* where the stream started (in seconds) */
-       result = pt->start_chunk * pt->seconds / pt->num_chunks;
+       /* where the stream started (in milliseconds) */
+       result = 1000ULL * pt->start_chunk * pt->seconds / pt->num_chunks;
        if (pt->wn.btrn) { /* Add the uptime of the writer node */
                struct timeval diff = {.tv_sec = 0}, wstime;
                btr_get_node_start(pt->wn.btrn, &wstime);
                if (wstime.tv_sec > 0)
                        tv_diff(now, &wstime, &diff);
-               result += diff.tv_sec;
+               result += tv2ms(&diff);
        }
-       result = PARA_MIN(result, pt->seconds);
+       result = PARA_MIN(result, pt->seconds * 1000);
        result = PARA_MAX(result, 0UL);
        return result;
 }
 
-
 static void wipe_receiver_node(void)
 {
        PARA_NOTICE_LOG("cleaning up receiver node\n");
@@ -243,8 +230,7 @@ static int get_playback_error(void)
                return 0;
        if (task_status(pt->rn.task) >= 0)
                return 0;
-       if (err == -E_BTR_EOF || err == -E_RECV_EOF || err == -E_EOF
-                       || err == -E_WRITE_COMMON_EOF)
+       if (err == -E_EOF)
                return 1;
        return err;
 }
@@ -270,6 +256,7 @@ static int eof_cleanup(void)
        if (decoder->close)
                decoder->close(&pt->fn);
        btr_remove_node(&pt->fn.btrn);
+       lls_free_parse_result(pt->fn.lpr, FILTER_CMD(pt->fn.filter_num));
        free(pt->fn.conf);
        memset(&pt->fn, 0, sizeof(struct filter_node));
 
@@ -293,7 +280,7 @@ static int shuffle_compare(__a_unused const void *a, __a_unused const void *b)
 static void init_shuffle_map(void)
 {
        unsigned n, num_inputs = lls_num_inputs(play_lpr);
-       shuffle_map = para_malloc(num_inputs * sizeof(unsigned));
+       shuffle_map = arr_alloc(num_inputs, sizeof(unsigned));
        for (n = 0; n < num_inputs; n++)
                shuffle_map[n] = n;
        if (!OPT_GIVEN(RANDOMIZE))
@@ -410,16 +397,16 @@ static int load_file(void)
        pt->rn.task = task_register(
                &(struct task_info) {
                        .name = lls_command_name(AFH_RECV_CMD),
-                       .pre_select = AFH_RECV->pre_select,
-                       .post_select = AFH_RECV->post_select,
+                       .pre_monitor = AFH_RECV->pre_monitor,
+                       .post_monitor = AFH_RECV->post_monitor,
                        .context = &pt->rn
                }, &sched);
        sprintf(buf, "%s decoder", af);
        pt->fn.task = task_register(
                &(struct task_info) {
                        .name = buf,
-                       .pre_select = decoder->pre_select,
-                       .post_select = decoder->post_select,
+                       .pre_monitor = decoder->pre_monitor,
+                       .post_monitor = decoder->post_monitor,
                        .context = &pt->fn
                }, &sched);
        register_writer_node(&pt->wn, pt->fn.btrn, &sched);
@@ -434,6 +421,15 @@ static int next_valid_file(void)
        int i, j = pt->current_file;
        unsigned num_inputs = lls_num_inputs(play_lpr);
 
+       if (j == num_inputs - 1) {
+               switch (OPT_UINT32_VAL(END_OF_PLAYLIST)) {
+               case EOP_LOOP: break;
+               case EOP_STOP:
+                       pt->playing = false;
+                       return 0;
+               case EOP_QUIT: return -E_EOP;
+               }
+       }
        for (i = 0; i < num_inputs; i++) {
                j = (j + 1) % num_inputs;
                if (!pt->invalid[j])
@@ -587,7 +583,7 @@ static char *get_user_key_map_seq(int key)
        if (!p)
                return NULL;
        len = p - kma;
-       result = para_malloc(len + 1);
+       result = alloc(len + 1);
        memcpy(result, kma, len);
        result[len] = '\0';
        return result;
@@ -607,7 +603,7 @@ static char *get_key_map_seq_safe(int key)
 
        if (len == 1 && isprint(*seq))
                return seq;
-       sseq = para_malloc(2 + 2 * len + 1);
+       sseq = alloc(2 + 2 * len + 1);
        sseq[0] = '0';
        sseq[1] = 'x';
        for (n = 0; n < len; n++) {
@@ -647,7 +643,7 @@ static char **get_mapped_keyseqs(void)
        char **result;
        int i;
 
-       result = para_malloc((NUM_MAPPED_KEYS + 1) * sizeof(char *));
+       result = arr_alloc(NUM_MAPPED_KEYS + 1, sizeof(char *));
        FOR_EACH_MAPPED_KEY(i) {
                char *seq = get_key_map_seq(i);
                result[i] = seq;
@@ -705,6 +701,11 @@ static void detach_stdout(void)
        btr_remove_node(&pt->btrn);
 }
 
+#define EXPORT_PLAY_CMD_HANDLER(_cmd) \
+       const struct play_command_info lsg_play_cmd_com_ ## _cmd ## _user_data = { \
+               .handler = com_ ## _cmd \
+       };
+
 static int com_quit(__a_unused struct lls_parse_result *lpr)
 {
        pt->rq = CRT_TERM_RQ;
@@ -831,20 +832,21 @@ EXPORT_PLAY_CMD_HANDLER(play);
 static int com_pause(__a_unused struct lls_parse_result *lpr)
 {
        char state;
-       long unsigned seconds, ss;
+       uint64_t ms;
+       unsigned long cn; /* chunk num */
 
        state = get_playback_state();
        pt->playing = false;
        if (state != 'P')
                return 0;
-       seconds = get_play_time();
+       ms = get_play_time();
        pt->playing = false;
-       ss = 0;
+       cn = 0;
        if (pt->seconds > 0)
-               ss = seconds * pt->num_chunks / pt->seconds + 1;
-       ss = PARA_MAX(ss, 0UL);
-       ss = PARA_MIN(ss, pt->num_chunks);
-       pt->start_chunk = ss;
+               cn = ms * pt->num_chunks / pt->seconds / 1000 + 1;
+       cn = PARA_MIN(cn, pt->num_chunks);
+       pt->start_chunk = cn;
+       pt->rq = CRT_REPOS;
        kill_stream();
        return 0;
 }
@@ -943,7 +945,7 @@ static int com_ff(struct lls_parse_result *lpr)
                return ret;
        if (pt->playing && !pt->fn.btrn)
                return 0;
-       seconds += get_play_time();
+       seconds += (get_play_time() + 500) / 1000;
        seconds = PARA_MIN(seconds, (typeof(seconds))pt->seconds - 4);
        seconds = PARA_MAX(seconds, 0);
        pt->start_chunk = pt->num_chunks * seconds / pt->seconds;
@@ -1043,9 +1045,16 @@ static void session_open(void)
                history_file = para_strdup(OPT_STRING_VAL(HISTORY_FILE));
        else {
                char *home = para_homedir();
-               history_file = make_message("%s/.paraslash/play.history",
-                       home);
+               char *dot_para = make_message("%s/.paraslash", home);
+
                free(home);
+               ret = para_mkdir(dot_para);
+               /* warn, but otherwise ignore mkdir error */
+               if (ret < 0)
+                       PARA_WARNING_LOG("Can not create %s: %s\n", dot_para,
+                               para_strerror(-ret));
+               history_file = make_message("%s/play.history", dot_para);
+               free(dot_para);
        }
        ici.history_file = history_file;
        ici.loglevel = loglevel;
@@ -1058,7 +1067,7 @@ static void session_open(void)
        sigemptyset(&act.sa_mask);
        act.sa_flags = 0;
        sigaction(SIGWINCH, &act, NULL);
-       sched.select_function = i9e_select;
+       sched.poll_function = i9e_poll;
 
        ici.bound_keyseqs = get_mapped_keyseqs();
        pt->btrn = ici.producer = btr_new_node(&(struct btr_node_description)
@@ -1086,22 +1095,22 @@ static void session_update_time_string(char *str, unsigned len)
                if (btr_get_input_queue_size(pt->btrn) > 0)
                        return;
        }
-       ie9_print_status_bar(str, len);
+       i9e_print_status_bar(str, len);
 }
 
 /*
  * If we are about to die we must call i9e_close() to reset the terminal.
  * However, i9e_close() must be called in *this* context, i.e. from
- * play_task.post_select() rather than from i9e_post_select(), because
+ * play_task.post_monitor() rather than from i9e_post_monitor(), because
  * otherwise i9e would access freed memory upon return. So the play task must
  * stay alive until the i9e task terminates.
  *
  * We achieve this by sending a fake SIGTERM signal via i9e_signal_dispatch()
- * and reschedule. In the next iteration, i9e->post_select returns an error and
+ * and reschedule. In the next iteration, i9e->post_monitor returns an error and
  * terminates. Subsequent calls to i9e_get_error() then return negative and we
  * are allowed to call i9e_close() and terminate as well.
  */
-static int session_post_select(__a_unused struct sched *s)
+static int session_post_monitor(__a_unused struct sched *s)
 {
        int ret;
 
@@ -1124,11 +1133,11 @@ static int session_post_select(__a_unused struct sched *s)
 
 #else /* HAVE_READLINE */
 
-static int session_post_select(struct sched *s)
+static int session_post_monitor(struct sched *s)
 {
        char c;
 
-       if (!FD_ISSET(STDIN_FILENO, &s->rfds))
+       if (!sched_read_ok(STDIN_FILENO, s))
                return 0;
        if (read(STDIN_FILENO, &c, 1))
                do_nothing;
@@ -1147,11 +1156,11 @@ static void session_update_time_string(char *str, __a_unused unsigned len)
 }
 #endif /* HAVE_READLINE */
 
-static void play_pre_select(struct sched *s, __a_unused void *context)
+static void play_pre_monitor(struct sched *s, __a_unused void *context)
 {
        char state;
 
-       para_fd_set(STDIN_FILENO, &s->rfds, &s->max_fileno);
+       sched_monitor_readfd(STDIN_FILENO, s);
        state = get_playback_state();
        if (state == 'R' || state == 'F' || state == 'X')
                return sched_min_delay(s);
@@ -1171,7 +1180,7 @@ static unsigned get_time_string(char **result)
        length = pt->seconds;
        if (length == 0)
                return xasprintf(result, "0:00 [0:00] (0%%/0:00)");
-       seconds = get_play_time();
+       seconds = (get_play_time() + 500) / 1000;
        return xasprintf(result, "#%u: %d:%02d [%d:%02d] (%d%%/%d:%02d) %s",
                pt->current_file,
                seconds / 60,
@@ -1185,7 +1194,7 @@ static unsigned get_time_string(char **result)
        );
 }
 
-static int play_post_select(struct sched *s, __a_unused void *context)
+static int play_post_monitor(struct sched *s, __a_unused void *context)
 {
        int ret;
 
@@ -1194,7 +1203,7 @@ static int play_post_select(struct sched *s, __a_unused void *context)
                pt->rq = CRT_TERM_RQ;
                return 0;
        }
-       ret = session_post_select(s);
+       ret = session_post_monitor(s);
        if (ret < 0)
                goto out;
        if (!pt->wn.btrn && !pt->fn.btrn) {
@@ -1228,29 +1237,43 @@ out:
 /**
  * The main function of para_play.
  *
- * \param argc Standard.
- * \param argv Standard.
+ * \param argc See man page.
+ * \param argv See man page.
+ *
+ * para_play distributes its work by submitting various tasks to the paraslash
+ * scheduler. The receiver, filter and writer tasks, which are used to play an
+ * audio file, require one task each to maintain their underlying buffer tree
+ * node. These tasks only exist when an audio file is playing.
+ *
+ * The i9 task, which is submitted and maintained by the i9e subsystem, reads
+ * an input line and calls the corresponding command handler such as com_stop()
+ * which is implemented in this file. The command handlers typically write a
+ * request to the global play_task structure, whose contents are read and acted
+ * upon by another task, the play task.
+ *
+ * As a rule, playlist handling is performed exclusively in play context, i.e.
+ * in the post-monitor step of the play task, while command handlers are only
+ * called in i9e context.
  *
- * \return \p EXIT_FAILURE or \p EXIT_SUCCESS.
+ * \return EXIT_FAILURE or EXIT_SUCCESS.
  */
 int main(int argc, char *argv[])
 {
        int ret;
        unsigned num_inputs;
 
-       sched.default_timeout.tv_sec = 5;
+       sched.default_timeout = 5000;
        parse_config_or_die(argc, argv);
        session_open();
        num_inputs = lls_num_inputs(play_lpr);
        init_shuffle_map();
-       pt->invalid = para_calloc(sizeof(*pt->invalid) * num_inputs);
+       pt->invalid = arr_zalloc(num_inputs, sizeof(*pt->invalid));
        pt->rq = CRT_FILE_CHANGE;
-       pt->current_file = num_inputs - 1;
        pt->playing = true;
        pt->task = task_register(&(struct task_info){
                .name = "play",
-               .pre_select = play_pre_select,
-               .post_select = play_post_select,
+               .pre_monitor = play_pre_monitor,
+               .post_monitor = play_post_monitor,
                .context = pt,
        }, &sched);
        ret = schedule(&sched);
index 5f83b0fe0a35c9e5c31c97828bfa7af2bebc6208..c145b0fd80ce0520bda58f0949c46211eeee5fdd 100644 (file)
 
 /** \file playlist.c Functions for loading and saving playlists. */
 
-/** Structure used for adding entries to a playlist. */
-struct playlist_info {
+/**
+ * The state of a playlist instance.
+ *
+ * A structure of this type is allocated and initialized at playlist load time.
+ */
+struct playlist_instance {
        /** The name of the playlist. */
        char *name;
        /** The number of entries currently in the playlist. */
        unsigned length;
+       /** Contains all valid paths of the playlist. */
+       struct osl_table *score_table;
 };
-static struct playlist_info current_playlist;
+static struct playlist_instance current_playlist;
 
 /**
  * Re-insert an audio file into the tree of admissible files.
@@ -38,7 +44,7 @@ static int playlist_update_audio_file(const struct osl_row *aft_row)
 
 static int add_playlist_entry(char *path, void *data)
 {
-       struct playlist_info *playlist = data;
+       struct playlist_instance *pi = data;
        struct osl_row *aft_row;
        int ret = aft_get_row_of_path(path, &aft_row);
 
@@ -46,73 +52,43 @@ static int add_playlist_entry(char *path, void *data)
                PARA_NOTICE_LOG("%s: %s\n", path, para_strerror(-ret));
                return 1;
        }
-       ret = score_add(aft_row, -playlist->length);
+       ret = score_add(aft_row, -pi->length, pi->score_table);
        if (ret < 0) {
                PARA_ERROR_LOG("failed to add %s: %s\n", path, para_strerror(-ret));
                return ret;
        }
-       playlist->length++;
-       return 1;
-}
-
-/* returns -E_PLAYLIST_LOADED on _success_ to terminate the loop */
-static int load_playlist(struct osl_row *row, void *data)
-{
-       struct playlist_info *playlist = data;
-       struct osl_object playlist_def;
-       char *playlist_name;
-       int ret;
-
-       ret = pl_get_name_and_def_by_row(row, &playlist_name, &playlist_def);
-       if (ret < 0)
-               goto err;
-       playlist->length = 0;
-       ret = for_each_line(FELF_READ_ONLY, playlist_def.data,
-               playlist_def.size, add_playlist_entry, playlist);
-       osl_close_disk_object(&playlist_def);
-       if (ret < 0)
-               goto err;
-       ret = -E_PLAYLIST_EMPTY;
-       if (!playlist->length)
-               goto err;
-       playlist->name = para_strdup(playlist_name);
-       PARA_NOTICE_LOG("loaded playlist %s (%u files)\n", playlist->name,
-               playlist->length);
-       return -E_PLAYLIST_LOADED;
-err:
-       if (ret != -E_DUMMY_ROW)
-               PARA_NOTICE_LOG("unable to load playlist (%s)\n",
-                       para_strerror(-ret));
+       pi->length++;
        return 1;
 }
 
 static int check_playlist_path(char *path, void *data)
 {
-       struct para_buffer *pb = data;
+       struct afs_callback_arg *aca = data;
        struct osl_row *aft_row;
        int ret = aft_get_row_of_path(path, &aft_row);
 
        if (ret < 0)
-               para_printf(pb, "%s: %s\n", path, para_strerror(-ret));
+               afs_error(aca, "%s: %s\n", path, para_strerror(-ret));
        return 1; /* do not fail the loop on bad paths */
 }
 
 static int check_playlist(struct osl_row *row, void *data)
 {
-       struct para_buffer *pb = data;
+       struct afs_callback_arg *aca = data;
+       struct para_buffer *pb = &aca->pbout;
        struct osl_object playlist_def;
        char *playlist_name;
        int ret = pl_get_name_and_def_by_row(row, &playlist_name, &playlist_def);
 
        if (ret < 0) { /* log error, but continue */
-               para_printf(pb, "failed to get playlist data: %s\n",
+               afs_error(aca, "failed to get playlist data: %s\n",
                        para_strerror(-ret));
                return 1;
        }
        if (*playlist_name) { /* skip dummy row */
                para_printf(pb, "checking playlist %s...\n", playlist_name);
                for_each_line(FELF_READ_ONLY, playlist_def.data,
-                       playlist_def.size, check_playlist_path, pb);
+                       playlist_def.size, check_playlist_path, aca);
        }
        osl_close_disk_object(&playlist_def);
        return 1;
@@ -129,49 +105,110 @@ static int check_playlist(struct osl_row *row, void *data)
 int playlist_check_callback(struct afs_callback_arg *aca)
 {
        para_printf(&aca->pbout, "checking playlists...\n");
-       return osl(osl_rbtree_loop(playlists_table, BLOBCOL_ID, &aca->pbout,
+       return osl(osl_rbtree_loop(playlists_table, BLOBCOL_ID, aca,
                check_playlist));
 }
 
 /**
- * Close the current playlist.
+ * Free all resources of the given/current playlist.
  *
- * \sa \ref playlist_open().
+ * \param pi NULL means to unload the current playlist.
  */
-void playlist_close(void)
+void playlist_unload(struct playlist_instance *pi)
 {
+       if (pi) {
+               score_close(pi->score_table);
+               free(pi->name);
+               free(pi);
+               return;
+       }
        if (!current_playlist.name)
                return;
+       score_clear();
        free(current_playlist.name);
        current_playlist.name = NULL;
+       current_playlist.length = 0;
 }
 
 /**
- * Open the given playlist.
+ * Populate the score table from the paths of a playlist database object.
  *
- * \param name The name of the playlist to open.
+ * This loads the blob object which corresponds to the given name from the
+ * playlist table. Each line of the blob is regarded as a path which is looked
+ * up in the audio file table. If the path lookup succeeds, a reference to the
+ * corresponding row of the audio file table is added to the score table.
  *
- * Files which are listed in the playlist, but not contained in the database
- * are ignored.  This is not considered an error.
+ * \param name The name of the playlist to load.
+ * \param result Opaque, refers to the underlying score table.
+ * \param msg Error message or playlist info is returned here.
  *
- * \return Standard.
+ * \return The length of the loaded playlist on success, negative error code
+ * else. Files which are listed in the playlist, but are not contained in the
+ * database are ignored. This is not considered an error.
  */
-int playlist_open(const char *name)
+int playlist_load(const char *name, struct playlist_instance **result, char **msg)
 {
-       struct osl_object obj;
        int ret;
-       struct osl_row *row;
+       struct playlist_instance *pi;
+       struct osl_object playlist_def;
 
-       obj.data = (char *)name;
-       obj.size = strlen(obj.data);
-       ret = osl(osl_get_row(playlists_table, BLOBCOL_NAME, &obj, &row));
-       if (ret < 0) {
-               PARA_NOTICE_LOG("failed to load playlist %s\n", name);
-               return ret;
+       if (!name || !*name) {
+               if (msg)
+                       *msg = make_message("empty playlist name\n");
+               return -ERRNO_TO_PARA_ERROR(EINVAL);
+       }
+       ret = pl_get_def_by_name(name, &playlist_def);
+       if (ret < 0)
+               goto err;
+       pi = zalloc(sizeof(*pi));
+       if (result)
+               score_open(&pi->score_table);
+       ret = for_each_line(FELF_READ_ONLY, playlist_def.data,
+               playlist_def.size, add_playlist_entry, pi);
+       osl_close_disk_object(&playlist_def);
+       if (ret < 0)
+               goto close_score_table;
+       ret = -E_PLAYLIST_EMPTY;
+       if (pi->length == 0)
+               goto close_score_table;
+       /* success */
+       if (msg)
+               *msg = make_message("loaded playlist %s (%u files)\n", name,
+                       pi->length);
+       pi->name = para_strdup(name);
+       if (result)
+               *result = pi;
+       else {
+               playlist_unload(NULL);
+               current_playlist = *pi;
        }
-       playlist_close();
-       ret = load_playlist(row, &current_playlist);
-       return (ret == -E_PLAYLIST_LOADED)? current_playlist.length : ret;
+       return pi->length;
+close_score_table:
+       if (result)
+               score_close(pi->score_table);
+       free(pi);
+err:
+       PARA_NOTICE_LOG("unable to load playlist %s\n", name);
+       if (msg)
+               *msg = make_message("unable to load playlist %s\n", name);
+       return ret;
+}
+
+/**
+ * Iterate over all admissible audio files of a playlist instance.
+ *
+ * This wrapper around \ref score_loop() is the playlist counterpart of \ref
+ * mood_loop().
+ *
+ * \param pi Determines the score table to iterate. Must not be NULL.
+ * \param func See \ref score_loop().
+ * \param data See \ref score_loop().
+ *
+ * \return See \ref score_loop(), \ref mood_loop().
+ */
+int playlist_loop(struct playlist_instance *pi, osl_rbtree_loop_func *func, void *data)
+{
+       return score_loop(func, pi->score_table, data);
 }
 
 static int search_path(char *path, void *data)
@@ -183,17 +220,14 @@ static int search_path(char *path, void *data)
 
 static int handle_audio_file_event(enum afs_events event, void *data)
 {
-       int ret, was_admissible = 0, is_admissible;
+       int ret;
+       bool was_admissible = false, is_admissible;
        struct osl_object playlist_def;
        char *new_path;
        const struct osl_row *row = data;
 
-       if (event == AUDIO_FILE_RENAME) {
-               ret = row_belongs_to_score_table(row, NULL);
-               if (ret < 0)
-                       return ret;
-               was_admissible = ret;
-       }
+       if (event == AUDIO_FILE_RENAME)
+               was_admissible = row_belongs_to_score_table(row);
        ret = get_audio_file_path_of_row(row, &new_path);
        if (ret < 0)
                return ret;
@@ -214,7 +248,7 @@ static int handle_audio_file_event(enum afs_events event, void *data)
        }
        /* !was_admissible && is_admissible */
        current_playlist.length++;
-       return score_add(row, 0); /* play it immediately */
+       return score_add(row, 0, NULL); /* play it immediately */
 }
 
 /**
@@ -229,7 +263,6 @@ static int handle_audio_file_event(enum afs_events event, void *data)
 int playlists_event_handler(enum afs_events event,
        __a_unused struct para_buffer *pb, void *data)
 {
-       int ret;
        struct afsi_change_event_data *aced = data;
 
        if (!current_playlist.name)
@@ -241,10 +274,7 @@ int playlists_event_handler(enum afs_events event,
        case AUDIO_FILE_ADD:
                return handle_audio_file_event(event, data);
        case AUDIO_FILE_REMOVE:
-               ret = row_belongs_to_score_table(data, NULL);
-               if (ret < 0)
-                       return ret;
-               if (!ret)
+               if (!row_belongs_to_score_table(data))
                        return 1;
                current_playlist.length--;
                return score_delete(data);
index ad2373767a8b8df5793472b795ac3d5b85327b55..28b710108d435bd40d5970ca117b103d6fb0b1a3 100644 (file)
@@ -22,7 +22,7 @@ struct private_prebuffer_data {
        struct timeval barrier;
 };
 
-static void prebuffer_pre_select(struct sched *s, void *context)
+static void prebuffer_pre_monitor(struct sched *s, void *context)
 {
        struct filter_node *fn = context;
        struct btr_node *btrn = fn->btrn;
@@ -50,14 +50,21 @@ static void prebuffer_close(struct filter_node *fn)
        free(fn->private_data);
 }
 
-static int prebuffer_post_select(__a_unused struct sched *s, void *context)
+static int prebuffer_post_monitor(__a_unused struct sched *s, void *context)
 {
        struct filter_node *fn = context;
        struct btr_node *btrn = fn->btrn;
        size_t iqs = btr_get_input_queue_size(btrn);
        struct private_prebuffer_data *ppd = fn->private_data;
        uint32_t size = FILTER_CMD_OPT_UINT32_VAL(PREBUFFER, SIZE, fn->lpr);
+       int ret;
 
+       ret = task_get_notification(fn->task);
+       if (ret < 0)
+               goto fail;
+       ret = btr_node_status(btrn, size, BTR_NT_INTERNAL);
+       if (ret < 0)
+               goto fail;
        if (ppd->barrier.tv_sec == 0)
                return 0;
        if (tv_diff(now, &ppd->barrier, NULL) < 0)
@@ -66,17 +73,20 @@ static int prebuffer_post_select(__a_unused struct sched *s, void *context)
                return 0;
        btr_splice_out_node(&fn->btrn);
        return -E_PREBUFFER_SUCCESS;
+fail:
+       btr_remove_node(&fn->btrn);
+       return ret;
 }
 
 static void prebuffer_open(struct filter_node *fn)
 {
-       struct private_prebuffer_data *ppd = para_calloc(sizeof(*ppd));
+       struct private_prebuffer_data *ppd = zalloc(sizeof(*ppd));
        fn->private_data = ppd;
 }
 
 const struct filter lsg_filter_cmd_com_prebuffer_user_data = {
        .open = prebuffer_open,
        .close = prebuffer_close,
-       .pre_select = prebuffer_pre_select,
-       .post_select = prebuffer_post_select,
+       .pre_monitor = prebuffer_pre_monitor,
+       .post_monitor = prebuffer_post_monitor,
 };
diff --git a/recv.c b/recv.c
index 10d55d218071ccf6a314fc87d4e5f78001012db4..68417187348b058b375993b9a74eb6c2ca7d826b 100644 (file)
--- a/recv.c
+++ b/recv.c
@@ -97,13 +97,12 @@ int main(int argc, char *argv[])
        stdout_task_register(&sot, &s);
 
        ti.name = lls_command_name(cmd);
-       ti.pre_select = r->pre_select;
-       ti.post_select = r->post_select;
+       ti.pre_monitor = r->pre_monitor;
+       ti.post_monitor = r->post_monitor;
        ti.context = &rn;
        rn.task = task_register(&ti, &s);
 
-       s.default_timeout.tv_sec = 1;
-       s.default_timeout.tv_usec = 0;
+       s.default_timeout = 1000;
        ret = schedule(&s);
        sched_shutdown(&s);
        r->close(&rn);
diff --git a/recv.h b/recv.h
index 36b0f1db62e348c9ab7ccdfc3b32ba656d698c5f..391395b241d67abf12eb70996a10ee7950519087 100644 (file)
--- a/recv.h
+++ b/recv.h
@@ -21,11 +21,11 @@ struct receiver_node {
        /**
         * The file descriptor to receive the stream.
         *
-        * The pre_select function of the receiver adds this file descriptor to
+        * The pre_monitor function of the receiver adds this file descriptor to
         * the set of file descriptors which are watched for readability or
         * writability, depending on the state of the connection (if any).
         *
-        * If \a fd is readable, the post_select function of the receiver reads
+        * If \a fd is readable, the post_monitor function of the receiver reads
         * data from this fd into the buffer pool area of \a btrp.
         *
         * \sa \ref receiver.
@@ -34,9 +34,18 @@ struct receiver_node {
 };
 
 /**
- * Describes one supported paraslash receiver.
+ * Describes a possible data source for audio streams.
  *
- * \sa \ref http_recv.c, \ref udp_recv.c.
+ * A paraslash receiver is a modular piece of software which is capable of
+ * receiving an audio data stream from a data source. Received audio data is
+ * fed to consumers through the buffer tree mechanism.
+ *
+ * This structure contains the methods which have to be implemented by each
+ * receiver.
+ *
+ * \sa \ref http_recv.c, \ref udp_recv.c, \ref dccp_recv.c, \ref afh_recv.c,
+ * struct \ref receiver_node, struct \ref filter, struct \ref writer, struct
+ * \ref sched.
  */
 struct receiver {
        /**
@@ -45,8 +54,6 @@ struct receiver {
         * This should allocate the output buffer of the given receiver node
         * and prepare it for retrieving the audio stream according to the
         * configuration stored in rn->lpr.
-        *
-        * \sa struct \ref receiver_node.
         */
        int (*open)(struct receiver_node *rn);
        /**
@@ -58,31 +65,10 @@ struct receiver {
         * \sa \ref receiver_node.
         */
        void (*close)(struct receiver_node *rn);
-       /**
-        * Add file descriptors to fd_sets and compute timeout for select(2).
-        *
-        * If this is not NULL, the function is called in each iteration of the
-        * scheduler's select loop. The receiver may define it to add file
-        * descriptors to the file descriptor sets given by s. Those will be
-        * monitored in the subsequent call to select(2). The function may also
-        * lower the timeout value of s to make select(2) return earlier if no
-        * file descriptors are ready for I/O.
-        *
-        * \sa select(2), \ref time.c, struct \ref sched.
-        */
-       void (*pre_select)(struct sched *s, void *context);
-       /**
-        * Evaluate the result from select(2).
-        *
-        * This is called after the call to select(2). It should check all file
-        * descriptors which were added to any of the fd sets in the previous
-        * call to ->pre_select() and perform (non-blocking) I/O operations on
-        * those fds which are ready for I/O, for example in order to establish
-        * a connection or to receive a part of the audio stream.
-        *
-        * \sa select(2), struct \ref receiver.
-        */
-       int (*post_select)(struct sched *s, void *context);
+       /** Ask the scheduler to monitor receive fds. */
+       void (*pre_monitor)(struct sched *s, void *context);
+       /** Receive data and make it available to consumers. */
+       int (*post_monitor)(struct sched *s, void *context);
        /**
         * Answer a buffer tree query.
         *
@@ -110,4 +96,4 @@ struct receiver {
 
 int check_receiver_arg(const char *ra, struct lls_parse_result **lprp);
 void print_receiver_helps(bool detailed);
-int generic_recv_pre_select(struct sched *s, struct receiver_node *rn);
+int generic_recv_pre_monitor(struct sched *s, struct receiver_node *rn);
index 31fd81f1ec52c3d0b6204e8b3663a7d36e14fbb3..1939300a7c946d68d1b324ee6beb8c0bf146893b 100644 (file)
@@ -41,7 +41,7 @@ int check_receiver_arg(const char *ra, struct lls_parse_result **lprp)
        *lprp = NULL;
        if (!ra || !*ra) {
                argc = 1;
-               argv = para_malloc(2 * sizeof(char*));
+               argv = alloc(2 * sizeof(char*));
                argv[0] = para_strdup("http");
                argv[1] = NULL;
        } else {
@@ -98,19 +98,19 @@ void print_receiver_helps(bool detailed)
 }
 
 /**
- * Simple pre-select hook, used by all receivers.
+ * Request a minimal timeout in case of buffer tree errors.
  *
- * \param s Scheduler info.
- * \param rn The receiver node.
+ * \param s The scheduler instance.
+ * \param rn The buffer tree node is derived from this.
  *
- * This requests a minimal delay from the scheduler if the status of the buffer
- * tree node indicates an error/eof condition. No file descriptors are added to
- * the fd sets of \a s.
+ * If the buffer tree node of the given receiver node is in error or EOF state,
+ * a minimal I/O timeout is requested from the scheduler. Otherwise, the
+ * function does nothing. No file descriptors are asked to be monitored.
  *
- * \return The status of the btr node of the receiver node, i.e. the return
- * value of the underlying call to \ref btr_node_status().
+ * \return The status of of the receiver node's buffer tree node. That is, the
+ * return value of the underlying call to \ref btr_node_status().
  */
-int generic_recv_pre_select(struct sched *s, struct receiver_node *rn)
+int generic_recv_pre_monitor(struct sched *s, struct receiver_node *rn)
 {
        int ret = btr_node_status(rn->btrn, 0, BTR_NT_ROOT);
 
index bbdda51c525630c6d1411dfa547193f8ccdae9ce..72cb3f62ea766ee7ed94e3a68ed5a17591f2f133 100644 (file)
@@ -51,7 +51,7 @@ static void resample_close(struct filter_node *fn)
 
 static void resample_open(struct filter_node *fn)
 {
-       struct resample_context *ctx = para_calloc(sizeof(*ctx));
+       struct resample_context *ctx = zalloc(sizeof(*ctx));
        struct btr_node *btrn = fn->btrn;
        struct wav_params wp;
 
@@ -62,7 +62,7 @@ static void resample_open(struct filter_node *fn)
        btr_log_tree(btr_parent(btr_parent(btrn)), LL_INFO);
 }
 
-static void resample_pre_select(struct sched *s, void *context)
+static void resample_pre_monitor(struct sched *s, void *context)
 {
        struct filter_node *fn = context;
        struct resample_context *ctx = fn->private_data;
@@ -70,7 +70,7 @@ static void resample_pre_select(struct sched *s, void *context)
 
        if (ret != 0)
                return sched_min_delay(s);
-       check_wav_pre_select(s, ctx->cwc);
+       check_wav_pre_monitor(s, ctx->cwc);
 }
 
 static int get_btr_val(const char *what, struct btr_node *btrn)
@@ -167,10 +167,10 @@ static int resample_frames(int16_t *in, size_t num_frames, bool have_more,
        data.output_frames = num_frames * ctx->ratio + 1;
        out_samples = data.output_frames * ctx->channels;
 
-       in_float = para_malloc(num_samples * sizeof(float));
+       in_float = arr_alloc(num_samples, sizeof(float));
        src_short_to_float_array(in, in_float, num_samples);
        data.data_in = in_float;
-       data.data_out = para_malloc(out_samples * sizeof(float));
+       data.data_out = arr_alloc(out_samples, sizeof(float));
        ret = src_process(ctx->src_state, &data);
        free(in_float);
        if (ret != 0) {
@@ -179,7 +179,7 @@ static int resample_frames(int16_t *in, size_t num_frames, bool have_more,
                return -E_LIBSAMPLERATE;
        }
        out_samples = data.output_frames_gen * ctx->channels;
-       out = para_malloc(out_samples * sizeof(short));
+       out = arr_alloc(out_samples, sizeof(short));
        src_float_to_short_array(data.data_out, out, out_samples);
        free(data.data_out);
        *result = out;
@@ -187,7 +187,7 @@ static int resample_frames(int16_t *in, size_t num_frames, bool have_more,
        return data.input_frames_used;
 }
 
-static int resample_post_select(__a_unused struct sched *s, void *context)
+static int resample_post_monitor(__a_unused struct sched *s, void *context)
 {
        int ret;
        struct filter_node *fn = context;
@@ -197,7 +197,7 @@ static int resample_post_select(__a_unused struct sched *s, void *context)
        size_t in_bytes, num_frames;
        bool have_more;
 
-       ret = check_wav_post_select(ctx->cwc);
+       ret = check_wav_post_monitor(ctx->cwc);
        if (ret < 0)
                goto out;
        ret = btr_node_status(btrn, fn->min_iqs, BTR_NT_INTERNAL);
@@ -220,7 +220,7 @@ static int resample_post_select(__a_unused struct sched *s, void *context)
        }
        btr_merge(btrn, fn->min_iqs);
        in_bytes = btr_next_buffer(btrn, (char **)&in);
-       ret = -E_RESAMPLE_EOF;
+       ret = -E_EOF;
        num_frames = in_bytes / 2 / ctx->channels;
        if (num_frames == 0)
                goto out;
@@ -236,7 +236,7 @@ out:
        if (ret < 0) {
                btr_remove_node(&fn->btrn);
                /* This releases the check_wav btr node */
-               check_wav_post_select(ctx->cwc);
+               check_wav_post_monitor(ctx->cwc);
        }
        return ret;
 }
@@ -277,8 +277,8 @@ static void resample_teardown(__a_unused const struct lls_parse_result *lpr,
 const struct filter lsg_filter_cmd_com_resample_user_data = {
        .setup = resample_setup,
        .open = resample_open,
-       .pre_select = resample_pre_select,
-       .post_select = resample_post_select,
+       .pre_monitor = resample_pre_monitor,
+       .post_monitor = resample_post_monitor,
        .close = resample_close,
        .teardown = resample_teardown,
        .execute = resample_execute
index 76e2d7afc8ab7caea1207a36c37c8b6452af2816..0e706f0f1ad6e906732f3bc95e8b653da53f9a6d 100644 (file)
@@ -41,8 +41,8 @@ struct ringbuffer
  */
 struct ringbuffer *ringbuffer_new(unsigned size)
 {
-       struct ringbuffer *rb = para_calloc(sizeof(struct ringbuffer));
-       rb->entries = para_calloc(size * sizeof(void *));
+       struct ringbuffer *rb = zalloc(sizeof(struct ringbuffer));
+       rb->entries = zalloc(size * sizeof(void *));
        rb->size = size;
        return rb;
 }
diff --git a/sched.c b/sched.c
index a2903940fdaea1b24d6a49cfc2f54766c136070f..20822038b81aff04d8ee8c21f7f8d7773831ca37 100644 (file)
--- a/sched.c
+++ b/sched.c
@@ -17,9 +17,9 @@
  * The possible states of a task.
  *
  * In addition to the states listed here, a task may also enter zombie state.
- * This happens when its ->post_select function returns negative, the ->status
+ * This happens when its ->post_monitor function returns negative, the ->status
  * field is then set to this return value. Such tasks are not scheduled any
- * more (i.e. ->pre_select() and ->post_select() are no longer called), but
+ * more (i.e. ->pre_monitor() and ->post_monitor() are no longer called), but
  * they stay on the scheduler task list until \ref task_reap() or
  * \ref sched_shutdown() is called.
  */
@@ -46,7 +46,7 @@ struct task {
 static struct timeval now_struct;
 const struct timeval *now = &now_struct;
 
-static void sched_preselect(struct sched *s)
+static void sched_pre_monitor(struct sched *s)
 {
        struct task *t, *tmp;
 
@@ -55,46 +55,42 @@ static void sched_preselect(struct sched *s)
                        continue;
                if (t->notification != 0)
                        sched_min_delay(s);
-               if (t->info.pre_select)
-                       t->info.pre_select(s, t->info.context);
+               if (t->info.pre_monitor)
+                       t->info.pre_monitor(s, t->info.context);
        }
 }
 
 static void unlink_and_free_task(struct task *t)
 {
-       PARA_INFO_LOG("freeing task %s (%s)\n", t->name, t->status < 0?
-               para_strerror(-t->status) :
-               (t->status == TS_DEAD? "[dead]" : "[running]"));
-
        list_del(&t->node);
        free(t->name);
        free(t);
 }
 
 //#define SCHED_DEBUG 1
-static inline void call_post_select(struct sched *s, struct task *t)
+static inline void call_post_monitor(struct sched *s, struct task *t)
 {
        int ret;
 
 #ifndef SCHED_DEBUG
-       ret = t->info.post_select(s, t->info.context);
+       ret = t->info.post_monitor(s, t->info.context);
 #else
        struct timeval t1, t2, diff;
        unsigned long pst;
 
        clock_get_realtime(&t1);
-       ret = t->info.post_select(s, t->info.context);
+       ret = t->info.post_monitor(s, t->info.context);
        clock_get_realtime(&t2);
        tv_diff(&t1, &t2, &diff);
        pst = tv2ms(&diff);
        if (pst > 50)
-               PARA_WARNING_LOG("%s: post_select time: %lums\n",
+               PARA_WARNING_LOG("%s: post_monitor time: %lums\n",
                        t->name, pst);
 #endif
        t->status = ret < 0? ret : TS_RUNNING;
 }
 
-static unsigned sched_post_select(struct sched *s)
+static unsigned sched_post_monitor(struct sched *s)
 {
        struct task *t, *tmp;
        unsigned num_running_tasks = 0;
@@ -103,7 +99,7 @@ static unsigned sched_post_select(struct sched *s)
                if (t->status == TS_DEAD) /* task has been reaped */
                        unlink_and_free_task(t);
                else if (t->status == TS_RUNNING) {
-                       call_post_select(s, t); /* sets t->status */
+                       call_post_monitor(s, t); /* sets t->status */
                        t->notification = 0;
                        if (t->status == TS_RUNNING)
                                num_running_tasks++;
@@ -117,13 +113,13 @@ static unsigned sched_post_select(struct sched *s)
  *
  * \param s Pointer to the scheduler struct.
  *
- * This function updates the global \a now pointer, calls all registered
- * pre_select hooks which may set the timeout and add any file descriptors to
- * the fd sets of \a s.  Next, it calls para_select() and makes the result available
- * to the registered tasks by calling their post_select hook.
+ * This function updates the global now pointer, calls all registered
+ * pre_monitor hooks which may set the timeout and add any file descriptors to
+ * the pollfd array. Next, it calls the poll function and makes the result
+ * available to the registered tasks by calling their post_monitor hook.
  *
  * \return Zero if no more tasks are left in the task list, negative if the
- * select function returned an error.
+ * poll function returned an error.
  *
  * \sa \ref now.
  */
@@ -132,31 +128,20 @@ int schedule(struct sched *s)
        int ret;
        unsigned num_running_tasks;
 
-       if (!s->select_function)
-               s->select_function = para_select;
+       if (!s->poll_function)
+               s->poll_function = xpoll;
 again:
-       FD_ZERO(&s->rfds);
-       FD_ZERO(&s->wfds);
-       s->select_timeout = s->default_timeout;
-       s->max_fileno = -1;
+       s->num_pfds = 0;
+       if (s->pidx)
+               memset(s->pidx, 0xff, s->pidx_array_len * sizeof(unsigned));
+       s->timeout = s->default_timeout;
        clock_get_realtime(&now_struct);
-       sched_preselect(s);
-       ret = s->select_function(s->max_fileno + 1, &s->rfds, &s->wfds,
-               &s->select_timeout);
+       sched_pre_monitor(s);
+       ret = s->poll_function(s->pfd, s->num_pfds, s->timeout);
        if (ret < 0)
                return ret;
-       if (ret == 0) {
-               /*
-                * APUE: Be careful not to check the descriptor sets on return
-                * unless the return value is greater than zero. The return
-                * state of the descriptor sets is implementation dependent if
-                * either a signal is caught or the timer expires.
-                */
-               FD_ZERO(&s->rfds);
-               FD_ZERO(&s->wfds);
-       }
        clock_get_realtime(&now_struct);
-       num_running_tasks = sched_post_select(s);
+       num_running_tasks = sched_post_monitor(s);
        if (num_running_tasks == 0)
                return 0;
        goto again;
@@ -194,10 +179,11 @@ int task_reap(struct task **tptr)
        if (t->status >= 0)
                return 0;
        ret = t->status;
+       PARA_INFO_LOG("reaping %s: %s\n", t->name, para_strerror(-ret));
        /*
         * With list_for_each_entry_safe() it is only safe to remove the
         * _current_ list item. Since we are being called from the loop in
-        * schedule() via some task's ->post_select() function, freeing the
+        * schedule() via some task's ->post_monitor() function, freeing the
         * given task here would result in use-after-free bugs in schedule().
         * So we only set the task status to TS_DEAD which tells schedule() to
         * free the task in the next iteration of its loop.
@@ -226,6 +212,8 @@ void sched_shutdown(struct sched *s)
                                t->name);
                unlink_and_free_task(t);
        }
+       free(s->pfd);
+       free(s->pidx);
 }
 
 /**
@@ -239,12 +227,12 @@ void sched_shutdown(struct sched *s)
  */
 struct task *task_register(struct task_info *info, struct sched *s)
 {
-       struct task *t = para_malloc(sizeof(*t));
+       struct task *t = alloc(sizeof(*t));
 
-       assert(info->post_select);
+       assert(info->post_monitor);
 
        if (!s->task_list.next)
-               INIT_LIST_HEAD(&s->task_list);
+               init_list_head(&s->task_list);
 
        t->info = *info;
        t->name = para_strdup(info->name);
@@ -288,14 +276,14 @@ char *get_task_list(struct sched *s)
  * \param err A positive error code.
  *
  * Tasks which honor notifications are supposed to call \ref
- * task_get_notification() in their post_select function and act on the
+ * task_get_notification() in their post_monitor function and act on the
  * returned notification value.
  *
- * If the scheduler detects during its pre_select loop that at least one task
- * has been notified, the loop terminates, and the post_select methods of all
+ * If the scheduler detects during its pre_monitor loop that at least one task
+ * has been notified, the loop terminates, and the post_monitor methods of all
  * taks are immediately called again.
  *
- * The notification for a task is reset after the call to its post_select
+ * The notification for a task is reset after the call to its post_monitor
  * method.
  *
  * \sa \ref task_get_notification().
@@ -316,7 +304,7 @@ void task_notify(struct task *t, int err)
  *
  * \return The notification value. If this is negative, the task has been
  * notified by another task. Tasks are supposed to check for notifications by
- * calling this function from their post_select method.
+ * calling this function from their post_monitor method.
  *
  * \sa \ref task_notify().
  */
@@ -362,43 +350,43 @@ void task_notify_all(struct sched *s, int err)
 }
 
 /**
- * Set the select timeout to the minimal possible value.
+ * Set the I/O timeout to the minimal possible value.
  *
  * \param s Pointer to the scheduler struct.
  *
- * This causes the next select() call to return immediately.
+ * This causes the next poll() call to return immediately.
  */
 void sched_min_delay(struct sched *s)
 {
-       s->select_timeout.tv_sec = s->select_timeout.tv_usec = 0;
+       s->timeout = 0;
 }
 
 /**
- * Impose an upper bound for the timeout of the next select() call.
+ * Impose an upper bound for the I/O timeout.
  *
  * \param to Maximal allowed timeout.
  * \param s Pointer to the scheduler struct.
  *
- * If the current scheduler timeout is already smaller than \a to, this
- * function does nothing. Otherwise the timeout for the next select() call is
- * set to the given value.
+ * If the current I/O timeout is already smaller than to, this function does
+ * nothing. Otherwise the timeout is set to the given value.
  *
  * \sa \ref sched_request_timeout_ms().
  */
 void sched_request_timeout(struct timeval *to, struct sched *s)
 {
-       if (tv_diff(&s->select_timeout, to, NULL) > 0)
-               s->select_timeout = *to;
+       long unsigned ms = tv2ms(to);
+       if (s->timeout > ms)
+               s->timeout = ms;
 }
 
 /**
- * Force the next select() call to return before the given amount of milliseconds.
+ * Bound the I/O timeout to at most the given amount of milliseconds.
  *
  * \param ms The maximal allowed timeout in milliseconds.
  * \param s Pointer to the scheduler struct.
  *
- * Like sched_request_timeout() this imposes an upper bound on the timeout
- * value for the next select() call.
+ * Like \ref sched_request_timeout() this imposes an upper bound on the I/O
+ * timeout.
  */
 void sched_request_timeout_ms(long unsigned ms, struct sched *s)
 {
@@ -408,13 +396,13 @@ void sched_request_timeout_ms(long unsigned ms, struct sched *s)
 }
 
 /**
- * Force the next select() call to return before the given future time.
+ * Bound the I/O timeout by an absolute time in the future.
  *
- * \param barrier Absolute time before select() should return.
+ * \param barrier Defines the upper bound for the timeout.
  * \param s Pointer to the scheduler struct.
  *
- * \return If \a barrier is in the past, this function does nothing and returns
- * zero. Otherwise it returns one.
+ * \return If the barrier is in the past, this function does nothing and
+ * returns zero. Otherwise it returns one.
  *
  * \sa \ref sched_request_barrier_or_min_delay().
  */
@@ -429,12 +417,12 @@ int sched_request_barrier(struct timeval *barrier, struct sched *s)
 }
 
 /**
- * Force the next select() call to return before the given time.
+ * Bound the I/O timeout or request a minimal delay.
  *
- * \param barrier Absolute time before select() should return.
+ * \param barrier Absolute time as in \ref sched_request_barrier().
  * \param s Pointer to the scheduler struct.
  *
- * \return If \a barrier is in the past, this function requests a minimal
+ * \return If the barrier is in the past, this function requests a minimal
  * timeout and returns zero. Otherwise it returns one.
  *
  * \sa \ref sched_min_delay(), \ref sched_request_barrier().
@@ -450,3 +438,126 @@ int sched_request_barrier_or_min_delay(struct timeval *barrier, struct sched *s)
        sched_request_timeout(&diff, s);
        return 1;
 }
+
+static void add_pollfd(int fd, struct sched *s, short events)
+{
+       assert(fd >= 0);
+#if 0
+       {
+               int flags = fcntl(fd, F_GETFL);
+               if (!(flags & O_NONBLOCK)) {
+                       PARA_EMERG_LOG("fd %d is a blocking file descriptor\n", fd);
+                       exit(EXIT_FAILURE);
+               }
+       }
+#endif
+       if (s->pidx_array_len > fd) { /* is fd already registered? */
+               if (s->pidx[fd] < s->pfd_array_len) { /* yes, it is */
+                       assert(s->pfd[s->pidx[fd]].fd == fd);
+                       s->pfd[s->pidx[fd]].events |= events;
+                       return;
+               }
+       } else { /* need to extend the index array */
+               unsigned old_len = s->pidx_array_len;
+               while (s->pidx_array_len <= fd)
+                       s->pidx_array_len = s->pidx_array_len * 2 + 1;
+               PARA_INFO_LOG("pidx array len: %u\n", s->pidx_array_len);
+               s->pidx = para_realloc(s->pidx,
+                       s->pidx_array_len * sizeof(unsigned));
+               memset(s->pidx + old_len, 0xff,
+                       (s->pidx_array_len - old_len) * sizeof(unsigned));
+       }
+       /*
+        * The given fd is not part of the pfd array yet. Initialize pidx[fd]
+        * to point at the next unused slot of this array and initialize the
+        * slot.
+        */
+       s->pidx[fd] = s->num_pfds;
+       if (s->pfd_array_len <= s->num_pfds) {
+               unsigned old_len = s->pfd_array_len;
+               s->pfd_array_len = old_len * 2 + 1;
+               PARA_INFO_LOG("pfd array len: %u\n", s->pfd_array_len);
+               s->pfd = para_realloc(s->pfd,
+                       s->pfd_array_len * sizeof(struct pollfd));
+               memset(s->pfd + old_len, 0,
+                       (s->pfd_array_len - old_len) * sizeof(struct pollfd));
+       }
+       s->pfd[s->num_pfds].fd = fd;
+       s->pfd[s->num_pfds].events = events;
+       s->pfd[s->num_pfds].revents = 0;
+       s->num_pfds++;
+}
+
+/**
+ * Instruct the scheduler to monitor an fd for readiness for reading.
+ *
+ * \param fd The file descriptor.
+ * \param s The scheduler.
+ *
+ * \sa \ref sched_monitor_writefd().
+ */
+void sched_monitor_readfd(int fd, struct sched *s)
+{
+       add_pollfd(fd, s, POLLIN);
+}
+
+/**
+ * Instruct the scheduler to monitor an fd for readiness for writing.
+ *
+ * \param fd The file descriptor.
+ * \param s The scheduler.
+ *
+ * \sa \ref sched_monitor_readfd().
+ */
+void sched_monitor_writefd(int fd, struct sched *s)
+{
+       add_pollfd(fd, s, POLLOUT);
+}
+
+static int get_revents(int fd, const struct sched *s)
+{
+       if (fd < 0)
+               return 0;
+       if (fd >= s->pidx_array_len)
+               return 0;
+       if (s->pidx[fd] >= s->num_pfds)
+               return 0;
+       if (s->pfd[s->pidx[fd]].fd != fd)
+               return 0;
+       assert((s->pfd[s->pidx[fd]].revents & POLLNVAL) == 0);
+       return s->pfd[s->pidx[fd]].revents;
+}
+
+/**
+ * Check whether there is data to read on the given fd.
+ *
+ * To be called from the ->post_monitor() method of a task.
+ *
+ * \param fd Should have been monitored with \ref sched_monitor_readfd().
+ * \param s The scheduler instance.
+ *
+ * \return True if the file descriptor is ready for reading, false otherwise.
+ * If fd is negative, or has not been monitored in the current iteration of the
+ * scheduler's main loop, the function also returns false.
+ *
+ * \sa \ref sched_write_ok().
+ */
+bool sched_read_ok(int fd, const struct sched *s)
+{
+       return get_revents(fd, s) & (POLLIN | POLLERR | POLLHUP);
+}
+
+/**
+ * Check whether writing is possible (i.e., does not block).
+ *
+ * \param fd Should have been monitored with \ref sched_monitor_writefd().
+ * \param s The scheduler instance.
+ *
+ * \return True if the file descriptor is ready for writing, false otherwise.
+ * The comment in \ref sched_read_ok() about invalid file descriptors applies
+ * to this function as well.
+ */
+bool sched_write_ok(int fd, const struct sched *s)
+{
+       return get_revents(fd, s) & (POLLOUT | POLLERR | POLLHUP);
+}
diff --git a/sched.h b/sched.h
index 35e2503e383be3611fc302f5a732e45cf322d506..ede5e67ea2ac042c5765a92e6100bc00ab47e103 100644 (file)
--- a/sched.h
+++ b/sched.h
@@ -7,24 +7,30 @@
  * Paraslash's scheduler.
  *
  * Designed with KISS in mind. It maintains a list of task structures which is
- * extended when a new task is registered. Each task may define a pre_select
+ * extended when a new task is registered. Each task may define a pre_monitor
  * function which is called from the scheduler main loop before it calls
- * select(). Similarly, each task must define a post_select function which is
- * called after the select call.
+ * poll(2). Similarly, each task must define a post_monitor function which is
+ * called after poll(2) returns.
+ *
+ * \sa select(2), poll(2).
  */
 struct sched {
-       /** Initial value before any pre_select call. */
-       struct timeval default_timeout;
-       /** The current timeout for the upcoming select call. */
-       struct timeval select_timeout;
-       /** fds that should be watched for readability. */
-       fd_set rfds;
-       /** fds that should be watched for writability. */
-       fd_set wfds;
-       /** Highest numbered file descriptor in any of the above fd sets. */
-       int max_fileno;
-       /** If non-NULL, use this function instead of para_select. */
-       int (*select_function)(int, fd_set *, fd_set *, struct timeval *);
+       /** Initial value (in milliseconds) before any pre_monitor call. */
+       int default_timeout;
+       /** The timeout (also in milliseconds) for the next iteration. */
+       int timeout;
+       /** Passed to poll(2). */
+       struct pollfd *pfd;
+       /** Number of elements in the above array, passed to poll(2). */
+       unsigned pfd_array_len;
+       /** Number of fds registered for montitoring so far. */
+       unsigned num_pfds;
+       /** Maps fds to indices of the pfd array. */
+       unsigned *pidx;
+       /** Mumber of elements in the above pidx array. */
+       unsigned pidx_array_len;
+       /** If non-NULL, use this function instead of \ref xpoll(). */
+       int (*poll_function)(struct pollfd *fds, nfds_t nfds, int timeout);
        /** Tasks which have been registered to the scheduler. */
        struct list_head task_list;
 };
@@ -36,23 +42,32 @@ struct task_info {
        /** Used for log messages and by \ref get_task_list(). */
        const char *name;
        /**
-        * The optional pre select method.
+        * Configure watch fds and impose an upper bound on the I/O timeout.
+        *
+        * If this is not NULL, the function is called at each iteration of the
+        * scheduler's main loop. Its purpose is to tell the scheduler that
+        * certain file descriptors should be monitored for readiness for I/O.
+        * The function may also lower the scheduler's timeout value (but shall
+        * never increase it) to impose an upper bound on the waiting time in
+        * case no file descriptors happen to be ready.
         *
-        * Its purpose is to add file descriptors to the fd sets of the
-        * scheduler and to decrease the select timeout if necessary.
+        * \sa \ref time.c.
         */
-       void (*pre_select)(struct sched *s, void *context);
+       void (*pre_monitor)(struct sched *s, void *context);
        /**
-        * The mandatory post select method.
+        * Perform I/O on file descriptors which are ready for I/O.
+        *
+        * This mandatory hook is called after the system call which monitors
+        * file descriptors returns. The function should perform non-blocking
+        * I/O on those file descriptors which are reported as being ready.
         *
-        * Its purpose is to evaluate and act upon the results of the previous
-        * select call. If this function returns a negative value, the
-        * scheduler unregisters the task.
+        * If this function returns a negative value, the scheduler unregisters
+        * the task.
         */
-       int (*post_select)(struct sched *s, void *context);
+       int (*post_monitor)(struct sched *s, void *context);
        /**
         * This pointer is saved when the task is registered. It is passed to
-        * ->pre_select() and ->post_select(). Usually this is a pointer to the
+        * ->pre_monitor() and ->post_monitor(). Usually this is a pointer to the
         * struct owned by the caller which contains the task pointer.
         */
        void *context;
@@ -80,3 +95,7 @@ void sched_request_timeout(struct timeval *to, struct sched *s);
 void sched_request_timeout_ms(long unsigned ms, struct sched *s);
 int sched_request_barrier(struct timeval *barrier, struct sched *s);
 int sched_request_barrier_or_min_delay(struct timeval *barrier, struct sched *s);
+void sched_monitor_readfd(int fd, struct sched *s);
+void sched_monitor_writefd(int fd, struct sched *s);
+bool sched_read_ok(int fd, const struct sched *s);
+bool sched_write_ok(int fd, const struct sched *s);
diff --git a/score.c b/score.c
index 983589333f088b600d85d9c0271044d7a21f8eb7..c03e3472da306a1d535b201ea2d23c90ccf8f90a 100644 (file)
--- a/score.c
+++ b/score.c
@@ -21,15 +21,11 @@ static int ptr_compare(const struct osl_object *obj1, const struct osl_object *o
        return NUM_COMPARE(d1, d2);
 }
 
-/**
- * Compare the score of two audio files
- *
- * \param obj1 Pointer to the first score object.
- * \param obj2 Pointer to the second score object.
- *
- * This function first compares the score values as usual integers. If they compare as
- * equal, the address of \a obj1 and \a obj2 are compared. So this compare function
- * returns zero if and only if \a obj1 and \a obj2 point to the same memory area.
+/*
+ * This function first compares the score values. If they are equal, the
+ * addresses of the two objects are compared. Thus, the function returns
+ * "equal" only if the two objects alias each other, i.e., point to the same
+ * memory address.
  */
 static int score_compare(const struct osl_object *obj1, const struct osl_object *obj2)
 {
@@ -80,32 +76,11 @@ static struct osl_table_description score_table_desc = {
        .column_descriptions = score_cols
 };
 
-/**
- * Compute the number of files in score table.
- *
- * \param num Result is returned here.
- *
- * \return Positive on success, negative on errors.
- */
-int get_num_admissible_files(unsigned *num)
-{
-       return osl(osl_get_num_rows(score_table, num));
-}
-
-/**
- * Get the score of the audio file associated with given row of the score table.
- *
- * \param score_row Pointer to the row in the score table.
- * \param score Result is returned here on success.
- *
- * On errors (negative return value) the content of \a score is undefined.
- *
- * \return The return value of the underlying call to osl_get_object().
- */
-static int get_score_of_row(void *score_row, long *score)
+/* On errors (negative return value) the content of score is undefined. */
+static int get_score_of_row(struct osl_table *t, void *score_row, long *score)
 {
        struct osl_object obj;
-       int ret = osl(osl_get_object(score_table, score_row, SCORECOL_SCORE, &obj));
+       int ret = osl(osl_get_object(t, score_row, SCORECOL_SCORE, &obj));
 
        if (ret >= 0)
                *score = *(long *)obj.data;
@@ -113,14 +88,15 @@ static int get_score_of_row(void *score_row, long *score)
 }
 
 /**
- * Add an entry to the table of admissible files.
+ * Add a (row, score) pair to the score table.
  *
- * \param aft_row The audio file to be added.
- * \param score The score for this file.
+ * \param aft_row Identifies the audio file to be added.
+ * \param score The score value of the audio file.
+ * \param t NULL means to operate on the currently active table.
  *
  * \return The return value of the underlying call to osl_add_row().
  */
-int score_add(const struct osl_row *aft_row, long score)
+int score_add(const struct osl_row *aft_row, long score, struct osl_table *t)
 {
        int ret;
        struct osl_object score_objs[NUM_SCORE_COLUMNS];
@@ -132,12 +108,12 @@ int score_add(const struct osl_row *aft_row, long score)
        score_objs[SCORECOL_AFT_ROW].size = size;
 
        size = score_table_desc.column_descriptions[SCORECOL_SCORE].data_size;
-       score_objs[SCORECOL_SCORE].data = para_malloc(size);
+       score_objs[SCORECOL_SCORE].data = alloc(size);
        score_objs[SCORECOL_SCORE].size = size;
        *(long *)(score_objs[SCORECOL_SCORE].data) = score;
 
 //     PARA_DEBUG_LOG("adding %p\n", *(void **) (score_objs[SCORECOL_AFT_ROW].data));
-       ret = osl(osl_add_row(score_table, score_objs));
+       ret = osl(osl_add_row(t? t : score_table, score_objs));
        if (ret < 0) {
                PARA_ERROR_LOG("%s\n", para_strerror(-ret));
                free(score_objs[SCORECOL_SCORE].data);
@@ -145,16 +121,6 @@ int score_add(const struct osl_row *aft_row, long score)
        return ret;
 }
 
-static int get_nth_score(unsigned n, long *score)
-{
-       struct osl_row *row;
-       int ret = osl(osl_get_nth_row(score_table, SCORECOL_SCORE, n, &row));
-
-       if (ret < 0)
-               return ret;
-       return get_score_of_row(row, score);
-}
-
 /**
  * Replace a row of the score table.
  *
@@ -169,7 +135,7 @@ static int get_nth_score(unsigned n, long *score)
  */
 int score_update(const struct osl_row *aft_row, long percent)
 {
-       struct osl_row *row;
+       struct osl_row *row, *rrow; /* score row, reference row */
        long new_score;
        unsigned n, new_pos;
        struct osl_object obj = {.data = (struct osl_row *)aft_row,
@@ -180,16 +146,19 @@ int score_update(const struct osl_row *aft_row, long percent)
                return 1;
        if (ret < 0)
                return ret;
-       ret = get_num_admissible_files(&n);
+       ret = osl(osl_get_num_rows(score_table, &n));
        if (ret < 0)
                return ret;
        new_pos = 1 + (n - 1) * percent / 100;
-       ret = get_nth_score(new_pos, &new_score);
+       ret = osl(osl_get_nth_row(score_table, SCORECOL_SCORE, new_pos, &rrow));
+       if (ret < 0)
+               return ret;
+       ret = get_score_of_row(score_table, rrow, &new_score);
        if (ret < 0)
                return ret;
        new_score--;
        obj.size = sizeof(long);
-       obj.data = para_malloc(obj.size);
+       obj.data = alloc(obj.size);
        *(long *)obj.data = new_score;
        PARA_DEBUG_LOG("new score: %ld, rank %u/%u\n", new_score, new_pos, n);
        return osl(osl_update_object(score_table, row, SCORECOL_SCORE, &obj));
@@ -202,14 +171,13 @@ int score_update(const struct osl_row *aft_row, long percent)
  * \param score Result pointer.
  * \param aft_row Result pointer.
  *
- * \return Negative on errors, positive on success. Possible errors: Errors
- * returned by osl_get_object().
+ * \return Standard.
  */
 int get_score_and_aft_row(struct osl_row *score_row, long *score,
                struct osl_row **aft_row)
 {
        struct osl_object obj;
-       int ret = get_score_of_row(score_row, score);
+       int ret = get_score_of_row(score_table, score_row, score);
 
        if (ret < 0)
                return ret;
@@ -220,28 +188,28 @@ int get_score_and_aft_row(struct osl_row *score_row, long *score,
        return 1;
 }
 
-static int get_score_row_from_aft_row(const struct osl_row *aft_row,
-               struct osl_row **score_row)
+static int get_score_row_from_aft_row(struct osl_table *t,
+               const struct osl_row *aft_row, struct osl_row **score_row)
 {
        struct osl_object obj = {.data = (struct osl_row *)aft_row,
                .size = sizeof(aft_row)};
-       return osl(osl_get_row(score_table, SCORECOL_AFT_ROW, &obj, score_row));
+       return osl(osl_get_row(t, SCORECOL_AFT_ROW, &obj, score_row));
 }
 
 /**
- * Loop over all files in the score table.
+ * Call the given function for each row of the score table.
  *
- * \param data A pointer to arbitrary data.
- * \param func Function to be called for each admissible file.
+ * \param func Callback, called once per row.
+ * \param t NULL means to use the currently active score table.
+ * \param data Passed verbatim to the callback.
  *
- * \return The return value of the underlying call to osl_rbtree_loop().
- *
- * This is used for the ls command. The \a data parameter is passed as the
- * second argument to \a func.
+ * \return The return value of the underlying call to osl_rbtree_loop(). The
+ * loop terminates early if the callback returns negative.
  */
-int admissible_file_loop(void *data, osl_rbtree_loop_func *func)
+int score_loop(osl_rbtree_loop_func *func, struct osl_table *t, void *data)
 {
-       return osl(osl_rbtree_loop(score_table, SCORECOL_SCORE, data, func));
+       return osl(osl_rbtree_loop(t? t : score_table, SCORECOL_SCORE, data,
+               func));
 }
 
 /**
@@ -250,8 +218,7 @@ int admissible_file_loop(void *data, osl_rbtree_loop_func *func)
  * \param aft_row Points to the row in the aft of the "best" audio file.
  * \param score Highest score value in the score table.
  *
- * \return Positive on success, negative on errors. Possible errors: Errors
- * returned by osl_rbtree_last_row(), osl_get_object().
+ * \return Standard.
  */
 int score_get_best(struct osl_row **aft_row, long *score)
 {
@@ -265,7 +232,7 @@ int score_get_best(struct osl_row **aft_row, long *score)
        if (ret < 0)
                return ret;
        *aft_row = obj.data;
-       return get_score_of_row(row, score);
+       return get_score_of_row(score_table, row, score);
 }
 
 /**
@@ -273,15 +240,14 @@ int score_get_best(struct osl_row **aft_row, long *score)
  *
  * \param aft_row The file which is no longer admissible.
  *
- * \return Positive on success, negative on errors. Possible errors:
- * Errors returned by osl_get_row() and osl_del_row().
+ * \return Standard.
  *
  * \sa \ref score_add().
  */
 int score_delete(const struct osl_row *aft_row)
 {
        struct osl_row *score_row;
-       int ret = get_score_row_from_aft_row(aft_row, &score_row);
+       int ret = get_score_row_from_aft_row(score_table, aft_row, &score_row);
 
        if (ret < 0)
                return ret;
@@ -292,75 +258,72 @@ int score_delete(const struct osl_row *aft_row)
  * Find out whether an audio file is contained in the score table.
  *
  * \param aft_row The row of the audio file table.
- * \param rank Result pointer
  *
- * \return Positive, if \a aft_row belongs to the audio file table,
- * zero if not, negative on errors. If \a aft_row was found, and \a rank
- * is not \p NULL, the rank of \a aft_row is returned in \a rank.
+ * \return If the lookup operation fails for any other reason than "not found",
+ * the function aborts the current process (afs), since this is considered a
+ * fatal error that should never happen.
  */
-int row_belongs_to_score_table(const struct osl_row *aft_row, unsigned *rank)
+bool row_belongs_to_score_table(const struct osl_row *aft_row)
 {
        struct osl_row *score_row;
-       int ret = get_score_row_from_aft_row(aft_row, &score_row);
+       int ret = get_score_row_from_aft_row(score_table, aft_row, &score_row);
 
        if (ret == -OSL_ERRNO_TO_PARA_ERROR(E_OSL_RB_KEY_NOT_FOUND))
-               return 0;
-       if (ret < 0)
-               return ret;
-       if (!rank)
-               return 1;
-       ret = osl(osl_get_rank(score_table, score_row, SCORECOL_SCORE, rank));
-       if (ret < 0)
-               return ret;
-       return 1;
-}
-
-/* Close the score table. */
-static void score_close(void)
-{
-       osl_close_table(score_table, OSL_FREE_VOLATILE);
-       score_table = NULL;
+               return false;
+       assert(ret >= 0);
+       return true;
 }
 
 /**
- * Open the score table.
+ * Free all volatile objects, then close the table.
  *
- * \param dir Unused.
+ * \param t As returned from \ref score_open().
  *
- * \return The return value of the underlying call to osl_open_table().
+ * This either succeeds or terminates the calling process.
  */
-static int score_open(__a_unused const char *dir)
+void score_close(struct osl_table *t)
 {
-       score_table_desc.dir = NULL; /* this table has only volatile columns */
-       return osl(osl_open_table(&score_table_desc, &score_table));
+       assert(osl_close_table(t? t : score_table, OSL_FREE_VOLATILE) >= 0);
 }
 
-/**
- * Remove all entries from the score table, but keep the table open.
- *
- * \return Standard.
- */
-int clear_score_table(void)
+static void close_global_table(void)
 {
-       score_close();
-       return score_open(NULL);
+       score_close(NULL);
 }
 
-static int score_event_handler(__a_unused enum afs_events event,
-               __a_unused struct para_buffer *pb, __a_unused void *data)
+static int open_global_table(__a_unused const char *dir)
 {
+       assert(osl(osl_open_table(&score_table_desc, &score_table)) >= 0);
        return 1;
 }
 
 /**
- * Initialize the scoring subsystem.
+ * Allocate a score table instance.
  *
- * \param t The members of \a t are filled in by the function.
+ * \param result NULL means to open the currently active score table.
+ *
+ * Since the score table does no filesystem I/O, this function always succeeds.
+ * \sa \ref score_close().
  */
-void score_init(struct afs_table *t)
+void score_open(struct osl_table **result)
 {
-       t->name = score_table_desc.name;
-       t->open = score_open;
-       t->close = score_close;
-       t->event_handler = score_event_handler;
+       if (result)
+               assert(osl(osl_open_table(&score_table_desc, result)) >= 0);
+       else
+               open_global_table(NULL);
 }
+
+/**
+ * Remove all entries from the score table, but keep the table open.
+ */
+void score_clear(void)
+{
+       close_global_table();
+       open_global_table(NULL);
+}
+
+/** The score table stores (aft row, score) pairs in memory. */
+const struct afs_table_operations score_ops = {
+       .open = open_global_table,
+       .close = close_global_table,
+};
diff --git a/send.h b/send.h
index f6aafbb41a2bf9b089f2ddcc9968278dea734fa1..3407bc5c28a876f1fb5aca6e748368deb58afe0b 100644 (file)
--- a/send.h
+++ b/send.h
@@ -76,27 +76,10 @@ struct sender {
        void (*send)(long unsigned current_chunk, long unsigned chunks_sent,
                const char *buf, size_t len, const char *header_buf,
                size_t header_len);
-       /**
-        * Add file descriptors to fd_sets.
-        *
-        * The pre_select function of each supported sender is called just before
-        * para_server enters its main select loop. Each sender may add its own
-        * file descriptors to the \a rfds or the \a wfds set.
-        *
-        * If a file descriptor was added, \a max_fileno must be increased by
-        * this function, if necessary.
-        *
-        * \sa select(2).
-        */
-       void (*pre_select)(int *max_fileno, fd_set *rfds, fd_set *wfds);
-       /**
-        * Handle the file descriptors which are ready for I/O.
-        *
-        * If the pre_select hook added one ore more file descriptors to the
-        * read or write set, this is the hook to check the result and do any
-        * I/O on those descriptors which are ready for reading/writing.
-        */
-       void (*post_select)(fd_set *rfds, fd_set *wfds);
+       /** Ask the scheduler to monitor file descriptors. */
+       void (*pre_monitor)(struct sched *s);
+       /** Perform I/O on the file descriptors which are ready. */
+       void (*post_monitor)(struct sched *s);
        /**
         * Terminate all connected clients.
         *
@@ -213,7 +196,7 @@ void init_sender_status(struct sender_status *ss,
                const struct lls_opt_result *listen_address_opt_result,
                int default_port, int max_clients, int default_deny);
 void free_sender_status(const struct sender_status *ss);
-char *generic_sender_status(struct sender_status *ss, const char *name);
+__malloc char *generic_sender_status(struct sender_status *ss, const char *name);
 void generic_com_allow(struct sender_command_data *scd,
                struct sender_status *ss);
 void generic_com_deny(struct sender_command_data *scd,
@@ -221,7 +204,7 @@ void generic_com_deny(struct sender_command_data *scd,
 void generic_com_on(struct sender_status *ss, unsigned protocol);
 void generic_acl_deplete(struct list_head *acl);
 void generic_com_off(struct sender_status *ss);
-char *generic_sender_help(void);
-struct sender_client *accept_sender_client(struct sender_status *ss, fd_set *rfds);
+__malloc char *generic_sender_help(void);
+struct sender_client *accept_sender_client(struct sender_status *ss);
 int send_queued_chunks(int fd, struct chunk_queue *cq);
 int parse_fec_url(const char *arg, struct sender_command_data *scd);
index ea494d9a7b23f5ee87186917d11e883631ec9cbc..8dc82e9cba42aecf027761c7682289fb0a9b6797 100644 (file)
 #include "afs.h"
 #include "server.h"
 #include "acl.h"
+#include "sched.h"
 #include "send.h"
 #include "close_on_fork.h"
 #include "chunk_queue.h"
-#include "sched.h"
 #include "vss.h"
 
 /** Clients will be kicked if there are more than that many bytes pending. */
  */
 void shutdown_client(struct sender_client *sc, struct sender_status *ss)
 {
-       PARA_INFO_LOG("shutting down %s on fd %d\n", sc->name, sc->fd);
-       free(sc->name);
        if (!process_is_command_handler()) {
+               PARA_INFO_LOG("shutting down %s on fd %d\n", sc->name, sc->fd);
                close(sc->fd);
                del_close_on_fork_list(sc->fd);
        }
+       free(sc->name);
        cq_destroy(sc->cq);
        list_del(&sc->node);
        free(sc->private_data);
@@ -120,14 +120,14 @@ void init_sender_status(struct sender_status *ss,
 
        if (n == 0) {
                ss->num_listen_fds = 1;
-               ss->listen_addresses = para_malloc(sizeof(char *));
+               ss->listen_addresses = alloc(sizeof(char *));
                ss->listen_addresses[0] = NULL;
-               ss->listen_fds = para_malloc(sizeof(int));
+               ss->listen_fds = alloc(sizeof(int));
                ss->listen_fds[0] = -1;
        } else {
                ss->num_listen_fds = n;
-               ss->listen_addresses = para_malloc(n * sizeof(char *));
-               ss->listen_fds = para_malloc(n * sizeof(int));
+               ss->listen_addresses = alloc(n * sizeof(char *));
+               ss->listen_fds = alloc(n * sizeof(int));
                FOR_EACH_LISTEN_FD(i, ss) {
                        ss->listen_addresses[i] = para_strdup(lls_string_val(i,
                                listen_address_opt_result));
@@ -136,9 +136,9 @@ void init_sender_status(struct sender_status *ss,
        }
        ss->default_port = default_port;
 
-       INIT_LIST_HEAD(&ss->client_list);
+       init_list_head(&ss->client_list);
        /* Initialize an access control list */
-       INIT_LIST_HEAD(&ss->acl);
+       init_list_head(&ss->acl);
        for (i = 0; i < lls_opt_given(acl_opt_result); i++) {
                const char *arg = lls_string_val(i, acl_opt_result);
                char addr[16];
@@ -181,7 +181,7 @@ void free_sender_status(const struct sender_status *ss)
  *
  * \return The string printed in the "si" command.
  */
-char *generic_sender_status(struct sender_status *ss, const char *name)
+__malloc char *generic_sender_status(struct sender_status *ss, const char *name)
 {
        char *clnts = NULL, *ret, *addr = NULL;
        struct sender_client *sc, *tmp_sc;
@@ -343,7 +343,6 @@ void generic_com_off(struct sender_status *ss)
  * Accept a connection on the socket(s) this server is listening on.
  *
  * \param ss The sender whose listening fd is ready for reading.
- * \param rfds Passed to para_accept(),
  *
  * This accepts incoming connections on any of the listening sockets of the
  * server. If there is a connection pending, the function
@@ -367,7 +366,7 @@ void generic_com_off(struct sender_status *ss)
  * \sa \ref para_accept(), \ref mark_fd_nonblocking(), \ref acl_check_access(),
  * \ref cq_new(), \ref add_close_on_fork_list().
  */
-struct sender_client *accept_sender_client(struct sender_status *ss, fd_set *rfds)
+struct sender_client *accept_sender_client(struct sender_status *ss)
 {
        struct sender_client *sc;
        int fd, ret;
@@ -376,7 +375,7 @@ struct sender_client *accept_sender_client(struct sender_status *ss, fd_set *rfd
        FOR_EACH_LISTEN_FD(n, ss) {
                if (ss->listen_fds[n] < 0)
                        continue;
-               ret = para_accept(ss->listen_fds[n], rfds, NULL, 0, &fd);
+               ret = para_accept(ss->listen_fds[n], NULL, 0, &fd);
                if (ret < 0)
                        goto warn;
                if (ret == 0)
@@ -391,7 +390,7 @@ struct sender_client *accept_sender_client(struct sender_status *ss, fd_set *rfd
                if (ret < 0)
                        goto close_fd_and_warn;
                ss->num_clients++;
-               sc = para_calloc(sizeof(*sc));
+               sc = zalloc(sizeof(*sc));
                sc->fd = fd;
                sc->name = para_strdup(remote_name(fd));
                sc->cq = cq_new(MAX_CQ_BYTES);
@@ -414,7 +413,7 @@ warn:
  * \return A dynamically allocated string containing the help text for
  * a paraslash sender.
  */
-char *generic_sender_help(void)
+__malloc char *generic_sender_help(void)
 {
        return make_message(
                "usage: {on|off}\n"
index e0d50f4f8689e1de283ef80f8341504bcf386d33..ea9cc9c003616762a96aa546c381d54417fd34a2 100644 (file)
--- a/server.c
+++ b/server.c
@@ -2,31 +2,6 @@
 
 /** \file server.c Paraslash's main server. */
 
-/**
- * \mainpage Main data structures and selected APIs:
- *
- *     - Senders: \ref sender,
- *     - Audio file selector: \ref afs_info, \ref afs_table,
- *     - Audio format handler: \ref audio_format_handler, \ref afh_info
- *     - Receivers/filters/writers: \ref receiver, \ref receiver_node,
- *       \ref filter, \ref filter_node, \ref writer_node, \ref writer.
- *     - Scheduling: \ref sched.h,
- *     - Buffer trees: \ref buffer_tree.h,
- *     - Sideband API: \ref sideband.h,
- *     - Crypto: \ref crypt.h, \ref crypt_backend.h,
- *     - Error subsystem: \ref error.h,
- *     - Inter process communication: \ref ipc.h,
- *     - Forward error correction: \ref fec.h,
- *     - Daemons: \ref daemon.h,
- *     - Mixer API: \ref mix.h,
- *     - Interactive sessions: \ref interactive.h,
- *     - File descriptors: \ref fd.h,
- *     - Signals: \ref signal.h,
- *     - Networking: \ref net.h,
- *     - Time: \ref time.c,
- *     - Doubly linked lists: \ref list.h.
- */
-
 #include <netinet/in.h>
 #include <sys/socket.h>
 #include <signal.h>
@@ -49,8 +24,8 @@
 #include "net.h"
 #include "server.h"
 #include "list.h"
-#include "send.h"
 #include "sched.h"
+#include "send.h"
 #include "vss.h"
 #include "config.h"
 #include "close_on_fork.h"
@@ -110,7 +85,7 @@ static struct signal_task *signal_task;
 /** The process id of the audio file selector process. */
 pid_t afs_pid = 0;
 
-/* The the main server process (parent of afs and the command handlers). */
+/* The main server process (parent of afs and the command handlers). */
 static pid_t server_pid;
 
 /**
@@ -197,6 +172,7 @@ static void init_ipc_or_die(void)
        mmd->active_connections = 0;
        mmd->vss_status_flags = VSS_NEXT;
        mmd->new_vss_status_flags = VSS_NEXT;
+       mmd->loglevel = OPT_UINT32_VAL(LOGLEVEL);
        return;
 destroy_mmd_mutex:
        mutex_destroy(mmd_mutex);
@@ -205,6 +181,9 @@ err_out:
        exit(EXIT_FAILURE);
 }
 
+/** Get a reference to the supercommand of para_server. */
+#define CMD_PTR (lls_cmd(0, server_suite))
+
 /**
  * (Re-)read the server configuration files.
  *
@@ -230,7 +209,7 @@ void parse_config_or_die(bool reload)
                        para_strerror(-ret));
                exit(EXIT_FAILURE);
        }
-       daemon_set_loglevel(ENUM_STRING_VAL(LOGLEVEL));
+       daemon_set_loglevel(OPT_UINT32_VAL(LOGLEVEL));
        if (OPT_GIVEN(LOGFILE)) {
                daemon_set_logfile(OPT_STRING_VAL(LOGFILE));
                daemon_open_log_or_die();
@@ -275,14 +254,14 @@ static void handle_sighup(void)
                kill(afs_pid, SIGHUP);
 }
 
-static int signal_post_select(struct sched *s, __a_unused void *context)
+static int signal_post_monitor(struct sched *s, __a_unused void *context)
 {
        int ret, signum;
 
        ret = task_get_notification(signal_task->task);
        if (ret < 0)
                return ret;
-       signum = para_next_signal(&s->rfds);
+       signum = para_next_signal();
        switch (signum) {
        case 0:
                return 0;
@@ -298,14 +277,14 @@ static int signal_post_select(struct sched *s, __a_unused void *context)
                        if (pid != afs_pid)
                                continue;
                        PARA_EMERG_LOG("fatal: afs died\n");
-                       kill(0, SIGTERM);
-                       goto cleanup;
+                       goto genocide;
                }
                break;
        /* die on sigint/sigterm. Kill all children too. */
        case SIGINT:
        case SIGTERM:
                PARA_EMERG_LOG("terminating on signal %d\n", signum);
+genocide:
                kill(0, SIGTERM);
                /*
                 * We must wait for all of our children to die. For the afs
@@ -320,7 +299,6 @@ static int signal_post_select(struct sched *s, __a_unused void *context)
                while (wait(NULL) != -1 || errno != ECHILD)
                        ; /* still at least one child alive */
                mutex_lock(mmd_mutex);
-cleanup:
                free(mmd->afd.afhi.chunk_table);
                task_notify_all(s, E_DEADLY_SIGNAL);
                return -E_DEADLY_SIGNAL;
@@ -339,20 +317,20 @@ static void init_signal_task(void)
        add_close_on_fork_list(signal_task->fd);
        signal_task->task = task_register(&(struct task_info) {
                .name = "signal",
-               .pre_select = signal_pre_select,
-               .post_select = signal_post_select,
+               .pre_monitor = signal_pre_monitor,
+               .post_monitor = signal_post_monitor,
                .context = signal_task,
 
        }, &sched);
 }
 
-static void command_pre_select(struct sched *s, void *context)
+static void command_pre_monitor(struct sched *s, void *context)
 {
        unsigned n;
        struct server_command_task *sct = context;
 
        for (n = 0; n < sct->num_listen_fds; n++)
-               para_fd_set(sct->listen_fds[n], &s->rfds, &s->max_fileno);
+               sched_monitor_readfd(sct->listen_fds[n], s);
 }
 
 static int command_task_accept(unsigned listen_idx, struct sched *s,
@@ -363,7 +341,7 @@ static int command_task_accept(unsigned listen_idx, struct sched *s,
        pid_t child_pid;
        uint32_t *chunk_table;
 
-       ret = para_accept(sct->listen_fds[listen_idx], &s->rfds, NULL, 0, &new_fd);
+       ret = para_accept(sct->listen_fds[listen_idx], NULL, 0, &new_fd);
        if (ret <= 0)
                goto out;
        mmd->num_connects++;
@@ -409,13 +387,13 @@ static int command_task_accept(unsigned listen_idx, struct sched *s,
        /*
         * After we return, the scheduler calls server_select() with a minimal
         * timeout value, because the remaining tasks have a notification
-        * pending. Next it calls the ->post_select method of these tasks,
+        * pending. Next it calls the ->post_monitor method of these tasks,
         * which will return negative in view of the notification. This causes
         * schedule() to return as there are no more runnable tasks.
         *
         * Note that semaphores are not inherited across a fork(), so we don't
-        * hold the lock at this point. Since server_select() drops the lock
-        * prior to calling para_select(), we need to acquire it here.
+        * hold the lock at this point. Since server_poll() drops the lock
+        * prior to calling poll(), we need to acquire it here.
         */
        mutex_lock(mmd_mutex);
        return -E_CHILD_CONTEXT;
@@ -425,7 +403,7 @@ out:
        return 0;
 }
 
-static int command_post_select(struct sched *s, void *context)
+static int command_post_monitor(struct sched *s, void *context)
 {
        struct server_command_task *sct = context;
        unsigned n;
@@ -433,15 +411,16 @@ static int command_post_select(struct sched *s, void *context)
 
        ret = task_get_notification(sct->task);
        if (ret < 0)
-               return ret;
+               goto fail;
        for (n = 0; n < sct->num_listen_fds; n++) {
                ret = command_task_accept(n, s, sct);
-               if (ret < 0) {
-                       free(sct->listen_fds);
-                       return ret;
-               }
+               if (ret < 0)
+                       goto fail;
        }
        return 0;
+fail:
+       free(sct->listen_fds);
+       return ret;
 }
 
 static void init_server_command_task(struct server_command_task *sct,
@@ -457,14 +436,14 @@ static void init_server_command_task(struct server_command_task *sct,
        sct->argv = argv;
        if (!OPT_GIVEN(LISTEN_ADDRESS)) {
                sct->num_listen_fds = 1;
-               sct->listen_fds = para_malloc(sizeof(int));
+               sct->listen_fds = alloc(sizeof(int));
                ret = para_listen_simple(IPPROTO_TCP, port);
                if (ret < 0)
                        goto err;
                sct->listen_fds[0] = ret;
        } else {
                sct->num_listen_fds = OPT_GIVEN(LISTEN_ADDRESS);
-               sct->listen_fds = para_malloc(sct->num_listen_fds * sizeof(int));
+               sct->listen_fds = alloc(sct->num_listen_fds * sizeof(int));
                for (n = 0; n < OPT_GIVEN(LISTEN_ADDRESS); n++) {
                        const char *arg;
                        arg = lls_string_val(n, OPT_RESULT(LISTEN_ADDRESS));
@@ -484,8 +463,8 @@ static void init_server_command_task(struct server_command_task *sct,
 
        sct->task = task_register(&(struct task_info) {
                .name = "server command",
-               .pre_select = command_pre_select,
-               .post_select = command_post_select,
+               .pre_monitor = command_pre_monitor,
+               .post_monitor = command_post_monitor,
                .context = sct,
        }, &sched);
        /*
@@ -568,7 +547,7 @@ static void server_init(int argc, char **argv, struct server_command_task *sct)
        if (ret < 0)
                goto fail;
        server_lpr = cmdline_lpr;
-       daemon_set_loglevel(ENUM_STRING_VAL(LOGLEVEL));
+       daemon_set_loglevel(OPT_UINT32_VAL(LOGLEVEL));
        daemon_drop_privileges_or_die(OPT_STRING_VAL(USER),
                OPT_STRING_VAL(GROUP));
        version_handle_flag("server", OPT_GIVEN(VERSION));
@@ -642,14 +621,14 @@ out:
        killpg(0, SIGUSR1);
 }
 
-static int server_select(int max_fileno, fd_set *readfds, fd_set *writefds,
-               struct timeval *timeout_tv)
+static int server_poll(struct pollfd *fds, nfds_t nfds, int timeout)
 {
        int ret;
 
+       daemon_set_loglevel(mmd->loglevel);
        status_refresh();
        mutex_unlock(mmd_mutex);
-       ret = para_select(max_fileno + 1, readfds, writefds, timeout_tv);
+       ret = xpoll(fds, nfds, timeout);
        mutex_lock(mmd_mutex);
        return ret;
 }
@@ -683,15 +662,15 @@ int main(int argc, char *argv[])
        struct server_command_task server_command_task_struct,
                *sct = &server_command_task_struct;
 
-       sched.default_timeout.tv_sec = 1;
-       sched.select_function = server_select;
+       sched.default_timeout = 1000;
+       sched.poll_function = server_poll;
 
        server_init(argc, argv, sct);
        mutex_lock(mmd_mutex);
        ret = schedule(&sched);
        /*
-        * We hold the mmd lock: it was re-acquired in server_select()
-        * after the select call.
+        * We hold the mmd lock: it was re-acquired in server_poll()
+        * after the poll(2) call.
         */
        mutex_unlock(mmd_mutex);
        sched_shutdown(&sched);
@@ -704,12 +683,13 @@ int main(int argc, char *argv[])
                deplete_close_on_fork_list();
                if (ret < 0)
                        PARA_EMERG_LOG("%s\n", para_strerror(-ret));
+               vss_shutdown();
        } else {
+               vss_shutdown();
                alarm(ALARM_TIMEOUT);
                close_listed_fds();
                ret = handle_connect(sct->child_fd);
        }
-       vss_shutdown();
        shm_detach(mmd);
        user_list_deplete();
        free_lpr();
index da75d86bdf191b130d02da12f49172ac5e0482d7..10bb6172860ddb467c3f9fd26ef6ce9e3cb8aab1 100644 (file)
--- a/server.h
+++ b/server.h
@@ -73,6 +73,8 @@ struct misc_meta_data {
        char afs_mode_string[MAXLINE];
        /** Used by the sender command. */
        struct sender_command_data sender_cmd_data;
+       /** Set by the ll command. */
+       int loglevel;
        /** Describes the current audio file. */
        struct audio_file_data afd;
 };
@@ -80,15 +82,6 @@ struct misc_meta_data {
 extern pid_t afs_pid;
 extern struct lls_parse_result *server_lpr;
 
-/**
- * Get a reference to the supercommand of para_server.
- *
- * This is needed for parsing the command line and for the ENUM_STRING_VAL()
- * macro below. The latter macro is used in command.c, so CMD_PTR() can not
- * be made local to server.c.
- */
-#define CMD_PTR (lls_cmd(0, server_suite))
-
 /** Get the parse result of an option to para_server. */
 #define OPT_RESULT(_name) (lls_opt_result( \
                LSG_SERVER_PARA_SERVER_OPT_ ## _name, server_lpr))
@@ -105,10 +98,6 @@ extern struct lls_parse_result *server_lpr;
 /** The (first) argument to a server option of type int32. */
 #define OPT_INT32_VAL(_name) (lls_int32_val(0, OPT_RESULT(_name)))
 
-/** Get the string which corresponds to an enum constant. */
-#define ENUM_STRING_VAL(_name) (lls_enum_string_val(OPT_UINT32_VAL(_name), \
-       lls_opt(LSG_SERVER_PARA_SERVER_OPT_ ## _name, CMD_PTR)))
-
 int handle_connect(int fd);
 void parse_config_or_die(bool reload);
 char *server_get_tasks(void);
index ed7867a1c37027d7a49d1047bd349c5976d0e8d7..d4876234a192a956f112c48f871ed1fb0d5321b7 100644 (file)
@@ -37,7 +37,7 @@ struct sb_context {
 struct sb_context *sb_new_recv(size_t max_size, sb_transformation t,
                void *trafo_context)
 {
-       struct sb_context *c = para_calloc(sizeof(*c));
+       struct sb_context *c = zalloc(sizeof(*c));
 
        c->max_size = max_size;
        c->trafo = t;
@@ -62,7 +62,7 @@ struct sb_context *sb_new_recv(size_t max_size, sb_transformation t,
 struct sb_context *sb_new_send(struct sb_buffer *sbb, bool dont_free,
                sb_transformation t, void *trafo_context)
 {
-       struct sb_context *c = para_calloc(sizeof(*c));
+       struct sb_context *c = zalloc(sizeof(*c));
        struct iovec src, dst, *srcp, *dstp;
 
        assert(sbb);
@@ -266,7 +266,7 @@ int sb_received(struct sb_context *c, size_t nbytes, struct sb_buffer *result)
         */
        if (sbb->iov.iov_len == (size_t)-1)
                return -E_SB_PACKET_SIZE;
-       sbb->iov.iov_base = para_malloc(sbb->iov.iov_len + 1);
+       sbb->iov.iov_base = alloc(sbb->iov.iov_len + 1);
        return 0; /* ready to read body */
 success:
        *result = c->sbb;
index 32d6ab6624e5f493ac393b630a603c0968f11497..d9a6aa37057415117c32a6a18c33d81977499392 100644 (file)
--- a/signal.c
+++ b/signal.c
@@ -27,7 +27,7 @@ static int signal_pipe[2];
  * signal arrives, the signal handler writes the number of the signal received
  * to one end of the signal pipe. The application can test for pending signals
  * by checking if the file descriptor of the other end of the signal pipe is
- * ready for reading, see select(2).
+ * ready for reading.
  *
  * \return This function either succeeds or calls exit(3) to terminate the
  * current process. On success, a signal task structure is returned.
@@ -48,7 +48,7 @@ struct signal_task *signal_init_or_die(void)
        ret = mark_fd_nonblocking(signal_pipe[1]);
        if (ret < 0)
                goto err_out;
-       st = para_calloc(sizeof(*st));
+       st = zalloc(sizeof(*st));
        st->fd = signal_pipe[0];
        return st;
 err_out:
@@ -72,10 +72,13 @@ static void generic_signal_handler(int s)
                errno = save_errno;
                return;
        }
-       if (ret < 0)
-               PARA_EMERG_LOG("%s\n", strerror(errno));
-       else
-               PARA_EMERG_LOG("short write to signal pipe\n");
+       /*
+        * This is a fatal error which should never happen. We must not call
+        * PARA_XXX_LOG() here because this might acquire the log mutex which
+        * is already taken by the main program if the interrupt occurs while a
+        * log message is being printed. The mutex will not be released as long
+        * as this signal handler is running, so a deadlock ensues.
+        */
        exit(EXIT_FAILURE);
 }
 
@@ -202,16 +205,14 @@ void para_unblock_signal(int sig)
 /**
  * Return the number of the next pending signal.
  *
- * \param rfds The fd_set containing the signal pipe.
- *
  * \return On success, the number of the received signal is returned. If there
  * is no signal currently pending, the function returns zero. On read errors
  * from the signal pipe, the process is terminated.
  */
-int para_next_signal(fd_set *rfds)
+int para_next_signal(void)
 {
        size_t n;
-       int s, ret = read_nonblock(signal_pipe[0], &s, sizeof(s), rfds, &n);
+       int s, ret = read_nonblock(signal_pipe[0], &s, sizeof(s), &n);
 
        if (ret < 0) {
                PARA_EMERG_LOG("%s\n", para_strerror(-ret));
index e5532ded5a9378619f7951708b49c5b38bf52d18..d9e98e78a635930577038d6090c9d5260772ddec 100644 (file)
--- a/signal.h
+++ b/signal.h
@@ -13,32 +13,31 @@ struct signal_task {
 };
 
 /**
- * A generic pre-select method for signal tasks.
+ * Monitor the signal fd for reading.
  *
- * \param s Passed to para_fd_set().
+ * \param s The scheduler instance.
  * \param context Signal task pointer.
  *
  * This convenience helper is called from several programs which need to handle
- * signals, including para_server and para_audiod. These programs define a
- * signal task structure and set its ->pre_select method to this function which
- * adds the file descriptor of the signal task to the set of descriptors to be
- * watched in the next select() call.
+ * signals, including para_server and para_audiod. These programs set up a
+ * signal pipe and a signal task structure, and use this function to tell the
+ * scheduler to monitor the read end of the pipe.
  *
  * Although the second parameter must be in fact a pointer to a signal_task
- * structure, the parameter is specified as void * here to match the
- * ->pre_select method of struct task.
+ * structure, the parameter is specified as void * here to match the signature
+ * declared in struct \ref task_info.
  */
-_static_inline_ void signal_pre_select(struct sched *s, void *context)
+_static_inline_ void signal_pre_monitor(struct sched *s, void *context)
 {
        struct signal_task *st = context;
-       para_fd_set(st->fd, &s->rfds, &s->max_fileno);
+       sched_monitor_readfd(st->fd, s);
 }
 
 struct signal_task *signal_init_or_die(void);
 void para_sigaction(int sig, void (*handler)(int));
 void para_install_sighandler(int);
 int para_reap_child(pid_t *pid);
-int para_next_signal(fd_set *rfds);
+int para_next_signal(void);
 void signal_shutdown(struct signal_task *st);
 void para_block_signal(int sig);
 void para_unblock_signal(int sig);
index caeacb1921341b04b26f117d5de6607a80ab95e9..cd3b7cde907388465f04ead39af10bd125d154ca 100644 (file)
--- a/spx_afh.c
+++ b/spx_afh.c
@@ -195,7 +195,7 @@ static size_t spx_make_meta_packet(struct taginfo *tags, char **result)
        }
        PARA_DEBUG_LOG("meta packet size: %zu bytes\n", sz);
        /* terminating zero byte for the last sprintf() */
-       buf = p = para_malloc(sz + 1);
+       buf = p = alloc(sz + 1);
        write_u32(p, comment_sz);
        p += 4;
        strcpy(p, tags->comment);
index 7be817ddaa49bf97e5d4025c43be63b42da9cd4c..08eac02a557b2d503646666edd7ffb35e1c5c5f7 100644 (file)
@@ -81,7 +81,7 @@ struct private_spxdec_data {
 
 static void spxdec_open(struct filter_node *fn)
 {
-       struct private_spxdec_data *psd = para_calloc(sizeof(*psd));
+       struct private_spxdec_data *psd = zalloc(sizeof(*psd));
 
        fn->private_data = psd;
        fn->min_iqs = 200;
@@ -171,7 +171,7 @@ static int speexdec_write_frames(int packet_no,
                if (new_frame_size <= 0)
                        continue;
                samples = new_frame_size * psd->shi.channels;
-               btr_output = para_malloc(2 * samples);
+               btr_output = arr_alloc(samples, 2);
                for (i = 0; i < samples; i++)
                        btr_output[i] = read_u16(output + i + skip_idx);
                btr_add_output((char *)btr_output, samples * 2, btrn);
@@ -246,7 +246,7 @@ static int compute_skip_samples(ogg_page *og, struct private_spxdec_data *psd)
        return ret;
 }
 
-static int speexdec_post_select(__a_unused struct sched *s, void *context)
+static int speexdec_post_monitor(__a_unused struct sched *s, void *context)
 {
        struct filter_node *fn = context;
        struct private_spxdec_data *psd = fn->private_data;
@@ -305,7 +305,7 @@ fail:
 const struct filter lsg_filter_cmd_com_spxdec_user_data = {
        .open = spxdec_open,
        .close = speexdec_close,
-       .pre_select = generic_filter_pre_select,
-       .post_select = speexdec_post_select,
+       .pre_monitor = generic_filter_pre_monitor,
+       .post_monitor = speexdec_post_monitor,
        .execute = speexdec_execute,
 };
diff --git a/stdin.c b/stdin.c
index 9408235a045a6767e2acf443208d95196990d379..d025b949e8e8d2566abde3f2f214db7998ba44ff 100644 (file)
--- a/stdin.c
+++ b/stdin.c
 #include "string.h"
 
 /*
- * If there is space left in the buffer of the stdin task add STDIN_FILENO to
- * the read fd set of s.
+ * If there is space left in the buffer of the stdin task, ask the scheduler to
+ * monitor STDIN_FILENO.
  */
-static void stdin_pre_select(struct sched *s, void *context)
+static void stdin_pre_monitor(struct sched *s, void *context)
 {
        struct stdin_task *sit = context;
        int ret;
@@ -28,16 +28,15 @@ static void stdin_pre_select(struct sched *s, void *context)
        if (ret <= 0)
                return;
        if (btr_pool_unused(sit->btrp) > 0)
-               return para_fd_set(STDIN_FILENO, &s->rfds, &s->max_fileno);
+               return sched_monitor_readfd(STDIN_FILENO, s);
        sched_request_timeout_ms(100, s);
 }
 
 /*
- * This function checks if STDIN_FILENO was included by in the read fd set of s
- * during the previous pre_select call. If so, and if STDIN_FILENO is readable,
- * data is read from stdin and fed into the buffer tree.
+ * Feed data from stdin into the buffer tree if STDIN_FILENO is ready for
+ * reading.
  */
-static int stdin_post_select(struct sched *s, void *context)
+static int stdin_post_monitor(__a_unused struct sched *s, void *context)
 {
        struct stdin_task *sit = context;
        ssize_t ret;
@@ -64,7 +63,7 @@ static int stdin_post_select(struct sched *s, void *context)
         * reference can not be freed, we're stuck.
         */
        sz = PARA_MIN(sz, btr_pool_size(sit->btrp) / 2);
-       ret = read_nonblock(STDIN_FILENO, buf, sz, &s->rfds, &n);
+       ret = read_nonblock(STDIN_FILENO, buf, sz, &n);
        if (n > 0)
                btr_add_output_pool(sit->btrp, n, sit->btrn);
        if (ret >= 0)
@@ -91,8 +90,8 @@ void stdin_task_register(struct stdin_task *sit, struct sched *s)
        int ret;
        struct task_info ti = {
                .name = "stdin",
-               .pre_select = stdin_pre_select,
-               .post_select = stdin_post_select,
+               .pre_monitor = stdin_pre_monitor,
+               .post_monitor = stdin_post_monitor,
                .context = sit,
        };
 
index 1f7791094a33a961c193c273a4c09e9b16f2be07..ba5f19670fce6c3c04d256a2056477064f5df7d3 100644 (file)
--- a/stdout.c
+++ b/stdout.c
 #include "stdout.h"
 #include "buffer_tree.h"
 
-/* Add STDOUT_FILENO to the write fd set if there is input data available. */
-static void stdout_pre_select(struct sched *s, void *context)
+/* Monitor STDOUT_FILENO if there is input data available. */
+static void stdout_pre_monitor(struct sched *s, void *context)
 {
        struct stdout_task *sot = context;
        int ret;
 
        ret = btr_node_status(sot->btrn, 0, BTR_NT_LEAF);
        if (ret > 0)
-               para_fd_set(STDOUT_FILENO, &s->wfds, &s->max_fileno);
+               sched_monitor_writefd(STDOUT_FILENO, s);
        else if (ret < 0)
                sched_min_delay(s);
 }
 
 /*
- * This function writes input data from the buffer tree to stdout if
- * STDOUT_FILENO is writable.
+ * If input from the buffer tree is available and STDOUT_FILENO is ready, write
+ * as much as possible.
  */
-static int stdout_post_select(struct sched *s, void *context)
+static int stdout_post_monitor(struct sched *s, void *context)
 {
        struct stdout_task *sot = context;
        struct btr_node *btrn = sot->btrn;
@@ -40,7 +40,7 @@ static int stdout_post_select(struct sched *s, void *context)
                goto out;
        if (ret == 0)
                return 0;
-       if (!FD_ISSET(STDOUT_FILENO, &s->wfds))
+       if (!sched_write_ok(STDOUT_FILENO, s))
                return 0;
 
        if (sot->must_set_nonblock_flag) {
@@ -79,8 +79,8 @@ void stdout_task_register(struct stdout_task *sot, struct sched *s)
 {
        int ret;
        struct task_info ti = {
-               .pre_select = stdout_pre_select,
-               .post_select = stdout_post_select,
+               .pre_monitor = stdout_pre_monitor,
+               .post_monitor = stdout_post_monitor,
                .context = sot,
                .name = "stdout",
        };
index 198e9f1d286ee2228638a2a7994fe9e65f41ccac..d8bd027b7010a149be59b8883b3e5ee685fb3c01 100644 (file)
--- a/string.c
+++ b/string.c
 #include "error.h"
 
 /**
- * Paraslash's version of realloc().
+ * Reallocate an array, abort on failure or bugs.
  *
- * \param p Pointer to the memory block, may be \p NULL.
- * \param size The desired new size.
+ * \param ptr Pointer to the memory block, may be NULL.
+ * \param nmemb Number of elements.
+ * \param size The size of one element in bytes.
  *
- * A wrapper for realloc(3). It calls \p exit(\p EXIT_FAILURE) on errors,
- * i.e. there is no need to check the return value in the caller.
+ * A wrapper for realloc(3) which aborts on invalid arguments or integer
+ * overflow. The wrapper also terminates the current process on allocation
+ * errors, so the caller does not need to check for failure.
  *
  * \return A pointer to newly allocated memory which is suitably aligned for
- * any kind of variable and may be different from \a p.
+ * any kind of variable and may be different from ptr.
  *
  * \sa realloc(3).
  */
-__must_check void *para_realloc(void *p, size_t size)
+__must_check void *arr_realloc(void *ptr, size_t nmemb, size_t size)
 {
+       size_t pr;
+
+       assert(size > 0);
+       assert(nmemb > 0);
+       assert(!__builtin_mul_overflow(nmemb, size, &pr));
+       assert(pr != 0);
+       ptr = realloc(ptr, pr);
+       assert(ptr);
+       return ptr;
+}
+
+/**
+ * Allocate an array, abort on failure or bugs.
+ *
+ * \param nmemb See \ref arr_realloc().
+ * \param size See \ref arr_realloc().
+ *
+ * Like \ref arr_realloc(), this aborts on invalid arguments, integer overflow
+ * and allocation errors.
+ *
+ * \return A pointer to newly allocated memory which is suitably aligned for
+ * any kind of variable.
+ *
+ * \sa See \ref arr_realloc().
+ */
+__must_check __malloc void *arr_alloc(size_t nmemb, size_t size)
+{
+       return arr_realloc(NULL, nmemb, size);
+}
+
+/**
+ * Allocate and initialize an array, abort on failure or bugs.
+ *
+ * \param nmemb See \ref arr_realloc().
+ * \param size See \ref arr_realloc().
+ *
+ * This calls \ref arr_alloc() and zeroes-out the array.
+ *
+ * \return See \ref arr_alloc().
+ */
+__must_check __malloc void *arr_zalloc(size_t nmemb, size_t size)
+{
+       void *ptr = arr_alloc(nmemb, size);
+
        /*
-        * No need to check for NULL pointers: If p is NULL, the call
-        * to realloc is equivalent to malloc(size)
+        * This multiplication can not overflow because the above call to \ref
+        * arr_alloc() aborts on overflow.
         */
-       assert(size);
-       if (!(p = realloc(p, size))) {
-               PARA_EMERG_LOG("realloc failed (size = %zu), aborting\n",
-                       size);
-               exit(EXIT_FAILURE);
-       }
-       return p;
+       memset(ptr, 0, nmemb * size);
+       return ptr;
 }
 
 /**
- * Paraslash's version of malloc().
+ * Allocate and initialize memory.
  *
  * \param size The desired new size.
  *
- * A wrapper for malloc(3) which exits on errors.
- *
- * \return A pointer to the allocated memory, which is suitably aligned for any
- * kind of variable.
+ * \return A pointer to the allocated and zeroed-out memory, which is suitably
+ * aligned for any kind of variable.
  *
- * \sa malloc(3).
+ * \sa \ref alloc(), calloc(3).
  */
-__must_check __malloc void *para_malloc(size_t size)
+__must_check void *zalloc(size_t size)
 {
-       void *p;
-
-       assert(size);
-       p = malloc(size);
-       if (!p) {
-               PARA_EMERG_LOG("malloc failed (size = %zu), aborting\n",
-                       size);
-               exit(EXIT_FAILURE);
-       }
-       return p;
+       return arr_zalloc(1, size);
 }
 
 /**
- * Paraslash's version of calloc().
+ * Paraslash's version of realloc().
  *
+ * \param p Pointer to the memory block, may be \p NULL.
  * \param size The desired new size.
  *
- * A wrapper for calloc(3) which exits on errors.
+ * A wrapper for realloc(3). It calls \p exit(\p EXIT_FAILURE) on errors,
+ * i.e. there is no need to check the return value in the caller.
  *
- * \return A pointer to the allocated and zeroed-out memory, which is suitably
- * aligned for any kind of variable.
+ * \return A pointer to newly allocated memory which is suitably aligned for
+ * any kind of variable and may be different from \a p.
  *
- * \sa calloc(3)
+ * \sa realloc(3).
  */
-__must_check __malloc void *para_calloc(size_t size)
+__must_check void *para_realloc(void *p, size_t size)
 {
-       void *ret = para_malloc(size);
+       return arr_realloc(p, 1, size);
+}
 
-       memset(ret, 0, size);
-       return ret;
+/**
+ * Paraslash's version of malloc().
+ *
+ * \param size The desired new size.
+ *
+ * A wrapper for malloc(3) which exits on errors.
+ *
+ * \return A pointer to the allocated memory, which is suitably aligned for any
+ * kind of variable.
+ *
+ * \sa malloc(3).
+ */
+__must_check __malloc void *alloc(size_t size)
+{
+       return arr_alloc(1, size);
 }
 
 /**
@@ -94,22 +140,23 @@ __must_check __malloc void *para_calloc(size_t size)
  *
  * \param s The string to be duplicated.
  *
- * A wrapper for strdup(3). It calls \p exit(EXIT_FAILURE) on errors, i.e.
- * there is no need to check the return value in the caller.
+ * A strdup(3)-like function which aborts if insufficient memory was available
+ * to allocate the duplicated string, absolving the caller from the
+ * responsibility to check for failure.
  *
- * \return A pointer to the duplicated string. If \a s was the \p NULL pointer,
- * an pointer to an empty string is returned.
+ * \return A pointer to the duplicated string. Unlike strdup(3), the caller may
+ * pass NULL, in which case the function returns a pointer to an empty string.
+ * Regardless of whether or not NULL was passed, the returned string is
+ * allocated on the heap and has to be freed by the caller.
  *
- * \sa strdup(3)
+ * \sa strdup(3).
  */
 __must_check __malloc char *para_strdup(const char *s)
 {
-       char *ret;
+       char *dupped_string = strdup(s? s: "");
 
-       if ((ret = strdup(s? s: "")))
-               return ret;
-       PARA_EMERG_LOG("strdup failed, aborting\n");
-       exit(EXIT_FAILURE);
+       assert(dupped_string);
+       return dupped_string;
 }
 
 /**
@@ -137,7 +184,7 @@ __printf_2_0 unsigned xvasprintf(char **result, const char *fmt, va_list ap)
        size_t size = 150;
        va_list aq;
 
-       *result = para_malloc(size + 1);
+       *result = alloc(size + 1);
        va_copy(aq, ap);
        ret = vsnprintf(*result, size, fmt, aq);
        va_end(aq);
@@ -245,56 +292,6 @@ __must_check __malloc char *para_strcat(char *a, const char *b)
        return tmp;
 }
 
-/**
- * Paraslash's version of dirname().
- *
- * \param name Pointer to the full path.
- *
- * Compute the directory component of \p name.
- *
- * \return If \a name is \p NULL or the empty string, return \p NULL.
- * Otherwise, Make a copy of \a name and return its directory component. Caller
- * is responsible to free the result.
- */
-__must_check __malloc char *para_dirname(const char *name)
-{
-       char *p, *ret;
-
-       if (!name || !*name)
-               return NULL;
-       ret = para_strdup(name);
-       p = strrchr(ret, '/');
-       if (!p)
-               *ret = '\0';
-       else
-               *p = '\0';
-       return ret;
-}
-
-/**
- * Paraslash's version of basename().
- *
- * \param name Pointer to the full path.
- *
- * Compute the filename component of \a name.
- *
- * \return \p NULL if (a) \a name is the empty string or \p NULL, or (b) name
- * ends with a slash.  Otherwise, a pointer within \a name is returned.  Caller
- * must not free the result.
- */
-__must_check char *para_basename(const char *name)
-{
-       char *ret;
-
-       if (!name || !*name)
-               return NULL;
-       ret = strrchr(name, '/');
-       if (!ret)
-               return (char *)name;
-       ret++;
-       return ret;
-}
-
 /**
  * Get the logname of the current user.
  *
@@ -311,15 +308,32 @@ __must_check __malloc char *para_logname(void)
 }
 
 /**
- * Get the home directory of the current user.
+ * Get the home directory of the calling user.
  *
  * \return A dynamically allocated string that must be freed by the caller. If
- * the home directory could not be found, this function returns "/tmp".
+ * no entry is found which matches the UID of the calling process, or any other
+ * error occurs, the function prints an error message and aborts.
+ *
+ * \sa getpwuid(3), getuid(2).
  */
 __must_check __malloc char *para_homedir(void)
 {
-       struct passwd *pw = getpwuid(getuid());
-       return para_strdup(pw? pw->pw_dir : "/tmp");
+       struct passwd *pw;
+
+       /*
+        * To distinguish between the error case and the "not found" case we
+        * have to check errno after getpwuid(3). The manual page recommends to
+        * set it to zero before the call.
+        */
+       errno = 0;
+       pw = getpwuid(getuid());
+       if (pw)
+               return para_strdup(pw->pw_dir);
+       if (errno != 0)
+               PARA_EMERG_LOG("getpwuid error: %s\n", strerror(errno));
+       else
+               PARA_EMERG_LOG("no pw entry for uid %u\n", (unsigned)getuid());
+       exit(EXIT_FAILURE);
 }
 
 /**
@@ -389,7 +403,7 @@ int for_each_line(unsigned flags, char *buf, size_t size,
                if (!(flags & FELF_DISCARD_FIRST) || start != buf) {
                        if (flags & FELF_READ_ONLY) {
                                size_t s = end - start;
-                               char *b = para_malloc(s + 1);
+                               char *b = alloc(s + 1);
                                memcpy(b, start, s);
                                b[s] = '\0';
                                ret = line_handler(b, private_data);
@@ -493,7 +507,7 @@ __printf_2_3 int para_printf(struct para_buffer *b, const char *fmt, ...)
        int ret, sz_off = (b->flags & PBF_SIZE_PREFIX)? 5 : 0;
 
        if (!b->buf) {
-               b->buf = para_malloc(128);
+               b->buf = alloc(128);
                b->size = 128;
                b->offset = 0;
        }
@@ -602,37 +616,6 @@ int para_atoi32(const char *str, int32_t *value)
        return 1;
 }
 
-static inline int loglevel_equal(const char *arg, const char * const ll)
-{
-       return !strncasecmp(arg, ll, strlen(ll));
-}
-
-/**
- * Compute the loglevel number from its name.
- *
- * \param txt The name of the loglevel (debug, info, ...).
- *
- * \return The numeric representation of the loglevel name.
- */
-int get_loglevel_by_name(const char *txt)
-{
-       if (loglevel_equal(txt, "debug"))
-               return LL_DEBUG;
-       if (loglevel_equal(txt, "info"))
-               return LL_INFO;
-       if (loglevel_equal(txt, "notice"))
-               return LL_NOTICE;
-       if (loglevel_equal(txt, "warning"))
-               return LL_WARNING;
-       if (loglevel_equal(txt, "error"))
-               return LL_ERROR;
-       if (loglevel_equal(txt, "crit"))
-               return LL_CRIT;
-       if (loglevel_equal(txt, "emerg"))
-               return LL_EMERG;
-       return -E_BAD_LL;
-}
-
 static int get_next_word(const char *buf, const char *delim, char **word)
 {
        enum line_state_flags {LSF_HAVE_WORD = 1, LSF_BACKSLASH = 2,
@@ -641,7 +624,7 @@ static int get_next_word(const char *buf, const char *delim, char **word)
        char *out;
        int ret, state = 0;
 
-       out = para_malloc(strlen(buf) + 1);
+       out = alloc(strlen(buf) + 1);
        *out = '\0';
        *word = out;
        for (in = buf; *in; in++) {
@@ -773,19 +756,17 @@ void free_argv(char **argv)
 static int create_argv_offset(int offset, const char *buf, const char *delim,
                char ***result)
 {
-       char *word, **argv = para_malloc((offset + 1) * sizeof(char *));
+       char *word, **argv = arr_zalloc(offset + 1, sizeof(char *));
        const char *p;
        int i, ret;
 
-       for (i = 0; i < offset; i++)
-               argv[i] = NULL;
-       for (p = buf; p && *p; p += ret, i++) {
+       for (p = buf, i = offset; p && *p; p += ret, i++) {
                ret = get_next_word(p, delim, &word);
                if (ret < 0)
                        goto err;
                if (!ret)
                        break;
-               argv = para_realloc(argv, (i + 2) * sizeof(char*));
+               argv = arr_realloc(argv, i + 2, sizeof(char*));
                argv[i] = word;
        }
        argv[i] = NULL;
@@ -840,27 +821,6 @@ int create_shifted_argv(const char *buf, const char *delim, char ***result)
        return create_argv_offset(1, buf, delim, result);
 }
 
-/**
- * Find out if the given string is contained in the arg vector.
- *
- * \param arg The string to look for.
- * \param argv The array to search.
- *
- * \return The first index whose value equals \a arg, or \p -E_ARG_NOT_FOUND if
- * arg was not found in \a argv.
- */
-int find_arg(const char *arg, char **argv)
-{
-       int i;
-
-       if (!argv)
-               return -E_ARG_NOT_FOUND;
-       for (i = 0; argv[i]; i++)
-               if (strcmp(arg, argv[i]) == 0)
-                       return i;
-       return -E_ARG_NOT_FOUND;
-}
-
 /**
  * Compile a regular expression.
  *
@@ -881,7 +841,7 @@ int para_regcomp(regex_t *preg, const char *regex, int cflags)
        if (ret == 0)
                return 1;
        size = regerror(ret, preg, NULL, 0);
-       buf = para_malloc(size);
+       buf = alloc(size);
        regerror(ret, preg, buf, size);
        PARA_ERROR_LOG("%s\n", buf);
        free(buf);
@@ -907,7 +867,7 @@ char *safe_strdup(const char *src, size_t len)
        char *p;
 
        assert(len < (size_t)-1);
-       p = para_malloc(len + 1);
+       p = alloc(len + 1);
        if (len > 0)
                memcpy(p, src, len);
        p[len] = '\0';
@@ -1061,7 +1021,7 @@ __must_check int strwidth(const char *s, size_t *result)
                return -ERRNO_TO_PARA_ERROR(errno);
        if (num_wchars == 0)
                return 0;
-       dest = para_malloc((num_wchars + 1) * sizeof(*dest));
+       dest = arr_alloc(num_wchars + 1, sizeof(*dest));
        src = s;
        memset(&state, 0, sizeof(state));
        num_wchars = mbsrtowcs(dest, &src, num_wchars, &state);
@@ -1116,7 +1076,7 @@ __must_check int sanitize_str(const char *src, size_t max_width,
        num_wchars = mbsrtowcs(NULL, &src, 0, &state);
        if (num_wchars == (size_t)-1)
                return -ERRNO_TO_PARA_ERROR(errno);
-       wcs = para_malloc((num_wchars + 1) * sizeof(*wcs));
+       wcs = arr_alloc(num_wchars + 1, sizeof(*wcs));
        memset(&state, 0, sizeof(state));
        num_wchars = mbsrtowcs(wcs, &src, num_wchars + 1, &state);
        assert(num_wchars != (size_t)-1);
@@ -1127,7 +1087,7 @@ __must_check int sanitize_str(const char *src, size_t max_width,
        }
        wcs[n] = L'\0';
        n = wcstombs(NULL, wcs, 0) + 1;
-       *result = para_malloc(n);
+       *result = alloc(n);
        num_wchars = wcstombs(*result, wcs, n);
        assert(num_wchars != (size_t)-1);
        free(wcs);
index 10251ae7c10b734bd906a644e0832e29b9e6bd9c..d773600fbf0a0168584235a5098beabb110a3416 100644 (file)
--- a/string.h
+++ b/string.h
@@ -67,28 +67,27 @@ int for_each_line(unsigned flags, char *buf, size_t size,
 } \
 )
 
-__must_check __malloc void *para_realloc(void *p, size_t size);
-__must_check __malloc void *para_malloc(size_t size);
-__must_check __malloc void *para_calloc(size_t size);
+__must_check void *arr_realloc(void *ptr, size_t nmemb, size_t size);
+__must_check void *para_realloc(void *p, size_t size);
+__must_check __malloc void *alloc(size_t size);
+__must_check __malloc void *zalloc(size_t size);
+__must_check __malloc void *arr_alloc(size_t nmemb, size_t size);
+__must_check __malloc void *arr_zalloc(size_t nmemb, size_t size);
 __must_check __malloc char *para_strdup(const char *s);
 
 __printf_2_0 unsigned xvasprintf(char **result, const char *fmt, va_list ap);
 __printf_2_3 unsigned xasprintf(char **result, const char *fmt, ...);
 __must_check __malloc __printf_1_2 char *make_message(const char *fmt, ...);
 __must_check __malloc char *para_strcat(char *a, const char *b);
-__must_check __malloc char *para_dirname(const char *name);
-__must_check char *para_basename(const char *name);
 __must_check __malloc char *para_logname(void);
 __must_check __malloc char *para_homedir(void);
 __malloc char *para_hostname(void);
 __printf_2_3 int para_printf(struct para_buffer *b, const char *fmt, ...);
 int para_atoi64(const char *str, int64_t *result);
 int para_atoi32(const char *str, int32_t *value);
-int get_loglevel_by_name(const char *txt);
 int read_size_header(const char *buf);
 int create_argv(const char *buf, const char *delim, char ***result);
 int create_shifted_argv(const char *buf, const char *delim, char ***result);
-int find_arg(const char *arg, char **argv);
 void free_argv(char **argv);
 int para_regcomp(regex_t *preg, const char *regex, int cflags);
 void freep(void *arg);
index 2ca2a657de6a79cb9b9e9ee3be47789e11f48e05..20db1b1d1136066a7a12b88b0f4b8cc539daea62 100644 (file)
@@ -102,8 +102,8 @@ static void sync_open(struct filter_node *fn)
        unsigned buddy_given;
        const struct lls_opt_result *r_b;
 
-       ctx = fn->private_data = para_calloc(sizeof(*ctx));
-       INIT_LIST_HEAD(&ctx->buddies);
+       ctx = fn->private_data = zalloc(sizeof(*ctx));
+       init_list_head(&ctx->buddies);
 
        /* create socket to listen for incoming packets */
        ret = makesock(
@@ -148,7 +148,7 @@ static void sync_open(struct filter_node *fn)
                        close(fd);
                        goto fail;
                }
-               buddy = para_malloc(sizeof(*buddy));
+               buddy = alloc(sizeof(*buddy));
                buddy->fd = fd;
                buddy->sbi = sbi + i;
                buddy->ping_received = false;
@@ -176,12 +176,12 @@ static void *sync_setup(const struct lls_parse_result *lpr)
 
        r_b = FILTER_CMD_OPT_RESULT(SYNC, BUDDY, lpr);
        n = lls_opt_given(r_b);
-       sbi = para_malloc(n * sizeof(*sbi));
+       sbi = arr_alloc(n, sizeof(*sbi));
        PARA_INFO_LOG("initializing buddy info array of length %u\n", n);
        for (i = 0; i < n; i++) {
                const char *url = lls_string_val(i, r_b);
                size_t len = strlen(url);
-               char *host = para_malloc(len + 1);
+               char *host = alloc(len + 1);
                int port;
                struct addrinfo *ai;
 
@@ -248,7 +248,7 @@ static void sync_set_timeout(struct sync_filter_context *ctx,
        tv_add(now, &to, &ctx->timeout);
 }
 
-static void sync_pre_select(struct sched *s, void *context)
+static void sync_pre_monitor(struct sched *s, void *context)
 {
        int ret;
        struct filter_node *fn = context;
@@ -261,7 +261,7 @@ static void sync_pre_select(struct sched *s, void *context)
        ret = btr_node_status(fn->btrn, 0, BTR_NT_INTERNAL);
        if (ret < 0)
                return sched_min_delay(s);
-       para_fd_set(ctx->listen_fd, &s->rfds, &s->max_fileno);
+       sched_monitor_readfd(ctx->listen_fd, s);
        if (ret == 0)
                return;
        if (ctx->timeout.tv_sec == 0) { /* must ping buddies */
@@ -284,7 +284,7 @@ static struct sync_buddy *sync_find_buddy(struct sockaddr *addr,
        return NULL;
 }
 
-static int sync_post_select(__a_unused struct sched *s, void *context)
+static int sync_post_monitor(__a_unused struct sched *s, void *context)
 {
        int ret;
        struct filter_node *fn = context;
@@ -324,7 +324,7 @@ static int sync_post_select(__a_unused struct sched *s, void *context)
                }
                ctx->ping_sent = true;
        }
-       if (FD_ISSET(ctx->listen_fd, &s->rfds)) {
+       if (sched_read_ok(ctx->listen_fd, s)) {
                char c;
                for (;;) {
                        struct sockaddr src_addr;
@@ -365,7 +365,8 @@ success:
        ret = -E_SYNC_COMPLETE; /* success */
        goto out;
 fail:
-       PARA_WARNING_LOG("%s\n", para_strerror(-ret));
+       if (ret != -E_EOF)
+               PARA_WARNING_LOG("%s\n", para_strerror(-ret));
 out:
        sync_close_buddies(ctx);
        btr_splice_out_node(&fn->btrn);
@@ -376,8 +377,8 @@ out:
 const struct filter lsg_filter_cmd_com_sync_user_data = {
        .setup = sync_setup,
        .open = sync_open,
-       .pre_select = sync_pre_select,
-       .post_select = sync_post_select,
+       .pre_monitor = sync_pre_monitor,
+       .post_monitor = sync_post_monitor,
        .close = sync_close,
        .teardown = sync_teardown
 };
diff --git a/t/audio_files/short-44100-2.mp3 b/t/audio_files/short-44100-2.mp3
new file mode 100644 (file)
index 0000000..917d59d
Binary files /dev/null and b/t/audio_files/short-44100-2.mp3 differ
index 03957464e17e2a50596149e59627cd7584424fe9..f7a407dc9e4bd99a71a3b3e364a6386fc55f1d44 100755 (executable)
@@ -24,7 +24,8 @@ get_audio_file_paths ogg
 declare -a oggs=($result)
 declare -a oggs_base=(${oggs[@]##*/})
 
-declare -a commands=() cmdline=() required_objects=() good=() bad=()
+declare -a commands=() cmdline=() required_objects=() good=() bad=() \
+       expect_failure=()
 i=0
 commands[$i]="help"
 cmdline[$i]="help -l"
@@ -36,6 +37,18 @@ cmdline[$i]="init"
 good[$i]='^successfully'
 bad[$i]='!^successfully'
 
+let i++
+commands[$i]='add_dir'
+required_objects[$i]='ogg_afh'
+cmdline[$i]="add -v $test_audio_file_dir"
+good[$i]='^adding'
+
+let i++
+commands[$i]='rm'
+required_objects[$i]='ogg_afh'
+cmdline[$i]="rm -v $test_audio_file_dir/*"
+good[$i]='^removing'
+
 let i++
 commands[$i]="add_ogg"
 required_objects[$i]='ogg_afh'
@@ -72,6 +85,15 @@ required_objects[$i]='ogg_afh'
 cmdline[$i]="ls -l=v ${oggs[@]}"
 good[$i]='^attributes_txt: 33'
 
+let i++
+commands[$i]='addmood'
+cmdline[$i]="addmood test-mood"
+
+let i++
+commands[$i]='empty-mood-parameter'
+cmdline[$i]="select m/"
+expect_failure[$i]='true'
+
 let i++
 commands[$i]="term"
 cmdline[$i]="term"
@@ -137,14 +159,19 @@ for ((i=0; i < ${#commands[@]}; i++)); do
                        continue
                fi
        fi
-       test_expect_success "$command" "
+       if [[ -n "${expect_failure[$i]}" ]]; then
+               f=test_expect_failure
+       else
+               f=test_expect_success
+       fi
+       $f "$command" "
        $PARA_CLIENT \
                --loglevel $loglevel \
                --server-port $port \
                --key-file $privkey \
                --config-file /dev/null \
                -- \
-               ${cmdline[$i]} > $command.out &&
+               ${cmdline[$i]} > $command.out < /dev/null &&
                { [[ -z \"${good[$i]}\" ]] || grep \"${good[$i]}\"; } < $command.out &&
                { [[ -z \"${bad[$i]}\" ]]  || ! grep \"${bad[$i]}\"; } < $command.out
        "
index 75249fe32b0b0e159b4b6ad1773efa2673b2cd22..1ba70632a95d585de16eb5b0e961291fe4ca83f1 100644 (file)
@@ -17,9 +17,12 @@ get_audio_file_paths()
 
 say_color()
 {
+       local severity=$1
+
+       shift
        if [[ "$o_nocolor" != "true" && -n "$1" ]]; then
                export TERM=$ORIGINAL_TERM
-               case "$1" in
+               case "$severity" in
                        error) tput $C_BOLD; tput $C_SETAF 1;;
                        skip)  tput $C_SETAF 5;;
                        ok)
@@ -32,8 +35,11 @@ say_color()
                                tput $C_SETAF 6;;
                esac
        fi
-       shift
-       printf "%s\n" "$*"
+       if [[ "$severity" == 'error' ]]; then
+               printf "%s\n" "$*" 1>&2
+       else
+               printf "%s\n" "$*"
+       fi
        if [[ "$o_nocolor" != "true" && -n "$1" ]]; then
                tput $C_SGR0
                export TERM=dumb
@@ -279,7 +285,7 @@ fixup_dirs()
        [[ -z "$o_trash_dir" ]] && o_trash_dir="$test_dir/trashes"
        [[ -z "$o_man_dir" ]] && o_man_dir="$test_dir/../build/man/man1"
 
-       # we want alsolute paths because relative paths become invalid
+       # we want absolute paths because relative paths become invalid
        # after changing to the trash dir
        [[ -n "${o_results_dir##/*}" ]] && o_results_dir="$wd/$o_results_dir"
        [[ -n "${o_executables_dir##/*}" ]] && o_executables_dir="$wd/$o_results_dir"
index 58d45ab43d6508619d34186b3f4a23f6d455ecc7..f98a9664aec2d1ee3d8129e70442468423fa79a8 100644 (file)
 #include "net.h"
 #include "fd.h"
 
-static void udp_recv_pre_select(struct sched *s, void *context)
+static void udp_recv_pre_monitor(struct sched *s, void *context)
 {
        struct receiver_node *rn = context;
 
-       if (generic_recv_pre_select(s, rn) <= 0)
+       if (generic_recv_pre_monitor(s, rn) <= 0)
                return;
-       para_fd_set(rn->fd, &s->rfds, &s->max_fileno);
+       sched_monitor_readfd(rn->fd, s);
 }
 
 static int udp_check_eof(size_t sz, struct iovec iov[2])
@@ -40,17 +40,17 @@ static int udp_check_eof(size_t sz, struct iovec iov[2])
                if (memcmp(iov[0].iov_base, FEC_EOF_PACKET,
                                FEC_EOF_PACKET_LEN) != 0)
                        return 0;
-               return -E_RECV_EOF;
+               return -E_EOF;
        }
        if (memcmp(iov[0].iov_base, FEC_EOF_PACKET, iov[0].iov_len) != 0)
                return 0;
        if (memcmp(iov[1].iov_base, &FEC_EOF_PACKET[iov[0].iov_len],
                        FEC_EOF_PACKET_LEN - iov[0].iov_len) != 0)
                return 0;
-       return -E_RECV_EOF;
+       return -E_EOF;
 }
 
-static int udp_recv_post_select(__a_unused struct sched *s, void *context)
+static int udp_recv_post_monitor(__a_unused struct sched *s, void *context)
 {
        struct receiver_node *rn = context;
        struct btr_node *btrn = rn->btrn;
@@ -68,7 +68,7 @@ static int udp_recv_post_select(__a_unused struct sched *s, void *context)
        ret = -E_UDP_OVERRUN;
        if (iovcnt == 0)
                goto out;
-       ret = readv_nonblock(rn->fd, iov, iovcnt, &s->rfds, &num_bytes);
+       ret = readv_nonblock(rn->fd, iov, iovcnt, &num_bytes);
        if (num_bytes == 0)
                goto out;
        readv_ret = ret;
@@ -168,7 +168,7 @@ static int udp_recv_open(struct receiver_node *rn)
        uint32_t port = RECV_CMD_OPT_UINT32_VAL(UDP, PORT, lpr);
        int ret;
 
-       ret = makesock(IPPROTO_UDP, 1, host, port, NULL);
+       ret = makesock(IPPROTO_UDP, true /* passive */, host, port, NULL);
        if (ret < 0)
                return ret;
        rn->fd = ret;
@@ -189,6 +189,6 @@ err:
 const struct receiver lsg_recv_cmd_com_udp_user_data = {
        .open = udp_recv_open,
        .close = udp_recv_close,
-       .pre_select = udp_recv_pre_select,
-       .post_select = udp_recv_post_select,
+       .pre_monitor = udp_recv_pre_monitor,
+       .post_monitor = udp_recv_post_monitor,
 };
index 04e2982f86bf125c5b4ce8ab2d6daa9280ea0bce..fe001025bc1ad6c73c0e64c8589977db1e3203af 100644 (file)
@@ -21,8 +21,8 @@
 #include "net.h"
 #include "server.h"
 #include "list.h"
-#include "send.h"
 #include "sched.h"
+#include "send.h"
 #include "vss.h"
 #include "portable_io.h"
 #include "fd.h"
@@ -56,9 +56,11 @@ static void udp_close_target(struct sender_client *sc)
        size_t len;
        struct udp_target *ut = sc->private_data;
 
+       if (process_is_command_handler())
+               return;
        if (ut->sent_fec_eof)
                return;
-       PARA_NOTICE_LOG("sending FEC EOF\n");
+       PARA_INFO_LOG("sending FEC EOF\n");
        len = vss_get_fec_eof_packet(&buf);
        /* Ignore write() errors since we are closing the target anyway. */
        if (write(sc->fd, buf, len))
@@ -70,7 +72,8 @@ static void udp_delete_target(struct sender_client *sc, const char *msg)
 {
        struct udp_target *ut = sc->private_data;
 
-       PARA_NOTICE_LOG("deleting %s (%s) from list\n", sc->name, msg);
+       if (!process_is_command_handler())
+               PARA_NOTICE_LOG("deleting %s (%s) from list\n", sc->name, msg);
        udp_close_target(sc);
        /* command handlers already called close_listed_fds() */
        if (!process_is_command_handler()) {
@@ -184,7 +187,7 @@ static int udp_resolve_target(const char *url, struct sender_command_data *scd)
                return ret;
        port = scd->port > 0 ? scd->port : OPT_UINT32_VAL(UDP_DEFAULT_PORT);
 
-       ret = para_connect_simple(IPPROTO_UDP, scd->host, port);
+       ret = para_connect(IPPROTO_UDP, scd->host, port);
        if (ret < 0)
                return ret;
 
@@ -246,7 +249,7 @@ static int udp_init_fec(struct sender_client *sc)
        struct udp_target *ut = sc->private_data;
        int mps;
 
-       PARA_NOTICE_LOG("sending to udp %s\n", sc->name);
+       PARA_INFO_LOG("sending to udp %s\n", sc->name);
        ut->sent_fec_eof = false;
        mps = generic_max_transport_msg_size(sc->fd) - sizeof(struct udphdr);
        PARA_INFO_LOG("current MPS = %d bytes\n", mps);
@@ -323,8 +326,8 @@ static int udp_com_add(struct sender_command_data *scd)
                                sc->name);
                return -E_TARGET_EXISTS;
        }
-       ut = para_calloc(sizeof(*ut));
-       sc = para_calloc(sizeof(*sc));
+       ut = zalloc(sizeof(*ut));
+       sc = zalloc(sizeof(*sc));
        ut->fcp.slices_per_group      = scd->slices_per_group;
        ut->fcp.data_slices_per_group = scd->data_slices_per_group;
        ut->fcp.init_fec              = udp_init_fec;
@@ -333,7 +336,7 @@ static int udp_com_add(struct sender_command_data *scd)
 
        sc->private_data = ut;
        sc->fd = -1;
-       ret = para_connect_simple(IPPROTO_UDP, scd->host, scd->port);
+       ret = para_connect(IPPROTO_UDP, scd->host, scd->port);
        if (ret < 0)
                goto err;
        sc->fd = ret;
@@ -392,7 +395,7 @@ static void udp_init_target_list(void)
        struct sender_command_data scd;
        int i;
 
-       INIT_LIST_HEAD(&targets);
+       init_list_head(&targets);
        for (i = 0; i < OPT_GIVEN(UDP_TARGET); i++) {
                const char *arg = lls_string_val(i, OPT_RESULT(UDP_TARGET));
                if (udp_resolve_target(arg, &scd) < 0)
@@ -425,7 +428,7 @@ static char *udp_help(void)
 /* Initialize the list of udp targets. */
 static void udp_send_init(void)
 {
-       INIT_LIST_HEAD(&targets);
+       init_list_head(&targets);
        sender_status = SENDER_off;
        udp_init_target_list();
        if (!OPT_GIVEN(UDP_NO_AUTOSTART))
diff --git a/upgrade_db.c b/upgrade_db.c
new file mode 100644 (file)
index 0000000..487d46c
--- /dev/null
@@ -0,0 +1,386 @@
+/* Copyright (C) 2020 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
+
+/** \file upgrade_db.c Prepare the paraslash database for paraslash-0.7. */
+
+#include <osl.h>
+#include <lopsub.h>
+#include <regex.h>
+
+#include "upgrade_db.lsg.h"
+#include "para.h"
+#include "error.h"
+#include "string.h"
+#include "fd.h"
+#include "crypt.h"
+#include "version.h"
+
+#define CMD_PTR (lls_cmd(0, upgrade_db_suite))
+#define OPT_RESULT(_name, _lpr) \
+       (lls_opt_result(LSG_UPGRADE_DB_PARA_UPGRADE_DB_OPT_ ## _name, lpr))
+#define OPT_GIVEN(_name, _lpr) (lls_opt_given(OPT_RESULT(_name, _lpr)))
+#define OPT_UINT32_VAL(_name, _lpr) (lls_uint32_val(0, OPT_RESULT(_name, _lpr)))
+#define OPT_STRING_VAL(_name, _lpr) (lls_string_val(0, OPT_RESULT(_name, _lpr)))
+
+static int loglevel;
+INIT_STDERR_LOGGING(loglevel);
+
+/** Array of error strings. */
+DEFINE_PARA_ERRLIST;
+
+static void handle_help_flag(struct lls_parse_result *lpr)
+{
+       char *help;
+
+       if (OPT_GIVEN(DETAILED_HELP, lpr))
+               help = lls_long_help(CMD_PTR);
+       else if (OPT_GIVEN(HELP, lpr))
+               help = lls_short_help(CMD_PTR);
+       else
+               return;
+       printf("%s\n", help);
+       free(help);
+       exit(EXIT_SUCCESS);
+}
+
+static struct stat *path_exists(const char *path)
+{
+       static struct stat sb;
+
+       if (stat(path, &sb) < 0)
+               return NULL;
+       return &sb;
+}
+
+static bool is_dir(const char *path)
+{
+       struct stat *sb = path_exists(path);
+       if (!sb)
+               return false;
+       return (sb->st_mode & S_IFMT) == S_IFDIR;
+}
+
+__noreturn static void die(const char *msg)
+{
+       PARA_EMERG_LOG("%s\n", msg);
+       exit(EXIT_FAILURE);
+}
+
+static int string_compare(const struct osl_object *obj1, const struct osl_object *obj2)
+{
+       const char *str1 = obj1->data;
+       const char *str2 = obj2->data;
+       return strncmp(str1, str2, PARA_MIN(obj1->size, obj2->size));
+}
+
+static char *src_db_dir, *dst_db_dir, *src_aft_dir, *dst_aft_dir;
+
+static void set_paths(const struct lls_parse_result *lpr)
+{
+       char *home = para_homedir();
+
+       if (OPT_GIVEN(SRC_DATABASE_DIR, lpr))
+               src_db_dir = para_strdup(OPT_STRING_VAL(SRC_DATABASE_DIR,
+                       lpr));
+       else
+               src_db_dir = make_message(
+                       "%s/.paraslash/afs_database-0.4", home);
+       if (OPT_GIVEN(DST_DATABASE_DIR, lpr))
+               dst_db_dir = para_strdup(OPT_STRING_VAL(DST_DATABASE_DIR,
+                       lpr));
+       else
+               dst_db_dir = make_message(
+                       "%s/.paraslash/afs_database-0.7", home);
+       free(home);
+       src_aft_dir = make_message("%s/audio_files", src_db_dir);
+       dst_aft_dir = make_message("%s/audio-files", src_db_dir);
+       PARA_NOTICE_LOG("source aft dir: %s\n", src_aft_dir);
+       PARA_NOTICE_LOG("destination aft dir: %s\n", dst_aft_dir);
+}
+
+static void check_sanity(void)
+{
+       PARA_INFO_LOG("checking source and destination directories\n");
+       if (!is_dir(src_db_dir))
+               die("source db directory does not exist");
+       if (path_exists(dst_db_dir))
+               die("destination db already exists");
+       if (!is_dir(src_aft_dir))
+               die("source audio file table does not exist");
+       if (path_exists(dst_aft_dir))
+               die("destination audio file table already exists");
+}
+
+/** The columns of the audio file table (both old and new). */
+enum audio_file_table_columns {
+       /** The hash on the content of the audio file. */
+       AFTCOL_HASH,
+       /** The full path in the filesystem. */
+       AFTCOL_PATH,
+       /** The audio file selector info. */
+       AFTCOL_AFSI,
+       /** The audio format handler info. */
+       AFTCOL_AFHI,
+       /** The chunk table info and the chunk table of the audio file. */
+       AFTCOL_CHUNKS,
+       /** The number of columns of this table. */
+       NUM_AFT_COLUMNS
+};
+
+#define AFSI_SIZE 32
+
+static int src_aft_hash_compare(const struct osl_object *obj1,
+               const struct osl_object *obj2)
+{
+       return hash_compare((unsigned char *)obj1->data,
+               (unsigned char *)obj2->data);
+}
+
+static struct osl_column_description src_aft_cols[] = {
+       [AFTCOL_HASH] = {
+               .storage_type = OSL_MAPPED_STORAGE,
+               .storage_flags = OSL_RBTREE | OSL_FIXED_SIZE | OSL_UNIQUE,
+               .name = "hash",
+               .compare_function = src_aft_hash_compare,
+               .data_size = HASH_SIZE
+       },
+       [AFTCOL_PATH] = {
+               .storage_type = OSL_MAPPED_STORAGE,
+               .storage_flags = OSL_RBTREE | OSL_UNIQUE,
+               .name = "path",
+               .compare_function = string_compare,
+       },
+       [AFTCOL_AFSI] = {
+               .storage_type = OSL_MAPPED_STORAGE,
+               .storage_flags = OSL_FIXED_SIZE,
+               .name = "afs_info",
+               .data_size = AFSI_SIZE
+       },
+       [AFTCOL_AFHI] = {
+               .storage_type = OSL_MAPPED_STORAGE,
+               .name = "afh_info",
+       },
+       [AFTCOL_CHUNKS] = {
+               .storage_type = OSL_DISK_STORAGE,
+               .name = "chunks",
+       }
+};
+
+static struct osl_table_description src_aft_desc = {
+       .name = "audio_files",
+       .num_columns = NUM_AFT_COLUMNS,
+       .flags = OSL_LARGE_TABLE,
+       .column_descriptions = src_aft_cols
+};
+
+static struct osl_table *src_aft, *dst_aft;
+
+static void open_src_aft(void)
+{
+       int ret;
+
+       PARA_NOTICE_LOG("opening: %s\n", src_aft_dir);
+       src_aft_desc.dir = src_db_dir;
+       ret = osl(osl_open_table(&src_aft_desc, &src_aft));
+       if (ret < 0) {
+               PARA_EMERG_LOG("can not open source audio file table: %s\n",
+                        para_strerror(-ret));
+               exit(EXIT_FAILURE);
+       }
+       PARA_INFO_LOG("successfully opened source audio file table\n");
+}
+
+static int dst_aft_hash_compare(const struct osl_object *obj1,
+               const struct osl_object *obj2)
+{
+       return hash2_compare((unsigned char *)obj1->data,
+               (unsigned char *)obj2->data);
+}
+
+/* identical to src_aft_cols except the comparator and the hash size. */
+static struct osl_column_description dst_aft_cols[] = {
+       [AFTCOL_HASH] = {
+               .storage_type = OSL_MAPPED_STORAGE,
+               .storage_flags = OSL_RBTREE | OSL_FIXED_SIZE | OSL_UNIQUE,
+               .name = "hash",
+               .compare_function = dst_aft_hash_compare,
+               .data_size = HASH2_SIZE
+       },
+       [AFTCOL_PATH] = {
+               .storage_type = OSL_MAPPED_STORAGE,
+               .storage_flags = OSL_RBTREE | OSL_UNIQUE,
+               .name = "path",
+               .compare_function = string_compare,
+       },
+       [AFTCOL_AFSI] = {
+               .storage_type = OSL_MAPPED_STORAGE,
+               .storage_flags = OSL_FIXED_SIZE,
+               .name = "afs_info",
+               .data_size = AFSI_SIZE
+       },
+       [AFTCOL_AFHI] = {
+               .storage_type = OSL_MAPPED_STORAGE,
+               .name = "afh_info",
+       },
+       [AFTCOL_CHUNKS] = {
+               .storage_type = OSL_DISK_STORAGE,
+               .name = "chunks",
+       }
+};
+
+static struct osl_table_description dst_aft_desc = {
+       .name = "audio-files",
+       .num_columns = NUM_AFT_COLUMNS,
+       .flags = OSL_LARGE_TABLE,
+       .column_descriptions = dst_aft_cols
+};
+
+static int create_and_open_dst_aft(void)
+{
+       int ret;
+
+       PARA_NOTICE_LOG("creating %s\n", dst_aft_dir);
+       dst_aft_desc.dir = src_db_dir;
+       ret = osl(osl_create_table(&dst_aft_desc));
+       if (ret < 0) {
+               PARA_EMERG_LOG("could not create destination audio file table\n");
+               return ret;
+       }
+       ret = osl(osl_open_table(&dst_aft_desc, &dst_aft));
+       if (ret < 0) {
+               PARA_EMERG_LOG("could not open destination audio file table: %s\n",
+                        para_strerror(-ret));
+               exit(EXIT_FAILURE);
+       }
+       PARA_INFO_LOG("successfully opened destination audio file table\n");
+       return 0;
+}
+
+static int copy_aft_row(struct osl_row *row, void *data)
+{
+       unsigned *n = data;
+       int i, ret;
+       unsigned char hash2[HASH2_SIZE] = "\0";
+       struct osl_object objs[NUM_AFT_COLUMNS] = {
+               [AFTCOL_HASH] = {.data = hash2, .size = HASH2_SIZE}
+       };
+
+       ret = osl(osl_open_disk_object(src_aft, row, AFTCOL_CHUNKS,
+               objs + AFTCOL_CHUNKS));
+       if (ret < 0) {
+               PARA_ERROR_LOG("can not open disk object: %s\n",
+                       para_strerror(-ret));
+               return ret;
+       }
+       for (i = 0; i < NUM_AFT_COLUMNS; i++) {
+               if (i == AFTCOL_HASH) /* never assign to this index */
+                       continue;
+               if (i == AFTCOL_CHUNKS) /* disk storage object handled above */
+                       continue;
+               /* mapped storage */
+               ret = osl(osl_get_object(src_aft, row, i, objs + i));
+               if (ret < 0) {
+                       PARA_ERROR_LOG("get_object (col = %d): %s\n",
+                               i, para_strerror(-ret));
+                       return ret;
+               }
+               if (i == AFTCOL_PATH)
+                       PARA_DEBUG_LOG("copying %s\n", (char *)objs[i].data);
+       }
+       (*n)++;
+       memcpy(hash2, n, sizeof(*n));
+       ret = osl(osl_add_row(dst_aft, objs));
+       if (ret < 0)
+               PARA_ERROR_LOG("failed to add row: %s\n", para_strerror(-ret));
+       osl_close_disk_object(objs + AFTCOL_CHUNKS);
+       return ret;
+}
+
+static int convert_aft(void)
+{
+       unsigned n;
+       int ret;
+
+       osl_get_num_rows(src_aft, &n);
+       PARA_NOTICE_LOG("converting hash of %u rows to sha256\n", n);
+       n = 0;
+       ret = osl(osl_rbtree_loop(src_aft, AFTCOL_HASH, &n, copy_aft_row));
+       if (ret < 0)
+               PARA_ERROR_LOG("osl_rbtree_loop failed\n");
+       return ret;
+}
+
+static int remove_source_aft(void)
+{
+       pid_t pid;
+       int fds[3] = {-1, -1, -1}; /* no redirection of stdin/stdout/stderr */
+       int ret, wstatus;
+       char *cmdline = make_message("rm -rf %s", src_aft_dir);
+
+       PARA_NOTICE_LOG("removing %s\n", src_aft_dir);
+       ret = para_exec_cmdline_pid(&pid, cmdline, fds);
+       if (ret < 0) {
+               PARA_ERROR_LOG("exec failure\n");
+               goto out;
+       }
+       do {
+               ret = waitpid(pid, &wstatus, 0);
+       } while (ret < 0 && errno == EINTR);
+       if (ret < 0)
+               PARA_ERROR_LOG("waitpid failure\n");
+out:
+       return ret;
+}
+
+static int rename_db(void)
+{
+       PARA_NOTICE_LOG("renaming %s -> %s\n", src_db_dir, dst_db_dir);
+       if (rename(src_db_dir, dst_db_dir) < 0) {
+               int ret = -ERRNO_TO_PARA_ERROR(errno);
+               PARA_ERROR_LOG("rename failed\n");
+               return ret;
+       }
+       return 1;
+}
+
+int main(int argc, char *argv[])
+{
+       struct lls_parse_result *lpr; /* command line */
+       char *errctx;
+       int ret;
+
+       ret = lls(lls_parse(argc, argv, CMD_PTR, &lpr, &errctx));
+       if (ret < 0)
+               goto out;
+       loglevel = OPT_UINT32_VAL(LOGLEVEL, lpr);
+       version_handle_flag("recv", OPT_GIVEN(VERSION, lpr));
+       handle_help_flag(lpr);
+       set_paths(lpr);
+       check_sanity();
+       open_src_aft();
+       ret = create_and_open_dst_aft();
+       if (ret < 0)
+               goto close_src_aft;
+       ret = convert_aft();
+       if (ret < 0)
+               goto close_dst_aft;
+       ret = remove_source_aft();
+       if (ret < 0)
+               goto close_dst_aft;
+       ret = rename_db();
+close_dst_aft:
+       osl_close_table(dst_aft, OSL_MARK_CLEAN);
+close_src_aft:
+       PARA_INFO_LOG("closing audio file tables\n");
+       osl_close_table(src_aft, OSL_MARK_CLEAN);
+out:
+       if (ret < 0) {
+               if (errctx)
+                       PARA_ERROR_LOG("%s\n", errctx);
+               free(errctx);
+               PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+       } else {
+               PARA_WARNING_LOG("success. Now start para_server and force-add"
+                       " all audio files.\n");
+       }
+       return ret < 0? EXIT_FAILURE : EXIT_SUCCESS;
+}
index 32a4309d4360fa73a8e7d0bbef622a7928001bb0..46770edf701e983d424e8869e7c9ccd4730c3cf9 100644 (file)
@@ -91,7 +91,7 @@ void user_list_init(const char *user_list_file)
                        continue;
                if (strcmp(w, "user"))
                        continue;
-               PARA_DEBUG_LOG("found entry for user %s\n", n);
+               PARA_INFO_LOG("loading pubkey %s for user %s\n", k, n);
                ret = apc_get_pubkey(k, &pubkey);
                if (ret < 0) {
                        PARA_NOTICE_LOG("skipping entry for user %s: %s\n", n,
@@ -110,7 +110,7 @@ void user_list_init(const char *user_list_file)
                        apc_free_pubkey(pubkey);
                        continue;
                }
-               u = para_malloc(sizeof(*u));
+               u = alloc(sizeof(*u));
                u->name = para_strdup(n);
                u->pubkey = pubkey;
                u->perms = 0;
diff --git a/vss.c b/vss.c
index 73c7231128b94152268b1bc8ea02d1f524b25b6a..cd55851c366cec61c0bf7bc9501609fc2fa8a6c5 100644 (file)
--- a/vss.c
+++ b/vss.c
@@ -28,8 +28,8 @@
 #include "net.h"
 #include "server.h"
 #include "list.h"
-#include "send.h"
 #include "sched.h"
+#include "send.h"
 #include "vss.h"
 #include "ipc.h"
 #include "fd.h"
@@ -43,7 +43,7 @@ const struct sender * const senders[] = {
 enum afs_socket_status {
        /** Socket is inactive. */
        AFS_SOCKET_READY,
-       /** Socket fd was included in the write fd set for select(). */
+       /** Socket fd was monitored for writing. */
        AFS_SOCKET_CHECK_FOR_WRITE,
        /** vss wrote a request to the socket and waits for reply from afs. */
        AFS_SOCKET_AFD_PENDING
@@ -148,14 +148,10 @@ struct fec_client {
        struct fec_group group;
        /** The current slice. */
        uint8_t current_slice_num;
-       /** The data to be FEC-encoded (point to a region within the mapped audio file). */
-       const unsigned char **src_data;
+       /** The data to be FEC-encoded */
+       unsigned char **src_data;
        /** Last time an audio  header was sent. */
        struct timeval next_header_time;
-       /** Used for the last source pointer of an audio file. */
-       unsigned char *extra_src_buf;
-       /** Needed for the last slice of the audio file header. */
-       unsigned char *extra_header_buf;
        /** Extra slices needed to store largest chunk + header. */
        int num_extra_slices;
        /** Contains the FEC-encoded data. */
@@ -240,6 +236,21 @@ static bool need_data_slices(struct fec_client *fc, struct vss_task *vsst)
        return false;
 }
 
+static int fc_num_data_slices(const struct fec_client *fc)
+{
+       return fc->fcp->data_slices_per_group + fc->num_extra_slices;
+}
+
+static int fc_num_slices(const struct fec_client *fc)
+{
+       return fc->fcp->slices_per_group + fc->num_extra_slices;
+}
+
+static int fc_num_redundant_slices(const struct fec_client *fc)
+{
+       return fc->fcp->slices_per_group - fc->fcp->data_slices_per_group;
+}
+
 static int num_slices(long unsigned bytes, int max_payload, int rs)
 {
        int ret;
@@ -270,7 +281,7 @@ static void set_group_timing(struct fec_client *fc, struct vss_task *vsst)
 
 static int initialize_fec_client(struct fec_client *fc, struct vss_task *vsst)
 {
-       int k, n, ret;
+       int i, k, n, ret;
        int hs, ds, rs; /* header/data/redundant slices */
        struct fec_client_parms *fcp = fc->fcp;
 
@@ -290,7 +301,17 @@ static int initialize_fec_client(struct fec_client *fc, struct vss_task *vsst)
        if (fc->mps <= FEC_HEADER_SIZE)
                return -ERRNO_TO_PARA_ERROR(EINVAL);
 
-       rs = fc->fcp->slices_per_group - fc->fcp->data_slices_per_group;
+       /* free previous buffers, if any */
+       if (fc->src_data) {
+               k = fc_num_data_slices(fc);
+               for (i = 0; i < k; i++)
+                       free(fc->src_data[i]);
+               free(fc->src_data);
+               fc->src_data = NULL;
+       }
+       free(fc->enc_buf);
+
+       rs = fc_num_redundant_slices(fc);
        ret = num_slices(vsst->header_len, fc->mps - FEC_HEADER_SIZE, rs);
        if (ret < 0)
                return ret;
@@ -306,17 +327,18 @@ static int initialize_fec_client(struct fec_client *fc, struct vss_task *vsst)
        if (k < fc->fcp->data_slices_per_group)
                k = fc->fcp->data_slices_per_group;
        fc->num_extra_slices = k - fc->fcp->data_slices_per_group;
-       n = k + rs;
+       n = fc_num_slices(fc);
+       PARA_INFO_LOG("mps: %d, k: %d, n: %d, extra slices: %d\n",
+               fc->mps, k, n, fc->num_extra_slices);
+
        fec_free(fc->parms);
        ret = fec_new(k, n, &fc->parms);
        if (ret < 0)
                return ret;
-       PARA_INFO_LOG("mps: %d, k: %d, n: %d, extra slices: %d\n",
-               fc->mps, k, n, fc->num_extra_slices);
-       fc->src_data = para_realloc(fc->src_data, k * sizeof(char *));
-       fc->enc_buf = para_realloc(fc->enc_buf, fc->mps);
-       fc->extra_src_buf = para_realloc(fc->extra_src_buf, fc->mps);
-       fc->extra_header_buf = para_realloc(fc->extra_header_buf, fc->mps);
+       fc->src_data = arr_alloc(k, sizeof(char *));
+       for (i = 0; i < k; i++)
+               fc->src_data[i] = alloc(fc->mps);
+       fc->enc_buf = alloc(fc->mps);
 
        fc->state = FEC_STATE_READY_TO_RUN;
        fc->next_header_time.tv_sec = 0;
@@ -326,7 +348,7 @@ static int initialize_fec_client(struct fec_client *fc, struct vss_task *vsst)
 }
 
 static int vss_get_chunk(int chunk_num, struct vss_task *vsst,
-               char **buf, size_t *sz)
+               char **buf, uint32_t *len)
 {
        int ret;
 
@@ -341,15 +363,15 @@ static int vss_get_chunk(int chunk_num, struct vss_task *vsst,
        if (chunk_num == 0 && vsst->header_len > 0) {
                assert(vsst->header_buf);
                *buf = vsst->header_buf; /* stripped header */
-               *sz = vsst->header_len;
+               *len = vsst->header_len;
                return 0;
        }
        ret = afh_get_chunk(chunk_num, &mmd->afd.afhi,
                mmd->afd.audio_format_id, vsst->map, vsst->mapsize,
-               (const char **)buf, sz, &vsst->afh_context);
+               (const char **)buf, len, &vsst->afh_context);
        if (ret < 0) {
                *buf = NULL;
-               *sz = 0;
+               *len = 0;
        }
        return ret;
 }
@@ -358,7 +380,7 @@ static int compute_group_size(struct vss_task *vsst, struct fec_group *g,
                int max_bytes)
 {
        char *buf;
-       size_t len;
+       uint32_t len;
        int ret, i, max_chunks = PARA_MAX(1LU, 150 / tv2ms(vss_chunk_time()));
 
        if (g->first_chunk == 0) {
@@ -394,7 +416,10 @@ static int compute_group_size(struct vss_task *vsst, struct fec_group *g,
                g->bytes += len;
                g->num_chunks++;
        }
-       assert(g->num_chunks);
+       if (g->num_chunks == 0)
+               return -E_EOF;
+       PARA_DEBUG_LOG("group #%u: %u chunks, %u bytes total\n", g->num,
+               g->num_chunks, g->bytes);
        return 1;
 }
 
@@ -448,8 +473,8 @@ static int compute_group_size(struct vss_task *vsst, struct fec_group *g,
 static int compute_slice_size(struct fec_client *fc, struct vss_task *vsst)
 {
        struct fec_group *g = &fc->group;
-       int k = fc->fcp->data_slices_per_group + fc->num_extra_slices;
-       int n = fc->fcp->slices_per_group + fc->num_extra_slices;
+       int k = fc_num_data_slices(fc);
+       int n = fc_num_slices(fc);
        int ret, k1, k2, h, d, min, max, sum;
        int max_slice_bytes = fc->mps - FEC_HEADER_SIZE;
        int max_group_bytes;
@@ -512,9 +537,8 @@ static int compute_slice_size(struct fec_client *fc, struct vss_task *vsst)
 
 static int setup_next_fec_group(struct fec_client *fc, struct vss_task *vsst)
 {
-       int ret, i, k, n, data_slices;
-       size_t len;
-       char *buf, *p;
+       int ret, i, c;
+       size_t copy, src_copied, slice_copied;
        struct fec_group *g = &fc->group;
 
        if (fc->state == FEC_STATE_NONE) {
@@ -538,86 +562,63 @@ static int setup_next_fec_group(struct fec_client *fc, struct vss_task *vsst)
                g->first_chunk += g->num_chunks;
                g->num++;
        }
-       k = fc->fcp->data_slices_per_group + fc->num_extra_slices;
-       n = fc->fcp->slices_per_group + fc->num_extra_slices;
-
        compute_slice_size(fc, vsst);
        assert(g->slice_bytes > 0);
-       ret = num_slices(g->bytes, g->slice_bytes, n - k);
-       if (ret < 0)
-               return ret;
-       data_slices = ret;
-       assert(g->num_header_slices + data_slices <= k);
        fc->current_slice_num = 0;
        if (g->num == 0)
                set_group_timing(fc, vsst);
        /* setup header slices */
-       buf = vsst->header_buf;
-       for (i = 0; i < g->num_header_slices; i++) {
-               uint32_t payload_size;
-               if (buf + g->slice_bytes <= vsst->header_buf + vsst->header_len) {
-                       fc->src_data[i] = (const unsigned char *)buf;
-                       buf += g->slice_bytes;
-                       continue;
-               }
-               /*
-                * Can not use vss->header_buf for this slice as it
-                * goes beyond the buffer. This slice will not be fully
-                * used.
-                */
-               payload_size = vsst->header_buf + vsst->header_len - buf;
-               memcpy(fc->extra_header_buf, buf, payload_size);
-               if (payload_size < g->slice_bytes)
-                       memset(fc->extra_header_buf + payload_size, 0,
-                               g->slice_bytes - payload_size);
-               /*
-                * There might be more than one header slice to fill although
-                * only the first one will be used. Set all header slices to
-                * our extra buffer.
-                */
-               while (i < g->num_header_slices)
-                       fc->src_data[i++] = fc->extra_header_buf;
-               break; /* we don't want i to be increased. */
+       for (i = 0, src_copied = 0; i < g->num_header_slices; i++) {
+               copy = PARA_MIN((size_t)g->slice_bytes, vsst->header_len - src_copied);
+               if (copy == 0)
+                       break;
+               memcpy(fc->src_data[i], vsst->header_buf + src_copied, copy);
+               if (copy < g->slice_bytes)
+                       memset(fc->src_data[i] + copy, 0, g->slice_bytes - copy);
+               src_copied += copy;
        }
-
        /*
-        * Setup data slices. Note that for ogg streams chunk 0 points to a
-        * buffer on the heap rather than to the mapped audio file.
+        * There might be more than one header slice to fill although only the
+        * first one will be used. Zero out any remaining header slices.
         */
-       ret = vss_get_chunk(g->first_chunk, vsst, &buf, &len);
-       if (ret < 0)
-               return ret;
-       for (p = buf; i < g->num_header_slices + data_slices; i++) {
-               if (p + g->slice_bytes > buf + g->bytes) {
-                       /*
-                        * We must make a copy for this slice since using p
-                        * directly would exceed the buffer.
-                        */
-                       uint32_t payload_size = buf + g->bytes - p;
-                       assert(payload_size + FEC_HEADER_SIZE <= fc->mps);
-                       memcpy(fc->extra_src_buf, p, payload_size);
-                       if (payload_size < g->slice_bytes)
-                               memset(fc->extra_src_buf + payload_size, 0,
-                                       g->slice_bytes - payload_size);
-                       fc->src_data[i] = fc->extra_src_buf;
-                       i++;
-                       break;
+       while (i < g->num_header_slices)
+               memset(fc->src_data[i++], 0, g->slice_bytes);
+
+       slice_copied = 0;
+       for (c = g->first_chunk; c < g->first_chunk + g->num_chunks; c++) {
+               char *buf;
+               uint32_t src_len;
+               ret = vss_get_chunk(c, vsst, &buf, &src_len);
+               if (ret < 0)
+                       return ret;
+               if (src_len == 0)
+                       continue;
+               src_copied = 0;
+               while (src_copied < src_len) {
+                       copy = PARA_MIN((size_t)g->slice_bytes - slice_copied,
+                               src_len - src_copied);
+                       memcpy(fc->src_data[i] + slice_copied,
+                               buf + src_copied, copy);
+                       src_copied += copy;
+                       slice_copied += copy;
+                       if (slice_copied == g->slice_bytes) {
+                               i++;
+                               slice_copied = 0;
+                       }
                }
-               fc->src_data[i] = (const unsigned char *)p;
-               p += g->slice_bytes;
-       }
-       if (i < k) {
-               /* use arbitrary data for all remaining slices */
-               buf = vsst->map;
-               for (; i < k; i++)
-                       fc->src_data[i] = (const unsigned char *)buf;
        }
+       if (i < fc_num_data_slices(fc) && slice_copied < g->slice_bytes)
+               memset(fc->src_data[i] + slice_copied, 0,
+                        g->slice_bytes - slice_copied);
+       /* zero out remaining slices, if any */
+       while (++i < fc_num_data_slices(fc))
+               memset(fc->src_data[i], 0, g->slice_bytes);
        PARA_DEBUG_LOG("FEC group %u: %u chunks (%u - %u), %u bytes\n",
                g->num, g->num_chunks, g->first_chunk,
                g->first_chunk + g->num_chunks - 1, g->bytes
        );
        PARA_DEBUG_LOG("slice_bytes: %d, %d header slices, %d data slices\n",
-               g->slice_bytes, g->num_header_slices, data_slices
+               g->slice_bytes, g->num_header_slices, fc_num_data_slices(fc)
        );
        return 1;
 }
@@ -637,8 +638,9 @@ static int compute_next_fec_slice(struct fec_client *fc, struct vss_task *vsst)
                }
        }
        write_fec_header(fc, vsst);
-       fec_encode(fc->parms, fc->src_data, fc->enc_buf + FEC_HEADER_SIZE,
-               fc->current_slice_num, fc->group.slice_bytes);
+       fec_encode(fc->parms, (const unsigned char * const*)fc->src_data,
+               fc->enc_buf + FEC_HEADER_SIZE, fc->current_slice_num,
+               fc->group.slice_bytes);
        return 1;
 }
 
@@ -669,7 +671,7 @@ size_t vss_get_fec_eof_packet(const char **buf)
 struct fec_client *vss_add_fec_client(struct sender_client *sc,
                                      struct fec_client_parms *fcp)
 {
-       struct fec_client *fc = para_calloc(sizeof(*fc));
+       struct fec_client *fc = zalloc(sizeof(*fc));
 
        fc->sc  = sc;
        fc->fcp = fcp;
@@ -684,11 +686,15 @@ struct fec_client *vss_add_fec_client(struct sender_client *sc,
  */
 void vss_del_fec_client(struct fec_client *fc)
 {
+       int i;
+
        list_del(&fc->node);
-       free(fc->src_data);
        free(fc->enc_buf);
-       free(fc->extra_src_buf);
-       free(fc->extra_header_buf);
+       if (fc->src_data) {
+               for (i = 0; i < fc_num_data_slices(fc); i++)
+                       free(fc->src_data[i]);
+               free(fc->src_data);
+       }
        fec_free(fc->parms);
        free(fc);
 }
@@ -821,7 +827,7 @@ static void vss_compute_timeout(struct sched *s, struct vss_task *vsst)
        if (sched_request_barrier(&vsst->data_send_barrier, s) == 1)
                return;
        /*
-        * Compute the select timeout as the minimal time until the next
+        * Compute the I/O timeout as the minimal time until the next
         * chunk/slice is due for any client.
         */
        compute_chunk_time(mmd->chunks_sent, &mmd->afd.afhi.chunk_tv,
@@ -839,7 +845,6 @@ static void vss_compute_timeout(struct sched *s, struct vss_task *vsst)
 
 static void vss_eof(struct vss_task *vsst)
 {
-
        if (!vsst->map)
                return;
        if (mmd->new_vss_status_flags & VSS_NOMORE)
@@ -850,8 +855,8 @@ static void vss_eof(struct vss_task *vsst)
        para_munmap(vsst->map, vsst->mapsize);
        vsst->map = NULL;
        mmd->chunks_sent = 0;
-       //mmd->offset = 0;
        mmd->afd.afhi.seconds_total = 0;
+       mmd->afd.afhi.chunks_total = 0;
        mmd->afd.afhi.chunk_tv.tv_sec = 0;
        mmd->afd.afhi.chunk_tv.tv_usec = 0;
        free(mmd->afd.afhi.chunk_table);
@@ -887,21 +892,21 @@ static void set_mmd_offset(void)
        mmd->offset = tv2ms(&offset);
 }
 
-static void vss_pre_select(struct sched *s, void *context)
+static void vss_pre_monitor(struct sched *s, void *context)
 {
        int i;
        struct vss_task *vsst = context;
 
        if (need_to_request_new_audio_file(vsst)) {
                PARA_DEBUG_LOG("ready and playing, but no audio file\n");
-               para_fd_set(vsst->afs_socket, &s->wfds, &s->max_fileno);
+               sched_monitor_writefd(vsst->afs_socket, s);
                vsst->afsss = AFS_SOCKET_CHECK_FOR_WRITE;
        } else
-               para_fd_set(vsst->afs_socket, &s->rfds, &s->max_fileno);
+               sched_monitor_readfd(vsst->afs_socket, s);
        FOR_EACH_SENDER(i) {
-               if (!senders[i]->pre_select)
+               if (!senders[i]->pre_monitor)
                        continue;
-               senders[i]->pre_select(&s->max_fileno, &s->rfds, &s->wfds);
+               senders[i]->pre_monitor(s);
        }
        vss_compute_timeout(s, vsst);
 }
@@ -945,13 +950,13 @@ static int recv_afs_msg(int afs_socket, int *fd, uint32_t *code, uint32_t *data)
 #define MAP_POPULATE 0
 #endif
 
-static void recv_afs_result(struct vss_task *vsst, fd_set *rfds)
+static void recv_afs_result(struct vss_task *vsst, const struct sched *s)
 {
        int ret, passed_fd, shmid;
        uint32_t afs_code = 0, afs_data = 0;
        struct stat statbuf;
 
-       if (!FD_ISSET(vsst->afs_socket, rfds))
+       if (!sched_read_ok(vsst->afs_socket, s))
                return;
        ret = recv_afs_msg(vsst->afs_socket, &passed_fd, &afs_code, &afs_data);
        if (ret == -ERRNO_TO_PARA_ERROR(EAGAIN))
@@ -959,11 +964,17 @@ static void recv_afs_result(struct vss_task *vsst, fd_set *rfds)
        if (ret < 0)
                goto err;
        vsst->afsss = AFS_SOCKET_READY;
-       PARA_DEBUG_LOG("fd: %d, code: %u, shmid: %u\n", passed_fd, afs_code,
-               afs_data);
+       if (afs_code == NO_ADMISSIBLE_FILES) {
+               PARA_NOTICE_LOG("no admissible files\n");
+               ret = 0;
+               goto err;
+       }
        ret = -E_NOFD;
-       if (afs_code != NEXT_AUDIO_FILE)
+       if (afs_code != NEXT_AUDIO_FILE) {
+               PARA_ERROR_LOG("afs code: %u, expected: %d\n", afs_code,
+                       NEXT_AUDIO_FILE);
                goto err;
+       }
        if (passed_fd < 0)
                goto err;
        shmid = afs_data;
@@ -994,20 +1005,17 @@ static void recv_afs_result(struct vss_task *vsst, fd_set *rfds)
        return;
 err:
        free(mmd->afd.afhi.chunk_table);
+       mmd->afd.afhi.chunk_table = NULL;
        if (passed_fd >= 0)
                close(passed_fd);
-       PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+       if (ret < 0)
+               PARA_ERROR_LOG("%s\n", para_strerror(-ret));
        mmd->new_vss_status_flags = VSS_NEXT;
 }
 
 /**
- * Main sending function.
- *
- * This function gets called from vss_post_select(). It checks whether the next
- * chunk of data should be pushed out. It obtains a pointer to the data to be
- * sent out as well as its length from mmd->afd.afhi. This information is then
- * passed to each supported sender's send() function as well as to the send()
- * functions of each registered fec client.
+ * If the next chunk needs to be sent, pass a pointer to the chunk data to all
+ * registered fec clients and to each sender's ->send() method.
  */
 static void vss_send(struct vss_task *vsst)
 {
@@ -1016,7 +1024,7 @@ static void vss_send(struct vss_task *vsst)
        struct timeval due;
        struct fec_client *fc, *tmp_fc;
        char *buf;
-       size_t len;
+       uint32_t len;
 
        if (!vsst->map || !vss_playing())
                return;
@@ -1074,7 +1082,7 @@ static void vss_send(struct vss_task *vsst)
        mmd->current_chunk++;
 }
 
-static int vss_post_select(struct sched *s, void *context)
+static int vss_post_monitor(struct sched *s, void *context)
 {
        int ret, i;
        struct vss_task *vsst = context;
@@ -1124,9 +1132,9 @@ static int vss_post_select(struct sched *s, void *context)
                mmd->sender_cmd_data.cmd_num = -1;
        }
        if (vsst->afsss != AFS_SOCKET_CHECK_FOR_WRITE)
-               recv_afs_result(vsst, &s->rfds);
-       else if (FD_ISSET(vsst->afs_socket, &s->wfds)) {
-               PARA_NOTICE_LOG("requesting new fd from afs\n");
+               recv_afs_result(vsst, s);
+       else if (sched_write_ok(vsst->afs_socket, s)) {
+               PARA_INFO_LOG("requesting new fd from afs\n");
                ret = write_buffer(vsst->afs_socket, "new");
                if (ret < 0)
                        PARA_CRIT_LOG("%s\n", para_strerror(-ret));
@@ -1134,9 +1142,9 @@ static int vss_post_select(struct sched *s, void *context)
                        vsst->afsss = AFS_SOCKET_AFD_PENDING;
        }
        FOR_EACH_SENDER(i) {
-               if (!senders[i]->post_select)
+               if (!senders[i]->post_monitor)
                        continue;
-               senders[i]->post_select(&s->rfds, &s->wfds);
+               senders[i]->post_monitor(s);
        }
        if ((vss_playing() && !(mmd->vss_status_flags & VSS_PLAYING)) ||
                        (vss_next() && vss_playing()))
@@ -1164,7 +1172,7 @@ void vss_init(int afs_socket, struct sched *s)
        vsst->afs_socket = afs_socket;
        ms2tv(announce_time, &vsst->announce_tv);
        PARA_INFO_LOG("announce timeval: %lums\n", tv2ms(&vsst->announce_tv));
-       INIT_LIST_HEAD(&fec_client_list);
+       init_list_head(&fec_client_list);
        FOR_EACH_SENDER(i) {
                PARA_NOTICE_LOG("initializing %s sender\n", senders[i]->name);
                senders[i]->init();
@@ -1181,8 +1189,8 @@ void vss_init(int afs_socket, struct sched *s)
        }
        vsst->task = task_register(&(struct task_info) {
                .name = "vss",
-               .pre_select = vss_pre_select,
-               .post_select = vss_post_select,
+               .pre_monitor = vss_pre_monitor,
+               .post_monitor = vss_post_monitor,
                .context = vsst,
        }, s);
 }
@@ -1190,16 +1198,19 @@ void vss_init(int afs_socket, struct sched *s)
 /**
  * Turn off the virtual streaming system.
  *
- * This is only executed on exit. It calls the ->shutdowwn method of all senders.
+ * This is only executed on exit. It calls the ->shutdown method of all senders.
  */
 void vss_shutdown(void)
 {
        int i;
+       bool is_command_handler = process_is_command_handler();
 
        FOR_EACH_SENDER(i) {
                if (!senders[i]->shutdown)
                        continue;
-               PARA_NOTICE_LOG("shutting down %s sender\n", senders[i]->name);
+               if (!is_command_handler)
+                       PARA_NOTICE_LOG("shutting down %s sender\n",
+                               senders[i]->name);
                senders[i]->shutdown();
        }
 }
index e749160d3337e87dc7c908c023ea100c955590e4..de4a3e6aafc76b83de2d9e2f4112c39c6e038d06 100644 (file)
@@ -53,12 +53,12 @@ static void wav_open(struct filter_node *fn)
 {
        int *bof;
 
-       fn->private_data = para_malloc(sizeof(int));
+       fn->private_data = alloc(sizeof(int));
        bof = fn->private_data;
        *bof = 1;
 }
 
-static void wav_pre_select(struct sched *s, void *context)
+static void wav_pre_monitor(struct sched *s, void *context)
 {
        struct filter_node *fn = context;
        size_t iqs = btr_get_input_queue_size(fn->btrn);
@@ -68,7 +68,7 @@ static void wav_pre_select(struct sched *s, void *context)
        sched_min_delay(s);
 }
 
-static int wav_post_select(__a_unused struct sched *s, void *context)
+static int wav_post_monitor(__a_unused struct sched *s, void *context)
 {
        struct filter_node *fn = context;
        struct btr_node *btrn = fn->btrn;
@@ -78,7 +78,7 @@ static int wav_post_select(__a_unused struct sched *s, void *context)
        int32_t rate, ch;
 
        if (iqs == 0) {
-               ret = -E_WAV_EOF;
+               ret = -E_EOF;
                if (btr_no_parent(btrn))
                        goto err;
                return 0;
@@ -101,7 +101,7 @@ static int wav_post_select(__a_unused struct sched *s, void *context)
        free(buf);
        if (ret < 0)
                goto err;
-       header = para_malloc(WAV_HEADER_LEN);
+       header = alloc(WAV_HEADER_LEN);
        make_wav_header(ch, rate, header);
        btr_add_output(header, WAV_HEADER_LEN, btrn);
        ret = -E_WAV_SUCCESS;
@@ -118,6 +118,6 @@ err:
 const struct filter lsg_filter_cmd_com_wav_user_data = {
        .close = wav_close,
        .open = wav_open,
-       .pre_select = wav_pre_select,
-       .post_select = wav_post_select,
+       .pre_monitor = wav_pre_monitor,
+       .post_monitor = wav_post_monitor,
 };
index 8395864a00a6e376634272954708cd96739afc2f..f188ff2566ad30645365e73aa62e399439dfbf5e 100644 (file)
@@ -1,8 +1,8 @@
 <h1>About</h1>
 <hr>
 
-Paraslash is a collection of network audio streaming tools for Unix
-systems. It is written in C and released under the GPLv2.
+<p> Paraslash is a collection of network audio streaming tools for
+Unix systems. It is written in C and released under the GPLv2. </p>
 
 <ul>
        <li> Runs on Linux, FreeBSD, NetBSD </li>
@@ -10,15 +10,17 @@ systems. It is written in C and released under the GPLv2.
        <li> http, dccp and udp network streaming </li>
        <li> Stand-alone decoder, player, tagger </li>
        <li> Curses-based gui (<a href="gui.png">screenshot</a>) </li>
-       <li> Integrated volume normalizer, fader, alarm clock </li>
+       <li> Volume normalizer, fader, alarm clock </li>
        <li> Sophisticated audio file selector </li>
        <li> Command line interface with tab-completion </li>
        <li> Open source and well documented </li>
 </ul>
 
-<b> Author: </b> Andr&eacute; Noll,
-<a href="mailto:maan@tuebingen.mpg.de">maan@tuebingen.mpg.de</a>,
-Homepage: <a href="http://people.tuebingen.mpg.de/maan/">http://people.tuebingen.mpg.de/maan/</a>
-<br>
-Comments and bug reports are welcome. Please provide the version of
-paraslash you are using and relevant parts of the logs.
+<p> Author: Andre Noll, <a
+href="mailto:maan@tuebingen.mpg.de">maan@tuebingen.mpg.de</a>,
+Homepage: <a
+href="http://people.tuebingen.mpg.de/maan/">http://people.tuebingen.mpg.de/maan/</a>
+</p>
+
+<p> Comments and bug reports are welcome. Please provide the version
+of paraslash you are using and relevant parts of the logs. </p>
index d6d690a16bd5299a6fdb8d304cfe48fc13dbc6c3..32efe9c3b191ff2bfa6df658480e63a884e3952f 100644 (file)
        [<a href="para_filter.man.html">para_filter</a>]
        [<a href="para_write.man.html">para_write</a>]
        [<a href="para_gui.man.html">para_gui</a>]
-       [<a href="para_mixer.man.html">para_mixere</a>]
+       [<a href="para_mixer.man.html">para_mixer</a>]
        [<a href="para_play.man.html">para_play</a>]
+       [<a href="para_upgrade_db.man.html">para_upgrade_db</a>]
 </p>
 
 <h2> Source code documentation </h2>
 
 <ul>
-       <li> <a href="doxygen/html/index.html">API Reference</a> </li>
+       <li> <a href="doxygen/html/files.html">API Reference</a> </li>
 </ul>
index 9ef92b7af654ec06f193ad5dd3941648b82a83b8..2b31e5d76c7f9f7c348ba08f852dc6c5a5780f73 100644 (file)
@@ -1,8 +1,9 @@
 <h1>Download</h1>
 <hr>
 
-Paraslash is only available as source code, no binary packages are
-provided at this point. There are several ways to download the source:
+<p> Paraslash is only available as source code, no binary packages
+are provided at this point. There are several ways to download the
+source: </p>
 
 <ul>
        <li> <em> git</em>.
@@ -19,7 +20,7 @@ provided at this point. There are several ways to download the source:
                checkout of any of the four integration branches maint,
                master, next, pu (see the
 
-               <a href="manual.html#Git.branches">Git branches</a>
+               <a href="manual.html#Git-branches">Git branches</a>
 
                section of the manual). All previous releases
                correspond to tagged commits and may be checked out
index 38572c940dac47cafcd170a64c04a21953dc74bf..cceeff1a3097597c261254b020c1ffbe0dd3ef19 100644 (file)
@@ -1,5 +1,3 @@
 <hr>
-       </td>
-       </table>
 </body>
 </html>
index a5d01204939d789cf4841499ce31119bfd5e1c35..1e102a6b3e7732c43d3c8eaa56797540022cb134 100644 (file)
 <body>
        <table>
        <tr>
-               <td>
-                       <a title="paraslash homepage" href=".">
-                               <img src="paraslash.png" alt="paraslash">
+               <td rowspan="2">
+                       <a title="paraslash homepage" href="./index.html">
+                               <img src="paraslash.svg" alt="paraslash">
                        </a>
                </td>
                <td>
-                       <h3>Paraslash network audio streaming tools</h3>
+                       <span class="slogan">Paraslash Audio Streaming</span>
                </td>
        </tr>
        <tr>
-               <td valign="top">
-                       <br>
-                       <a href=".">About</a><br>
-                       <a href="news.html">News</a><br>
-                       <a href="download.html">Download</a><br>
-                       <a href="documentation.html">Documentation</a><br>
-                       <a href="devel.html">Development</a><br>
-               </td>
                <td>
+                       <a href="./index.html">About</a>&nbsp;
+                       &nbsp;<a href="news.html">News</a>&nbsp;
+                       &nbsp;<a href="download.html">Download</a>&nbsp;
+                       &nbsp;<a href="documentation.html">Documentation</a>&nbsp;
+                       &nbsp;<a href="devel.html">Development</a>
+               </td>
+       </tr>
+       </table>
+<hr>
index 98235b8621324bc9176d2f9803ea4cf097afd83a..29ec02d40eb266c3f1eaa2617b6c609cd1d90b40 100644 (file)
 <body>
        <table>
        <tr>
-               <td>
-                       <a title="paraslash homepage" href="../..//">
-                               <img src="../../paraslash.png" alt="paraslash">
+               <td rowspan="2">
+                       <a title="paraslash homepage" href="../../index.html">
+                               <img src="../../paraslash.svg" alt="paraslash">
                        </a>
                </td>
                <td>
-                       <h3>Paraslash network audio streaming tools</h3>
+                       <span class="slogan">Paraslash Audio Streaming</span>
                </td>
        </tr>
        <tr>
-               <td valign="top">
-                       <br>
-                       <a href="../..">About</a><br>
-                       <a href="../../news.html">News</a><br>
-                       <a href="../../download.html">Download</a><br>
-                       <a href="../../documentation.html">Documentation</a><br>
-                       <a href="../../devel.html">Development</a><br>
-               </td>
                <td>
-                       <h1>API Reference</h1>
-                       <hr />
-
+                       <a href="../../index.html">About</a>&nbsp;
+                       &nbsp;<a href="../../news.html">News</a>&nbsp;
+                       &nbsp;<a href="../../download.html">Download</a>&nbsp;
+                       &nbsp;<a href="../../documentation.html">Documentation</a>&nbsp;
+                       &nbsp;<a href="../../devel.html">Development</a>
+               </td>
+       </tr>
+       </table>
+<hr>
index 06ff03362e8a33dfe2b02bd2c214892dd3ba59c4..65a0dd69fd3e52d529e63b8ea039a3b32a7cbce4 100644 (file)
Binary files a/web/images/paraslash.ico and b/web/images/paraslash.ico differ
diff --git a/web/images/paraslash.png b/web/images/paraslash.png
deleted file mode 100644 (file)
index 804752c..0000000
Binary files a/web/images/paraslash.png and /dev/null differ
diff --git a/web/images/paraslash.svg b/web/images/paraslash.svg
new file mode 100644 (file)
index 0000000..b0616a7
--- /dev/null
@@ -0,0 +1,44 @@
+<?xml version="1.0" standalone="no"?>
+<svg
+       xmlns="http://www.w3.org/2000/svg"
+       width="80" height="100"
+       xmlns:xlink="http://www.w3.org/1999/xlink"
+>
+       <defs>
+               <radialGradient id="gradient" r="70%">
+                       <stop offset="0%" stop-color="#d40"/>
+                       <stop offset="100%" stop-color="#010"/>
+               </radialGradient>
+
+               <g
+                       id="bow"
+                       stroke="#111" stroke-width="0"
+                       fill="url(#gradient)"
+               >
+                       <path d="M 40,50 c 0,-21 30,-21 30,0"/>
+               </g>
+       </defs>
+       <g transform="scale(1.0, 1.3) translate(0, -11)">
+               <circle
+                       cx="40" cy="50" r="38"
+                       stroke-width="0"
+                       fill="url(#gradient)"
+               />
+               <use
+                       xlink:href="#bow"
+                       transform="
+                               scale(1.5)
+                               rotate(320, 40,50)
+                               translate(-14, -24)
+                       "
+               />
+               <use
+                       xlink:href="#bow"
+                       transform="
+                               translate(0, -36)
+                               scale(1.5)
+                               rotate(140, 40,50)
+                       "
+               />
+       </g>
+</svg>
index 44799e910fc8c041667599fcff8669a1a89608c1..b5329ea07f3d8a2f9553f384c9129c4ad1a262e3 100644 (file)
@@ -293,7 +293,7 @@ Requirements
        cd osl && make && sudo make install && sudo ldconfig
        sudo apt-get install autoconf libssl-dev m4 \
               libmad0-dev libid3tag0-dev libasound2-dev libvorbis-dev \
-              libfaad-dev libspeex-dev libFLAC-dev libsamplerate-dev realpath \
+              libfaad-dev libspeex-dev libflac-dev libsamplerate-dev \
               libasound2-dev libao-dev libreadline-dev libncurses-dev \
               libopus-dev
 
@@ -309,8 +309,8 @@ repository with
                git clone git://git.tuebingen.mpg.de/lopsub
 
 - [gcc](ftp://ftp.gnu.org/pub/gnu/gcc) or
-[clang](http://clang.llvm.org). All gcc versions >= 4.2 are currently
-supported. Clang version 1.1 or newer should work as well.
+[clang](http://clang.llvm.org). All gcc versions >= 5.4 are currently
+supported. Moderately recent versions of clang should work as well.
 
 - [gnu make](ftp://ftp.gnu.org/pub/gnu/make) is also shipped with the
 disto. On BSD systems the gnu make executable is often called gmake.
@@ -358,7 +358,7 @@ recognized. The mp3 tagger also needs this library for modifying
 you need libogg, libvorbis, libvorbisfile. The corresponding Debian
 packages are called `libogg-dev` and `libvorbis-dev`.
 
-- [libfaad and mp4ff](http://www.audiocoding.com/). For aac files
+- [libfaad and mp4ff](https://sourceforge.net/projects/faac/). For aac files
 (m4a) you need libfaad and libmp4ff (package: `libfaad-dev`). Note
 that for some distributions, e.g. Ubuntu, mp4ff is not part of the
 libfaad package. Install the faad library from sources (available
@@ -488,10 +488,10 @@ An empty database is created with
        para_client init
 
 This initializes a couple of empty tables under
-~/.paraslash/afs_database-0.4. You normally don't need to look at these
+~/.paraslash/afs_database-0.7. You normally don't need to look at these
 tables, but it's good to know that you can start from scratch with
 
-       rm -rf ~/.paraslash/afs_database-0.4
+       rm -rf ~/.paraslash/afs_database-0.7
 
 in case something went wrong.
 
@@ -613,10 +613,11 @@ while the second part is the session key.
 
 - para_client receives the encrypted buffer and decrypts it with the
 user's private key, thereby obtaining the challenge buffer and the
-session key. It sends the SHA1 hash value of the challenge back to
-para_server and stores the session key for further use.
+session key. It hashes the challenge buffer with a crytographic hash
+function, sends the hash value back to para_server and stores the
+session key for further use.
 
-- para_server also computes the SHA1 hash of the challenge and compares
+- para_server also computes the hash value of the challenge and compares
 it against what was sent back by the client.
 
 - If the two hashes do not match, the authentication has failed and
@@ -630,7 +631,7 @@ the session key known to both peers.
 paraslash relies on the quality of the pseudo-random bytes provided
 by the crypto library (openssl or libgcrypt), on the security of
 the implementation of the RSA and AES crypto routines and on the
-infeasibility to invert the SHA1 function.
+infeasibility to invert the hash function.
 
 Neither para_server or para_client create RSA keys on their
 own. This has to be done once for each user as sketched in
@@ -804,10 +805,11 @@ This is the most important and usually also the largest table of the
 AFS database. It contains the information needed to stream each audio
 file. In particular the following data is stored for each audio file.
 
-- SHA1 hash value of the audio file contents. This is computed once
-when the file is added to the database. Whenever AFS selects this
-audio file for streaming the hash value is recomputed and checked
-against the value stored in the database to detect content changes.
+- The cryptographic hash value of the audio file contents. This is
+computed once when the file is added to the database. Whenever AFS
+selects this audio file for streaming the hash value is recomputed
+and checked against the value stored in the database to detect
+content changes.
 
 - The time when this audio file was last played.
 
@@ -1023,6 +1025,7 @@ Keyword              |    Type | Semantic value
 `bitrate`            | integer | The average bitrate
 `frequency`          | integer | The output sample rate
 `channels`           | integer | The number of channels
+`duration`           | integer | The number of milliseconds
 `is_set("foo")`      | boolean | True if attribute "foo" is set.
 
 [\*] For most audio formats, the year tag is stored as a string. It
@@ -1146,7 +1149,7 @@ if the "-a" switch is given:
 File renames and content changes
 --------------------------------
 
-Since the audio file selector knows the SHA1 of each audio file that
+Since the audio file selector knows the hash of each audio file that
 has been added to the afs database, it recognizes if the content of
 a file has changed, e.g. because an ID3 tag was added or modified.
 Also, if a file has been renamed or moved to a different location,
@@ -1175,14 +1178,14 @@ may refuse to start again because of "dirty osl tables". In this
 case you'll have to run the oslfsck program of libosl to fix your
 database:
 
-       oslfsck -fd ~/.paraslash/afs_database-0.4
+       oslfsck -fd ~/.paraslash/afs_database-0.7
 
 However, make sure para_server isn't running before executing oslfsck.
 
 If you don't mind to recreate your database you can start
 from scratch by removing the entire database directory, i.e.
 
-       rm -rf ~/.paraslash/afs_database-0.4
+       rm -rf ~/.paraslash/afs_database-0.7
 
 Be aware that this removes all attribute definitions, all playlists
 and all mood definitions and requires to re-initialize the tables.
@@ -2074,11 +2077,11 @@ here are the most important points.
 - Don't leave whitespace at the end of lines.
 - The limit on the length of lines is 80 columns.
 - Use K&R style for placing braces and spaces:
-
+<pre>
                if (x is true) {
                        we do y
                }
-
+</pre>
 - Use a space after (most) keywords.
 - Do not add spaces around (inside) parenthesized expressions.
 - Use one space around (on each side of) most binary and ternary operators.
index e8c43489f4681bcbd8bb859db80582b5fa83c068..2043fbb70b2afdf9ef243200920910bf564f2a1e 100644 (file)
@@ -1,46 +1,29 @@
-body,h1,h2,h3,h4,h5,h6,p,center,td,th,ul,dl,div {
-       font-family: sans-serif;
-}
-
 body {
+       font-family: sans-serif;
        background-color: black;
        color: #bbbbbb;
-       margin: 0px;
-}
-
-table {
-       padding: 8px 4px;
-}
-
-th {
-       padding: 2px 5px;
-       font-size: 100%;
-       text-align: left;
+       margin: 20px;
 }
 
 td {
-       padding: 2px 5px;
-       font-size: 100%;
+       padding: 2px 10px 2px 10px;
        vertical-align: top;
 }
 
+span.slogan {
+       font-size: 200%;
+       font-weight: bold;
+       color: #ddd;
+}
+
 a {
        color: #cc3322;
 }
 
 hr {
-       height: 1px;
-       border: none;
        border-top: 1px solid yellow;
 }
 
-img {
-       float: right;
-       border-width: 0px;
-}
-
-caption { font-weight: bold }
-
 /* doxgen */
 
 /* Data structure index. Box with clickable letters */
index 63e49677b0f1b85c13c17160a7b249531c1fbcd4..8bff7cfcaa98f8ae3bdfb00f87545c70c7927d94 100644 (file)
--- a/wma_afh.c
+++ b/wma_afh.c
@@ -195,7 +195,7 @@ static int wma_make_chunk_table(char *buf, size_t buf_size, uint32_t packet_size
        size_t ct_size = 250;
        int ret, count = 0, num_frames, num_superframes;
 
-       afhi->chunk_table = para_malloc(ct_size * sizeof(uint32_t));
+       afhi->chunk_table = arr_alloc(ct_size, sizeof(uint32_t));
        afhi->chunk_table[0] = 0;
        afhi->chunk_table[1] = afhi->header_len;
 
@@ -318,7 +318,7 @@ static int convert_utf8_to_utf16(char *src, char **dst)
        int ret;
 
        if (!src || !*src) {
-               *dst = para_calloc(2);
+               *dst = zalloc(2);
                return 0;
        }
        /*
@@ -334,7 +334,7 @@ static int convert_utf8_to_utf16(char *src, char **dst)
        /* even though src is in UTF-8, strlen() should DTRT */
        inbytes = inbytesleft = strlen(src);
        outbytes = outbytesleft = 4 * inbytes + 2; /* hope that's enough */
-       *dst = outbuf = para_malloc(outbytes);
+       *dst = outbuf = alloc(outbytes);
        sz = iconv(cd, ICONV_CAST &inbuf, &inbytesleft, &outbuf, &outbytesleft);
        if (sz == (size_t)-1) {
                ret = -ERRNO_TO_PARA_ERROR(errno);
@@ -411,7 +411,7 @@ static int make_cdo(struct taginfo *tags, const struct asf_object *cdo,
        result->size = 16 + 8 + 5 * 2 + title_bytes + artist_bytes
                + orig_cr_bytes + comment_bytes + orig_rating_bytes;
        PARA_DEBUG_LOG("cdo is %zu bytes\n", (size_t)result->size);
-       p = result->ptr = para_malloc(result->size);
+       p = result->ptr = alloc(result->size);
        memcpy(p, content_description_header, 16);
        p += 16;
        write_u64(p, result->size);
@@ -469,7 +469,7 @@ static int make_ecdo(struct taginfo *tags, struct asf_object *result)
        result->size += 2 + sizeof(album_tag_header) + 2 + 2 + 2 + album_bytes;
        result->size += 2 + sizeof(year_tag_header) + 2 + 2 + 2 + year_bytes;
 
-       p = result->ptr = para_malloc(result->size);
+       p = result->ptr = alloc(result->size);
        memcpy(p, extended_content_header, 16);
        p += 16;
        write_u64(p, result->size);
@@ -622,7 +622,7 @@ static int wma_rewrite_tags(const char *map, size_t mapsize,
        if (top.reserved2 != 2)
                return -E_NO_WMA;
        p++; /* objects start at p */
-       top.objects = para_malloc(top.num_objects * sizeof(struct asf_object));
+       top.objects = arr_alloc(top.num_objects, sizeof(struct asf_object));
        ret = read_asf_objects(p, top.size - (p - map), top.num_objects,
                top.objects, &ton);
        if (ret < 0)
index edf50cb0b3834d8971fe9602f6528e2f2bd86e5d..f2ca273cd08979f33b42ab0a7f84b533fe29a583 100644 (file)
@@ -16,7 +16,6 @@
 
 #include <math.h>
 #include <regex.h>
-#include <sys/select.h>
 
 #include "para.h"
 #include "error.h"
@@ -160,8 +159,8 @@ static void init_coef_vlc(struct private_wmadec_data *pwd, int sidx, int didx)
        int i, l, j, k, level, n = src->n;
 
        init_vlc(dst, VLCBITS, n, src->huffbits, src->huffcodes, 4);
-       pwd->run_table[didx] = para_malloc(n * sizeof(uint16_t));
-       pwd->level_table[didx] = para_malloc(n * sizeof(uint16_t));
+       pwd->run_table[didx] = arr_alloc(n, sizeof(uint16_t));
+       pwd->level_table[didx] = arr_alloc(n, sizeof(uint16_t));
        i = 2;
        level = 1;
        k = 0;
@@ -428,7 +427,7 @@ static int wma_decode_init(char *initial_buf, int len, struct private_wmadec_dat
        int ret, i;
 
        PARA_NOTICE_LOG("initial buf: %d bytes\n", len);
-       pwd = para_calloc(sizeof(*pwd));
+       pwd = zalloc(sizeof(*pwd));
        ret = read_asf_header(initial_buf, len, &pwd->ahi);
        if (ret <= 0) {
                free(pwd);
@@ -1159,7 +1158,7 @@ static int wmadec_execute(struct btr_node *btrn, const char *cmd, char **result)
 
 #define WMA_OUTPUT_BUFFER_SIZE (128 * 1024)
 
-static int wmadec_post_select(__a_unused struct sched *s, void *context)
+static int wmadec_post_monitor(__a_unused struct sched *s, void *context)
 {
        struct filter_node *fn = context;
        int ret, converted, out_size;
@@ -1177,7 +1176,7 @@ next_buffer:
                return 0;
        btr_merge(btrn, fn->min_iqs);
        len = btr_next_buffer(btrn, &in);
-       ret = -E_WMADEC_EOF;
+       ret = -E_EOF;
        if (len < fn->min_iqs)
                goto err;
        if (!pwd) {
@@ -1197,7 +1196,7 @@ next_buffer:
        if (fn->min_iqs > len)
                goto success;
        out_size = WMA_OUTPUT_BUFFER_SIZE;
-       out = para_malloc(out_size);
+       out = alloc(out_size);
        ret = wma_decode_superframe(pwd, out, &out_size,
                (uint8_t *)in + WMA_FRAME_SKIP);
        if (ret < 0) {
@@ -1229,6 +1228,6 @@ const struct filter lsg_filter_cmd_com_wmadec_user_data = {
        .open = wmadec_open,
        .close = wmadec_close,
        .execute = wmadec_execute,
-       .pre_select = generic_filter_pre_select,
-       .post_select = wmadec_post_select,
+       .pre_monitor = generic_filter_pre_monitor,
+       .post_monitor = wmadec_post_monitor,
 };
diff --git a/write.c b/write.c
index acfb94605b53d259a89ea41e75507b81e2cb2ca4..cb32d391eaa0f856414aa23318a7573def0d9eef 100644 (file)
--- a/write.c
+++ b/write.c
@@ -54,16 +54,16 @@ struct write_task {
        struct check_wav_context *cwc;
 };
 
-static void write_pre_select(struct sched *s, void *context)
+static void write_pre_monitor(struct sched *s, void *context)
 {
        struct write_task *wt = context;
-       check_wav_pre_select(s, wt->cwc);
+       check_wav_pre_monitor(s, wt->cwc);
 }
 
-static int write_post_select(__a_unused struct sched *s, void *context)
+static int write_post_monitor(__a_unused struct sched *s, void *context)
 {
        struct write_task *wt = context;
-       return check_wav_post_select(wt->cwc);
+       return check_wav_post_monitor(wt->cwc);
 }
 
 static int setup_and_schedule(struct lls_parse_result *lpr)
@@ -83,21 +83,20 @@ static int setup_and_schedule(struct lls_parse_result *lpr)
        wt.cwc = check_wav_init(sit.btrn, NULL, &wp, &cw_btrn);
        wt.task = task_register(&(struct task_info) {
                .name = "write",
-               .pre_select = write_pre_select,
-               .post_select = write_post_select,
+               .pre_monitor = write_pre_monitor,
+               .post_monitor = write_post_monitor,
                .context = &wt,
        }, &s);
 
        n = writer_given? writer_given : 1;
-       wns = para_calloc(n * sizeof(*wns));
+       wns = arr_zalloc(n, sizeof(*wns));
        for (i = 0; i < n; i++) {
                const char *arg = i < writer_given?
                        lls_string_val(i, OPT_RESULT(WRITER, lpr)) : NULL;
                wns[i].wid = check_writer_arg_or_die(arg, &wns[i].lpr);
                register_writer_node(wns + i, cw_btrn, &s);
        }
-       s.default_timeout.tv_sec = 10;
-       s.default_timeout.tv_usec = 50000;
+       s.default_timeout = 10500;
        ret = schedule(&s);
        if (ret >= 0) {
                int j, ts;
@@ -105,7 +104,7 @@ static int setup_and_schedule(struct lls_parse_result *lpr)
                        struct writer_node *wn = wns + j;
                        ts = task_status(wn->task);
                        assert(ts < 0);
-                       if (ts != -E_WRITE_COMMON_EOF && ts != -E_BTR_EOF) {
+                       if (ts != -E_EOF) {
                                const char *name = writer_name(wn->wid);
                                PARA_ERROR_LOG("%s: %s\n", name,
                                        para_strerror(-ts));
diff --git a/write.h b/write.h
index cb0beff812121b338e3749e80f63ad2cb99999db..35a8d29f1a3f7dd493b65c394bc8ce63aa7f9f8f 100644 (file)
--- a/write.h
+++ b/write.h
@@ -20,21 +20,23 @@ struct writer_node {
        size_t min_iqs;
 };
 
-/** Describes one supported writer. */
+/**
+ * Describes a data sink for audio streams.
+ *
+ * A paraslash writer obtains data via the buffer tree mechanism from its
+ * parent node. It consumes data without producing output on its own.
+ *
+ * This structure contains the methods which have to be implemented by each
+ * writer.
+ *
+ * \sa struct \ref writer_node, struct \ref receiver, struct \ref filter,
+ * struct \ref sched.
+ */
 struct writer {
-       /**
-        * Prepare the fd sets for select.
-        *
-        * This is called from scheduler. It may use the sched pointer to add
-        * any file descriptors or to decrease the select timeout.
-        */
-       void (*pre_select)(struct sched *s, void *context);
-       /**
-        * Write audio data.
-        *
-        * Called from the post_select function of the writer node's task.
-        */
-       int (*post_select)(struct sched *s, void *context);
+       /** Ask the scheduler to check whether data can be written. */
+       void (*pre_monitor)(struct sched *s, void *context);
+       /** Write audio data. */
+       int (*post_monitor)(struct sched *s, void *context);
        /**
         * Close one instance of the writer.
         *
@@ -66,7 +68,7 @@ const struct writer *writer_get(int wid);
 const char *writer_name(int wid);
 void register_writer_node(struct writer_node *wn, struct btr_node *parent,
                struct sched *s);
-void get_btr_sample_rate(struct btr_node *btrn, int32_t *result);
-void get_btr_channels(struct btr_node *btrn, int32_t *result);
-void get_btr_sample_format(struct btr_node *btrn, int32_t *result);
+int get_btr_sample_rate(struct btr_node *btrn, int32_t *result);
+int get_btr_channels(struct btr_node *btrn, int32_t *result);
+int get_btr_sample_format(struct btr_node *btrn, int32_t *result);
 void print_writer_helps(bool detailed);
index 14cc98a4189a5f74907f8bfcf416f62bcc2515c5..9a13090541895df5b7bc7ee8f42fbc2fda37a9f2 100644 (file)
@@ -85,7 +85,7 @@ int check_writer_arg_or_die(const char *wa, struct lls_parse_result **lprp)
        if (!wa || !*wa) {
                writer_num = default_writer_id();
                cmd = WRITE_CMD(writer_num);
-               argv = para_malloc(2 * sizeof(char *));
+               argv = alloc(2 * sizeof(char *));
                argc = 1;
                argv[0] = para_strdup(lls_command_name(cmd));
                argv[1] = NULL;
@@ -139,8 +139,8 @@ void register_writer_node(struct writer_node *wn, struct btr_node *parent,
                .handler = w->execute, .context = wn));
        wn->task = task_register(&(struct task_info) {
                .name = writer_name(wn->wid),
-               .pre_select = w->pre_select,
-               .post_select = w->post_select,
+               .pre_monitor = w->pre_monitor,
+               .post_monitor = w->post_monitor,
                .context = wn,
        }, s);
 }
@@ -174,24 +174,24 @@ void print_writer_helps(bool detailed)
        }
 }
 
-static void get_btr_value(struct btr_node *btrn, const char *cmd,
+static int get_btr_value(struct btr_node *btrn, const char *cmd,
                int32_t *result)
 {
        char *buf = NULL;
        int ret = btr_exec_up(btrn, cmd, &buf);
 
-       if (ret < 0) {
-               /*
-                * This really should not happen. It means one of our parent
-                * nodes died unexpectedly. Proceed with fingers crossed.
-                */
-               PARA_CRIT_LOG("cmd %s: %s\n", cmd, para_strerror(-ret));
-               *result = 0;
-               return;
-       }
+       *result = 0;
+       /*
+        * Errors may happen when the decoder returns EOF before the writer had
+        * a chance to query the buffer tree for the channel count, sample rate
+        * etc.
+        */
+       if (ret < 0)
+               return ret;
        ret = para_atoi32(buf, result);
        assert(ret >= 0);
        free(buf);
+       return ret;
 }
 
 /**
@@ -200,11 +200,11 @@ static void get_btr_value(struct btr_node *btrn, const char *cmd,
  * \param btrn Where to start the search.
  * \param result Filled in by this function.
  *
- * This function is assumed to succeed and terminates on errors.
+ * \return Standard.
  */
-void get_btr_sample_rate(struct btr_node *btrn, int32_t *result)
+int get_btr_sample_rate(struct btr_node *btrn, int32_t *result)
 {
-       get_btr_value(btrn, "sample_rate", result);
+       return get_btr_value(btrn, "sample_rate", result);
 }
 
 /**
@@ -212,10 +212,12 @@ void get_btr_sample_rate(struct btr_node *btrn, int32_t *result)
  *
  * \param btrn See \ref get_btr_sample_rate.
  * \param result See \ref get_btr_sample_rate.
+ *
+ * \return Standard.
  */
-void get_btr_channels(struct btr_node *btrn, int32_t *result)
+int get_btr_channels(struct btr_node *btrn, int32_t *result)
 {
-       get_btr_value(btrn, "channels", result);
+       return get_btr_value(btrn, "channels", result);
 }
 
 /**
@@ -223,8 +225,10 @@ void get_btr_channels(struct btr_node *btrn, int32_t *result)
  *
  * \param btrn See \ref get_btr_sample_rate.
  * \param result Contains the sample format as an enum sample_format type.
+ *
+ * \return Standard.
  */
-void get_btr_sample_format(struct btr_node *btrn, int32_t *result)
+int get_btr_sample_format(struct btr_node *btrn, int32_t *result)
 {
-       get_btr_value(btrn, "sample_format", result);
+       return get_btr_value(btrn, "sample_format", result);
 }
index 2dbe21b779a82eebd79403fa5f60bc56925489dd..4a53db2741754bf6359733af8b42ca60ca255e92 100644 (file)
--- a/yy/mp.lex
+++ b/yy/mp.lex
@@ -74,6 +74,7 @@ lyrics_id {return LYRICS_ID;}
 bitrate {return BITRATE;}
 frequency {return FREQUENCY;}
 channels {return CHANNELS;}
+duration {return DURATION;}
 true {return TRUE;}
 false {return FALSE;}
 
diff --git a/yy/mp.y b/yy/mp.y
index 0f2c9cb8b256a82eeb2fac78114f6026902a24f2..8df4f20ea04ebd2ea23b77ad77451e23e0022dbc 100644 (file)
--- a/yy/mp.y
+++ b/yy/mp.y
@@ -59,7 +59,7 @@ enum semantic_types {
 
 static struct mp_ast_node *ast_node_raw(int id)
 {
-       struct mp_ast_node *node = para_malloc(sizeof(struct mp_ast_node));
+       struct mp_ast_node *node = alloc(sizeof(struct mp_ast_node));
        node->id = id;
        return node;
 }
@@ -76,7 +76,7 @@ static struct mp_ast_node *ast_node_new_unary(int id, struct mp_ast_node *child)
 {
        struct mp_ast_node *node = ast_node_raw(id);
        node->num_children = 1;
-       node->children = para_malloc(sizeof(struct mp_ast_node *));
+       node->children = alloc(sizeof(struct mp_ast_node *));
        node->children[0] = child;
        return node;
 }
@@ -86,7 +86,7 @@ static struct mp_ast_node *ast_node_new_binary(int id, struct mp_ast_node *left,
 {
        struct mp_ast_node *node = ast_node_raw(id);
        node->num_children = 2;
-       node->children = para_malloc(2 * sizeof(struct mp_ast_node *));
+       node->children = arr_alloc(2, sizeof(struct mp_ast_node *));
        node->children[0] = left;
        node->children[1] = right;
        return node;
@@ -210,6 +210,9 @@ static int eval_node(struct mp_ast_node *node, struct mp_context *ctx,
        case CHANNELS:
                result->intval= mp_channels(ctx);
                return ST_INTVAL;
+       case DURATION:
+               result->intval= mp_duration(ctx);
+               return ST_INTVAL;
        /* bools */
        case IS_SET:
                arg = node->children[0]->sv.strval;
@@ -327,6 +330,7 @@ bool mp_eval_ast(struct mp_ast_node *root, struct mp_context *ctx)
 %token <node> BITRATE
 %token <node> FREQUENCY
 %token <node> CHANNELS
+%token <node> DURATION
 %token <node> FALSE TRUE
 
 /* keywords without semantic value */
@@ -377,6 +381,7 @@ exp: NUM {$$ = $1;}
        | BITRATE {$$ = mp_new_ast_leaf_node(BITRATE);}
        | FREQUENCY {$$ = mp_new_ast_leaf_node(FREQUENCY);}
        | CHANNELS {$$ = mp_new_ast_leaf_node(CHANNELS);}
+       | DURATION {$$ = mp_new_ast_leaf_node(DURATION);}
 ;
 
 boolexp: IS_SET '(' STRING_LITERAL ')' {$$ = ast_node_new_unary(IS_SET, $3);}