From: Andre Noll Date: Tue, 12 Mar 2024 18:02:39 +0000 (+0100) Subject: paraslash 0.7.3 X-Git-Tag: v0.7.3^0 X-Git-Url: http://git.tuebingen.mpg.de/?p=paraslash.git;a=commitdiff_plain;h=HEAD;hp=a79e210f33334b273d11c92a430dd477284ee95a paraslash 0.7.3 --- 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/Makefile.real b/Makefile.real index 6e8084d5..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 := 2022 +COPYRIGHT_YEAR := 2024 ifeq ("$(origin O)", "command line") build_dir := $(O) @@ -112,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 @@ -130,6 +134,11 @@ 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 @@ -138,12 +147,7 @@ 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 \ @@ -153,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 @@ -192,73 +197,48 @@ $(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 \ @@ -337,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 e9713d98..d5812289 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,12 +1,101 @@ NEWS ==== -------------------------------------------- -0.7.1 (to be announced) "digital spindrift" -------------------------------------------- +--------------------------------------------- +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" ---------------------------------- @@ -196,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/aac_afh.c b/aac_afh.c index 5c1225b6..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,13 +22,11 @@ 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; size_t have, rv; @@ -40,45 +34,32 @@ static uint32_t aac_afh_read_cb(void *user_data, void *dest, uint32_t want) if (want == 0 || c->fpos >= c->mapsize) return 0; have = c->mapsize - c->fpos; - rv = PARA_MIN(have, (size_t)want); + rv = PARA_MIN(have, want); 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; @@ -87,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; @@ -108,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(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"); } /* @@ -162,9 +124,8 @@ 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; uint32_t n, len; @@ -172,157 +133,116 @@ static int aac_get_file_info(char *map, size_t numbytes, __a_unused int fd, 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, &len) < 0) - break; + 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, &len) >= 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)) - 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)) - 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 59ffab3e..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; diff --git a/afh_recv.c b/afh_recv.c index 6a0ec239..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; @@ -188,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); @@ -205,7 +205,7 @@ static int afh_recv_post_select(__a_unused struct sched *s, void *context) 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) @@ -226,7 +226,7 @@ static int afh_recv_post_select(__a_unused struct sched *s, void *context) PARA_DEBUG_LOG("adding chunk %u\n", pard->current_chunk); 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++; @@ -242,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 71067025..445d5871 100644 --- a/afs.c +++ b/afs.c @@ -24,51 +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, - /* - * Moods and playlists organize the current set of admissible files in - * an osl table which contains only volatile columns. Each row consists - * of a pointer to an audio file and the score value of this file. - */ - 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. */ @@ -115,7 +100,7 @@ extern uint32_t afs_socket_cookie; */ 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; }; @@ -206,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) @@ -446,40 +431,30 @@ no_admissible_files: return write_all(server_socket, buf, 8); } -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) { + 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; - ret = change_current_mood(NULL, errmsg); - if (ret < 0) { - if (num_admissible) - *num_admissible = 0; - return ret; - } } 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 @@ -489,22 +464,15 @@ static int activate_mood_or_playlist(const char *arg, int *num_admissible, */ 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; } @@ -557,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) { - PARA_WARNING_LOG("could not activate %s: %s\n", arg, - para_strerror(-ret)); + PARA_WARNING_LOG("could not activate %s: %s\n", arg? + arg : "dummy", para_strerror(-ret)); if (arg) - activate_mood_or_playlist(NULL, NULL, NULL); + activate_mood_or_playlist(NULL, NULL); } } @@ -655,7 +560,7 @@ static void close_afs_tables(void) int i; 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; } @@ -675,26 +580,15 @@ static void get_database_dir(void) 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); @@ -703,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; @@ -715,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) { @@ -743,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); @@ -762,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); } /** @@ -823,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; @@ -842,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 */ @@ -862,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; @@ -877,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; @@ -917,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; @@ -927,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); @@ -935,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); @@ -948,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) @@ -959,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); @@ -973,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. * @@ -987,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]); ret = open_afs_tables(); if (ret < 0) goto out; @@ -1003,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) @@ -1014,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: @@ -1027,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; @@ -1035,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", @@ -1052,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; @@ -1066,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; @@ -1147,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 b1606493..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,30 +231,33 @@ 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(int *fd); @@ -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 5f9098aa..f1aca7fb 100644 --- a/aft.c +++ b/aft.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -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); } @@ -779,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) @@ -800,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; @@ -807,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; @@ -897,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)) @@ -913,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", @@ -1067,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) @@ -1102,7 +1103,7 @@ again: if (ret < 0) goto out; hash2_function(map.data, map.size, file_hash); - ret = hash_compare(file_hash, d->hash); + ret = hash2_compare(file_hash, d->hash); para_munmap(map.data, map.size); if (ret) { ret = -E_HASH_MISMATCH; @@ -1225,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; @@ -1318,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); @@ -1361,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; } @@ -1390,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); @@ -1425,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); @@ -1444,7 +1478,7 @@ 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 { @@ -1554,7 +1588,7 @@ 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); @@ -1638,7 +1672,8 @@ static int com_add_callback(struct afs_callback_arg *aca) 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); @@ -1650,7 +1685,7 @@ 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 = HASH2_SIZE; @@ -1706,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; @@ -1713,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 * 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); @@ -1762,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; } @@ -1904,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; @@ -1980,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; @@ -2039,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; } } @@ -2048,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; } } @@ -2091,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; } @@ -2321,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 == '+') @@ -2649,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 bbbf8b65..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) @@ -254,7 +254,7 @@ 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; @@ -277,7 +277,7 @@ again: 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 */ @@ -296,7 +296,7 @@ again: if (bytes == 0) /* no data available */ return 0; - pad = wn->private_data = para_calloc(sizeof(*pad)); + pad = wn->private_data = zalloc(sizeof(*pad)); ret = get_btr_sample_rate(btrn, &val); if (ret < 0) goto err; @@ -321,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; @@ -349,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 037b9299..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; @@ -388,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; @@ -421,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 fb1b3eac..51630b25 100644 --- a/attribute.c +++ b/attribute.c @@ -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,16 +218,14 @@ 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; @@ -277,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); @@ -306,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); @@ -499,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 838f375f..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; @@ -564,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]; @@ -584,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; @@ -602,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; @@ -629,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) @@ -648,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; @@ -749,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( @@ -819,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 = para_malloc(len + 1); + re = alloc(len + 1); strncpy(re, txt, len); re[len] = '\0'; ret = get_matching_audio_format_nums(re); @@ -835,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; @@ -886,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])); @@ -963,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; @@ -1055,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; @@ -1063,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; @@ -1092,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; @@ -1132,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); } @@ -1254,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; @@ -1286,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; @@ -1377,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); } @@ -1462,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)); @@ -1505,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 f0d2002d..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; @@ -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 cf74cc33..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,7 +130,7 @@ 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)); + struct chunk_queue *cq = alloc(sizeof(*cq)); init_list_head(&cq->q); cq->max_pending = max_pending; cq->num_pending = 0; diff --git a/client.c b/client.c index 8caf4483..84b7580c 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; @@ -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); @@ -246,6 +246,12 @@ I9E_DUMMY_COMPLETER(init); 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) { @@ -341,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); @@ -435,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++) @@ -532,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) @@ -578,7 +584,7 @@ struct supervisor_task { struct task *task; }; -static int supervisor_post_select(struct sched *s, void *context) +static int supervisor_post_monitor(struct sched *s, void *context) { struct supervisor_task *svt = context; int ret = task_status(ct->task); @@ -624,7 +630,7 @@ int main(int argc, char *argv[]) int ret; crypt_init(); - sched.default_timeout.tv_sec = 1; + sched.default_timeout = 1000; ret = client_parse_config(argc, argv, &ct, &client_loglevel); if (ret < 0) @@ -648,7 +654,7 @@ int main(int argc, char *argv[]) EMBRACE(.name = "stdout", .parent = ct->btrn[0])); supervisor_task.task = task_register(&(struct task_info) { .name = "supervisor", - .post_select = supervisor_post_select, + .post_monitor = supervisor_post_monitor, .context = &supervisor_task, }, &sched); @@ -660,8 +666,6 @@ int main(int argc, char *argv[]) /* these are not errors */ case -E_SERVER_CMD_SUCCESS: case -E_EOF: - case -E_SERVER_EOF: - case -E_BTR_EOF: ret = 0; break; default: ret = -E_SERVER_CMD_FAILURE; diff --git a/client_common.c b/client_common.c index 94a6e865..fe8234f9 100644 --- a/client_common.c +++ b/client_common.c @@ -57,7 +57,7 @@ void client_close(struct client_task *ct) * 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; @@ -68,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: @@ -83,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: @@ -92,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; } @@ -125,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; @@ -134,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 { @@ -146,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; @@ -248,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); @@ -258,9 +255,15 @@ 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) { - return find_arg(feature, ct->features) >= 0? true : false; + if (!ct->features) + return false; + for (int i = 0; ct->features[i]; i++) + if (strcmp(feature, ct->features[i]) == 0) + return true; + return false; } /* @@ -274,7 +277,7 @@ static bool has_feature(const char *feature, struct client_task *ct) * 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; @@ -288,7 +291,7 @@ 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); @@ -302,7 +305,7 @@ static int client_post_select(struct sched *s, void *context) * 0.8.0 we no longer need to request the feature. */ bool has_sha256; - if (!FD_ISSET(ct->scc.fd, &s->wfds)) + if (!sched_write_ok(ct->scc.fd, s)) return 0; has_sha256 = has_feature("sha256", ct); sprintf(buf, AUTH_REQUEST_MSG "%s%s", ct->user, has_sha256? @@ -324,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) { @@ -339,18 +342,20 @@ 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(HASH2_SIZE); - + ct->challenge_hash = alloc(HASH2_SIZE); if (has_feature("sha256", ct)) { - hash2_function((char *)crypt_buf, APC_CHALLENGE_SIZE, ct->challenge_hash); + 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_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); + 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; @@ -371,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); @@ -383,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) @@ -396,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); @@ -421,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) { @@ -452,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; @@ -489,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; @@ -504,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; @@ -577,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); } @@ -586,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 7b464d09..809f027e 100644 --- a/close_on_fork.c +++ b/close_on_fork.c @@ -31,7 +31,7 @@ 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); diff --git a/command.c b/command.c index 5b17f116..60c2aeba 100644 --- a/command.c +++ b/command.c @@ -10,7 +10,6 @@ #include #include -#include "server.lsg.h" #include "para.h" #include "error.h" #include "lsu.h" @@ -22,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 "daemon.h" #include "fd.h" @@ -48,12 +47,14 @@ extern struct misc_meta_data *mmd; int send_afs_status(struct command_context *cc, int parser_friendly); static bool subcmd_should_die; +/* + * Don't call PARA_XXX_LOG() here as we might already hold the log mutex. See + * generic_signal_handler() for details. + */ static void command_handler_sighandler(int s) { - if (s != SIGTERM) - return; - PARA_EMERG_LOG("terminating on signal %d\n", SIGTERM); - subcmd_should_die = true; + if (s == SIGTERM) + subcmd_should_die = true; } /* @@ -79,7 +80,7 @@ static char *vss_status_tohuman(unsigned int flags) */ static char *vss_get_status_flags(unsigned int flags) { - char *msg = para_malloc(5 * sizeof(char)); + char *msg = alloc(5 * sizeof(char)); msg[0] = (flags & VSS_PLAYING)? 'P' : '_'; msg[1] = (flags & VSS_NOMORE)? 'O' : '_'; @@ -391,7 +392,6 @@ static int com_si(struct command_context *cc, "server_pid: %d\n" "afs_pid: %d\n" "connections (active/accepted/total): %u/%u/%u\n" - "current loglevel: %s\n" "supported audio formats: %s\n", ut, mmd->num_played, (int)getppid(), @@ -399,7 +399,6 @@ static int com_si(struct command_context *cc, mmd->active_connections, mmd->num_commands, mmd->num_connects, - ENUM_STRING_VAL(LOGLEVEL), AUDIO_FORMAT_HANDLERS ); mutex_unlock(mmd_mutex); @@ -508,6 +507,7 @@ static int com_stat(struct command_context *cc, struct lls_parse_result *lpr) * while we sleep. */ para_block_signal(SIGTERM); + para_block_signal(SIGUSR1); for (;;) { sigset_t set; /* @@ -539,8 +539,10 @@ static int com_stat(struct command_context *cc, struct lls_parse_result *lpr) * open a race window similar to the one described above. */ pselect(1, NULL, NULL, NULL, &ts, &set); - if (subcmd_should_die) + if (subcmd_should_die) { + PARA_EMERG_LOG("terminating on SIGTERM\n"); goto out; + } ret = -E_SERVER_CRASH; if (getppid() == 1) goto out; @@ -590,9 +592,56 @@ static int com_hup(__a_unused struct command_context *cc, } EXPORT_SERVER_CMD_HANDLER(hup); +static int com_ll(struct command_context *cc, struct lls_parse_result *lpr) +{ + unsigned ll, perms; + char *errctx; + const char *sev[] = {SEVERITIES}, *arg; + int ret = lls(lls_check_arg_count(lpr, 0, 1, &errctx)); + + if (ret < 0) { + send_errctx(cc, errctx); + return ret; + } + if (lls_num_inputs(lpr) == 0) { /* reporting is an unprivileged op. */ + const char *severity; + mutex_lock(mmd_mutex); + severity = sev[mmd->loglevel]; + mutex_unlock(mmd_mutex); + return send_sb_va(&cc->scc, SBD_OUTPUT, "%s\n", severity); + } + /* + * Changing the loglevel changes the state of both the afs and the vss, + * so we require both AFS_WRITE and VSS_WRITE. + */ + perms = AFS_WRITE | VSS_WRITE; + if ((cc->u->perms & perms) != perms) + return -ERRNO_TO_PARA_ERROR(EPERM); + arg = lls_input(0, lpr); + for (ll = 0; ll < NUM_LOGLEVELS; ll++) + if (!strcmp(arg, sev[ll])) + break; + if (ll >= NUM_LOGLEVELS) + return -ERRNO_TO_PARA_ERROR(EINVAL); + PARA_INFO_LOG("new log level: %s\n", sev[ll]); + /* Ask the server and afs processes to adjust their log level. */ + mutex_lock(mmd_mutex); + mmd->loglevel = ll; + mutex_unlock(mmd_mutex); + return 1; +} +EXPORT_SERVER_CMD_HANDLER(ll); + static int com_term(__a_unused struct command_context *cc, __a_unused struct lls_parse_result *lpr) { + /* + * The server catches SIGTERM and propagates this signal to all its + * children. We are about to exit anyway, but we'd leak tons of memory + * if being terminated by the signal. So we ignore the signal here and + * terminate via the normal exit path, deallocating all memory. + */ + para_sigaction(SIGTERM, SIG_IGN); kill(getppid(), SIGTERM); return 1; } @@ -661,7 +710,7 @@ static int com_ff(struct command_context *cc, struct lls_parse_result *lpr) { long promille; int i, ret; - char c, *errctx; + char *errctx; ret = lls(lls_check_arg_count(lpr, 1, 1, &errctx)); if (ret < 0) { @@ -669,21 +718,8 @@ static int com_ff(struct command_context *cc, struct lls_parse_result *lpr) return ret; } ret = para_atoi32(lls_input(0, lpr), &i); - if (ret < 0) { - if (ret != -E_ATOI_JUNK_AT_END) - return ret; - /* - * Compatibility code to keep the historic syntax (ff 30-) - * working. This can be removed after 0.7.0. - */ - ret = sscanf(lls_input(0, lpr), "%i%c", &i, &c); - if (ret <= 0) - return -E_COMMAND_SYNTAX; - if (ret > 1 && c == '-') { - PARA_WARNING_LOG("use of obsolete syntax\n"); - i = -i; - } - } + if (ret < 0) + return ret; mutex_lock(mmd_mutex); ret = -E_NO_AUDIO_FILE; if (!mmd->afd.afhi.chunks_total || !mmd->afd.afhi.seconds_total) @@ -775,19 +811,11 @@ static int parse_auth_request(char *buf, int len, const struct user **u, *p = '\0'; p++; create_argv(p, ",", &features); - /* - * Still accept sideband and AES feature requests (as a no-op) - * because some 0.6.x clients request them. The two checks - * below may be removed after 0.7.1. - */ 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 but the - * check has to stay until 0.9.0. + * ->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; @@ -828,13 +856,13 @@ static int run_command(struct command_context *cc, struct iovec *iov) } perms = server_command_perms[ret]; if ((perms & cc->u->perms) != perms) - return -E_PERM; + return -ERRNO_TO_PARA_ERROR(EPERM); lcmd = lls_cmd(ret, server_cmd_suite); end = iov->iov_base + iov->iov_len; for (i = 0; p < end; i++) p += strlen(p) + 1; argc = i; - argv = para_malloc((argc + 1) * sizeof(char *)); + argv = arr_alloc(argc + 1, sizeof(char *)); for (i = 0, p = iov->iov_base; p < end; i++) { argv[i] = para_strdup(p); p += strlen(p) + 1; @@ -889,7 +917,7 @@ int handle_connect(int fd) int ret; unsigned char rand_buf[APC_CHALLENGE_SIZE + 2 * SESSION_KEY_LEN]; unsigned char challenge_hash[HASH2_SIZE]; - char *command = NULL, *buf = para_malloc(HANDSHAKE_BUFSIZE) /* must be on the heap */; + 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; diff --git a/compress_filter.c b/compress_filter.c index ff4ce6fb..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++; @@ -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 b817979f..92560e00 100644 --- a/configure.ac +++ b/configure.ac @@ -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 @@ -434,7 +437,7 @@ 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)) @@ -586,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 @@ -693,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)) @@ -728,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 @@ -786,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" @@ -842,6 +845,7 @@ audioc_errlist_objs=" lsu net fd + time version " if test $HAVE_READLINE = yes; then @@ -849,7 +853,6 @@ if test $HAVE_READLINE = yes; then buffer_tree interactive sched - time " fi audioc_objs="$audioc_errlist_objs" diff --git a/crypt.h b/crypt.h index 5ca6a541..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); diff --git a/crypt_common.c b/crypt_common.c index 3a44dbdd..286ebe38 100644 --- a/crypt_common.c +++ b/crypt_common.c @@ -295,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; @@ -317,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 dd5420a6..d8f598be 100644 --- a/daemon.c +++ b/daemon.c @@ -64,18 +64,24 @@ static void daemon_set_default_log_colors(void) */ 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 cd5c20ef..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"), \ @@ -18,25 +19,21 @@ 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"), \ @@ -51,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"), \ @@ -65,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"), \ @@ -73,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"), \ @@ -84,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"), \ @@ -97,7 +91,6 @@ PARA_ERROR(FLAC_CHAIN_READ, "could not read meta chain"), \ PARA_ERROR(FLACDEC_DECODER_ALLOC, "could not allocate stream decoder"), \ PARA_ERROR(FLACDEC_DECODER_INIT, "could not init stream decoder"), \ - PARA_ERROR(FLACDEC_EOF, "flacdec encountered end of file condition"), \ PARA_ERROR(FLAC_DECODE_POS, "could not get decode position"), \ PARA_ERROR(FLAC_ITER_ALLOC, "could not allocate meta iterator"), \ PARA_ERROR(FLAC_REPLACE_COMMENT, "could not replace vorbis comment"), \ @@ -115,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"), \ @@ -132,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"), \ @@ -152,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"), \ @@ -172,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"), \ @@ -192,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"), \ @@ -236,15 +219,12 @@ 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 @@ -266,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 @@ -309,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..f48e4570 100644 --- a/filter_common.c +++ b/filter_common.c @@ -169,17 +169,16 @@ void print_filter_list(void) } /** - * Set select timeout of the scheduler. + * Request a minimal timeout if not idle. * - * \param s The scheduler. - * \param context Pointer to the filter node (task context). + * \param s The scheduler instance. + * \param context Pointer to the filter node. * - * This looks at the status of the btr node of the filter. If data is available - * in the input queue of the filter, or if an error occurred, a minimal timeout - * for the next select call is requested from the scheduler. Otherwise the - * scheduler timeout is left unchanged. + * If the buffer tree node of the given filter node has data available (or is + * in error state) a minimal I/O timeout is requested from the scheduler. + * Otherwise the function does nothing. */ -void generic_filter_pre_select(struct sched *s, void *context) +void generic_filter_pre_monitor(struct sched *s, void *context) { struct filter_node *fn = context; 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 506f0bb8..b46f8f95 100644 --- a/gcrypt.c +++ b/gcrypt.c @@ -114,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) @@ -406,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; @@ -456,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: @@ -564,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) @@ -624,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 8c4545b4..1376cf1d 100644 --- a/interactive.c +++ b/interactive.c @@ -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); @@ -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 ddf02d76..6ef7f8e2 100644 --- a/interactive.h +++ b/interactive.h @@ -84,8 +84,7 @@ 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/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_cmd.suite.m4 b/m4/lls/server_cmd.suite.m4 index f73d66b2..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 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/mood.c b/mood.c index a228b243..1e15ef0e 100644 --- a/mood.c +++ b/mood.c @@ -12,7 +12,6 @@ #include "afh.h" #include "afs.h" #include "list.h" -#include "mood.h" /* * Mood parser API. It's overkill to have an own header file for @@ -47,20 +46,31 @@ struct afs_statistics { /** Number of admissible files */ unsigned num; }; -static struct afs_statistics statistics = {.normalization_divisor = 1}; -struct mood { - /** The name of this mood. */ +/** + * Stores an instance of a loaded mood (parser and statistics). + * + * A structure of this type is allocated and initialized when a mood is loaded. + */ +struct mood_instance { + /** NULL means that this is the "dummy" mood. */ char *name; - /** Info for the bison parser. */ + /** 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. @@ -119,49 +129,47 @@ __a_const static uint64_t int_sqrt(uint64_t x) return res; } -/* 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) -{ - if (!m) - return -E_NO_MOOD; - return mp_eval_row(aft_row, m->parser_context); -} - -static void destroy_mood(struct mood *m) +static void destroy_mood(struct mood_instance *m) { if (!m) return; 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); + m->stats.normalization_divisor = 1; return m; } -static int load_mood(const struct osl_row *mood_row, struct mood **m, - char **errmsg) +static int init_mood_parser(const char *mood_name, struct mood_instance **m, + char **err) { - char *mood_name; struct osl_object mood_def; int ret; - ret = mood_get_name_and_def_by_row(mood_row, &mood_name, &mood_def); + if (!*mood_name) { + if (err) + *err = make_message("empty mood name\n"); + return -ERRNO_TO_PARA_ERROR(EINVAL); + } + ret = mood_get_def_by_name(mood_name, &mood_def); if (ret < 0) { - if (errmsg) - *errmsg = make_message( - "could not read mood definition"); + if (err) + *err = make_message("could not read mood definition\n"); return ret; } - assert(*mood_name); *m = alloc_new_mood(mood_name); - PARA_INFO_LOG("opening mood %s\n", mood_name); - ret = mp_init(mood_def.data, mood_def.size, &(*m)->parser_context, errmsg); + 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) destroy_mood(*m); @@ -170,14 +178,14 @@ static int load_mood(const struct osl_row *mood_row, struct mood **m, static int check_mood(struct osl_row *mood_row, void *data) { - struct para_buffer *pb = data; + struct afs_callback_arg *aca = data; char *mood_name, *errmsg; struct osl_object mood_def; - struct mood *m; + 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 */ @@ -186,9 +194,9 @@ static int check_mood(struct osl_row *mood_row, void *data) 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); + afs_error(aca, "%s: %s\n%s\n", mood_name, errmsg, + para_strerror(-ret)); free(errmsg); - para_printf(pb, "%s\n", para_strerror(-ret)); } else destroy_mood(m); ret = 1; /* don't fail the loop on invalid mood definitions */ @@ -200,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. @@ -208,8 +216,7 @@ 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)); } /* @@ -249,23 +256,27 @@ int mood_check_callback(struct afs_callback_arg *aca) * overflows and rounding errors we store the common divisor of the * correction factors separately. */ -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) +static long compute_score(struct afs_info *afsi, + const struct afs_statistics *stats) { - long score = -normalized_value(afsi->num_played, statistics.num, - statistics.num_played_sum, statistics.num_played_qd); - score -= normalized_value(afsi->last_played, statistics.num, - statistics.last_played_sum, statistics.last_played_qd); - return score / 2; + 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; @@ -274,64 +285,65 @@ 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)); - statistics.normalization_divisor = 1; + 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 + * 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 @@ -341,7 +353,7 @@ static int del_afs_statistics(const struct osl_row *row) */ 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. */ @@ -355,19 +367,18 @@ struct admissible_array { static int add_if_admissible(struct osl_row *aft_row, void *data) { struct admissible_array *aa = data; - int ret; + struct afs_statistics *stats = &aa->m->stats; - ret = row_is_admissible(aft_row, aa->m); - 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 osl_row *)); + aa->array = arr_realloc(aa->array, aa->size, + sizeof(struct osl_row *)); } - aa->array[statistics.num] = aft_row; - return add_afs_statistics(aft_row); + aa->array[stats->num] = aft_row; + return add_afs_statistics(aft_row, stats); } /** @@ -412,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) +static int add_to_score_table(const struct osl_row *aft_row, + struct mood_instance *m) { long score; struct afs_info afsi; @@ -442,8 +449,8 @@ static int add_to_score_table(const struct osl_row *aft_row) if (ret < 0) return ret; - score = compute_score(&afsi); - 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) @@ -455,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); } @@ -490,95 +492,84 @@ 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); - 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); + 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); + 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); } /* sse: seconds since epoch. */ -static void log_statistics(int64_t sse) +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; - 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); - mean_days = (sse - 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: %" PRId64 "/%" PRIu64 "\n", - statistics.num_played_sum / n, - int_sqrt(statistics.num_played_qd / n)); - PARA_NOTICE_LOG("num_played correction factor: %" PRId64 "\n", - statistics.num_played_correction); - PARA_NOTICE_LOG("last_played correction factor: %" PRId64 "\n", - statistics.last_played_correction); - PARA_NOTICE_LOG("normalization divisor: %" PRId64 "\n", - statistics.normalization_divisor); + 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)); - statistics.normalization_divisor = 1; } -static void compute_correction_factors(int64_t sse) +static void compute_correction_factors(int64_t sse, struct afs_statistics *s) { - struct afs_statistics *s = &statistics; - if (s->num > 0) { s->normalization_divisor = int_sqrt(s->last_played_qd) * int_sqrt(s->num_played_qd) / s->num / 100; @@ -594,30 +585,45 @@ static void compute_correction_factors(int64_t sse) } /** - * Change the current mood. + * Populate a score table with admissible files for the given mood. * - * \param mood_name The name of the mood to open. - * \param errmsg Error description is returned here. + * 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. * - * 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. + * \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. * - * 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 the mood name is NULL, the dummy mood is loaded. This mood regards every + * audio file as admissible. * - * If there is already an open mood, it will be closed first. + * 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. * - * \return Positive on success, negative on errors. + * 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 the message pointer is not NULL, a suitable message is returned there in + * all cases. The caller must free this string. * - * \sa struct \ref afs_info::last_played, \ref mp_eval_row(). + * \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 \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". @@ -625,85 +631,86 @@ int change_current_mood(const char *mood_name, char **errmsg) struct timeval rnow; if (mood_name) { - struct mood *m; - struct osl_row *row; - struct osl_object obj; - - if (!*mood_name) { - *errmsg = make_message("empty mood name"); - return -ERRNO_TO_PARA_ERROR(EINVAL); - } - obj.data = (char *)mood_name; - obj.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"); + if (msg) /* false if we are called via the event handler */ + *msg = make_message("audio file loop failed\n"); goto out; } clock_get_realtime(&rnow); - compute_correction_factors(rnow.tv_sec); - log_statistics(rnow.tv_sec); - for (i = 0; i < statistics.num; i++) { - ret = add_to_score_table(aa.array[i]); + 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; } } - 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) - close_current_mood(); + 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 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. + * This wrapper around \ref score_loop() is the mood counterpart of \ref + * playlist_loop(). * - * If no mood is currently open, the function returns success. + * \param m 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 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; } @@ -715,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 c537e729..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; @@ -518,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; @@ -557,7 +557,7 @@ 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) { 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..5ca1307f --- /dev/null +++ b/mp4.c @@ -0,0 +1,1053 @@ +/* + * Copyright (C) 2003-2005 M. Bakker, Nero AG, http://www.nero.com + * FAAD2 - Freeware Advanced Audio (AAC) Decoder including SBR decoding + * + * See file COPYING. + */ + +/** \file mp4.c Paraslash's internal mp4 parser. */ + +/* + * This is a stripped down version of the former mp4ff library which used to be + * part of the faad decoder project but was removed from the faad code base in + * 2017. The original code has been cleaned up substantially and the public API + * has been documented. See the git commit log for details. + */ + +#include + +#include "para.h" +#include "error.h" +#include "portable_io.h" +#include "string.h" +#include "mp4.h" + +/** + * The three states of the mp4 parser. The parser only loads the audio specific + * values and tables when it is in the second state. + */ +enum audio_track_state { + /** We haven't encountered an mp4a atom so far. */ + ATS_INITIAL, + /** We have seen an mp4a atom but no subsequent trak atom yet. */ + ATS_SEEN_MP4A, + /** A trak atom was seen *after* the mp4a atom. */ + ATS_TRACK_CHANGE, +}; + +struct mp4_track { + /* determines which atoms we still need to parse. */ + enum audio_track_state state; + + /* mp4a */ + uint16_t channel_count; + uint16_t sample_rate; + + /* stsz */ + uint32_t stsz_sample_size; + uint32_t stsz_sample_count; + uint32_t *stsz_table; + + /* stts */ + uint32_t stts_entry_count; + uint32_t *stts_sample_count; + + /* stsc */ + uint32_t stsc_entry_count; + uint32_t *stsc_first_chunk; + uint32_t *stsc_samples_per_chunk; + + /* stsc */ + uint32_t stco_entry_count; + uint32_t *stco_chunk_offset; + + /* mdhd */ + uint32_t time_scale; + uint64_t duration; +}; + +struct mp4 { + const struct mp4_callback *cb; + + uint64_t moov_offset; + uint64_t moov_size; + uint64_t meta_offset; + uint32_t meta_size; + uint64_t ilst_offset; + uint32_t ilst_size; + uint64_t udta_offset; + uint32_t udta_size; + + uint8_t last_atom; + struct mp4_track track; + struct mp4_metadata meta; +}; + +/* + * Returns -E_MP4_READ, 0, or 1 on errors/EOF/success. Partial reads followed + * by EOF or read errors are treated as errors. + */ +static int read_data(struct mp4 *f, void *data, size_t size) +{ + while (size > 0) { + ssize_t ret = f->cb->read(f->cb->user_data, data, size); + if (ret < 0 && errno == EINTR) + continue; + /* regard EAGAIN as an error as reads should be blocking. */ + if (ret <= 0) + return ret < 0? -E_MP4_READ : 0; + size -= ret; + } + return 1; +} + +static int read_int64(struct mp4 *f, uint64_t *result) +{ + uint8_t data[8]; + int ret = read_data(f, data, 8); + + if (ret > 0) + *result = read_u64_be(data); + return ret; +} + +static int read_int32(struct mp4 *f, uint32_t *result) +{ + uint8_t data[4]; + int ret = read_data(f, data, 4); + + if (ret > 0) + *result = read_u32_be(data); + return ret; +} + +static int read_int16(struct mp4 *f, uint16_t *result) +{ + uint8_t data[2]; + int ret = read_data(f, data, 2); + + if (ret > 0) + *result = read_u16_be(data); + return ret; +} + +/** A macro defining the atoms we care about. It gets expanded twice. */ +#define ATOM_ITEMS \ + ATOM_ITEM(MOOV, 'm', 'o', 'o', 'v') /* movie (top-level container) */ \ + ATOM_ITEM(TRAK, 't', 'r', 'a', 'k') /* container for a single track */ \ + ATOM_ITEM(MDIA, 'm', 'd', 'i', 'a') /* media information */ \ + ATOM_ITEM(MINF, 'm', 'i', 'n', 'f') /* extends mdia */ \ + ATOM_ITEM(STBL, 's', 't', 'b', 'l') /* sample table container */ \ + ATOM_ITEM(UDTA, 'u', 'd', 't', 'a') /* user data */ \ + ATOM_ITEM(ILST, 'i', 'l', 's', 't') /* iTunes Metadata list */ \ + ATOM_ITEM(ARTIST, 0xa9, 'A', 'R', 'T') /* artist */ \ + ATOM_ITEM(TITLE, 0xa9, 'n', 'a', 'm') /* title */ \ + ATOM_ITEM(ALBUM, 0xa9, 'a', 'l', 'b') /* album */ \ + ATOM_ITEM(DATE, 0xa9, 'd', 'a', 'y') /* date */ \ + ATOM_ITEM(COMMENT, 0xa9, 'c', 'm', 't') /* comment */ \ + ATOM_ITEM(MDHD, 'm', 'd', 'h', 'd') /* track header */ \ + ATOM_ITEM(STSD, 's', 't', 's', 'd') /* sample description box */ \ + ATOM_ITEM(STTS, 's', 't', 't', 's') /* time to sample box */ \ + ATOM_ITEM(STSZ, 's', 't', 's', 'z') /* sample size box */ \ + ATOM_ITEM(STCO, 's', 't', 'c', 'o') /* chunk offset box */ \ + ATOM_ITEM(STSC, 's', 't', 's', 'c') /* sample to chunk box */ \ + ATOM_ITEM(MP4A, 'm', 'p', '4', 'a') /* mp4 audio */ \ + ATOM_ITEM(META, 'm', 'e', 't', 'a') /* iTunes Metadata box */ \ + ATOM_ITEM(DATA, 'd', 'a', 't', 'a') /* iTunes Metadata data box */ \ + +/** For the C enumeration we concatenate ATOM_ with the first argument. */ +#define ATOM_ITEM(_name, a, b, c, d) ATOM_ ## _name, +/** The enumeration of interesting atoms. */ +enum atom {ATOM_ITEMS}; +#undef ATOM_ITEM + +/** A cpp version of read_u32_be(). */ +#define ATOM_VALUE(a, b, c, d) ((a << 24) + (b << 16) + (c << 8) + d) + +static uint8_t atom_name_to_type(uint8_t *p) +{ + /** Expands to an instance of the following unnamed structure. */ + #define ATOM_ITEM(_name, a, b, c, d) \ + {.name = # _name, .val = ATOM_VALUE(a, b, c, d)}, + static const struct { + const char *name; + uint32_t val; + } atom_table[] = {ATOM_ITEMS}; + #undef ATOM_ITEM + uint32_t val = read_u32_be(p); + + for (uint8_t n = 0; n < ARRAY_SIZE(atom_table); n++) + if (val == atom_table[n].val) + return n; + return 255; +} + +/* read atom header, atom size is returned with header included. */ +static int atom_read_header(struct mp4 *f, uint8_t *atom_type, + uint8_t *header_size, uint64_t *atom_size) +{ + uint32_t size; + int ret; + uint8_t atom_header[8]; + + ret = read_data(f, atom_header, 8); + if (ret <= 0) + return ret; + size = read_u32_be(atom_header); + if (size == 1) { /* 64 bit atom size */ + if (header_size) + *header_size = 16; + ret = read_int64(f, atom_size); + if (ret <= 0) + return ret; + } else { + if (header_size) + *header_size = 8; + *atom_size = size; + } + *atom_type = atom_name_to_type(atom_header + 4); + return 1; +} + +static off_t get_position(const struct mp4 *f) +{ + return f->cb->seek(f->cb->user_data, 0, SEEK_CUR); +} + +static void set_position(struct mp4 *f, off_t position) +{ + f->cb->seek(f->cb->user_data, position, SEEK_SET); +} + +static void skip_bytes(struct mp4 *f, off_t num_skip) +{ + f->cb->seek(f->cb->user_data, num_skip, SEEK_CUR); +} + +static int read_stsz(struct mp4 *f) +{ + int ret; + struct mp4_track *t = &f->track; + + if (t->state != ATS_SEEN_MP4A || t->stsz_table) + return 1; + skip_bytes(f, 4); /* version (1), flags (3) */ + ret = read_int32(f, &t->stsz_sample_size); + if (ret <= 0) + return ret; + ret = read_int32(f, &t->stsz_sample_count); + if (ret <= 0) + return ret; + if (t->stsz_sample_size != 0) + return 1; + t->stsz_table = arr_alloc(t->stsz_sample_count, sizeof(int32_t)); + for (uint32_t n = 0; n < t->stsz_sample_count; n++) { + ret = read_int32(f, &t->stsz_table[n]); + if (ret <= 0) + return ret; + } + return 1; +} + +static int read_stts(struct mp4 *f) +{ + int ret; + struct mp4_track *t = &f->track; + + if (t->state != ATS_SEEN_MP4A || t->stts_sample_count) + return 1; + skip_bytes(f, 4); /* version (1), flags (3) */ + ret = read_int32(f, &t->stts_entry_count); + if (ret <= 0) + return ret; + t->stts_sample_count = arr_alloc(t->stts_entry_count, sizeof(int32_t)); + for (uint32_t n = 0; n < t->stts_entry_count; n++) { + ret = read_int32(f, &t->stts_sample_count[n]); + if (ret <= 0) + return ret; + skip_bytes(f, 4); /* sample delta */ + } + return 1; +} + +static int read_stsc(struct mp4 *f) +{ + int ret; + struct mp4_track *t = &f->track; + + if (t->state != ATS_SEEN_MP4A) + return 1; + if (t->stsc_first_chunk || t->stsc_samples_per_chunk) + return 1; + skip_bytes(f, 4); /* version (1), flags (3) */ + ret = read_int32(f, &t->stsc_entry_count); + if (ret <= 0) + return ret; + t->stsc_first_chunk = arr_alloc(t->stsc_entry_count, sizeof(int32_t)); + t->stsc_samples_per_chunk = arr_alloc(t->stsc_entry_count, + sizeof (int32_t)); + for (uint32_t n = 0; n < t->stsc_entry_count; n++) { + ret = read_int32(f, &t->stsc_first_chunk[n]); + if (ret <= 0) + return ret; + ret = read_int32(f, &t->stsc_samples_per_chunk[n]); + if (ret <= 0) + return ret; + skip_bytes(f, 4); /* sample desc index */ + } + return 1; +} + +static int read_stco(struct mp4 *f) +{ + int ret; + struct mp4_track *t = &f->track; + + if (t->state != ATS_SEEN_MP4A || t->stco_chunk_offset) + return 1; + skip_bytes(f, 4); /* version (1), flags (3) */ + ret = read_int32(f, &t->stco_entry_count); + if (ret <= 0) + return ret; + t->stco_chunk_offset = arr_alloc(t->stco_entry_count, sizeof(int32_t)); + for (uint32_t n = 0; n < t->stco_entry_count; n++) { + ret = read_int32(f, &t->stco_chunk_offset[n]); + if (ret <= 0) + return ret; + } + return 1; +} + +static int read_stsd(struct mp4 *f) +{ + int ret; + uint32_t entry_count; + + if (f->track.state != ATS_INITIAL) + return 1; + skip_bytes(f, 4); /* version (1), flags (3) */ + ret = read_int32(f, &entry_count); + if (ret <= 0) + return ret; + for (uint32_t n = 0; n < entry_count; n++) { + uint64_t skip = get_position(f); + uint64_t size; + uint8_t atom_type = 0; + ret = atom_read_header(f, &atom_type, NULL, &size); + if (ret <= 0) + return ret; + skip += size; + if (atom_type == ATOM_MP4A) { + f->track.state = ATS_SEEN_MP4A; + /* reserved (6), data reference index (2), reserved (8) */ + skip_bytes(f, 16); + ret = read_int16(f, &f->track.channel_count); + if (ret <= 0) + return ret; + skip_bytes(f, 6); + ret = read_int16(f, &f->track.sample_rate); + if (ret <= 0) + return ret; + } + set_position(f, skip); + } + return 1; +} + +static const char *get_metadata_name(uint8_t atom_type) +{ + switch (atom_type) { + case ATOM_TITLE: return "title"; + case ATOM_ARTIST: return "artist"; + case ATOM_ALBUM: return "album"; + case ATOM_DATE: return "date"; + case ATOM_COMMENT: return "comment"; + default: return "unknown"; + } +} + +static int parse_tag(struct mp4 *f, uint8_t parent, int32_t size) +{ + int ret; + uint64_t subsize, sumsize; + char *value = NULL; + uint32_t len = 0; + uint64_t destpos; + struct mp4_tag *tag; + + for ( + sumsize = 0; + sumsize < size; + set_position(f, destpos), sumsize += subsize + ) { + uint8_t atom_type; + uint8_t header_size = 0; + ret = atom_read_header(f, &atom_type, &header_size, &subsize); + if (ret <= 0) + goto fail; + destpos = get_position(f) + subsize - header_size; + if (atom_type != ATOM_DATA) + continue; + skip_bytes(f, 8); /* version (1), flags (3), reserved (4) */ + ret = -E_MP4_CORRUPT; + if (subsize < header_size + 8 || subsize > UINT_MAX) + goto fail; + len = subsize - (header_size + 8); + free(value); + value = alloc(len + 1); + ret = read_data(f, value, len); + if (ret <= 0) + goto fail; + value[len] = '\0'; + } + if (!value) + return -E_MP4_CORRUPT; + f->meta.tags = para_realloc(f->meta.tags, (f->meta.count + 1) + * sizeof(struct mp4_tag)); + tag = f->meta.tags + f->meta.count; + tag->item = para_strdup(get_metadata_name(parent)); + tag->value = value; + f->meta.count++; + return 1; +fail: + free(value); + return ret; +} + +static int read_mdhd(struct mp4 *f) +{ + int ret; + uint32_t version; + struct mp4_track *t = &f->track; + + if (t->state != ATS_INITIAL) + return 1; + ret = read_int32(f, &version); + if (ret <= 0) + return ret; + if (version == 1) { + skip_bytes(f, 16); /* creation time (8), modification time (8) */ + ret = read_int32(f, &t->time_scale); + if (ret <= 0) + return ret; + ret = read_int64(f, &t->duration); + if (ret <= 0) + return ret; + } else { /* version == 0 */ + uint32_t temp; + + skip_bytes(f, 8); /* creation time (4), modification time (4) */ + ret = read_int32(f, &t->time_scale); + if (ret <= 0) + return ret; + ret = read_int32(f, &temp); + if (ret <= 0) + return ret; + t->duration = (temp == (uint32_t) (-1))? + (uint64_t) (-1) : (uint64_t) (temp); + } + skip_bytes(f, 4); + return 1; +} + +static int read_ilst(struct mp4 *f, int32_t size) +{ + int ret; + uint64_t sumsize = 0; + + while (sumsize < size) { + uint8_t atom_type; + uint64_t subsize, destpos; + uint8_t header_size = 0; + ret = atom_read_header(f, &atom_type, &header_size, &subsize); + if (ret <= 0) + return ret; + destpos = get_position(f) + subsize - header_size; + switch (atom_type) { + case ATOM_ARTIST: + case ATOM_TITLE: + case ATOM_ALBUM: + case ATOM_COMMENT: + case ATOM_DATE: + ret = parse_tag(f, atom_type, subsize - header_size); + if (ret <= 0) + return ret; + } + set_position(f, destpos); + sumsize += subsize; + } + return 1; +} + +static int read_meta(struct mp4 *f, uint64_t size) +{ + int ret; + uint64_t subsize, sumsize = 0; + uint8_t atom_type; + uint8_t header_size = 0; + + skip_bytes(f, 4); /* version (1), flags (3) */ + while (sumsize < (size - (header_size + 4))) { + ret = atom_read_header(f, &atom_type, &header_size, &subsize); + if (ret <= 0) + return ret; + if (subsize <= header_size + 4) + return 1; + if (atom_type == ATOM_ILST) { + f->ilst_offset = get_position(f) - header_size; + f->ilst_size = subsize; + ret = read_ilst(f, subsize - (header_size + 4)); + if (ret <= 0) + return ret; + } else + set_position(f, get_position(f) + subsize - header_size); + sumsize += subsize; + } + return 1; +} + +static bool need_atom(uint8_t atom_type, bool meta_only) +{ + /* these are needed in any case */ + switch (atom_type) { + case ATOM_STSD: + case ATOM_META: + case ATOM_TRAK: + case ATOM_MDIA: + case ATOM_MINF: + case ATOM_STBL: + case ATOM_UDTA: + return true; + } + /* meta-only opens don't need anything else */ + if (meta_only) + return false; + /* these are only required for regular opens */ + switch (atom_type) { + case ATOM_STTS: + case ATOM_STSZ: + case ATOM_STCO: + case ATOM_STSC: + case ATOM_MDHD: + return true; + } + return false; +} + +/* parse atoms that are sub atoms of other atoms */ +static int parse_sub_atoms(struct mp4 *f, uint64_t total_size, bool meta_only) +{ + int ret; + uint64_t dest, size, end = get_position(f) + total_size; + + for (dest = get_position(f); dest < end; set_position(f, dest)) { + uint8_t header_size, atom_type; + ret = atom_read_header(f, &atom_type, &header_size, &size); + if (ret <= 0) + return ret; + if (size == 0) + return -E_MP4_CORRUPT; + dest = get_position(f) + size - header_size; + if (atom_type == ATOM_TRAK && f->track.state == ATS_SEEN_MP4A) { + f->track.state = ATS_TRACK_CHANGE; + continue; + } + if (atom_type == ATOM_UDTA) { + f->udta_offset = get_position(f) - header_size; + f->udta_size = size; + } + if (!need_atom(atom_type, meta_only)) + continue; + switch (atom_type) { + case ATOM_STSZ: ret = read_stsz(f); break; + case ATOM_STTS: ret = read_stts(f); break; + case ATOM_STSC: ret = read_stsc(f); break; + case ATOM_STCO: ret = read_stco(f); break; + case ATOM_STSD: ret = read_stsd(f); break; + case ATOM_MDHD: ret = read_mdhd(f); break; + case ATOM_META: + f->meta_offset = get_position(f) - header_size; + f->meta_size = size; + ret = read_meta(f, size); + break; + default: + ret = parse_sub_atoms(f, size - header_size, meta_only); + } + if (ret <= 0) + return ret; + } + return 1; +} + +/** + * Deallocate all resources associated with an mp4 file handle. + * + * \param f File handle returned by \ref mp4_open() or \ref mp4_open_meta(). + * + * This frees the metadata items and various tables which were allocated when + * the file was opened. The given file handle must not be NULL. + */ +void mp4_close(struct mp4 *f) +{ + free(f->track.stsz_table); + free(f->track.stts_sample_count); + free(f->track.stsc_first_chunk); + free(f->track.stsc_samples_per_chunk); + free(f->track.stco_chunk_offset); + for (uint32_t n = 0; n < f->meta.count; n++) { + free(f->meta.tags[n].item); + free(f->meta.tags[n].value); + } + free(f->meta.tags); + free(f); +} + +static int open_file(const struct mp4_callback *cb, bool meta_only, struct mp4 **result) +{ + int ret; + uint64_t size; + uint8_t atom_type, header_size; + struct mp4 *f = zalloc(sizeof(*f)); + + f->cb = cb; + while ((ret = atom_read_header(f, &atom_type, &header_size, &size)) > 0) { + f->last_atom = atom_type; + if (atom_type != ATOM_MOOV || size <= header_size) { /* skip */ + set_position(f, get_position(f) + size - header_size); + continue; + } + f->moov_offset = get_position(f) - header_size; + f->moov_size = size; + ret = parse_sub_atoms(f, size - header_size, meta_only); + if (ret <= 0) + break; + } + if (ret < 0) + goto fail; + ret = -E_MP4_TRACK; + if (f->track.channel_count == 0) + goto fail; + ret = -E_MP4_BAD_SAMPLERATE; + if (f->track.sample_rate == 0) + goto fail; + ret = -E_MP4_MISSING_ATOM; + if (f->udta_size == 0 || f->meta_size == 0 || f->ilst_size == 0) + goto fail; + *result = f; + return 1; +fail: + *result = NULL; + mp4_close(f); + return ret; +} + +/** + * Read the audio track and the metadata of an mp4 file. + * + * \param cb Only the ->read() and ->seek() methods need to be supplied. + * \param result Initialized to a non-NULL pointer iff the function succeeds. + * + * This detects and parses the first audio track and the metadata information + * of the mp4 file. Various error checks are performed after the mp4 atoms have + * been parsed successfully. + * + * This function does not modify the file. However, if the caller intents to + * update the metadata later, the ->write() and ->truncate() methods must be + * supplied in the callback structure. + * + * \return Standard. Several errors are possible. + * + * \sa \ref mp4_open_meta(). + */ +int mp4_open(const struct mp4_callback *cb, struct mp4 **result) +{ + struct mp4 *f; + int ret; + + *result = NULL; + ret = open_file(cb, false, &f); + if (ret < 0) + return ret; + ret = -E_MP4_BAD_SAMPLE_COUNT; + if (f->track.stsz_sample_count == 0) + goto fail; + ret = -E_MP4_CORRUPT; + if (f->track.time_scale == 0) + goto fail; + *result = f; + return 1; +fail: + mp4_close(f); + return ret; +} + +static int32_t chunk_of_sample(const struct mp4 *f, int32_t sample, + int32_t *chunk) +{ + const struct mp4_track *t = &f->track; + uint32_t *fc = t->stsc_first_chunk, *spc = t->stsc_samples_per_chunk; + uint32_t chunk1, chunk1samples, n, total, k; + + for (k = 1, total = 0; k < t->stsc_entry_count; k++, total += n) { + n = (fc[k] - fc[k - 1]) * spc[k - 1]; /* number of samples */ + if (sample < total + n) + break; + } + chunk1 = fc[k - 1]; + chunk1samples = spc[k - 1]; + if (chunk1samples != 0) + *chunk = (sample - total) / chunk1samples + chunk1; + else + *chunk = 1; + return total + (*chunk - chunk1) * chunk1samples; +} + +/** + * Compute the duration of an mp4 file. + * + * \param f See \ref mp4_close(). + * + * \return The number of milliseconds of the audio track. This function never + * fails. + */ +uint64_t mp4_get_duration(const struct mp4 *f) +{ + const struct mp4_track *t = &f->track; + + return t->duration * 1000 / t->time_scale; +} + +/** + * Reposition the read/write file offset. + * + * \param f See \ref mp4_close(). + * \param sample The number of the sample to reposition to. + * + * The given sample number must be within range, i.e., strictly less than the + * value returned by \ref mp4_num_samples(). + * + * \return Standard. The only possible error is an invalid sample number. + */ +int mp4_set_sample_position(struct mp4 *f, uint32_t sample) +{ + const struct mp4_track *t = &f->track; + int32_t offset, chunk, chunk_sample; + uint32_t n, srs; /* sample range size */ + + if (sample >= t->stsz_sample_count) + return -ERRNO_TO_PARA_ERROR(EINVAL); + chunk_sample = chunk_of_sample(f, sample, &chunk); + if (t->stsz_sample_size > 0) + srs = (sample - chunk_sample) * t->stsz_sample_size; + else { + for (srs = 0, n = chunk_sample; n < sample; n++) + srs += t->stsz_table[n]; + } + if (t->stco_entry_count > 0 && chunk > t->stco_entry_count) + offset = t->stco_chunk_offset[t->stco_entry_count - 1]; + else if (t->stco_entry_count > 0) + offset = t->stco_chunk_offset[chunk - 1]; + else + offset = 8; + set_position(f, offset + srs); + return 1; +} + +/** + * Look up and return the size of the given sample in the stsz table. + * + * \param f See \ref mp4_close(). + * \param sample The sample number of interest. + * \param result Sample size is returned here. + * + * For the sample argument the restriction mentioned in the documentation of + * \ref mp4_set_sample_position() applies as well. + * + * \return Standard. Like for \ref mp4_set_sample_position(), EINVAL is the + * only possible error. + */ +int mp4_get_sample_size(const struct mp4 *f, uint32_t sample, uint32_t *result) +{ + const struct mp4_track *t = &f->track; + + if (sample >= t->stsz_sample_count) + return -ERRNO_TO_PARA_ERROR(EINVAL); + if (t->stsz_sample_size != 0) + *result = t->stsz_sample_size; + else + *result = t->stsz_table[sample]; + return 1; +} + +/** + * Return the sample rate stored in the stsd atom. + * + * \param f See \ref mp4_close(). + * + * The sample rate is a property of the audio track of the mp4 file and is thus + * independent of the sample number. + * + * \return The function always returns a positive value because the open + * operation fails if the sample rate happens to be zero. A typical value is + * 44100. + */ +uint16_t mp4_get_sample_rate(const struct mp4 *f) +{ + return f->track.sample_rate; +} + +/** + * Return the number of channels of the audio track. + * + * \param f See \ref mp4_close(). + * + * \return The returned channel count is guaranteed to be positive because the + * open operation fails if the mp4a atom is missing or contains a zero channel + * count. + */ +uint16_t mp4_get_channel_count(const struct mp4 *f) +{ + return f->track.channel_count; +} + +/** + * Return the number of samples of the audio track. + * + * \param f See \ref mp4_close(). + * + * \return The sample count is read from the stsz atom during open. + */ +uint32_t mp4_num_samples(const struct mp4 *f) +{ + return f->track.stsz_sample_count; +} + +/** + * Open an mp4 file in metadata-only mode. + * + * \param cb See \ref mp4_open(). + * \param result See \ref mp4_open(). + * + * This is similar to \ref mp4_open() but is cheaper because it only parses the + * metadata of the mp4 file. The only functions that can subsequently be called + * with the file handle returned here are \ref mp4_get_meta() and \ref + * mp4_update_meta(). + * + * \return Standard. + * + * \sa \ref mp4_open(). The comment about ->write() and ->truncate() applies to + * this function as well. + */ +int mp4_open_meta(const struct mp4_callback *cb, struct mp4 **result) +{ + struct mp4 *f; + int ret = open_file(cb, true, &f); + + if (ret < 0) + return ret; + *result = f; + return 1; +} + +/** + * Return the metadata of an mp4 file. + * + * \param f See \ref mp4_close(). + * + * The caller is allowed to add, delete or modify the entries of the returned + * structure with the intention to pass the modified version to \ref + * mp4_update_meta(). + * + * \return This never returns NULL, even if the file contains no metadata tag + * items. However, the meta count will be zero and the ->tags pointer NULL in + * this case. + */ +struct mp4_metadata *mp4_get_meta(struct mp4 *f) +{ + return &f->meta; +} + +/** Total length of an on-disk metadata tag. */ +#define TAG_LEN(_len) (24 + (_len)) +static void create_ilst(const struct mp4_metadata *meta, uint8_t *out) +{ + for (unsigned n = 0; n < meta->count; n++) { + struct mp4_tag *tag = meta->tags + n; + unsigned len = strlen(tag->value); + const char *atom_name; + + if (!strcasecmp(tag->item, "title")) + atom_name = "\xA9" "nam"; + else if (!strcasecmp(tag->item, "artist")) + atom_name = "\xA9" "ART"; + else if (!strcasecmp(tag->item, "album")) + atom_name = "\xA9" "alb"; + else if (!strcasecmp(tag->item, "date")) + atom_name = "\xA9" "day"; + else if (!strcasecmp(tag->item, "comment")) + atom_name = "\xA9" "cmt"; + else + assert(false); + write_u32_be(out, TAG_LEN(len)); + memcpy(out + 4, atom_name, 4); + write_u32_be(out + 8, 8 /* data atom header */ + + 8 /* flags + reserved */ + + len); + memcpy(out + 12, "data", 4); + write_u32_be(out + 16, 1); /* flags */ + write_u32_be(out + 20, 0); /* reserved */ + memcpy(out + 24, tag->value, len); + out += TAG_LEN(len); + } +} + +static void *modify_moov(struct mp4 *f, uint32_t *out_size) +{ + int ret; + uint64_t total_base = f->moov_offset + 8; + uint32_t total_size = f->moov_size - 8; + uint32_t new_ilst_size = 0; + void *out_buffer; + uint8_t *p_out; + int32_t size_delta; + uint32_t tmp; + + for (unsigned n = 0; n < f->meta.count; n++) + new_ilst_size += TAG_LEN(strlen(f->meta.tags[n].value)); + size_delta = new_ilst_size - (f->ilst_size - 8); + *out_size = total_size + size_delta; + out_buffer = alloc(*out_size); + p_out = out_buffer; + set_position(f, total_base); + ret = read_data(f, p_out, f->udta_offset - total_base); + if (ret <= 0) + return NULL; + p_out += f->udta_offset - total_base; + ret = read_int32(f, &tmp); + if (ret <= 0) + return NULL; + write_u32_be(p_out, tmp + size_delta); + p_out += 4; + ret = read_data(f, p_out, 4); + if (ret <= 0) + return NULL; + p_out += 4; + ret = read_data(f, p_out, f->meta_offset - f->udta_offset - 8); + if (ret <= 0) + return NULL; + p_out += f->meta_offset - f->udta_offset - 8; + ret = read_int32(f, &tmp); + if (ret <= 0) + return NULL; + write_u32_be(p_out, tmp + size_delta); + p_out += 4; + ret = read_data(f, p_out, 4); + if (ret <= 0) + return NULL; + p_out += 4; + ret = read_data(f, p_out, f->ilst_offset - f->meta_offset - 8); + if (ret <= 0) + return NULL; + p_out += f->ilst_offset - f->meta_offset - 8; + ret = read_int32(f, &tmp); + if (ret <= 0) + return NULL; + write_u32_be(p_out, tmp + size_delta); + p_out += 4; + ret = read_data(f, p_out, 4); + if (ret <= 0) + return NULL; + p_out += 4; + create_ilst(&f->meta, p_out); + p_out += new_ilst_size; + set_position(f, f->ilst_offset + f->ilst_size); + ret = read_data(f, p_out, total_size - (f->ilst_offset - total_base) + - f->ilst_size); + if (ret <= 0) + return NULL; + return out_buffer; +} + +static int write_data(struct mp4 *f, void *data, size_t size) +{ + while (size > 0) { + ssize_t ret = f->cb->write(f->cb->user_data, data, size); + if (ret < 0) { + if (errno == EINTR) + continue; + return -ERRNO_TO_PARA_ERROR(errno); + } + size -= ret; + } + return 1; +} + +/** + * Write back the modified metadata items to the mp4 file. + * + * This is the only public function which modifies the contents of an mp4 file. + * This is achieved by calling the ->write() and ->truncate() methods of the + * callback structure passed to \ref mp4_open() or \ref mp4_open_meta(). + * + * \param f See \ref mp4_close(). + * + * The modified metadata structure does not need to be supplied to this + * function because it is part of the mp4 structure. + * + * \return Standard. + */ +int mp4_update_meta(struct mp4 *f) +{ + void *new_moov_data; + uint32_t new_moov_size; + uint8_t buf[8] = "----moov"; + int ret; + + set_position(f, 0); + new_moov_data = modify_moov(f, &new_moov_size); + if (!new_moov_data ) { + mp4_close(f); + return 0; + } + if (f->last_atom != ATOM_MOOV) { + set_position(f, f->moov_offset + 4); + ret = write_data(f, "free", 4); /* rename old moov to free */ + if (ret < 0) + goto free_moov; + /* write new moov atom at EOF */ + f->cb->seek(f->cb->user_data, 0, SEEK_END); + } else /* overwrite old moov atom */ + set_position(f, f->moov_offset); + write_u32_be(buf, new_moov_size + 8); + ret = write_data(f, buf, sizeof(buf)); + if (ret < 0) + goto free_moov; + ret = write_data(f, new_moov_data, new_moov_size); + if (ret < 0) + goto free_moov; + ret = f->cb->truncate(f->cb->user_data); + if (ret < 0) + ret = -ERRNO_TO_PARA_ERROR(errno); +free_moov: + free(new_moov_data); + return ret; +} + +/** + * Return the value of the given tag item. + * + * \param f See \ref mp4_close(). + * \param item "artist", "title", "album", "comment", or "date". + * + * \return The function returns NULL if the given item is not in the above + * list. Otherwise, if the file does not contain a tag for the given item, the + * function also returns NULL. Otherwise a copy of the tag value is returned + * and the caller should free this memory when it is no longer needed. + */ +__malloc char *mp4_get_tag_value(const struct mp4 *f, const char *item) +{ + for (unsigned n = 0; n < f->meta.count; n++) + if (!strcasecmp(f->meta.tags[n].item, item)) + return para_strdup(f->meta.tags[n].value); + return NULL; +} diff --git a/mp4.h b/mp4.h new file mode 100644 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 e1951e5e..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,7 +297,7 @@ 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); 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 32891cbb..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,16 +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) { - SHA256_CTX c; - SHA256_Init(&c); - SHA256_Update(&c, data, len); - SHA256_Final(hash, &c); + 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 0565167c..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; @@ -218,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); @@ -245,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 14fac42f..bd183b6b 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. */ @@ -117,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)) @@ -192,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(); @@ -201,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; } @@ -238,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; } @@ -265,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)); @@ -288,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)) @@ -405,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); @@ -591,7 +583,7 @@ static char *get_user_key_map_seq(int key) if (!p) return NULL; len = p - kma; - result = para_malloc(len + 1); + result = alloc(len + 1); memcpy(result, kma, len); result[len] = '\0'; return result; @@ -611,7 +603,7 @@ static char *get_key_map_seq_safe(int key) if (len == 1 && isprint(*seq)) return seq; - sseq = para_malloc(2 + 2 * len + 1); + sseq = alloc(2 + 2 * len + 1); sseq[0] = '0'; sseq[1] = 'x'; for (n = 0; n < len; n++) { @@ -651,7 +643,7 @@ static char **get_mapped_keyseqs(void) char **result; int i; - result = para_malloc((NUM_MAPPED_KEYS + 1) * sizeof(char *)); + result = arr_alloc(NUM_MAPPED_KEYS + 1, sizeof(char *)); FOR_EACH_MAPPED_KEY(i) { char *seq = get_key_map_seq(i); result[i] = seq; @@ -840,20 +832,21 @@ EXPORT_PLAY_CMD_HANDLER(play); static int com_pause(__a_unused struct lls_parse_result *lpr) { char state; - long unsigned seconds, ss; + uint64_t ms; + unsigned long cn; /* chunk num */ state = get_playback_state(); pt->playing = false; if (state != 'P') return 0; - seconds = get_play_time(); + ms = get_play_time(); pt->playing = false; - ss = 0; + cn = 0; if (pt->seconds > 0) - ss = seconds * pt->num_chunks / pt->seconds + 1; - ss = PARA_MAX(ss, 0UL); - ss = PARA_MIN(ss, pt->num_chunks); - pt->start_chunk = ss; + cn = ms * pt->num_chunks / pt->seconds / 1000 + 1; + cn = PARA_MIN(cn, pt->num_chunks); + pt->start_chunk = cn; + pt->rq = CRT_REPOS; kill_stream(); return 0; } @@ -952,7 +945,7 @@ static int com_ff(struct lls_parse_result *lpr) return ret; if (pt->playing && !pt->fn.btrn) return 0; - seconds += get_play_time(); + seconds += (get_play_time() + 500) / 1000; seconds = PARA_MIN(seconds, (typeof(seconds))pt->seconds - 4); seconds = PARA_MAX(seconds, 0); pt->start_chunk = pt->num_chunks * seconds / pt->seconds; @@ -1055,9 +1048,9 @@ static void session_open(void) char *dot_para = make_message("%s/.paraslash", home); free(home); - ret = para_mkdir(dot_para, 0777); + ret = para_mkdir(dot_para); /* warn, but otherwise ignore mkdir error */ - if (ret < 0 && ret != -ERRNO_TO_PARA_ERROR(EEXIST)) + 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); @@ -1074,7 +1067,7 @@ static void session_open(void) sigemptyset(&act.sa_mask); act.sa_flags = 0; sigaction(SIGWINCH, &act, NULL); - sched.select_function = i9e_select; + sched.poll_function = i9e_poll; ici.bound_keyseqs = get_mapped_keyseqs(); pt->btrn = ici.producer = btr_new_node(&(struct btr_node_description) @@ -1108,16 +1101,16 @@ static void session_update_time_string(char *str, unsigned 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; @@ -1140,11 +1133,11 @@ static int session_post_select(__a_unused struct sched *s) #else /* HAVE_READLINE */ -static int session_post_select(struct sched *s) +static int session_post_monitor(struct sched *s) { char c; - if (!FD_ISSET(STDIN_FILENO, &s->rfds)) + if (!sched_read_ok(STDIN_FILENO, s)) return 0; if (read(STDIN_FILENO, &c, 1)) do_nothing; @@ -1163,11 +1156,11 @@ static void session_update_time_string(char *str, __a_unused unsigned len) } #endif /* HAVE_READLINE */ -static void play_pre_select(struct sched *s, __a_unused void *context) +static void play_pre_monitor(struct sched *s, __a_unused void *context) { char state; - para_fd_set(STDIN_FILENO, &s->rfds, &s->max_fileno); + sched_monitor_readfd(STDIN_FILENO, s); state = get_playback_state(); if (state == 'R' || state == 'F' || state == 'X') return sched_min_delay(s); @@ -1187,7 +1180,7 @@ static unsigned get_time_string(char **result) length = pt->seconds; if (length == 0) return xasprintf(result, "0:00 [0:00] (0%%/0:00)"); - seconds = get_play_time(); + seconds = (get_play_time() + 500) / 1000; return xasprintf(result, "#%u: %d:%02d [%d:%02d] (%d%%/%d:%02d) %s", pt->current_file, seconds / 60, @@ -1201,7 +1194,7 @@ static unsigned get_time_string(char **result) ); } -static int play_post_select(struct sched *s, __a_unused void *context) +static int play_post_monitor(struct sched *s, __a_unused void *context) { int ret; @@ -1210,7 +1203,7 @@ static int play_post_select(struct sched *s, __a_unused void *context) pt->rq = CRT_TERM_RQ; return 0; } - ret = session_post_select(s); + ret = session_post_monitor(s); if (ret < 0) goto out; if (!pt->wn.btrn && !pt->fn.btrn) { @@ -1244,28 +1237,43 @@ out: /** * The main function of para_play. * - * \param argc Standard. - * \param argv Standard. + * \param argc See man page. + * \param argv See man page. + * + * para_play distributes its work by submitting various tasks to the paraslash + * scheduler. The receiver, filter and writer tasks, which are used to play an + * audio file, require one task each to maintain their underlying buffer tree + * node. These tasks only exist when an audio file is playing. + * + * The i9 task, which is submitted and maintained by the i9e subsystem, reads + * an input line and calls the corresponding command handler such as com_stop() + * which is implemented in this file. The command handlers typically write a + * request to the global play_task structure, whose contents are read and acted + * upon by another task, the play task. + * + * As a rule, playlist handling is performed exclusively in play context, i.e. + * in the post-monitor step of the play task, while command handlers are only + * called in i9e context. * - * \return \p EXIT_FAILURE or \p EXIT_SUCCESS. + * \return EXIT_FAILURE or EXIT_SUCCESS. */ int main(int argc, char *argv[]) { int ret; unsigned num_inputs; - sched.default_timeout.tv_sec = 5; + sched.default_timeout = 5000; parse_config_or_die(argc, argv); session_open(); num_inputs = lls_num_inputs(play_lpr); init_shuffle_map(); - pt->invalid = para_calloc(sizeof(*pt->invalid) * num_inputs); + pt->invalid = arr_zalloc(num_inputs, sizeof(*pt->invalid)); pt->rq = CRT_FILE_CHANGE; pt->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 9a801900..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; @@ -80,13 +80,13 @@ fail: 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 aac8efed..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,9 +227,9 @@ 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); @@ -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 894e8ca3..c03e3472 100644 --- a/score.c +++ b/score.c @@ -76,23 +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)); -} - /* On errors (negative return value) the content of score is undefined. */ -static int get_score_of_row(void *score_row, long *score) +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; @@ -100,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]; @@ -119,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); @@ -132,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. * @@ -156,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, @@ -167,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)); @@ -195,7 +177,7 @@ 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; @@ -206,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. - * - * \param data A pointer to arbitrary data. - * \param func Function to be called for each admissible file. + * Call the given function for each row of the score table. * - * \return The return value of the underlying call to osl_rbtree_loop(). + * \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. * - * 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,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); } /** @@ -265,7 +247,7 @@ int score_get_best(struct osl_row **aft_row, long *score) 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; @@ -276,67 +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; + return false; + assert(ret >= 0); + return true; } -static void score_close(void) +/** + * Free all volatile objects, then close the table. + * + * \param t As returned from \ref score_open(). + * + * This either succeeds or terminates the calling process. + */ +void score_close(struct osl_table *t) { - osl_close_table(score_table, OSL_FREE_VOLATILE); - score_table = NULL; + assert(osl_close_table(t? t : score_table, OSL_FREE_VOLATILE) >= 0); } -static int score_open(__a_unused const char *dir) +static void close_global_table(void) { - score_table_desc.dir = NULL; /* this table has only volatile columns */ - return osl(osl_open_table(&score_table_desc, &score_table)); + score_close(NULL); } -/** - * Remove all entries from the score table, but keep the table open. - * - * \return Standard. - */ -int clear_score_table(void) +static int open_global_table(__a_unused const char *dir) { - score_close(); - return score_open(NULL); + assert(osl(osl_open_table(&score_table_desc, &score_table)) >= 0); + return 1; } -static int score_event_handler(__a_unused enum afs_events event, - __a_unused struct para_buffer *pb, __a_unused void *data) +/** + * Allocate a score table instance. + * + * \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_open(struct osl_table **result) { - return 1; + if (result) + assert(osl(osl_open_table(&score_table_desc, result)) >= 0); + else + open_global_table(NULL); } /** - * Initialize the scoring subsystem. - * - * \param t The members of \a t are filled in by the function. + * Remove all entries from the score table, but keep the table open. */ -void score_init(struct afs_table *t) +void score_clear(void) { - t->name = score_table_desc.name; - t->open = score_open; - t->close = score_close; - t->event_handler = score_event_handler; + 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 90242d5c..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. */ @@ -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)); @@ -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 cb32d4d9..ea9cc9c0 100644 --- a/server.c +++ b/server.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 "config.h" #include "close_on_fork.h" @@ -172,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); @@ -180,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. * @@ -205,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(); @@ -250,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; @@ -313,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, @@ -337,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++; @@ -383,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; @@ -399,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; @@ -407,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, @@ -431,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)); @@ -458,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); /* @@ -542,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)); @@ -616,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; } @@ -657,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); @@ -678,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 10379a0e..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 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 *para_malloc(size_t size); -__must_check __malloc void *para_calloc(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 8e9ff2c5..20db1b1d 100644 --- a/sync_filter.c +++ b/sync_filter.c @@ -102,7 +102,7 @@ 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)); + ctx = fn->private_data = zalloc(sizeof(*ctx)); init_list_head(&ctx->buddies); /* create socket to listen for incoming packets */ @@ -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,7 @@ success: ret = -E_SYNC_COMPLETE; /* success */ goto out; fail: - if (ret != -E_BTR_EOF) + if (ret != -E_EOF) PARA_WARNING_LOG("%s\n", para_strerror(-ret)); out: sync_close_buddies(ctx); @@ -377,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 68d75e3c..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" @@ -187,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; @@ -326,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; @@ -336,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; 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 f9bf57b5..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; @@ -671,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; @@ -827,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, @@ -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)) @@ -1014,13 +1014,8 @@ err: } /** - * 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) { @@ -1087,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; @@ -1137,8 +1132,8 @@ 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)) { + 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) @@ -1147,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())) @@ -1194,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); } 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/manual.md b/web/manual.md index 78834a4d..b5329ea0 100644 --- a/web/manual.md +++ b/web/manual.md @@ -309,8 +309,8 @@ repository with git clone git://git.tuebingen.mpg.de/lopsub - [gcc](ftp://ftp.gnu.org/pub/gnu/gcc) or -[clang](http://clang.llvm.org). All gcc versions >= 4.2 are currently -supported. Clang version 1.1 or newer should work as well. +[clang](http://clang.llvm.org). All gcc versions >= 5.4 are currently +supported. Moderately recent versions of clang should work as well. - [gnu make](ftp://ftp.gnu.org/pub/gnu/make) is also shipped with the disto. On BSD systems the gnu make executable is often called gmake. diff --git a/wma_afh.c b/wma_afh.c index 63e49677..8bff7cfc 100644 --- a/wma_afh.c +++ b/wma_afh.c @@ -195,7 +195,7 @@ static int wma_make_chunk_table(char *buf, size_t buf_size, uint32_t packet_size size_t ct_size = 250; int ret, count = 0, num_frames, num_superframes; - afhi->chunk_table = para_malloc(ct_size * sizeof(uint32_t)); + afhi->chunk_table = arr_alloc(ct_size, sizeof(uint32_t)); afhi->chunk_table[0] = 0; afhi->chunk_table[1] = afhi->header_len; @@ -318,7 +318,7 @@ static int convert_utf8_to_utf16(char *src, char **dst) int ret; if (!src || !*src) { - *dst = para_calloc(2); + *dst = zalloc(2); return 0; } /* @@ -334,7 +334,7 @@ static int convert_utf8_to_utf16(char *src, char **dst) /* even though src is in UTF-8, strlen() should DTRT */ inbytes = inbytesleft = strlen(src); outbytes = outbytesleft = 4 * inbytes + 2; /* hope that's enough */ - *dst = outbuf = para_malloc(outbytes); + *dst = outbuf = alloc(outbytes); sz = iconv(cd, ICONV_CAST &inbuf, &inbytesleft, &outbuf, &outbytesleft); if (sz == (size_t)-1) { ret = -ERRNO_TO_PARA_ERROR(errno); @@ -411,7 +411,7 @@ static int make_cdo(struct taginfo *tags, const struct asf_object *cdo, result->size = 16 + 8 + 5 * 2 + title_bytes + artist_bytes + orig_cr_bytes + comment_bytes + orig_rating_bytes; PARA_DEBUG_LOG("cdo is %zu bytes\n", (size_t)result->size); - p = result->ptr = para_malloc(result->size); + p = result->ptr = alloc(result->size); memcpy(p, content_description_header, 16); p += 16; write_u64(p, result->size); @@ -469,7 +469,7 @@ static int make_ecdo(struct taginfo *tags, struct asf_object *result) result->size += 2 + sizeof(album_tag_header) + 2 + 2 + 2 + album_bytes; result->size += 2 + sizeof(year_tag_header) + 2 + 2 + 2 + year_bytes; - p = result->ptr = para_malloc(result->size); + p = result->ptr = alloc(result->size); memcpy(p, extended_content_header, 16); p += 16; write_u64(p, result->size); @@ -622,7 +622,7 @@ static int wma_rewrite_tags(const char *map, size_t mapsize, if (top.reserved2 != 2) return -E_NO_WMA; p++; /* objects start at p */ - top.objects = para_malloc(top.num_objects * sizeof(struct asf_object)); + top.objects = arr_alloc(top.num_objects, sizeof(struct asf_object)); ret = read_asf_objects(p, top.size - (p - map), top.num_objects, top.objects, &ton); if (ret < 0) diff --git a/wmadec_filter.c b/wmadec_filter.c index edf50cb0..f2ca273c 100644 --- a/wmadec_filter.c +++ b/wmadec_filter.c @@ -16,7 +16,6 @@ #include #include -#include #include "para.h" #include "error.h" @@ -160,8 +159,8 @@ static void init_coef_vlc(struct private_wmadec_data *pwd, int sidx, int didx) int i, l, j, k, level, n = src->n; init_vlc(dst, VLCBITS, n, src->huffbits, src->huffcodes, 4); - pwd->run_table[didx] = para_malloc(n * sizeof(uint16_t)); - pwd->level_table[didx] = para_malloc(n * sizeof(uint16_t)); + pwd->run_table[didx] = arr_alloc(n, sizeof(uint16_t)); + pwd->level_table[didx] = arr_alloc(n, sizeof(uint16_t)); i = 2; level = 1; k = 0; @@ -428,7 +427,7 @@ static int wma_decode_init(char *initial_buf, int len, struct private_wmadec_dat int ret, i; PARA_NOTICE_LOG("initial buf: %d bytes\n", len); - pwd = para_calloc(sizeof(*pwd)); + pwd = zalloc(sizeof(*pwd)); ret = read_asf_header(initial_buf, len, &pwd->ahi); if (ret <= 0) { free(pwd); @@ -1159,7 +1158,7 @@ static int wmadec_execute(struct btr_node *btrn, const char *cmd, char **result) #define WMA_OUTPUT_BUFFER_SIZE (128 * 1024) -static int wmadec_post_select(__a_unused struct sched *s, void *context) +static int wmadec_post_monitor(__a_unused struct sched *s, void *context) { struct filter_node *fn = context; int ret, converted, out_size; @@ -1177,7 +1176,7 @@ next_buffer: return 0; btr_merge(btrn, fn->min_iqs); len = btr_next_buffer(btrn, &in); - ret = -E_WMADEC_EOF; + ret = -E_EOF; if (len < fn->min_iqs) goto err; if (!pwd) { @@ -1197,7 +1196,7 @@ next_buffer: if (fn->min_iqs > len) goto success; out_size = WMA_OUTPUT_BUFFER_SIZE; - out = para_malloc(out_size); + out = alloc(out_size); ret = wma_decode_superframe(pwd, out, &out_size, (uint8_t *)in + WMA_FRAME_SKIP); if (ret < 0) { @@ -1229,6 +1228,6 @@ const struct filter lsg_filter_cmd_com_wmadec_user_data = { .open = wmadec_open, .close = wmadec_close, .execute = wmadec_execute, - .pre_select = generic_filter_pre_select, - .post_select = wmadec_post_select, + .pre_monitor = generic_filter_pre_monitor, + .post_monitor = wmadec_post_monitor, }; diff --git a/write.c b/write.c index acfb9460..cb32d391 100644 --- a/write.c +++ b/write.c @@ -54,16 +54,16 @@ struct write_task { struct check_wav_context *cwc; }; -static void write_pre_select(struct sched *s, void *context) +static void write_pre_monitor(struct sched *s, void *context) { struct write_task *wt = context; - check_wav_pre_select(s, wt->cwc); + check_wav_pre_monitor(s, wt->cwc); } -static int write_post_select(__a_unused struct sched *s, void *context) +static int write_post_monitor(__a_unused struct sched *s, void *context) { struct write_task *wt = context; - return check_wav_post_select(wt->cwc); + return check_wav_post_monitor(wt->cwc); } static int setup_and_schedule(struct lls_parse_result *lpr) @@ -83,21 +83,20 @@ static int setup_and_schedule(struct lls_parse_result *lpr) wt.cwc = check_wav_init(sit.btrn, NULL, &wp, &cw_btrn); wt.task = task_register(&(struct task_info) { .name = "write", - .pre_select = write_pre_select, - .post_select = write_post_select, + .pre_monitor = write_pre_monitor, + .post_monitor = write_post_monitor, .context = &wt, }, &s); n = writer_given? writer_given : 1; - wns = para_calloc(n * sizeof(*wns)); + wns = arr_zalloc(n, sizeof(*wns)); for (i = 0; i < n; i++) { const char *arg = i < writer_given? lls_string_val(i, OPT_RESULT(WRITER, lpr)) : NULL; wns[i].wid = check_writer_arg_or_die(arg, &wns[i].lpr); register_writer_node(wns + i, cw_btrn, &s); } - s.default_timeout.tv_sec = 10; - s.default_timeout.tv_usec = 50000; + s.default_timeout = 10500; ret = schedule(&s); if (ret >= 0) { int j, ts; @@ -105,7 +104,7 @@ static int setup_and_schedule(struct lls_parse_result *lpr) struct writer_node *wn = wns + j; ts = task_status(wn->task); assert(ts < 0); - if (ts != -E_WRITE_COMMON_EOF && ts != -E_BTR_EOF) { + if (ts != -E_EOF) { const char *name = writer_name(wn->wid); PARA_ERROR_LOG("%s: %s\n", name, para_strerror(-ts)); diff --git a/write.h b/write.h index 833cb69a..35a8d29f 100644 --- a/write.h +++ b/write.h @@ -20,21 +20,23 @@ struct writer_node { size_t min_iqs; }; -/** Describes one supported writer. */ +/** + * Describes a data sink for audio streams. + * + * A paraslash writer obtains data via the buffer tree mechanism from its + * parent node. It consumes data without producing output on its own. + * + * This structure contains the methods which have to be implemented by each + * writer. + * + * \sa struct \ref writer_node, struct \ref receiver, struct \ref filter, + * struct \ref sched. + */ struct writer { - /** - * Prepare the fd sets for select. - * - * This is called from scheduler. It may use the sched pointer to add - * any file descriptors or to decrease the select timeout. - */ - void (*pre_select)(struct sched *s, void *context); - /** - * Write audio data. - * - * Called from the post_select function of the writer node's task. - */ - int (*post_select)(struct sched *s, void *context); + /** Ask the scheduler to check whether data can be written. */ + void (*pre_monitor)(struct sched *s, void *context); + /** Write audio data. */ + int (*post_monitor)(struct sched *s, void *context); /** * Close one instance of the writer. * diff --git a/write_common.c b/write_common.c index 41c3eb23..9a130905 100644 --- a/write_common.c +++ b/write_common.c @@ -85,7 +85,7 @@ int check_writer_arg_or_die(const char *wa, struct lls_parse_result **lprp) if (!wa || !*wa) { writer_num = default_writer_id(); cmd = WRITE_CMD(writer_num); - argv = para_malloc(2 * sizeof(char *)); + argv = alloc(2 * sizeof(char *)); argc = 1; argv[0] = para_strdup(lls_command_name(cmd)); argv[1] = NULL; @@ -139,8 +139,8 @@ void register_writer_node(struct writer_node *wn, struct btr_node *parent, .handler = w->execute, .context = wn)); wn->task = task_register(&(struct task_info) { .name = writer_name(wn->wid), - .pre_select = w->pre_select, - .post_select = w->post_select, + .pre_monitor = w->pre_monitor, + .post_monitor = w->post_monitor, .context = wn, }, s); } diff --git a/yy/mp.y b/yy/mp.y index 06d76101..8df4f20e 100644 --- a/yy/mp.y +++ b/yy/mp.y @@ -59,7 +59,7 @@ enum semantic_types { static struct mp_ast_node *ast_node_raw(int id) { - struct mp_ast_node *node = para_malloc(sizeof(struct mp_ast_node)); + struct mp_ast_node *node = alloc(sizeof(struct mp_ast_node)); node->id = id; return node; } @@ -76,7 +76,7 @@ static struct mp_ast_node *ast_node_new_unary(int id, struct mp_ast_node *child) { struct mp_ast_node *node = ast_node_raw(id); node->num_children = 1; - node->children = para_malloc(sizeof(struct mp_ast_node *)); + node->children = alloc(sizeof(struct mp_ast_node *)); node->children[0] = child; return node; } @@ -86,7 +86,7 @@ static struct mp_ast_node *ast_node_new_binary(int id, struct mp_ast_node *left, { struct mp_ast_node *node = ast_node_raw(id); node->num_children = 2; - node->children = para_malloc(2 * sizeof(struct mp_ast_node *)); + node->children = arr_alloc(2, sizeof(struct mp_ast_node *)); node->children[0] = left; node->children[1] = right; return node;