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=06edb176d8fbbbe8323fe2f4f6bfda7153a66782 paraslash 0.7.3 --- diff --git a/Doxyfile b/Doxyfile index e147548f..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 @@ -2086,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 #--------------------------------------------------------------------------- @@ -2105,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. @@ -2341,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 95b68630..bd2bd9d9 100644 --- a/Makefile.real +++ b/Makefile.real @@ -21,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 := 2023 +COPYRIGHT_YEAR := 2024 ifeq ("$(origin O)", "command line") build_dir := $(O) @@ -119,6 +119,7 @@ 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 @@ -133,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 @@ -141,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 \ @@ -156,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 diff --git a/NEWS.md b/NEWS.md index fff7a242..d5812289 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,9 +1,54 @@ NEWS ==== ------------------------------------------- -0.7.2 (to be announced) "optical friction" ------------------------------------------- +--------------------------------------------- +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 @@ -14,9 +59,12 @@ NEWS - 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-git.tar.xz) +[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" diff --git a/afh_recv.c b/afh_recv.c index eebac67f..8449e787 100644 --- a/afh_recv.c +++ b/afh_recv.c @@ -205,7 +205,7 @@ static int afh_recv_post_monitor(__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_monitor(__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++; diff --git a/afs.c b/afs.c index b15f8385..445d5871 100644 --- a/afs.c +++ b/afs.c @@ -437,18 +437,18 @@ static int activate_mood_or_playlist(const char *arg, struct para_buffer *pb) int ret; char *msg; - if (!arg) { - ret = mood_load(NULL, &msg); + 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, &msg); + ret = playlist_load(arg + 2, NULL, &msg); mode = PLAY_MODE_PLAYLIST; } else if (!strncmp(arg, "m/", 2)) { - ret = mood_load(arg + 2, &msg); + ret = mood_load(arg + 2, NULL, &msg); mode = PLAY_MODE_MOOD; } else { ret = -ERRNO_TO_PARA_ERROR(EINVAL); - msg = make_message("%s: parse error", arg); + msg = make_message("%s: parse error\n", arg); } if (pb) para_printf(pb, "%s", msg); @@ -529,8 +529,8 @@ static void init_admissible_files(const char *arg) { 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); } @@ -580,17 +580,6 @@ 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; @@ -962,7 +951,8 @@ __noreturn void afs_init(int socket_fd) } ret = schedule(&s); sched_shutdown(&s); - mood_unload(); + mood_unload(NULL); + playlist_unload(NULL); out_close: close_afs_tables(); out: @@ -980,29 +970,32 @@ 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(); + mood_unload(NULL); else - playlist_unload(); - ret = activate_mood_or_playlist(arg, &aca->pbout); + 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, &aca->pbout); + 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, &aca->pbout); + activate_mood_or_playlist(NULL, pbout); free_lpr: lls_free_parse_result(aca->lpr, cmd); return ret; @@ -1018,7 +1011,8 @@ static int com_select(struct command_context *cc, struct lls_parse_result *lpr) send_errctx(cc, errctx); return ret; } - return send_lls_callback_request(com_select_callback, cmd, lpr, cc); + 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); @@ -1060,7 +1054,8 @@ 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) { diff --git a/afs.h b/afs.h index 9a1d7d9c..e8b8c865 100644 --- a/afs.h +++ b/afs.h @@ -238,10 +238,12 @@ int for_each_matching_row(struct pattern_match_data *pmd); /* score */ extern const struct afs_table_operations score_ops; -int score_loop(osl_rbtree_loop_func *func, void *data); +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 score_delete(const struct osl_row *aft_row); void score_clear(void); @@ -268,13 +270,17 @@ int aft_check_callback(struct afs_callback_arg *aca); void free_status_items(void); /* mood */ -int mood_load(const char *mood_name, char **msg); -void mood_unload(void); +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_load(const char *name, char **msg); -void playlist_unload(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 */ diff --git a/aft.c b/aft.c index 0009a54f..f1aca7fb 100644 --- a/aft.c +++ b/aft.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -799,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; @@ -806,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; @@ -912,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", @@ -1066,8 +1068,8 @@ again: if (ret < 0) return ret; if (!d->hash) - d->hash = alloc(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) @@ -1101,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; @@ -1360,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 = score_loop(prepare_ls_row, opts); - 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; } @@ -1389,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); @@ -1443,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 { @@ -1637,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); @@ -1649,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; @@ -1705,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; @@ -1712,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); @@ -1903,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; diff --git a/alsa_write.c b/alsa_write.c index 92d6cb70..53d7b1b4 100644 --- a/alsa_write.c +++ b/alsa_write.c @@ -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 */ diff --git a/amp_filter.c b/amp_filter.c index 360e3fc2..be69ad67 100644 --- a/amp_filter.c +++ b/amp_filter.c @@ -67,7 +67,7 @@ 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; } diff --git a/ao_write.c b/ao_write.c index 950f8f73..43a58dd6 100644 --- a/ao_write.c +++ b/ao_write.c @@ -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; } /* @@ -388,7 +388,7 @@ static int aow_post_monitor(__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; diff --git a/audioc.c b/audioc.c index d6c14cbc..f2e4cb91 100644 --- a/audioc.c +++ b/audioc.c @@ -175,7 +175,7 @@ static int audioc_post_monitor(struct sched *s, void *context) 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); diff --git a/audiod.c b/audiod.c index 0e8e5981..7c223995 100644 --- a/audiod.c +++ b/audiod.c @@ -290,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 */ @@ -958,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; diff --git a/audiod.h b/audiod.h index dedb038f..39beda1b 100644 --- a/audiod.h +++ b/audiod.h @@ -15,7 +15,7 @@ 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 */ diff --git a/audiod_command.c b/audiod_command.c index 743d73a9..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; @@ -121,7 +106,6 @@ static int stat_client_add(int fd, uint64_t mask, int parser_friendly) 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,7 +164,6 @@ 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); @@ -284,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}; @@ -296,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); diff --git a/buffer_tree.c b/buffer_tree.c index 05cdfa83..35353f56 100644 --- a/buffer_tree.c +++ b/buffer_tree.c @@ -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/client.c b/client.c index a6aee0f8..84b7580c 100644 --- a/client.c +++ b/client.c @@ -666,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 95e59fd2..fe8234f9 100644 --- a/client_common.c +++ b/client_common.c @@ -262,7 +262,7 @@ static bool has_feature(const char *feature, struct client_task *ct) return false; for (int i = 0; ct->features[i]; i++) if (strcmp(feature, ct->features[i]) == 0) - return i; + return true; return false; } @@ -344,15 +344,18 @@ static int client_post_monitor(struct sched *s, void *context) goto out; 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; @@ -398,12 +401,12 @@ static int client_post_monitor(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; @@ -578,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); } diff --git a/command.c b/command.c index c56a1582..60c2aeba 100644 --- a/command.c +++ b/command.c @@ -710,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) { @@ -718,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) @@ -824,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; diff --git a/compress_filter.c b/compress_filter.c index a6a00191..1bce35f5 100644 --- a/compress_filter.c +++ b/compress_filter.c @@ -59,7 +59,7 @@ 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; diff --git a/configure.ac b/configure.ac index c7258111..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], 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 d7471235..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; diff --git a/error.h b/error.h index 7c146da2..8805c9c7 100644 --- a/error.h +++ b/error.h @@ -19,13 +19,11 @@ 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"), \ @@ -36,7 +34,6 @@ 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,6 +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_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"), \ @@ -64,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"), \ @@ -72,7 +69,6 @@ PARA_ERROR(CLIENT_SYNTAX, "syntax error"), \ PARA_ERROR(CLIENT_WRITE, "client write error"), \ PARA_ERROR(COMMAND_SYNTAX, "syntax error in command"), \ - PARA_ERROR(COMPRESS_EOF, "compress: end of file"), \ PARA_ERROR(CREATE_OPUS_DECODER, "could not create opus decoder"), \ PARA_ERROR(DCCP_OVERRUN, "dccp output buffer buffer overrun"), \ PARA_ERROR(DECRYPT, "decrypt error"), \ @@ -83,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"), \ @@ -96,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"), \ @@ -114,7 +108,6 @@ PARA_ERROR(HEADER_BITRATE, "invalid header bitrate"), \ PARA_ERROR(HEADER_FREQ, "invalid header frequency"), \ PARA_ERROR(HTTP_RECV_OVERRUN, "http_recv: output buffer overrun"), \ - PARA_ERROR(I9E_EOF, "end of input"), \ PARA_ERROR(I9E_SETUPTERM, "failed to set up terminal"), \ PARA_ERROR(I9E_TERM_RQ, "received termination request"), \ PARA_ERROR(ID3_ATTACH, "could not attach id3 frame"), \ @@ -133,7 +126,6 @@ PARA_ERROR(MISSING_COLON, "syntax error: missing colon"), \ 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(MP4_READ, "mp4: read error or unexpected end of file"), \ PARA_ERROR(MP4_CORRUPT, "invalid/corrupt mp4 file"), \ @@ -173,10 +165,8 @@ 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"), \ @@ -186,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"), \ @@ -230,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 @@ -304,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 763f756c..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, ...) { @@ -250,64 +260,44 @@ int read_nonblock(int fd, void *buf, size_t sz, size_t *num_bytes) } /** - * Read a buffer and check its content for a pattern. - * - * \param fd The file descriptor to receive from. - * \param pattern The expected pattern. - * \param bufsize The size of the internal buffer. + * Read a buffer and compare its contents to a string, ignoring case. * - * 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) +int read_and_compare(int fd, const char *expectation) { - size_t n, len; - char *buf = alloc(bufsize + 1); - int ret = read_nonblock(fd, buf, bufsize, &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); -} - /** * Set a file descriptor to blocking mode. * @@ -397,86 +387,32 @@ int para_open(const char *path, int flags, mode_t mode) } /** - * Wrapper for chdir(2). - * - * \param path The specified directory. + * Create a directory, don't fail if it already exists. * - * \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. - * - * \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); } /** @@ -549,22 +485,21 @@ 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); } /** @@ -643,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 270d0ce2..e4f30903 100644 --- a/fd.h +++ b/fd.h @@ -5,14 +5,12 @@ 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 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); 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); @@ -21,11 +19,10 @@ int write_ok(int fd); void valid_fd_012(void); 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_pattern(int fd, const char *pattern, size_t bufsize); +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/fecdec_filter.c b/fecdec_filter.c index 1d8fbc16..375f4c0a 100644 --- a/fecdec_filter.c +++ b/fecdec_filter.c @@ -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; diff --git a/flacdec_filter.c b/flacdec_filter.c index f3060f57..fb8ebf15 100644 --- a/flacdec_filter.c +++ b/flacdec_filter.c @@ -232,7 +232,7 @@ static int flacdec_post_monitor(__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_monitor(__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) { diff --git a/gcrypt.c b/gcrypt.c index 763fa6de..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) @@ -457,10 +456,9 @@ int apc_get_pubkey(const char *key_file, struct asymmetric_key **result) } PARA_INFO_LOG("successfully read %u bit ssh public key\n", bits); key = alloc(sizeof(*key)); - key->num_bytes = ret; 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) diff --git a/gui.c b/gui.c index ebfab356..66fb7870 100644 --- a/gui.c +++ b/gui.c @@ -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) diff --git a/gui_theme.c b/gui_theme.c index f71e802b..81bbe0f6 100644 --- a/gui_theme.c +++ b/gui_theme.c @@ -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 32c9e7b9..8d2add19 100644 --- a/http_recv.c +++ b/http_recv.c @@ -105,7 +105,7 @@ static int http_recv_post_monitor(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)); + ret = read_and_compare(rn->fd, HTTP_OK_MSG); if (ret < 0) { PARA_ERROR_LOG("did not receive HTTP OK message\n"); goto out; diff --git a/http_send.c b/http_send.c index 90e3ee57..429b4662 100644 --- a/http_send.c +++ b/http_send.c @@ -170,7 +170,7 @@ static void http_post_monitor(__a_unused struct sched *s) case HTTP_STREAMING: /* nothing to do */ break; case HTTP_CONNECTED: /* need to recv get request */ - ret = read_pattern(sc->fd, HTTP_GET_MSG, MAXLINE); + ret = read_and_compare(sc->fd, HTTP_GET_MSG); if (ret < 0) phsd->status = HTTP_INVALID_GET_REQUEST; else if (ret > 0) { diff --git a/interactive.c b/interactive.c index e367a659..1376cf1d 100644 --- a/interactive.c +++ b/interactive.c @@ -287,7 +287,7 @@ static int i9e_post_monitor(__a_unused struct sched *s, __a_unused void *context 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; @@ -306,7 +306,7 @@ static int i9e_post_monitor(__a_unused struct sched *s, __a_unused void *context goto rm_btrn; } if (ret == 0) { - ret = -E_I9E_EOF; + ret = -E_EOF; goto rm_btrn; } buf[1] = '\0'; diff --git a/m4/lls/server_cmd.suite.m4 b/m4/lls/server_cmd.suite.m4 index 8200c624..02afaabb 100644 --- a/m4/lls/server_cmd.suite.m4 +++ b/m4/lls/server_cmd.suite.m4 @@ -222,6 +222,16 @@ m4_include(`com_ll.m4') 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 @@ -233,9 +243,19 @@ m4_include(`com_ll.m4') [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 @@ -407,6 +427,9 @@ m4_include(`com_ll.m4') 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/mood.c b/mood.c index 9cdfd011..1e15ef0e 100644 --- a/mood.c +++ b/mood.c @@ -59,6 +59,8 @@ struct mood_instance { 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; }; /* @@ -132,6 +134,8 @@ 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); } @@ -437,7 +441,7 @@ static void update_afs_statistics(struct afs_info *old_afsi, } static int add_to_score_table(const struct osl_row *aft_row, - const struct afs_statistics *stats) + struct mood_instance *m) { long score; struct afs_info afsi; @@ -445,8 +449,8 @@ static int add_to_score_table(const struct osl_row *aft_row, if (ret < 0) return ret; - score = compute_score(&afsi, stats); - 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) @@ -504,7 +508,7 @@ static int mood_update_audio_file(const struct osl_row *aft_row, ret = add_afs_statistics(aft_row, ¤t_mood->stats); if (ret < 0) return ret; - return add_to_score_table(aft_row, ¤t_mood->stats); + return add_to_score_table(aft_row, current_mood); } /* update score */ ret = get_afsi_of_row(aft_row, &afsi); @@ -529,24 +533,37 @@ static char *get_statistics(struct mood_instance *m, int64_t sse) unsigned n = m->stats.num; int mean_days, sigma_days; + 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) + int_sqrt(m->stats.num_played_qd / n), + 86400.0 * m->stats.last_played_correction / + m->stats.num_played_correction ); } -/** Free all resources of the current mood, if any. */ -void mood_unload(void) +/** + * Free all resources of a mood instance. + * + * \param m As obtained by \ref mood_load(). If NULL, unload the current mood. + * + * It's OK to call this with m == NULL even if no current mood is loaded. + */ +void mood_unload(struct mood_instance *m) { + if (m) + return destroy_mood(m); destroy_mood(current_mood); current_mood = NULL; } @@ -568,23 +585,42 @@ static void compute_correction_factors(int64_t sse, struct afs_statistics *s) } /** - * Change the current mood. + * Populate a score table with admissible files for the given mood. + * + * This consults the mood table to initialize the mood parser with the mood + * expression stored in the blob object which corresponds to the given name. A + * score table is allocated and populated with references to those entries of + * the audio file table which evaluate as admissible with respect to the mood + * expression. For each audio file a score value is computed and stored along + * with the file reference. * * \param mood_name The name of the mood to load. + * \param result Opaque, refers to the mood parser and the score table. * \param msg Error message or mood info is returned here. * - * 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. + * If the mood name is NULL, the dummy mood is loaded. This mood regards every + * audio file as admissible. + * + * 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. + * + * 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. * - * \return The number of admissible files on success, negative on errors. It is + * \return The number of admissible files on success, negative on errors. On + * errors, the current mood remains unaffected even if result is NULL. It is * not considered an error if no files are admissible. * - * \sa struct \ref afs_info::last_played, \ref mp_eval_row(). + * \sa \ref mp_eval_row(). */ -int mood_load(const char *mood_name, char **msg) +int mood_load(const char *mood_name, struct mood_instance **result, char **msg) { int i, ret; struct admissible_array aa = {.size = 0}; @@ -609,14 +645,10 @@ int mood_load(const char *mood_name, char **msg) } clock_get_realtime(&rnow); compute_correction_factors(rnow.tv_sec, &aa.m->stats); - if (aa.m->stats.num == 0) { - if (msg) - *msg = make_message("no admissible files\n"); - ret = 0; - goto out; - } + 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->stats); + ret = add_to_score_table(aa.array[i], aa.m); if (ret < 0) { if (msg) *msg = make_message( @@ -628,21 +660,43 @@ int mood_load(const char *mood_name, char **msg) if (msg) *msg = get_statistics(aa.m, rnow.tv_sec); ret = aa.m->stats.num; - mood_unload(); - current_mood = aa.m; + if (result) + *result = aa.m; + else { + mood_unload(NULL); + current_mood = aa.m; + } + ret = 1; out: free(aa.array); - if (ret < 0) + if (ret <= 0) /* error, or no admissible files */ destroy_mood(aa.m); return ret; } +/** + * Iterate over the admissible files of a mood instance. + * + * This wrapper around \ref score_loop() is the mood counterpart of \ref + * playlist_loop(). + * + * \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 list of - * admissible files useless, for example if an attribute is removed from the - * attribute table. + * 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) { @@ -655,8 +709,8 @@ static int reload_current_mood(void) current_mood->name : "(dummy)"); if (current_mood->name) mood_name = para_strdup(current_mood->name); - mood_unload(); - ret = mood_load(mood_name, NULL); + mood_unload(NULL); + ret = mood_load(mood_name, NULL, NULL); free(mood_name); return ret; } diff --git a/mp3_afh.c b/mp3_afh.c index d7493ac0..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] = { @@ -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; } diff --git a/mp3dec_filter.c b/mp3dec_filter.c index bc8ccdaa..d40df85e 100644 --- a/mp3dec_filter.c +++ b/mp3dec_filter.c @@ -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; diff --git a/mp4.c b/mp4.c index f8515ca2..5ca1307f 100644 --- a/mp4.c +++ b/mp4.c @@ -1044,7 +1044,7 @@ free_moov: * 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. */ -char *mp4_get_tag_value(const struct mp4 *f, const char *item) +__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)) diff --git a/mp4.h b/mp4.h index 1618aa31..c36a1f81 100644 --- a/mp4.h +++ b/mp4.h @@ -84,4 +84,4 @@ 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); -char *mp4_get_tag_value(const struct mp4 *f, const char *item); +__malloc char *mp4_get_tag_value(const struct mp4 *f, const char *item); diff --git a/net.c b/net.c index a24081f5..9b362442 100644 --- a/net.c +++ b/net.c @@ -190,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; diff --git a/net.h b/net.h index d206881c..33acfc89 100644 --- a/net.h +++ b/net.h @@ -25,7 +25,7 @@ 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); -char *format_url(const char *url, int default_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, diff --git a/ogg_afh_common.c b/ogg_afh_common.c index 3a5c263c..0a27a4ac 100644 --- a/ogg_afh_common.c +++ b/ogg_afh_common.c @@ -365,7 +365,7 @@ 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 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 da70d4c0..b1aec4bc 100644 --- a/oggdec_filter.c +++ b/oggdec_filter.c @@ -211,7 +211,7 @@ static int ogg_post_monitor(__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; diff --git a/openssl.c b/openssl.c index 71849876..f696cd9e 100644 --- a/openssl.c +++ b/openssl.c @@ -37,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) { @@ -101,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; @@ -153,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; @@ -220,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; @@ -271,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: @@ -284,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 = alloc(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, diff --git a/opusdec_filter.c b/opusdec_filter.c index 85287be0..f36990fa 100644 --- a/opusdec_filter.c +++ b/opusdec_filter.c @@ -217,7 +217,7 @@ static int opusdec_post_monitor(__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; diff --git a/oss_write.c b/oss_write.c index 96d7b187..4ea85afa 100644 --- a/oss_write.c +++ b/oss_write.c @@ -218,7 +218,7 @@ static int oss_post_monitor(__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; diff --git a/play.c b/play.c index 262f69ee..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; @@ -239,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; } @@ -266,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)); @@ -1057,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); @@ -1246,10 +1237,25 @@ 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[]) { diff --git a/playlist.c b/playlist.c index d02ade3b..c145b0fd 100644 --- a/playlist.c +++ b/playlist.c @@ -14,12 +14,18 @@ /** \file playlist.c Functions for loading and saving playlists. */ -/** Structure used for adding entries to a playlist. */ +/** + * 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_instance current_playlist; @@ -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_instance *playlist = data; + struct playlist_instance *pi = data; struct osl_row *aft_row; int ret = aft_get_row_of_path(path, &aft_row); @@ -46,12 +52,12 @@ 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++; + pi->length++; return 1; } @@ -103,11 +109,22 @@ int playlist_check_callback(struct afs_callback_arg *aca) check_playlist)); } -/** Free all resources of the current playlist, if any. */ -void playlist_unload(void) +/** + * Free all resources of the given/current playlist. + * + * \param pi NULL means to unload the current playlist. + */ +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; @@ -122,43 +139,78 @@ void playlist_unload(void) * corresponding row of the audio file table is added to the score table. * * \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 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_load(const char *name, char **msg) +int playlist_load(const char *name, struct playlist_instance **result, char **msg) { int ret; - struct playlist_instance *playlist = ¤t_playlist; + struct playlist_instance *pi; struct osl_object playlist_def; - ret = pl_get_def_by_name(name, &playlist_def); - if (ret < 0) { - *msg = make_message("could not read playlist %s\n", name); - return ret; + if (!name || !*name) { + if (msg) + *msg = make_message("empty playlist name\n"); + return -ERRNO_TO_PARA_ERROR(EINVAL); } - playlist_unload(); + 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, playlist); + playlist_def.size, add_playlist_entry, pi); osl_close_disk_object(&playlist_def); if (ret < 0) - goto err; + goto close_score_table; ret = -E_PLAYLIST_EMPTY; - if (!playlist->length) - goto err; - playlist->name = para_strdup(name); - *msg = make_message("loaded playlist %s (%u files)\n", playlist->name, - playlist->length); + if (pi->length == 0) + goto close_score_table; /* success */ - return current_playlist.length; + 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; + } + 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); - *msg = make_message("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) { if (strcmp(path, data)) @@ -196,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 */ } /** diff --git a/resample_filter.c b/resample_filter.c index bf28e975..72cb3f62 100644 --- a/resample_filter.c +++ b/resample_filter.c @@ -220,7 +220,7 @@ static int resample_post_monitor(__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; diff --git a/sched.c b/sched.c index c786c9f2..20822038 100644 --- a/sched.c +++ b/sched.c @@ -62,10 +62,6 @@ static void sched_pre_monitor(struct sched *s) 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); @@ -183,6 +179,7 @@ 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 diff --git a/score.c b/score.c index 10cd254a..c03e3472 100644 --- a/score.c +++ b/score.c @@ -77,10 +77,10 @@ static struct osl_table_description score_table_desc = { }; /* 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; @@ -88,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]; @@ -112,7 +113,7 @@ int score_add(const struct osl_row *aft_row, long score) *(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); @@ -152,7 +153,7 @@ int score_update(const struct osl_row *aft_row, long percent) ret = osl(osl_get_nth_row(score_table, SCORECOL_SCORE, new_pos, &rrow)); if (ret < 0) return ret; - ret = get_score_of_row(rrow, &new_score); + ret = get_score_of_row(score_table, rrow, &new_score); if (ret < 0) return ret; new_score--; @@ -176,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; @@ -187,26 +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)); } /** * Call the given function for each row of the score table. * * \param func Callback, called once per row. + * \param t NULL means to use the currently active score table. * \param data Passed verbatim to the callback. * * \return The return value of the underlying call to osl_rbtree_loop(). The * loop terminates early if the callback returns negative. */ -int score_loop(osl_rbtree_loop_func *func, void *data) +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)); } /** @@ -229,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); } /** @@ -244,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; @@ -263,7 +266,7 @@ int score_delete(const struct osl_row *aft_row) 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 false; @@ -271,29 +274,56 @@ bool row_belongs_to_score_table(const struct osl_row *aft_row) 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) +{ + assert(osl_close_table(t? t : score_table, OSL_FREE_VOLATILE) >= 0); +} + +static void close_global_table(void) { - osl_close_table(score_table, OSL_FREE_VOLATILE); - score_table = NULL; + score_close(NULL); } -static int score_open(__a_unused const char *dir) +static int open_global_table(__a_unused const char *dir) { - assert(osl_open_table(&score_table_desc, &score_table) >= 0); + assert(osl(osl_open_table(&score_table_desc, &score_table)) >= 0); return 1; } +/** + * 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) +{ + if (result) + assert(osl(osl_open_table(&score_table_desc, result)) >= 0); + else + open_global_table(NULL); +} + /** * Remove all entries from the score table, but keep the table open. */ void score_clear(void) { - score_close(); - score_open(NULL); + 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 = score_open, - .close = score_close, + .open = open_global_table, + .close = close_global_table, }; diff --git a/send.h b/send.h index dec5b0db..3407bc5c 100644 --- a/send.h +++ b/send.h @@ -196,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, @@ -204,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); +__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 26502cab..8dc82e9c 100644 --- a/send_common.c +++ b/send_common.c @@ -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; @@ -413,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/string.c b/string.c index 46b34623..d8bd027b 100644 --- a/string.c +++ b/string.c @@ -308,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); } /** @@ -739,13 +756,11 @@ void free_argv(char **argv) static int create_argv_offset(int offset, const char *buf, const char *delim, char ***result) { - char *word, **argv = arr_alloc(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; diff --git a/sync_filter.c b/sync_filter.c index b4710f67..20db1b1d 100644 --- a/sync_filter.c +++ b/sync_filter.c @@ -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); 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 9e681107..f7a407dc 100755 --- a/t/t0004-server.sh +++ b/t/t0004-server.sh @@ -37,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' 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 365b6863..f98a9664 100644 --- a/udp_recv.c +++ b/udp_recv.c @@ -40,14 +40,14 @@ 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_monitor(__a_unused struct sched *s, void *context) diff --git a/user_list.c b/user_list.c index f2436c9e..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, diff --git a/wav_filter.c b/wav_filter.c index 7d6c3714..de4a3e6a 100644 --- a/wav_filter.c +++ b/wav_filter.c @@ -78,7 +78,7 @@ static int wav_post_monitor(__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; diff --git a/wmadec_filter.c b/wmadec_filter.c index f7ee2c4d..f2ca273c 100644 --- a/wmadec_filter.c +++ b/wmadec_filter.c @@ -1176,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) { diff --git a/write.c b/write.c index f7018aa1..cb32d391 100644 --- a/write.c +++ b/write.c @@ -104,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));