From: Andre Noll
Date: Mon, 13 May 2024 21:32:52 +0000 (+0200)
Subject: play.c: Replace NULL check by assertion.
X-Git-Url: http://git.tuebingen.mpg.de/?a=commitdiff_plain;ds=sidebyside;h=HEAD;hp=5d27648bfa9921f158e700151296af3e4c4fe13c;p=paraslash.git
play.c: Replace NULL check by assertion.
If p is NULL, kma contains no colon, and we should not be here in the
first place. Instead, we should have errored out much earlier in the
command line parser.
Suggested-by: gcc(1)
---
diff --git a/.gitignore b/.gitignore
index bd5e0480..8f8d0af7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,3 +24,4 @@ confdefs.h
conftest
conftest.c
git-version.h
+*-local*
diff --git a/Doxyfile b/Doxyfile
index b11683e4..58e80239 100644
--- 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.
@@ -17,11 +17,11 @@
# 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 4a86e967..cabeb10f 100644
--- a/INSTALL
+++ b/INSTALL
@@ -2,15 +2,15 @@ Any knowledge of how to work with mouse and icons is not required.
Installing lopsub
~~~~~~~~~~~~~~~~~
- git clone git://git.tuebingen.mpg.de/lopsub
+ git clone https://git.tuebingen.mpg.de/lopsub
cd lopsub && make && sudo make install
- (see http://people.tuebingen.mpg.de/maan/lopsub/)
+ (see https://people.tuebingen.mpg.de/maan/lopsub/)
Installing osl
~~~~~~~~~~~~~~
- git clone git://git.tuebingen.mpg.de/osl
+ git clone https://git.tuebingen.mpg.de/osl
cd osl && make && sudo make install
- (see http://people.tuebingen.mpg.de/maan/osl/)
+ (see https://people.tuebingen.mpg.de/maan/osl/)
Installing paraslash from tarball
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -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
~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -37,4 +37,4 @@ Example for cross-compiling
For details see the user manual:
- http://people.tuebingen.mpg.de/maan/paraslash/manual.html
+ https://people.tuebingen.mpg.de/maan/paraslash/manual.html
diff --git a/Makefile.in b/Makefile.in
index d4a83a77..c618561d 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -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
diff --git a/Makefile.real b/Makefile.real
index ddf85b58..bd2bd9d9 100644
--- a/Makefile.real
+++ b/Makefile.real
@@ -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 := 2020
+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 6bd58962..d5812289 100644
--- 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
@@ -31,6 +211,10 @@ NEWS
- 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"
--------------------------------------
@@ -101,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"
---------------------------------------
diff --git a/README b/README
index d8a545fc..510c7e1f 100644
--- a/README
+++ b/README
@@ -5,9 +5,9 @@ audio files. See the user manual for details.
Distribution of paraslash is covered by the GNU GPL, version 2 unless
otherwise stated. See file COPYING.
-Web page: http://people.tuebingen.mpg.de/maan/paraslash/
-Gitweb: http://git.tuebingen.mpg.de/paraslash.git/
-Git URL: git://git.tuebingen.mpg.de/paraslash.git
+Web page: https://people.tuebingen.mpg.de/maan/paraslash/
+Gitweb: https://git.tuebingen.mpg.de/paraslash.git/
+Git URL: https://git.tuebingen.mpg.de/paraslash.git
Email: Andre Noll
Comments and bug reports are welcome.
diff --git a/aac_afh.c b/aac_afh.c
index 7f2a22a2..c4301a2f 100644
--- a/aac_afh.c
+++ b/aac_afh.c
@@ -10,11 +10,7 @@
#include
#include "para.h"
-
-/* To get the mp4ff_tag_t and mp4ff_metadata_t typedefs. */
-#define USE_TAGGING
-#include
-
+#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;
}
diff --git a/aacdec_filter.c b/aacdec_filter.c
index a2459d82..87a7900a 100644
--- a/aacdec_filter.c
+++ b/aacdec_filter.c
@@ -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 2c900526..ddf93ecc 100644
--- 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 567b560a..e419d270 100644
--- 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 881db3c2..ba72d80e 100644
--- 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);
diff --git a/afh_common.c b/afh_common.c
index a267f58b..7e8f63d2 100644
--- a/afh_common.c
+++ b/afh_common.c
@@ -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];
diff --git a/afh_recv.c b/afh_recv.c
index 4f8ff497..8449e787 100644
--- a/afh_recv.c
+++ b/afh_recv.c
@@ -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 c4de2e8f..445d5871 100644
--- a/afs.c
+++ b/afs.c
@@ -24,52 +24,36 @@
#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)
@@ -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,7 @@ 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: %s\n",
+ afs_error(aca, "cannot open afs tables: %s\n",
para_strerror(-ret));
out:
return ret;
@@ -1057,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;
@@ -1138,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 cfa9cc6d..e8b8c865 100644
--- 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 eb955e01..f1aca7fb 100644
--- a/aft.c
+++ b/aft.c
@@ -6,6 +6,7 @@
#include
#include
#include
+#include
#include
#include
@@ -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
@@ -412,7 +413,7 @@ static void load_chunk_table(struct afh_info *afhi, const struct osl_object *ct)
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));
}
@@ -721,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;
@@ -729,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);
}
@@ -776,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)
@@ -797,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;
@@ -804,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;
@@ -835,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);
@@ -856,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))
@@ -893,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))
@@ -909,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",
@@ -1020,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
@@ -1028,9 +1040,9 @@ 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;
@@ -1038,6 +1050,7 @@ int open_and_update_audio_file(struct audio_file_data *afd)
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(¤t_aft_row, &d->score);
if (ret < 0)
@@ -1055,8 +1068,8 @@ again:
if (ret < 0)
return ret;
if (!d->hash)
- d->hash = para_malloc(HASH_SIZE);
- memcpy(d->hash, tmp_hash, HASH_SIZE);
+ 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)
@@ -1070,11 +1083,11 @@ again:
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) {
@@ -1086,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;
@@ -1101,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;
/*
@@ -1112,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) {
@@ -1129,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)
@@ -1213,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;
@@ -1306,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);
@@ -1349,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;
}
@@ -1378,20 +1430,14 @@ static int com_ls_callback(struct afs_callback_arg *aca)
if (ret < 0)
goto out;
time(¤t_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);
@@ -1413,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);
@@ -1432,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;
}
}
@@ -1467,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;
}
}
@@ -1524,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),
};
/*
@@ -1542,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);
@@ -1623,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);
@@ -1638,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;
@@ -1694,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;
@@ -1701,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);
@@ -1750,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;
}
@@ -1808,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);
@@ -1836,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)
@@ -1892,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;
@@ -1968,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;
@@ -2027,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;
}
}
@@ -2036,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;
}
}
@@ -2079,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;
}
@@ -2309,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 == '+')
@@ -2505,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;
@@ -2637,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,
+};
diff --git a/alsa_mix.c b/alsa_mix.c
index 1d81e5d9..af4adc46 100644
--- a/alsa_mix.c
+++ b/alsa_mix.c
@@ -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) {
diff --git a/alsa_write.c b/alsa_write.c
index 363e3930..53d7b1b4 100644
--- a/alsa_write.c
+++ b/alsa_write.c
@@ -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,
};
diff --git a/amp_filter.c b/amp_filter.c
index 61b1653e..be69ad67 100644
--- a/amp_filter.c
+++ b/amp_filter.c
@@ -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,
};
diff --git a/ao_write.c b/ao_write.c
index 447dea84..43a58dd6 100644
--- a/ao_write.c
+++ b/ao_write.c
@@ -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,
};
diff --git a/attribute.c b/attribute.c
index f7091727..51630b25 100644
--- a/attribute.c
+++ b/attribute.c
@@ -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,
+};
diff --git a/audioc.c b/audioc.c
index af670633..f2e4cb91 100644
--- 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)
diff --git a/audiod.c b/audiod.c
index a5a77437..7c223995 100644
--- 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);
diff --git a/audiod.h b/audiod.h
index b40fdd67..39beda1b 100644
--- 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);
diff --git a/audiod_command.c b/audiod_command.c
index bb54dfab..5f0b35a5 100644
--- a/audiod_command.c
+++ b/audiod_command.c
@@ -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);
diff --git a/autogen.sh b/autogen.sh
index caf1401d..cbfea7fd 100755
--- a/autogen.sh
+++ b/autogen.sh
@@ -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
diff --git a/base64.c b/base64.c
index 122465f8..ac6f65aa 100644
--- 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;
diff --git a/bitstream.c b/bitstream.c
index dfc1e55e..a1c7c943 100644
--- a/bitstream.c
+++ b/bitstream.c
@@ -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 4ecbc45b..1802de5d 100644
--- 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) \
diff --git a/buffer_tree.c b/buffer_tree.c
index 8a317513..35353f56 100644
--- a/buffer_tree.c
+++ b/buffer_tree.c
@@ -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;
diff --git a/check_wav.c b/check_wav.c
index 89ebdacc..3789f30a 100644
--- a/check_wav.c
+++ b/check_wav.c
@@ -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)
diff --git a/check_wav.h b/check_wav.h
index 79b11962..e6188c52 100644
--- a/check_wav.h
+++ b/check_wav.h
@@ -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);
diff --git a/chunk_queue.c b/chunk_queue.c
index 08f57e9d..1f11359f 100644
--- a/chunk_queue.c
+++ b/chunk_queue.c
@@ -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;
diff --git a/client.c b/client.c
index f72719f2..d2d11fd3 100644
--- 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)
@@ -544,6 +549,7 @@ __noreturn static void interactive_session(void)
i9e_close();
para_log = stderr_log;
out:
+ free(ici.history_file);
if (ret < 0)
PARA_ERROR_LOG("%s\n", para_strerror(-ret));
exit(ret < 0? EXIT_FAILURE : EXIT_SUCCESS);
@@ -579,7 +585,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 +631,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 +655,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 +667,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;
diff --git a/client_common.c b/client_common.c
index dc24a628..fe8234f9 100644
--- a/client_common.c
+++ b/client_common.c
@@ -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,7 +83,7 @@ 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);
}
__attribute__ ((fallthrough));
case CL_EXECUTING:
@@ -93,7 +92,7 @@ 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->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);
@@ -395,9 +426,9 @@ static int client_post_select(struct sched *s, void *context)
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;
diff --git a/close_on_fork.c b/close_on_fork.c
index 28c5eabb..809f027e 100644
--- a/close_on_fork.c
+++ b/close_on_fork.c
@@ -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;
diff --git a/command.c b/command.c
index 8ea725de..bddb9cf0 100644
--- a/command.c
+++ b/command.c
@@ -6,14 +6,10 @@
#include
#include
#include
-#include
-#include
#include
-#include
#include
#include
-#include "server.lsg.h"
#include "para.h"
#include "error.h"
#include "lsu.h"
@@ -22,12 +18,11 @@
#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"
@@ -37,12 +32,14 @@
#include "signal.h"
#include "version.h"
+/** \cond server_cmd_aux_info */
#define SERVER_CMD_AUX_INFO(_arg) _arg,
static const unsigned server_command_perms[] = {LSG_SERVER_CMD_AUX_INFOS};
#undef SERVER_CMD_AUX_INFO
#define SERVER_CMD_AUX_INFO(_arg) #_arg,
static const char * const server_command_perms_txt[] = {LSG_SERVER_CMD_AUX_INFOS};
#undef SERVER_CMD_AUX_INFO
+/** \endcond server_cmd_aux_info */
/** Commands including options must be shorter than this. */
#define MAX_COMMAND_LEN 32768
@@ -52,12 +49,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 +82,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 +394,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 +401,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);
@@ -425,7 +422,8 @@ static int com_version(struct command_context *cc, struct lls_parse_result *lpr)
}
EXPORT_SERVER_CMD_HANDLER(version);
-/** These status items are cleared if no audio file is currently open. */
+/** \cond empty_status_items */
+/* These status items are cleared if no audio file is currently open. */
#define EMPTY_STATUS_ITEMS \
ITEM(path) \
ITEM(directory) \
@@ -459,6 +457,8 @@ EXPORT_SERVER_CMD_HANDLER(version);
ITEM(amplification) \
ITEM(play_time) \
+/** \endcond empty_status_items */
+
/*
* Create a set of audio-file related status items with empty values. These are
* written to stat clients when no audio file is open.
@@ -512,6 +512,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 +544,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 +557,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 +597,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 +715,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 +723,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 +754,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 +762,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 +782,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 +791,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 +817,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 +861,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;
@@ -892,8 +921,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 +937,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 +985,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);
diff --git a/compress_filter.c b/compress_filter.c
index 15bed6df..1bce35f5 100644
--- a/compress_filter.c
+++ b/compress_filter.c
@@ -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,
};
diff --git a/configure.ac b/configure.ac
index f518f89a..b9ac3d0d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,7 +1,7 @@
AC_PREREQ([2.61])
AC_INIT([paraslash], [m4_esyscmd_s(./GIT-VERSION-GEN)],
- [maan@tuebingen.mpg.de], [], [http://people.tuebingen.mpg.de/maan/paraslash/])
+ [maan@tuebingen.mpg.de], [], [https://people.tuebingen.mpg.de/maan/paraslash/])
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_FILES([Makefile])
@@ -82,7 +82,7 @@ if test $HAVE_LOPSUB = no; then AC_MSG_ERROR([
The lopsub library is required to build this software, but
the above checks indicate it is not installed on your system.
Run the following command to download a copy.
- git clone git://git.tuebingen.mpg.de/lopsub.git
+ git clone https://git.tuebingen.mpg.de/lopsub.git
Install the library, then run this configure script again.
])
fi
@@ -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],
@@ -291,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
@@ -373,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
@@ -391,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
@@ -430,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"
@@ -571,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
@@ -678,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))
@@ -713,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
@@ -771,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"
@@ -827,6 +845,7 @@ audioc_errlist_objs="
lsu
net
fd
+ time
version
"
if test $HAVE_READLINE = yes; then
@@ -834,7 +853,6 @@ if test $HAVE_READLINE = yes; then
buffer_tree
interactive
sched
- time
"
fi
audioc_objs="$audioc_errlist_objs"
@@ -855,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 9623a003..5578cd56 100644
--- 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);
@@ -200,3 +200,42 @@ void hash_to_asc(const unsigned char *hash, char *asc);
* less than or equal to h2, respectively.
*/
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);
diff --git a/crypt_common.c b/crypt_common.c
index ff24e356..286ebe38 100644
--- a/crypt_common.c
+++ b/crypt_common.c
@@ -160,6 +160,31 @@ int hash_compare(const unsigned char *h1, const 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;
diff --git a/daemon.c b/daemon.c
index a4e2f319..d8f598be 100644
--- 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)
{
diff --git a/daemon.h b/daemon.h
index b530b0d7..54879924 100644
--- 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);
diff --git a/dccp_recv.c b/dccp_recv.c
index 639c93fc..0b20bcc8 100644
--- a/dccp_recv.c
+++ b/dccp_recv.c
@@ -27,6 +27,10 @@
#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,
};
diff --git a/dccp_send.c b/dccp_send.c
index bca7ad67..6182c964 100644
--- a/dccp_send.c
+++ b/dccp_send.c
@@ -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);
@@ -249,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 fe44ff5c..8805c9c7 100644
--- 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"), \
@@ -13,31 +14,26 @@
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"), \
@@ -85,7 +79,6 @@
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"), \
@@ -98,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"), \
@@ -116,7 +108,6 @@
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"), \
@@ -133,19 +124,16 @@
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"), \
@@ -153,7 +141,6 @@
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"), \
@@ -166,7 +153,6 @@
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"), \
@@ -174,17 +160,13 @@
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"), \
@@ -194,7 +176,6 @@
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"), \
@@ -238,21 +219,26 @@
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
@@ -260,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
@@ -303,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 33891d2e..1af902f9 100644
--- 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 c9e79426..e4f30903 100644
--- 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 2301cc8d..932e0693 100644
--- 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);
diff --git a/fecdec_filter.c b/fecdec_filter.c
index 13d4f7b2..375f4c0a 100644
--- a/fecdec_filter.c
+++ b/fecdec_filter.c
@@ -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,
};
diff --git a/file_write.c b/file_write.c
index 9a5ed5d7..ba902070 100644
--- a/file_write.c
+++ b/file_write.c
@@ -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,
};
diff --git a/filter.c b/filter.c
index d4a24239..722cb16f 100644
--- 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);
diff --git a/filter.h b/filter.h
index 69d4dfee..77057e6a 100644
--- 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);
diff --git a/filter_common.c b/filter_common.c
index add788a8..c1576752 100644
--- a/filter_common.c
+++ b/filter_common.c
@@ -99,6 +99,7 @@ int filter_setup(const char *fa, void **conf, struct lls_parse_result **lprp)
if (ret < 0)
goto free_argv;
f = filter_get(filter_num);
+ assert(f);
*conf = f->setup? f->setup(*lprp) : NULL;
ret = filter_num;
free_argv:
@@ -169,17 +170,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;
diff --git a/flac_afh.c b/flac_afh.c
index 6e236839..2b5b6c1d 100644
--- a/flac_afh.c
+++ b/flac_afh.c
@@ -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;
diff --git a/flacdec_filter.c b/flacdec_filter.c
index 6a3a8eff..fb8ebf15 100644
--- a/flacdec_filter.c
+++ b/flacdec_filter.c
@@ -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,
};
diff --git a/gcrypt.c b/gcrypt.c
index dbe49008..b46f8f95 100644
--- 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,
diff --git a/grab_client.c b/grab_client.c
index 83706493..1019e579 100644
--- a/grab_client.c
+++ b/grab_client.c
@@ -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 d779ff86..66fb7870 100644
--- a/gui.c
+++ b/gui.c
@@ -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);
}
@@ -1089,7 +1089,7 @@ static void handle_command(int 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;
@@ -1115,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);
@@ -1166,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)
@@ -1267,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) {
@@ -1275,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)
@@ -1286,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)
@@ -1391,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);
@@ -1422,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);
diff --git a/gui_theme.c b/gui_theme.c
index 6f4acf37..81bbe0f6 100644
--- a/gui_theme.c
+++ b/gui_theme.c
@@ -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 = "";
@@ -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 = "";
diff --git a/http_recv.c b/http_recv.c
index 1fb60bad..8d2add19 100644
--- a/http_recv.c
+++ b/http_recv.c
@@ -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,
};
diff --git a/http_send.c b/http_send.c
index c6b9decc..429b4662 100644
--- a/http_send.c
+++ b/http_send.c
@@ -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 93577b54..2e1089f1 100644
--- 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;
diff --git a/interactive.c b/interactive.c
index a8197308..1376cf1d 100644
--- a/interactive.c
+++ b/interactive.c
@@ -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);
+}
diff --git a/interactive.h b/interactive.h
index 40ff2940..6ef7f8e2 100644
--- a/interactive.h
+++ b/interactive.h
@@ -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/ipc.c b/ipc.c
index 8e9dd51a..c245f690 100644
--- a/ipc.c
+++ b/ipc.c
@@ -218,7 +218,7 @@ size_t shm_get_shmmax(void)
{
int fd = open("/proc/sys/kernel/shmmax", O_RDONLY);
if (fd >= 0) {
- char buf[100] = "";
+ char buf[100];
int ret = read(fd, buf, sizeof(buf) - 1);
if (ret > 0) {
buf[ret] = '\0';
diff --git a/list.h b/list.h
index 66c6d915..78c302fa 100644
--- a/list.h
+++ b/list.h
@@ -2,166 +2,134 @@
* 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 /* 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)
diff --git a/m4/lls/audiod_cmd.suite.m4 b/m4/lls/audiod_cmd.suite.m4
index 80ae2e42..bd2a23e2 100644
--- a/m4/lls/audiod_cmd.suite.m4
+++ b/m4/lls/audiod_cmd.suite.m4
@@ -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
index 00000000..d7576eae
--- /dev/null
+++ b/m4/lls/include/com_ll.m4
@@ -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]
diff --git a/m4/lls/makefile b/m4/lls/makefile
index daf6de92..a13f079e 100644
--- a/m4/lls/makefile
+++ b/m4/lls/makefile
@@ -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
diff --git a/m4/lls/mixer.suite.m4 b/m4/lls/mixer.suite.m4
index 3019f769..e366a201 100644
--- a/m4/lls/mixer.suite.m4
+++ b/m4/lls/mixer.suite.m4
@@ -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
diff --git a/m4/lls/server.suite.m4 b/m4/lls/server.suite.m4
index 93e4b573..1c941c43 100644
--- a/m4/lls/server.suite.m4
+++ b/m4/lls/server.suite.m4
@@ -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
diff --git a/m4/lls/server_cmd.suite.m4 b/m4/lls/server_cmd.suite.m4
index 2a39907c..02afaabb 100644
--- a/m4/lls/server_cmd.suite.m4
+++ b/m4/lls/server_cmd.suite.m4
@@ -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
index 00000000..7f46b749
--- /dev/null
+++ b/m4/lls/upgrade_db.suite.m4
@@ -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 eae89291..dda7fc1d 100644
--- a/mixer.c
+++ b/mixer.c
@@ -187,11 +187,11 @@ static int com_fade(const struct mixer *m)
}
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);
@@ -215,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;
@@ -263,6 +273,20 @@ static int set_initial_volume(const struct mixer *m, struct mixer_handle *h)
return 1;
}
+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;
@@ -270,6 +294,7 @@ static int com_sleep(const struct mixer *m)
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);
@@ -316,17 +341,27 @@ static int com_sleep(const struct mixer *m)
}
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)
- goto close_mixer;
change_afs_mode(fo_mood);
client_cmd("play");
- ret = set_channel(m, h, OPT_STRING_VAL(PARA_MIXER, MIXER_CHANNEL));
- if (ret < 0)
- goto close_mixer;
ret = fade(m, h, fov, fot);
if (ret < 0)
goto close_mixer;
@@ -340,7 +375,7 @@ static int com_sleep(const struct mixer *m)
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;
@@ -355,13 +390,12 @@ static int com_sleep(const struct mixer *m)
sleep(delay);
}
change_afs_mode(fi_mood);
- if (sleep_mood && *sleep_mood) /* currently playing */
- client_cmd("next");
- else /* currently stopped */
- client_cmd("play");
ret = open_mixer_and_set_channel(m, &h);
if (ret < 0)
return ret;
+ if (sleep_mood && *sleep_mood) /* currently playing */
+ stop(m, h);
+ client_cmd("play");
ret = fade(m, h, fiv, fit);
close_mixer:
m->close(&h);
diff --git a/mm.c b/mm.c
deleted file mode 100644
index 358783a1..00000000
--- a/mm.c
+++ /dev/null
@@ -1,375 +0,0 @@
-/* Copyright (C) 2007 Andre Noll , see file COPYING. */
-
-/** \file mm.c Paraslash's mood methods. */
-
-#include
-#include
-#include
-#include
-
-#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(¤t_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
index 28038e72..00000000
--- a/mm.h
+++ /dev/null
@@ -1,62 +0,0 @@
-/* Copyright (C) 2007 Andre Noll , 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 a63d4d2a..1e15ef0e 100644
--- 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;
-};
-
-/*
- * ] | deny [with score ] | score >
- * [if] [not] [options]
- * 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 = ¤t_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 = ¤t_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, ¤t_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, ¤t_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
index fcfe1efc..00000000
--- a/mood.h
+++ /dev/null
@@ -1,7 +0,0 @@
-/* Copyright (C) 2007 Andre Noll , 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 56c16e31..a068b043 100644
--- 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.
*
@@ -497,7 +518,7 @@ int mp_init(const char *definition, int nbytes, struct mp_context **result,
*errmsg = 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 febbe324..891bfb05 100644
--- 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 */
diff --git a/mp3_afh.c b/mp3_afh.c
index 728b25b8..56f28a09 100644
--- 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++;
diff --git a/mp3dec_filter.c b/mp3dec_filter.c
index ccb1553b..d40df85e 100644
--- a/mp3dec_filter.c
+++ b/mp3dec_filter.c
@@ -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
index 00000000..fe9d4b37
--- /dev/null
+++ b/mp4.c
@@ -0,0 +1,1056 @@
+/*
+ * 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
+
+#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;
+}
+
+/** \cond atom_items */
+/* 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 */ \
+
+/** \endcond atom_items */
+
+/** 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
index 00000000..c36a1f81
--- /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 91200fc0..9b362442 100644
--- a/net.c
+++ b/net.c
@@ -18,6 +18,13 @@
#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 2256f376..33acfc89 100644
--- a/net.h
+++ b/net.h
@@ -1,106 +1,32 @@
/* Copyright (C) 2006 Andre Noll , 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);
diff --git a/ogg_afh_common.c b/ogg_afh_common.c
index 3e36bdd5..0a27a4ac 100644
--- a/ogg_afh_common.c
+++ b/ogg_afh_common.c
@@ -167,7 +167,7 @@ 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;
@@ -179,9 +179,9 @@ int oac_get_file_info(char *map, size_t numbytes, struct afh_info *afhi,
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;
}
@@ -365,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));
}
/**
diff --git a/ogg_afh_common.h b/ogg_afh_common.h
index e0cf2d40..03bf88b5 100644
--- a/ogg_afh_common.h
+++ b/ogg_afh_common.h
@@ -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);
diff --git a/oggdec_filter.c b/oggdec_filter.c
index 708a27e5..b1aec4bc 100644
--- a/oggdec_filter.c
+++ b/oggdec_filter.c
@@ -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
};
diff --git a/openssl.c b/openssl.c
index 0ad9d7db..f696cd9e 100644
--- a/openssl.c
+++ b/openssl.c
@@ -11,6 +11,7 @@
#include
#include
#include
+#include
#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)
{
@@ -100,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;
@@ -152,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;
@@ -219,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;
@@ -270,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:
@@ -283,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,
@@ -324,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);
@@ -366,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();
@@ -390,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);
@@ -409,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);
}
diff --git a/opus_afh.c b/opus_afh.c
index dca6cfba..0a291bb1 100644
--- a/opus_afh.c
+++ b/opus_afh.c
@@ -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);
diff --git a/opusdec_filter.c b/opusdec_filter.c
index 10ed394d..f36990fa 100644
--- a/opusdec_filter.c
+++ b/opusdec_filter.c
@@ -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,
};
diff --git a/oss_mix.c b/oss_mix.c
index f80301e9..0814336f 100644
--- 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;
diff --git a/oss_write.c b/oss_write.c
index 311a514d..4ea85afa 100644
--- a/oss_write.c
+++ b/oss_write.c
@@ -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 b406818b..280c2823 100644
--- a/para.h
+++ b/para.h
@@ -20,6 +20,8 @@
#include
#include
#include
+#include
+
#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 2346c6b0..4adedcc0 100644
--- a/play.c
+++ b/play.c
@@ -7,6 +7,7 @@
#include
#include "recv_cmd.lsg.h"
+#include "filter_cmd.lsg.h"
#include "play_cmd.lsg.h"
#include "write_cmd.lsg.h"
#include "play.lsg.h"
@@ -24,16 +25,6 @@
#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,16 +193,16 @@ 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;
}
@@ -242,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;
}
@@ -269,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));
@@ -292,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))
@@ -409,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);
@@ -592,10 +580,9 @@ static char *get_user_key_map_seq(int key)
char *result;
int len;
- if (!p)
- return NULL;
+ assert(p);
len = p - kma;
- result = para_malloc(len + 1);
+ result = alloc(len + 1);
memcpy(result, kma, len);
result[len] = '\0';
return result;
@@ -615,7 +602,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++) {
@@ -655,7 +642,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;
@@ -713,6 +700,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;
@@ -839,20 +831,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;
}
@@ -951,7 +944,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;
@@ -1051,9 +1044,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;
@@ -1066,7 +1066,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)
@@ -1094,22 +1094,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;
@@ -1132,11 +1132,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;
@@ -1155,11 +1155,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);
@@ -1179,7 +1179,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,
@@ -1193,7 +1193,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;
@@ -1202,7 +1202,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) {
@@ -1236,28 +1236,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->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);
diff --git a/playlist.c b/playlist.c
index 5f83b0fe..c145b0fd 100644
--- a/playlist.c
+++ b/playlist.c
@@ -14,14 +14,20 @@
/** \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, ¤t_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);
diff --git a/prebuffer_filter.c b/prebuffer_filter.c
index 1988e6e0..28b71010 100644
--- a/prebuffer_filter.c
+++ b/prebuffer_filter.c
@@ -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,7 +50,7 @@ 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;
@@ -61,10 +61,10 @@ static int prebuffer_post_select(__a_unused struct sched *s, void *context)
ret = task_get_notification(fn->task);
if (ret < 0)
- return ret;
+ goto fail;
ret = btr_node_status(btrn, size, BTR_NT_INTERNAL);
if (ret < 0)
- return ret;
+ goto fail;
if (ppd->barrier.tv_sec == 0)
return 0;
if (tv_diff(now, &ppd->barrier, NULL) < 0)
@@ -73,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 10d55d21..68417187 100644
--- 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 36b0f1db..391395b2 100644
--- 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);
diff --git a/recv_common.c b/recv_common.c
index 31fd81f1..1939300a 100644
--- a/recv_common.c
+++ b/recv_common.c
@@ -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);
diff --git a/resample_filter.c b/resample_filter.c
index bbdda51c..72cb3f62 100644
--- a/resample_filter.c
+++ b/resample_filter.c
@@ -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
diff --git a/ringbuffer.c b/ringbuffer.c
index 76e2d7af..0e706f0f 100644
--- a/ringbuffer.c
+++ b/ringbuffer.c
@@ -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 a2903940..20822038 100644
--- 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 35e2503e..ede5e67e 100644
--- 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 98358933..c03e3472 100644
--- 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 f6aafbb4..3407bc5c 100644
--- 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);
diff --git a/send_common.c b/send_common.c
index ea494d9a..8dc82e9c 100644
--- a/send_common.c
+++ b/send_common.c
@@ -21,10 +21,10 @@
#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. */
@@ -44,12 +44,12 @@
*/
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"
diff --git a/server.c b/server.c
index e0d50f4f..ea9cc9c0 100644
--- 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
#include
#include
@@ -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();
diff --git a/server.h b/server.h
index da75d86b..10bb6172 100644
--- 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);
diff --git a/sideband.c b/sideband.c
index ed7867a1..d4876234 100644
--- a/sideband.c
+++ b/sideband.c
@@ -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;
diff --git a/signal.c b/signal.c
index 32d6ab66..d9a6aa37 100644
--- 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));
diff --git a/signal.h b/signal.h
index e5532ded..d9e98e78 100644
--- 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);
diff --git a/spx_afh.c b/spx_afh.c
index caeacb19..cd3b7cde 100644
--- 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);
diff --git a/spxdec_filter.c b/spxdec_filter.c
index 7be817dd..08eac02a 100644
--- a/spxdec_filter.c
+++ b/spxdec_filter.c
@@ -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 9408235a..d025b949 100644
--- a/stdin.c
+++ b/stdin.c
@@ -14,10 +14,10 @@
#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,
};
diff --git a/stdout.c b/stdout.c
index 1f779109..ba5f1967 100644
--- a/stdout.c
+++ b/stdout.c
@@ -10,24 +10,24 @@
#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",
};
diff --git a/string.c b/string.c
index 198e9f1d..d8bd027b 100644
--- a/string.c
+++ b/string.c
@@ -15,78 +15,124 @@
#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);
diff --git a/string.h b/string.h
index 10251ae7..d773600f 100644
--- 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);
diff --git a/sync_filter.c b/sync_filter.c
index 2ca2a657..20db1b1d 100644
--- a/sync_filter.c
+++ b/sync_filter.c
@@ -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
index 00000000..917d59df
Binary files /dev/null and b/t/audio_files/short-44100-2.mp3 differ
diff --git a/t/t0004-server.sh b/t/t0004-server.sh
index 03957464..f7a407dc 100755
--- a/t/t0004-server.sh
+++ b/t/t0004-server.sh
@@ -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
"
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 75249fe3..1ba70632 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -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"
diff --git a/udp_recv.c b/udp_recv.c
index 58d45ab4..f98a9664 100644
--- a/udp_recv.c
+++ b/udp_recv.c
@@ -23,13 +23,13 @@
#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,
};
diff --git a/udp_send.c b/udp_send.c
index 04e2982f..fe001025 100644
--- a/udp_send.c
+++ b/udp_send.c
@@ -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
index 00000000..487d46c0
--- /dev/null
+++ b/upgrade_db.c
@@ -0,0 +1,386 @@
+/* Copyright (C) 2020 Andre Noll , see file COPYING. */
+
+/** \file upgrade_db.c Prepare the paraslash database for paraslash-0.7. */
+
+#include
+#include
+#include
+
+#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;
+}
diff --git a/user_list.c b/user_list.c
index 32a4309d..46770edf 100644
--- a/user_list.c
+++ b/user_list.c
@@ -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 9857d92d..cd55851c 100644
--- 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
@@ -335,10 +335,10 @@ static int initialize_fec_client(struct fec_client *fc, struct vss_task *vsst)
ret = fec_new(k, n, &fc->parms);
if (ret < 0)
return ret;
- fc->src_data = para_malloc(k * sizeof(char *));
+ fc->src_data = arr_alloc(k, sizeof(char *));
for (i = 0; i < k; i++)
- fc->src_data[i] = para_malloc(fc->mps);
- fc->enc_buf = para_malloc(fc->mps);
+ 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;
@@ -348,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;
@@ -363,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;
}
@@ -380,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) {
@@ -416,7 +416,8 @@ 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;
@@ -586,7 +587,7 @@ static int setup_next_fec_group(struct fec_client *fc, struct vss_task *vsst)
slice_copied = 0;
for (c = g->first_chunk; c < g->first_chunk + g->num_chunks; c++) {
char *buf;
- size_t src_len;
+ uint32_t src_len;
ret = vss_get_chunk(c, vsst, &buf, &src_len);
if (ret < 0)
return ret;
@@ -670,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;
@@ -826,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,
@@ -844,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)
@@ -855,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);
@@ -892,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);
}
@@ -950,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))
@@ -964,6 +964,11 @@ static void recv_afs_result(struct vss_task *vsst, fd_set *rfds)
if (ret < 0)
goto err;
vsst->afsss = AFS_SOCKET_READY;
+ 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) {
PARA_ERROR_LOG("afs code: %u, expected: %d\n", afs_code,
@@ -1000,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)
{
@@ -1022,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;
@@ -1080,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;
@@ -1130,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));
@@ -1140,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()))
@@ -1170,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();
@@ -1187,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);
}
@@ -1201,11 +1203,14 @@ void vss_init(int afs_socket, struct sched *s)
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();
}
}
diff --git a/wav_filter.c b/wav_filter.c
index e749160d..de4a3e6a 100644
--- a/wav_filter.c
+++ b/wav_filter.c
@@ -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,
};
diff --git a/web/about.in.html b/web/about.in.html
index 8395864a..452fa357 100644
--- a/web/about.in.html
+++ b/web/about.in.html
@@ -1,8 +1,8 @@
About
-Paraslash is a collection of network audio streaming tools for Unix
-systems. It is written in C and released under the GPLv2.
+ Paraslash is a collection of network audio streaming tools for
+Unix systems. It is written in C and released under the GPLv2.
- Runs on Linux, FreeBSD, NetBSD
@@ -10,15 +10,17 @@ systems. It is written in C and released under the GPLv2.
- http, dccp and udp network streaming
- Stand-alone decoder, player, tagger
- Curses-based gui (screenshot)
- - Integrated volume normalizer, fader, alarm clock
+ - Volume normalizer, fader, alarm clock
- Sophisticated audio file selector
- Command line interface with tab-completion
- Open source and well documented
- Author: André Noll,
-maan@tuebingen.mpg.de,
-Homepage: http://people.tuebingen.mpg.de/maan/
-
-Comments and bug reports are welcome. Please provide the version of
-paraslash you are using and relevant parts of the logs.
+ Author: Andre Noll, maan@tuebingen.mpg.de,
+Homepage: https://people.tuebingen.mpg.de/maan/
+
+
+ Comments and bug reports are welcome. Please provide the version
+of paraslash you are using and relevant parts of the logs.
diff --git a/web/documentation.in.html b/web/documentation.in.html
index d6d690a1..32efe9c3 100644
--- a/web/documentation.in.html
+++ b/web/documentation.in.html
@@ -23,12 +23,13 @@
[para_filter]
[para_write]
[para_gui]
- [para_mixere]
+ [para_mixer]
[para_play]
+ [para_upgrade_db]
Source code documentation
diff --git a/web/download.in.html b/web/download.in.html
index 9ef92b7a..1287597e 100644
--- a/web/download.in.html
+++ b/web/download.in.html
@@ -1,8 +1,9 @@
Download
-Paraslash is only available as source code, no binary packages are
-provided at this point. There are several ways to download the source:
+ Paraslash is only available as source code, no binary packages
+are provided at this point. There are several ways to download the
+source:
- git.
@@ -10,7 +11,7 @@ provided at this point. There are several ways to download the source:
Clone the git repository by executing
- git clone git://git.tuebingen.mpg.de/paraslash.git
+ git clone https://git.tuebingen.mpg.de/paraslash.git
The repository contains the full history of the
@@ -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
- Git branches
+ Git branches
section of the manual). All previous releases
correspond to tagged commits and may be checked out
@@ -65,7 +66,7 @@ provided at this point. There are several ways to download the source:
The
- gitweb
+ gitweb
page contains a snapshot link for each revision. This
allows getting a specific revision without downloading
diff --git a/web/footer.html b/web/footer.html
index 38572c94..cceeff1a 100644
--- a/web/footer.html
+++ b/web/footer.html
@@ -1,5 +1,3 @@
-
-